Commit 4ef75593 authored by Yuliya Bozhko's avatar Yuliya Bozhko Committed by Aaron Wells
Browse files

Generate SVG image preview for page layouts (Bug #1392096)



Change-Id: I32faf161d9ef7c9cd020134a08998ce48acc7060
Signed-off-by: default avatarYuliya Bozhko <yuliya.bozhko@totaralms.com>
parent b5b899b1
...@@ -1274,11 +1274,8 @@ class PluginImportLeap extends PluginImport { ...@@ -1274,11 +1274,8 @@ class PluginImportLeap extends PluginImport {
db_rollback(); db_rollback();
return false; return false;
} }
else { $layout = (object) array('id' => $layoutresult['layoutid']);
$viewobj->updatecustomlayoutpreview($layoutdata); db_commit();
$layout = (object) array('id' => $layoutresult['layoutid']);
db_commit();
}
} }
} }
......
...@@ -10,9 +10,9 @@ ...@@ -10,9 +10,9 @@
var currentcollayout = $('#customrow_' + numrows).find('#selectcollayoutrow_' + numrows).val(); var currentcollayout = $('#customrow_' + numrows).find('#selectcollayoutrow_' + numrows).val();
$(newrow).find('.customrowtitle').html('<strong>' + get_string('rownr', numrows + 1) + '</strong>'); $(newrow).find('.customrowtitle').html('<strong>' + get_string('rownr', numrows + 1) + '</strong>');
$(newrow).attr('id', 'customrow_' + (numrows + 1)); $(newrow).attr('id', 'customrow_' + (numrows + 1));
$(newrow).find('#selectnumcolsrow_' + numrows).attr('value', currentncols); $(newrow).find('#selectnumcolsrow_' + numrows).val(currentncols);
$(newrow).find('#selectnumcolsrow_' + numrows).attr('id', 'selectnumcolsrow_' + (numrows + 1)); $(newrow).find('#selectnumcolsrow_' + numrows).attr('id', 'selectnumcolsrow_' + (numrows + 1));
$(newrow).find('#selectcollayoutrow_' + numrows).attr('value', currentcollayout); $(newrow).find('#selectcollayoutrow_' + numrows).val(currentcollayout);
$(newrow).find('#selectcollayoutrow_' + numrows).attr('id', 'selectcollayoutrow_' + (numrows + 1)); $(newrow).find('#selectcollayoutrow_' + numrows).attr('id', 'selectcollayoutrow_' + (numrows + 1));
if ((oldremovebutton = $(newrow).find('input')).length != 0) { if ((oldremovebutton = $(newrow).find('input')).length != 0) {
oldremovebutton.attr('class', 'removecustomrow_' + (numrows + 1)); oldremovebutton.attr('class', 'removecustomrow_' + (numrows + 1));
...@@ -97,9 +97,7 @@ ...@@ -97,9 +97,7 @@
var id = 'viewlayout_advancedlayoutselect' + unique_timestamp(); var id = 'viewlayout_advancedlayoutselect' + unique_timestamp();
$('input', clone).attr('id', id); $('input', clone).attr('id', id);
$('input', clone).val(layoutid); $('input', clone).val(layoutid);
$('img', clone).attr('src', config['wwwroot'] + 'thumb.php?type=viewlayout&vl=' + data.data.layoutid); $('svg', clone).replaceWith(data.data.layoutpreview);
$('img', clone).attr('alt', data.data.alttext);
$('img', clone).attr('title', data.data.alttext);
//insert into appropriate row //insert into appropriate row
var rowcontainer = $('#viewlayout_advancedlayoutselect_row'+numrows); var rowcontainer = $('#viewlayout_advancedlayoutselect_row'+numrows);
...@@ -183,7 +181,7 @@ ...@@ -183,7 +181,7 @@
} }
function link_thumbs_to_radio_buttons() { function link_thumbs_to_radio_buttons() {
$('div.layoutthumb img').each(function(event) { $('div.layoutthumb svg').each(function(event) {
$(this).click(function(e) { $(this).click(function(e) {
$(this).closest('div.layoutoption').children(':radio').attr('checked', 'checked').trigger('click'); $(this).closest('div.layoutoption').children(':radio').attr('checked', 'checked').trigger('click');
$('#viewlayout_layoutselect').val( $(this).closest('div.layoutoption').children(':radio').val() ); $('#viewlayout_layoutselect').val( $(this).closest('div.layoutoption').children(':radio').val() );
...@@ -215,21 +213,7 @@ ...@@ -215,21 +213,7 @@
} }
pd['action_updatecustomlayoutpreview_numrows_' + numrows + collayouts] = 1; pd['action_updatecustomlayoutpreview_numrows_' + numrows + collayouts] = 1;
sendjsonrequest(config['wwwroot'] + 'view/blocks.json.php', pd, 'POST', function(data) { sendjsonrequest(config['wwwroot'] + 'view/blocks.json.php', pd, 'POST', function(data) {
var pi = $('<img>').attr({ $('#custompreview').html(data.data);
'src' : data.data.data,
'alt' : data.data.alttext,
'title': data.data.alttext
});
if (data.data.newimage) {
$('#custompreview').html('<p>' + get_string('generatingpreview', 'view') + '</p>');
// delay to allow image to be written to disk
setTimeout(function() {
$('#custompreview').html(pi);
},500);
}
else {
$('#custompreview').html(pi);
}
}); });
if (typeof formchangemanager !== 'undefined') { if (typeof formchangemanager !== 'undefined') {
......
...@@ -253,7 +253,6 @@ $string['nrrows'] = array( ...@@ -253,7 +253,6 @@ $string['nrrows'] = array(
'%s row', '%s row',
'%s rows', '%s rows',
); );
$string['generatingpreview'] = 'Generating preview...';
$string['addnewblockhere'] = 'Add new block here'; $string['addnewblockhere'] = 'Add new block here';
$string['add'] = 'Add'; $string['add'] = 'Add';
...@@ -295,6 +294,7 @@ $string['addarow'] = 'Add a row'; ...@@ -295,6 +294,7 @@ $string['addarow'] = 'Add a row';
$string['removethisrow'] = 'Remove this row'; $string['removethisrow'] = 'Remove this row';
$string['columnlayout'] = 'Column layout'; $string['columnlayout'] = 'Column layout';
$string['layoutpreview'] = 'Layout preview'; $string['layoutpreview'] = 'Layout preview';
$string['layoutpreviewimage'] = 'Layout preview image';
$string['Help'] = 'Help'; $string['Help'] = 'Help';
$string['by'] = 'by'; $string['by'] = 'by';
......
...@@ -3851,5 +3851,13 @@ function xmldb_core_upgrade($oldversion=0) { ...@@ -3851,5 +3851,13 @@ function xmldb_core_upgrade($oldversion=0) {
} }
} }
if ($oldversion < 2014121200) {
require_once('file.php');
$layoutdir = get_config('dataroot') . 'images/layoutpreviewthumbs';
if (file_exists($layoutdir)) {
rmdirr($layoutdir);
}
}
return $status; return $status;
} }
...@@ -20,25 +20,18 @@ class LayoutPreviewImage { ...@@ -20,25 +20,18 @@ class LayoutPreviewImage {
private $rows = 1; private $rows = 1;
private $layout; // contains cols per row data private $layout; // contains cols per row data
private $description; // currently not used private $text;
private $owner; // currently not used
private static $standard_preview_width = 76; private static $standard_preview_width = 76;
private static $one_row_height = 48; private static $one_row_height = 48;
private static $two_row_height = 23; private static $two_row_height = 23;
private static $three_row_height = 15; private static $three_row_height = 15;
private static $spacer = 3; private static $spacer = 3;
public static $destinationfolder = 'images/layoutpreviewthumbs';
/* Constructor. /* Constructor.
* @param data containing 'layout' (required) that consists of * @param data containing 'layout' (required) that consists of an array of columns per row.
* an array of columns per row eg. * Example:
* array('row1' => '30-30-30', * array('row1' => '30-30-30',
* 'row2' => '25-25-25-25'); * 'row2' => '25-25-25-25');
* image manupulation information and place to
* save the resulting image file.
*
* The image manipulation information can be left out and
* the pre-defined defaults will be used.
*/ */
public function __construct($data = null) { public function __construct($data = null) {
...@@ -58,109 +51,70 @@ class LayoutPreviewImage { ...@@ -58,109 +51,70 @@ class LayoutPreviewImage {
} }
} }
/* Generates the preview image (.png) for a custom layout and /* Generates the preview SVG image.
* saves it in the specified $destinationfolder within
* the $dataroot directory
* *
* @return bool true on successful creation and saving of image. * @return string SVG of the preview image.
*/ */
public function create_preview() { public function create_preview() {
global $THEME;
$width = $this->get_preview_width(); $width = $this->get_preview_width();
$height = $this->get_preview_height(); $height = $this->get_preview_height();
$im = imagecreate($width,$height); // in pixels $id = uniqid('lid');
$layout = "<svg xmlns=http://www.w3.org/2000/svg role='img' width='{$width}' height='{$height}' aria-labelledby='title{$id} desc{$id}'>";
// maximum rows = $maxlayoutrows in View class if (!empty($this->text)) {
$white = imagecolorallocate($im,255,255,255); $layout .= "<title id='title{$id}' >" . get_string('layoutpreviewimage', 'view') . "</title>";
$grey1 = imagecolorallocate($im,102,102,102); $layout .= "<desc id='desc{$id}'>" . hsc($this->text) . "</desc>";
$grey2 = imagecolorallocate($im,77,77,77); }
$grey3 = $grey1;
$grey4 = $grey2;
$grey5 = $grey1;
$grey6 = $grey2;
$colours = array($grey1, $grey2, $grey3, $grey4, $grey5, $grey6);
$x = 0; $x = 0;
$y = 0; $y = 0;
$col_height = $this->get_preview_column_height(); $col_height = $this->get_preview_column_height();
$filename = 'vl-';
$class = true;
foreach ($this->layout as $key => $row) { foreach ($this->layout as $key => $row) {
$style = 'layout' . (int)$class;
$columns = explode('-', $row); $columns = explode('-', $row);
foreach ($columns as $column) { foreach ($columns as $column) {
$col_width = $this->get_percentage_column_width(count($columns), $column); $col_width = $this->get_percentage_column_width(count($columns), $column);
imagefilledrectangle($im,$x,$y,$x+$col_width,$y+$col_height,$colours[$key-1]); $layout .= "<rect x='{$x}' y='{$y}' width='{$col_width}' height='{$col_height}' class='{$style}'/>";
$x += ($col_width + self::$spacer); // increment x val for next col $x += ($col_width + self::$spacer); // increment x val for next col
} }
$x = 0; $x = 0;
$y += ($col_height + self::$spacer); // increment y val for next row $y += ($col_height + self::$spacer); // increment y val for next row
$filename .= $row; // build filename $class = !$class;
if ($key < count($this->layout)) {
$filename .= '_';
}
}
$filename .= '.png';
$maxsize = get_config('maxuploadsize');
if ($maxsize && filesize($im) > $maxsize) {
return get_string('uploadedfiletoobig');
} }
$dataroot = get_config('dataroot'); $layout .= '</svg>';
$destination = $dataroot . self::$destinationfolder;
if (!check_dir_exists($destination, true, true)) { return $layout;
throw new UploadException('Unable to create upload directory for layout preview images');
}
if (self::preview_exists($filename)) {
imagedestroy($im);
return true;
}
if ($madenewimage = imagepng($im, $destination . '/' . $filename) ) {
chmod($destination . '/' . $filename, get_config('filepermissions'));
imagedestroy($im);
return true;
}
imagedestroy($im);
return false;
}
public static function preview_exists($filename) {
return file_exists(self::$destinationfolder . '/' . $filename);
} }
private function get_preview_height() { private function get_preview_height() {
if ($this->rows == 1) { if ($this->rows == 1) {
return self::$one_row_height; return self::$one_row_height;
} }
$preview_height = ($this->rows > 2)? $this->rows * self::$three_row_height + ($this->rows-1) * self::$spacer : $this->rows * self::$two_row_height + ($this->rows-1) * self::$spacer; if ($this->rows > 2) {
return $preview_height; return $this->rows * self::$three_row_height + ($this->rows - 1) * self::$spacer;
}
return $this->rows * self::$two_row_height + ($this->rows - 1) * self::$spacer;
} }
private function get_preview_column_height() { private function get_preview_column_height() {
if ($this->rows == 1) { if ($this->rows == 1) {
return self::$one_row_height; return self::$one_row_height;
} }
$column_height = ($this->rows > 2)? self::$three_row_height : self::$two_row_height; if ($this->rows > 2) {
return $column_height; return self::$three_row_height;
}
return self::$two_row_height;
} }
private function get_preview_width() { private function get_preview_width() {
return self::$standard_preview_width; return self::$standard_preview_width;
} }
private function get_equal_column_widths($numcols) {
return (self::$standard_preview_width - self::$spacer * ($numcols-1)) / $numcols;
}
private function get_percentage_column_width($numcols, $percent) { private function get_percentage_column_width($numcols, $percent) {
return (self::$standard_preview_width - self::$spacer * ($numcols-1)) * $percent/100; return (self::$standard_preview_width - self::$spacer * ($numcols - 1)) * $percent / 100;
} }
} }
\ No newline at end of file
...@@ -16,7 +16,7 @@ $config = new stdClass(); ...@@ -16,7 +16,7 @@ $config = new stdClass();
// See https://wiki.mahara.org/index.php/Developer_Area/Version_Numbering_Policy // See https://wiki.mahara.org/index.php/Developer_Area/Version_Numbering_Policy
// For upgrades on stable branches, increment the version by one. On master, use the date. // For upgrades on stable branches, increment the version by one. On master, use the date.
$config->version = 2014112700; $config->version = 2014121200;
$config->series = '15.04'; $config->series = '15.04';
$config->release = '15.04dev'; $config->release = '15.04dev';
$config->minupgradefrom = 2009022600; $config->minupgradefrom = 2009022600;
......
...@@ -1022,10 +1022,12 @@ class View { ...@@ -1022,10 +1022,12 @@ class View {
db_commit(); db_commit();
} }
/*Return preview image for creation of custom layout /* Returns preview image for creation of custom layout
*
* @param array
* @return string SVG preview image
*/ */
public function updatecustomlayoutpreview($values) { public function updatecustomlayoutpreview($values) {
global $THEME;
require_once(get_config('libroot') . 'layoutpreviewimage.php'); require_once(get_config('libroot') . 'layoutpreviewimage.php');
$require = array('numrows'); $require = array('numrows');
...@@ -1043,7 +1045,6 @@ class View { ...@@ -1043,7 +1045,6 @@ class View {
} }
} }
$previewimage = 'vl-';
$alttext = ''; $alttext = '';
$customlayout = array(); $customlayout = array();
for ($i=0; $i<$numrows; $i++) { for ($i=0; $i<$numrows; $i++) {
...@@ -1051,44 +1052,32 @@ class View { ...@@ -1051,44 +1052,32 @@ class View {
$widths = get_field('view_layout_columns', 'widths', 'id', $id); $widths = get_field('view_layout_columns', 'widths', 'id', $id);
$hyphenatedwidths = str_replace(',', '-', $widths); $hyphenatedwidths = str_replace(',', '-', $widths);
$customlayout[$i+1] = $hyphenatedwidths; $customlayout[$i+1] = $hyphenatedwidths;
$previewimage .= $hyphenatedwidths;
$alttext .= $hyphenatedwidths; $alttext .= $hyphenatedwidths;
if ($i != $numrows -1) { if ($i != $numrows - 1) {
$previewimage .= '_';
$alttext .= ' / '; $alttext .= ' / ';
} }
} }
if (LayoutPreviewImage::preview_exists($previewimage)) { // Generate thumbnail images.
$img = get_config('wwwroot') . 'thumb.php?type=customviewlayout&cvl=' . $previewimage; $data = array();
$data = array('data' => $img, 'alttext' => $alttext, 'newimage' => 0); $data['layout'] = $customlayout;
return $data; $data['text'] = $alttext;
}
else {
// generate thumbnail images with GD
$data= array();
$data['layout'] = $customlayout;
$data['description'] = 'test';
$data['owner'] = 1;
$previewlayoutimage = new LayoutPreviewImage($data); $previewlayoutimage = new LayoutPreviewImage($data);
$newpreviewimage = $previewlayoutimage->create_preview(); $previewimage = $previewlayoutimage->create_preview();
if ($newpreviewimage) { return $previewimage;
$img = get_config('wwwroot') . 'thumb.php?type=customviewlayout&cvl=' . $previewimage;
$data = array('data' => $img, 'alttext' => $alttext, 'newimage' => 1);
return $data;
}
else {
$msg = '<p>' . get_string('previewimagegenerationfailed', 'error') . '</p>';
$data = array('data' => $msg, 'alttext' => $alttext, 'newimage' => 0);
return $data;
}
}
} }
/*
* Adds custom layout records to database and returns an array
* with layout id and image preview.
*
* @param array
* @return array
*/
public function addcustomlayout($values) { public function addcustomlayout($values) {
global $THEME; require_once(get_config('libroot') . 'layoutpreviewimage.php');
$require = array('numrows'); $require = array('numrows');
foreach ($require as $require) { foreach ($require as $require) {
if (!array_key_exists($require, $values) || empty($values[$require])) { if (!array_key_exists($require, $values) || empty($values[$require])) {
...@@ -1100,8 +1089,6 @@ class View { ...@@ -1100,8 +1089,6 @@ class View {
$alttext = ''; $alttext = '';
$rowscolssql = ''; $rowscolssql = '';
$rowscols = array(); $rowscols = array();
$resultids = array();
$layoutid = 0;
for ($i=0; $i<$numrows; $i++) { for ($i=0; $i<$numrows; $i++) {
if (array_key_exists('row'. ($i+1), $values)) { if (array_key_exists('row'. ($i+1), $values)) {
...@@ -1200,7 +1187,8 @@ class View { ...@@ -1200,7 +1187,8 @@ class View {
for ($i=0; $i<$numrows; $i++) { for ($i=0; $i<$numrows; $i++) {
if (array_key_exists(($i+1), $rowscols)) { if (array_key_exists(($i+1), $rowscols)) {
$numcols = get_field('view_layout_columns', 'columns', 'id', $rowscols[$i+1]); $widths = get_field('view_layout_columns', 'widths', 'id', $rowscols[$i+1]);
$structure['layout']['row' . ($i + 1)] = get_string($widths, 'view');
$newrec = insert_record('view_layout_rows_columns', (object) array('viewlayout' => $newlayoutid, 'row' => ($i+1), 'columns' => $rowscols[$i+1])); $newrec = insert_record('view_layout_rows_columns', (object) array('viewlayout' => $newlayoutid, 'row' => ($i+1), 'columns' => $rowscols[$i+1]));
if (!$newrec) { if (!$newrec) {
db_rollback(); db_rollback();
...@@ -1210,7 +1198,13 @@ class View { ...@@ -1210,7 +1198,13 @@ class View {
} }
db_commit(); db_commit();
$data = array('layoutid' => $newlayoutid, 'newlayout' => 1, 'alttext' => $alttext);
// Generate new custom layout preview.
$structure['text'] = $alttext;
$layoutpreview = new LayoutPreviewImage($structure);
$preview = $layoutpreview->create_preview();
$data = array('layoutid' => $newlayoutid, 'newlayout' => 1, 'layoutpreview' => $preview);
return $data; return $data;
} }
} }
......
...@@ -38,22 +38,15 @@ $output .= '<span class="help" id="basiclayouthelp">' ...@@ -38,22 +38,15 @@ $output .= '<span class="help" id="basiclayouthelp">'
$output .= '<div id="basiclayoutoptions">'; $output .= '<div id="basiclayoutoptions">';
foreach ($templatedata['basiclayoutoptions'] as $value => $data) { foreach ($templatedata['basiclayoutoptions'] as $value => $data) {
if (is_array($data)) { $description = (isset($data['description'])) ? $data['description'] : '';
$text = $data['columns']; $text = Pieform::hsc($data['columns']);
$description = (isset($data['description'])) ? $data['description'] : '';
}
else {
$text = $data;
$description = '';
}
$text = Pieform::hsc($text);
$output .= '<div class="layoutoption">' $output .= '<div class="layoutoption">'
. '<label class="accessible-hidden" for="radiolayout_' . Pieform::hsc($value) . '">' . $text . '</label>' . '<label class="accessible-hidden" for="radiolayout_' . Pieform::hsc($value) . '">' . $text . '</label>'
. '<input type="radio" id="radiolayout_' . Pieform::hsc($value) . '" name="layoutselect"' . '<input type="radio" id="radiolayout_' . Pieform::hsc($value) . '" name="layoutselect"'
. ' value="' . Pieform::hsc($value) . '"' . ' value="' . Pieform::hsc($value) . '"'
. (($templatedata['currentlayout'] == $value) ? ' checked="checked"' : '') . '>' . (($templatedata['currentlayout'] == $value) ? ' checked="checked"' : '') . '>'
. ($description != '' ? '<div class="radio-description">' . $description . '</div>' : '') . ($description != '' ? '<div class="radio-description">' . $description . '</div>' : '')
. '<div class="layoutthumb"><img src="' . $wwwroot . 'thumb.php?type=viewlayout&amp;vl=' . Pieform::hsc($value) . '" title="' . $text . '" alt="' . $text . '"></div>' . '<div class="layoutthumb">' . $data['layout'] . '</div>'
. '</div>'; . '</div>';
} }
...@@ -79,24 +72,16 @@ for ($row = 0; $row < $templatedata['maxrows']; $row++) { ...@@ -79,24 +72,16 @@ for ($row = 0; $row < $templatedata['maxrows']; $row++) {
$output .= '<div id="viewlayout_advancedlayoutselect_row' . ($row + 1) . '">'; $output .= '<div id="viewlayout_advancedlayoutselect_row' . ($row + 1) . '">';
foreach ($templatedata['layoutoptions'] as $value => $data) { foreach ($templatedata['layoutoptions'] as $value => $data) {
if ($data['rows'] == $row+1) { if ($data['rows'] == $row+1) {
if (is_array($data)) { $description = (isset($data['description'])) ? $data['description'] : '';
$text = $data['columns']; $text = Pieform::hsc($data['columns']);
$description = (isset($data['description'])) ? $data['description'] : '';
}
else {
$text = $data;
$description = '';
}
$text = Pieform::hsc($text);
$output .= '<div class="layoutoption">' $output .= '<div class="layoutoption">'
. '<label class="accessible-hidden" for="advancedlayout_' . Pieform::hsc($value) . '">' . $text . '</label>' . '<label class="accessible-hidden" for="advancedlayout_' . Pieform::hsc($value) . '">' . $text . '</label>'
. '<input type="radio" name="advancedlayoutselect" id="advancedlayout_' . Pieform::hsc($value) . '"' . '<input type="radio" name="advancedlayoutselect" id="advancedlayout_' . Pieform::hsc($value) . '"'
. ' value="' . Pieform::hsc($value) . '"' . ' value="' . Pieform::hsc($value) . '"'
. (($templatedata['currentlayout'] == $value) ? ' checked="checked"' : '') . '>' . (($templatedata['currentlayout'] == $value) ? ' checked="checked"' : '') . '>'
. ($description != '' ? '<div class="radio-description">' . $description . '</div>' : '') . ($description != '' ? '<div class="radio-description">' . $description . '</div>' : '')
. '<div class="layoutthumb"><img src="' . $wwwroot . 'thumb.php?type=viewlayout&amp;vl=' . Pieform::hsc($value) . '" title="' . $text . '" alt="' . $text . '"></div>' . '<div class="layoutthumb">' . $data['layout'] . '</div>'
. '</div>'; . '</div>';
}