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 CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# CHANGELOG

## v4.0.0 – 2025-04-28
## v4.0.0 – 2025-04-29

- Version jump to 4.0.0 to align numbering with eXeLearning for consistency across related projects.
- Introduce fully integrated embedded eXeLearning editor inside Moodle, enabling content creation and editing without leaving the platform.
Expand Down
183 changes: 183 additions & 0 deletions admin/styles.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.

/**
* Admin page for managing eXeLearning styles exposed to the embedded editor.
*
* @package mod_exeweb
* @copyright 2025 eXeLearning
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/

require('../../../config.php');
require_once($CFG->libdir . '/adminlib.php');

use mod_exeweb\local\styles_service;

admin_externalpage_setup('mod_exeweb_styles');

$context = \context_system::instance();
require_capability('moodle/site:config', $context);
require_capability('mod/exeweb:manageembeddededitor', $context);

$action = optional_param('action', '', PARAM_ALPHA);
$returnurl = new moodle_url('/mod/exeweb/admin/styles.php');
$settingsurl = new moodle_url('/admin/settings.php', ['section' => 'modsettingexeweb']);

// --------------------------------------------------------------------
// Toggle/delete actions use GET + sesskey (simple URL handlers).
// Upload happens inline in the plugin settings page.
// --------------------------------------------------------------------
if ($action !== '') {
require_sesskey();
switch ($action) {
case 'toggleuploaded':
$slug = required_param('slug', PARAM_TEXT);
$enabled = (bool) required_param('enabled', PARAM_INT);
styles_service::set_uploaded_enabled($slug, $enabled);
redirect($returnurl);
break;

case 'togglebuiltin':
$id = required_param('id', PARAM_TEXT);
$enabled = (bool) required_param('enabled', PARAM_INT);
styles_service::set_builtin_enabled($id, $enabled);
redirect($returnurl);
break;

case 'delete':
$slug = required_param('slug', PARAM_TEXT);
styles_service::delete_uploaded($slug);
redirect($returnurl,
get_string('stylesdelete_success', 'mod_exeweb'),
null,
\core\output\notification::NOTIFY_SUCCESS
);
break;
}
}

// --------------------------------------------------------------------
// Render.
// --------------------------------------------------------------------
echo $OUTPUT->header();
echo $OUTPUT->heading(get_string('stylesmanager', 'mod_exeweb'));

if (get_config('exeweb', 'editormode') !== 'embedded') {
echo $OUTPUT->notification(get_string('stylesonlywhenembedded', 'mod_exeweb'),
\core\output\notification::NOTIFY_WARNING);
}

echo html_writer::tag('p', get_string('stylesmanager_intro', 'mod_exeweb'));

// Point admins at the inline uploader on the plugin settings page —
// the filemanager there is the single entry point for uploading styles.
echo html_writer::tag('p',
html_writer::link(
$settingsurl,
get_string('stylesupload_goto_settings', 'mod_exeweb'),
['class' => 'btn btn-secondary']
)
);

// Uploaded styles table.
$uploaded = styles_service::list_uploaded_styles();
echo $OUTPUT->heading(get_string('stylesuploaded', 'mod_exeweb'), 3);
if (empty($uploaded)) {
echo html_writer::tag('p', get_string('stylesuploaded_empty', 'mod_exeweb'), ['class' => 'text-muted']);
} else {
$table = new html_table();
$table->head = [
get_string('stylestable_title', 'mod_exeweb'),
get_string('stylestable_id', 'mod_exeweb'),
get_string('stylestable_version', 'mod_exeweb'),
get_string('stylestable_installed', 'mod_exeweb'),
get_string('stylestable_enabled', 'mod_exeweb'),
get_string('stylestable_actions', 'mod_exeweb'),
];
foreach ($uploaded as $style) {
$toggleurl = new moodle_url('/mod/exeweb/admin/styles.php', [
'action' => 'toggleuploaded',
'slug' => $style['id'],
'enabled' => empty($style['enabled']) ? 1 : 0,
'sesskey' => sesskey(),
]);
$togglelabel = empty($style['enabled'])
? get_string('stylesenable', 'mod_exeweb')
: get_string('stylesdisable', 'mod_exeweb');
$deleteurl = new moodle_url('/mod/exeweb/admin/styles.php', [
'action' => 'delete',
'slug' => $style['id'],
'sesskey' => sesskey(),
]);
$table->data[] = [
s($style['title'] ?? $style['id']),
html_writer::tag('code', s($style['id'])),
s($style['version'] ?? ''),
s($style['installed_at'] ?? ''),
html_writer::link($toggleurl, $togglelabel, ['class' => 'btn btn-secondary btn-sm']),
html_writer::link(
$deleteurl,
get_string('stylesdelete', 'mod_exeweb'),
[
'class' => 'btn btn-danger btn-sm',
'onclick' => "return confirm('"
. addslashes_js(get_string('stylesdelete_confirm', 'mod_exeweb'))
. "');",
]
),
];
}
echo html_writer::table($table);
}

// Built-in styles table.
$builtins = styles_service::list_builtin_themes();
echo $OUTPUT->heading(get_string('stylesbuiltin', 'mod_exeweb'), 3);
if (empty($builtins)) {
echo html_writer::tag('p', get_string('stylesbuiltin_empty', 'mod_exeweb'), ['class' => 'text-muted']);
} else {
$registry = styles_service::get_registry();
$disabledlist = $registry['disabled_builtins'];
$table = new html_table();
$table->head = [
get_string('stylestable_title', 'mod_exeweb'),
get_string('stylestable_id', 'mod_exeweb'),
get_string('stylestable_version', 'mod_exeweb'),
get_string('stylestable_enabled', 'mod_exeweb'),
];
foreach ($builtins as $style) {
$isdisabled = in_array($style['id'], $disabledlist, true);
$toggleurl = new moodle_url('/mod/exeweb/admin/styles.php', [
'action' => 'togglebuiltin',
'id' => $style['id'],
'enabled' => $isdisabled ? 1 : 0,
'sesskey' => sesskey(),
]);
$togglelabel = $isdisabled
? get_string('stylesenable', 'mod_exeweb')
: get_string('stylesdisable', 'mod_exeweb');
$table->data[] = [
s($style['title']),
html_writer::tag('code', s($style['id'])),
s($style['version']),
html_writer::link($toggleurl, $togglelabel, ['class' => 'btn btn-secondary btn-sm']),
];
}
echo html_writer::table($table);
}

echo $OUTPUT->footer();
131 changes: 131 additions & 0 deletions classes/admin/admin_setting_stylesbuiltins.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.

/**
* Admin setting that renders built-in styles as a list of checkboxes.
*
* @package mod_exeweb
* @copyright 2025 eXeLearning
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/

namespace mod_exeweb\admin;

defined('MOODLE_INTERNAL') || die();

use mod_exeweb\local\styles_service;

/**
* Per-built-in style enable/disable checkboxes, inline in the plugin
* settings page. Source of truth stays in the styles registry
* (`config_plugin(exeweb).styles_registry`), so the widget reads state
* from it at render time and writes back through the service.
*/
class admin_setting_stylesbuiltins extends \admin_setting {

/**
* @param string $name Setting key (used for the HTML input name).
*/
public function __construct(string $name) {
parent::__construct(
$name,
get_string('stylesbuiltin', 'mod_exeweb'),
get_string('stylesbuiltin_hint', 'mod_exeweb'),
[]
);
}

/**
* This setting does not live in $CFG; the registry owns the state.
*
* @return array
*/
public function get_setting() {
return [];
}

/**
* @return array
*/
public function get_defaultsetting() {
return [];
}

/**
* Persist checkbox state back into the registry.
*
* @param array $data Posted value map (slug => '1' when checked).
* @return string Empty on success.
*/
public function write_setting($data) {
if (!is_array($data)) {
$data = [];
}
foreach (styles_service::list_builtin_themes() as $theme) {
$id = $theme['id'];
$enabled = !empty($data[$id]);
styles_service::set_builtin_enabled($id, $enabled);
}
return '';
}

/**
* Render the checkbox list.
*
* @param mixed $data Unused — current state comes from the registry.
* @param string $query Current admin search query.
* @return string
*/
public function output_html($data, $query = '') {
$registry = styles_service::get_registry();
$disabled = $registry['disabled_builtins'];
$builtins = styles_service::list_builtin_themes();
if (empty($builtins)) {
$html = \html_writer::tag('p',
get_string('stylesbuiltin_empty', 'mod_exeweb'),
['class' => 'text-muted']
);
return format_admin_setting($this, $this->visiblename, $html, $this->description);
}
$rows = '';
foreach ($builtins as $theme) {
$id = $theme['id'];
$checked = in_array($id, $disabled, true) ? '' : 'checked';
$inputname = $this->get_full_name() . '[' . $id . ']';
$version = !empty($theme['version'])
? ' <span class="text-muted small">v' . s($theme['version']) . '</span>'
: '';
$rows .= '<li style="margin-bottom:.25em;">'
. '<label>'
. '<input type="checkbox" name="' . s($inputname) . '" value="1" ' . $checked . '> '
. s($theme['title'])
. $version
. ' <code class="text-muted small">' . s($id) . '</code>'
. '</label>'
. '</li>';
}
// Hidden sentinel so the form always posts the parent name. Without
// it admin_find_write_settings() skips write_setting() when every
// checkbox is cleared, so disabling all builtins from the form
// would leave them silently re-enabled.
$sentinel = '<input type="hidden" name="' . s($this->get_full_name())
. '[__sentinel]" value="1">';
$html = $sentinel
. '<ul class="mod_exeweb-styles-builtins list-unstyled" '
. 'style="list-style:none;padding:0;margin:0;">' . $rows . '</ul>';
return format_admin_setting($this, $this->visiblename, $html, $this->description);
}
}
Loading
Loading