Commit f59f89b4 authored by Marina Glancy's avatar Marina Glancy
Browse files

MDL-52869 course: use inplace_editable for activity names

parent e8952c59
......@@ -51,6 +51,7 @@ class block_site_main_menu extends block_list {
require_once($CFG->dirroot.'/course/lib.php');
$context = context_course::instance($course->id);
$isediting = $this->page->user_is_editing() && has_capability('moodle/course:manageactivities', $context);
$courserenderer = $this->page->get_renderer('core', 'course');
/// extra fast view mode
if (!$isediting) {
......@@ -69,32 +70,18 @@ class block_site_main_menu extends block_list {
}
if (!empty($cm->url)) {
$attrs = array();
$attrs['title'] = $cm->modfullname;
$attrs['class'] = $cm->extraclasses . ' activity-action';
if ($cm->onclick) {
// Get on-click attribute value if specified and decode the onclick - it
// has already been encoded for display.
$attrs['onclick'] = htmlspecialchars_decode($cm->onclick);
}
if (!$cm->visible) {
$attrs['class'] .= ' dimmed';
}
$icon = '<img src="' . $cm->get_icon_url() . '" class="icon" alt="" />';
$content = html_writer::link($cm->url, $icon . $cm->get_formatted_name(), $attrs);
$content = html_writer::div($courserenderer->course_section_cm_name($cm), 'activity');
} else {
$content = $cm->get_formatted_content(array('overflowdiv' => true, 'noclean' => true));
}
$this->content->items[] = $indent.html_writer::div($content, 'main-menu-content');
$this->content->items[] = $indent . $content;
}
}
return $this->content;
}
// Slow & hacky editing mode.
/** @var core_course_renderer $courserenderer */
$courserenderer = $this->page->get_renderer('core', 'course');
$ismoving = ismoving($course->id);
course_create_sections_if_missing($course, 0);
$modinfo = get_fast_modinfo($course);
......@@ -104,11 +91,9 @@ class block_site_main_menu extends block_list {
$strmovehere = get_string('movehere');
$strmovefull = strip_tags(get_string('movefull', '', "'$USER->activitycopyname'"));
$strcancel= get_string('cancel');
$stractivityclipboard = $USER->activitycopyname;
} else {
$strmove = get_string('move');
}
$editbuttons = '';
if ($ismoving) {
$this->content->icons[] = '<img src="'.$OUTPUT->pix_url('t/move') . '" class="iconsmall" alt="" />';
......@@ -116,7 +101,6 @@ class block_site_main_menu extends block_list {
}
if (!empty($modinfo->sections[0])) {
$options = array('overflowdiv'=>true);
foreach ($modinfo->sections[0] as $modnumber) {
$mod = $modinfo->cms[$modnumber];
if (!$mod->uservisible) {
......@@ -153,27 +137,12 @@ class block_site_main_menu extends block_list {
} else {
$indent = '';
}
$url = $mod->url;
if (!$url) {
if (!$mod->url) {
$content = $mod->get_formatted_content(array('overflowdiv' => true, 'noclean' => true));
} else {
//Accessibility: incidental image - should be empty Alt text
$attrs = array();
$attrs['title'] = $mod->modfullname;
$attrs['class'] = $mod->extraclasses . ' activity-action';
if ($mod->onclick) {
// Get on-click attribute value if specified and decode the onclick - it
// has already been encoded for display.
$attrs['onclick'] = htmlspecialchars_decode($mod->onclick);
}
if (!$mod->visible) {
$attrs['class'] .= ' dimmed';
}
$icon = '<img src="' . $mod->get_icon_url() . '" class="icon" alt="" />';
$content = html_writer::link($url, $icon . $mod->get_formatted_name(), $attrs);
$content = html_writer::div($courserenderer->course_section_cm_name($mod), ' activity');
}
$this->content->items[] = $indent.html_writer::div($content . $editbuttons, 'main-menu-content');
$this->content->items[] = $indent. $content . $editbuttons;
}
}
}
......
......@@ -6,5 +6,4 @@
.block_site_main_menu .footer { margin-top: 1em; }
.block_site_main_menu .section_add_menus noscript div { display: inline;}
.block_site_main_menu .mod-indent,
.block_site_main_menu .main-menu-content { display: table-cell; }
.block_site_main_menu .main-menu-content > .activity-action { display: block; }
.block_site_main_menu .activity { display: table-cell; }
@block @block_main_menu
Feature: Edit activities in main menu block
In order to use main menu block
As an admin
I need to add and edit activities there
@javascript
Scenario: Edit name of acitivity in-place in site main menu block
Given I log in as "admin"
And I am on site homepage
And I navigate to "Turn editing on" node in "Front page settings"
When I add a "Forum" to section "0" and I fill the form with:
| Forum name | My forum name |
And I click on "Edit title" "link" in the "//.[contains(@class,'block_site_main_menu')]//li[contains(.,'My forum name')]" "xpath_element"
And I set the field "New name for activity My forum name" to "New forum name"
And I press key "13" in the field "New name for activity My forum name"
Then I should not see "My forum name"
And I should see "New forum name"
And I follow "New forum name"
And I should not see "My forum name"
And I should see "New forum name"
......@@ -48,6 +48,7 @@ class block_social_activities extends block_list {
}
$course = $this->page->course;
$courserenderer = $this->page->get_renderer('core', 'course');
require_once($CFG->dirroot.'/course/lib.php');
......@@ -58,25 +59,18 @@ class block_social_activities extends block_list {
/// extra fast view mode
if (!$isediting) {
if (!empty($modinfo->sections[0])) {
$options = array('overflowdiv'=>true);
foreach($modinfo->sections[0] as $cmid) {
$cm = $modinfo->cms[$cmid];
if (!$cm->uservisible) {
continue;
}
$content = $cm->get_formatted_content(array('overflowdiv' => true, 'noclean' => true));
$instancename = $cm->get_formatted_name();
if (!($url = $cm->url)) {
if (!$cm->url) {
$content = $cm->get_formatted_content(array('overflowdiv' => true, 'noclean' => true));
$this->content->items[] = $content;
$this->content->icons[] = '';
} else {
$linkcss = $cm->visible ? '' : ' class="dimmed" ';
//Accessibility: incidental image - should be empty Alt text
$icon = '<img src="' . $cm->get_icon_url() . '" class="icon" alt="" />&nbsp;';
$this->content->items[] = '<a title="'.$cm->modplural.'" '.$linkcss.' '.$cm->extra.
' href="' . $url . '">' . $icon . $instancename . '</a>';
$this->content->items[] = html_writer::div($courserenderer->course_section_cm_name($cm), 'activity');
}
}
}
......@@ -85,21 +79,16 @@ class block_social_activities extends block_list {
// Slow & hacky editing mode.
/** @var core_course_renderer $courserenderer */
$courserenderer = $this->page->get_renderer('core', 'course');
$ismoving = ismoving($course->id);
$modinfo = get_fast_modinfo($course);
$section = $modinfo->get_section_info(0);
if ($ismoving) {
$strmovehere = get_string('movehere');
$strmovefull = strip_tags(get_string('movefull', '', "'$USER->activitycopyname'"));
$strcancel= get_string('cancel');
$stractivityclipboard = $USER->activitycopyname;
} else {
$strmove = get_string('move');
}
$editbuttons = '';
if ($ismoving) {
$this->content->icons[] = '&nbsp;<img align="bottom" src="'.$OUTPUT->pix_url('t/move') . '" class="iconsmall" alt="" />';
......@@ -107,7 +96,6 @@ class block_social_activities extends block_list {
}
if (!empty($modinfo->sections[0])) {
$options = array('overflowdiv'=>true);
foreach ($modinfo->sections[0] as $modnumber) {
$mod = $modinfo->cms[$modnumber];
if (!$mod->uservisible) {
......@@ -139,19 +127,13 @@ class block_social_activities extends block_list {
'<img style="height:16px; width:80px; border:0px" src="'.$OUTPUT->pix_url('movehere') . '" alt="'.$strmovehere.'" /></a>';
$this->content->icons[] = '';
}
$content = $mod->get_formatted_content(array('overflowdiv' => true, 'noclean' => true));
$instancename = $mod->get_formatted_name();
$linkcss = $mod->visible ? '' : ' class="dimmed" ';
if (!($url = $mod->url)) {
if (!$mod->url) {
$content = $mod->get_formatted_content(array('overflowdiv' => true, 'noclean' => true));
$this->content->items[] = $content . $editbuttons;
$this->content->icons[] = '';
} else {
//Accessibility: incidental image - should be empty Alt text
$icon = '<img src="' . $mod->get_icon_url() . '" class="icon" alt="" />&nbsp;';
$this->content->items[] = '<a title="' . $mod->modfullname . '" ' . $linkcss . ' ' . $mod->extra .
' href="' . $url . '">' . $icon . $instancename . '</a>' . $editbuttons;
$this->content->items[] = html_writer::div($courserenderer->course_section_cm_name($mod), 'activity') .
$editbuttons;
}
}
}
......
@block @block_social_activities
Feature: Edit activities in social activities block
In order to use social activities block
As a teacher
I need to add and edit activities there
@javascript
Scenario: Edit name of acitivity in-place in social activities block
Given the following "courses" exist:
| fullname | shortname | format |
| Course 1 | C1 | social |
And the following "users" exist:
| username | firstname | lastname |
| user1 | User | One |
And the following "course enrolments" exist:
| user | course | role |
| user1 | C1 | editingteacher |
Given I log in as "user1"
And I follow "Course 1"
And I turn editing mode on
And I set the field "Add an activity to section 'section 0'" to "Forum"
And I set the field "Forum name" to "My forum name"
And I press "Save and return to course"
And I click on "Edit title" "link" in the "//.[contains(@class,'block_social_activities')]//li[contains(.,'My forum name')]" "xpath_element"
And I set the field "New name for activity My forum name" to "New forum name"
And I press key "13" in the field "New name for activity My forum name"
Then I should not see "My forum name" in the "Social activities" "block"
And I should see "New forum name"
And I follow "New forum name"
And I should not see "My forum name"
And I should see "New forum name"
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Contains class core_tag\output\course_module_name
*
* @package core_course
* @copyright 2016 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_course\output;
use context_module;
use lang_string;
use cm_info;
/**
* Class to prepare a course module name for display and in-place editing
*
* @package core_course
* @copyright 2016 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class course_module_name extends \core\output\inplace_editable {
/** @var cm_info */
protected $cm;
/** @var array */
protected $displayoptions;
/**
* Constructor.
*
* @param cm_info $cm
* @param bool $editable
* @param array $displayoptions
*/
public function __construct(cm_info $cm, $editable, $displayoptions = array()) {
$this->cm = $cm;
$this->displayoptions = $displayoptions;
$value = $cm->name;
$edithint = new lang_string('edittitle');
$editlabel = new lang_string('newactivityname', '', $cm->get_formatted_name());
$editable = $editable && has_capability('moodle/course:manageactivities',
context_module::instance($cm->id));
parent::__construct(
'core_course', 'activityname', $cm->id, $editable, $value, $value, $edithint, $editlabel);
}
/**
* Export this data so it can be used as the context for a mustache template (core/inplace_editable).
*
* @param renderer_base $output typically, the renderer that's calling this function
* @return array data context for a mustache template
*/
public function export_for_template(\renderer_base $output) {
global $PAGE;
$courserenderer = $PAGE->get_renderer('core', 'course');
$this->displayvalue = $courserenderer->course_section_cm_name_title($this->cm, $this->displayoptions);
if (strval($this->displayvalue) === '') {
$this->editable = false;
}
return parent::export_for_template($output);
}
/**
* Updates course module name
*
* @param int $itemid course module id
* @param string $newvalue new name
* @return static
*/
public static function update($itemid, $newvalue) {
list($course, $cm) = get_course_and_cm_from_cmid($itemid);
$context = context_module::instance($cm->id);
// Check access.
require_login($course, false, $cm, true, true);
require_capability('moodle/course:manageactivities', $context);
// Update value.
set_coursemodule_name($cm->id, $newvalue);
// Return instance.
$cm = get_fast_modinfo($course)->get_cm($cm->id);
return new static($cm, true);
}
}
......@@ -1605,6 +1605,50 @@ function set_coursemodule_visible($id, $visible) {
return true;
}
/**
* Changes the course module name
*
* @param int $id course module id
* @param string $name new value for a name
* @return bool whether a change was made
*/
function set_coursemodule_name($id, $name) {
global $CFG, $DB;
require_once($CFG->libdir . '/gradelib.php');
$cm = get_coursemodule_from_id('', $id, 0, false, MUST_EXIST);
$module = new \stdClass();
$module->id = $cm->instance;
// Escape strings as they would be by mform.
if (!empty($CFG->formatstringstriptags)) {
$module->name = clean_param($name, PARAM_TEXT);
} else {
$module->name = clean_param($name, PARAM_CLEANHTML);
}
if ($module->name === $cm->name || strval($module->name) === '') {
return false;
}
if (\core_text::strlen($module->name) > 255) {
throw new \moodle_exception('maximumchars', 'moodle', '', 255);
}
$module->timemodified = time();
$DB->update_record($cm->modname, $module);
$cm->name = $module->name;
\core\event\course_module_updated::create_from_cm($cm)->trigger();
rebuild_course_cache($cm->course, true);
// Attempt to update the grade item if relevant.
$grademodule = $DB->get_record($cm->modname, array('id' => $cm->instance));
$grademodule->cmidnumber = $cm->idnumber;
$grademodule->modname = $cm->modname;
grade_update_mod_grades($grademodule);
return true;
}
/**
* This function will handle the whole deletion process of a module. This includes calling
* the modules delete_instance function, deleting files, events, grades, conditional data,
......@@ -2241,53 +2285,6 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) {
return $actions;
}
/**
* Returns the rename action.
*
* @param cm_info $mod The module to produce editing buttons for
* @param int $sr The section to link back to (used for creating the links)
* @return The markup for the rename action, or an empty string if not available.
*/
function course_get_cm_rename_action(cm_info $mod, $sr = null) {
global $COURSE, $OUTPUT;
static $str;
static $baseurl;
$modcontext = context_module::instance($mod->id);
$hasmanageactivities = has_capability('moodle/course:manageactivities', $modcontext);
if (!isset($str)) {
$str = get_strings(array('edittitle'));
}
if (!isset($baseurl)) {
$baseurl = new moodle_url('/course/mod.php', array('sesskey' => sesskey()));
}
if ($sr !== null) {
$baseurl->param('sr', $sr);
}
// AJAX edit title.
if ($mod->has_view() && $hasmanageactivities && course_ajax_enabled($COURSE) &&
(($mod->course == $COURSE->id) || ($mod->course == SITEID))) {
// we will not display link if we are on some other-course page (where we should not see this module anyway)
return html_writer::span(
html_writer::link(
new moodle_url($baseurl, array('update' => $mod->id)),
$OUTPUT->pix_icon('t/editstring', '', 'moodle', array('class' => 'iconsmall visibleifjs', 'title' => '')),
array(
'class' => 'editing_title',
'data-action' => 'edittitle',
'title' => $str->edittitle,
)
)
);
}
return '';
}
/**
* Returns the move action.
*
......@@ -3897,3 +3894,17 @@ function course_get_tagged_courses($tag, $exclusivemode = false, $fromctx = 0, $
return new core_tag\output\tagindex($tag, 'core', 'course', $content,
$exclusivemode, $fromctx, $ctx, $rec, $page, $totalpages);
}
/**
* Implements callback inplace_editable() allowing to edit values in-place
*
* @param string $itemtype
* @param int $itemid
* @param mixed $newvalue
* @return \core\output\inplace_editable
*/
function core_course_inplace_editable($itemtype, $itemid, $newvalue) {
if ($itemtype === 'activityname') {
return \core_course\output\course_module_name::update($itemid, $newvalue);
}
}
......@@ -733,10 +733,34 @@ class core_course_renderer extends plugin_renderer_base {
* @return string
*/
public function course_section_cm_name(cm_info $mod, $displayoptions = array()) {
global $CFG;
if ((!$mod->uservisible && empty($mod->availableinfo)) || !$mod->url) {
// Nothing to be displayed to the user.
return '';
}
// Render element that allows to edit activity name inline. It calls {@link course_section_cm_name_title()}
// to get the display title of the activity.
$tmpl = new \core_course\output\course_module_name($mod, $this->page->user_is_editing(), $displayoptions);
return $this->output->render_from_template('core/inplace_editable', $tmpl->export_for_template($this->output));
}
/**
* Renders html to display a name with the link to the course module on a course page
*
* If module is unavailable for user but still needs to be displayed
* in the list, just the name is returned without a link
*
* Note, that for course modules that never have separate pages (i.e. labels)
* this function return an empty string
*
* @param cm_info $mod
* @param array $displayoptions
* @return string
*/
public function course_section_cm_name_title(cm_info $mod, $displayoptions = array()) {
$output = '';
if (!$mod->uservisible && empty($mod->availableinfo)) {
// nothing to be displayed to the user
// Nothing to be displayed to the user.
return $output;
}
$url = $mod->url;
......@@ -985,10 +1009,6 @@ class core_course_renderer extends plugin_renderer_base {
$output .= $cmname;
if ($this->page->user_is_editing()) {
$output .= ' ' . course_get_cm_rename_action($mod, $sectionreturn);
}
// Module can put text after the link (e.g. forum unread)
$output .= $mod->afterlink;
......
......@@ -150,49 +150,6 @@ switch($requestmethod) {
$isvisible = moveto_module($cm, $section, $beforemod);
echo json_encode(array('visible' => (bool) $isvisible));
break;
case 'gettitle':
require_capability('moodle/course:manageactivities', $modcontext);
$cm = get_coursemodule_from_id('', $id, 0, false, MUST_EXIST);
$module = new stdClass();
$module->id = $cm->instance;
// Don't pass edit strings through multilang filters - we need the entire string
echo json_encode(array('instancename' => $cm->name));
break;
case 'updatetitle':
require_capability('moodle/course:manageactivities', $modcontext);
require_once($CFG->libdir . '/gradelib.php');
$cm = get_coursemodule_from_id('', $id, 0, false, MUST_EXIST);
$module = new stdClass();
$module->id = $cm->instance;
// Escape strings as they would be by mform
if (!empty($CFG->formatstringstriptags)) {
$module->name = clean_param($title, PARAM_TEXT);
} else {
$module->name = clean_param($title, PARAM_CLEANHTML);
}
if (strval($module->name) !== '') {
$DB->update_record($cm->modname, $module);
$cm->name = $module->name;
\core\event\course_module_updated::create_from_cm($cm, $modcontext)->trigger();
rebuild_course_cache($cm->course);
} else {
$module->name = $cm->name;
}
// Attempt to update the grade item if relevant
$grademodule = $DB->get_record($cm->modname, array('id' => $cm->instance));
$grademodule->cmidnumber = $cm->idnumber;
$grademodule->modname = $cm->modname;
grade_update_mod_grades($grademodule);
// We need to return strings after they've been through filters for multilang
$stringoptions = new stdClass;
$stringoptions->context = $coursecontext;
echo json_encode(array('instancename' => html_entity_decode(format_string($module->name, true, $stringoptions))));
break;
}
break;
......
@core @core_course
Feature: Edit activity name in-place
In order to quickly edit activity name
As a teacher
I need to use inplace editing
@javascript
Scenario: Edit activity name in-place
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "courses" exist:
| fullname | shortname | format |
| Course 1 | C1 | topics |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
When I log in as "teacher1"
And I follow "Course 1"
And I turn editing mode on
And I add a "Forum" to section "1" and I fill the form with:
| Forum name | Test forum name |
| Description | Test forum description |
# Rename activity
And I click on "Edit title" "link" in the "//div[contains(@class,'activityinstance') and contains(.,'Test forum name')]" "xpath_element"
And I set the field "New name for activity Test forum name" to "Good news"
And I press key "13" in the field "New name for activity Test forum name"
Then I should not see "Test forum name" in the ".course-content" "css_element"
And "New name for activity Test forum name" "field" should not exist
And I should see "Good news"
And I follow "Course 1"
And I should see "Good news"
And I should not see "Test forum name"