Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Mergin/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,7 @@ def set_current_workspace(self, workspace):
iface.messageBar().pushMessage(
"Mergin Maps",
"Your attention is required. Please visit the "
f"<a href='{self.mc.url}/dashboard?utm_source=plugin&utm_medium=attention-required'>"
f"<a href='{self.mc.url}/dashboard?utm_source=plugin&utm_medium=attention-required&workspace={workspace_id}'>"
"Mergin dashboard</a>",
level=Qgis.Critical,
duration=0,
Expand Down
18 changes: 13 additions & 5 deletions Mergin/project_settings_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
set_tracking_layer_flags,
is_experimental_plugin_enabled,
remove_prefix,
invalid_filename_character,
)

ui_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), "ui", "ui_project_config.ui")
Expand Down Expand Up @@ -224,14 +225,21 @@ def update_preview(self, expression, layer, field_name):
exp = QgsExpression(expression)
exp.prepare(context)
if exp.hasParserError():
self.label_preview.setText(f"<i>{exp.parserErrorString()}</i>")
self.label_preview.setText(f"{exp.parserErrorString()}")
return

val = exp.evaluate(context)
if exp.hasEvalError():
self.label_preview.setText(f"<i>{exp.evalErrorString()}</i>")
self.label_preview.setText(f"{exp.evalErrorString()}")
return

if val:
# check if evaluated expression contains invalid filename characters
invalid_char = invalid_filename_character(val)
if invalid_char:
self.label_preview.setText(
f"The file name '{val}.jpg' contains an invalid character. Do not use '{invalid_char}' character in the file name."
)
return
config = layer.fields().field(field_name).editorWidgetSetup().config()
target_dir = resolve_target_dir(layer, config)
prefix = prefix_for_relative_path(
Expand All @@ -240,9 +248,9 @@ def update_preview(self, expression, layer, field_name):
target_dir,
)
if prefix:
self.label_preview.setText(f"<i>{remove_prefix(prefix, QgsProject.instance().homePath())}/{val}.jpg</i>")
self.label_preview.setText(f"{remove_prefix(prefix, QgsProject.instance().homePath())}/{val}.jpg")
else:
self.label_preview.setText(f"<i>{val}.jpg</i>")
self.label_preview.setText(f"{val}.jpg")

def check_project(self, state):
"""
Expand Down
9 changes: 7 additions & 2 deletions Mergin/ui/ui_project_config.ui
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,11 @@
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<italic>true</italic>
</font>
</property>
<property name="text">
<string/>
</property>
Expand All @@ -214,14 +219,14 @@
<item row="4" column="1">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Preview</string>
<string>Preview:</string>
</property>
</widget>
</item>
<item row="1" column="1" colspan="2">
<widget class="QLabel" name="label_6">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Photo name format&lt;/span&gt;&lt;/p&gt;&lt;p&gt;Set up custom photo names format based on an expression. Make sure that the name is unique for each photo.&lt;/p&gt;&lt;p&gt;Pro tip: use variables like &lt;span style=&quot; font-family:'Noto Sans Mono';&quot;&gt;@mm_user_email&lt;/span&gt;, &lt;span style=&quot; font-family:'Noto Sans Mono';&quot;&gt;@layer_name&lt;/span&gt; or layer fields in combination with &lt;span style=&quot; font-family:'Noto Sans Mono';&quot;&gt;now()&lt;/span&gt; (to get the current time) in order to generate unique photo names.&lt;/p&gt;&lt;p&gt;For further details, please refer to &lt;a href=&quot;https://merginmaps.com/docs/layer/settingup_forms_photo/#customising-photo-name-format-with-expressions&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#1d99f3;&quot;&gt;our documentation&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Photo name format&lt;/span&gt;&lt;/p&gt;&lt;p&gt;Set up custom photo names format based on an expression. Make sure that the name is unique for each photo.&lt;/p&gt;&lt;p&gt;Pro tip: use variables like &lt;span style=&quot; font-family:'Noto Sans Mono';&quot;&gt;@mm_user_email&lt;/span&gt;, &lt;span style=&quot; font-family:'Noto Sans Mono';&quot;&gt;@layer_name&lt;/span&gt; or layer fields in combination with &lt;span style=&quot; font-family:'Noto Sans Mono';&quot;&gt;now()&lt;/span&gt; (to get the current time) in order to generate unique photo names.&lt;/p&gt;&lt;p&gt;For further details, please refer to &lt;a href=&quot;https://merginmaps.com/docs/layer/settingup_forms_photo/#customising-photo-name-format-with-expressions&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#1d99f3;&quot;&gt;our documentation&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;File names may include letters (A–Z, a–z), numbers (0–9), spaces, dashes (-), and underscores (_).&lt;br&gt;Use '/' only to separate subfolders.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
Expand Down
9 changes: 9 additions & 0 deletions Mergin/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -1656,3 +1656,12 @@ def is_experimental_plugin_enabled() -> bool:
else:
value = settings.value("plugin-manager/allow-experimental", False)
return value


def invalid_filename_character(filename: str) -> str:
"""Returns invalid character for the filename"""
illegal_filename_chars = re.compile(r'[\x00-\x19<>:|?*"]')

match = illegal_filename_chars.search(filename)
if match:
return match.group()
15 changes: 13 additions & 2 deletions Mergin/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,10 @@
QGIS_NET_PROVIDERS,
is_versioned_file,
get_layer_by_path,
invalid_filename_character,
)

INVALID_CHARS = re.compile('[\\\/\(\)\[\]\{\}"\n\r]')
INVALID_FIELD_NAME_CHARS = re.compile('[\\\/\(\)\[\]\{\}"\n\r]')
PROJECT_VARS = re.compile("\@project_home|\@project_path|\@project_folder")


Expand Down Expand Up @@ -62,6 +63,7 @@ class Warning(Enum):
EDITOR_JSON_CONFIG_CHANGE = 26
EDITOR_DIFFBASED_FILE_REMOVED = 27
PROJECT_HOME_PATH = 28
INVALID_ADDED_FILENAME = 29


class MultipleLayersWarning:
Expand Down Expand Up @@ -128,6 +130,7 @@ def run_checks(self):
self.check_datum_shift_grids()
self.check_svgs_embedded()
self.check_editor_perms()
self.check_filenames()

return self.issues

Expand Down Expand Up @@ -345,7 +348,7 @@ def check_field_names(self):
if dp.storageType() == "GPKG":
fields = layer.fields()
for f in fields:
if INVALID_CHARS.search(f.name()):
if INVALID_FIELD_NAME_CHARS.search(f.name()):
self.issues.append(SingleLayerWarning(lid, Warning.INCORRECT_FIELD_NAME))

def check_snapping(self):
Expand Down Expand Up @@ -452,6 +455,12 @@ def check_editor_perms(self):
url = f"reset_file?layer={path}"
self.issues.append(SingleLayerWarning(layer.id(), Warning.EDITOR_DIFFBASED_FILE_REMOVED, url))

def check_filenames(self):
"""Checks that files to upload have valid filenames. Otherwise, push will be refused by the server."""
for file in self.changes["added"]:
if invalid_filename_character(file["path"]):
self.issues.append(MultipleLayersWarning(Warning.INVALID_ADDED_FILENAME, file["path"]))


def warning_display_string(warning_id, url=None):
"""Returns a display string for a corresponding warning"""
Expand Down Expand Up @@ -516,3 +525,5 @@ def warning_display_string(warning_id, url=None):
return f"You don't have permission to remove this layer. <a href='{url}'>Reset the layer</a> to be able to sync changes."
elif warning_id == Warning.PROJECT_HOME_PATH:
return "QGIS Project Home Path is specified. <a href='fix_project_home_path'>Quick fix the issue. (This will unset project home)</a>"
elif warning_id == Warning.INVALID_ADDED_FILENAME:
return f"You cannot synchronize a file with invalid characters in it's name. Please sanitize the name of this file '{url}'"
Loading