Commit 62e432fb authored by Robert Lyon's avatar Robert Lyon

Bug 1829940: Create plugin base config form / system

And create a form for the blocktype class
Allow the blocktype_installed to have a sort order

behatnotneeded

Change-Id: I2e11274a36dba59dfc193b2f5b504303a7e21a54
Signed-off-by: Robert Lyon's avatarRobert Lyon <robertl@catalyst.net.nz>
parent 51af094b
......@@ -197,7 +197,7 @@ var installplugin = (function($) {
}(jQuery));
JAVASCRIPT;
$plugins['blocktype']['configure'] = true;
$smarty = smarty();
setpageicon($smarty, 'icon-plug');
......
<?php
/**
*
* @package mahara
* @subpackage admin
* @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.
*
*/
define('INTERNAL', 1);
define('ADMIN', 1);
define('MENUITEM', 'configextensions/pluginadmin');
require(dirname(dirname(dirname(__FILE__))) . '/init.php');
define('TITLE', get_string('pluginadmin', 'admin'));
$plugintype = param_alpha('plugintype');
define('SECTION_PLUGINTYPE', $plugintype);
define('SECTION_PAGE', 'pluginconfig');
require_once(get_config('docroot') . $plugintype . '/lib.php');
$classname = 'Plugin' . ucfirst($plugintype);
if (!call_static_method($classname, 'has_base_config')) {
throw new InvalidArgumentException("$classname doesn't have config options available");
}
if (method_exists($classname, 'get_base_config_options_css')) {
$formcss = call_static_method($classname, 'get_base_config_options_css');
}
else {
$formcss = array();
}
if (method_exists($classname, 'get_base_config_options_js')) {
$formjs = call_static_method($classname, 'get_base_config_options_js');
}
else {
$formjs = '';
}
$form = call_static_method($classname, 'get_base_config_options');
if (!array_key_exists('class', $form)) {
$form['class'] = 'card card-body';
}
$form['plugintype'] = $plugintype;
$form['name'] = 'pluginconfig';
$form['pluginconfigform'] = true;
$form['jsform'] = true;
$form['successcallback'] = 'plugintypeconfig_submit';
$form['validatecallback'] = 'plugintypeconfig_validate';
$form['elements']['plugintype'] = array(
'type' => 'hidden',
'value' => $plugintype
);
$form['elements']['save'] = array(
'type' => 'submit',
'class' => 'btn-primary',
'value' => get_string('save'),
);
$form = pieform($form);
$smarty = smarty(array('js/jquery/jquery-ui/js/jquery-ui.min.js','js/jquery/jquery-ui/js/jquery-ui.touch-punch.min.js'), $formcss);
$smarty->assign('form', $form);
$smarty->assign('plugintype', $plugintype);
$heading = get_string('pluginadmin', 'admin') . ': ' . $plugintype;
$smarty->assign('PAGEHEADING', $heading);
$smarty->assign('INLINEJAVASCRIPT', $formjs);
$smarty->display('admin/extensions/pluginconfig.tpl');
function plugintypeconfig_submit(Pieform $form, $values) {
global $plugintype, $classname;
$success = true;
if (is_callable($classname . '::save_base_config_options')) {
$success = false;
try {
call_static_method($classname, 'save_base_config_options', $form, $values);
$success = true;
}
catch (Exception $e) {
$success = false;
}
}
if ($success) {
$form->json_reply(PIEFORM_OK, get_string('settingssaved'));
}
else {
$form->json_reply(PIEFORM_ERR, array('message' => get_string('settingssavefailed')));
}
}
function plugintypeconfig_validate(PieForm $form, $values) {
global $plugintype, $classname;
if (is_callable($classname . '::validate_base_config_options')) {
call_static_method($classname, 'validate_base_config_options', $form, $values);
}
}
<?php
/**
*
* @package mahara
* @subpackage core
* @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.
*
*/
define('INTERNAL', 1);
define('ADMIN', 1);
define('JSON', 1);
require(dirname(dirname(__FILE__)) . '/init.php');
$id = param_alphanum('id');
$direction = param_variable('direction', '');
$message = null;
if (!empty($direction)) {
parse_str($direction, $direction_array);
if (is_array($direction_array['row']) && !empty($direction_array['row'])) {
foreach ($direction_array['row'] as $k => $v) {
execute_sql("UPDATE {blocktype_installed_category} SET sortorder = ? WHERE blocktype = ?", array(($k + 1) * 1000, $v));
}
}
}
// return updated info
$type = array();
$blocks = get_records_sql_array("SELECT b.name, b.artefactplugin, bc.sortorder,
(SELECT COUNT(bi.*) FROM {block_instance} bi WHERE bi.blocktype = b.name) AS blockcount
FROM {blocktype_installed} b
JOIN {blocktype_installed_category} bc ON bc.blocktype = b.name
WHERE b.active = 1
AND b.name != ?
ORDER BY bc.sortorder", array('placeholder'));
foreach ($blocks as $block) {
$namespaced = blocktype_single_to_namespaced($block->name, $block->artefactplugin);
safe_require('blocktype', $block->name);
$classname = generate_class_name('blocktype', $namespaced);
$types[] = array('name' => $block->name,
'title' => call_static_method($classname, 'get_title'),
'cssicon' => call_static_method($classname, 'get_css_icon', $block->name),
'cssicontype' => call_static_method($classname, 'get_css_icon_type', $block->name),
'count' => $block->blockcount,
);
}
$smarty = smarty_core();
$smarty->assign('types', $types);
$typeslist = $smarty->fetch('blocktype:placeholder:contenttypeslist.tpl');
$smarty->assign('typeslist', $typeslist);
$typeshtml = $smarty->fetch('blocktype:placeholder:contenttypes.tpl');
$message = get_string('blocktypeupdatedsuccess', 'admin');
json_reply(false, array(
'message' => $message,
'html' => $typeshtml,
));
......@@ -86,6 +86,127 @@ abstract class PluginBlocktype extends Plugin implements IPluginBlocktype {
return false;
}
/**
* To define a pluginwide configuration
*/
public static function has_base_config() {
return true;
}
/**
* To define a pluginwide configuration
*/
public static function get_base_config_options() {
$type = array();
$blocks = get_records_sql_array("SELECT b.name, b.artefactplugin, bc.sortorder,
(SELECT COUNT(bi.*) FROM {block_instance} bi WHERE bi.blocktype = b.name) AS blockcount
FROM {blocktype_installed} b
JOIN {blocktype_installed_category} bc ON bc.blocktype = b.name
WHERE b.active = 1
AND b.name != ?
ORDER BY bc.sortorder", array('placeholder'));
foreach ($blocks as $block) {
$namespaced = blocktype_single_to_namespaced($block->name, $block->artefactplugin);
safe_require('blocktype', $namespaced);
$classname = generate_class_name('blocktype', $namespaced);
$types[] = array('name' => $block->name,
'title' => call_static_method($classname, 'get_title'),
'cssicon' => call_static_method($classname, 'get_css_icon', $block->name),
'cssicontype' => call_static_method($classname, 'get_css_icon_type', $block->name),
'count' => $block->blockcount,
);
}
$form = array(
'elements' => array(
'types' => array(
'type' => 'fieldset',
'legend' => get_string('contenttypes', 'blocktype.placeholder'),
'elements' => array(
'contenttypes' => array(
'type' => 'html',
'value' => '',
),
),
),
)
);
$smarty = smarty_core();
$smarty->assign('types', $types);
$typeslist = $smarty->fetch('blocktype:placeholder:contenttypeslist.tpl');
$smarty->assign('typeslist', $typeslist);
$typeshtml = $smarty->fetch('blocktype:placeholder:contenttypes.tpl');
$form['elements']['types']['elements']['contenttypes']['value'] = $typeshtml;
return $form;
}
/**
* To define a pluginwide configuration
*/
public static function get_base_config_options_js() {
global $USER;
$sesskey = $USER->get('sesskey');
$js = <<<EOF
$(function() {
$('#placeholderlist button').each(function() {
$(this).off('click');
$(this).on('click', function(ev) {
ev.stopPropagation();
ev.preventDefault();
});
});
var updaterows = function(option) {
var sortorder = $('#placeholderlist').sortable('serialize');
$.post(config['wwwroot'] + "blocktype/config.json.php", { sesskey: '$sesskey', id: option, direction: sortorder })
.done(function(data) {
// update the page with the new list
if (data.returnCode == '0') {
$('#placeholderlist').replaceWith(data.message.html);
if (data.message.message) {
var okmessage = $('<div id="changestatusline" class="alert alert-dismissible alert-success" role="alert"><button type="button" class="close" data-dismiss="alert" aria-label="' + get_string('Close') + '"><span aria-hidden="true">&times;</span></button><p>' + data.message.message + '</p></div>');
$('#messages').empty().append(okmessage);
}
wiresortables();
}
});
};
var wiresortables = function() {
$('#placeholderlist > div').each(function() {
$(this).prepend('<div class="handle">&nbsp;</div>');
$('.handle').css('position', 'absolute');
$('.handle').css('z-index', '3');
$('.handle').css('width', $(this).find('button').css('width'));
$('.handle').css('height', $(this).find('button').css('height'));
});
$('#placeholderlist').sortable({
items: '> div',
appendTo: '#placeholderlist',
cursor: 'move',
opacity: 0.8,
helper: 'clone',
handle: '.handle',
stop: function(e, ui) {
var id = $(ui.item).find('button').data('option');
updaterows(id);
},
})
.disableSelection()
.on("mouseenter mouseleave", function() {
$(this).css('cursor', 'move');
});
// hide the 'save' button as this form works with drag / drop saving
$('#pluginconfig_save_container').hide();
};
// init
wiresortables();
});
EOF;
return $js;
}
public static function get_theme_path($pluginname) {
if (($artefactname = blocktype_artefactplugin($pluginname))) {
// Path for block plugins that sit under an artefact
......@@ -338,7 +459,7 @@ abstract class PluginBlocktype extends Plugin implements IPluginBlocktype {
}
public static function get_blocktypes_for_category($category, View $view, $blocktype = null) {
$sql = 'SELECT bti.name, bti.artefactplugin
$sql = 'SELECT bti.name, bti.artefactplugin, btic.sortorder
FROM {blocktype_installed} bti
JOIN {blocktype_installed_category} btic ON btic.blocktype = bti.name
JOIN {blocktype_installed_viewtype} btiv ON btiv.blocktype = bti.name
......@@ -389,6 +510,7 @@ abstract class PluginBlocktype extends Plugin implements IPluginBlocktype {
'thumbnail_path' => get_config('wwwroot') . 'thumb.php?type=blocktype&bt=' . $bt->name . ((!empty($bt->artefactplugin)) ? '&ap=' . $bt->artefactplugin : ''),
'cssicon' => call_static_method($classname, 'get_css_icon', $bt->name),
'cssicontype' => call_static_method($classname, 'get_css_icon_type', $bt->name),
'sortorder' => $bt->sortorder,
);
}
}
......
......@@ -110,6 +110,10 @@ class PluginBlocktypePlaceholder extends MaharaCoreBlocktype {
$blocks = array_merge($blocks, $blocktypes);
}
$count = count($blocks);
// sort and return limit
usort($blocks, function ($a, $b) {
return $a['sortorder'] < $b['sortorder'] ? -1 : 1;
});
$blocks = array_slice($blocks, $offset, $limit);
return array($count, $blocks);
}
......
......@@ -1499,3 +1499,5 @@ $string['userdeletiondeniedsuccessful'] = 'Request denied successfully.';
$string['userdeletiondeniedunsuccessful'] = 'The attempted user account deletion denial failed.';
$string['consented'] = 'Consented';
$string['groupid'] = 'Group ID';
$string['blocktypeupdatedsuccess'] = 'Updated blocktype sort order';
......@@ -1943,4 +1943,57 @@ JS;
$this->$action($element, $xpath, "xpath_element");
}
/**
* Allows interaction with blocktype reorder page in administration.
* Seen as we can't do a drag drop easily in behat we will just reorder them via db
* and reload the page
*
* @Then I move blocktype :moveblock to before :otherblock
* @param string $moveblock The block to move
* @param string $otherblock The block to move before
*/
public function i_move_blocktype($moveblock, $otherblock) {
// Find the blocktypes.
$moveblockliteral = $this->escaper->escapeLiteral($moveblock);
$otherblockliteral = $this->escaper->escapeLiteral($otherblock);
if ($moveblockliteral == $otherblockliteral) {
throw new ExpectationException('The blocktype to move with title ' . $moveblockliteral . ' is the same as ' . $otherblockliteral, $this->getSession());
}
$blocks = array('move' => array('text' => $moveblockliteral),
'over' => array('text' => $otherblockliteral),
);
// Our blocklist form db
$blocklist = get_column_sql("SELECT b.name FROM {blocktype_installed} b
JOIN {blocktype_installed_category} bc ON bc.blocktype = b.name
WHERE b.active = 1 AND b.name != ? ORDER BY bc.sortorder", array('placeholder'));
foreach ($blocks as $k => $block) {
$xpath = "//div[contains(@id,'placeholderlist')]" .
"/div/button/div[contains(normalize-space(.), " . $block['text'] . ")]";
// Check that we have the block
try {
$blockobj = $this->find('xpath', $xpath);
}
catch (ElementNotFoundException $e) {
throw new ExpectationException('The blocktype with title ' . $block . ' was not found', $this->getSession());
}
$blocks[$k]['type'] = $blockobj->getParent()->getAttribute('data-option');
}
if (($moveblock = array_search($blocks['move']['type'], $blocklist)) !== false) {
$mover = array_slice($blocklist, $moveblock, 1);
unset($blocklist[$moveblock]);
$blocklist = array_values($blocklist);
if (($otherblock = array_search($blocks['over']['type'], $blocklist)) === false) {
throw new ExpectationException('Can not find the blocktype in db for blocktype with title ' . $blocks['over']['text'], $this->getSession());
}
array_splice($blocklist, $otherblock, 0, $mover);
foreach ($blocklist as $k => $v) {
execute_sql("UPDATE {blocktype_installed_category} SET sortorder = ? WHERE blocktype = ?", array(($k + 1) * 1000, $v));
}
}
else {
throw new ExpectationException('Can not find the blocktype in db for blocktype with title ' . $blocks['move']['text'], $this->getSession());
}
}
}
{foreach from=$types item=type}
<div class="card-option card-quarter">
<div class="card-option card-quarter" id="row_{$type.name}">
<button class="card placeholder btn-secondary" data-option="{$type.name}" title="{$type.description}" data-blockid="{$blockid}">
<div class="icon icon-lg icon-{$type.cssicon}"></div>
<div>{$type.title}</div>
<div class="icon icon-lg icon-{$type.cssicon} {$type.cssicontype}"></div>
<div>{$type.title}{if $type.count} ({$type.count}){/if}</div>
</button>
</div>
{/foreach}
......@@ -858,3 +858,8 @@ a.online-users {
#forums-list .button {
white-space: nowrap;
}
.extensions h2 .btn-group-top {
margin-top: -11px;
margin-bottom: 0;
}
{include file='header.tpl'}
<p class="lead">{str tag='pluginexplainaddremove'} {str tag='pluginexplainartefactblocktypes'}</p>
<div class="card-items js-masonry" data-masonry-options='{ "itemSelector": ".card" }'>
<div class="card-items js-masonry extensions" data-masonry-options='{ "itemSelector": ".card" }'>
{foreach from=$plugins key='plugintype' item='plugins'}
<div class="card">
<h2 class="card-header">{str tag='plugintype'}: {$plugintype}</h2>
<h2 class="card-header">{str tag='plugintype'}: {$plugintype}
{if $plugins.configure}
<div class="btn-group btn-group-top">
<a class="btn btn-secondary float-left btn-group-item" title="{str tag='configfor'} {$plugintype}" href="plugintypeconfig.php?plugintype={$plugintype}">
<span class="icon icon-cog icon-lg" role="presentation" aria-hidden="true"></span>
<span class="accessible-hidden sr-only ">{str tag='configfor'} {$plugintype}</span>
</a>
</div>
{/if}
</h2>
{assign var="installed" value=$plugins.installed}
{assign var="notinstalled" value=$plugins.notinstalled}
......
......@@ -3,6 +3,8 @@ Feature: Adding a placeholder block to a page
As a student
I need to be able to add a placeholder block to my portfolio
and then change it to be a block of my choosing
As an admin I need to be able to alter the order of blocks
to make more popular ones list first
Background:
Given the following "users" exist:
......@@ -13,7 +15,7 @@ Given the following "users" exist:
| title | description | ownertype | ownername |
| Page UserA_01 | Page 01| user | UserA |
Scenario:
Scenario: Adding a placeholder block to the page
# Logging in as a user
Given I log in as "UserA" with password "Kupuh1pa!"
And I choose "Pages and collections" in "Create" from main menu
......@@ -38,3 +40,15 @@ Scenario:
And I press "Save"
Then I should see "Mahara text block title"
Then I should see "Mahara text block content"
Scenario: Adjusting the order of the placeholder blocks
Given I log in as "Admin" with password "Kupuh1pa!"
And I choose "Plugin administration" in "Extensions" from administration menu
And I click on "Configuration for blocktype"
Then I should see "Content types"
And I move blocktype "Some HTML" to before "Text"
And I move blocktype "Comments" to before "Text"
And I move blocktype "External media" to before "Text"
And I move blocktype "PDF" to before "Text"
And I reload the page
# TODO - then go to add a placeholder block and check the order
\ No newline at end of file
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