Moodle Block Resource-Download

Der Moodle Resource Download Block

Moodle Resource Download Block Auf vielfachen Schülerwunsch hin habe ich einen Block für unser Learning Management System Moodle codiert: Den Resource Download Block. Er erlaubt den Download aller Kursdateien und Verzeichnisse in einem ZIP-Archiv. Dieses Zip-Archiv wird wie im Beitrag Zipdateien on-the-fly erstellen mit PHP dynamisch erstellt, da (in der Theorie) jeder Lernende eine andere Kursansicht haben kann.

Einen Block erstellen ist relativ einfach, wenn man dem Block Howto folgt, aber der Teufel liegt wie immer im Detail.

Configwerte

Jeder Block kann verschiedene Konfigurationswerte erfragen und erhalten. Diese unterscheiden sich aber, ob der Block global (pinned, sticky) ist, in welchem er nur von einem Ort aus konfiguriert wird oder ob er den individuellen Kursen hinzugefügt wurde, wobei dann jede Instanz ihre eigene Konfiguration hat. Ich habe mich für den zweiten Weg entschieden.

Um die Konfigurationsvariablen mit einem Defaultwert zu versehen, muss man sich in der instance_config_print() Methode darum kümmern:

if (!isset ($this->config)) {
    // ... teacher has not yet configured the block, let's put some default values here to explain things
    $this->config->exclusionregexp= block_downloader_default_exclusionregexp();
    $this->config->compression= block_downloader_default_compression();
    $this->config->maxsize= block_downloader_default_maxsize();
}

Die Datei, die das Zip zusammenstellt ist mehr oder weniger ausserhalb von Moodle, da sie nicht „als Block“ erscheinen kann. Um da an die Konfigurationsdaten zu kommen, muss man etwas mehr Aufwand treiben. Sie befinden sich base64 codiert in den Tabellen block_instance respektive blocks_pinned für globale Blocks. courseid und instanceid werden dabei vom Link im Block übergeben.

<?php

/**
 * Create the zip on the fly and push it to the browser.
 */


require_once ('../../config.php');
require_once ($CFG->dirroot . '/blocks/downloader/lib/archive.php');
require_once ($CFG->dirroot . '/blocks/downloader/lib/downloadlib.php');
require_once ($CFG->dirroot . '/lib/filelib.php');
require_once ($CFG->dirroot . '/lib/moodlelib.php');
require_once ($CFG->dirroot . '/blocks/downloader/lib/downloadlib.php');

$courseid= required_param('courseid', PARAM_INT); // Course identification
$instanceid= required_param('instanceid', PARAM_INT); // Instance of the block

// Securitycheck
[SNIP]

// Get Config Data
$where= "pagetype = 'course-view' AND visible = 1 AND id=" . addslashes($instanceid);

// Instance?
$blockarr= get_records_select('block_instance', $where . " AND pageid=" . addslashes($courseid));

// Maybe pinned? In this case we have no courseid
if (!$blockarr) {
    $blockarr= get_records_select('block_pinned', $where);
}

if (!$blockarr) {
    error("cannot find block with: " . $where . " AND pageid=" . addslashes($courseid));
    exit ();
}

// Take the first result
$block= array_pop($blockarr);

$configdata= unserialize(base64_decode($block->configdata));

print_r($configdata);

[...]

Dateien pro Kurs abholen

Das Zusammenstellen aller Pfade für Dateien, die ein Benutzer sieht und auf die er Zugriff hat ist echt mühsam. Ich wünsche mir hier ein Bisschen den Servicegedanken: Beispielsweise würde man viel Zeit sparen, wenn der Entwickler mit dem entsprechenden Wissen Funktionen wie can_read($userid, $resourceid) implementieren würde. Zusätzlich wäre man unabhängig vom verwendeten Absicherungsschema. Ich werde das zukünftig in meinen Programmen berücksichtigen.

Reaktionen

Die Community hat – wie meistens bei Moodle – sehr nett auf die Veröffentlichung reagiert. Zwei Tage nach Version 1.0.0 habe ich schon eine Slovakische Übersetzung und diverse Hinweise auf Bugs erhalten.

Zipdateien on-the-fly erstellen mit PHP

Das Problem

On-The-Fly An unserer Schule wird Moodle als LMS (Learning Management System) verwendet. Es eignet sich sehr gut um Unterrichtsunterlagen wie Skripts, Aufträge und Präsentationen zu publizieren und den Lernenden auf eine angenehme Art und Weise zur Verfügung zu stellen. In einigen Kursen gibt es sehr viele dieser Ressourcen (Dateien und Verzeichnisse) zum Download und alleine schon das Anklicken und Speichern nimmt einen grossen Teil des zur Verfügung stehenden Zeitbudgets ein.

Einzelne Ressourcen können von der Lehrperson unsichtbar gemacht werden und nicht alle Dateien eines Kurses stehen zum Download bereit. Die Ansicht ist also sehr individuell und kann sich schnell ändern. Die Dateien werden über eine Versionsverwaltung in das System eingespiesen und können sich so ausserhalb des Systems verändern. Leider verunmöglicht dies die offensichtliche Lösung, einfach ein statisches Zip-Archiv zum Download zur Verfügung zu stellen.

Screenshot Moodle

Die Lösung

Das Zip muss also dynamisch (im Speicher) generiert werden bei einem Aufruf. Fündig wurde ich bei der Klasse archive bei phpclasses.org. Neben Zip-Archiven kann sie auch tar’s, gzip’s und bzip2’s erzeugen. Ich habe einige, kleine Erweiterungen vorgenommen, die noch beschrieben werden.

Die Handhabung ist sehr einfach:

// Archivklasse einbinden:
require_once ('lib/archive.php');

// Objekt erzeugen. Das Argument bezeichnet den Dateinamen
$zipfile= new zip_file('Meine Zipdatei.zip');

// Die Optionen
$zipfile->set_options(array (
        'basedir' => "/home/me/toZip/", // Das Basisverzeichnis. Sonst wird der ganze Pfad von / an im Zip gespeichert.
        'followlinks' => 1, // Symlinks sollen berücksichtigt werden
        'inmemory' => 1, // Die Datei nur im Speicher erstellen
        'level' => 6, // Level 1 = schnell, Level 9 = gut
        'recurse' => 1, // In Unterverzeichnisse wechseln
        // Wenn zu grosse dateien verarbeitet werden, kannes zu einem php memory error kommen
        // Man sollte nicht über das halbe memory_limit (php.ini) hinausgehen
        'maxsize' => 12*1024*1024 // Nur Dateien die <= 12 MB gross sind zippen
));

// Alle Dateien im Verzeichnis /home/me/toZip/Stuff hinzufügen
// Alle ".doc" Dateien und alle Ordner im Verzeichnis /home/me/toZip/Letters hinzufügen
$zipfile->add_files(array("Stuff", "Letters/*.doc"));

// Alle ".tmp" dateien in Stuff ausschliessen
$zipfile->exclude_files("htdocs/*.tmp");

// Alle Dateien in ".svn" und "CVS" Verzeichnissen ausschliessen (Regular Expressions)
$zipfile->exclude_regexp_files('.*/CVS|.*/CVS/.*|.*/\.svn|.*/\.svn.*');

// Archiv erstellen
$zipfile->create_archive();

// Archiv zum Download anbieten
$zipfile->download_file();

// Oder speichern (vielen Dank an PHPler!!!)
$zipfile->save_file('myzip.zip', $path = '/tmp/');

Änderungen

Ich habe folgende Dinge gegenüber der original archive.php geändert:

  • Mittels exclude_regexp_files() können Dateien auf Grund von Regular Expressions ausgeschlossen werden.
  • Die maxsize Option erlaubt das Ausschliessen von Dateien die grösser sind als die angegebene Zahl in Byte.
  • Verzeichnisse unter Linux werden als Verzeichnisse behandelt (Bug?)
  • exclude_file() prüft, ob eine Datei ausgeschlossen würde

Dank PHPler lönnen nun auch mit der Methode save_file($filename, $path = '') inmemory zips als Dateien gespeichert werden.

Meine Version kann natürlich auch frei heruntergeladen werden.