Commit 177cdb2d authored by Robert Lyon's avatar Robert Lyon

Bug 1826284: Adding the pdf option to exports

1) Extends the HTML export
2) Dump the pages as PDFs as well
3) Mentions on install page what external dependencies there are

behatnotneeded

Change-Id: I15e29eb7bfc6f40f40e305499f1369b2db5c6164
Signed-off-by: Robert Lyon's avatarRobert Lyon <robertl@catalyst.net.nz>
parent d3425360
......@@ -49,6 +49,7 @@ foreach (array_keys($plugins) as $plugin) {
'disableable' => call_static_method($classname, 'can_be_disabled'),
'deprecated' => call_static_method($classname, 'is_deprecated'),
'name' => call_static_method($classname, 'get_plugin_display_name'),
'dependencies' => call_static_method($classname, 'has_plugin_dependencies'),
'enableable' => call_static_method($classname, 'is_usable')
);
if (
......@@ -106,12 +107,21 @@ foreach (array_keys($plugins) as $plugin) {
validate_plugin($plugin, $dir);
$classname = generate_class_name($plugin, $dir);
$classname::sanity_check();
$name = call_static_method($classname, 'get_plugin_display_name');
$plugins[$plugin]['notinstalled'][$dir]['name'] = $name;
$plugins[$plugin]['notinstalled'][$dir]['name'] = call_static_method($classname, 'get_plugin_display_name');
$plugins[$plugin]['notinstalled'][$dir]['dependencies'] = call_static_method($classname, 'has_plugin_dependencies');
}
catch (InstallationException $e) {
$plugins[$plugin]['notinstalled'][$dir]['notinstallable'] = $e->GetMessage();
}
// If there are 'required' dependencies then we mark the plugin notinstallable
if (isset($plugins[$plugin]['notinstalled'][$dir]['dependencies']['requires'])) {
if (isset($plugins[$plugin]['notinstalled'][$dir]['notinstallable'])) {
$plugins[$plugin]['notinstalled'][$dir]['notinstallable'] .= $plugins[$plugin]['notinstalled'][$dir]['dependencies']['requires'];
}
else {
$plugins[$plugin]['notinstalled'][$dir]['notinstallable'] = $plugins[$plugin]['notinstalled'][$dir]['dependencies']['requires'];
}
}
}
if ($plugin == 'artefact' && table_exists(new XMLDBTable('blocktype_installed'))) { // go check it for blocks as well
$btlocation = get_config('docroot') . $plugin . '/' . $dir . '/blocktype';
......
......@@ -15,6 +15,18 @@ require(dirname(dirname(dirname(__FILE__))) . '/init.php');
define('TITLE', get_string('bulkexporttitle1', 'admin'));
$exportplugins = plugins_installed('export');
if (!$exportplugins) {
die_info(get_string('noexportpluginsenabled', 'export'));
}
foreach ($exportplugins as $plugin) {
safe_require('export', $plugin->name);
$exportoptions[$plugin->name] = call_static_method(generate_class_name('export', $plugin->name), 'get_title');
}
$pdfrun = 'multi';
/**
* Convert a 2D array to a CSV file. This follows the basic rules from http://en.wikipedia.org/wiki/Comma-separated_values
*
......@@ -99,7 +111,7 @@ function create_zipfile($listing, $files) {
}
function bulkexport_submit(Pieform $form, $values) {
global $SESSION;
global $SESSION, $pdfrun;
$usernames = array();
......@@ -142,9 +154,25 @@ function bulkexport_submit(Pieform $form, $values) {
if ($exporttype == 'html') {
$exporter = new PluginExportHtml($user, PluginExport::EXPORT_ALL_VIEWS_COLLECTIONS, PluginExport::EXPORT_ALL_ARTEFACTS);
}
else if ($exporttype == 'pdf') {
if ($exportcount === 0 && $num_users === 1) {
$pdfrun = 'all';
}
else if ($exportcount === 0) {
$pdfrun = 'first';
}
else if ($num_users == ($exportcount + 1)) {
$pdfrun = 'last';
}
else {
$pdfrun = 'multi';
}
$exporter = new PluginExportPdf($user, PluginExport::EXPORT_ALL_VIEWS_COLLECTIONS, PluginExport::EXPORT_ALL_ARTEFACTS);
}
else {
$exporter = new PluginExportLeap($user, PluginExport::EXPORT_ALL_VIEWS_COLLECTIONS, PluginExport::EXPORT_ALL_ARTEFACTS);
}
try {
$exporter->export(true);
$zipfile = $exporter->export_compress();
......@@ -160,6 +188,7 @@ function bulkexport_submit(Pieform $form, $values) {
}
if (!$zipfile = create_zipfile($listing, $files)) {
include_once(get_config('wwwroot') . 'export/download.php');
export_iframe_die(get_string('bulkexportempty', 'admin'));
}
......@@ -211,8 +240,7 @@ $form = array(
'exporttype' => array(
'type' => 'select',
'title' => get_string('chooseanexportformat', 'export'),
'options' => array('leap' => 'Leap2A',
'html' => 'HTML'),
'options' => $exportoptions,
'defaultvalue' => 'leap'
),
'authinstance' => $authinstanceelement,
......
......@@ -416,7 +416,7 @@ class PluginExportHtml extends PluginExport {
/**
* Dumps all views into the HTML export
*/
private function dump_view_export_data() {
protected function dump_view_export_data() {
safe_require('artefact', 'comment');
$progressstart = 55;
$progressend = 75;
......@@ -517,7 +517,7 @@ class PluginExportHtml extends PluginExport {
* 'description' => ...
* )
*/
private function get_view_collection_summary() {
protected function get_view_collection_summary() {
$list = array();
foreach ($this->collections as $id => $collection) {
......@@ -840,7 +840,7 @@ private function get_folder_modals(&$idarray, BlockInstance $bi) {
/**
* Copies the static files (stylesheets etc.) into the export
*/
private function copy_static_files() {
protected function copy_static_files() {
global $THEME, $SESSION;
require_once('file.php');
$staticdir = $this->get('exportdir') . '/' . $this->get('rootdir') . '/static/';
......
......@@ -943,6 +943,8 @@ class PluginExportAll extends PluginExport {
protected $htmlexporter;
protected $leapexporter;
protected $pdfexporter;
protected $pdfactive;
protected $exportdir;
protected $zipfile;
......@@ -951,15 +953,24 @@ class PluginExportAll extends PluginExport {
safe_require('export', 'leap');
$this->htmlexporter = new PluginExportHtml($user, $views, $artefacts, $progresscallback);
$this->leapexporter = new PluginExportLeap($user, $views, $artefacts, $progresscallback);
$this->pdfactive = get_field('export_installed', 'active', 'name', 'pdf');
if ($this->pdfactive) {
$this->pdfexporter = new PluginExportPdf($user, $views, $artefacts, $progresscallback);
}
$this->exportdir = $this->htmlexporter->get('exportdir');
$this->zipfile = 'mahara-export-user'
. $user->get('id') . '-' . date('Y-m-d_H-i', time()) . '.zip';
}
public function is_diskspace_available() {
return ($this->htmlexporter->is_diskspace_available() && $this->leapexporter->is_diskspace_available());
$spaceok = true;
if ($this->pdfactive) {
$spaceok = ($spaceok && $this->pdfexporter->is_diskspace_available());
}
$spaceok = ($spaceok && $this->htmlexporter->is_diskspace_available() && $this->leapexporter->is_diskspace_available());
return $spaceok;
}
public static function get_title() {}
public static function get_description() {}
......@@ -974,6 +985,15 @@ class PluginExportAll extends PluginExport {
catch (SystemException $e) {
throw new SystemException('Failed create html export: ' . $e->getMessage());
}
if ($this->pdfactive) {
$this->notify_progress_callback(0, get_string('startingpdfexport', 'export'));
try {
$pdf = $this->pdfexporter->export();
}
catch (SystemException $e) {
throw new SystemException('Failed create pdf export: ' . $e->getMessage());
}
}
$this->notify_progress_callback(0, get_string('startingleapexport', 'export'));
try {
$leap = $this->leapexporter->export();
......
<?php
/**
*
* @package mahara
* @subpackage export.pdf
* @author Catalyst IT Ltd
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL version 3 or later
* @copyright For copyright information on Mahara, please see the README file distributed with this software.
*
*/
defined('INTERNAL') || die();
$string['title'] = 'PDF files of pages / collections';
$string['description'] = 'This creates a zipped file containing PDFs of your portfolios. You cannot import this again, but it is readable in a standard PDF viewer.';
$string['needspdfconfig'] = 'Requires config.php setting "usepdfexport" to be true.';
$string['needschromeheadless'] = 'Experimental export option that utilises Headless Chrome to Print PDFs. Install the latest version of the Chrome or Chromium browser on the server to use this plugin.';
$string['needschromeheadlessphp'] = 'Requires "chrome-php". You can install this via "make pdfexport"';
$string['needspdfunite'] = 'Requires "pdfunite". You can install this via "apt-get install poppler-utils".';
$string['exportpdfdisabled'] = 'PDF export dependencies missing so PDF export disabled. For more information see <a href="%s">Plugin administration</a>.';
<?php
/**
*
* @package mahara
* @subpackage export-html
* @author Catalyst IT Ltd
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL version 3 or later
* @copyright For copyright information on Mahara, please see the README file distributed with this software.
*
*/
defined('INTERNAL') || die();
require_once(get_config('docroot') . 'export/html/lib.php');
$pdfactive = false;
if (db_table_exists('export_installed')) {
$pdfactive = get_field('export_installed', 'active', 'name', 'pdf');
}
if ($pdfactive && !file_exists(get_config('docroot') . 'lib/chrome-php/headless-chromium-php-master/vendor/autoload.php')) {
global $SESSION;
// need to disable the PDF export option
execute_sql("UPDATE {export_installed} SET active = 0 WHERE name = ?", array('pdf'));
$SESSION->add_info_msg(get_string('exportpdfdisabled', 'export.pdf', get_config('wwwroot') . 'admin/extensions/plugins.php'), false);
if (defined('INSTALLER')) {
redirect();
}
else {
redirect($_SERVER['SCRIPT_NAME']);
}
}
else if ($pdfactive) {
require_once(get_config('docroot') . 'lib/chrome-php/headless-chromium-php-master/vendor/autoload.php');
}
use HeadlessChromium\BrowserFactory;
use HeadlessChromium\Cookies\Cookie;
/**
* HTML export plugin
*/
class PluginExportPdf extends PluginExportHtml {
/**
* constructor. overrides the parent class
* to set up smarty and the attachment directory
*/
public function __construct(User $user, $views, $artefacts, $progresscallback=null) {
global $THEME;
parent::__construct($user, $views, $artefacts, $progresscallback);
$this->zipfile = 'mahara-export-pdf-user'
. $this->get('user')->get('id') . '-' . $this->exporttime . '.zip';
}
public static function get_title() {
return get_string('title', 'export.pdf');
}
public static function get_description() {
return get_string('description', 'export.pdf');
}
public static function get_plugin_display_name() {
return 'PDF';
}
public static function has_plugin_dependencies() {
$needs = get_string('needschromeheadless', 'export.pdf');
// make sure that composer has installed the headlessbrowser hook
$requires = array();
if (!file_exists(get_config('docroot') . 'lib/chrome-php/headless-chromium-php-master/src/BrowserFactory.php')) {
$requires[] = get_string('needschromeheadlessphp', 'export.pdf');
}
if ($pdfunite = exec('apt -qq list poppler-utils')) {
if (!preg_match('/\[installed\]/', $pdfunite)) {
$requires[] = get_string('needspdfunite', 'export.pdf');
}
}
if (!get_config('usepdfexport')) {
$requires[] = get_string('needspdfconfig', 'export.pdf');
}
$out = array('needs' => $needs, 'requires' => implode('<br>', $requires));
return $out;
}
/**
* Main export routine
* @param $createarchive Boolean specifies whether a zipfile will be created here
* or later on, i.e. in PluginExportAll which creates a zipfile of all export formats.
*/
public function export($createarchive=false) {
// Only call parent if we do not do pdf export after html export
if ($createarchive) {
parent::export($createarchive);
}
$this->pdf_view_export_data();
return true;
}
/**
* Dumps all views into the HTML export
*/
private function pdf_view_export_data() {
global $pdfrun;
static $browser;
static $page;
$progressstart = 85;
$progressend = 95;
$i = 0;
$viewcount = count($this->views);
$browsertype = 'chromium-browser';
system('dpkg -l | grep ' . $browsertype, $error);
if ($error) {
$browsertype = 'chrome';
system('dpkg -l | grep ' . $browsertype, $error2);
if ($error2) {
throw new MaharaException('Need to have a Chrome browser installed to use the headless pdf option');
}
}
if (!isset($pdfrun) || $pdfrun == 'first' || $pdfrun == 'all') {
$browserFactory = new BrowserFactory($browsertype);
// starts headless chrome
$browser = $browserFactory->createBrowser(['windowSize' => [1280,800],
'ignoreCertificateErrors' => true,
'connectionDelay' => 0.8]);
// creates a new page and navigate to an url
$page = $browser->createPage();
}
// Map the view id order to their collection order if applicable
$viewids = array_keys($this->views);
$viewobjs = array();
if (!empty($viewids)) {
$colviews = get_column_sql("SELECT v.id FROM {view} v
LEFT JOIN {collection_view} cv ON cv.view = v.id
WHERE v.id IN (" . join(',', array_map('intval', $viewids)) . ")
ORDER BY cv.collection, cv.displayorder, v.id");
foreach ($colviews as $id) {
$view = $this->views[$id];
$cid = 0;
if ($view->get_collection()) {
$cid = $view->get_collection()->get('id');
}
$viewobjs[$cid][$id] = $view;
}
}
$colpdfs = $viewpdfs = array();
foreach ($viewobjs as $collectionid => $views) {
foreach ($views as $viewid => $view) {
$this->notify_progress_callback(intval($progressstart + (++$i / $viewcount) * ($progressend - $progressstart)), get_string('exportingviewsprogresspdf', 'export', $i, $viewcount));
if ($this->exportingoneview) {
$directory = $this->exportdir . '/' . $this->rootdir;
}
else {
$directory = $this->exportdir . '/' . $this->rootdir . '/views/' . parent::text_to_filename($view->get('title'));
}
$filename = $directory . "/index.html";
// Navigate to the needed page
$page->navigate('file://' . $filename)->waitForNavigation();
$shortname = generate_urlid($view->get('title'), get_config('cleanurlviewdefault'), 3, 50);
// Create the pdf file
// Note: pdf is created in @media print mode
$pdfname = $directory . '/' . $viewid . '_' . $shortname . '.pdf';
if ($collectionid > 0) {
$colpdfs[$collectionid][] = $pdfname;
}
else {
$viewpdfs[] = $pdfname;
}
$page->pdf(['printBackground' => true,
'preferCSSPageSize' => true])->saveToFile($pdfname);
if (!file_exists($filename) || !is_readable($filename)) {
throw new SystemException("Could not read view page for creating pdf for $viewid");
}
}
}
if (!isset($pdfrun) || $pdfrun == 'last' || $pdfrun == 'all') {
// Close the headlesss browser
$page->close();
$browser->close();
}
$output = array();
$directory = $this->exportdir . '/' . $this->rootdir;
foreach ($colpdfs as $collectionid => $collection) {
$collectionname = $this->collections[$collectionid]->get('name');
$collectionname = preg_replace('/\s+/', '_', $collectionname);
exec('pdfunite ' . implode(' ', $collection) . ' ' . $directory . '/' . $collectionid . '_' . $collectionname . '.pdf', $output);
// remove the page pdfs that are now in collections
foreach ($collection as $c) {
unlink($c);
}
}
// Move view PDF files to same place as collection files
foreach ($viewpdfs as $view) {
$path = explode('/', $view);
$file = array_pop($path);
rename($view, $directory . '/' . $file);
}
}
}
\ No newline at end of file
<?php
/**
*
* @package mahara
* @subpackage export-pdf
* @author Catalyst IT Ltd
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL version 3 or later
* @copyright For copyright information on Mahara, please see the README file distributed with this software.
*
*/
defined('INTERNAL') || die();
$config = new stdClass();
$config->version = 2019041800;
$config->release = '1';
......@@ -23,6 +23,7 @@ $string['clickheretodownload'] = 'Click here to download it';
$string['continue'] = 'Continue';
$string['startinghtmlexport'] = 'Starting HTML export';
$string['startingleapexport'] = 'Starting Leap2A export';
$string['startingpdfexport'] = 'Starting PDF export';
$string['exportgeneratedsuccessfully'] = 'Export generated successfully. %sClick here to download it%s';
$string['exportgeneratedsuccessfully1'] = 'Export generated successfully.';
$string['exportgeneratedwitherrors'] = 'Export generated with some errors.';
......@@ -34,6 +35,7 @@ $string['exportingviews'] = 'Exporting pages';
$string['exportingcollections'] = 'Exporting collections';
$string['exportingviewsprogresshtml'] = 'Exporting pages for HTML: %s/%s';
$string['exportingviewsprogressleap'] = 'Exporting pages for Leap2A: %s/%s';
$string['exportingviewsprogresspdf'] = 'Creating PDFs: %s/%s';
$string['exportportfoliodescription1'] = '<p class="lead">This tool exports all of your portfolio information and pages. It does not export your site settings or any of the content you uploaded or created in groups.
</p><p p class="lead">You can export your personal portfolio content. Your account settings or content uploaded or created in groups are not exported.</p>';
$string['exportyourportfolio'] = 'Export your portfolio';
......
......@@ -843,3 +843,12 @@ $cfg->saml_log_attributes = false;
*/
//$cfg->saml_create_institution=true;
//$cfg->saml_create_institution_default = 'mahara';
/**
* Allow the export option called PDF export
* This option exports pages and collections as pdf files
*
* Note: This is an experimental feature requiring the install of a Chrome / Chromium browser on
* your server to ustilise the print to PDF commandline option - Use with caution.
*/
//$cfg->usepdfexport = true;
......@@ -2704,6 +2704,15 @@ abstract class Plugin implements IPlugin {
public static function get_plugin_display_name() {
return null;
}
/**
* Check if plugin's contains dependencies before installing it.
* For example, it relies on an operating system package to be installed
* @return $tring or null
*/
public static function has_plugin_dependencies() {
return null;
}
}
/**
......
......@@ -138,6 +138,9 @@ function check_test_site_config() {
// Set site name
set_config('sitename', self::$sitedefaultinfo['sitename']);
// Stop PDF export from being active
set_field('export_installed', 'active', 0, 'name', 'pdf');
// We need to keep the installed dataroot artefact files.
// So each time we reset the dataroot before running a test, the default files are still installed.
self::save_original_data_files();
......
......@@ -105,6 +105,15 @@
.text-small {
font-family: $font-family-base;
}
.notes {
font-weight: $font-weight-base;
font-size: $font-size-base;
color: $gray-300;
&.bold {
font-weight: $font-weight-bold;
}
}
}
.list-group-item h4.list-group-item-heading {
......@@ -123,7 +132,16 @@
background-color: $state-warning-bg;
.list-group-item-heading {
color: $state-warning-text;
}
}
.list-group-item-danger {
.notes {
font-weight: $font-weight-base;
font-size: $font-size-base;
color: $gray-300;
&.bold {
font-weight: $font-weight-bold;
}
}
}
.list-group-item-private {
......
......@@ -27,9 +27,12 @@
<li class="list-group-item list-group-item-danger" id="{$plugintype}.{$plugin}">
{if $data.name}{$data.name}{else}{$plugin}{/if}
{if $data.notinstallable}
{str tag='notinstallable'}: {$data.notinstallable}
{str tag='notinstallable'}: {$data.notinstallable|clean_html|safe}
{else}
<span id="{$plugintype}.{$plugin}.install">(<a href="" onClick="{$installlink}('{$plugintype}.{$plugin}'); return false;">{str tag='install' section='admin'} <span class="accessible-hidden sr-only">{$plugintype} {if $data.name}{$data.name}{else}{$plugin}{/if}</span></a>)</span>
<span id="{$plugintype}.{$plugin}.install">(<a href="" onClick="{$installlink}('{$plugintype}.{$plugin}'); return false;">{str tag='install' section='admin'}<span class="accessible-hidden sr-only"> {$plugintype} {if $data.name}{$data.name}{else}{$plugin}{/if}</span></a>)</span>
{/if}
{if $data.dependencies}
{if $data.dependencies.needs}<div class="notes">{$data.dependencies.needs|safe}</div>{/if}
{/if}
<span id="{$plugintype}.{$plugin}.message"></span>
</li>
......@@ -58,6 +61,10 @@
{/if}
</div>
{if $data.deprecated}{if gettype($data.deprecated) eq 'string'}<div class="alert alert-warning text-small">{$data.deprecated}</div>{else}{str tag=deprecated section=admin}{/if}{/if}
{if $data.dependencies}
{if $data.dependencies.needs}<div class="notes">{$data.dependencies.needs|safe}</div>{/if}
{if $data.dependencies.requires}<div class="danger">{$data.dependencies.requires|safe}</div>{/if}
{/if}
</div>
{if $data.types}
......
......@@ -6,8 +6,8 @@ Feature: Mahara users can export collections with bulk option
Background:
Given the following "institutions" exist:
| name | displayname | registerallowed | registerconfirm |
| instone | Institution One | ON | OFF |
| name | displayname | registerallowed | registerconfirm |
| instone | Institution One | ON | OFF |
And the following "users" exist:
| username | password | email | firstname | lastname | institution | authname | role |
......@@ -31,7 +31,7 @@ Scenario: Export collections in bulk
And I choose "Export" in "Manage" from main menu
When I select the radio "Just some of my collections"
Then I should see "Select all"
Then I should see "Reverse selection"
And I should see "Reverse selection"
When I follow "selection_all_collections"
Then the "Collection UserA_01" checkbox should be checked
And the "Collection UserA_02" checkbox should be checked
......@@ -50,6 +50,13 @@ Scenario: Export collections in bulk
When I click on "Generate export"
Then I should see "Please wait while your export is being generated..."
Scenario: Export collections in bulk as PDF
Given I log in as "admin" with password "Kupuh1pa!"
And I choose "Plugin administration" in "Extensions" from administration menu
Then I should see "Experimental export option that utilises Headless Chrome to Print PDFs"
And I should see "Requires \"chrome-php\""
And I should see "Requires config.php setting \"usepdfexport\" to be true"
Scenario: Institution One admin locks First name, Last name fields
I want to lock fields
So that institution fields will not change when users upload Leap2a portfolios
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment