diff --git a/actions/mediawiki b/actions/mediawiki
index 44189ac50..08ad36e1c 100755
--- a/actions/mediawiki
+++ b/actions/mediawiki
@@ -48,6 +48,10 @@ def parse_arguments():
help='Set the default skin')
default_skin.add_argument('skin', help='name of the skin')
+ server_url = subparsers.add_parser(
+ 'set-server-url', help='Set the value of $wgServer for this server')
+ server_url.add_argument('server_url', help='value of $wgServer')
+
subparsers.required = True
return parser.parse_args()
@@ -218,28 +222,37 @@ def subcommand_private_mode(arguments):
conf_value + '\n')
-def subcommand_set_default_skin(arguments):
- """Set a default skin"""
- skin = arguments.skin
- skin_setting = f'$wgDefaultSkin = "{skin}";\n'
-
+def _update_setting(setting_name, setting_line):
+ """Update the value of one setting in the config file."""
with open(CONF_FILE, 'r') as conf_file:
lines = conf_file.readlines()
inserted = False
for i, line in enumerate(lines):
- if line.strip().startswith('$wgDefaultSkin'):
- lines[i] = skin_setting
+ if line.strip().startswith(setting_name):
+ lines[i] = setting_line
inserted = True
break
if not inserted:
- lines.append(skin_setting)
+ lines.append(setting_line)
with open(CONF_FILE, 'w') as conf_file:
conf_file.writelines(lines)
+def subcommand_set_default_skin(arguments):
+ """Set a default skin."""
+ skin = arguments.skin
+ _update_setting('$wgDefaultSkin ', f'$wgDefaultSkin = "{skin}";\n')
+
+
+def subcommand_set_server_url(arguments):
+ """Set the value of $wgServer for this MediaWiki server."""
+ # This is a required setting from MediaWiki 1.34
+ _update_setting('$wgServer', f'$wgServer = "{arguments.server_url}";\n')
+
+
def main():
"""Parse arguments and perform all duties."""
arguments = parse_arguments()
diff --git a/plinth/modules/mediawiki/__init__.py b/plinth/modules/mediawiki/__init__.py
index a588f9834..95925da93 100644
--- a/plinth/modules/mediawiki/__init__.py
+++ b/plinth/modules/mediawiki/__init__.py
@@ -4,6 +4,7 @@ FreedomBox app to configure MediaWiki.
"""
import re
+from urllib.parse import urlparse
from django.utils.translation import ugettext_lazy as _
@@ -16,7 +17,7 @@ from plinth.modules.firewall.components import Firewall
from .manifest import backup, clients # noqa, pylint: disable=unused-import
-version = 8
+version = 9
managed_packages = ['mediawiki', 'imagemagick', 'php-sqlite3']
@@ -39,6 +40,9 @@ _description = [
app = None
+STATIC_CONFIG_FILE = '/etc/mediawiki/FreedomBoxStaticSettings.php'
+USER_CONFIG_FILE = '/etc/mediawiki/FreedomBoxSettings.php'
+
class MediaWikiApp(app_module.App):
"""FreedomBox app for MediaWiki."""
@@ -109,24 +113,44 @@ def is_public_registration_enabled():
def is_private_mode_enabled():
- """ Return whether private mode is enabled or disabled"""
+ """Return whether private mode is enabled or disabled."""
output = actions.superuser_run('mediawiki', ['private-mode', 'status'])
return output.strip() == 'enabled'
-def get_default_skin():
- """Return the value of the default skin"""
-
- def _find_skin(config_file):
- with open(config_file, 'r') as config:
- for line in config:
- if line.startswith('$wgDefaultSkin'):
- return re.findall(r'["\'][^"\']*["\']',
- line)[0].strip('"\'')
+def _get_config_value_in_file(setting_name, config_file):
+ """Return the value of a setting from a config file."""
+ with open(config_file, 'r') as config:
+ for line in config:
+ if line.startswith(setting_name):
+ return re.findall(r'["\'][^"\']*["\']', line)[0].strip('"\'')
return None
- user_config = '/etc/mediawiki/FreedomBoxSettings.php'
- static_config = '/etc/mediawiki/FreedomBoxStaticSettings.php'
- return _find_skin(user_config) or _find_skin(static_config)
+def _get_config_value(setting_name):
+ """Return a configuration value from multiple configuration files."""
+ return _get_config_value_in_file(setting_name, USER_CONFIG_FILE) or \
+ _get_config_value_in_file(setting_name, STATIC_CONFIG_FILE)
+
+
+def get_default_skin():
+ """Return the value of the default skin."""
+ return _get_config_value('$wgDefaultSkin')
+
+
+def set_default_skin(skin):
+ """Set the value of the default skin."""
+ actions.superuser_run('mediawiki', ['set-default-skin', skin])
+
+
+def get_server_url():
+ """Return the value of the server URL."""
+ server_url = _get_config_value('$wgServer')
+ return urlparse(server_url).netloc
+
+
+def set_server_url(server_url):
+ """Set the value of $wgServer."""
+ actions.superuser_run('mediawiki',
+ ['set-server-url', f'https://{server_url}'])
diff --git a/plinth/modules/mediawiki/data/etc/mediawiki/FreedomBoxStaticSettings.php b/plinth/modules/mediawiki/data/etc/mediawiki/FreedomBoxStaticSettings.php
index 70f33f2a1..53cb45859 100644
--- a/plinth/modules/mediawiki/data/etc/mediawiki/FreedomBoxStaticSettings.php
+++ b/plinth/modules/mediawiki/data/etc/mediawiki/FreedomBoxStaticSettings.php
@@ -38,3 +38,6 @@ $wgSessionCacheType = CACHE_DB;
# Use the mobile-friendly skin Timeless by default
$wgDefaultSkin = "timeless";
+
+# Domain Name
+$wgServer = "https://freedombox.local";
diff --git a/plinth/modules/mediawiki/forms.py b/plinth/modules/mediawiki/forms.py
index 9c4823998..2c3f75812 100644
--- a/plinth/modules/mediawiki/forms.py
+++ b/plinth/modules/mediawiki/forms.py
@@ -6,6 +6,8 @@ FreedomBox app for configuring MediaWiki.
import pathlib
from django import forms
+from django.forms import Widget
+from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
@@ -19,6 +21,29 @@ def get_skins():
if skin.is_dir()]
+class PrependWidget(Widget):
+ """Widget to create input-groups with prepended text."""
+
+ def __init__(self, base_widget, data, *args, **kwargs):
+ """Initialize widget and get base instance"""
+ super(PrependWidget, self).__init__(*args, **kwargs)
+ self.base_widget = base_widget(*args, **kwargs)
+ self.data = data
+
+ def render(self, name, value, attrs=None, renderer=None):
+ """Render base widget and add bootstrap spans."""
+ attrs['class'] = 'form-control'
+ field = self.base_widget.render(name, value, attrs, renderer)
+ widget_html = '''
+
+
+ %(data)s
+
+ %(field)s
+
'''
+ return mark_safe((widget_html) % {'field': field, 'data': self.data})
+
+
class MediaWikiForm(forms.Form): # pylint: disable=W0232
"""MediaWiki configuration form."""
password = forms.CharField(
@@ -27,6 +52,12 @@ class MediaWikiForm(forms.Form): # pylint: disable=W0232
'(admin). Leave this field blank to keep the current password.'),
required=False, widget=forms.PasswordInput)
+ server_url = forms.CharField(
+ label=_('Server URL'), required=False, help_text=_(
+ 'Used by MediaWiki to generate URLs that point to the wiki '
+ 'such as in footer, feeds and emails.'),
+ widget=PrependWidget(base_widget=forms.TextInput, data='https://'))
+
enable_public_registrations = forms.BooleanField(
label=_('Enable public registrations'), required=False,
help_text=_('If enabled, anyone on the internet will be able to '
diff --git a/plinth/modules/mediawiki/tests/conftest.py b/plinth/modules/mediawiki/tests/conftest.py
new file mode 100644
index 000000000..72729133a
--- /dev/null
+++ b/plinth/modules/mediawiki/tests/conftest.py
@@ -0,0 +1,53 @@
+# SPDX-License-Identifier: AGPL-3.0-or-later
+"""
+Common test fixtures for MediaWiki.
+"""
+
+import shutil
+import importlib
+import pathlib
+import types
+from unittest.mock import patch
+
+import pytest
+
+current_directory = pathlib.Path(__file__).parent
+
+
+def _load_actions_module():
+ actions_file_path = str(current_directory / '..' / '..' / '..' / '..' /
+ 'actions' / 'mediawiki')
+ loader = importlib.machinery.SourceFileLoader('mediawiki',
+ actions_file_path)
+ module = types.ModuleType(loader.name)
+ loader.exec_module(module)
+ return module
+
+
+actions = _load_actions_module()
+
+
+@pytest.fixture(name='call_action')
+def fixture_call_action(capsys, conf_file):
+ """Run actions with custom root path."""
+
+ def _call_action(module_name, args, **kwargs):
+ actions.CONF_FILE = conf_file
+ with patch('argparse._sys.argv', [module_name] + args):
+ actions.main()
+ captured = capsys.readouterr()
+ return captured.out
+
+ return _call_action
+
+
+@pytest.fixture(name='conf_file')
+def fixture_conf_file(tmp_path):
+ """Uses a dummy configuration file."""
+ settings_file_name = 'FreedomBoxSettings.php'
+ conf_file = tmp_path / settings_file_name
+ conf_file.touch()
+ shutil.copyfile(
+ str(current_directory / '..' / 'data' / 'etc' / 'mediawiki' /
+ settings_file_name), str(conf_file))
+ return str(conf_file)
diff --git a/plinth/modules/mediawiki/tests/mediawiki.feature b/plinth/modules/mediawiki/tests/mediawiki.feature
index fd48e1297..fb2bc6f71 100644
--- a/plinth/modules/mediawiki/tests/mediawiki.feature
+++ b/plinth/modules/mediawiki/tests/mediawiki.feature
@@ -7,6 +7,7 @@ Feature: MediaWiki Wiki Engine
Background:
Given I'm a logged in user
Given the mediawiki application is installed
+ Given the server url is set to test config url
Scenario: Enable mediawiki application
Given the mediawiki application is disabled
diff --git a/plinth/modules/mediawiki/tests/test_functional.py b/plinth/modules/mediawiki/tests/test_functional.py
index 626dfbd1b..48654a0ac 100644
--- a/plinth/modules/mediawiki/tests/test_functional.py
+++ b/plinth/modules/mediawiki/tests/test_functional.py
@@ -4,14 +4,21 @@ Functional, browser based tests for mediawiki app.
"""
import pathlib
+from urllib.parse import urlparse
-from pytest_bdd import parsers, scenarios, then, when
+from pytest_bdd import given, parsers, scenarios, then, when
from plinth.tests import functional
+from plinth.tests.functional import config
scenarios('mediawiki.feature')
+@given(parsers.parse('the server url is set to test config url'))
+def set_server_url(session_browser):
+ _set_server_url(session_browser)
+
+
@when(parsers.parse('I enable mediawiki public registrations'))
def enable_mediawiki_public_registrations(session_browser):
_enable_public_registrations(session_browser)
@@ -57,8 +64,7 @@ def mediawiki_allows_anonymous_reads_edits(session_browser):
@then(
parsers.parse(
'the mediawiki site should not allow anonymous reads and writes'))
-def mediawiki_does_not_allow__account_creation_anonymous_reads_edits(
- session_browser):
+def mediawiki_does_not_allow_anonymous_reads_edits(session_browser):
_verify_no_anonymous_reads_edits_link(session_browser)
@@ -216,3 +222,11 @@ def __has_main_page(browser):
functional.visit(browser, '/mediawiki/Main_Page')
content = browser.find_by_id('mw-content-text').first
return 'This page has been deleted.' not in content.text
+
+
+def _set_server_url(browser):
+ """Set the value of server url to the value in the given env_var."""
+ functional.nav_to_module(browser, 'mediawiki')
+ server_url = urlparse(config['DEFAULT']['url']).netloc
+ browser.find_by_id('id_server_url').fill(server_url)
+ functional.submit(browser, form_class='form-configuration')
diff --git a/plinth/modules/mediawiki/tests/test_settings.py b/plinth/modules/mediawiki/tests/test_settings.py
new file mode 100644
index 000000000..b129590e9
--- /dev/null
+++ b/plinth/modules/mediawiki/tests/test_settings.py
@@ -0,0 +1,46 @@
+# SPDX-License-Identifier: AGPL-3.0-or-later
+"""
+Test module for MediaWiki utility functions.
+"""
+
+import pathlib
+from unittest.mock import patch
+
+import pytest
+
+from plinth.modules import mediawiki
+
+
+@pytest.fixture(name='test_configuration', autouse=True)
+def fixture_test_configuration(call_action, conf_file):
+ """Use a separate MediaWiki configuration for tests.
+
+ Uses local FreedomBoxStaticSettings.php, a temp version of
+ FreedomBoxSettings.php and patches actions.superuser_run with the fixture
+ call_action
+
+ """
+ data_directory = pathlib.Path(__file__).parent.parent / 'data'
+ static_config_file = str(data_directory / 'etc' / 'mediawiki' /
+ mediawiki.STATIC_CONFIG_FILE.split('/')[-1])
+ with patch('plinth.modules.mediawiki.STATIC_CONFIG_FILE',
+ static_config_file), \
+ patch('plinth.modules.mediawiki.USER_CONFIG_FILE', conf_file), \
+ patch('plinth.actions.superuser_run', call_action):
+ yield
+
+
+def test_default_skin():
+ """Test getting and setting the default skin."""
+ assert mediawiki.get_default_skin() == 'timeless'
+ new_skin = 'vector'
+ mediawiki.set_default_skin(new_skin)
+ assert mediawiki.get_default_skin() == new_skin
+
+
+def test_server_url():
+ """Test getting and setting $wgServer."""
+ assert mediawiki.get_server_url() == 'freedombox.local'
+ new_server_url = 'mydomain.freedombox.rocks'
+ mediawiki.set_server_url(new_server_url)
+ assert mediawiki.get_server_url() == new_server_url
diff --git a/plinth/modules/mediawiki/views.py b/plinth/modules/mediawiki/views.py
index 0abacb969..f26d15650 100644
--- a/plinth/modules/mediawiki/views.py
+++ b/plinth/modules/mediawiki/views.py
@@ -11,7 +11,7 @@ from django.utils.translation import ugettext as _
from plinth import actions, views
from plinth.modules import mediawiki
-from . import (get_default_skin, is_private_mode_enabled,
+from . import (get_default_skin, get_server_url, is_private_mode_enabled,
is_public_registration_enabled)
from .forms import MediaWikiForm
@@ -30,7 +30,8 @@ class MediaWikiAppView(views.AppView):
initial.update({
'enable_public_registrations': is_public_registration_enabled(),
'enable_private_mode': is_private_mode_enabled(),
- 'default_skin': get_default_skin()
+ 'default_skin': get_default_skin(),
+ 'server_url': get_server_url()
})
return initial
@@ -39,15 +40,15 @@ class MediaWikiAppView(views.AppView):
old_config = self.get_initial()
new_config = form.cleaned_data
- def is_unchanged(key):
- return old_config[key] == new_config[key]
+ def is_changed(key):
+ return old_config.get(key) != new_config.get(key)
if new_config['password']:
actions.superuser_run('mediawiki', ['change-password'],
input=new_config['password'].encode())
messages.success(self.request, _('Password updated'))
- if not is_unchanged('enable_public_registrations'):
+ if is_changed('enable_public_registrations'):
# note action public-registration restarts, if running now
if new_config['enable_public_registrations']:
if not new_config['enable_private_mode']:
@@ -65,7 +66,7 @@ class MediaWikiAppView(views.AppView):
messages.success(self.request,
_('Public registrations disabled'))
- if not is_unchanged('enable_private_mode'):
+ if is_changed('enable_private_mode'):
if new_config['enable_private_mode']:
actions.superuser_run('mediawiki', ['private-mode', 'enable'])
messages.success(self.request, _('Private mode enabled'))
@@ -80,9 +81,12 @@ class MediaWikiAppView(views.AppView):
shortcut = mediawiki.app.get_component('shortcut-mediawiki')
shortcut.login_required = new_config['enable_private_mode']
- if not is_unchanged('default_skin'):
- actions.superuser_run(
- 'mediawiki', ['set-default-skin', new_config['default_skin']])
+ if is_changed('default_skin'):
+ mediawiki.set_default_skin(new_config['default_skin'])
messages.success(self.request, _('Default skin changed'))
+ if is_changed('server_url'):
+ mediawiki.set_server_url(new_config['server_url'])
+ messages.success(self.request, _('Server URL updated'))
+
return super().form_valid(form)
diff --git a/plinth/tests/functional/__init__.py b/plinth/tests/functional/__init__.py
index e5c6f8c78..03e01a8c9 100644
--- a/plinth/tests/functional/__init__.py
+++ b/plinth/tests/functional/__init__.py
@@ -152,7 +152,10 @@ def is_available(browser, site_name):
not_404 = '404' not in browser.title
# The site might have a default path after the sitename,
# e.g /mediawiki/Main_Page
- no_redirect = browser.url.startswith(url_to_visit.strip('/'))
+ print('URL =', browser.url, url_to_visit, browser.title)
+ browser_url = browser.url.partition('://')[2]
+ url_to_visit_without_proto = url_to_visit.strip('/').partition('://')[2]
+ no_redirect = browser_url.startswith(url_to_visit_without_proto)
return not_404 and no_redirect