Crowdfunding a moodle-plugin to show and modify TiddlyWikis within moodle as an "activity"

Dear Educators and Developers,
many of us use TW as a part of their educational work. Since in schools in the last years the importance of LMS has risen and more and more pressure is put to stay within these fenced gardens, it is of vital importance to enhance the compatibility of TW with moodle - or as I suggest here the comatibility of moodle with TW by developping a moodle plugin to host TW .

Since moodle relies on a modular PHP framework and store.php was one of the first ways to save to a server this should be doable. Basically this would mean to modify our store php and replace the athentification mechanism with something that uses the moodle-user-rights.

store.php - modified for moodle and updated to comply with newer php and renamed.tid to be able to store it here (7.8 KB)

As a start I put a link here to
the moodle documentation

and development tutorial in youtube.

it would be fine if the plugin started with an empty index.html optimized for education. (We had a classroomscreen discussion some time ago and I am still working on that part…) but had the possibilities to upload/drag n’ drop an existing TW there to replace it.

It would be practical to launch the TW in Moodle in a new tab.

Since the most developers in this community do not seem to rely on moodle, I guess it is usefull to start a crowdfunding for this. As already said, I would put the first 250 €uros to that jar.

Who would participate in a crowd funding - or volunteer for the development?

4 Likes

This would be an excellent candidate to add as a project for community based fundraising on OpenCollective.

@JanJo If you are willing to help administer the project, I suggest writing up the initial scope and clearly defining the desired features and interaction with Moodle. Once that is in place, post it to the OpenCollective category here requesting that this be added as a fundraising goal.

Part of the challenge will be estimating the initial cost of the work and setting a fundraising goal. Software development can be significantly more expensive than many community members expect, see Expectations of costs for a discussion around this. This project would be best approached from the perspective of fundraising to support a developer that has their own interest in working on the Moodle integration, rather than thinking in terms of outright hiring someone for the implementation.

3 Likes

Thanks @saqimtiaz I will see if I can manage to do that.
Do you think the global approach to use the store.php is correct?

I would recommend focusing on the minimum features that are needed in terms of integration with Moodle, and not on the technical implementation. That is a later detail that will need to be figured out with some study of both Moodle and store.php.

Such a project may easily spawn other integrations as similar techniques may be possible for Moodle, WordPress, Drupal etc… basicaly you may want to link, reference or iframe TiddlyWiki’s as content within the target platforms. Many of these platforms sit on top of php servers. For this I have used GitHub - sendwheel/tw-receiver: TiddlyWiki Plugin - save to PHP server.

I entered the following prompt to piasso.xyz
write a moodle-plugin that allows to upload a TiddlyWiki-file via form or drag and drop, then allow to open the TiddlyWiki in an IFrame or in Fullscreen mode and to save the edits made in the file based on bidix’ store.php file and the rights the user has in moodle

Could this work or is this just a halucination?

<?php

/* 
 * This Moodle plugin allows users to upload a TiddlyWiki file and view it in
 * an IFrame or Fullscreen mode. Users can also edit the file and save their changes.
 * 
 * The plugin uses bidix's store.php file to save edits and relies on Moodle's 
 * user permissions to determine whether the user has the right to edit the file.
 */

// Define plugin capabilities.
$capabilities = array(
    'local/tiddlywiki:upload' => array(
        'captype' => 'write',
        'contextlevel' => CONTEXT_SYSTEM,
        'archetypes' => array(
            'user' => CAP_ALLOW
        )
    ),
    'local/tiddlywiki:edit' => array(
        'captype' => 'write',
        'contextlevel' => CONTEXT_SYSTEM,
        'archetypes' => array(
            'user' => CAP_ALLOW
        )
    )
);

// Register capabilities with Moodle.
foreach ($capabilities as $capability => $value) {
    $DB->insert_record('capabilities', (object) array(
        'name' => $capability,
        'capability' => $capability,
        'description' => '',
        'archetype' => 'user',
        'contextlevel' => $value['contextlevel'],
        'component' => 'local_tiddlywiki'
    ));
}

// Define plugin functions for uploading and viewing TiddlyWiki files.
function tiddlywiki_upload($courseid, $file) {
    // Check if user has upload capability.
    if (!has_capability('local/tiddlywiki:upload', context_system::instance())) {
        throw new moodle_exception('nopermission', 'error', '', get_string('nopermission'));
    }
    
    // Save uploaded file to Moodle data directory.
    $fs = get_file_storage();
    $context = context_system::instance();
    $file = $fs->create_file_from_pathname(array(
        'contextid' => $context->id,
        'component' => 'local_tiddlywiki',
        'filearea' => 'tiddlywikis',
        'itemid' => $courseid
    ), $file);
    
    // Return path of saved file.
    return $file->get_filepath();
}

function tiddlywiki_view($courseid, $path, $fullscreen = false) {
    // Check if user has view capability.
    if (!has_capability('moodle/course:view', context_course::instance($courseid))) {
        throw new moodle_exception('nopermission', 'error', '', get_string('nopermission'));
    }
    
    // Build HTML for IFrame or fullscreen view.
    if ($fullscreen) {
        $html = "<html><head><title>TiddlyWiki</title></head><body><div id='wrapper'><embed src='$path' type='application/octet-stream' width='100%' height='100%'/></div></body></html>";
    } else {
        $html = "<html><head><title>TiddlyWiki</title></head><body><div id='wrapper'><iframe src='$path' width='100%' height='100%' frameborder='0'></iframe></div></body></html>";
    }
    
    // Print HTML and exit.
    echo $html;
    exit;
}

// Define plugin function for saving TiddlyWiki edits.
function tiddlywiki_save($courseid, $path, $params) {
    // Check if user has edit capability.
    if (!has_capability('local/tiddlywiki:edit', context_system::instance())) {
        throw new moodle_exception('nopermission', 'error', '', get_string('nopermission'));
    }
    
    // Get contents of TiddlyWiki file.
    $fs = get_file_storage();
    $context = context_system::instance();
    $file = $fs->get_file($context->id, 'local_tiddlywiki', 'tiddlywikis', $courseid, '/', $path);
    $contents = $file->get_content();
    
    // Apply changes based on params.
    $changes = json_decode($params['changes'], true);
    foreach ($changes as $change) {
        $contents = str_replace($change['original'], $change['modified'], $contents);
    }
    
    // Save updated contents to bidix's store.php file.
    $params['contents'] = $contents;
    $params['ts'] = time();
    $store = new store();
    $store->store_tiddler($params);
}

?>

piasso explans:
The above code is a PHP script that defines a Moodle plugin that allows users to upload and view TiddlyWiki files in Moodle. The plugin also allows users to edit the file and save their changes based on bidix’s store.php file.

  • The plugin first defines the capabilities required for uploading and editing TiddlyWiki files, before registering them with Moodle. The tiddlywiki_upload() function handles the file upload process and saves the uploaded file to Moodle’s data directory.
  • The tiddlywiki_view() function allows users to view the uploaded file in an IFrame or fullscreen mode, depending on the fullscreen parameter.
  • The tiddlywiki_save() function handles the file editing process and saves the changes made by the user to bidix’s store.php file.

To integrate this plugin to Moodle, you will need to perform the following steps:

  • Copy the code to a new file and save it as pluginname.php in the /local/tiddlywiki directory of your Moodle instance.
  • Create a new directory called tiddlywikis in the /moodledata directory of your Moodle instance.
  • Update the capability names and descriptions to match your specific use case, if necessary.
  • Add language strings for the plugin in the appropriate language files (/lang/en/local_tiddlywiki.php for English, for example).
  • Use the Moodle plug-in installation process to install the plugin (Administration > Site administration > Plugins > Install plugins).
  • Once the plugin is installed, activate it by going to Administration > Site administration > Plugins > Local plugins > Manage local plugins and enabling the TiddlyWiki plugin.
  • You can now use the plugin by either programmatically calling its functions or through a user interface (e.g. a new block or a new activity).

Note that the above code is just a starting point, and you will need to implement additional functionality and testing to fully integrate this plugin with Moodle.

After I asked about the moodle version params piasso added them politely :

<?php
/**
 * This Moodle plugin allows users to upload a TiddlyWiki file and view it in an IFrame or Fullscreen mode.
 *
 * @package    local_tiddlywiki
 * @copyright  2022 Your Name
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */

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

$plugin->version   = 2022020400; // The current plugin version (YYYYMMDDXX).
$plugin->requires  = 2019051100; //  The minimum Moodle version needed.
$plugin->component = 'local_tiddlywiki'; // The plugin component name.

// Define plugin capabilities.
$capabilities = array(
    'local/tiddlywiki:upload' => array(
        'captype' => 'write',
        'contextlevel' => CONTEXT_SYSTEM,
        'archetypes' => array(
            'user' => CAP_ALLOW
        )
    ),
    'local/tiddlywiki:edit' => array(
        'captype' => 'write',
        'contextlevel' => CONTEXT_SYSTEM,
        'archetypes' => array(
            'user' => CAP_ALLOW
        )
    )
);

// Register capabilities with Moodle.
foreach ($capabilities as $capability => $value) {
    $DB->insert_record('capabilities', (object) array(
        'name' => $capability,
        'capability' => $capability,
        'description' => '',
        'archetype' => 'user',
        'contextlevel' => $value['contextlevel'],
        'component' => 'local_tiddlywiki'
    ));
}

// Define plugin functions for uploading and viewing TiddlyWiki files.
function tiddlywiki_upload($courseid, $file) {
    // Check if user has upload capability.
    if (!has_capability('local/tiddlywiki:upload', context_system::instance())) {
        throw new moodle_exception('nopermission', 'error', '', get_string('nopermission'));
    }
    
    // Save uploaded file to Moodle data directory.
    $fs = get_file_storage();
    $context = context_system::instance();
    $file = $fs->create_file_from_pathname(array(
        'contextid' => $context->id,
        'component' => 'local_tiddlywiki',
        'filearea' => 'tiddlywikis',
        'itemid' => $courseid
    ), $file);
    
    // Return path of saved file.
    return $file->get_filepath();
}

function tiddlywiki_view($courseid, $path, $fullscreen = false) {
    // Check if user has view capability.
    if (!has_capability('moodle/course:view', context_course::instance($courseid))) {
        throw new moodle_exception('nopermission', 'error', '', get_string('nopermission'));
    }
    
    // Build HTML for IFrame or fullscreen view.
    if ($fullscreen) {
        $html = "<html><head><title>TiddlyWiki</title></head><body><div id='wrapper'><embed src='$path' type='application/octet-stream' width='100%' height='100%'/></div></body></html>";
    } else {
        $html = "<html><head><title>TiddlyWiki</title></head><body><div id='wrapper'><iframe src='$path' width='100%' height='100%' frameborder='0'></iframe></div></body></html>";
    }
    
    // Print HTML and exit.
    echo $html;
    exit;
}

// Define plugin function for saving TiddlyWiki edits.
function tiddlywiki_save($courseid, $path, $params) {
    // Check if user has edit capability.
    if (!has_capability('local/tiddlywiki:edit', context_system::instance())) {
        throw new moodle_exception('nopermission', 'error', '', get_string('nopermission'));
    }
    
    // Get contents of TiddlyWiki file.
    $fs = get_file_storage();
    $context = context_system::instance();
    $file = $fs->get_file($context->id, 'local_tiddlywiki', 'tiddlywikis', $courseid, '/', $path);
    $contents = $file->get_content();
    
    // Apply changes based on params.
    $changes = json_decode($params['changes'], true);
    foreach ($changes as $change) {
        $contents = str_replace($change['original'], $change['modified'], $contents);
    }
    
    // Save updated contents to bidix's store.php file.
    $params['contents'] = $contents;
    $params['ts'] = time();
    $store = new store();
    $store->store_tiddler($params);
}

Q:
what is the mechamism this plugin uses to store changes in the tiddlywiki.html?
A:
This plugin uses the TiddlyWiki mechanism for storing changes in the tiddlywiki.html
file. Specifically, it makes use of the store.php file, which is part of the TiddlyWiki system and provides a scripting interface for saving changes to a TiddlyWiki file. In the tiddlywiki_save()
function of the plugin, the changes submitted by the user are applied to the contents of the tiddlywiki.html file using the str_replace()
function. The resulting updated contents are then saved to the store.php file using the store_tiddler()
function of the store class.

Here’s a summary of the steps involved:

  1. Get the contents of the tiddlywiki.html file using the get_content() method of a Moodle file storage object.
  2. Apply the changes submitted by the user to the contents using str_replace()
  3. Create a store object and call its store_tiddler() method, passing in the updated contents and other relevant information (e.g. title, tags, etc.).
  4. The store_tiddler() method saves the updated contents to the store.php file, which in turn saves the changes to the tiddlywiki.html file.

It’s important to note that this plugin assumes that the TiddlyWiki file is stored on the server file system and not in a database, which can cause performance and concurrency issues. If you plan on using this plugin in a production environment, consider using a database-based approach for storing TiddlyWiki files.

<?php
/**
* Pluginfile works with TiddlyWiki Files
*
* @package    mod_tiddlywiki
* @category   mod
* @version    Moodle 3.11.2 (Build: 20211108)
* @copyright 
* @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/

require_once(dirname(dirname(dirname(__FILE__))) . '/config.php');
require_once(dirname(__FILE__) . '/locallib.php');

$id = optional_param('id', 0, PARAM_INT); // course_module ID, or
$n  = optional_param('n', 0, PARAM_INT);  // tiddlywiki instance ID - it should be named as the first character of the module

if ($id) {
    if (!$cm = get_coursemodule_from_id('tiddlywiki', $id)) {
        print_error('invalidcoursemodule');
    }

    if (!$tiddlywiki = $DB->get_record('tiddlywiki', array('id' => $cm->instance), '*', MUST_EXIST)) {
        print_error('instancenotfound', 'tiddlywiki');
    }
} elseif ($n) {
    if (!$tiddlywiki = $DB->get_record('tiddlywiki', array('id' => $n), '*', MUST_EXIST)) {
        print_error('instancenotfound', 'tiddlywiki');
    }

    if (!$cm = get_coursemodule_from_instance('tiddlywiki', $tiddlywiki->id, $tiddlywiki->course, false, MUST_EXIST)) {
        print_error('invalidcoursemodule');
    }
} else {
    print_error('invalidaccessparameter');
}

// set context and check user's access to the module
$context = context_module::instance($cm->id);
require_login($cm->course, true, $cm);
require_capability('mod/tiddlywiki:view', $context);

// check if the TiddlyWiki file has been uploaded yet
if (empty($tiddlywiki->data)) {
    print_error('tiddlywikifilenotuploaded', 'tiddlywiki', $returnurl);
}

// handle file upload
if (isset($_POST['action']) && $_POST['action'] == 'upload' && isset($_FILES['tiddlywiki'])) {
    // check user's access to upload a TiddlyWiki file
    require_capability('mod/tiddlywiki:update', $context);

    // get the uploaded file
    $uploaded_file = $_FILES['tiddlywiki'];

    // handle file upload errors
    if ($uploaded_file['error'] !== UPLOAD_ERR_OK) {
        $errors = array(
            UPLOAD_ERR_INI_SIZE => get_string('upload_max_filesize_exceeded', 'mod_tiddlywiki'),
            UPLOAD_ERR_FORM_SIZE => get_string('upload_max_filesize_exceeded', 'mod_tiddlywiki'),
            UPLOAD_ERR_PARTIAL => get_string('file_upload_partial', 'mod_tiddlywiki'),
            UPLOAD_ERR_NO_FILE => get_string('file_upload_no_file', 'mod_tiddlywiki'),
            UPLOAD_ERR_NO_TMP_DIR => get_string('file_upload_no_tmp_dir', 'mod_tiddlywiki'),
            UPLOAD_ERR_EXTENSION => get_string('file_upload_extension_error', 'mod_tiddlywiki')
        );
        print_error($errors[$uploaded_file['error']], 'tiddlywiki');
    }

    // get the file name and extension
    $filename = $uploaded_file['name'];
    $extension = pathinfo($filename, PATHINFO_EXTENSION);

    // check if the uploaded file is a valid TiddlyWiki file
    if ($extension != 'html') {
        print_error('invalidtiddlywikifile', 'tiddlywiki');
    }

    // save the uploaded file
    $fs = get_file_storage();
    $file_record = new stdClass();
    $file_record->contextid = $context->id;
    $file_record->component = 'mod_tiddlywiki';
    $file_record->filearea = 'content';
    $file_record->itemid = 0;
    $file_record->filename = $filename;
    $file_record->filepath = '/';
    $file_record->mimetype = 'text/html';
    $fs->create_file_from_pathname($file_record, $uploaded_file['tmp_name']);

    // redirect to the TiddlyWiki file
    redirect($fileurl);
}

// serve the TiddlyWiki file
$servicename = 'tiddlywiki';
$filename = $tiddlywiki->name . '.html';
$fileurl = ($CFG->uservideojsstreaming) ? moodle_url::make_pluginfile_url($context->id, $servicename, $tiddlywiki->id, '', '/', $filename) : get_file_url($tiddlywiki, $context, 'mod_tiddlywiki', 'tiddlywiki', 0, '/', $filename);

// output file and file upload form
echo $OUTPUT->header();

if ($tiddlywiki->canedit) {
    echo '
        <form method="post" action="'.$fileurl.'" enctype="multipart/form-data">
            <input type="hidden" name="MAX_FILE_SIZE" value="5000000">
            <input type="file" name="tiddlywiki">
            <input type="hidden" name="action" value="upload">
            <input type="submit" value="Upload">
        </form>
    ';
}

echo '<iframe src="'.$fileurl.'" frameborder="0" style="width: 100%; height: 800px;"></iframe>';

echo $OUTPUT->footer();

I am still on this topic but I only have made little progress myself so far.
Looking for an example to teach me how to do this I stumbled upon a moodle-static-page plugin

Though it is no activity-pluging as I intended to make I made a github-fork to start with.
This was my first attempt: static-page-mod the new start is below:

2 Likes

Having a closer look, the original ressource-activty proved more promising to me.
It already allows uploading and displaying files and already can handle a TW.
So I made a repository based on this to splice in a modified version of the store.php.

Beware: So far this is just for development and not for installation.

update: recently modified title of the repo

FEATURES (of the original plugin):

  • The module allows creating a moodle activity.
  • It allows uploading and manage multiple files
  • Set which file will be displayed
  • Set the Wiki to be displayed either in embedded or in a popup

TODO:
Phase 1.

  • Adapting names in order to avoid confusion with the ressource module,
  • Add the possiblity to save changes made to the Tiddlywiki by the Tiddlywiki saver and a modified version of store.php saving as a new file and setting if the User has capabilities to edit.
  • Automatically set the most recent file to be shown.

Phase 2.

  • Ad the possibility to an empty TW with the click of a button
  • Adapt the store.php mechanism to keep a only reasonable number of backups.
  • Allow to upload images and other media-files that can be embedded in moodle.
  • Allow users to save tiddlers they modified as json-files, to be reimported in the moodle.

Phase 3.

  • Allow users to stave and view their own copies of the wiki (or a json containing the modifications)
  • Invent a mechanism for cooperation that keeps the userfiles updated to the new version.
2 Likes

Hi folks,
is there anyone with good relations to an experience moodle admin with debugging experience.
I roughly tailored the code using the resource-plugin buld replacing the original name with tiddlywiki ans adding a store.php and a viewuserfiles.php to see the version history.

I tested it on a raspi moodlebox.
It is not working yet, actually it is buggy enough to kill the display of the entire admin settings.
My problem now is that I do not really know how to setup a testing / debugging environment for moodle.

At least I found a way to install moodle on my PC to develop and test the plugin:
https://bitnami-moodle-stack.software.informer.com/download/

The activity has icon now and the latest stage of plugin (not the one one github which remains a menace…) causes no fatal errors…there will be a heap of work before it really does what I want, but it already looks great:

1 Like

Hi I made some advances, but I am not ready yet.
The plugin does not kill the moodle anymore and it allows to upload Tiddlywikis as an “activity” and display them. (But this is basically what the resource activity does…). They even can go fullscreen and offer quite a nice experience.
It is possible now to reach the store.php from the Iframe in which the wiki is displayed and to store the TW in the pluginfolder.
This is just half of what is necessary because to be displayed in multiple instances in the wiki, the store php has to acess the database and because it stores from an Iframe it has lost the moodle course context. So I will have to retrieve the context from the database searching for the title of the wiki retrieved from the url and then save the TW based on these params…
I am not excellent at sql which is necessary for this, so help would be apreciated…

I made a moodle-Saver plugin $ _plugins_JJ_Moodle.json (3.0 KB) to put the params in the store php…but this will only work when I integrated the db-access to the store.php.

Hi everyone,
I finally managed to create an alpha of the Moodle-part of the Tiddlywiki-saver.

Of course you also need the tiddlywiki-moodle plugin in the thread above to set the path to the store.php and transfer the IFrameURL

You can upload and view a TiddlyWiki, edit and save the changes :rocket: in a Moodle-Activity-Plugin using the moodleDB and filedir for BLOBs with the TIddlyWikis Save-mechanism
You don’t need a password, because it uses Moodle’s capabilies systems.
For now it does no backups but only replaces the wiki with the newly saved one because I did not implement a clean backup function yet and did not want wikis to be flooded with tons of draft-wikis.

Volunteers and testers are very welcome! This has been tested on my 3.5 installation and a moodlebox, but as moodle is so configurable, testing is very important.

UPDATE: A shadow is cast on my euphoria as the store.php does not respond to the post-request on the 4.1 Moodle I just tested it on. May be a question of userrights or chmod.

It would be nice to get some attention in the moodle community … so star this on github.

1 Like