Merge branch 'wip-MDL-4782-master-3' of https://github.com/marinaglancy/moodle
This commit is contained in:
@@ -54,4 +54,10 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
|
||||
|
||||
$optionalsubsystems->add(new admin_setting_configcheckbox('enableglobalsearch', new lang_string('enableglobalsearch', 'admin'),
|
||||
new lang_string('enableglobalsearch_desc', 'admin'), 0, 1, 0));
|
||||
|
||||
$choices = array();
|
||||
$choices[0] = new lang_string('no');
|
||||
$choices[1] = new lang_string('yes');
|
||||
$optionalsubsystems->add(new admin_setting_configselect('allowstealth', new lang_string('allowstealthmodules'),
|
||||
new lang_string('allowstealthmodules_help'), 0, $choices));
|
||||
}
|
||||
|
||||
@@ -264,7 +264,7 @@ class backup_module_structure_step extends backup_structure_step {
|
||||
|
||||
$module = new backup_nested_element('module', array('id', 'version'), array(
|
||||
'modulename', 'sectionid', 'sectionnumber', 'idnumber',
|
||||
'added', 'score', 'indent', 'visible',
|
||||
'added', 'score', 'indent', 'visible', 'visibleoncoursepage',
|
||||
'visibleold', 'groupmode', 'groupingid',
|
||||
'completion', 'completiongradeitemnumber', 'completionview', 'completionexpected',
|
||||
'availability', 'showdescription'));
|
||||
|
||||
@@ -59,7 +59,7 @@ class block_site_main_menu extends block_list {
|
||||
if (!empty($modinfo->sections[0])) {
|
||||
foreach($modinfo->sections[0] as $cmid) {
|
||||
$cm = $modinfo->cms[$cmid];
|
||||
if (!$cm->uservisible) {
|
||||
if (!$cm->uservisible || !$cm->is_visible_on_course_page()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ class block_site_main_menu extends block_list {
|
||||
if (!empty($cm->url)) {
|
||||
$content = html_writer::div($courserenderer->course_section_cm_name($cm), 'activity');
|
||||
} else {
|
||||
$content = $cm->get_formatted_content(array('overflowdiv' => true, 'noclean' => true));
|
||||
$content = $courserenderer->course_section_cm_text($cm);
|
||||
}
|
||||
|
||||
$this->content->items[] = $indent . html_writer::div($content, 'main-menu-content');
|
||||
@@ -103,7 +103,7 @@ class block_site_main_menu extends block_list {
|
||||
if (!empty($modinfo->sections[0])) {
|
||||
foreach ($modinfo->sections[0] as $modnumber) {
|
||||
$mod = $modinfo->cms[$modnumber];
|
||||
if (!$mod->uservisible) {
|
||||
if (!$mod->uservisible || !$mod->is_visible_on_course_page()) {
|
||||
continue;
|
||||
}
|
||||
if (!$ismoving) {
|
||||
@@ -138,7 +138,7 @@ class block_site_main_menu extends block_list {
|
||||
$indent = '';
|
||||
}
|
||||
if (!$mod->url) {
|
||||
$content = $mod->get_formatted_content(array('overflowdiv' => true, 'noclean' => true));
|
||||
$content = $courserenderer->course_section_cm_text($mod);
|
||||
} else {
|
||||
$content = html_writer::div($courserenderer->course_section_cm_name($mod), ' activity');
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@block @block_main_menu
|
||||
@block @block_site_main_menu
|
||||
Feature: Add URL to main menu block
|
||||
In order to add helpful resources for students
|
||||
As a admin
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Behat steps definitions for block site main menu
|
||||
*
|
||||
* @package block_site_main_menu
|
||||
* @category test
|
||||
* @copyright 2016 Marina Glancy
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
// NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
|
||||
|
||||
require_once(__DIR__ . '/../../../../lib/behat/behat_base.php');
|
||||
|
||||
use Behat\Mink\Exception\ExpectationException as ExpectationException,
|
||||
Behat\Mink\Exception\DriverException as DriverException,
|
||||
Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException;
|
||||
|
||||
/**
|
||||
* Behat steps definitions for block site main menu
|
||||
*
|
||||
* @package block_site_main_menu
|
||||
* @category test
|
||||
* @copyright 2016 Marina Glancy
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class behat_block_site_main_menu extends behat_base {
|
||||
|
||||
/**
|
||||
* Returns the DOM node of the activity in the site menu block
|
||||
*
|
||||
* @throws ElementNotFoundException Thrown by behat_base::find
|
||||
* @param string $activityname The activity name
|
||||
* @return NodeElement
|
||||
*/
|
||||
protected function get_site_menu_activity_node($activityname) {
|
||||
$activityname = behat_context_helper::escape($activityname);
|
||||
$xpath = "//*[contains(concat(' ',normalize-space(@class),' '),' block_site_main_menu ')]//li[contains(., $activityname)]";
|
||||
|
||||
return $this->find('xpath', $xpath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the specified activity's action menu contains an item.
|
||||
*
|
||||
* @Then /^"(?P<activity_name_string>(?:[^"]|\\")*)" activity in site main menu block should have "(?P<icon_name_string>(?:[^"]|\\")*)" editing icon$/
|
||||
* @param string $activityname
|
||||
* @param string $iconname
|
||||
*/
|
||||
public function activity_in_site_main_menu_block_should_have_editing_icon($activityname, $iconname) {
|
||||
$activitynode = $this->get_site_menu_activity_node($activityname);
|
||||
|
||||
$notfoundexception = new ExpectationException('"' . $activityname . '" doesn\'t have a "' .
|
||||
$iconname . '" editing icon', $this->getSession());
|
||||
$this->find('named_partial', array('link', $iconname), $notfoundexception, $activitynode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the specified activity's action menu contains an item.
|
||||
*
|
||||
* @Then /^"(?P<activity_name_string>(?:[^"]|\\")*)" activity in site main menu block should not have "(?P<icon_name_string>(?:[^"]|\\")*)" editing icon$/
|
||||
* @param string $activityname
|
||||
* @param string $iconname
|
||||
*/
|
||||
public function activity_in_site_main_menu_block_should_not_have_editing_icon($activityname, $iconname) {
|
||||
$activitynode = $this->get_site_menu_activity_node($activityname);
|
||||
|
||||
try {
|
||||
$this->find('named_partial', array('link', $iconname), false, $activitynode);
|
||||
throw new ExpectationException('"' . $activityname . '" has a "' . $iconname .
|
||||
'" editing icon when it should not', $this->getSession());
|
||||
} catch (ElementNotFoundException $e) {
|
||||
// This is good, the menu item should not be there.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clicks on the specified element of the activity. You should be in the course page with editing mode turned on.
|
||||
*
|
||||
* @Given /^I click on "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>(?:[^"]|\\")*)" in the "(?P<activity_name_string>(?:[^"]|\\")*)" activity in site main menu block$/
|
||||
* @param string $element
|
||||
* @param string $selectortype
|
||||
* @param string $activityname
|
||||
*/
|
||||
public function i_click_on_in_the_activity_in_site_main_menu_block($element, $selectortype, $activityname) {
|
||||
$element = $this->get_site_menu_activity_element($element, $selectortype, $activityname);
|
||||
$element->click();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clicks on the specified element inside the activity container.
|
||||
*
|
||||
* @throws ElementNotFoundException
|
||||
* @param string $element
|
||||
* @param string $selectortype
|
||||
* @param string $activityname
|
||||
* @return NodeElement
|
||||
*/
|
||||
protected function get_site_menu_activity_element($element, $selectortype, $activityname) {
|
||||
$activitynode = $this->get_site_menu_activity_node($activityname);
|
||||
|
||||
// Transforming to Behat selector/locator.
|
||||
list($selector, $locator) = $this->transform_selector($selectortype, $element);
|
||||
$exception = new ElementNotFoundException($this->getSession(), '"' . $element . '" "' .
|
||||
$selectortype . '" in "' . $activityname . '" ');
|
||||
|
||||
return $this->find($selector, $locator, $exception, $activitynode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the specified activity is hidden.
|
||||
*
|
||||
* @Then /^"(?P<activity_name_string>(?:[^"]|\\")*)" activity in site main menu block should be hidden$/
|
||||
* @param string $activityname
|
||||
*/
|
||||
public function activity_in_site_main_menu_block_should_be_hidden($activityname) {
|
||||
$this->get_site_menu_activity_element("a.dimmed", "css_element", $activityname);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the specified activity is hidden.
|
||||
*
|
||||
* @Then /^"(?P<activity_name_string>(?:[^"]|\\")*)" activity in site main menu block should be available but hidden from course page$/
|
||||
* @param string $activityname
|
||||
*/
|
||||
public function activity_in_site_main_menu_block_should_be_available_but_hidden_from_course_page($activityname) {
|
||||
$this->get_site_menu_activity_element("a.stealth", "css_element", $activityname);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens an activity actions menu if it is not already opened.
|
||||
*
|
||||
* @Given /^I open "(?P<activity_name_string>(?:[^"]|\\")*)" actions menu in site main menu block$/
|
||||
* @throws DriverException The step is not available when Javascript is disabled
|
||||
* @param string $activityname
|
||||
*/
|
||||
public function i_open_actions_menu_in_site_main_menu_block($activityname) {
|
||||
$activityname = behat_context_helper::escape($activityname);
|
||||
$xpath = "//*[contains(concat(' ',normalize-space(@class),' '),' block_site_main_menu ')]//li[contains(., $activityname)]";
|
||||
$this->execute('behat_action_menu::i_open_the_action_menu_in', [$xpath, 'xpath_element']);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
@block @block_main_menu
|
||||
@block @block_site_main_menu
|
||||
Feature: Edit activities in main menu block
|
||||
In order to use main menu block
|
||||
As an admin
|
||||
@@ -12,7 +12,7 @@ Feature: Edit activities in main menu block
|
||||
And I add the "Main menu" block
|
||||
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 click on "Edit title" "link" in the "My forum name" activity in site main menu block
|
||||
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"
|
||||
@@ -20,3 +20,49 @@ Feature: Edit activities in main menu block
|
||||
And I follow "New forum name"
|
||||
And I should not see "My forum name"
|
||||
And I should see "New forum name"
|
||||
|
||||
@javascript
|
||||
Scenario: Activities in main menu block can be made available but not visible on a course page
|
||||
And I log in as "admin"
|
||||
And I set the following administration settings values:
|
||||
| allowstealth | 1 |
|
||||
And I am on site homepage
|
||||
And I navigate to "Turn editing on" node in "Front page settings"
|
||||
And I add the "Main menu" block
|
||||
When I add a "Forum" to section "0" and I fill the form with:
|
||||
| Forum name | Visible forum |
|
||||
When I add a "Forum" to section "0" and I fill the form with:
|
||||
| Forum name | My forum name |
|
||||
And "My forum name" activity in site main menu block should have "Hide" editing icon
|
||||
And "My forum name" activity in site main menu block should not have "Show" editing icon
|
||||
And "My forum name" activity in site main menu block should not have "Make available" editing icon
|
||||
And "My forum name" activity in site main menu block should not have "Make unavailable" editing icon
|
||||
And I open "My forum name" actions menu in site main menu block
|
||||
And I click on "Hide" "link" in the "My forum name" activity in site main menu block
|
||||
And "My forum name" activity in site main menu block should be hidden
|
||||
And "My forum name" activity in site main menu block should not have "Hide" editing icon
|
||||
And "My forum name" activity in site main menu block should have "Show" editing icon
|
||||
And "My forum name" activity in site main menu block should have "Make available" editing icon
|
||||
And "My forum name" activity in site main menu block should not have "Make unavailable" editing icon
|
||||
And I open "My forum name" actions menu in site main menu block
|
||||
And I click on "Make available" "link" in the "My forum name" activity in site main menu block
|
||||
And "My forum name" activity in site main menu block should be available but hidden from course page
|
||||
And "My forum name" activity in site main menu block should not have "Hide" editing icon
|
||||
And "My forum name" activity in site main menu block should have "Show" editing icon
|
||||
And "My forum name" activity in site main menu block should not have "Make available" editing icon
|
||||
And "My forum name" activity in site main menu block should have "Make unavailable" editing icon
|
||||
# Make sure that "Visible" dropdown in the edit menu has three options.
|
||||
And I open "My forum name" actions menu in site main menu block
|
||||
And I click on "Edit settings" "link" in the "My forum name" activity in site main menu block
|
||||
And I expand all fieldsets
|
||||
And the "Visible" select box should contain "Show"
|
||||
And the "Visible" select box should contain "Hidden from students"
|
||||
And the "Visible" select box should not contain "Hide"
|
||||
And the field "Visible" matches value "Available but not displayed on course page"
|
||||
And I press "Save and return to course"
|
||||
And "My forum name" activity in site main menu block should be available but hidden from course page
|
||||
And I navigate to "Turn editing off" node in "Front page settings"
|
||||
And "My forum name" activity in site main menu block should be available but hidden from course page
|
||||
And I log out
|
||||
And I should not see "My forum name" in the "Main menu" "block"
|
||||
And I should see "Visible forum" in the "Main menu" "block"
|
||||
|
||||
@@ -61,12 +61,12 @@ class block_social_activities extends block_list {
|
||||
if (!empty($modinfo->sections[0])) {
|
||||
foreach($modinfo->sections[0] as $cmid) {
|
||||
$cm = $modinfo->cms[$cmid];
|
||||
if (!$cm->uservisible) {
|
||||
if (!$cm->uservisible || !$cm->is_visible_on_course_page()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$cm->url) {
|
||||
$content = $cm->get_formatted_content(array('overflowdiv' => true, 'noclean' => true));
|
||||
$content = $courserenderer->course_section_cm_text($cm);
|
||||
$this->content->items[] = $content;
|
||||
$this->content->icons[] = '';
|
||||
} else {
|
||||
@@ -98,7 +98,7 @@ class block_social_activities extends block_list {
|
||||
if (!empty($modinfo->sections[0])) {
|
||||
foreach ($modinfo->sections[0] as $modnumber) {
|
||||
$mod = $modinfo->cms[$modnumber];
|
||||
if (!$mod->uservisible) {
|
||||
if (!$mod->uservisible || !$mod->is_visible_on_course_page()) {
|
||||
continue;
|
||||
}
|
||||
if (!$ismoving) {
|
||||
@@ -128,7 +128,7 @@ class block_social_activities extends block_list {
|
||||
$this->content->icons[] = '';
|
||||
}
|
||||
if (!$mod->url) {
|
||||
$content = $mod->get_formatted_content(array('overflowdiv' => true, 'noclean' => true));
|
||||
$content = $courserenderer->course_section_cm_text($mod);
|
||||
$this->content->items[] = $content . $editbuttons;
|
||||
$this->content->icons[] = '';
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Behat steps definitions for block social activities
|
||||
*
|
||||
* @package block_social_activities
|
||||
* @category test
|
||||
* @copyright 2016 Marina Glancy
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
// NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
|
||||
|
||||
require_once(__DIR__ . '/../../../../lib/behat/behat_base.php');
|
||||
|
||||
use Behat\Mink\Exception\ExpectationException as ExpectationException,
|
||||
Behat\Mink\Exception\DriverException as DriverException,
|
||||
Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException;
|
||||
|
||||
/**
|
||||
* Behat steps definitions for block social activities
|
||||
*
|
||||
* @package block_social_activities
|
||||
* @category test
|
||||
* @copyright 2016 Marina Glancy
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class behat_block_social_activities extends behat_base {
|
||||
|
||||
/**
|
||||
* Returns the DOM node of the activity in the social activities block
|
||||
*
|
||||
* @throws ElementNotFoundException Thrown by behat_base::find
|
||||
* @param string $activityname The activity name
|
||||
* @return NodeElement
|
||||
*/
|
||||
protected function get_social_block_activity_node($activityname) {
|
||||
$activityname = behat_context_helper::escape($activityname);
|
||||
$xpath = "//*[contains(concat(' ',normalize-space(@class),' '),' block_social_activities ')]//li[contains(., $activityname)]";
|
||||
|
||||
return $this->find('xpath', $xpath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the specified activity's action menu contains an item.
|
||||
*
|
||||
* @Then /^"(?P<activity_name_string>(?:[^"]|\\")*)" activity in social activities block should have "(?P<icon_name_string>(?:[^"]|\\")*)" editing icon$/
|
||||
* @param string $activityname
|
||||
* @param string $iconname
|
||||
*/
|
||||
public function activity_in_social_activities_block_should_have_editing_icon($activityname, $iconname) {
|
||||
$activitynode = $this->get_social_block_activity_node($activityname);
|
||||
|
||||
$notfoundexception = new ExpectationException('"' . $activityname . '" doesn\'t have a "' .
|
||||
$iconname . '" editing icon', $this->getSession());
|
||||
$this->find('named_partial', array('link', $iconname), $notfoundexception, $activitynode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the specified activity's action menu contains an item.
|
||||
*
|
||||
* @Then /^"(?P<activity_name_string>(?:[^"]|\\")*)" activity in social activities block should not have "(?P<icon_name_string>(?:[^"]|\\")*)" editing icon$/
|
||||
* @param string $activityname
|
||||
* @param string $iconname
|
||||
*/
|
||||
public function activity_in_social_activities_block_should_not_have_editing_icon($activityname, $iconname) {
|
||||
$activitynode = $this->get_social_block_activity_node($activityname);
|
||||
|
||||
try {
|
||||
$this->find('named_partial', array('link', $iconname), false, $activitynode);
|
||||
throw new ExpectationException('"' . $activityname . '" has a "' . $iconname .
|
||||
'" editing icon when it should not', $this->getSession());
|
||||
} catch (ElementNotFoundException $e) {
|
||||
// This is good, the menu item should not be there.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clicks on the specified element of the activity. You should be in the course page with editing mode turned on.
|
||||
*
|
||||
* @Given /^I click on "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>(?:[^"]|\\")*)" in the "(?P<activity_name_string>(?:[^"]|\\")*)" activity in social activities block$/
|
||||
* @param string $element
|
||||
* @param string $selectortype
|
||||
* @param string $activityname
|
||||
*/
|
||||
public function i_click_on_in_the_activity_in_social_activities_block($element, $selectortype, $activityname) {
|
||||
$element = $this->get_social_block_activity_element($element, $selectortype, $activityname);
|
||||
$element->click();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clicks on the specified element inside the activity container.
|
||||
*
|
||||
* @throws ElementNotFoundException
|
||||
* @param string $element
|
||||
* @param string $selectortype
|
||||
* @param string $activityname
|
||||
* @return NodeElement
|
||||
*/
|
||||
protected function get_social_block_activity_element($element, $selectortype, $activityname) {
|
||||
$activitynode = $this->get_social_block_activity_node($activityname);
|
||||
|
||||
// Transforming to Behat selector/locator.
|
||||
list($selector, $locator) = $this->transform_selector($selectortype, $element);
|
||||
$exception = new ElementNotFoundException($this->getSession(), '"' . $element . '" "' .
|
||||
$selectortype . '" in "' . $activityname . '" ');
|
||||
|
||||
return $this->find($selector, $locator, $exception, $activitynode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the specified activity is hidden.
|
||||
*
|
||||
* @Then /^"(?P<activity_name_string>(?:[^"]|\\")*)" activity in social activities block should be hidden$/
|
||||
* @param string $activityname
|
||||
*/
|
||||
public function activity_in_social_activities_block_should_be_hidden($activityname) {
|
||||
$this->get_social_block_activity_element("a.dimmed", "css_element", $activityname);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the specified activity is hidden.
|
||||
*
|
||||
* @Then /^"(?P<activity_name_string>(?:[^"]|\\")*)" activity in social activities block should be available but hidden from course page$/
|
||||
* @param string $activityname
|
||||
*/
|
||||
public function activity_in_social_activities_block_should_be_available_but_hidden_from_course_page($activityname) {
|
||||
$this->get_social_block_activity_element("a.stealth", "css_element", $activityname);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens an activity actions menu if it is not already opened.
|
||||
*
|
||||
* @Given /^I open "(?P<activity_name_string>(?:[^"]|\\")*)" actions menu in social activities block$/
|
||||
* @throws DriverException The step is not available when Javascript is disabled
|
||||
* @param string $activityname
|
||||
*/
|
||||
public function i_open_actions_menu_in_social_activities_block($activityname) {
|
||||
$activityname = behat_context_helper::escape($activityname);
|
||||
$xpath = "//*[contains(concat(' ',normalize-space(@class),' '),' block_social_activities ')]//li[contains(., $activityname)]";
|
||||
$this->execute('behat_action_menu::i_open_the_action_menu_in', [$xpath, 'xpath_element']);
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,31 @@
|
||||
@block @block_social_activities
|
||||
@block @block_social_activities @format_social
|
||||
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
|
||||
Background:
|
||||
Given the following "courses" exist:
|
||||
| fullname | shortname | format |
|
||||
| Course 1 | C1 | social |
|
||||
And the following "users" exist:
|
||||
| username | firstname | lastname |
|
||||
| user1 | User | One |
|
||||
| student1 | Student | One |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| user1 | C1 | editingteacher |
|
||||
| student1 | C1 | student |
|
||||
|
||||
@javascript
|
||||
Scenario: Edit name of acitivity in-place in social activities block
|
||||
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 click on "Edit title" "link" in the "My forum name" activity in social activities block
|
||||
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"
|
||||
@@ -29,3 +33,56 @@ Feature: Edit activities in social activities block
|
||||
And I follow "New forum name"
|
||||
And I should not see "My forum name"
|
||||
And I should see "New forum name"
|
||||
|
||||
@javascript
|
||||
Scenario: Activities in social activities block can be made available but not visible on a course page
|
||||
And I log in as "admin"
|
||||
And I set the following administration settings values:
|
||||
| allowstealth | 1 |
|
||||
And I log out
|
||||
And I log in as "user1"
|
||||
And I follow "Course 1"
|
||||
And I turn editing mode on
|
||||
And I add the "Recent activity" block
|
||||
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 "My forum name" activity in social activities block should have "Hide" editing icon
|
||||
And "My forum name" activity in social activities block should not have "Show" editing icon
|
||||
And "My forum name" activity in social activities block should not have "Make available" editing icon
|
||||
And "My forum name" activity in social activities block should not have "Make unavailable" editing icon
|
||||
And I wait until the page is ready
|
||||
And I open "My forum name" actions menu in social activities block
|
||||
And I click on "Hide" "link" in the "My forum name" activity in social activities block
|
||||
And "My forum name" activity in social activities block should be hidden
|
||||
And "My forum name" activity in social activities block should not have "Hide" editing icon
|
||||
And "My forum name" activity in social activities block should have "Show" editing icon
|
||||
And "My forum name" activity in social activities block should have "Make available" editing icon
|
||||
And "My forum name" activity in social activities block should not have "Make unavailable" editing icon
|
||||
And I open "My forum name" actions menu in social activities block
|
||||
And I click on "Make available" "link" in the "My forum name" activity in social activities block
|
||||
And "My forum name" activity in social activities block should be available but hidden from course page
|
||||
And "My forum name" activity in social activities block should not have "Hide" editing icon
|
||||
And "My forum name" activity in social activities block should have "Show" editing icon
|
||||
And "My forum name" activity in social activities block should not have "Make available" editing icon
|
||||
And "My forum name" activity in social activities block should have "Make unavailable" editing icon
|
||||
# Make sure that "Visible" dropdown in the edit menu has three options.
|
||||
And I open "My forum name" actions menu in social activities block
|
||||
And I click on "Edit settings" "link" in the "My forum name" activity in social activities block
|
||||
And I expand all fieldsets
|
||||
And the "Visible" select box should contain "Show"
|
||||
And the "Visible" select box should contain "Hidden from students"
|
||||
And the "Visible" select box should not contain "Hide"
|
||||
And the field "Visible" matches value "Available but not displayed on course page"
|
||||
And I press "Save and return to course"
|
||||
And "My forum name" activity in social activities block should be available but hidden from course page
|
||||
And I turn editing mode off
|
||||
And "My forum name" activity in social activities block should be available but hidden from course page
|
||||
And I log out
|
||||
# Student will not see the module on the course page but can access it from other reports and blocks:
|
||||
And I log in as "student1"
|
||||
And I follow "Course 1"
|
||||
And I should not see "My forum name" in the "Social activities" "block"
|
||||
And I click on "My forum name" "link" in the "Recent activity" "block"
|
||||
And I should see "My forum name" in the ".breadcrumb" "css_element"
|
||||
And I log out
|
||||
|
||||
@@ -42,7 +42,8 @@ Feature: Restrict activity availability through date conditions
|
||||
And I am on site homepage
|
||||
And I follow "Course 1"
|
||||
Then I should see "Available from 31 December 2037"
|
||||
And "Test assignment 1" activity should be hidden
|
||||
And "Test assignment 1" activity should be dimmed
|
||||
And "Test assignment 1" "link" should not exist
|
||||
And I log out
|
||||
|
||||
@javascript
|
||||
|
||||
@@ -44,7 +44,8 @@ Feature: Restrict activity availability through grade conditions
|
||||
And I am on site homepage
|
||||
And I follow "Course 1"
|
||||
Then I should see "Not available unless: You achieve a required score in Grade assignment"
|
||||
And "Test page name" activity should be hidden
|
||||
And "Test page name" activity should be dimmed
|
||||
And "Test page name" "link" should not exist
|
||||
And I follow "Grade assignment"
|
||||
And I press "Add submission"
|
||||
And I set the following fields to these values:
|
||||
|
||||
Vendored
+1
File diff suppressed because one or more lines are too long
@@ -0,0 +1,594 @@
|
||||
// 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/>.
|
||||
|
||||
/**
|
||||
* Various actions on modules and sections in the editing mode - hiding, duplicating, deleting, etc.
|
||||
*
|
||||
* @module core_course/actions
|
||||
* @package core
|
||||
* @copyright 2016 Marina Glancy
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @since 3.3
|
||||
*/
|
||||
define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str', 'core/url', 'core/yui'],
|
||||
function($, ajax, templates, notification, str, url, Y) {
|
||||
var CSS = {
|
||||
EDITINPROGRESS: 'editinprogress',
|
||||
SECTIONDRAGGABLE: 'sectiondraggable',
|
||||
EDITINGMOVE: 'editing_move'
|
||||
};
|
||||
var SELECTOR = {
|
||||
ACTIVITYLI: 'li.activity',
|
||||
ACTIONAREA: '.actions',
|
||||
ACTIVITYACTION: 'a.cm-edit-action',
|
||||
MENU: '.moodle-actionmenu[data-enhance=moodle-core-actionmenu]',
|
||||
TOGGLE: '.toggle-display,.dropdown-toggle',
|
||||
SECTIONLI: 'li.section',
|
||||
SECTIONACTIONMENU: '.section_action_menu'
|
||||
};
|
||||
|
||||
Y.use('moodle-course-coursebase', function() {
|
||||
var courseformatselector = M.course.format.get_section_selector();
|
||||
if (courseformatselector) {
|
||||
SELECTOR.SECTIONLI = courseformatselector;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Wrapper for Y.Moodle.core_course.util.cm.getId
|
||||
*
|
||||
* @param {JQuery} element
|
||||
* @returns {Integer}
|
||||
*/
|
||||
var getModuleId = function(element) {
|
||||
var id;
|
||||
Y.use('moodle-course-util', function(Y) {
|
||||
id = Y.Moodle.core_course.util.cm.getId(Y.Node(element.get(0)));
|
||||
});
|
||||
return id;
|
||||
};
|
||||
|
||||
/**
|
||||
* Wrapper for Y.Moodle.core_course.util.cm.getName
|
||||
*
|
||||
* @param {JQuery} element
|
||||
* @returns {String}
|
||||
*/
|
||||
var getModuleName = function(element) {
|
||||
var name;
|
||||
Y.use('moodle-course-util', function(Y) {
|
||||
name = Y.Moodle.core_course.util.cm.getName(Y.Node(element.get(0)));
|
||||
});
|
||||
return name;
|
||||
};
|
||||
|
||||
/**
|
||||
* Wrapper for M.util.add_spinner for an activity
|
||||
*
|
||||
* @param {JQuery} activity
|
||||
* @returns {Node}
|
||||
*/
|
||||
var addActivitySpinner = function(activity) {
|
||||
activity.addClass(CSS.EDITINPROGRESS);
|
||||
var actionarea = activity.find(SELECTOR.ACTIONAREA).get(0);
|
||||
if (actionarea) {
|
||||
var spinner = M.util.add_spinner(Y, Y.Node(actionarea));
|
||||
spinner.show();
|
||||
return spinner;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Wrapper for M.util.add_spinner for a section
|
||||
*
|
||||
* @param {JQuery} sectionelement
|
||||
* @returns {Node}
|
||||
*/
|
||||
var addSectionSpinner = function(sectionelement) {
|
||||
sectionelement.addClass(CSS.EDITINPROGRESS);
|
||||
var actionarea = sectionelement.find(SELECTOR.SECTIONACTIONMENU).get(0);
|
||||
if (actionarea) {
|
||||
var spinner = M.util.add_spinner(Y, Y.Node(actionarea));
|
||||
spinner.show();
|
||||
return spinner;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Wrapper for M.util.add_lightbox
|
||||
*
|
||||
* @param {JQuery} sectionelement
|
||||
* @returns {Node}
|
||||
*/
|
||||
var addSectionLightbox = function(sectionelement) {
|
||||
var lightbox = M.util.add_lightbox(Y, Y.Node(sectionelement.get(0)));
|
||||
lightbox.show();
|
||||
return lightbox;
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes the spinner element
|
||||
*
|
||||
* @param {JQuery} element
|
||||
* @param {Node} spinner
|
||||
* @param {Number} delay
|
||||
*/
|
||||
var removeSpinner = function(element, spinner, delay) {
|
||||
window.setTimeout(function() {
|
||||
element.removeClass(CSS.EDITINPROGRESS);
|
||||
if (spinner) {
|
||||
spinner.hide();
|
||||
}
|
||||
}, delay);
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes the lightbox element
|
||||
*
|
||||
* @param {Node} lightbox lighbox YUI element returned by addSectionLightbox
|
||||
* @param {Number} delay
|
||||
*/
|
||||
var removeLightbox = function(lightbox, delay) {
|
||||
if (lightbox) {
|
||||
window.setTimeout(function() {
|
||||
lightbox.hide();
|
||||
}, delay);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialise action menu for the element (section or module)
|
||||
*
|
||||
* @param {String} elementid CSS id attribute of the element
|
||||
* @param {Boolean} openmenu whether to open menu - this can be used when re-initiating menu after indent action was pressed
|
||||
*/
|
||||
var initActionMenu = function(elementid, openmenu) {
|
||||
// Initialise action menu in the new activity.
|
||||
Y.use('moodle-course-coursebase', function() {
|
||||
M.course.coursebase.invoke_function('setup_for_resource', '#' + elementid);
|
||||
});
|
||||
if (M.core.actionmenu && M.core.actionmenu.newDOMNode) {
|
||||
M.core.actionmenu.newDOMNode(Y.one('#' + elementid));
|
||||
}
|
||||
// Open action menu if the original element had data-keepopen.
|
||||
if (openmenu) {
|
||||
// We must use YUI click simulate here so the toggle works in Clean theme. This toggle is not
|
||||
// needed in Boost because we use standard bootstrapbase action menu.
|
||||
var toggle = Y.one('#' + elementid + ' ' + SELECTOR.MENU).one(SELECTOR.TOGGLE);
|
||||
if (toggle && toggle.simulate) {
|
||||
toggle.simulate('click');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns focus to the element that was clicked or "Edit" link if element is no longer visible.
|
||||
*
|
||||
* @param {String} elementId CSS id attribute of the element
|
||||
* @param {String} action data-action property of the element that was clicked
|
||||
*/
|
||||
var focusActionItem = function(elementId, action) {
|
||||
var mainelement = $('#' + elementId);
|
||||
var selector = '[data-action=' + action + ']';
|
||||
if (action === 'groupsseparate' || action === 'groupsvisible' || action === 'groupsnone') {
|
||||
// New element will have different data-action.
|
||||
selector = '[data-action=groupsseparate],[data-action=groupsvisible],[data-action=groupsnone]';
|
||||
}
|
||||
if (mainelement.find(selector).is(':visible')) {
|
||||
mainelement.find(selector).focus();
|
||||
} else {
|
||||
// Element not visible, focus the "Edit" link.
|
||||
mainelement.find(SELECTOR.MENU).find(SELECTOR.TOGGLE).focus();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Find next <a> after the element
|
||||
*
|
||||
* @param {JQuery} mainElement element that is about to be deleted
|
||||
* @returns {JQuery}
|
||||
*/
|
||||
var findNextFocusable = function(mainElement) {
|
||||
var tabables = $("a:visible");
|
||||
var isInside = false, foundElement = null;
|
||||
tabables.each(function() {
|
||||
if ($.contains(mainElement[0], this)) {
|
||||
isInside = true;
|
||||
} else if (isInside) {
|
||||
foundElement = this;
|
||||
return false; // Returning false in .each() is equivalent to "break;" inside the loop in php.
|
||||
}
|
||||
});
|
||||
return foundElement;
|
||||
};
|
||||
|
||||
/**
|
||||
* Performs an action on a module (moving, deleting, duplicating, hiding, etc.)
|
||||
*
|
||||
* @param {JQuery} moduleElement activity element we perform action on
|
||||
* @param {Number} cmid
|
||||
* @param {JQuery} target the element (menu item) that was clicked
|
||||
*/
|
||||
var editModule = function(moduleElement, cmid, target) {
|
||||
var keepopen = target.attr('data-keepopen'),
|
||||
action = target.attr('data-action');
|
||||
var spinner = addActivitySpinner(moduleElement);
|
||||
var promises = ajax.call([{
|
||||
methodname: 'core_course_edit_module',
|
||||
args: {id: cmid,
|
||||
action: action,
|
||||
sectionreturn: target.attr('data-sectionreturn') ? target.attr('data-sectionreturn') : 0
|
||||
}
|
||||
}], true);
|
||||
|
||||
var lightbox;
|
||||
if (action === 'duplicate') {
|
||||
lightbox = addSectionLightbox(target.closest(SELECTOR.SECTIONLI));
|
||||
}
|
||||
$.when.apply($, promises)
|
||||
.done(function(data) {
|
||||
var elementToFocus = findNextFocusable(moduleElement);
|
||||
moduleElement.replaceWith(data);
|
||||
// Initialise action menu for activity(ies) added as a result of this.
|
||||
$('<div>' + data + '</div>').find(SELECTOR.ACTIVITYLI).each(function(index) {
|
||||
initActionMenu($(this).attr('id'), keepopen);
|
||||
if (index === 0) {
|
||||
focusActionItem($(this).attr('id'), action);
|
||||
elementToFocus = null;
|
||||
}
|
||||
});
|
||||
// In case of activity deletion focus the next focusable element.
|
||||
if (elementToFocus) {
|
||||
elementToFocus.focus();
|
||||
}
|
||||
// Remove spinner and lightbox with a delay.
|
||||
removeSpinner(moduleElement, spinner, 400);
|
||||
removeLightbox(lightbox, 400);
|
||||
// Trigger event that can be observed by course formats.
|
||||
moduleElement.trigger($.Event('coursemoduleedited', {ajaxreturn: data, action: action}));
|
||||
}).fail(function(ex) {
|
||||
// Remove spinner and lightbox.
|
||||
removeSpinner(moduleElement, spinner);
|
||||
removeLightbox(lightbox);
|
||||
// Trigger event that can be observed by course formats.
|
||||
var e = $.Event('coursemoduleeditfailed', {exception: ex, action: action});
|
||||
moduleElement.trigger(e);
|
||||
if (!e.isDefaultPrevented()) {
|
||||
notification.exception(ex);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Requests html for the module via WS core_course_get_module and updates the module on the course page
|
||||
*
|
||||
* Used after d&d of the module to another section
|
||||
*
|
||||
* @param {JQuery} activityElement
|
||||
* @param {Number} cmid
|
||||
* @param {Number} sectionreturn
|
||||
*/
|
||||
var refreshModule = function(activityElement, cmid, sectionreturn) {
|
||||
var spinner = addActivitySpinner(activityElement);
|
||||
var promises = ajax.call([{
|
||||
methodname: 'core_course_get_module',
|
||||
args: {id: cmid, sectionreturn: sectionreturn}
|
||||
}], true);
|
||||
|
||||
$.when.apply($, promises)
|
||||
.done(function(data) {
|
||||
removeSpinner(activityElement, spinner, 400);
|
||||
replaceActivityHtmlWith(data);
|
||||
}).fail(function() {
|
||||
removeSpinner(activityElement, spinner);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Displays the delete confirmation to delete a module
|
||||
*
|
||||
* @param {JQuery} mainelement activity element we perform action on
|
||||
* @param {function} onconfirm function to execute on confirm
|
||||
*/
|
||||
var confirmDeleteModule = function(mainelement, onconfirm) {
|
||||
var modtypename = mainelement.attr('class').match(/modtype_([^\s]*)/)[1];
|
||||
var modulename = getModuleName(mainelement);
|
||||
|
||||
str.get_string('pluginname', modtypename).done(function(pluginname) {
|
||||
var plugindata = {
|
||||
type: pluginname,
|
||||
name: modulename
|
||||
};
|
||||
str.get_strings([
|
||||
{key: 'confirm'},
|
||||
{key: modulename === null ? 'deletechecktype' : 'deletechecktypename', param: plugindata},
|
||||
{key: 'yes'},
|
||||
{key: 'no'}
|
||||
]).done(function(s) {
|
||||
notification.confirm(s[0], s[1], s[2], s[3], onconfirm);
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Displays the delete confirmation to delete a section
|
||||
*
|
||||
* @param {String} message confirmation message
|
||||
* @param {function} onconfirm function to execute on confirm
|
||||
*/
|
||||
var confirmEditSection = function(message, onconfirm) {
|
||||
str.get_strings([
|
||||
{key: 'confirm'}, // TODO link text
|
||||
{key: 'yes'},
|
||||
{key: 'no'}
|
||||
]).done(function(s) {
|
||||
notification.confirm(s[0], message, s[1], s[2], onconfirm);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Replaces an action menu item with another one (for example Show->Hide, Set marker->Remove marker)
|
||||
*
|
||||
* @param {JQuery} actionitem
|
||||
* @param {String} image new image name ("i/show", "i/hide", etc.)
|
||||
* @param {String} stringname new string for the action menu item
|
||||
* @param {String} stringcomponent
|
||||
* @param {String} titlestr string for "title" attribute (if different from stringname)
|
||||
* @param {String} titlecomponent
|
||||
* @param {String} newaction new value for data-action attribute of the link
|
||||
*/
|
||||
var replaceActionItem = function(actionitem, image, stringname,
|
||||
stringcomponent, titlestr, titlecomponent, newaction) {
|
||||
actionitem.find('img').attr('src', url.imageUrl(image, 'core'));
|
||||
str.get_string(stringname, stringcomponent).done(function(newstring) {
|
||||
actionitem.find('span.menu-action-text').html(newstring);
|
||||
actionitem.attr('title', newstring);
|
||||
});
|
||||
if (titlestr) {
|
||||
str.get_string(titlestr, titlecomponent).done(function(newtitle) {
|
||||
actionitem.attr('title', newtitle);
|
||||
});
|
||||
}
|
||||
actionitem.attr('data-action', newaction);
|
||||
};
|
||||
|
||||
/**
|
||||
* Default post-processing for section AJAX edit actions.
|
||||
*
|
||||
* This can be overridden in course formats by listening to event coursesectionedited:
|
||||
*
|
||||
* $('body').on('coursesectionedited', 'li.section', function(e) {
|
||||
* var action = e.action,
|
||||
* sectionElement = $(e.target),
|
||||
* data = e.ajaxreturn;
|
||||
* // ... Do some processing here.
|
||||
* e.preventDefault(); // Prevent default handler.
|
||||
* });
|
||||
*
|
||||
* @param {JQuery} sectionElement
|
||||
* @param {JQuery} actionItem
|
||||
* @param {Object} data
|
||||
* @param {String} courseformat
|
||||
*/
|
||||
var defaultEditSectionHandler = function(sectionElement, actionItem, data, courseformat) {
|
||||
var action = actionItem.attr('data-action');
|
||||
if (action === 'hide' || action === 'show') {
|
||||
if (action === 'hide') {
|
||||
sectionElement.addClass('hidden');
|
||||
replaceActionItem(actionItem, 'i/show',
|
||||
'showfromothers', 'format_' + courseformat, null, null, 'show');
|
||||
} else {
|
||||
sectionElement.removeClass('hidden');
|
||||
replaceActionItem(actionItem, 'i/hide',
|
||||
'hidefromothers', 'format_' + courseformat, null, null, 'hide');
|
||||
}
|
||||
// Replace the modules with new html (that indicates that they are now hidden or not hidden).
|
||||
if (data.modules !== undefined) {
|
||||
for (var i in data.modules) {
|
||||
replaceActivityHtmlWith(data.modules[i]);
|
||||
}
|
||||
}
|
||||
// Replace the section availability information.
|
||||
if (data.section_availability !== undefined) {
|
||||
sectionElement.find('.section_availability').first().replaceWith(data.section_availability);
|
||||
}
|
||||
} else if (action === 'setmarker') {
|
||||
var oldmarker = $(SELECTOR.SECTIONLI + '.current'),
|
||||
oldActionItem = oldmarker.find(SELECTOR.SECTIONACTIONMENU + ' ' + 'a[data-action=removemarker]');
|
||||
oldmarker.removeClass('current');
|
||||
replaceActionItem(oldActionItem, 'i/marker',
|
||||
'highlight', 'core', 'markthistopic', 'core', 'setmarker');
|
||||
sectionElement.addClass('current');
|
||||
replaceActionItem(actionItem, 'i/marked',
|
||||
'highlightoff', 'core', 'markedthistopic', 'core', 'removemarker');
|
||||
} else if (action === 'removemarker') {
|
||||
sectionElement.removeClass('current');
|
||||
replaceActionItem(actionItem, 'i/marker',
|
||||
'highlight', 'core', 'markthistopic', 'core', 'setmarker');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Replaces the course module with the new html (used to update module after it was edited or its visibility was changed).
|
||||
*
|
||||
* @param {String} activityHTML
|
||||
*/
|
||||
var replaceActivityHtmlWith = function(activityHTML) {
|
||||
$('<div>' + activityHTML + '</div>').find(SELECTOR.ACTIVITYLI).each(function() {
|
||||
// Extract id from the new activity html.
|
||||
var id = $(this).attr('id');
|
||||
// Find the existing element with the same id and replace its contents with new html.
|
||||
$(SELECTOR.ACTIVITYLI + '#' + id).replaceWith(activityHTML);
|
||||
// Initialise action menu.
|
||||
initActionMenu(id, false);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Performs an action on a module (moving, deleting, duplicating, hiding, etc.)
|
||||
*
|
||||
* @param {JQuery} sectionElement section element we perform action on
|
||||
* @param {Nunmber} sectionid
|
||||
* @param {JQuery} target the element (menu item) that was clicked
|
||||
* @param {String} courseformat
|
||||
*/
|
||||
var editSection = function(sectionElement, sectionid, target, courseformat) {
|
||||
var action = target.attr('data-action'),
|
||||
sectionreturn = target.attr('data-sectionreturn') ? target.attr('data-sectionreturn') : 0;
|
||||
var spinner = addSectionSpinner(sectionElement);
|
||||
var promises = ajax.call([{
|
||||
methodname: 'core_course_edit_section',
|
||||
args: {id: sectionid, action: action, sectionreturn: sectionreturn}
|
||||
}], true);
|
||||
|
||||
var lightbox = addSectionLightbox(sectionElement);
|
||||
$.when.apply($, promises)
|
||||
.done(function(dataencoded) {
|
||||
var data = $.parseJSON(dataencoded);
|
||||
removeSpinner(sectionElement, spinner);
|
||||
removeLightbox(lightbox);
|
||||
sectionElement.find(SELECTOR.SECTIONACTIONMENU).find(SELECTOR.TOGGLE).focus();
|
||||
// Trigger event that can be observed by course formats.
|
||||
var e = $.Event('coursesectionedited', {ajaxreturn: data, action: action});
|
||||
sectionElement.trigger(e);
|
||||
if (!e.isDefaultPrevented()) {
|
||||
defaultEditSectionHandler(sectionElement, target, data, courseformat);
|
||||
}
|
||||
}).fail(function(ex) {
|
||||
// Remove spinner and lightbox.
|
||||
removeSpinner(sectionElement, spinner);
|
||||
removeLightbox(lightbox);
|
||||
// Trigger event that can be observed by course formats.
|
||||
var e = $.Event('coursesectioneditfailed', {exception: ex, action: action});
|
||||
sectionElement.trigger(e);
|
||||
if (!e.isDefaultPrevented()) {
|
||||
notification.exception(ex);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Register a function to be executed after D&D of an activity.
|
||||
Y.use('moodle-course-coursebase', function() {
|
||||
M.course.coursebase.register_module({
|
||||
// Ignore camelcase eslint rule for the next line because it is an expected name of the callback.
|
||||
// eslint-disable-next-line camelcase
|
||||
set_visibility_resource_ui: function(args) {
|
||||
var mainelement = $(args.element.getDOMNode());
|
||||
var cmid = getModuleId(mainelement);
|
||||
if (cmid) {
|
||||
var sectionreturn = mainelement.find('.' + CSS.EDITINGMOVE).attr('data-sectionreturn');
|
||||
refreshModule(mainelement, cmid, sectionreturn);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return /** @alias module:core_course/actions */ {
|
||||
|
||||
/**
|
||||
* Initialises course page
|
||||
*
|
||||
* @method init
|
||||
* @param {String} courseformat name of the current course format (for fetching strings)
|
||||
*/
|
||||
initCoursePage: function(courseformat) {
|
||||
|
||||
// Add a handler for course module actions.
|
||||
$('body').on('click keypress', SELECTOR.ACTIVITYLI + ' ' +
|
||||
SELECTOR.ACTIVITYACTION + '[data-action]', function(e) {
|
||||
if (e.type === 'keypress' && e.keyCode !== 13) {
|
||||
return;
|
||||
}
|
||||
var actionItem = $(this),
|
||||
moduleElement = actionItem.closest(SELECTOR.ACTIVITYLI),
|
||||
action = actionItem.attr('data-action'),
|
||||
moduleId = getModuleId(moduleElement);
|
||||
switch (action) {
|
||||
case 'moveleft':
|
||||
case 'moveright':
|
||||
case 'delete':
|
||||
case 'duplicate':
|
||||
case 'hide':
|
||||
case 'stealth':
|
||||
case 'show':
|
||||
case 'groupsseparate':
|
||||
case 'groupsvisible':
|
||||
case 'groupsnone':
|
||||
break;
|
||||
default:
|
||||
// Nothing to do here!
|
||||
return;
|
||||
}
|
||||
if (!moduleId) {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
if (action === 'delete') {
|
||||
// Deleting requires confirmation.
|
||||
confirmDeleteModule(moduleElement, function() {
|
||||
editModule(moduleElement, moduleId, actionItem);
|
||||
});
|
||||
} else {
|
||||
editModule(moduleElement, moduleId, actionItem);
|
||||
}
|
||||
});
|
||||
|
||||
// Add a handler for section show/hide actions.
|
||||
$('body').on('click keypress', SELECTOR.SECTIONLI + ' ' +
|
||||
SELECTOR.SECTIONACTIONMENU + '[data-sectionid] ' +
|
||||
'a[data-action]', function(e) {
|
||||
if (e.type === 'keypress' && e.keyCode !== 13) {
|
||||
return;
|
||||
}
|
||||
var actionItem = $(this),
|
||||
sectionElement = actionItem.closest(SELECTOR.SECTIONLI),
|
||||
sectionId = actionItem.closest(SELECTOR.SECTIONACTIONMENU).attr('data-sectionid');
|
||||
e.preventDefault();
|
||||
if (actionItem.attr('data-confirm')) {
|
||||
// Action requires confirmation.
|
||||
confirmEditSection(actionItem.attr('data-confirm'), function() {
|
||||
editSection(sectionElement, sectionId, actionItem, courseformat);
|
||||
});
|
||||
} else {
|
||||
editSection(sectionElement, sectionId, actionItem, courseformat);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Replaces a section action menu item with another one (for example Show->Hide, Set marker->Remove marker)
|
||||
*
|
||||
* This method can be used by course formats in their listener to the coursesectionedited event
|
||||
*
|
||||
* @param {JQuery} sectionelement
|
||||
* @param {String} selector CSS selector inside the section element, for example "a[data-action=show]"
|
||||
* @param {String} image new image name ("i/show", "i/hide", etc.)
|
||||
* @param {String} stringname new string for the action menu item
|
||||
* @param {String} stringcomponent
|
||||
* @param {String} titlestr string for "title" attribute (if different from stringname)
|
||||
* @param {String} titlecomponent
|
||||
* @param {String} newaction new value for data-action attribute of the link
|
||||
*/
|
||||
replaceSectionActionItem: function(sectionelement, selector, image, stringname,
|
||||
stringcomponent, titlestr, titlecomponent, newaction) {
|
||||
var actionitem = sectionelement.find(SELECTOR.SECTIONACTIONMENU + ' ' + selector);
|
||||
replaceActionItem(actionitem, image, stringname, stringcomponent, titlestr, titlecomponent, newaction);
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -270,6 +270,7 @@ class core_course_external extends external_api {
|
||||
context_module::instance($cm->id));
|
||||
//user that can view hidden module should know about the visibility
|
||||
$module['visible'] = $cm->visible;
|
||||
$module['visibleoncoursepage'] = $cm->visibleoncoursepage;
|
||||
|
||||
// Availability date (also send to user who can see hidden module).
|
||||
if ($CFG->enableavailability && ($canviewhidden || $canupdatecourse)) {
|
||||
@@ -342,6 +343,8 @@ class core_course_external extends external_api {
|
||||
'instance' => new external_value(PARAM_INT, 'instance id', VALUE_OPTIONAL),
|
||||
'description' => new external_value(PARAM_RAW, 'activity description', VALUE_OPTIONAL),
|
||||
'visible' => new external_value(PARAM_INT, 'is the module visible', VALUE_OPTIONAL),
|
||||
'visibleoncoursepage' => new external_value(PARAM_INT, 'is the module visible on course page',
|
||||
VALUE_OPTIONAL),
|
||||
'modicon' => new external_value(PARAM_URL, 'activity icon url'),
|
||||
'modname' => new external_value(PARAM_PLUGIN, 'activity module type'),
|
||||
'modplural' => new external_value(PARAM_TEXT, 'activity module plural name'),
|
||||
@@ -2558,6 +2561,7 @@ class core_course_external extends external_api {
|
||||
'score' => new external_value(PARAM_INT, 'Score', VALUE_OPTIONAL),
|
||||
'indent' => new external_value(PARAM_INT, 'Indentation', VALUE_OPTIONAL),
|
||||
'visible' => new external_value(PARAM_INT, 'If visible', VALUE_OPTIONAL),
|
||||
'visibleoncoursepage' => new external_value(PARAM_INT, 'If visible on course page', VALUE_OPTIONAL),
|
||||
'visibleold' => new external_value(PARAM_INT, 'Visible old', VALUE_OPTIONAL),
|
||||
'completiongradeitemnumber' => new external_value(PARAM_INT, 'Completion grade item', VALUE_OPTIONAL),
|
||||
'completionview' => new external_value(PARAM_INT, 'Completion view setting', VALUE_OPTIONAL),
|
||||
@@ -3235,4 +3239,226 @@ class core_course_external extends external_api {
|
||||
public static function get_updates_since_returns() {
|
||||
return self::check_updates_returns();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parameters for function edit_module()
|
||||
*
|
||||
* @since Moodle 3.3
|
||||
* @return external_function_parameters
|
||||
*/
|
||||
public static function edit_module_parameters() {
|
||||
return new external_function_parameters(
|
||||
array(
|
||||
'action' => new external_value(PARAM_ALPHA,
|
||||
'action: hide, show, stealth, duplicate, delete, moveleft, moveright, group...', VALUE_REQUIRED),
|
||||
'id' => new external_value(PARAM_INT, 'course module id', VALUE_REQUIRED),
|
||||
'sectionreturn' => new external_value(PARAM_INT, 'section to return to', VALUE_DEFAULT, null),
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs one of the edit module actions and return new html for AJAX
|
||||
*
|
||||
* Returns html to replace the current module html with, for example:
|
||||
* - empty string for "delete" action,
|
||||
* - two modules html for "duplicate" action
|
||||
* - updated module html for everything else
|
||||
*
|
||||
* Throws exception if operation is not permitted/possible
|
||||
*
|
||||
* @since Moodle 3.3
|
||||
* @param string $action
|
||||
* @param int $id
|
||||
* @param null|int $sectionreturn
|
||||
* @return string
|
||||
*/
|
||||
public static function edit_module($action, $id, $sectionreturn = null) {
|
||||
global $PAGE, $DB;
|
||||
// Validate and normalize parameters.
|
||||
$params = self::validate_parameters(self::edit_module_parameters(),
|
||||
array('action' => $action, 'id' => $id, 'sectionreturn' => $sectionreturn));
|
||||
$action = $params['action'];
|
||||
$id = $params['id'];
|
||||
$sectionreturn = $params['sectionreturn'];
|
||||
|
||||
list($course, $cm) = get_course_and_cm_from_cmid($id);
|
||||
$modcontext = context_module::instance($cm->id);
|
||||
$coursecontext = context_course::instance($course->id);
|
||||
self::validate_context($modcontext);
|
||||
$courserenderer = $PAGE->get_renderer('core', 'course');
|
||||
$completioninfo = new completion_info($course);
|
||||
|
||||
switch($action) {
|
||||
case 'hide':
|
||||
case 'show':
|
||||
case 'stealth':
|
||||
require_capability('moodle/course:activityvisibility', $modcontext);
|
||||
$visible = ($action === 'hide') ? 0 : 1;
|
||||
$visibleoncoursepage = ($action === 'stealth') ? 0 : 1;
|
||||
set_coursemodule_visible($id, $visible, $visibleoncoursepage);
|
||||
\core\event\course_module_updated::create_from_cm($cm, $modcontext)->trigger();
|
||||
break;
|
||||
case 'duplicate':
|
||||
require_capability('moodle/course:manageactivities', $coursecontext);
|
||||
require_capability('moodle/backup:backuptargetimport', $coursecontext);
|
||||
require_capability('moodle/restore:restoretargetimport', $coursecontext);
|
||||
if (!course_allowed_module($course, $cm->modname)) {
|
||||
throw new moodle_exception('No permission to create that activity');
|
||||
}
|
||||
if ($newcm = duplicate_module($course, $cm)) {
|
||||
$cm = get_fast_modinfo($course)->get_cm($id);
|
||||
$newcm = get_fast_modinfo($course)->get_cm($newcm->id);
|
||||
return $courserenderer->course_section_cm_list_item($course, $completioninfo, $cm, $sectionreturn) .
|
||||
$courserenderer->course_section_cm_list_item($course, $completioninfo, $newcm, $sectionreturn);
|
||||
}
|
||||
break;
|
||||
case 'groupsseparate':
|
||||
case 'groupsvisible':
|
||||
case 'groupsnone':
|
||||
require_capability('moodle/course:manageactivities', $modcontext);
|
||||
if ($action === 'groupsseparate') {
|
||||
$newgroupmode = SEPARATEGROUPS;
|
||||
} else if ($action === 'groupsvisible') {
|
||||
$newgroupmode = VISIBLEGROUPS;
|
||||
} else {
|
||||
$newgroupmode = NOGROUPS;
|
||||
}
|
||||
if (set_coursemodule_groupmode($cm->id, $newgroupmode)) {
|
||||
\core\event\course_module_updated::create_from_cm($cm, $modcontext)->trigger();
|
||||
}
|
||||
break;
|
||||
case 'moveleft':
|
||||
case 'moveright':
|
||||
require_capability('moodle/course:manageactivities', $modcontext);
|
||||
$indent = $cm->indent + (($action === 'moveright') ? 1 : -1);
|
||||
if ($cm->indent >= 0) {
|
||||
$DB->update_record('course_modules', array('id' => $cm->id, 'indent' => $indent));
|
||||
rebuild_course_cache($cm->course);
|
||||
}
|
||||
break;
|
||||
case 'delete':
|
||||
require_capability('moodle/course:manageactivities', $modcontext);
|
||||
course_delete_module($cm->id, true);
|
||||
return '';
|
||||
default:
|
||||
throw new coding_exception('Unrecognised action');
|
||||
}
|
||||
|
||||
$cm = get_fast_modinfo($course)->get_cm($id);
|
||||
return $courserenderer->course_section_cm_list_item($course, $completioninfo, $cm, $sectionreturn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return structure for edit_module()
|
||||
*
|
||||
* @since Moodle 3.3
|
||||
* @return external_description
|
||||
*/
|
||||
public static function edit_module_returns() {
|
||||
return new external_value(PARAM_RAW, 'html to replace the current module with');
|
||||
}
|
||||
|
||||
/**
|
||||
* Parameters for function get_module()
|
||||
*
|
||||
* @since Moodle 3.3
|
||||
* @return external_function_parameters
|
||||
*/
|
||||
public static function get_module_parameters() {
|
||||
return new external_function_parameters(
|
||||
array(
|
||||
'id' => new external_value(PARAM_INT, 'course module id', VALUE_REQUIRED),
|
||||
'sectionreturn' => new external_value(PARAM_INT, 'section to return to', VALUE_DEFAULT, null),
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns html for displaying one activity module on course page
|
||||
*
|
||||
* @since Moodle 3.3
|
||||
* @param int $id
|
||||
* @param null|int $sectionreturn
|
||||
* @return string
|
||||
*/
|
||||
public static function get_module($id, $sectionreturn = null) {
|
||||
global $PAGE;
|
||||
// Validate and normalize parameters.
|
||||
$params = self::validate_parameters(self::get_module_parameters(),
|
||||
array('id' => $id, 'sectionreturn' => $sectionreturn));
|
||||
$id = $params['id'];
|
||||
$sectionreturn = $params['sectionreturn'];
|
||||
|
||||
// Validate access to the course (note, this is html for the course view page, we don't validate access to the module).
|
||||
list($course, $cm) = get_course_and_cm_from_cmid($id);
|
||||
self::validate_context(context_course::instance($course->id));
|
||||
|
||||
$courserenderer = $PAGE->get_renderer('core', 'course');
|
||||
$completioninfo = new completion_info($course);
|
||||
return $courserenderer->course_section_cm_list_item($course, $completioninfo, $cm, $sectionreturn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return structure for edit_module()
|
||||
*
|
||||
* @since Moodle 3.3
|
||||
* @return external_description
|
||||
*/
|
||||
public static function get_module_returns() {
|
||||
return new external_value(PARAM_RAW, 'html to replace the current module with');
|
||||
}
|
||||
|
||||
/**
|
||||
* Parameters for function edit_section()
|
||||
*
|
||||
* @since Moodle 3.3
|
||||
* @return external_function_parameters
|
||||
*/
|
||||
public static function edit_section_parameters() {
|
||||
return new external_function_parameters(
|
||||
array(
|
||||
'action' => new external_value(PARAM_ALPHA, 'action: hide, show, stealth, setmarker, removemarker', VALUE_REQUIRED),
|
||||
'id' => new external_value(PARAM_INT, 'course section id', VALUE_REQUIRED),
|
||||
'sectionreturn' => new external_value(PARAM_INT, 'section to return to', VALUE_DEFAULT, null),
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs one of the edit section actions
|
||||
*
|
||||
* @since Moodle 3.3
|
||||
* @param string $action
|
||||
* @param int $id section id
|
||||
* @param int $sectionreturn section to return to
|
||||
* @return string
|
||||
*/
|
||||
public static function edit_section($action, $id, $sectionreturn) {
|
||||
global $DB;
|
||||
// Validate and normalize parameters.
|
||||
$params = self::validate_parameters(self::edit_section_parameters(),
|
||||
array('action' => $action, 'id' => $id, 'sectionreturn' => $sectionreturn));
|
||||
$action = $params['action'];
|
||||
$id = $params['id'];
|
||||
$sr = $params['sectionreturn'];
|
||||
|
||||
$section = $DB->get_record('course_sections', array('id' => $id), '*', MUST_EXIST);
|
||||
$coursecontext = context_course::instance($section->course);
|
||||
self::validate_context($coursecontext);
|
||||
|
||||
$rv = course_get_format($section->course)->section_action($section, $action, $sectionreturn);
|
||||
if ($rv) {
|
||||
return json_encode($rv);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return structure for edit_section()
|
||||
*
|
||||
* @since Moodle 3.3
|
||||
* @return external_description
|
||||
*/
|
||||
public static function edit_section_returns() {
|
||||
return new external_value(PARAM_RAW, 'Additional data for javascript (JSON-encoded string)');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1170,6 +1170,68 @@ abstract class format_base {
|
||||
$startdate = $mform->getElementValue($fieldnames['startdate']);
|
||||
return $mform->getElement($fieldnames['startdate'])->exportValue($startdate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this course format allows the activity to
|
||||
* have "triple visibility state" - visible always, hidden on course page but available, hidden.
|
||||
*
|
||||
* @param stdClass|cm_info $cm course module (may be null if we are displaying a form for adding a module)
|
||||
* @param stdClass|section_info $section section where this module is located or will be added to
|
||||
* @return bool
|
||||
*/
|
||||
public function allow_stealth_module_visibility($cm, $section) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback used in WS core_course_edit_section when teacher performs an AJAX action on a section (show/hide)
|
||||
*
|
||||
* Access to the course is already validated in the WS but the callback has to make sure
|
||||
* that particular action is allowed by checking capabilities
|
||||
*
|
||||
* Course formats should register
|
||||
*
|
||||
* @param stdClass|section_info $section
|
||||
* @param string $action
|
||||
* @param int $sr
|
||||
* @return null|array|stdClass any data for the Javascript post-processor (must be json-encodeable)
|
||||
*/
|
||||
public function section_action($section, $action, $sr) {
|
||||
global $PAGE;
|
||||
if (!$this->uses_sections() || !$section->section) {
|
||||
// No section actions are allowed if course format does not support sections.
|
||||
// No actions are allowed on the 0-section by default (overwrite in course format if needed).
|
||||
throw new moodle_exception('sectionactionnotsupported', 'core', null, s($action));
|
||||
}
|
||||
|
||||
$course = $this->get_course();
|
||||
$coursecontext = context_course::instance($course->id);
|
||||
switch($action) {
|
||||
case 'hide':
|
||||
case 'show':
|
||||
require_capability('moodle/course:sectionvisibility', $coursecontext);
|
||||
$visible = ($action === 'hide') ? 0 : 1;
|
||||
course_update_section($course, $section, array('visible' => $visible));
|
||||
break;
|
||||
default:
|
||||
throw new moodle_exception('sectionactionnotsupported', 'core', null, s($action));
|
||||
}
|
||||
|
||||
$modules = [];
|
||||
|
||||
$modinfo = get_fast_modinfo($course);
|
||||
$coursesections = $modinfo->sections;
|
||||
if (array_key_exists($section->section, $coursesections)) {
|
||||
$courserenderer = $PAGE->get_renderer('core', 'course');
|
||||
$completioninfo = new completion_info($course);
|
||||
foreach ($coursesections[$section->section] as $cmid) {
|
||||
$cm = $modinfo->get_cm($cmid);
|
||||
$modules[] = $courserenderer->course_section_cm_list_item($course, $completioninfo, $cm, $sr);
|
||||
}
|
||||
}
|
||||
|
||||
return ['modules' => $modules];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1231,4 +1293,16 @@ class format_site extends format_base {
|
||||
}
|
||||
return $courseformatoptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this course format allows the activity to
|
||||
* have "triple visibility state" - visible always, hidden on course page but available, hidden.
|
||||
*
|
||||
* @param stdClass|cm_info $cm course module (may be null if we are displaying a form for adding a module)
|
||||
* @param stdClass|section_info $section section where this module is located or will be added to
|
||||
* @return bool
|
||||
*/
|
||||
public function allow_stealth_module_visibility($cm, $section) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
+47
-31
@@ -38,7 +38,7 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
abstract class format_section_renderer_base extends plugin_renderer_base {
|
||||
|
||||
/** @var contains instance of core course renderer */
|
||||
/** @var core_course_renderer contains instance of core course renderer */
|
||||
protected $courserenderer;
|
||||
|
||||
/**
|
||||
@@ -127,7 +127,8 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
|
||||
$menu->add($al);
|
||||
}
|
||||
|
||||
$o .= html_writer::div($this->render($menu), 'section_action_menu');
|
||||
$o .= html_writer::div($this->render($menu), 'section_action_menu',
|
||||
array('data-sectionid' => $section->id));
|
||||
}
|
||||
|
||||
return $o;
|
||||
@@ -194,7 +195,8 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
|
||||
// Only in the non-general sections.
|
||||
if (!$section->visible) {
|
||||
$sectionstyle = ' hidden';
|
||||
} else if (course_get_format($course)->is_section_current($section)) {
|
||||
}
|
||||
if (course_get_format($course)->is_section_current($section)) {
|
||||
$sectionstyle = ' current';
|
||||
}
|
||||
}
|
||||
@@ -226,13 +228,11 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
|
||||
$sectionname = html_writer::tag('span', $this->section_title($section, $course));
|
||||
$o.= $this->output->heading($sectionname, 3, 'sectionname' . $classes);
|
||||
|
||||
$o.= html_writer::start_tag('div', array('class' => 'summary'));
|
||||
$o.= $this->format_summary_text($section);
|
||||
$o.= html_writer::end_tag('div');
|
||||
$o .= $this->section_availability($section);
|
||||
|
||||
$context = context_course::instance($course->id);
|
||||
$o .= $this->section_availability_message($section,
|
||||
has_capability('moodle/course:viewhiddensections', $context));
|
||||
$o .= html_writer::start_tag('div', array('class' => 'summary'));
|
||||
$o .= $this->format_summary_text($section);
|
||||
$o .= html_writer::end_tag('div');
|
||||
|
||||
return $o;
|
||||
}
|
||||
@@ -305,14 +305,12 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
|
||||
return array();
|
||||
}
|
||||
|
||||
$sectionreturn = $onsectionpage ? $section->section : null;
|
||||
|
||||
$coursecontext = context_course::instance($course->id);
|
||||
$isstealth = isset($course->numsections) && ($section->section > $course->numsections);
|
||||
|
||||
if ($onsectionpage) {
|
||||
$baseurl = course_get_url($course, $section->section);
|
||||
} else {
|
||||
$baseurl = course_get_url($course);
|
||||
}
|
||||
$baseurl = course_get_url($course, $sectionreturn);
|
||||
$baseurl->param('sesskey', sesskey());
|
||||
|
||||
$controls = array();
|
||||
@@ -325,7 +323,6 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
|
||||
$streditsection = get_string('editsection');
|
||||
}
|
||||
|
||||
$sectionreturn = $onsectionpage ? $section->section : 0;
|
||||
$controls['edit'] = array(
|
||||
'url' => new moodle_url('/course/editsection.php', array('id' => $section->id, 'sr' => $sectionreturn)),
|
||||
'icon' => 'i/settings',
|
||||
@@ -346,7 +343,8 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
|
||||
'icon' => 'i/hide',
|
||||
'name' => $strhidefromothers,
|
||||
'pixattr' => array('class' => '', 'alt' => $strhidefromothers),
|
||||
'attr' => array('class' => 'icon editing_showhide', 'title' => $strhidefromothers));
|
||||
'attr' => array('class' => 'icon editing_showhide', 'title' => $strhidefromothers,
|
||||
'data-sectionreturn' => $sectionreturn, 'data-action' => 'hide'));
|
||||
} else {
|
||||
$strshowfromothers = get_string('showfromothers', 'format_'.$course->format);
|
||||
$url->param('show', $section->section);
|
||||
@@ -355,7 +353,8 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
|
||||
'icon' => 'i/show',
|
||||
'name' => $strshowfromothers,
|
||||
'pixattr' => array('class' => '', 'alt' => $strshowfromothers),
|
||||
'attr' => array('class' => 'icon editing_showhide', 'title' => $strshowfromothers));
|
||||
'attr' => array('class' => 'icon editing_showhide', 'title' => $strshowfromothers,
|
||||
'data-sectionreturn' => $sectionreturn, 'data-action' => 'show'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -398,14 +397,14 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
|
||||
}
|
||||
$url = new moodle_url('/course/editsection.php', array(
|
||||
'id' => $section->id,
|
||||
'sr' => $onsectionpage ? $section->section : 0,
|
||||
'sr' => $sectionreturn,
|
||||
'delete' => 1));
|
||||
$controls['delete'] = array(
|
||||
'url' => $url,
|
||||
'icon' => 'i/delete',
|
||||
'name' => $strdelete,
|
||||
'pixattr' => array('class' => '', 'alt' => $strdelete),
|
||||
'attr' => array('class' => 'icon delete', 'title' => $strdelete));
|
||||
'attr' => array('class' => 'icon editing_delete', 'title' => $strdelete));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -452,9 +451,7 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
|
||||
$o.= html_writer::end_tag('div');
|
||||
$o.= $this->section_activity_summary($section, $course, null);
|
||||
|
||||
$context = context_course::instance($course->id);
|
||||
$o .= $this->section_availability_message($section,
|
||||
has_capability('moodle/course:viewhiddensections', $context));
|
||||
$o .= $this->section_availability($section);
|
||||
|
||||
$o .= html_writer::end_tag('div');
|
||||
$o .= html_writer::end_tag('li');
|
||||
@@ -548,31 +545,50 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
|
||||
* are going to be unavailable etc). This logic is the same as for
|
||||
* activities.
|
||||
*
|
||||
* @param stdClass $section The course_section entry from DB
|
||||
* @param section_info $section The course_section entry from DB
|
||||
* @param bool $canviewhidden True if user can view hidden sections
|
||||
* @return string HTML to output
|
||||
*/
|
||||
protected function section_availability_message($section, $canviewhidden) {
|
||||
global $CFG;
|
||||
$o = '';
|
||||
if (!$section->uservisible) {
|
||||
// Note: We only get to this function if availableinfo is non-empty,
|
||||
// so there is definitely something to print.
|
||||
$formattedinfo = \core_availability\info::format_info(
|
||||
$section->availableinfo, $section->course);
|
||||
$o .= html_writer::div($formattedinfo, 'availabilityinfo');
|
||||
} else if ($canviewhidden && !empty($CFG->enableavailability) && $section->visible) {
|
||||
if (!$section->visible) {
|
||||
if ($canviewhidden) {
|
||||
$o .= $this->courserenderer->availability_info(get_string('hiddenfromstudents'), 'ishidden');
|
||||
}
|
||||
} else if (!$section->uservisible) {
|
||||
if ($section->availableinfo) {
|
||||
// Note: We only get to this function if availableinfo is non-empty,
|
||||
// so there is definitely something to print.
|
||||
$formattedinfo = \core_availability\info::format_info(
|
||||
$section->availableinfo, $section->course);
|
||||
$o .= $this->courserenderer->availability_info($formattedinfo);
|
||||
}
|
||||
} else if ($canviewhidden && !empty($CFG->enableavailability)) {
|
||||
// Check if there is an availability restriction.
|
||||
$ci = new \core_availability\info_section($section);
|
||||
$fullinfo = $ci->get_full_information();
|
||||
if ($fullinfo) {
|
||||
$formattedinfo = \core_availability\info::format_info(
|
||||
$fullinfo, $section->course);
|
||||
$o .= html_writer::div($formattedinfo, 'availabilityinfo');
|
||||
$o .= $this->courserenderer->availability_info($formattedinfo);
|
||||
}
|
||||
}
|
||||
return $o;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays availability information for the section (hidden, not available unles, etc.)
|
||||
*
|
||||
* @param section_info $section
|
||||
* @return string
|
||||
*/
|
||||
public function section_availability($section) {
|
||||
$context = context_course::instance($section->course);
|
||||
$canviewhidden = has_capability('moodle/course:viewhiddensections', $context);
|
||||
return html_writer::div($this->section_availability_message($section, $canviewhidden), 'section_availability');
|
||||
}
|
||||
|
||||
/**
|
||||
* Show if something is on on the course clipboard (moving around)
|
||||
*
|
||||
|
||||
@@ -108,4 +108,16 @@ class format_social extends format_base {
|
||||
}
|
||||
return $courseformatoptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this course format allows the activity to
|
||||
* have "triple visibility state" - visible always, hidden on course page but available, hidden.
|
||||
*
|
||||
* @param stdClass|cm_info $cm course module (may be null if we are displaying a form for adding a module)
|
||||
* @param stdClass|section_info $section section where this module is located or will be added to
|
||||
* @return bool
|
||||
*/
|
||||
public function allow_stealth_module_visibility($cm, $section) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -410,6 +410,36 @@ class format_topics extends format_base {
|
||||
public function supports_news() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this course format allows the activity to
|
||||
* have "triple visibility state" - visible always, hidden on course page but available, hidden.
|
||||
*
|
||||
* @param stdClass|cm_info $cm course module (may be null if we are displaying a form for adding a module)
|
||||
* @param stdClass|section_info $section section where this module is located or will be added to
|
||||
* @return bool
|
||||
*/
|
||||
public function allow_stealth_module_visibility($cm, $section) {
|
||||
// Allow the third visibility state inside visible sections or in section 0, not allow in orphaned sections.
|
||||
return !$section->section || ($section->visible && $section->section <= $this->get_course()->numsections);
|
||||
}
|
||||
|
||||
public function section_action($section, $action, $sr) {
|
||||
global $PAGE;
|
||||
|
||||
if ($section->section && ($action === 'setmarker' || $action === 'removemarker')) {
|
||||
// Format 'topics' allows to set and remove markers in addition to common section actions.
|
||||
require_capability('moodle/course:setcurrentsection', context_course::instance($this->courseid));
|
||||
course_set_marker($this->courseid, ($action === 'setmarker') ? $section->section : 0);
|
||||
return null;
|
||||
}
|
||||
|
||||
// For show/hide actions call the parent method and return the new content for .section_availability element.
|
||||
$rv = parent::section_action($section, $action, $sr);
|
||||
$renderer = $PAGE->get_renderer('format_topics');
|
||||
$rv['section_availability'] = $renderer->section_availability($this->get_section($section));
|
||||
return $rv;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -129,7 +129,8 @@ class format_topics_renderer extends format_section_renderer_base {
|
||||
$controls['highlight'] = array('url' => $url, "icon" => 'i/marked',
|
||||
'name' => $highlightoff,
|
||||
'pixattr' => array('class' => '', 'alt' => $markedthistopic),
|
||||
'attr' => array('class' => 'editing_highlight', 'title' => $markedthistopic));
|
||||
'attr' => array('class' => 'editing_highlight', 'title' => $markedthistopic,
|
||||
'data-action' => 'removemarker'));
|
||||
} else {
|
||||
$url->param('marker', $section->section);
|
||||
$markthistopic = get_string('markthistopic');
|
||||
@@ -137,7 +138,8 @@ class format_topics_renderer extends format_section_renderer_base {
|
||||
$controls['highlight'] = array('url' => $url, "icon" => 'i/marker',
|
||||
'name' => $highlight,
|
||||
'pixattr' => array('class' => '', 'alt' => $markthistopic),
|
||||
'attr' => array('class' => 'editing_highlight', 'title' => $markthistopic));
|
||||
'attr' => array('class' => 'editing_highlight', 'title' => $markthistopic,
|
||||
'data-action' => 'setmarker'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,15 @@ This files describes API changes for course formats
|
||||
|
||||
Overview of this plugin type at http://docs.moodle.org/dev/Course_formats
|
||||
|
||||
=== 3.3 ===
|
||||
* Javascript code for editing activities and sections was moved to an AMD module, course/rest.php is no longer
|
||||
responsible for editing actions, instead it is done in web services. Carefully test all editing actions during upgrade.
|
||||
* The new method format_base::allow_stealth_module_visibility() can indicate whether course format supports "stealth"
|
||||
activities mode when they are available but not visible on course page. Course format that supports stealth mode
|
||||
must check $cm->is_visible_on_course_page() when displaying activities list on the course page instead of $cm->uservisible.
|
||||
For all other plugins except course formats the same property $cm->uservisible indicates if the activity contents
|
||||
is actually available to student.
|
||||
|
||||
=== 3.2 ===
|
||||
* Callback delete_course is deprecated and should be replaced with observer for event \core\event\course_content_deleted
|
||||
* Course formats can overwrite get_default_course_enddate function to set the default course end date for new courses.
|
||||
|
||||
@@ -497,6 +497,29 @@ class format_weeks extends format_base {
|
||||
public function supports_news() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this course format allows the activity to
|
||||
* have "triple visibility state" - visible always, hidden on course page but available, hidden.
|
||||
*
|
||||
* @param stdClass|cm_info $cm course module (may be null if we are displaying a form for adding a module)
|
||||
* @param stdClass|section_info $section section where this module is located or will be added to
|
||||
* @return bool
|
||||
*/
|
||||
public function allow_stealth_module_visibility($cm, $section) {
|
||||
// Allow the third visibility state inside visible sections or in section 0, not allow in orphaned sections.
|
||||
return !$section->section || ($section->visible && $section->section <= $this->get_course()->numsections);
|
||||
}
|
||||
|
||||
public function section_action($section, $action, $sr) {
|
||||
global $PAGE;
|
||||
|
||||
// Call the parent method and return the new content for .section_availability element.
|
||||
$rv = parent::section_action($section, $action, $sr);
|
||||
$renderer = $PAGE->get_renderer('format_weeks');
|
||||
$rv['section_availability'] = $renderer->section_availability($this->get_section($section));
|
||||
return $rv;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+87
-49
@@ -388,13 +388,16 @@ function get_array_of_activities($courseid) {
|
||||
if (empty($rawmods)) {
|
||||
return $mod; // always return array
|
||||
}
|
||||
$courseformat = course_get_format($course);
|
||||
|
||||
if ($sections = $DB->get_records('course_sections', array('course' => $courseid), 'section ASC', 'id,section,sequence')) {
|
||||
if ($sections = $DB->get_records('course_sections', array('course' => $courseid),
|
||||
'section ASC', 'id,section,sequence,visible')) {
|
||||
// First check and correct obvious mismatches between course_sections.sequence and course_modules.section.
|
||||
if ($errormessages = course_integrity_check($courseid, $rawmods, $sections)) {
|
||||
debugging(join('<br>', $errormessages));
|
||||
$rawmods = get_course_mods($courseid);
|
||||
$sections = $DB->get_records('course_sections', array('course' => $courseid), 'section ASC', 'id,section,sequence');
|
||||
$sections = $DB->get_records('course_sections', array('course' => $courseid),
|
||||
'section ASC', 'id,section,sequence,visible');
|
||||
}
|
||||
// Build array of activities.
|
||||
foreach ($sections as $section) {
|
||||
@@ -404,6 +407,13 @@ function get_array_of_activities($courseid) {
|
||||
if (empty($rawmods[$seq])) {
|
||||
continue;
|
||||
}
|
||||
// Adjust visibleoncoursepage, value in DB may not respect format availability.
|
||||
$rawmods[$seq]->visibleoncoursepage = (!$rawmods[$seq]->visible
|
||||
|| $rawmods[$seq]->visibleoncoursepage
|
||||
|| empty($CFG->allowstealth)
|
||||
|| !$courseformat->allow_stealth_module_visibility($rawmods[$seq], $section)) ? 1 : 0;
|
||||
|
||||
// Create an object that will be cached.
|
||||
$mod[$seq] = new stdClass();
|
||||
$mod[$seq]->id = $rawmods[$seq]->instance;
|
||||
$mod[$seq]->cm = $rawmods[$seq]->id;
|
||||
@@ -418,6 +428,7 @@ function get_array_of_activities($courseid) {
|
||||
$mod[$seq]->score = $rawmods[$seq]->score;
|
||||
$mod[$seq]->idnumber = $rawmods[$seq]->idnumber;
|
||||
$mod[$seq]->visible = $rawmods[$seq]->visible;
|
||||
$mod[$seq]->visibleoncoursepage = $rawmods[$seq]->visibleoncoursepage;
|
||||
$mod[$seq]->visibleold = $rawmods[$seq]->visibleold;
|
||||
$mod[$seq]->groupmode = $rawmods[$seq]->groupmode;
|
||||
$mod[$seq]->groupingid = $rawmods[$seq]->groupingid;
|
||||
@@ -557,9 +568,15 @@ function get_module_types_names($plural = false) {
|
||||
* @return void
|
||||
*/
|
||||
function course_set_marker($courseid, $marker) {
|
||||
global $DB;
|
||||
global $DB, $COURSE;
|
||||
$DB->set_field("course", "marker", $marker, array('id' => $courseid));
|
||||
format_base::reset_course_cache($courseid);
|
||||
if ($COURSE && $COURSE->id == $courseid) {
|
||||
$COURSE->marker = $marker;
|
||||
}
|
||||
if (class_exists('format_base')) {
|
||||
format_base::reset_course_cache($courseid);
|
||||
}
|
||||
course_modinfo::clear_instance_cache($courseid);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -950,33 +967,29 @@ function set_coursemodule_idnumber($id, $idnumber) {
|
||||
*
|
||||
* @param int $id of the module
|
||||
* @param int $visible state of the module
|
||||
* @param int $visibleoncoursepage state of the module on the course page
|
||||
* @return bool false when the module was not found, true otherwise
|
||||
*/
|
||||
function set_coursemodule_visible($id, $visible) {
|
||||
function set_coursemodule_visible($id, $visible, $visibleoncoursepage = 1) {
|
||||
global $DB, $CFG;
|
||||
require_once($CFG->libdir.'/gradelib.php');
|
||||
require_once($CFG->dirroot.'/calendar/lib.php');
|
||||
|
||||
// Trigger developer's attention when using the previously removed argument.
|
||||
if (func_num_args() > 2) {
|
||||
debugging('Wrong number of arguments passed to set_coursemodule_visible(), $prevstateoverrides
|
||||
has been removed.', DEBUG_DEVELOPER);
|
||||
}
|
||||
|
||||
if (!$cm = $DB->get_record('course_modules', array('id'=>$id))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create events and propagate visibility to associated grade items if the value has changed.
|
||||
// Only do this if it's changed to avoid accidently overwriting manual showing/hiding of student grades.
|
||||
if ($cm->visible == $visible) {
|
||||
if ($cm->visible == $visible && $cm->visibleoncoursepage == $visibleoncoursepage) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!$modulename = $DB->get_field('modules', 'name', array('id'=>$cm->module))) {
|
||||
return false;
|
||||
}
|
||||
if ($events = $DB->get_records('event', array('instance'=>$cm->instance, 'modulename'=>$modulename))) {
|
||||
if (($cm->visible != $visible) &&
|
||||
($events = $DB->get_records('event', array('instance' => $cm->instance, 'modulename' => $modulename)))) {
|
||||
foreach($events as $event) {
|
||||
if ($visible) {
|
||||
$event = new calendar_event($event);
|
||||
@@ -993,17 +1006,19 @@ function set_coursemodule_visible($id, $visible) {
|
||||
$cminfo = new stdClass();
|
||||
$cminfo->id = $id;
|
||||
$cminfo->visible = $visible;
|
||||
$cminfo->visibleoncoursepage = $visibleoncoursepage;
|
||||
$cminfo->visibleold = $visible;
|
||||
$DB->update_record('course_modules', $cminfo);
|
||||
|
||||
// Hide the associated grade items so the teacher doesn't also have to go to the gradebook and hide them there.
|
||||
// Note that this must be done after updating the row in course_modules, in case
|
||||
// the modules grade_item_update function needs to access $cm->visible.
|
||||
if (plugin_supports('mod', $modulename, FEATURE_CONTROLS_GRADE_VISIBILITY) &&
|
||||
if ($cm->visible != $visible &&
|
||||
plugin_supports('mod', $modulename, FEATURE_CONTROLS_GRADE_VISIBILITY) &&
|
||||
component_callback_exists('mod_' . $modulename, 'grade_item_update')) {
|
||||
$instance = $DB->get_record($modulename, array('id' => $cm->instance), '*', MUST_EXIST);
|
||||
component_callback('mod_' . $modulename, 'grade_item_update', array($instance));
|
||||
} else {
|
||||
} else if ($cm->visible != $visible) {
|
||||
$grade_items = grade_item::fetch_all(array('itemtype'=>'mod', 'itemmodule'=>$modulename, 'iteminstance'=>$cm->instance, 'courseid'=>$cm->course));
|
||||
if ($grade_items) {
|
||||
foreach ($grade_items as $grade_item) {
|
||||
@@ -1593,10 +1608,10 @@ function course_update_section($course, $section, $data) {
|
||||
if ($cm = get_coursemodule_from_id(null, $moduleid, $courseid)) {
|
||||
if ($data['visible']) {
|
||||
// As we unhide the section, we use the previously saved visibility stored in visibleold.
|
||||
set_coursemodule_visible($moduleid, $cm->visibleold);
|
||||
set_coursemodule_visible($moduleid, $cm->visibleold, $cm->visibleoncoursepage);
|
||||
} else {
|
||||
// We hide the section, so we hide the module but we store the original state in visibleold.
|
||||
set_coursemodule_visible($moduleid, 0);
|
||||
set_coursemodule_visible($moduleid, 0, $cm->visibleoncoursepage);
|
||||
$DB->set_field('course_modules', 'visibleold', $cm->visible, array('id' => $moduleid));
|
||||
}
|
||||
\core\event\course_module_updated::create_from_cm($cm)->trigger();
|
||||
@@ -1765,12 +1780,13 @@ function moveto_module($mod, $section, $beforemod=NULL) {
|
||||
* @return array array of action_link or pix_icon objects
|
||||
*/
|
||||
function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) {
|
||||
global $COURSE, $SITE;
|
||||
global $COURSE, $SITE, $CFG;
|
||||
|
||||
static $str;
|
||||
|
||||
$coursecontext = context_course::instance($mod->course);
|
||||
$modcontext = context_module::instance($mod->id);
|
||||
$courseformat = course_get_format($mod->get_course());
|
||||
|
||||
$editcaps = array('moodle/course:manageactivities', 'moodle/course:activityvisibility', 'moodle/role:assign');
|
||||
$dupecaps = array('moodle/backup:backuptargetimport', 'moodle/restore:restoretargetimport');
|
||||
@@ -1784,7 +1800,7 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) {
|
||||
|
||||
if (!isset($str)) {
|
||||
$str = get_strings(array('delete', 'move', 'moveright', 'moveleft',
|
||||
'editsettings', 'duplicate', 'hide', 'show'), 'moodle');
|
||||
'editsettings', 'duplicate', 'hide', 'makeavailable', 'makeunavailable', 'show'), 'moodle');
|
||||
$str->assign = get_string('assignroles', 'role');
|
||||
$str->groupsnone = get_string('clicktochangeinbrackets', 'moodle', get_string("groupsnone"));
|
||||
$str->groupsseparate = get_string('clicktochangeinbrackets', 'moodle', get_string("groupsseparate"));
|
||||
@@ -1830,7 +1846,8 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) {
|
||||
new moodle_url($baseurl, array('id' => $mod->id, 'indent' => '1')),
|
||||
new pix_icon($rightarrow, $str->moveright, 'moodle', array('class' => 'iconsmall', 'title' => '')),
|
||||
$str->moveright,
|
||||
array('class' => 'editing_moveright ' . $enabledclass, 'data-action' => 'moveright', 'data-keepopen' => true)
|
||||
array('class' => 'editing_moveright ' . $enabledclass, 'data-action' => 'moveright',
|
||||
'data-keepopen' => true, 'data-sectionreturn' => $sr)
|
||||
);
|
||||
|
||||
if ($indent <= $indentlimits->min) {
|
||||
@@ -1842,21 +1859,33 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) {
|
||||
new moodle_url($baseurl, array('id' => $mod->id, 'indent' => '-1')),
|
||||
new pix_icon($leftarrow, $str->moveleft, 'moodle', array('class' => 'iconsmall', 'title' => '')),
|
||||
$str->moveleft,
|
||||
array('class' => 'editing_moveleft ' . $enabledclass, 'data-action' => 'moveleft', 'data-keepopen' => true)
|
||||
array('class' => 'editing_moveleft ' . $enabledclass, 'data-action' => 'moveleft',
|
||||
'data-keepopen' => true, 'data-sectionreturn' => $sr)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
// Hide/Show.
|
||||
// Hide/Show/Available/Unavailable.
|
||||
if (has_capability('moodle/course:activityvisibility', $modcontext)) {
|
||||
if ($mod->visible) {
|
||||
$allowstealth = !empty($CFG->allowstealth) && $courseformat->allow_stealth_module_visibility($mod, $mod->get_section_info());
|
||||
|
||||
$sectionvisible = $mod->get_section_info()->visible;
|
||||
// The module on the course page may be in one of the following states:
|
||||
// - Available and displayed on the course page ($displayedoncoursepage);
|
||||
// - Not available and not displayed on the course page ($unavailable);
|
||||
// - Available but not displayed on the course page ($stealth) - this can also be a visible activity in a hidden section.
|
||||
$displayedoncoursepage = $mod->visible && $mod->visibleoncoursepage && $sectionvisible;
|
||||
$unavailable = !$mod->visible;
|
||||
$stealth = $mod->visible && (!$mod->visibleoncoursepage || !$sectionvisible);
|
||||
if ($displayedoncoursepage) {
|
||||
$actions['hide'] = new action_menu_link_secondary(
|
||||
new moodle_url($baseurl, array('hide' => $mod->id)),
|
||||
new pix_icon('t/hide', $str->hide, 'moodle', array('class' => 'iconsmall', 'title' => '')),
|
||||
$str->hide,
|
||||
array('class' => 'editing_hide', 'data-action' => 'hide')
|
||||
);
|
||||
} else {
|
||||
} else if (!$displayedoncoursepage && $sectionvisible) {
|
||||
// Offer to "show" only if the section is visible.
|
||||
$actions['show'] = new action_menu_link_secondary(
|
||||
new moodle_url($baseurl, array('show' => $mod->id)),
|
||||
new pix_icon('t/show', $str->show, 'moodle', array('class' => 'iconsmall', 'title' => '')),
|
||||
@@ -1864,16 +1893,38 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) {
|
||||
array('class' => 'editing_show', 'data-action' => 'show')
|
||||
);
|
||||
}
|
||||
|
||||
if ($stealth) {
|
||||
// When making the "stealth" module unavailable we perform the same action as hiding the visible module.
|
||||
$actions['hide'] = new action_menu_link_secondary(
|
||||
new moodle_url($baseurl, array('hide' => $mod->id)),
|
||||
new pix_icon('t/unblock', $str->makeunavailable, 'moodle', array('class' => 'iconsmall', 'title' => '')),
|
||||
$str->makeunavailable,
|
||||
array('class' => 'editing_makeunavailable', 'data-action' => 'hide', 'data-sectionreturn' => $sr)
|
||||
);
|
||||
} else if ($unavailable && (!$sectionvisible || $allowstealth) && $mod->has_view()) {
|
||||
// Allow to make visually hidden module available in gradebook and other reports by making it a "stealth" module.
|
||||
// When the section is hidden it is an equivalent of "showing" the module.
|
||||
// Activities without the link (i.e. labels) can not be made available but hidden on course page.
|
||||
$action = $sectionvisible ? 'stealth' : 'show';
|
||||
$actions[$action] = new action_menu_link_secondary(
|
||||
new moodle_url($baseurl, array($action => $mod->id)),
|
||||
new pix_icon('t/block', $str->makeavailable, 'moodle', array('class' => 'iconsmall', 'title' => '')),
|
||||
$str->makeavailable,
|
||||
array('class' => 'editing_makeavailable', 'data-action' => $action, 'data-sectionreturn' => $sr)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Duplicate (require both target import caps to be able to duplicate and backup2 support, see modduplicate.php)
|
||||
if (has_all_capabilities($dupecaps, $coursecontext) &&
|
||||
plugin_supports('mod', $mod->modname, FEATURE_BACKUP_MOODLE2)) {
|
||||
plugin_supports('mod', $mod->modname, FEATURE_BACKUP_MOODLE2) &&
|
||||
course_allowed_module($mod->get_course(), $mod->modname)) {
|
||||
$actions['duplicate'] = new action_menu_link_secondary(
|
||||
new moodle_url($baseurl, array('duplicate' => $mod->id)),
|
||||
new pix_icon('t/copy', $str->duplicate, 'moodle', array('class' => 'iconsmall', 'title' => '')),
|
||||
$str->duplicate,
|
||||
array('class' => 'editing_duplicate', 'data-action' => 'duplicate', 'data-sr' => $sr)
|
||||
array('class' => 'editing_duplicate', 'data-action' => 'duplicate', 'data-sectionreturn' => $sr)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1884,16 +1935,19 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) {
|
||||
$nextgroupmode = VISIBLEGROUPS;
|
||||
$grouptitle = $str->groupsseparate;
|
||||
$actionname = 'groupsseparate';
|
||||
$nextactionname = 'groupsvisible';
|
||||
$groupimage = 'i/groups';
|
||||
} else if ($mod->effectivegroupmode == VISIBLEGROUPS) {
|
||||
$nextgroupmode = NOGROUPS;
|
||||
$grouptitle = $str->groupsvisible;
|
||||
$actionname = 'groupsvisible';
|
||||
$nextactionname = 'groupsnone';
|
||||
$groupimage = 'i/groupv';
|
||||
} else {
|
||||
$nextgroupmode = SEPARATEGROUPS;
|
||||
$grouptitle = $str->groupsnone;
|
||||
$actionname = 'groupsnone';
|
||||
$nextactionname = 'groupsseparate';
|
||||
$groupimage = 'i/groupn';
|
||||
}
|
||||
|
||||
@@ -1901,7 +1955,8 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) {
|
||||
new moodle_url($baseurl, array('id' => $mod->id, 'groupmode' => $nextgroupmode)),
|
||||
new pix_icon($groupimage, null, 'moodle', array('class' => 'iconsmall')),
|
||||
$grouptitle,
|
||||
array('class' => 'editing_'. $actionname, 'data-action' => $actionname, 'data-nextgroupmode' => $nextgroupmode, 'aria-live' => 'assertive')
|
||||
array('class' => 'editing_'. $actionname, 'data-action' => $nextactionname,
|
||||
'aria-live' => 'assertive', 'data-sectionreturn' => $sr)
|
||||
);
|
||||
} else {
|
||||
$actions['nogroupsupport'] = new action_menu_filler();
|
||||
@@ -1914,7 +1969,7 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) {
|
||||
new moodle_url('/admin/roles/assign.php', array('contextid' => $modcontext->id)),
|
||||
new pix_icon('t/assignroles', $str->assign, 'moodle', array('class' => 'iconsmall', 'title' => '')),
|
||||
$str->assign,
|
||||
array('class' => 'editing_assign', 'data-action' => 'assignroles')
|
||||
array('class' => 'editing_assign', 'data-action' => 'assignroles', 'data-sectionreturn' => $sr)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1924,7 +1979,7 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) {
|
||||
new moodle_url($baseurl, array('delete' => $mod->id)),
|
||||
new pix_icon('t/delete', $str->delete, 'moodle', array('class' => 'iconsmall', 'title' => '')),
|
||||
$str->delete,
|
||||
array('class' => 'editing_delete', 'data-action' => 'delete')
|
||||
array('class' => 'editing_delete', 'data-action' => 'delete', 'data-sectionreturn' => $sr)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1970,7 +2025,7 @@ function course_get_cm_move(cm_info $mod, $sr = null) {
|
||||
return html_writer::link(
|
||||
new moodle_url($baseurl, array('copy' => $mod->id)),
|
||||
$OUTPUT->pix_icon($pixicon, $str->move, 'moodle', array('class' => 'iconsmall', 'title' => '')),
|
||||
array('class' => 'editing_move', 'data-action' => 'move')
|
||||
array('class' => 'editing_move', 'data-action' => 'move', 'data-sectionreturn' => $sr)
|
||||
);
|
||||
}
|
||||
return '';
|
||||
@@ -3007,25 +3062,6 @@ function include_course_ajax($course, $usedmodules = array(), $enabledmodules =
|
||||
$config->pageparams = array();
|
||||
}
|
||||
|
||||
// Include toolboxes
|
||||
$PAGE->requires->yui_module('moodle-course-toolboxes',
|
||||
'M.course.init_resource_toolbox',
|
||||
array(array(
|
||||
'courseid' => $course->id,
|
||||
'ajaxurl' => $config->resourceurl,
|
||||
'config' => $config,
|
||||
))
|
||||
);
|
||||
$PAGE->requires->yui_module('moodle-course-toolboxes',
|
||||
'M.course.init_section_toolbox',
|
||||
array(array(
|
||||
'courseid' => $course->id,
|
||||
'format' => $course->format,
|
||||
'ajaxurl' => $config->sectionurl,
|
||||
'config' => $config,
|
||||
))
|
||||
);
|
||||
|
||||
// Include course dragdrop
|
||||
if (course_format_uses_sections($course->format)) {
|
||||
$PAGE->requires->yui_module('moodle-course-dragdrop', 'M.course.init_section_dragdrop',
|
||||
@@ -3088,6 +3124,8 @@ function include_course_ajax($course, $usedmodules = array(), $enabledmodules =
|
||||
require_once($CFG->dirroot.'/course/dnduploadlib.php');
|
||||
dndupload_add_to_course($course, $enabledmodules);
|
||||
|
||||
$PAGE->requires->js_call_amd('core_course/actions', 'initCoursePage', array($course->format));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
+19
-16
@@ -33,6 +33,7 @@ $indent = optional_param('indent', 0, PARAM_INT);
|
||||
$update = optional_param('update', 0, PARAM_INT);
|
||||
$duplicate = optional_param('duplicate', 0, PARAM_INT);
|
||||
$hide = optional_param('hide', 0, PARAM_INT);
|
||||
$stealth = optional_param('stealth', 0, PARAM_INT);
|
||||
$show = optional_param('show', 0, PARAM_INT);
|
||||
$copy = optional_param('copy', 0, PARAM_INT);
|
||||
$moveto = optional_param('moveto', 0, PARAM_INT);
|
||||
@@ -202,28 +203,30 @@ if ((!empty($movetosection) or !empty($moveto)) and confirm_sesskey()) {
|
||||
$modcontext = context_module::instance($cm->id);
|
||||
require_capability('moodle/course:activityvisibility', $modcontext);
|
||||
|
||||
set_coursemodule_visible($cm->id, 0);
|
||||
\core\event\course_module_updated::create_from_cm($cm, $modcontext)->trigger();
|
||||
if (set_coursemodule_visible($cm->id, 0)) {
|
||||
\core\event\course_module_updated::create_from_cm($cm, $modcontext)->trigger();
|
||||
}
|
||||
redirect(course_get_url($course, $cm->sectionnum, array('sr' => $sectionreturn)));
|
||||
|
||||
} else if (!empty($stealth) and confirm_sesskey()) {
|
||||
list($course, $cm) = get_course_and_cm_from_cmid($stealth);
|
||||
require_login($course, false, $cm);
|
||||
require_capability('moodle/course:activityvisibility', $cm->context);
|
||||
|
||||
if (set_coursemodule_visible($cm->id, 1, 0)) {
|
||||
\core\event\course_module_updated::create_from_cm($cm)->trigger();
|
||||
}
|
||||
redirect(course_get_url($course, $cm->sectionnum, array('sr' => $sectionreturn)));
|
||||
|
||||
} else if (!empty($show) and confirm_sesskey()) {
|
||||
$cm = get_coursemodule_from_id('', $show, 0, true, MUST_EXIST);
|
||||
$course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
|
||||
|
||||
list($course, $cm) = get_course_and_cm_from_cmid($show);
|
||||
require_login($course, false, $cm);
|
||||
$coursecontext = context_course::instance($course->id);
|
||||
$modcontext = context_module::instance($cm->id);
|
||||
require_capability('moodle/course:activityvisibility', $modcontext);
|
||||
require_capability('moodle/course:activityvisibility', $cm->context);
|
||||
$section = $cm->get_section_info();
|
||||
|
||||
$section = $DB->get_record('course_sections', array('id'=>$cm->section), '*', MUST_EXIST);
|
||||
|
||||
$module = $DB->get_record('modules', array('id'=>$cm->module), '*', MUST_EXIST);
|
||||
|
||||
if ($module->visible and ($section->visible or (SITEID == $cm->course))) {
|
||||
set_coursemodule_visible($cm->id, 1);
|
||||
\core\event\course_module_updated::create_from_cm($cm, $modcontext)->trigger();
|
||||
if (set_coursemodule_visible($cm->id, 1)) {
|
||||
\core\event\course_module_updated::create_from_cm($cm)->trigger();
|
||||
}
|
||||
|
||||
redirect(course_get_url($course, $section->section, array('sr' => $sectionreturn)));
|
||||
|
||||
} else if ($groupmode > -1 and confirm_sesskey()) {
|
||||
|
||||
+3
-1
@@ -60,6 +60,7 @@ function add_moduleinfo($moduleinfo, $course, $mform = null) {
|
||||
$newcm->module = $moduleinfo->module;
|
||||
$newcm->instance = 0; // Not known yet, will be updated later (this is similar to restore code).
|
||||
$newcm->visible = $moduleinfo->visible;
|
||||
$newcm->visibleoncoursepage = $moduleinfo->visibleoncoursepage;
|
||||
$newcm->visibleold = $moduleinfo->visible;
|
||||
if (isset($moduleinfo->cmidnumber)) {
|
||||
$newcm->idnumber = $moduleinfo->cmidnumber;
|
||||
@@ -597,7 +598,7 @@ function update_moduleinfo($cm, $moduleinfo, $course, $mform = null) {
|
||||
|
||||
// Make sure visibility is set correctly (in particular in calendar).
|
||||
if (has_capability('moodle/course:activityvisibility', $modcontext)) {
|
||||
set_coursemodule_visible($moduleinfo->coursemodule, $moduleinfo->visible);
|
||||
set_coursemodule_visible($moduleinfo->coursemodule, $moduleinfo->visible, $moduleinfo->visibleoncoursepage);
|
||||
}
|
||||
|
||||
if (isset($moduleinfo->cmidnumber)) { // Label.
|
||||
@@ -655,6 +656,7 @@ function get_moduleinfo_data($cm, $course) {
|
||||
$data->coursemodule = $cm->id;
|
||||
$data->section = $cw->section; // The section number itself - relative!!! (section column in course_sections)
|
||||
$data->visible = $cm->visible; //?? $cw->visible ? $cm->visible : 0; // section hiding overrides
|
||||
$data->visibleoncoursepage = $cm->visibleoncoursepage;
|
||||
$data->cmidnumber = $cm->idnumber; // The cm IDnumber
|
||||
$data->groupmode = groups_get_activity_groupmode($cm); // locked later if forced
|
||||
$data->groupingid = $cm->groupingid;
|
||||
|
||||
@@ -21,7 +21,7 @@ abstract class moodleform_mod extends moodleform {
|
||||
* Section of course that module instance will be put in or is in.
|
||||
* This is always the section number itself (column 'section' from 'course_sections' table).
|
||||
*
|
||||
* @var mixed
|
||||
* @var int
|
||||
*/
|
||||
protected $_section;
|
||||
/**
|
||||
@@ -555,7 +555,10 @@ abstract class moodleform_mod extends moodleform {
|
||||
|
||||
$mform->addElement('header', 'modstandardelshdr', get_string('modstandardels', 'form'));
|
||||
|
||||
$mform->addElement('modvisible', 'visible', get_string('visible'));
|
||||
$section = get_fast_modinfo($COURSE)->get_section_info($this->_section);
|
||||
$allowstealth = !empty($CFG->allowstealth) && $this->courseformat->allow_stealth_module_visibility($this->_cm, $section);
|
||||
$mform->addElement('modvisible', 'visible', get_string('visible'), null,
|
||||
array('allowstealth' => $allowstealth, 'sectionvisible' => $section->visible, 'cm' => $this->_cm));
|
||||
if (!empty($this->_cm)) {
|
||||
$context = context_module::instance($this->_cm->id);
|
||||
if (!has_capability('moodle/course:activityvisibility', $context)) {
|
||||
|
||||
+92
-69
@@ -450,7 +450,7 @@ class core_course_renderer extends plugin_renderer_base {
|
||||
public function course_section_cm_completion($course, &$completioninfo, cm_info $mod, $displayoptions = array()) {
|
||||
global $CFG;
|
||||
$output = '';
|
||||
if (!empty($displayoptions['hidecompletion']) || !isloggedin() || isguestuser() || !$mod->uservisible) {
|
||||
if (!$mod->is_visible_on_course_page()) {
|
||||
return $output;
|
||||
}
|
||||
if ($completioninfo === null) {
|
||||
@@ -582,15 +582,58 @@ class core_course_renderer extends plugin_renderer_base {
|
||||
* @return string
|
||||
*/
|
||||
public function course_section_cm_name(cm_info $mod, $displayoptions = array()) {
|
||||
if ((!$mod->uservisible && empty($mod->availableinfo)) || !$mod->url) {
|
||||
if (!$mod->is_visible_on_course_page() || !$mod->url) {
|
||||
// Nothing to be displayed to the user.
|
||||
return '';
|
||||
}
|
||||
|
||||
list($linkclasses, $textclasses) = $this->course_section_cm_classes($mod);
|
||||
$groupinglabel = $mod->get_grouping_label($textclasses);
|
||||
|
||||
// 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));
|
||||
return $this->output->render_from_template('core/inplace_editable', $tmpl->export_for_template($this->output)) .
|
||||
$groupinglabel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the CSS classes for the activity name/content
|
||||
*
|
||||
* For items which are hidden, unavailable or stealth but should be displayed
|
||||
* to current user ($mod->is_visible_on_course_page()), we show those as dimmed.
|
||||
* Students will also see as dimmed activities names that are not yet available
|
||||
* but should still be displayed (without link) with availability info.
|
||||
*
|
||||
* @param cm_info $mod
|
||||
* @return array array of two elements ($linkclasses, $textclasses)
|
||||
*/
|
||||
protected function course_section_cm_classes(cm_info $mod) {
|
||||
$linkclasses = '';
|
||||
$textclasses = '';
|
||||
if ($mod->uservisible) {
|
||||
$conditionalhidden = $this->is_cm_conditionally_hidden($mod);
|
||||
$accessiblebutdim = (!$mod->visible || $conditionalhidden) &&
|
||||
has_capability('moodle/course:viewhiddenactivities', $mod->context);
|
||||
if ($accessiblebutdim) {
|
||||
$linkclasses .= ' dimmed';
|
||||
$textclasses .= ' dimmed_text';
|
||||
if ($conditionalhidden) {
|
||||
$linkclasses .= ' conditionalhidden';
|
||||
$textclasses .= ' conditionalhidden';
|
||||
}
|
||||
}
|
||||
if ($mod->is_stealth()) {
|
||||
// Stealth activity is the one that is not visible on course page.
|
||||
// It still may be displayed to the users who can manage it.
|
||||
$linkclasses .= ' stealth';
|
||||
$textclasses .= ' stealth';
|
||||
}
|
||||
} else {
|
||||
$linkclasses .= ' dimmed';
|
||||
$textclasses .= ' dimmed_text';
|
||||
}
|
||||
return array($linkclasses, $textclasses);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -608,12 +651,9 @@ class core_course_renderer extends plugin_renderer_base {
|
||||
*/
|
||||
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.
|
||||
return $output;
|
||||
}
|
||||
$url = $mod->url;
|
||||
if (!$url) {
|
||||
if (!$mod->is_visible_on_course_page() || !$url) {
|
||||
// Nothing to be displayed to the user.
|
||||
return $output;
|
||||
}
|
||||
|
||||
@@ -632,51 +672,22 @@ class core_course_renderer extends plugin_renderer_base {
|
||||
$altname = get_accesshide(' '.$altname);
|
||||
}
|
||||
|
||||
// For items which are hidden but available to current user
|
||||
// ($mod->uservisible), we show those as dimmed only if the user has
|
||||
// viewhiddenactivities, so that teachers see 'items which might not
|
||||
// be available to some students' dimmed but students do not see 'item
|
||||
// which is actually available to current student' dimmed.
|
||||
$linkclasses = '';
|
||||
$accesstext = '';
|
||||
$textclasses = '';
|
||||
if ($mod->uservisible) {
|
||||
$conditionalhidden = $this->is_cm_conditionally_hidden($mod);
|
||||
$accessiblebutdim = (!$mod->visible || $conditionalhidden) &&
|
||||
has_capability('moodle/course:viewhiddenactivities', $mod->context);
|
||||
if ($accessiblebutdim) {
|
||||
$linkclasses .= ' dimmed';
|
||||
$textclasses .= ' dimmed_text';
|
||||
if ($conditionalhidden) {
|
||||
$linkclasses .= ' conditionalhidden';
|
||||
$textclasses .= ' conditionalhidden';
|
||||
}
|
||||
// Show accessibility note only if user can access the module himself.
|
||||
$accesstext = get_accesshide(get_string('hiddenfromstudents').':'. $mod->modfullname);
|
||||
}
|
||||
} else {
|
||||
$linkclasses .= ' dimmed';
|
||||
$textclasses .= ' dimmed_text';
|
||||
}
|
||||
list($linkclasses, $textclasses) = $this->course_section_cm_classes($mod);
|
||||
|
||||
// Get on-click attribute value if specified and decode the onclick - it
|
||||
// has already been encoded for display (puke).
|
||||
$onclick = htmlspecialchars_decode($mod->onclick, ENT_QUOTES);
|
||||
|
||||
$groupinglabel = $mod->get_grouping_label($textclasses);
|
||||
|
||||
// Display link itself.
|
||||
$activitylink = html_writer::empty_tag('img', array('src' => $mod->get_icon_url(),
|
||||
'class' => 'iconlarge activityicon', 'alt' => ' ', 'role' => 'presentation')) . $accesstext .
|
||||
'class' => 'iconlarge activityicon', 'alt' => ' ', 'role' => 'presentation')) .
|
||||
html_writer::tag('span', $instancename . $altname, array('class' => 'instancename'));
|
||||
if ($mod->uservisible) {
|
||||
$output .= html_writer::link($url, $activitylink, array('class' => $linkclasses, 'onclick' => $onclick)) .
|
||||
$groupinglabel;
|
||||
$output .= html_writer::link($url, $activitylink, array('class' => $linkclasses, 'onclick' => $onclick));
|
||||
} else {
|
||||
// We may be displaying this just in order to show information
|
||||
// about visibility, without the actual link ($mod->uservisible)
|
||||
$output .= html_writer::tag('div', $activitylink, array('class' => $textclasses)) .
|
||||
$groupinglabel;
|
||||
// about visibility, without the actual link ($mod->is_visible_on_course_page()).
|
||||
$output .= html_writer::tag('div', $activitylink, array('class' => $textclasses));
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
@@ -690,29 +701,13 @@ class core_course_renderer extends plugin_renderer_base {
|
||||
*/
|
||||
public function course_section_cm_text(cm_info $mod, $displayoptions = array()) {
|
||||
$output = '';
|
||||
if (!$mod->uservisible && empty($mod->availableinfo)) {
|
||||
if (!$mod->is_visible_on_course_page()) {
|
||||
// nothing to be displayed to the user
|
||||
return $output;
|
||||
}
|
||||
$content = $mod->get_formatted_content(array('overflowdiv' => true, 'noclean' => true));
|
||||
$accesstext = '';
|
||||
$textclasses = '';
|
||||
if ($mod->uservisible) {
|
||||
$conditionalhidden = $this->is_cm_conditionally_hidden($mod);
|
||||
$accessiblebutdim = (!$mod->visible || $conditionalhidden) &&
|
||||
has_capability('moodle/course:viewhiddenactivities', $mod->context);
|
||||
if ($accessiblebutdim) {
|
||||
$textclasses .= ' dimmed_text';
|
||||
if ($conditionalhidden) {
|
||||
$textclasses .= ' conditionalhidden';
|
||||
}
|
||||
// Show accessibility note only if user can access the module himself.
|
||||
$accesstext = get_accesshide(get_string('hiddenfromstudents').':'. $mod->modfullname);
|
||||
}
|
||||
} else {
|
||||
$textclasses .= ' dimmed_text';
|
||||
}
|
||||
if ($mod->url) {
|
||||
list($linkclasses, $textclasses) = $this->course_section_cm_classes($mod);
|
||||
if ($mod->url && $mod->uservisible) {
|
||||
if ($content) {
|
||||
// If specified, display extra content after link.
|
||||
$output = html_writer::tag('div', $content, array('class' =>
|
||||
@@ -722,12 +717,24 @@ class core_course_renderer extends plugin_renderer_base {
|
||||
$groupinglabel = $mod->get_grouping_label($textclasses);
|
||||
|
||||
// No link, so display only content.
|
||||
$output = html_writer::tag('div', $accesstext . $content . $groupinglabel,
|
||||
$output = html_writer::tag('div', $content . $groupinglabel,
|
||||
array('class' => 'contentwithoutlink ' . $textclasses));
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays availability info for a course section or course module
|
||||
*
|
||||
* @param string $text
|
||||
* @param string $additionalclasses
|
||||
* @return string
|
||||
*/
|
||||
public function availability_info($text, $additionalclasses = '') {
|
||||
$data = ['text' => $text, 'classes' => $additionalclasses];
|
||||
return $this->render_from_template('core/availability_info', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders HTML to show course module availability information (for someone who isn't allowed
|
||||
* to see the activity itself, or for staff)
|
||||
@@ -738,13 +745,17 @@ class core_course_renderer extends plugin_renderer_base {
|
||||
*/
|
||||
public function course_section_cm_availability(cm_info $mod, $displayoptions = array()) {
|
||||
global $CFG;
|
||||
$output = '';
|
||||
if (!$mod->is_visible_on_course_page()) {
|
||||
return $output;
|
||||
}
|
||||
if (!$mod->uservisible) {
|
||||
// this is a student who is not allowed to see the module but might be allowed
|
||||
// to see availability info (i.e. "Available from ...")
|
||||
if (!empty($mod->availableinfo)) {
|
||||
$formattedinfo = \core_availability\info::format_info(
|
||||
$mod->availableinfo, $mod->get_course());
|
||||
$output = html_writer::tag('div', $formattedinfo, array('class' => 'availabilityinfo'));
|
||||
$output = $this->availability_info($formattedinfo);
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
@@ -752,7 +763,19 @@ class core_course_renderer extends plugin_renderer_base {
|
||||
// information that module is not available to all/some students
|
||||
$modcontext = context_module::instance($mod->id);
|
||||
$canviewhidden = has_capability('moodle/course:viewhiddenactivities', $modcontext);
|
||||
if ($canviewhidden && !$mod->visible) {
|
||||
// This module is hidden but current user has capability to see it.
|
||||
// Do not display the availability info if the whole section is hidden.
|
||||
if ($mod->get_section_info()->visible) {
|
||||
$output .= $this->availability_info(get_string('hiddenfromstudents'), 'ishidden');
|
||||
}
|
||||
} else if ($mod->is_stealth()) {
|
||||
// This module is available but is normally not displayed on the course page
|
||||
// (this user can see it because they can manage it).
|
||||
$output .= $this->availability_info(get_string('hiddenoncoursepage'), 'isstealth');
|
||||
}
|
||||
if ($canviewhidden && !empty($CFG->enableavailability)) {
|
||||
// Display information about conditional availability.
|
||||
// Don't add availability information if user is not editing and activity is hidden.
|
||||
if ($mod->visible || $this->page->user_is_editing()) {
|
||||
$hidinfoclass = '';
|
||||
@@ -764,11 +787,11 @@ class core_course_renderer extends plugin_renderer_base {
|
||||
if ($fullinfo) {
|
||||
$formattedinfo = \core_availability\info::format_info(
|
||||
$fullinfo, $mod->get_course());
|
||||
return html_writer::div($formattedinfo, 'availabilityinfo ' . $hidinfoclass);
|
||||
$output .= $this->availability_info($formattedinfo, $hidinfoclass);
|
||||
}
|
||||
}
|
||||
}
|
||||
return '';
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -823,7 +846,7 @@ class core_course_renderer extends plugin_renderer_base {
|
||||
// 2) The 'availableinfo' is empty, i.e. the activity was
|
||||
// hidden in a way that leaves no info, such as using the
|
||||
// eye icon.
|
||||
if (!$mod->uservisible && empty($mod->availableinfo)) {
|
||||
if (!$mod->is_visible_on_course_page()) {
|
||||
return $output;
|
||||
}
|
||||
|
||||
@@ -890,15 +913,15 @@ class core_course_renderer extends plugin_renderer_base {
|
||||
$output .= html_writer::span($modicons, 'actions');
|
||||
}
|
||||
|
||||
// Show availability info (if module is not available).
|
||||
$output .= $this->course_section_cm_availability($mod, $displayoptions);
|
||||
|
||||
// If there is content AND a link, then display the content here
|
||||
// (AFTER any icons). Otherwise it was displayed before
|
||||
if (!empty($url)) {
|
||||
$output .= $contentpart;
|
||||
}
|
||||
|
||||
// show availability info (if module is not available)
|
||||
$output .= $this->course_section_cm_availability($mod, $displayoptions);
|
||||
|
||||
$output .= html_writer::end_tag('div'); // $indentclasses
|
||||
|
||||
// End of indentation div.
|
||||
|
||||
+29
-116
@@ -33,17 +33,10 @@ require_once($CFG->dirroot.'/course/lib.php');
|
||||
$courseid = required_param('courseId', PARAM_INT);
|
||||
$class = required_param('class', PARAM_ALPHA);
|
||||
$field = optional_param('field', '', PARAM_ALPHA);
|
||||
$instanceid = optional_param('instanceId', 0, PARAM_INT);
|
||||
$sectionid = optional_param('sectionId', 0, PARAM_INT);
|
||||
$beforeid = optional_param('beforeId', 0, PARAM_INT);
|
||||
$value = optional_param('value', 0, PARAM_INT);
|
||||
$column = optional_param('column', 0, PARAM_ALPHA);
|
||||
$id = optional_param('id', 0, PARAM_INT);
|
||||
$summary = optional_param('summary', '', PARAM_RAW);
|
||||
$sequence = optional_param('sequence', '', PARAM_SEQUENCE);
|
||||
$visible = optional_param('visible', 0, PARAM_INT);
|
||||
$pageaction = optional_param('action', '', PARAM_ALPHA); // Used to simulate a DELETE command
|
||||
$title = optional_param('title', '', PARAM_TEXT);
|
||||
|
||||
$PAGE->set_url('/course/rest.php', array('courseId'=>$courseid,'class'=>$class));
|
||||
|
||||
@@ -63,113 +56,33 @@ require_sesskey();
|
||||
|
||||
echo $OUTPUT->header(); // send headers
|
||||
|
||||
// OK, now let's process the parameters and do stuff
|
||||
// MDL-10221 the DELETE method is not allowed on some web servers, so we simulate it with the action URL param
|
||||
$requestmethod = $_SERVER['REQUEST_METHOD'];
|
||||
if ($pageaction == 'DELETE') {
|
||||
$requestmethod = 'DELETE';
|
||||
}
|
||||
|
||||
switch($requestmethod) {
|
||||
case 'POST':
|
||||
|
||||
switch ($class) {
|
||||
case 'section':
|
||||
|
||||
if (!$DB->record_exists('course_sections', array('course'=>$course->id, 'section'=>$id))) {
|
||||
throw new moodle_exception('AJAX commands.php: Bad Section ID '.$id);
|
||||
}
|
||||
|
||||
switch ($field) {
|
||||
case 'visible':
|
||||
require_capability('moodle/course:sectionvisibility', $coursecontext);
|
||||
$resourcestotoggle = set_section_visible($course->id, $id, $value);
|
||||
echo json_encode(array('resourcestotoggle' => $resourcestotoggle));
|
||||
break;
|
||||
|
||||
case 'move':
|
||||
require_capability('moodle/course:movesections', $coursecontext);
|
||||
move_section_to($course, $id, $value);
|
||||
// See if format wants to do something about it
|
||||
$response = course_get_format($course)->ajax_section_move();
|
||||
if ($response !== null) {
|
||||
echo json_encode($response);
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'resource':
|
||||
switch ($field) {
|
||||
case 'visible':
|
||||
require_capability('moodle/course:activityvisibility', $modcontext);
|
||||
set_coursemodule_visible($cm->id, $value);
|
||||
\core\event\course_module_updated::create_from_cm($cm, $modcontext)->trigger();
|
||||
break;
|
||||
|
||||
case 'duplicate':
|
||||
require_capability('moodle/course:manageactivities', $coursecontext);
|
||||
require_capability('moodle/backup:backuptargetimport', $coursecontext);
|
||||
require_capability('moodle/restore:restoretargetimport', $coursecontext);
|
||||
if (!course_allowed_module($course, $cm->modname)) {
|
||||
throw new moodle_exception('No permission to create that activity');
|
||||
}
|
||||
$sr = optional_param('sr', null, PARAM_INT);
|
||||
$result = mod_duplicate_activity($course, $cm, $sr);
|
||||
echo json_encode($result);
|
||||
break;
|
||||
|
||||
case 'groupmode':
|
||||
require_capability('moodle/course:manageactivities', $modcontext);
|
||||
set_coursemodule_groupmode($cm->id, $value);
|
||||
\core\event\course_module_updated::create_from_cm($cm, $modcontext)->trigger();
|
||||
break;
|
||||
|
||||
case 'indent':
|
||||
require_capability('moodle/course:manageactivities', $modcontext);
|
||||
$cm->indent = $value;
|
||||
if ($cm->indent >= 0) {
|
||||
$DB->update_record('course_modules', $cm);
|
||||
rebuild_course_cache($cm->course);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'move':
|
||||
require_capability('moodle/course:manageactivities', $modcontext);
|
||||
if (!$section = $DB->get_record('course_sections', array('course'=>$course->id, 'section'=>$sectionid))) {
|
||||
throw new moodle_exception('AJAX commands.php: Bad section ID '.$sectionid);
|
||||
}
|
||||
|
||||
if ($beforeid > 0){
|
||||
$beforemod = get_coursemodule_from_id('', $beforeid, $course->id);
|
||||
$beforemod = $DB->get_record('course_modules', array('id'=>$beforeid));
|
||||
} else {
|
||||
$beforemod = NULL;
|
||||
}
|
||||
|
||||
$isvisible = moveto_module($cm, $section, $beforemod);
|
||||
echo json_encode(array('visible' => (bool) $isvisible));
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'course':
|
||||
switch($field) {
|
||||
case 'marker':
|
||||
require_capability('moodle/course:setcurrentsection', $coursecontext);
|
||||
course_set_marker($course->id, $value);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'DELETE':
|
||||
switch ($class) {
|
||||
case 'resource':
|
||||
require_capability('moodle/course:manageactivities', $modcontext);
|
||||
course_delete_module($cm->id, true);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
if ($class === 'section' && $field === 'move') {
|
||||
if (!$DB->record_exists('course_sections', array('course' => $course->id, 'section' => $id))) {
|
||||
throw new moodle_exception('AJAX commands.php: Bad Section ID ' . $id);
|
||||
}
|
||||
|
||||
require_capability('moodle/course:movesections', $coursecontext);
|
||||
move_section_to($course, $id, $value);
|
||||
// See if format wants to do something about it.
|
||||
$response = course_get_format($course)->ajax_section_move();
|
||||
if ($response !== null) {
|
||||
echo json_encode($response);
|
||||
}
|
||||
|
||||
} else if ($class === 'resource' && $field === 'move') {
|
||||
|
||||
require_capability('moodle/course:manageactivities', $modcontext);
|
||||
if (!$section = $DB->get_record('course_sections', array('course' => $course->id, 'section' => $sectionid))) {
|
||||
throw new moodle_exception('AJAX commands.php: Bad section ID '.$sectionid);
|
||||
}
|
||||
|
||||
if ($beforeid > 0) {
|
||||
$beforemod = get_coursemodule_from_id('', $beforeid, $course->id);
|
||||
$beforemod = $DB->get_record('course_modules', array('id' => $beforeid));
|
||||
} else {
|
||||
$beforemod = null;
|
||||
}
|
||||
|
||||
$isvisible = moveto_module($cm, $section, $beforemod);
|
||||
echo json_encode(array('visible' => (bool) $isvisible));
|
||||
}
|
||||
|
||||
@@ -25,35 +25,62 @@ Feature: Toggle activities visibility from the course page
|
||||
| Description | Test forum description |
|
||||
| Visible | Show |
|
||||
When I open "Test forum name" actions menu
|
||||
Then "Test forum name" actions menu should not have "Show" item
|
||||
And "Test forum name" actions menu should not have "Make available" item
|
||||
And "Test forum name" actions menu should not have "Make unavailable" item
|
||||
And I click on "Hide" "link" in the "Test forum name" activity
|
||||
Then "Test forum name" activity should be hidden
|
||||
And "Test forum name" activity should be hidden
|
||||
And I open "Test forum name" actions menu
|
||||
And "Test forum name" actions menu should not have "Hide" item
|
||||
# Stealth behaviour is not available by default:
|
||||
And "Test forum name" actions menu should not have "Make available" item
|
||||
And "Test forum name" actions menu should not have "Make unavailable" item
|
||||
And I click on "Show" "link" in the "Test forum name" activity
|
||||
And "Test forum name" activity should be visible
|
||||
And I open "Test forum name" actions menu
|
||||
And "Test forum name" actions menu should not have "Show" item
|
||||
And "Test forum name" actions menu should not have "Make available" item
|
||||
And "Test forum name" actions menu should not have "Make unavailable" item
|
||||
And I click on "Hide" "link" in the "Test forum name" activity
|
||||
And "Test forum name" activity should be hidden
|
||||
And I reload the page
|
||||
And "Test forum name" activity should be hidden
|
||||
# Make sure that "Visible" dropdown in the edit menu has two options: Show/Hide.
|
||||
And I open "Test forum name" actions menu
|
||||
And I click on "Edit settings" "link" in the "Test forum name" activity
|
||||
And I expand all fieldsets
|
||||
And the "Visible" select box should not contain "Hidden from students"
|
||||
And the "Visible" select box should not contain "Available but not displayed on course page"
|
||||
And the "Visible" select box should contain "Show"
|
||||
And the field "Visible" matches value "Hide"
|
||||
And I press "Save and return to course"
|
||||
And "Test forum name" activity should be hidden
|
||||
And I turn editing mode off
|
||||
And "Test forum name" activity should be hidden
|
||||
And I log out
|
||||
# Student should not see this activity.
|
||||
And I log in as "student1"
|
||||
And I follow "Course 1"
|
||||
And "Test forum name" activity should be hidden
|
||||
And I should not see "Test forum name"
|
||||
And I log out
|
||||
|
||||
@javascript
|
||||
Scenario: Activities can be shown and hidden inside a hidden section
|
||||
Scenario: Activities can be made available and unavailable inside a hidden section
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Teacher | 1 | teacher1@example.com |
|
||||
| student1 | Student | 1 | student1@example.com |
|
||||
And the following "courses" exist:
|
||||
| fullname | shortname | format | numsections |
|
||||
| Course 1 | C1 | topics | 2 |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
| student1 | C1 | student |
|
||||
And I log in as "teacher1"
|
||||
And I follow "Course 1"
|
||||
And I turn editing mode on
|
||||
And I add the "Recent activity" block
|
||||
And I add a "Forum" to section "2" and I fill the form with:
|
||||
| Forum name | Test forum name |
|
||||
| Description | Test forum description |
|
||||
@@ -61,11 +88,39 @@ Feature: Toggle activities visibility from the course page
|
||||
When I hide section "2"
|
||||
Then "Test forum name" activity should be hidden
|
||||
And I open "Test forum name" actions menu
|
||||
And I click on "Show" "link" in the "Test forum name" activity
|
||||
And "Test forum name" activity should be visible
|
||||
And "Test forum name" actions menu should not have "Show" item
|
||||
And "Test forum name" actions menu should not have "Hide" item
|
||||
And "Test forum name" actions menu should not have "Make unavailable" item
|
||||
And I click on "Make available" "link" in the "Test forum name" activity
|
||||
And "Test forum name" activity should be available but hidden from course page
|
||||
And I open "Test forum name" actions menu
|
||||
And I click on "Hide" "link" in the "Test forum name" activity
|
||||
And "Test forum name" actions menu should not have "Show" item
|
||||
And "Test forum name" actions menu should not have "Hide" item
|
||||
And "Test forum name" actions menu should not have "Make available" item
|
||||
And I click on "Make unavailable" "link" in the "Test forum name" activity
|
||||
And "Test forum name" activity should be hidden
|
||||
# Make sure that "Visible" dropdown in the edit menu has three options.
|
||||
And I open "Test forum name" actions menu
|
||||
And I click on "Edit settings" "link" in the "Test forum name" activity
|
||||
And I expand all fieldsets
|
||||
And the "Visible" select box should contain "Hidden from students"
|
||||
And the "Visible" select box should contain "Available but not displayed on course page"
|
||||
And the "Visible" select box should not contain "Hide"
|
||||
And the "Visible" select box should not contain "Show"
|
||||
And I set the field "Visible" to "Available but not displayed on course page"
|
||||
And I press "Save and return to course"
|
||||
And "Test forum name" activity should be available but hidden from course page
|
||||
And I turn editing mode off
|
||||
And "Test forum name" activity should be available but hidden from course page
|
||||
And I log out
|
||||
# Student will not see the module on the course page but can access it from other reports and blocks:
|
||||
And I log in as "student1"
|
||||
And I follow "Course 1"
|
||||
And "Test forum name" activity should be hidden
|
||||
And I click on "Test forum name" "link" in the "Recent activity" "block"
|
||||
And I should see "Test forum name"
|
||||
And I should see "(There are no discussion topics yet in this forum)"
|
||||
And I log out
|
||||
|
||||
@javascript
|
||||
Scenario: Activities can be shown and hidden inside an orphaned section
|
||||
@@ -88,8 +143,74 @@ Feature: Toggle activities visibility from the course page
|
||||
When I click on ".reduce-sections" "css_element"
|
||||
Then "Test forum name" activity should be visible
|
||||
And I open "Test forum name" actions menu
|
||||
And "Test forum name" actions menu should not have "Show" item
|
||||
And "Test forum name" actions menu should not have "Make available" item
|
||||
And "Test forum name" actions menu should not have "Make unavailable" item
|
||||
And I click on "Hide" "link" in the "Test forum name" activity
|
||||
And "Test forum name" activity should be hidden
|
||||
And I open "Test forum name" actions menu
|
||||
And "Test forum name" actions menu should not have "Hide" item
|
||||
And "Test forum name" actions menu should not have "Make available" item
|
||||
And "Test forum name" actions menu should not have "Make unavailable" item
|
||||
And I click on "Show" "link" in the "Test forum name" activity
|
||||
And "Test forum name" activity should be visible
|
||||
|
||||
@javascript
|
||||
Scenario: Activities can be made available but not visible on a course page
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Teacher | 1 | teacher1@example.com |
|
||||
| student1 | Student | 1 | student1@example.com |
|
||||
And the following "courses" exist:
|
||||
| fullname | shortname | format | numsections |
|
||||
| Course 1 | C1 | topics | 2 |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
| student1 | C1 | student |
|
||||
And I log in as "admin"
|
||||
And I set the following administration settings values:
|
||||
| allowstealth | 1 |
|
||||
And I log out
|
||||
And I log in as "teacher1"
|
||||
And I follow "Course 1"
|
||||
And I turn editing mode on
|
||||
And I add the "Recent activity" block
|
||||
And I add a "Assignment" to section "2" and I fill the form with:
|
||||
| Assignment name | Test assignment name |
|
||||
| Description | Test assignment description |
|
||||
| Visible | Show |
|
||||
When I open "Test assignment name" actions menu
|
||||
Then "Test assignment name" actions menu should not have "Show" item
|
||||
And "Test assignment name" actions menu should have "Hide" item
|
||||
And "Test assignment name" actions menu should not have "Make available" item
|
||||
And "Test assignment name" actions menu should not have "Make unavailable" item
|
||||
And I click on "Hide" "link" in the "Test assignment name" activity
|
||||
And "Test assignment name" activity should be hidden
|
||||
And I open "Test assignment name" actions menu
|
||||
And "Test assignment name" actions menu should have "Show" item
|
||||
And "Test assignment name" actions menu should not have "Hide" item
|
||||
And "Test assignment name" actions menu should not have "Make unavailable" item
|
||||
And I click on "Make available" "link" in the "Test assignment name" activity
|
||||
And "Test assignment name" activity should be available but hidden from course page
|
||||
# Make sure that "Visible" dropdown in the edit menu has three options.
|
||||
And I open "Test assignment name" actions menu
|
||||
And I click on "Edit settings" "link" in the "Test assignment name" activity
|
||||
And I expand all fieldsets
|
||||
And the "Visible" select box should contain "Show"
|
||||
And the "Visible" select box should contain "Hidden from students"
|
||||
And the "Visible" select box should not contain "Hide"
|
||||
And the field "Visible" matches value "Available but not displayed on course page"
|
||||
And I press "Save and return to course"
|
||||
And "Test assignment name" activity should be available but hidden from course page
|
||||
And I turn editing mode off
|
||||
And "Test assignment name" activity should be available but hidden from course page
|
||||
And I log out
|
||||
# Student will not see the module on the course page but can access it from other reports and blocks:
|
||||
And I log in as "student1"
|
||||
And I follow "Course 1"
|
||||
And "Test assignment name" activity should be hidden
|
||||
And I click on "Test assignment name" "link" in the "Recent activity" "block"
|
||||
And I should see "Test assignment name"
|
||||
And I should see "Submission status"
|
||||
And I log out
|
||||
|
||||
@@ -623,9 +623,67 @@ class behat_course extends behat_base {
|
||||
// All ok.
|
||||
}
|
||||
|
||||
// The 'Hide' button should be available.
|
||||
$nohideexception = new ExpectationException('"' . $activityname . '" don\'t have a "' . get_string('hide') . '" icon', $this->getSession());
|
||||
$this->find('named_partial', array('link', get_string('hide')), $nohideexception, $activitynode);
|
||||
// Additional check if this is a teacher in editing mode.
|
||||
if ($this->is_editing_on()) {
|
||||
// The 'Hide' button should be available.
|
||||
$nohideexception = new ExpectationException('"' . $activityname . '" doesn\'t have a "' .
|
||||
get_string('hide') . '" icon', $this->getSession());
|
||||
$this->find('named_partial', array('link', get_string('hide')), $nohideexception, $activitynode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the specified activity is visible. You need to be in the course page.
|
||||
* It can be used being logged as a student and as a teacher on editing mode.
|
||||
*
|
||||
* @Then /^"(?P<activity_or_resource_string>(?:[^"]|\\")*)" activity should be available but hidden from course page$/
|
||||
* @param string $activityname
|
||||
* @throws ExpectationException
|
||||
*/
|
||||
public function activity_should_be_available_but_hidden_from_course_page($activityname) {
|
||||
|
||||
if ($this->is_course_editor()) {
|
||||
|
||||
// The activity must exists and be visible.
|
||||
$activitynode = $this->get_activity_node($activityname);
|
||||
|
||||
// The activity should not be dimmed.
|
||||
try {
|
||||
$xpath = "/descendant-or-self::a[contains(concat(' ', normalize-space(@class), ' '), ' dimmed ')] | " .
|
||||
"/descendant-or-self::div[contains(concat(' ', normalize-space(@class), ' '), ' dimmed_text ')]";
|
||||
$this->find('xpath', $xpath, false, $activitynode);
|
||||
throw new ExpectationException('"' . $activityname . '" is hidden', $this->getSession());
|
||||
} catch (ElementNotFoundException $e) {
|
||||
// All ok.
|
||||
}
|
||||
|
||||
// Should has "stealth" class.
|
||||
$exception = new ExpectationException('"' . $activityname . '" does not have CSS class "stealth"', $this->getSession());
|
||||
$xpath = "/descendant-or-self::a[contains(concat(' ', normalize-space(@class), ' '), ' stealth ')]";
|
||||
$this->find('xpath', $xpath, $exception, $activitynode);
|
||||
|
||||
// Additional check if this is a teacher in editing mode.
|
||||
if ($this->is_editing_on()) {
|
||||
// Also has either 'Hide' or 'Make unavailable' edit control.
|
||||
$nohideexception = new ExpectationException('"' . $activityname . '" has neither "' . get_string('hide') .
|
||||
'" nor "' . get_string('makeunavailable') . '" icons', $this->getSession());
|
||||
try {
|
||||
$this->find('named_partial', array('link', get_string('hide')), false, $activitynode);
|
||||
} catch (ElementNotFoundException $e) {
|
||||
$this->find('named_partial', array('link', get_string('makeunavailable')), $nohideexception, $activitynode);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// Student should not see the activity at all.
|
||||
try {
|
||||
$this->get_activity_node($activityname);
|
||||
throw new ExpectationException('The "' . $activityname . '" should not appear', $this->getSession());
|
||||
} catch (ElementNotFoundException $e) {
|
||||
// This is good, the activity should not be there.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -649,15 +707,23 @@ class behat_course extends behat_base {
|
||||
"/descendant-or-self::div[contains(concat(' ', normalize-space(@class), ' '), ' dimmed_text ')]";
|
||||
$this->find('xpath', $xpath, $exception, $activitynode);
|
||||
|
||||
// Also 'Show' icon.
|
||||
$noshowexception = new ExpectationException('"' . $activityname . '" don\'t have a "' . get_string('show') . '" icon', $this->getSession());
|
||||
$this->find('named_partial', array('link', get_string('show')), $noshowexception, $activitynode);
|
||||
// Additional check if this is a teacher in editing mode.
|
||||
if ($this->is_editing_on()) {
|
||||
// Also has either 'Show' or 'Make available' edit control.
|
||||
$noshowexception = new ExpectationException('"' . $activityname . '" has neither "' . get_string('show') .
|
||||
'" nor "' . get_string('makeavailable') . '" icons', $this->getSession());
|
||||
try {
|
||||
$this->find('named_partial', array('link', get_string('show')), false, $activitynode);
|
||||
} catch (ElementNotFoundException $e) {
|
||||
$this->find('named_partial', array('link', get_string('makeavailable')), $noshowexception, $activitynode);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// It should not exist at all.
|
||||
try {
|
||||
$this->find_link($activityname);
|
||||
$this->get_activity_node($activityname);
|
||||
throw new ExpectationException('The "' . $activityname . '" should not appear', $this->getSession());
|
||||
} catch (ElementNotFoundException $e) {
|
||||
// This is good, the activity should not be there.
|
||||
@@ -666,6 +732,26 @@ class behat_course extends behat_base {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the specified activity is dimmed. You need to be in the course page.
|
||||
*
|
||||
* @Then /^"(?P<activity_or_resource_string>(?:[^"]|\\")*)" activity should be dimmed$/
|
||||
* @param string $activityname
|
||||
* @throws ExpectationException
|
||||
*/
|
||||
public function activity_should_be_dimmed($activityname) {
|
||||
|
||||
// The activity should exist.
|
||||
$activitynode = $this->get_activity_node($activityname);
|
||||
|
||||
// Should be hidden.
|
||||
$exception = new ExpectationException('"' . $activityname . '" is not dimmed', $this->getSession());
|
||||
$xpath = "/descendant-or-self::a[contains(concat(' ', normalize-space(@class), ' '), ' dimmed ')] | ".
|
||||
"/descendant-or-self::div[contains(concat(' ', normalize-space(@class), ' '), ' dimmed_text ')]";
|
||||
$this->find('xpath', $xpath, $exception, $activitynode);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the specified activity to the first slot of a section. This step is experimental when using it in Javascript tests. Editing mode should be on.
|
||||
*
|
||||
@@ -803,6 +889,42 @@ class behat_course extends behat_base {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the specified activity's action menu contains an item.
|
||||
*
|
||||
* @Then /^"(?P<activity_name_string>(?:[^"]|\\")*)" actions menu should have "(?P<menu_item_string>(?:[^"]|\\")*)" item$/
|
||||
* @throws DriverException The step is not available when Javascript is disabled
|
||||
* @param string $activityname
|
||||
* @param string $menuitem
|
||||
*/
|
||||
public function actions_menu_should_have_item($activityname, $menuitem) {
|
||||
$activitynode = $this->get_activity_node($activityname);
|
||||
|
||||
$notfoundexception = new ExpectationException('"' . $activityname . '" doesn\'t have a "' .
|
||||
$menuitem . '" item', $this->getSession());
|
||||
$this->find('named_partial', array('link', $menuitem), $notfoundexception, $activitynode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the specified activity's action menu does not contains an item.
|
||||
*
|
||||
* @Then /^"(?P<activity_name_string>(?:[^"]|\\")*)" actions menu should not have "(?P<menu_item_string>(?:[^"]|\\")*)" item$/
|
||||
* @throws DriverException The step is not available when Javascript is disabled
|
||||
* @param string $activityname
|
||||
* @param string $menuitem
|
||||
*/
|
||||
public function actions_menu_should_not_have_item($activityname, $menuitem) {
|
||||
$activitynode = $this->get_activity_node($activityname);
|
||||
|
||||
try {
|
||||
$this->find('named_partial', array('link', $menuitem), false, $activitynode);
|
||||
throw new ExpectationException('"' . $activityname . '" has a "' . $menuitem .
|
||||
'" item when it should not', $this->getSession());
|
||||
} catch (ElementNotFoundException $e) {
|
||||
// This is good, the menu item should not be there.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indents to the right the activity or resource specified by it's name. Editing mode should be on.
|
||||
*
|
||||
@@ -1159,6 +1281,15 @@ class behat_course extends behat_base {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the user can edit the course contents and the editing mode is on.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function is_editing_on() {
|
||||
return $this->getSession()->getPage()->findButton(get_string('turneditingoff')) ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the id of the category with the given idnumber.
|
||||
*
|
||||
|
||||
@@ -201,6 +201,7 @@ class core_course_courselib_testcase extends advanced_testcase {
|
||||
$moduleinfo->course = $course->id;
|
||||
$moduleinfo->groupingid = $grouping->id;
|
||||
$moduleinfo->visible = true;
|
||||
$moduleinfo->visibleoncoursepage = true;
|
||||
|
||||
// Sometimes optional generic values for some modules.
|
||||
$moduleinfo->name = 'My test module';
|
||||
@@ -436,6 +437,7 @@ class core_course_courselib_testcase extends advanced_testcase {
|
||||
$moduleinfo->course = $course->id;
|
||||
$moduleinfo->groupingid = $grouping->id;
|
||||
$moduleinfo->visible = true;
|
||||
$moduleinfo->visibleoncoursepage = true;
|
||||
|
||||
// Sometimes optional generic values for some modules.
|
||||
$moduleinfo->name = 'My test module';
|
||||
|
||||
@@ -1680,7 +1680,7 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
|
||||
|
||||
$this->assertCount(0, $result['warnings']);
|
||||
// Test we retrieve all the fields.
|
||||
$this->assertCount(27, $result['cm']);
|
||||
$this->assertCount(28, $result['cm']);
|
||||
$this->assertEquals($record['name'], $result['cm']['name']);
|
||||
$this->assertEquals($options['idnumber'], $result['cm']['idnumber']);
|
||||
$this->assertEquals(100, $result['cm']['grade']);
|
||||
@@ -1747,7 +1747,7 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
|
||||
|
||||
$this->assertCount(0, $result['warnings']);
|
||||
// Test we retrieve all the fields.
|
||||
$this->assertCount(22, $result['cm']);
|
||||
$this->assertCount(23, $result['cm']);
|
||||
$this->assertEquals($record['name'], $result['cm']['name']);
|
||||
$this->assertEquals($options['idnumber'], $result['cm']['idnumber']);
|
||||
|
||||
|
||||
@@ -99,6 +99,7 @@ class core_course_modlib_testcase extends advanced_testcase {
|
||||
$expecteddata->coursemodule = $assigncm->id;
|
||||
$expecteddata->section = $cw->section;
|
||||
$expecteddata->visible = $assigncm->visible;
|
||||
$expecteddata->visibleoncoursepage = $assigncm->visibleoncoursepage;
|
||||
$expecteddata->cmidnumber = $assigncm->idnumber;
|
||||
$expecteddata->groupmode = groups_get_activity_groupmode($cm);
|
||||
$expecteddata->groupingid = $assigncm->groupingid;
|
||||
|
||||
@@ -423,7 +423,8 @@ Y.extend(DRAGRESOURCE, M.core.dragdrop, {
|
||||
// Replace move icons
|
||||
var move = resourcesnode.one('a.' + CSS.EDITINGMOVE);
|
||||
if (move) {
|
||||
move.replace(this.resourcedraghandle.cloneNode(true));
|
||||
var sr = move.getData('sr');
|
||||
move.replace(this.resourcedraghandle.cloneNode(true).setAttribute('data-sectionreturn', sr));
|
||||
}
|
||||
}, this);
|
||||
},
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -419,7 +419,8 @@ Y.extend(DRAGRESOURCE, M.core.dragdrop, {
|
||||
// Replace move icons
|
||||
var move = resourcesnode.one('a.' + CSS.EDITINGMOVE);
|
||||
if (move) {
|
||||
move.replace(this.resourcedraghandle.cloneNode(true));
|
||||
var sr = move.getData('sr');
|
||||
move.replace(this.resourcedraghandle.cloneNode(true).setAttribute('data-sectionreturn', sr));
|
||||
}
|
||||
}, this);
|
||||
},
|
||||
|
||||
@@ -1,947 +0,0 @@
|
||||
YUI.add('moodle-course-toolboxes', function (Y, NAME) {
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
/**
|
||||
* Resource and activity toolbox class.
|
||||
*
|
||||
* This class is responsible for managing AJAX interactions with activities and resources
|
||||
* when viewing a course in editing mode.
|
||||
*
|
||||
* @module moodle-course-toolboxes
|
||||
* @namespace M.course.toolboxes
|
||||
*/
|
||||
|
||||
// The CSS classes we use.
|
||||
var CSS = {
|
||||
ACTIVITYINSTANCE: 'activityinstance',
|
||||
AVAILABILITYINFODIV: 'div.availabilityinfo',
|
||||
CONTENTWITHOUTLINK: 'contentwithoutlink',
|
||||
CONDITIONALHIDDEN: 'conditionalhidden',
|
||||
DIMCLASS: 'dimmed',
|
||||
DIMMEDTEXT: 'dimmed_text',
|
||||
EDITINSTRUCTIONS: 'editinstructions',
|
||||
HIDE: 'hide',
|
||||
MODINDENTCOUNT: 'mod-indent-',
|
||||
MODINDENTHUGE: 'mod-indent-huge',
|
||||
MODULEIDPREFIX: 'module-',
|
||||
SECTIONHIDDENCLASS: 'hidden',
|
||||
SECTIONIDPREFIX: 'section-',
|
||||
SHOW: 'editing_show'
|
||||
},
|
||||
// The CSS selectors we use.
|
||||
SELECTOR = {
|
||||
ACTIONAREA: '.actions',
|
||||
ACTIONLINKTEXT: '.actionlinktext',
|
||||
ACTIVITYACTION: 'a.cm-edit-action[data-action]',
|
||||
ACTIVITYICON: 'img.activityicon',
|
||||
ACTIVITYINSTANCE: '.' + CSS.ACTIVITYINSTANCE,
|
||||
ACTIVITYLINK: '.' + CSS.ACTIVITYINSTANCE + ' > a, .' + CSS.ACTIVITYINSTANCE +
|
||||
' > span[data-inplaceeditable] > a:not([data-inplaceeditablelink])',
|
||||
ACTIVITYLI: 'li.activity',
|
||||
COMMANDSPAN: '.commands',
|
||||
CONTENTAFTERLINK: 'div.contentafterlink',
|
||||
CONTENTWITHOUTLINK: 'div.contentwithoutlink',
|
||||
GROUPINGLABEL: '.' + CSS.ACTIVITYINSTANCE + ' .groupinglabel',
|
||||
HIDE: 'a.editing_hide',
|
||||
HIGHLIGHT: 'a.editing_highlight',
|
||||
INSTANCENAME: 'span.instancename',
|
||||
MODINDENTDIV: '.mod-indent',
|
||||
MODINDENTOUTER: '.mod-indent-outer',
|
||||
PAGECONTENT: 'body',
|
||||
SECTIONLI: 'li.section',
|
||||
SHOW: 'a.' + CSS.SHOW,
|
||||
SHOWHIDE: 'a.editing_showhide'
|
||||
},
|
||||
INDENTLIMITS = {
|
||||
MIN: 0,
|
||||
MAX: 16
|
||||
},
|
||||
BODY = Y.one(document.body);
|
||||
|
||||
// Setup the basic namespace.
|
||||
M.course = M.course || {};
|
||||
|
||||
/**
|
||||
* The toolbox class is a generic class which should never be directly
|
||||
* instantiated. Please extend it instead.
|
||||
*
|
||||
* @class toolbox
|
||||
* @constructor
|
||||
* @protected
|
||||
* @extends Base
|
||||
*/
|
||||
var TOOLBOX = function() {
|
||||
TOOLBOX.superclass.constructor.apply(this, arguments);
|
||||
};
|
||||
|
||||
Y.extend(TOOLBOX, Y.Base, {
|
||||
/**
|
||||
* Send a request using the REST API
|
||||
*
|
||||
* @method send_request
|
||||
* @param {Object} data The data to submit with the AJAX request
|
||||
* @param {Node} [statusspinner] A statusspinner which may contain a section loader
|
||||
* @param {Function} success_callback The callback to use on success
|
||||
* @param {Object} [optionalconfig] Any additional configuration to submit
|
||||
* @chainable
|
||||
*/
|
||||
send_request: function(data, statusspinner, success_callback, optionalconfig) {
|
||||
// Default data structure
|
||||
if (!data) {
|
||||
data = {};
|
||||
}
|
||||
// Handle any variables which we must pass back through to
|
||||
var pageparams = this.get('config').pageparams,
|
||||
varname;
|
||||
for (varname in pageparams) {
|
||||
data[varname] = pageparams[varname];
|
||||
}
|
||||
|
||||
data.sesskey = M.cfg.sesskey;
|
||||
data.courseId = this.get('courseid');
|
||||
|
||||
var uri = M.cfg.wwwroot + this.get('ajaxurl');
|
||||
|
||||
// Define the configuration to send with the request
|
||||
var responsetext = [];
|
||||
var config = {
|
||||
method: 'POST',
|
||||
data: data,
|
||||
on: {
|
||||
success: function(tid, response) {
|
||||
try {
|
||||
responsetext = Y.JSON.parse(response.responseText);
|
||||
if (responsetext.error) {
|
||||
new M.core.ajaxException(responsetext);
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignore.
|
||||
}
|
||||
|
||||
// Run the callback if we have one.
|
||||
if (success_callback) {
|
||||
Y.bind(success_callback, this, responsetext)();
|
||||
}
|
||||
|
||||
if (statusspinner) {
|
||||
window.setTimeout(function() {
|
||||
statusspinner.hide();
|
||||
}, 400);
|
||||
}
|
||||
},
|
||||
failure: function(tid, response) {
|
||||
if (statusspinner) {
|
||||
statusspinner.hide();
|
||||
}
|
||||
new M.core.ajaxException(response);
|
||||
}
|
||||
},
|
||||
context: this
|
||||
};
|
||||
|
||||
// Apply optional config
|
||||
if (optionalconfig) {
|
||||
for (varname in optionalconfig) {
|
||||
config[varname] = optionalconfig[varname];
|
||||
}
|
||||
}
|
||||
|
||||
if (statusspinner) {
|
||||
statusspinner.show();
|
||||
}
|
||||
|
||||
// Send the request
|
||||
Y.io(uri, config);
|
||||
return this;
|
||||
}
|
||||
},
|
||||
{
|
||||
NAME: 'course-toolbox',
|
||||
ATTRS: {
|
||||
/**
|
||||
* The ID of the Moodle Course being edited.
|
||||
*
|
||||
* @attribute courseid
|
||||
* @default 0
|
||||
* @type Number
|
||||
*/
|
||||
courseid: {
|
||||
'value': 0
|
||||
},
|
||||
|
||||
/**
|
||||
* The Moodle course format.
|
||||
*
|
||||
* @attribute format
|
||||
* @default 'topics'
|
||||
* @type String
|
||||
*/
|
||||
format: {
|
||||
'value': 'topics'
|
||||
},
|
||||
/**
|
||||
* The URL to use when submitting requests.
|
||||
* @attribute ajaxurl
|
||||
* @default null
|
||||
* @type String
|
||||
*/
|
||||
ajaxurl: {
|
||||
'value': null
|
||||
},
|
||||
/**
|
||||
* Any additional configuration passed when creating the instance.
|
||||
*
|
||||
* @attribute config
|
||||
* @default {}
|
||||
* @type Object
|
||||
*/
|
||||
config: {
|
||||
'value': {}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
/* global TOOLBOX, BODY, SELECTOR, INDENTLIMITS */
|
||||
|
||||
/**
|
||||
* Resource and activity toolbox class.
|
||||
*
|
||||
* This class is responsible for managing AJAX interactions with activities and resources
|
||||
* when viewing a course in editing mode.
|
||||
*
|
||||
* @module moodle-course-toolboxes
|
||||
* @namespace M.course.toolboxes
|
||||
*/
|
||||
|
||||
/**
|
||||
* Resource and activity toolbox class.
|
||||
*
|
||||
* This is a class extending TOOLBOX containing code specific to resources
|
||||
*
|
||||
* This class is responsible for managing AJAX interactions with activities and resources
|
||||
* when viewing a course in editing mode.
|
||||
*
|
||||
* @class resources
|
||||
* @constructor
|
||||
* @extends M.course.toolboxes.toolbox
|
||||
*/
|
||||
var RESOURCETOOLBOX = function() {
|
||||
RESOURCETOOLBOX.superclass.constructor.apply(this, arguments);
|
||||
};
|
||||
|
||||
Y.extend(RESOURCETOOLBOX, TOOLBOX, {
|
||||
/**
|
||||
* No groups are being used.
|
||||
*
|
||||
* @property GROUPS_NONE
|
||||
* @protected
|
||||
* @type Number
|
||||
*/
|
||||
GROUPS_NONE: 0,
|
||||
|
||||
/**
|
||||
* Separate groups are being used.
|
||||
*
|
||||
* @property GROUPS_SEPARATE
|
||||
* @protected
|
||||
* @type Number
|
||||
*/
|
||||
GROUPS_SEPARATE: 1,
|
||||
|
||||
/**
|
||||
* Visible groups are being used.
|
||||
*
|
||||
* @property GROUPS_VISIBLE
|
||||
* @protected
|
||||
* @type Number
|
||||
*/
|
||||
GROUPS_VISIBLE: 2,
|
||||
|
||||
/**
|
||||
* Initialize the resource toolbox
|
||||
*
|
||||
* For each activity the commands are updated and a reference to the activity is attached.
|
||||
* This way it doesn't matter where the commands are going to called from they have a reference to the
|
||||
* activity that they relate to.
|
||||
* This is essential as some of the actions are displayed in an actionmenu which removes them from the
|
||||
* page flow.
|
||||
*
|
||||
* This function also creates a single event delegate to manage all AJAX actions for all activities on
|
||||
* the page.
|
||||
*
|
||||
* @method initializer
|
||||
* @protected
|
||||
*/
|
||||
initializer: function() {
|
||||
M.course.coursebase.register_module(this);
|
||||
BODY.delegate('key', this.handle_data_action, 'down:enter', SELECTOR.ACTIVITYACTION, this);
|
||||
Y.delegate('click', this.handle_data_action, BODY, SELECTOR.ACTIVITYACTION, this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles the delegation event. When this is fired someone has triggered an action.
|
||||
*
|
||||
* Note not all actions will result in an AJAX enhancement.
|
||||
*
|
||||
* @protected
|
||||
* @method handle_data_action
|
||||
* @param {EventFacade} ev The event that was triggered.
|
||||
* @return {boolean}
|
||||
*/
|
||||
handle_data_action: function(ev) {
|
||||
// We need to get the anchor element that triggered this event.
|
||||
var node = ev.target;
|
||||
if (!node.test('a')) {
|
||||
node = node.ancestor(SELECTOR.ACTIVITYACTION);
|
||||
}
|
||||
|
||||
// From the anchor we can get both the activity (added during initialisation) and the action being
|
||||
// performed (added by the UI as a data attribute).
|
||||
var action = node.getData('action'),
|
||||
activity = node.ancestor(SELECTOR.ACTIVITYLI);
|
||||
|
||||
if (!node.test('a') || !action || !activity) {
|
||||
// It wasn't a valid action node.
|
||||
return;
|
||||
}
|
||||
|
||||
// Switch based upon the action and do the desired thing.
|
||||
switch (action) {
|
||||
case 'moveleft':
|
||||
case 'moveright':
|
||||
// The user changing the indent of the activity.
|
||||
this.change_indent(ev, node, activity, action);
|
||||
break;
|
||||
case 'delete':
|
||||
// The user is deleting the activity.
|
||||
this.delete_with_confirmation(ev, node, activity, action);
|
||||
break;
|
||||
case 'duplicate':
|
||||
// The user is duplicating the activity.
|
||||
this.duplicate(ev, node, activity, action);
|
||||
break;
|
||||
case 'hide':
|
||||
case 'show':
|
||||
// The user is changing the visibility of the activity.
|
||||
this.change_visibility(ev, node, activity, action);
|
||||
break;
|
||||
case 'groupsseparate':
|
||||
case 'groupsvisible':
|
||||
case 'groupsnone':
|
||||
// The user is changing the group mode.
|
||||
this.change_groupmode(ev, node, activity, action);
|
||||
break;
|
||||
case 'move':
|
||||
case 'update':
|
||||
case 'assignroles':
|
||||
break;
|
||||
default:
|
||||
// Nothing to do here!
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a loading icon to the specified activity.
|
||||
*
|
||||
* The icon is added within the action area.
|
||||
*
|
||||
* @method add_spinner
|
||||
* @param {Node} activity The activity to add a loading icon to
|
||||
* @return {Node|null} The newly created icon, or null if the action area was not found.
|
||||
*/
|
||||
add_spinner: function(activity) {
|
||||
var actionarea = activity.one(SELECTOR.ACTIONAREA);
|
||||
if (actionarea) {
|
||||
return M.util.add_spinner(Y, actionarea);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Change the indent of the activity or resource.
|
||||
*
|
||||
* @method change_indent
|
||||
* @protected
|
||||
* @param {EventFacade} ev The event that was fired.
|
||||
* @param {Node} button The button that triggered this action.
|
||||
* @param {Node} activity The activity node that this action will be performed on.
|
||||
* @param {String} action The action that has been requested. Will be 'moveleft' or 'moveright'.
|
||||
*/
|
||||
change_indent: function(ev, button, activity, action) {
|
||||
// Prevent the default button action
|
||||
ev.preventDefault();
|
||||
|
||||
var direction = (action === 'moveleft') ? -1 : 1;
|
||||
|
||||
// And we need to determine the current and new indent level
|
||||
var indentdiv = activity.one(SELECTOR.MODINDENTDIV),
|
||||
indent = indentdiv.getAttribute('class').match(/mod-indent-(\d{1,})/),
|
||||
oldindent = 0,
|
||||
newindent;
|
||||
|
||||
if (indent) {
|
||||
oldindent = parseInt(indent[1], 10);
|
||||
}
|
||||
newindent = oldindent + parseInt(direction, 10);
|
||||
|
||||
if (newindent < INDENTLIMITS.MIN || newindent > INDENTLIMITS.MAX) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (indent) {
|
||||
indentdiv.removeClass(indent[0]);
|
||||
}
|
||||
|
||||
// Perform the move
|
||||
indentdiv.addClass(CSS.MODINDENTCOUNT + newindent);
|
||||
var data = {
|
||||
'class': 'resource',
|
||||
'field': 'indent',
|
||||
'value': newindent,
|
||||
'id': Y.Moodle.core_course.util.cm.getId(activity)
|
||||
};
|
||||
var spinner = this.add_spinner(activity);
|
||||
this.send_request(data, spinner);
|
||||
|
||||
var remainingmove;
|
||||
|
||||
// Handle removal/addition of the moveleft button.
|
||||
if (newindent === INDENTLIMITS.MIN) {
|
||||
button.addClass('hidden');
|
||||
remainingmove = activity.one('.editing_moveright');
|
||||
} else if (newindent > INDENTLIMITS.MIN && oldindent === INDENTLIMITS.MIN) {
|
||||
button.ancestor('.menu').one('[data-action=moveleft]').removeClass('hidden');
|
||||
}
|
||||
|
||||
if (newindent === INDENTLIMITS.MAX) {
|
||||
button.addClass('hidden');
|
||||
remainingmove = activity.one('.editing_moveleft');
|
||||
} else if (newindent < INDENTLIMITS.MAX && oldindent === INDENTLIMITS.MAX) {
|
||||
button.ancestor('.menu').one('[data-action=moveright]').removeClass('hidden');
|
||||
}
|
||||
|
||||
// Handle massive indentation to match non-ajax display
|
||||
var hashugeclass = indentdiv.hasClass(CSS.MODINDENTHUGE);
|
||||
if (newindent > 15 && !hashugeclass) {
|
||||
indentdiv.addClass(CSS.MODINDENTHUGE);
|
||||
} else if (newindent <= 15 && hashugeclass) {
|
||||
indentdiv.removeClass(CSS.MODINDENTHUGE);
|
||||
}
|
||||
|
||||
if (ev.type && ev.type === "key" && remainingmove) {
|
||||
remainingmove.focus();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Deletes the given activity or resource after confirmation.
|
||||
*
|
||||
* @protected
|
||||
* @method delete_with_confirmation
|
||||
* @param {EventFacade} ev The event that was fired.
|
||||
* @param {Node} button The button that triggered this action.
|
||||
* @param {Node} activity The activity node that this action will be performed on.
|
||||
* @chainable
|
||||
*/
|
||||
delete_with_confirmation: function(ev, button, activity) {
|
||||
// Prevent the default button action
|
||||
ev.preventDefault();
|
||||
|
||||
// Get the element we're working on
|
||||
var element = activity,
|
||||
// Create confirm string (different if element has or does not have name)
|
||||
confirmstring = '',
|
||||
plugindata = {
|
||||
type: M.util.get_string('pluginname', element.getAttribute('class').match(/modtype_([^\s]*)/)[1])
|
||||
};
|
||||
if (Y.Moodle.core_course.util.cm.getName(element) !== null) {
|
||||
plugindata.name = Y.Moodle.core_course.util.cm.getName(element);
|
||||
confirmstring = M.util.get_string('deletechecktypename', 'moodle', plugindata);
|
||||
} else {
|
||||
confirmstring = M.util.get_string('deletechecktype', 'moodle', plugindata);
|
||||
}
|
||||
|
||||
// Create the confirmation dialogue.
|
||||
var confirm = new M.core.confirm({
|
||||
question: confirmstring,
|
||||
modal: true,
|
||||
visible: false
|
||||
});
|
||||
confirm.show();
|
||||
|
||||
// If it is confirmed.
|
||||
confirm.on('complete-yes', function() {
|
||||
|
||||
// Actually remove the element.
|
||||
element.remove();
|
||||
var data = {
|
||||
'class': 'resource',
|
||||
'action': 'DELETE',
|
||||
'id': Y.Moodle.core_course.util.cm.getId(element)
|
||||
};
|
||||
this.send_request(data);
|
||||
if (M.core.actionmenu && M.core.actionmenu.instance) {
|
||||
M.core.actionmenu.instance.hideMenu(ev);
|
||||
}
|
||||
|
||||
}, this);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Duplicates the activity.
|
||||
*
|
||||
* @method duplicate
|
||||
* @protected
|
||||
* @param {EventFacade} ev The event that was fired.
|
||||
* @param {Node} button The button that triggered this action.
|
||||
* @param {Node} activity The activity node that this action will be performed on.
|
||||
* @chainable
|
||||
*/
|
||||
duplicate: function(ev, button, activity) {
|
||||
// Prevent the default button action
|
||||
ev.preventDefault();
|
||||
|
||||
// Get the element we're working on
|
||||
var element = activity;
|
||||
|
||||
// Add the lightbox.
|
||||
var section = activity.ancestor(M.course.format.get_section_selector(Y)),
|
||||
lightbox = M.util.add_lightbox(Y, section).show();
|
||||
|
||||
// Build and send the request.
|
||||
var data = {
|
||||
'class': 'resource',
|
||||
'field': 'duplicate',
|
||||
'id': Y.Moodle.core_course.util.cm.getId(element),
|
||||
'sr': button.getData('sr')
|
||||
};
|
||||
this.send_request(data, lightbox, function(response) {
|
||||
var newcm = Y.Node.create(response.fullcontent);
|
||||
|
||||
// Append to the section?
|
||||
activity.insert(newcm, 'after');
|
||||
Y.use('moodle-course-coursebase', function() {
|
||||
M.course.coursebase.invoke_function('setup_for_resource', newcm);
|
||||
});
|
||||
if (M.core.actionmenu && M.core.actionmenu.newDOMNode) {
|
||||
M.core.actionmenu.newDOMNode(newcm);
|
||||
}
|
||||
});
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Changes the visibility of this activity or resource.
|
||||
*
|
||||
* @method change_visibility
|
||||
* @protected
|
||||
* @param {EventFacade} ev The event that was fired.
|
||||
* @param {Node} button The button that triggered this action.
|
||||
* @param {Node} activity The activity node that this action will be performed on.
|
||||
* @param {String} action The action that has been requested.
|
||||
* @chainable
|
||||
*/
|
||||
change_visibility: function(ev, button, activity, action) {
|
||||
// Prevent the default button action
|
||||
ev.preventDefault();
|
||||
|
||||
// Get the element we're working on
|
||||
var element = activity;
|
||||
var value = this.handle_resource_dim(button, activity, action);
|
||||
|
||||
// Send the request
|
||||
var data = {
|
||||
'class': 'resource',
|
||||
'field': 'visible',
|
||||
'value': value,
|
||||
'id': Y.Moodle.core_course.util.cm.getId(element)
|
||||
};
|
||||
var spinner = this.add_spinner(element);
|
||||
this.send_request(data, spinner);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles the UI aspect of dimming the activity or resource.
|
||||
*
|
||||
* @method handle_resource_dim
|
||||
* @protected
|
||||
* @param {Node} button The button that triggered the action.
|
||||
* @param {Node} activity The activity node that this action will be performed on.
|
||||
* @param {String} action 'show' or 'hide'.
|
||||
* @return {Number} 1 if we changed to visible, 0 if we were hiding.
|
||||
*/
|
||||
handle_resource_dim: function(button, activity, action) {
|
||||
var toggleclass = CSS.DIMCLASS,
|
||||
dimarea = activity.one([
|
||||
SELECTOR.ACTIVITYLINK,
|
||||
SELECTOR.CONTENTWITHOUTLINK
|
||||
].join(', ')),
|
||||
availabilityinfo = activity.one(CSS.AVAILABILITYINFODIV),
|
||||
nextaction = (action === 'hide') ? 'show' : 'hide',
|
||||
buttontext = button.one('span'),
|
||||
newstring = M.util.get_string(nextaction, 'moodle'),
|
||||
buttonimg = button.one('img');
|
||||
|
||||
// Update button info.
|
||||
buttonimg.setAttrs({
|
||||
'src': M.util.image_url('t/' + nextaction)
|
||||
});
|
||||
|
||||
if (Y.Lang.trim(button.getAttribute('title'))) {
|
||||
button.setAttribute('title', newstring);
|
||||
}
|
||||
|
||||
if (Y.Lang.trim(buttonimg.getAttribute('alt'))) {
|
||||
buttonimg.setAttribute('alt', newstring);
|
||||
}
|
||||
|
||||
button.replaceClass('editing_' + action, 'editing_' + nextaction);
|
||||
button.setData('action', nextaction);
|
||||
if (buttontext) {
|
||||
buttontext.set('text', newstring);
|
||||
}
|
||||
|
||||
if (activity.one(SELECTOR.CONTENTWITHOUTLINK)) {
|
||||
dimarea = activity.one(SELECTOR.CONTENTWITHOUTLINK);
|
||||
toggleclass = CSS.DIMMEDTEXT;
|
||||
}
|
||||
|
||||
// If activity is conditionally hidden, then don't toggle.
|
||||
if (!dimarea.hasClass(CSS.CONDITIONALHIDDEN)) {
|
||||
if (action === 'hide') {
|
||||
// Change the UI.
|
||||
dimarea.addClass(toggleclass);
|
||||
// We need to toggle dimming on the description too.
|
||||
activity.all(SELECTOR.CONTENTAFTERLINK).addClass(CSS.DIMMEDTEXT);
|
||||
activity.all(SELECTOR.GROUPINGLABEL).addClass(CSS.DIMMEDTEXT);
|
||||
} else {
|
||||
// Change the UI.
|
||||
dimarea.removeClass(toggleclass);
|
||||
// We need to toggle dimming on the description too.
|
||||
activity.all(SELECTOR.CONTENTAFTERLINK).removeClass(CSS.DIMMEDTEXT);
|
||||
activity.all(SELECTOR.GROUPINGLABEL).removeClass(CSS.DIMMEDTEXT);
|
||||
}
|
||||
}
|
||||
// Toggle availablity info for conditional activities.
|
||||
if (availabilityinfo) {
|
||||
availabilityinfo.toggleClass(CSS.HIDE);
|
||||
}
|
||||
return (action === 'hide') ? 0 : 1;
|
||||
},
|
||||
|
||||
/**
|
||||
* Changes the groupmode of the activity to the next groupmode in the sequence.
|
||||
*
|
||||
* @method change_groupmode
|
||||
* @protected
|
||||
* @param {EventFacade} ev The event that was fired.
|
||||
* @param {Node} button The button that triggered this action.
|
||||
* @param {Node} activity The activity node that this action will be performed on.
|
||||
* @chainable
|
||||
*/
|
||||
change_groupmode: function(ev, button, activity) {
|
||||
// Prevent the default button action.
|
||||
ev.preventDefault();
|
||||
|
||||
// Current Mode
|
||||
var groupmode = parseInt(button.getData('nextgroupmode'), 10),
|
||||
newtitle = '',
|
||||
iconsrc = '',
|
||||
newtitlestr,
|
||||
data,
|
||||
spinner,
|
||||
nextgroupmode = groupmode + 1,
|
||||
buttonimg = button.one('img');
|
||||
|
||||
if (nextgroupmode > 2) {
|
||||
nextgroupmode = 0;
|
||||
}
|
||||
|
||||
if (groupmode === this.GROUPS_NONE) {
|
||||
newtitle = 'groupsnone';
|
||||
iconsrc = M.util.image_url('i/groupn', 'moodle');
|
||||
} else if (groupmode === this.GROUPS_SEPARATE) {
|
||||
newtitle = 'groupsseparate';
|
||||
iconsrc = M.util.image_url('i/groups', 'moodle');
|
||||
} else if (groupmode === this.GROUPS_VISIBLE) {
|
||||
newtitle = 'groupsvisible';
|
||||
iconsrc = M.util.image_url('i/groupv', 'moodle');
|
||||
}
|
||||
newtitlestr = M.util.get_string('clicktochangeinbrackets', 'moodle', M.util.get_string(newtitle, 'moodle'));
|
||||
|
||||
// Change the UI
|
||||
var oldAction = button.getData('action');
|
||||
button.replaceClass('editing_' + oldAction, 'editing_' + newtitle);
|
||||
buttonimg.setAttrs({
|
||||
'src': iconsrc
|
||||
});
|
||||
if (Y.Lang.trim(button.getAttribute('title'))) {
|
||||
button.setAttribute('title', newtitlestr).setData('action', newtitle).setData('nextgroupmode', nextgroupmode);
|
||||
}
|
||||
|
||||
if (Y.Lang.trim(buttonimg.getAttribute('alt'))) {
|
||||
buttonimg.setAttribute('alt', newtitlestr);
|
||||
}
|
||||
|
||||
// And send the request
|
||||
data = {
|
||||
'class': 'resource',
|
||||
'field': 'groupmode',
|
||||
'value': groupmode,
|
||||
'id': Y.Moodle.core_course.util.cm.getId(activity)
|
||||
};
|
||||
|
||||
spinner = this.add_spinner(activity);
|
||||
this.send_request(data, spinner);
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the visibility of the specified resource to match the visible parameter.
|
||||
*
|
||||
* Note: This is not a toggle function and only changes the visibility
|
||||
* in the browser (no ajax update is performed).
|
||||
*
|
||||
* @method set_visibility_resource_ui
|
||||
* @param {object} args An object containing the required information to trigger a change.
|
||||
* @param {Node} args.element The resource to toggle
|
||||
* @param {Boolean} args.visible The target visibility
|
||||
*/
|
||||
set_visibility_resource_ui: function(args) {
|
||||
var element = args.element,
|
||||
buttonnode = element.one(SELECTOR.HIDE),
|
||||
// By default we assume that the item is visible and we're going to hide it.
|
||||
currentVisibility = true,
|
||||
targetVisibility = false;
|
||||
|
||||
if (!buttonnode) {
|
||||
// If the buttonnode was not found, try to find the HIDE button
|
||||
// and change the target visibility setting to false.
|
||||
buttonnode = element.one(SELECTOR.SHOW);
|
||||
currentVisibility = false;
|
||||
targetVisibility = true;
|
||||
}
|
||||
|
||||
if (typeof args.visible !== 'undefined') {
|
||||
// If we were provided with a visibility argument, use that instead.
|
||||
targetVisibility = args.visible;
|
||||
}
|
||||
|
||||
// Only trigger a change if necessary.
|
||||
if (currentVisibility !== targetVisibility) {
|
||||
var action = 'hide';
|
||||
if (targetVisibility) {
|
||||
action = 'show';
|
||||
}
|
||||
|
||||
this.handle_resource_dim(buttonnode, element, action);
|
||||
}
|
||||
}
|
||||
}, {
|
||||
NAME: 'course-resource-toolbox',
|
||||
ATTRS: {
|
||||
}
|
||||
});
|
||||
|
||||
M.course.resource_toolbox = null;
|
||||
M.course.init_resource_toolbox = function(config) {
|
||||
M.course.resource_toolbox = new RESOURCETOOLBOX(config);
|
||||
return M.course.resource_toolbox;
|
||||
};
|
||||
/* global SELECTOR, TOOLBOX */
|
||||
|
||||
/**
|
||||
* Resource and activity toolbox class.
|
||||
*
|
||||
* This class is responsible for managing AJAX interactions with activities and resources
|
||||
* when viewing a course in editing mode.
|
||||
*
|
||||
* @module moodle-course-toolboxes
|
||||
* @namespace M.course.toolboxes
|
||||
*/
|
||||
|
||||
/**
|
||||
* Section toolbox class.
|
||||
*
|
||||
* This class is responsible for managing AJAX interactions with sections
|
||||
* when viewing a course in editing mode.
|
||||
*
|
||||
* @class section
|
||||
* @constructor
|
||||
* @extends M.course.toolboxes.toolbox
|
||||
*/
|
||||
var SECTIONTOOLBOX = function() {
|
||||
SECTIONTOOLBOX.superclass.constructor.apply(this, arguments);
|
||||
};
|
||||
|
||||
Y.extend(SECTIONTOOLBOX, TOOLBOX, {
|
||||
/**
|
||||
* Initialize the section toolboxes module.
|
||||
*
|
||||
* Updates all span.commands with relevant handlers and other required changes.
|
||||
*
|
||||
* @method initializer
|
||||
* @protected
|
||||
*/
|
||||
initializer: function() {
|
||||
M.course.coursebase.register_module(this);
|
||||
|
||||
// Section Highlighting.
|
||||
Y.delegate('click', this.toggle_highlight, SELECTOR.PAGECONTENT, SELECTOR.SECTIONLI + ' ' + SELECTOR.HIGHLIGHT, this);
|
||||
|
||||
// Section Visibility.
|
||||
Y.delegate('click', this.toggle_hide_section, SELECTOR.PAGECONTENT, SELECTOR.SECTIONLI + ' ' + SELECTOR.SHOWHIDE, this);
|
||||
},
|
||||
|
||||
toggle_hide_section: function(e) {
|
||||
// Prevent the default button action.
|
||||
e.preventDefault();
|
||||
|
||||
// Get the section we're working on.
|
||||
var section = e.target.ancestor(M.course.format.get_section_selector(Y)),
|
||||
button = e.target.ancestor('a', true),
|
||||
hideicon = button.one('img'),
|
||||
buttontext = button.one('span'),
|
||||
|
||||
// The value to submit
|
||||
value,
|
||||
|
||||
// The text for strings and images. Also determines the icon to display.
|
||||
action,
|
||||
nextaction;
|
||||
|
||||
if (!section.hasClass(CSS.SECTIONHIDDENCLASS)) {
|
||||
section.addClass(CSS.SECTIONHIDDENCLASS);
|
||||
value = 0;
|
||||
action = 'hide';
|
||||
nextaction = 'show';
|
||||
} else {
|
||||
section.removeClass(CSS.SECTIONHIDDENCLASS);
|
||||
value = 1;
|
||||
action = 'show';
|
||||
nextaction = 'hide';
|
||||
}
|
||||
|
||||
var newstring = M.util.get_string(nextaction + 'fromothers', 'format_' + this.get('format'));
|
||||
hideicon.setAttrs({
|
||||
'alt': newstring,
|
||||
'src': M.util.image_url('i/' + nextaction)
|
||||
});
|
||||
button.set('title', newstring);
|
||||
if (buttontext) {
|
||||
buttontext.set('text', newstring);
|
||||
}
|
||||
|
||||
// Change the show/hide status
|
||||
var data = {
|
||||
'class': 'section',
|
||||
'field': 'visible',
|
||||
'id': Y.Moodle.core_course.util.section.getId(section.ancestor(M.course.format.get_section_wrapper(Y), true)),
|
||||
'value': value
|
||||
};
|
||||
|
||||
var lightbox = M.util.add_lightbox(Y, section);
|
||||
lightbox.show();
|
||||
|
||||
this.send_request(data, lightbox, function(response) {
|
||||
var activities = section.all(SELECTOR.ACTIVITYLI);
|
||||
activities.each(function(node) {
|
||||
var button;
|
||||
if (node.one(SELECTOR.SHOW)) {
|
||||
button = node.one(SELECTOR.SHOW);
|
||||
} else {
|
||||
button = node.one(SELECTOR.HIDE);
|
||||
}
|
||||
var activityid = Y.Moodle.core_course.util.cm.getId(node);
|
||||
|
||||
// NOTE: resourcestotoggle is returned as a string instead
|
||||
// of a Number so we must cast our activityid to a String.
|
||||
if (Y.Array.indexOf(response.resourcestotoggle, "" + activityid) !== -1) {
|
||||
M.course.resource_toolbox.handle_resource_dim(button, node, action);
|
||||
}
|
||||
}, this);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggle highlighting the current section.
|
||||
*
|
||||
* @method toggle_highlight
|
||||
* @param {EventFacade} e
|
||||
*/
|
||||
toggle_highlight: function(e) {
|
||||
// Prevent the default button action.
|
||||
e.preventDefault();
|
||||
|
||||
// Get the section we're working on.
|
||||
var section = e.target.ancestor(M.course.format.get_section_selector(Y));
|
||||
var button = e.target.ancestor('a', true);
|
||||
var buttonicon = button.one('img');
|
||||
var buttontext = button.one('span');
|
||||
|
||||
// Determine whether the marker is currently set.
|
||||
var togglestatus = section.hasClass('current');
|
||||
var value = 0;
|
||||
|
||||
// Set the current highlighted item text.
|
||||
var old_string = M.util.get_string('markthistopic', 'moodle');
|
||||
|
||||
var selectedpage = Y.one(SELECTOR.PAGECONTENT);
|
||||
selectedpage
|
||||
.all(M.course.format.get_section_selector(Y) + '.current ' + SELECTOR.HIGHLIGHT)
|
||||
.set('title', old_string);
|
||||
selectedpage
|
||||
.all(M.course.format.get_section_selector(Y) + '.current ' + SELECTOR.HIGHLIGHT + ' span')
|
||||
.set('text', M.util.get_string('highlight', 'moodle'));
|
||||
selectedpage
|
||||
.all(M.course.format.get_section_selector(Y) + '.current ' + SELECTOR.HIGHLIGHT + ' img')
|
||||
.set('alt', old_string)
|
||||
.set('src', M.util.image_url('i/marker'));
|
||||
|
||||
// Remove the highlighting from all sections.
|
||||
selectedpage.all(M.course.format.get_section_selector(Y))
|
||||
.removeClass('current');
|
||||
|
||||
// Then add it if required to the selected section.
|
||||
if (!togglestatus) {
|
||||
section.addClass('current');
|
||||
value = Y.Moodle.core_course.util.section.getId(section.ancestor(M.course.format.get_section_wrapper(Y), true));
|
||||
var new_string = M.util.get_string('markedthistopic', 'moodle');
|
||||
button
|
||||
.set('title', new_string);
|
||||
buttonicon
|
||||
.set('alt', new_string)
|
||||
.set('src', M.util.image_url('i/marked'));
|
||||
if (buttontext) {
|
||||
buttontext
|
||||
.set('text', M.util.get_string('highlightoff', 'moodle'));
|
||||
}
|
||||
}
|
||||
|
||||
// Change the highlight status.
|
||||
var data = {
|
||||
'class': 'course',
|
||||
'field': 'marker',
|
||||
'value': value
|
||||
};
|
||||
var lightbox = M.util.add_lightbox(Y, section);
|
||||
lightbox.show();
|
||||
this.send_request(data, lightbox);
|
||||
}
|
||||
}, {
|
||||
NAME: 'course-section-toolbox',
|
||||
ATTRS: {
|
||||
}
|
||||
});
|
||||
|
||||
M.course.init_section_toolbox = function(config) {
|
||||
return new SECTIONTOOLBOX(config);
|
||||
};
|
||||
|
||||
|
||||
}, '@VERSION@', {"requires": ["node", "base", "event-key", "node", "io", "moodle-course-coursebase", "moodle-course-util"]});
|
||||
File diff suppressed because one or more lines are too long
@@ -1,947 +0,0 @@
|
||||
YUI.add('moodle-course-toolboxes', function (Y, NAME) {
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
/**
|
||||
* Resource and activity toolbox class.
|
||||
*
|
||||
* This class is responsible for managing AJAX interactions with activities and resources
|
||||
* when viewing a course in editing mode.
|
||||
*
|
||||
* @module moodle-course-toolboxes
|
||||
* @namespace M.course.toolboxes
|
||||
*/
|
||||
|
||||
// The CSS classes we use.
|
||||
var CSS = {
|
||||
ACTIVITYINSTANCE: 'activityinstance',
|
||||
AVAILABILITYINFODIV: 'div.availabilityinfo',
|
||||
CONTENTWITHOUTLINK: 'contentwithoutlink',
|
||||
CONDITIONALHIDDEN: 'conditionalhidden',
|
||||
DIMCLASS: 'dimmed',
|
||||
DIMMEDTEXT: 'dimmed_text',
|
||||
EDITINSTRUCTIONS: 'editinstructions',
|
||||
HIDE: 'hide',
|
||||
MODINDENTCOUNT: 'mod-indent-',
|
||||
MODINDENTHUGE: 'mod-indent-huge',
|
||||
MODULEIDPREFIX: 'module-',
|
||||
SECTIONHIDDENCLASS: 'hidden',
|
||||
SECTIONIDPREFIX: 'section-',
|
||||
SHOW: 'editing_show'
|
||||
},
|
||||
// The CSS selectors we use.
|
||||
SELECTOR = {
|
||||
ACTIONAREA: '.actions',
|
||||
ACTIONLINKTEXT: '.actionlinktext',
|
||||
ACTIVITYACTION: 'a.cm-edit-action[data-action]',
|
||||
ACTIVITYICON: 'img.activityicon',
|
||||
ACTIVITYINSTANCE: '.' + CSS.ACTIVITYINSTANCE,
|
||||
ACTIVITYLINK: '.' + CSS.ACTIVITYINSTANCE + ' > a, .' + CSS.ACTIVITYINSTANCE +
|
||||
' > span[data-inplaceeditable] > a:not([data-inplaceeditablelink])',
|
||||
ACTIVITYLI: 'li.activity',
|
||||
COMMANDSPAN: '.commands',
|
||||
CONTENTAFTERLINK: 'div.contentafterlink',
|
||||
CONTENTWITHOUTLINK: 'div.contentwithoutlink',
|
||||
GROUPINGLABEL: '.' + CSS.ACTIVITYINSTANCE + ' .groupinglabel',
|
||||
HIDE: 'a.editing_hide',
|
||||
HIGHLIGHT: 'a.editing_highlight',
|
||||
INSTANCENAME: 'span.instancename',
|
||||
MODINDENTDIV: '.mod-indent',
|
||||
MODINDENTOUTER: '.mod-indent-outer',
|
||||
PAGECONTENT: 'body',
|
||||
SECTIONLI: 'li.section',
|
||||
SHOW: 'a.' + CSS.SHOW,
|
||||
SHOWHIDE: 'a.editing_showhide'
|
||||
},
|
||||
INDENTLIMITS = {
|
||||
MIN: 0,
|
||||
MAX: 16
|
||||
},
|
||||
BODY = Y.one(document.body);
|
||||
|
||||
// Setup the basic namespace.
|
||||
M.course = M.course || {};
|
||||
|
||||
/**
|
||||
* The toolbox class is a generic class which should never be directly
|
||||
* instantiated. Please extend it instead.
|
||||
*
|
||||
* @class toolbox
|
||||
* @constructor
|
||||
* @protected
|
||||
* @extends Base
|
||||
*/
|
||||
var TOOLBOX = function() {
|
||||
TOOLBOX.superclass.constructor.apply(this, arguments);
|
||||
};
|
||||
|
||||
Y.extend(TOOLBOX, Y.Base, {
|
||||
/**
|
||||
* Send a request using the REST API
|
||||
*
|
||||
* @method send_request
|
||||
* @param {Object} data The data to submit with the AJAX request
|
||||
* @param {Node} [statusspinner] A statusspinner which may contain a section loader
|
||||
* @param {Function} success_callback The callback to use on success
|
||||
* @param {Object} [optionalconfig] Any additional configuration to submit
|
||||
* @chainable
|
||||
*/
|
||||
send_request: function(data, statusspinner, success_callback, optionalconfig) {
|
||||
// Default data structure
|
||||
if (!data) {
|
||||
data = {};
|
||||
}
|
||||
// Handle any variables which we must pass back through to
|
||||
var pageparams = this.get('config').pageparams,
|
||||
varname;
|
||||
for (varname in pageparams) {
|
||||
data[varname] = pageparams[varname];
|
||||
}
|
||||
|
||||
data.sesskey = M.cfg.sesskey;
|
||||
data.courseId = this.get('courseid');
|
||||
|
||||
var uri = M.cfg.wwwroot + this.get('ajaxurl');
|
||||
|
||||
// Define the configuration to send with the request
|
||||
var responsetext = [];
|
||||
var config = {
|
||||
method: 'POST',
|
||||
data: data,
|
||||
on: {
|
||||
success: function(tid, response) {
|
||||
try {
|
||||
responsetext = Y.JSON.parse(response.responseText);
|
||||
if (responsetext.error) {
|
||||
new M.core.ajaxException(responsetext);
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignore.
|
||||
}
|
||||
|
||||
// Run the callback if we have one.
|
||||
if (success_callback) {
|
||||
Y.bind(success_callback, this, responsetext)();
|
||||
}
|
||||
|
||||
if (statusspinner) {
|
||||
window.setTimeout(function() {
|
||||
statusspinner.hide();
|
||||
}, 400);
|
||||
}
|
||||
},
|
||||
failure: function(tid, response) {
|
||||
if (statusspinner) {
|
||||
statusspinner.hide();
|
||||
}
|
||||
new M.core.ajaxException(response);
|
||||
}
|
||||
},
|
||||
context: this
|
||||
};
|
||||
|
||||
// Apply optional config
|
||||
if (optionalconfig) {
|
||||
for (varname in optionalconfig) {
|
||||
config[varname] = optionalconfig[varname];
|
||||
}
|
||||
}
|
||||
|
||||
if (statusspinner) {
|
||||
statusspinner.show();
|
||||
}
|
||||
|
||||
// Send the request
|
||||
Y.io(uri, config);
|
||||
return this;
|
||||
}
|
||||
},
|
||||
{
|
||||
NAME: 'course-toolbox',
|
||||
ATTRS: {
|
||||
/**
|
||||
* The ID of the Moodle Course being edited.
|
||||
*
|
||||
* @attribute courseid
|
||||
* @default 0
|
||||
* @type Number
|
||||
*/
|
||||
courseid: {
|
||||
'value': 0
|
||||
},
|
||||
|
||||
/**
|
||||
* The Moodle course format.
|
||||
*
|
||||
* @attribute format
|
||||
* @default 'topics'
|
||||
* @type String
|
||||
*/
|
||||
format: {
|
||||
'value': 'topics'
|
||||
},
|
||||
/**
|
||||
* The URL to use when submitting requests.
|
||||
* @attribute ajaxurl
|
||||
* @default null
|
||||
* @type String
|
||||
*/
|
||||
ajaxurl: {
|
||||
'value': null
|
||||
},
|
||||
/**
|
||||
* Any additional configuration passed when creating the instance.
|
||||
*
|
||||
* @attribute config
|
||||
* @default {}
|
||||
* @type Object
|
||||
*/
|
||||
config: {
|
||||
'value': {}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
/* global TOOLBOX, BODY, SELECTOR, INDENTLIMITS */
|
||||
|
||||
/**
|
||||
* Resource and activity toolbox class.
|
||||
*
|
||||
* This class is responsible for managing AJAX interactions with activities and resources
|
||||
* when viewing a course in editing mode.
|
||||
*
|
||||
* @module moodle-course-toolboxes
|
||||
* @namespace M.course.toolboxes
|
||||
*/
|
||||
|
||||
/**
|
||||
* Resource and activity toolbox class.
|
||||
*
|
||||
* This is a class extending TOOLBOX containing code specific to resources
|
||||
*
|
||||
* This class is responsible for managing AJAX interactions with activities and resources
|
||||
* when viewing a course in editing mode.
|
||||
*
|
||||
* @class resources
|
||||
* @constructor
|
||||
* @extends M.course.toolboxes.toolbox
|
||||
*/
|
||||
var RESOURCETOOLBOX = function() {
|
||||
RESOURCETOOLBOX.superclass.constructor.apply(this, arguments);
|
||||
};
|
||||
|
||||
Y.extend(RESOURCETOOLBOX, TOOLBOX, {
|
||||
/**
|
||||
* No groups are being used.
|
||||
*
|
||||
* @property GROUPS_NONE
|
||||
* @protected
|
||||
* @type Number
|
||||
*/
|
||||
GROUPS_NONE: 0,
|
||||
|
||||
/**
|
||||
* Separate groups are being used.
|
||||
*
|
||||
* @property GROUPS_SEPARATE
|
||||
* @protected
|
||||
* @type Number
|
||||
*/
|
||||
GROUPS_SEPARATE: 1,
|
||||
|
||||
/**
|
||||
* Visible groups are being used.
|
||||
*
|
||||
* @property GROUPS_VISIBLE
|
||||
* @protected
|
||||
* @type Number
|
||||
*/
|
||||
GROUPS_VISIBLE: 2,
|
||||
|
||||
/**
|
||||
* Initialize the resource toolbox
|
||||
*
|
||||
* For each activity the commands are updated and a reference to the activity is attached.
|
||||
* This way it doesn't matter where the commands are going to called from they have a reference to the
|
||||
* activity that they relate to.
|
||||
* This is essential as some of the actions are displayed in an actionmenu which removes them from the
|
||||
* page flow.
|
||||
*
|
||||
* This function also creates a single event delegate to manage all AJAX actions for all activities on
|
||||
* the page.
|
||||
*
|
||||
* @method initializer
|
||||
* @protected
|
||||
*/
|
||||
initializer: function() {
|
||||
M.course.coursebase.register_module(this);
|
||||
BODY.delegate('key', this.handle_data_action, 'down:enter', SELECTOR.ACTIVITYACTION, this);
|
||||
Y.delegate('click', this.handle_data_action, BODY, SELECTOR.ACTIVITYACTION, this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles the delegation event. When this is fired someone has triggered an action.
|
||||
*
|
||||
* Note not all actions will result in an AJAX enhancement.
|
||||
*
|
||||
* @protected
|
||||
* @method handle_data_action
|
||||
* @param {EventFacade} ev The event that was triggered.
|
||||
* @return {boolean}
|
||||
*/
|
||||
handle_data_action: function(ev) {
|
||||
// We need to get the anchor element that triggered this event.
|
||||
var node = ev.target;
|
||||
if (!node.test('a')) {
|
||||
node = node.ancestor(SELECTOR.ACTIVITYACTION);
|
||||
}
|
||||
|
||||
// From the anchor we can get both the activity (added during initialisation) and the action being
|
||||
// performed (added by the UI as a data attribute).
|
||||
var action = node.getData('action'),
|
||||
activity = node.ancestor(SELECTOR.ACTIVITYLI);
|
||||
|
||||
if (!node.test('a') || !action || !activity) {
|
||||
// It wasn't a valid action node.
|
||||
return;
|
||||
}
|
||||
|
||||
// Switch based upon the action and do the desired thing.
|
||||
switch (action) {
|
||||
case 'moveleft':
|
||||
case 'moveright':
|
||||
// The user changing the indent of the activity.
|
||||
this.change_indent(ev, node, activity, action);
|
||||
break;
|
||||
case 'delete':
|
||||
// The user is deleting the activity.
|
||||
this.delete_with_confirmation(ev, node, activity, action);
|
||||
break;
|
||||
case 'duplicate':
|
||||
// The user is duplicating the activity.
|
||||
this.duplicate(ev, node, activity, action);
|
||||
break;
|
||||
case 'hide':
|
||||
case 'show':
|
||||
// The user is changing the visibility of the activity.
|
||||
this.change_visibility(ev, node, activity, action);
|
||||
break;
|
||||
case 'groupsseparate':
|
||||
case 'groupsvisible':
|
||||
case 'groupsnone':
|
||||
// The user is changing the group mode.
|
||||
this.change_groupmode(ev, node, activity, action);
|
||||
break;
|
||||
case 'move':
|
||||
case 'update':
|
||||
case 'assignroles':
|
||||
break;
|
||||
default:
|
||||
// Nothing to do here!
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a loading icon to the specified activity.
|
||||
*
|
||||
* The icon is added within the action area.
|
||||
*
|
||||
* @method add_spinner
|
||||
* @param {Node} activity The activity to add a loading icon to
|
||||
* @return {Node|null} The newly created icon, or null if the action area was not found.
|
||||
*/
|
||||
add_spinner: function(activity) {
|
||||
var actionarea = activity.one(SELECTOR.ACTIONAREA);
|
||||
if (actionarea) {
|
||||
return M.util.add_spinner(Y, actionarea);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Change the indent of the activity or resource.
|
||||
*
|
||||
* @method change_indent
|
||||
* @protected
|
||||
* @param {EventFacade} ev The event that was fired.
|
||||
* @param {Node} button The button that triggered this action.
|
||||
* @param {Node} activity The activity node that this action will be performed on.
|
||||
* @param {String} action The action that has been requested. Will be 'moveleft' or 'moveright'.
|
||||
*/
|
||||
change_indent: function(ev, button, activity, action) {
|
||||
// Prevent the default button action
|
||||
ev.preventDefault();
|
||||
|
||||
var direction = (action === 'moveleft') ? -1 : 1;
|
||||
|
||||
// And we need to determine the current and new indent level
|
||||
var indentdiv = activity.one(SELECTOR.MODINDENTDIV),
|
||||
indent = indentdiv.getAttribute('class').match(/mod-indent-(\d{1,})/),
|
||||
oldindent = 0,
|
||||
newindent;
|
||||
|
||||
if (indent) {
|
||||
oldindent = parseInt(indent[1], 10);
|
||||
}
|
||||
newindent = oldindent + parseInt(direction, 10);
|
||||
|
||||
if (newindent < INDENTLIMITS.MIN || newindent > INDENTLIMITS.MAX) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (indent) {
|
||||
indentdiv.removeClass(indent[0]);
|
||||
}
|
||||
|
||||
// Perform the move
|
||||
indentdiv.addClass(CSS.MODINDENTCOUNT + newindent);
|
||||
var data = {
|
||||
'class': 'resource',
|
||||
'field': 'indent',
|
||||
'value': newindent,
|
||||
'id': Y.Moodle.core_course.util.cm.getId(activity)
|
||||
};
|
||||
var spinner = this.add_spinner(activity);
|
||||
this.send_request(data, spinner);
|
||||
|
||||
var remainingmove;
|
||||
|
||||
// Handle removal/addition of the moveleft button.
|
||||
if (newindent === INDENTLIMITS.MIN) {
|
||||
button.addClass('hidden');
|
||||
remainingmove = activity.one('.editing_moveright');
|
||||
} else if (newindent > INDENTLIMITS.MIN && oldindent === INDENTLIMITS.MIN) {
|
||||
button.ancestor('.menu').one('[data-action=moveleft]').removeClass('hidden');
|
||||
}
|
||||
|
||||
if (newindent === INDENTLIMITS.MAX) {
|
||||
button.addClass('hidden');
|
||||
remainingmove = activity.one('.editing_moveleft');
|
||||
} else if (newindent < INDENTLIMITS.MAX && oldindent === INDENTLIMITS.MAX) {
|
||||
button.ancestor('.menu').one('[data-action=moveright]').removeClass('hidden');
|
||||
}
|
||||
|
||||
// Handle massive indentation to match non-ajax display
|
||||
var hashugeclass = indentdiv.hasClass(CSS.MODINDENTHUGE);
|
||||
if (newindent > 15 && !hashugeclass) {
|
||||
indentdiv.addClass(CSS.MODINDENTHUGE);
|
||||
} else if (newindent <= 15 && hashugeclass) {
|
||||
indentdiv.removeClass(CSS.MODINDENTHUGE);
|
||||
}
|
||||
|
||||
if (ev.type && ev.type === "key" && remainingmove) {
|
||||
remainingmove.focus();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Deletes the given activity or resource after confirmation.
|
||||
*
|
||||
* @protected
|
||||
* @method delete_with_confirmation
|
||||
* @param {EventFacade} ev The event that was fired.
|
||||
* @param {Node} button The button that triggered this action.
|
||||
* @param {Node} activity The activity node that this action will be performed on.
|
||||
* @chainable
|
||||
*/
|
||||
delete_with_confirmation: function(ev, button, activity) {
|
||||
// Prevent the default button action
|
||||
ev.preventDefault();
|
||||
|
||||
// Get the element we're working on
|
||||
var element = activity,
|
||||
// Create confirm string (different if element has or does not have name)
|
||||
confirmstring = '',
|
||||
plugindata = {
|
||||
type: M.util.get_string('pluginname', element.getAttribute('class').match(/modtype_([^\s]*)/)[1])
|
||||
};
|
||||
if (Y.Moodle.core_course.util.cm.getName(element) !== null) {
|
||||
plugindata.name = Y.Moodle.core_course.util.cm.getName(element);
|
||||
confirmstring = M.util.get_string('deletechecktypename', 'moodle', plugindata);
|
||||
} else {
|
||||
confirmstring = M.util.get_string('deletechecktype', 'moodle', plugindata);
|
||||
}
|
||||
|
||||
// Create the confirmation dialogue.
|
||||
var confirm = new M.core.confirm({
|
||||
question: confirmstring,
|
||||
modal: true,
|
||||
visible: false
|
||||
});
|
||||
confirm.show();
|
||||
|
||||
// If it is confirmed.
|
||||
confirm.on('complete-yes', function() {
|
||||
|
||||
// Actually remove the element.
|
||||
element.remove();
|
||||
var data = {
|
||||
'class': 'resource',
|
||||
'action': 'DELETE',
|
||||
'id': Y.Moodle.core_course.util.cm.getId(element)
|
||||
};
|
||||
this.send_request(data);
|
||||
if (M.core.actionmenu && M.core.actionmenu.instance) {
|
||||
M.core.actionmenu.instance.hideMenu(ev);
|
||||
}
|
||||
|
||||
}, this);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Duplicates the activity.
|
||||
*
|
||||
* @method duplicate
|
||||
* @protected
|
||||
* @param {EventFacade} ev The event that was fired.
|
||||
* @param {Node} button The button that triggered this action.
|
||||
* @param {Node} activity The activity node that this action will be performed on.
|
||||
* @chainable
|
||||
*/
|
||||
duplicate: function(ev, button, activity) {
|
||||
// Prevent the default button action
|
||||
ev.preventDefault();
|
||||
|
||||
// Get the element we're working on
|
||||
var element = activity;
|
||||
|
||||
// Add the lightbox.
|
||||
var section = activity.ancestor(M.course.format.get_section_selector(Y)),
|
||||
lightbox = M.util.add_lightbox(Y, section).show();
|
||||
|
||||
// Build and send the request.
|
||||
var data = {
|
||||
'class': 'resource',
|
||||
'field': 'duplicate',
|
||||
'id': Y.Moodle.core_course.util.cm.getId(element),
|
||||
'sr': button.getData('sr')
|
||||
};
|
||||
this.send_request(data, lightbox, function(response) {
|
||||
var newcm = Y.Node.create(response.fullcontent);
|
||||
|
||||
// Append to the section?
|
||||
activity.insert(newcm, 'after');
|
||||
Y.use('moodle-course-coursebase', function() {
|
||||
M.course.coursebase.invoke_function('setup_for_resource', newcm);
|
||||
});
|
||||
if (M.core.actionmenu && M.core.actionmenu.newDOMNode) {
|
||||
M.core.actionmenu.newDOMNode(newcm);
|
||||
}
|
||||
});
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Changes the visibility of this activity or resource.
|
||||
*
|
||||
* @method change_visibility
|
||||
* @protected
|
||||
* @param {EventFacade} ev The event that was fired.
|
||||
* @param {Node} button The button that triggered this action.
|
||||
* @param {Node} activity The activity node that this action will be performed on.
|
||||
* @param {String} action The action that has been requested.
|
||||
* @chainable
|
||||
*/
|
||||
change_visibility: function(ev, button, activity, action) {
|
||||
// Prevent the default button action
|
||||
ev.preventDefault();
|
||||
|
||||
// Get the element we're working on
|
||||
var element = activity;
|
||||
var value = this.handle_resource_dim(button, activity, action);
|
||||
|
||||
// Send the request
|
||||
var data = {
|
||||
'class': 'resource',
|
||||
'field': 'visible',
|
||||
'value': value,
|
||||
'id': Y.Moodle.core_course.util.cm.getId(element)
|
||||
};
|
||||
var spinner = this.add_spinner(element);
|
||||
this.send_request(data, spinner);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles the UI aspect of dimming the activity or resource.
|
||||
*
|
||||
* @method handle_resource_dim
|
||||
* @protected
|
||||
* @param {Node} button The button that triggered the action.
|
||||
* @param {Node} activity The activity node that this action will be performed on.
|
||||
* @param {String} action 'show' or 'hide'.
|
||||
* @return {Number} 1 if we changed to visible, 0 if we were hiding.
|
||||
*/
|
||||
handle_resource_dim: function(button, activity, action) {
|
||||
var toggleclass = CSS.DIMCLASS,
|
||||
dimarea = activity.one([
|
||||
SELECTOR.ACTIVITYLINK,
|
||||
SELECTOR.CONTENTWITHOUTLINK
|
||||
].join(', ')),
|
||||
availabilityinfo = activity.one(CSS.AVAILABILITYINFODIV),
|
||||
nextaction = (action === 'hide') ? 'show' : 'hide',
|
||||
buttontext = button.one('span'),
|
||||
newstring = M.util.get_string(nextaction, 'moodle'),
|
||||
buttonimg = button.one('img');
|
||||
|
||||
// Update button info.
|
||||
buttonimg.setAttrs({
|
||||
'src': M.util.image_url('t/' + nextaction)
|
||||
});
|
||||
|
||||
if (Y.Lang.trim(button.getAttribute('title'))) {
|
||||
button.setAttribute('title', newstring);
|
||||
}
|
||||
|
||||
if (Y.Lang.trim(buttonimg.getAttribute('alt'))) {
|
||||
buttonimg.setAttribute('alt', newstring);
|
||||
}
|
||||
|
||||
button.replaceClass('editing_' + action, 'editing_' + nextaction);
|
||||
button.setData('action', nextaction);
|
||||
if (buttontext) {
|
||||
buttontext.set('text', newstring);
|
||||
}
|
||||
|
||||
if (activity.one(SELECTOR.CONTENTWITHOUTLINK)) {
|
||||
dimarea = activity.one(SELECTOR.CONTENTWITHOUTLINK);
|
||||
toggleclass = CSS.DIMMEDTEXT;
|
||||
}
|
||||
|
||||
// If activity is conditionally hidden, then don't toggle.
|
||||
if (!dimarea.hasClass(CSS.CONDITIONALHIDDEN)) {
|
||||
if (action === 'hide') {
|
||||
// Change the UI.
|
||||
dimarea.addClass(toggleclass);
|
||||
// We need to toggle dimming on the description too.
|
||||
activity.all(SELECTOR.CONTENTAFTERLINK).addClass(CSS.DIMMEDTEXT);
|
||||
activity.all(SELECTOR.GROUPINGLABEL).addClass(CSS.DIMMEDTEXT);
|
||||
} else {
|
||||
// Change the UI.
|
||||
dimarea.removeClass(toggleclass);
|
||||
// We need to toggle dimming on the description too.
|
||||
activity.all(SELECTOR.CONTENTAFTERLINK).removeClass(CSS.DIMMEDTEXT);
|
||||
activity.all(SELECTOR.GROUPINGLABEL).removeClass(CSS.DIMMEDTEXT);
|
||||
}
|
||||
}
|
||||
// Toggle availablity info for conditional activities.
|
||||
if (availabilityinfo) {
|
||||
availabilityinfo.toggleClass(CSS.HIDE);
|
||||
}
|
||||
return (action === 'hide') ? 0 : 1;
|
||||
},
|
||||
|
||||
/**
|
||||
* Changes the groupmode of the activity to the next groupmode in the sequence.
|
||||
*
|
||||
* @method change_groupmode
|
||||
* @protected
|
||||
* @param {EventFacade} ev The event that was fired.
|
||||
* @param {Node} button The button that triggered this action.
|
||||
* @param {Node} activity The activity node that this action will be performed on.
|
||||
* @chainable
|
||||
*/
|
||||
change_groupmode: function(ev, button, activity) {
|
||||
// Prevent the default button action.
|
||||
ev.preventDefault();
|
||||
|
||||
// Current Mode
|
||||
var groupmode = parseInt(button.getData('nextgroupmode'), 10),
|
||||
newtitle = '',
|
||||
iconsrc = '',
|
||||
newtitlestr,
|
||||
data,
|
||||
spinner,
|
||||
nextgroupmode = groupmode + 1,
|
||||
buttonimg = button.one('img');
|
||||
|
||||
if (nextgroupmode > 2) {
|
||||
nextgroupmode = 0;
|
||||
}
|
||||
|
||||
if (groupmode === this.GROUPS_NONE) {
|
||||
newtitle = 'groupsnone';
|
||||
iconsrc = M.util.image_url('i/groupn', 'moodle');
|
||||
} else if (groupmode === this.GROUPS_SEPARATE) {
|
||||
newtitle = 'groupsseparate';
|
||||
iconsrc = M.util.image_url('i/groups', 'moodle');
|
||||
} else if (groupmode === this.GROUPS_VISIBLE) {
|
||||
newtitle = 'groupsvisible';
|
||||
iconsrc = M.util.image_url('i/groupv', 'moodle');
|
||||
}
|
||||
newtitlestr = M.util.get_string('clicktochangeinbrackets', 'moodle', M.util.get_string(newtitle, 'moodle'));
|
||||
|
||||
// Change the UI
|
||||
var oldAction = button.getData('action');
|
||||
button.replaceClass('editing_' + oldAction, 'editing_' + newtitle);
|
||||
buttonimg.setAttrs({
|
||||
'src': iconsrc
|
||||
});
|
||||
if (Y.Lang.trim(button.getAttribute('title'))) {
|
||||
button.setAttribute('title', newtitlestr).setData('action', newtitle).setData('nextgroupmode', nextgroupmode);
|
||||
}
|
||||
|
||||
if (Y.Lang.trim(buttonimg.getAttribute('alt'))) {
|
||||
buttonimg.setAttribute('alt', newtitlestr);
|
||||
}
|
||||
|
||||
// And send the request
|
||||
data = {
|
||||
'class': 'resource',
|
||||
'field': 'groupmode',
|
||||
'value': groupmode,
|
||||
'id': Y.Moodle.core_course.util.cm.getId(activity)
|
||||
};
|
||||
|
||||
spinner = this.add_spinner(activity);
|
||||
this.send_request(data, spinner);
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the visibility of the specified resource to match the visible parameter.
|
||||
*
|
||||
* Note: This is not a toggle function and only changes the visibility
|
||||
* in the browser (no ajax update is performed).
|
||||
*
|
||||
* @method set_visibility_resource_ui
|
||||
* @param {object} args An object containing the required information to trigger a change.
|
||||
* @param {Node} args.element The resource to toggle
|
||||
* @param {Boolean} args.visible The target visibility
|
||||
*/
|
||||
set_visibility_resource_ui: function(args) {
|
||||
var element = args.element,
|
||||
buttonnode = element.one(SELECTOR.HIDE),
|
||||
// By default we assume that the item is visible and we're going to hide it.
|
||||
currentVisibility = true,
|
||||
targetVisibility = false;
|
||||
|
||||
if (!buttonnode) {
|
||||
// If the buttonnode was not found, try to find the HIDE button
|
||||
// and change the target visibility setting to false.
|
||||
buttonnode = element.one(SELECTOR.SHOW);
|
||||
currentVisibility = false;
|
||||
targetVisibility = true;
|
||||
}
|
||||
|
||||
if (typeof args.visible !== 'undefined') {
|
||||
// If we were provided with a visibility argument, use that instead.
|
||||
targetVisibility = args.visible;
|
||||
}
|
||||
|
||||
// Only trigger a change if necessary.
|
||||
if (currentVisibility !== targetVisibility) {
|
||||
var action = 'hide';
|
||||
if (targetVisibility) {
|
||||
action = 'show';
|
||||
}
|
||||
|
||||
this.handle_resource_dim(buttonnode, element, action);
|
||||
}
|
||||
}
|
||||
}, {
|
||||
NAME: 'course-resource-toolbox',
|
||||
ATTRS: {
|
||||
}
|
||||
});
|
||||
|
||||
M.course.resource_toolbox = null;
|
||||
M.course.init_resource_toolbox = function(config) {
|
||||
M.course.resource_toolbox = new RESOURCETOOLBOX(config);
|
||||
return M.course.resource_toolbox;
|
||||
};
|
||||
/* global SELECTOR, TOOLBOX */
|
||||
|
||||
/**
|
||||
* Resource and activity toolbox class.
|
||||
*
|
||||
* This class is responsible for managing AJAX interactions with activities and resources
|
||||
* when viewing a course in editing mode.
|
||||
*
|
||||
* @module moodle-course-toolboxes
|
||||
* @namespace M.course.toolboxes
|
||||
*/
|
||||
|
||||
/**
|
||||
* Section toolbox class.
|
||||
*
|
||||
* This class is responsible for managing AJAX interactions with sections
|
||||
* when viewing a course in editing mode.
|
||||
*
|
||||
* @class section
|
||||
* @constructor
|
||||
* @extends M.course.toolboxes.toolbox
|
||||
*/
|
||||
var SECTIONTOOLBOX = function() {
|
||||
SECTIONTOOLBOX.superclass.constructor.apply(this, arguments);
|
||||
};
|
||||
|
||||
Y.extend(SECTIONTOOLBOX, TOOLBOX, {
|
||||
/**
|
||||
* Initialize the section toolboxes module.
|
||||
*
|
||||
* Updates all span.commands with relevant handlers and other required changes.
|
||||
*
|
||||
* @method initializer
|
||||
* @protected
|
||||
*/
|
||||
initializer: function() {
|
||||
M.course.coursebase.register_module(this);
|
||||
|
||||
// Section Highlighting.
|
||||
Y.delegate('click', this.toggle_highlight, SELECTOR.PAGECONTENT, SELECTOR.SECTIONLI + ' ' + SELECTOR.HIGHLIGHT, this);
|
||||
|
||||
// Section Visibility.
|
||||
Y.delegate('click', this.toggle_hide_section, SELECTOR.PAGECONTENT, SELECTOR.SECTIONLI + ' ' + SELECTOR.SHOWHIDE, this);
|
||||
},
|
||||
|
||||
toggle_hide_section: function(e) {
|
||||
// Prevent the default button action.
|
||||
e.preventDefault();
|
||||
|
||||
// Get the section we're working on.
|
||||
var section = e.target.ancestor(M.course.format.get_section_selector(Y)),
|
||||
button = e.target.ancestor('a', true),
|
||||
hideicon = button.one('img'),
|
||||
buttontext = button.one('span'),
|
||||
|
||||
// The value to submit
|
||||
value,
|
||||
|
||||
// The text for strings and images. Also determines the icon to display.
|
||||
action,
|
||||
nextaction;
|
||||
|
||||
if (!section.hasClass(CSS.SECTIONHIDDENCLASS)) {
|
||||
section.addClass(CSS.SECTIONHIDDENCLASS);
|
||||
value = 0;
|
||||
action = 'hide';
|
||||
nextaction = 'show';
|
||||
} else {
|
||||
section.removeClass(CSS.SECTIONHIDDENCLASS);
|
||||
value = 1;
|
||||
action = 'show';
|
||||
nextaction = 'hide';
|
||||
}
|
||||
|
||||
var newstring = M.util.get_string(nextaction + 'fromothers', 'format_' + this.get('format'));
|
||||
hideicon.setAttrs({
|
||||
'alt': newstring,
|
||||
'src': M.util.image_url('i/' + nextaction)
|
||||
});
|
||||
button.set('title', newstring);
|
||||
if (buttontext) {
|
||||
buttontext.set('text', newstring);
|
||||
}
|
||||
|
||||
// Change the show/hide status
|
||||
var data = {
|
||||
'class': 'section',
|
||||
'field': 'visible',
|
||||
'id': Y.Moodle.core_course.util.section.getId(section.ancestor(M.course.format.get_section_wrapper(Y), true)),
|
||||
'value': value
|
||||
};
|
||||
|
||||
var lightbox = M.util.add_lightbox(Y, section);
|
||||
lightbox.show();
|
||||
|
||||
this.send_request(data, lightbox, function(response) {
|
||||
var activities = section.all(SELECTOR.ACTIVITYLI);
|
||||
activities.each(function(node) {
|
||||
var button;
|
||||
if (node.one(SELECTOR.SHOW)) {
|
||||
button = node.one(SELECTOR.SHOW);
|
||||
} else {
|
||||
button = node.one(SELECTOR.HIDE);
|
||||
}
|
||||
var activityid = Y.Moodle.core_course.util.cm.getId(node);
|
||||
|
||||
// NOTE: resourcestotoggle is returned as a string instead
|
||||
// of a Number so we must cast our activityid to a String.
|
||||
if (Y.Array.indexOf(response.resourcestotoggle, "" + activityid) !== -1) {
|
||||
M.course.resource_toolbox.handle_resource_dim(button, node, action);
|
||||
}
|
||||
}, this);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggle highlighting the current section.
|
||||
*
|
||||
* @method toggle_highlight
|
||||
* @param {EventFacade} e
|
||||
*/
|
||||
toggle_highlight: function(e) {
|
||||
// Prevent the default button action.
|
||||
e.preventDefault();
|
||||
|
||||
// Get the section we're working on.
|
||||
var section = e.target.ancestor(M.course.format.get_section_selector(Y));
|
||||
var button = e.target.ancestor('a', true);
|
||||
var buttonicon = button.one('img');
|
||||
var buttontext = button.one('span');
|
||||
|
||||
// Determine whether the marker is currently set.
|
||||
var togglestatus = section.hasClass('current');
|
||||
var value = 0;
|
||||
|
||||
// Set the current highlighted item text.
|
||||
var old_string = M.util.get_string('markthistopic', 'moodle');
|
||||
|
||||
var selectedpage = Y.one(SELECTOR.PAGECONTENT);
|
||||
selectedpage
|
||||
.all(M.course.format.get_section_selector(Y) + '.current ' + SELECTOR.HIGHLIGHT)
|
||||
.set('title', old_string);
|
||||
selectedpage
|
||||
.all(M.course.format.get_section_selector(Y) + '.current ' + SELECTOR.HIGHLIGHT + ' span')
|
||||
.set('text', M.util.get_string('highlight', 'moodle'));
|
||||
selectedpage
|
||||
.all(M.course.format.get_section_selector(Y) + '.current ' + SELECTOR.HIGHLIGHT + ' img')
|
||||
.set('alt', old_string)
|
||||
.set('src', M.util.image_url('i/marker'));
|
||||
|
||||
// Remove the highlighting from all sections.
|
||||
selectedpage.all(M.course.format.get_section_selector(Y))
|
||||
.removeClass('current');
|
||||
|
||||
// Then add it if required to the selected section.
|
||||
if (!togglestatus) {
|
||||
section.addClass('current');
|
||||
value = Y.Moodle.core_course.util.section.getId(section.ancestor(M.course.format.get_section_wrapper(Y), true));
|
||||
var new_string = M.util.get_string('markedthistopic', 'moodle');
|
||||
button
|
||||
.set('title', new_string);
|
||||
buttonicon
|
||||
.set('alt', new_string)
|
||||
.set('src', M.util.image_url('i/marked'));
|
||||
if (buttontext) {
|
||||
buttontext
|
||||
.set('text', M.util.get_string('highlightoff', 'moodle'));
|
||||
}
|
||||
}
|
||||
|
||||
// Change the highlight status.
|
||||
var data = {
|
||||
'class': 'course',
|
||||
'field': 'marker',
|
||||
'value': value
|
||||
};
|
||||
var lightbox = M.util.add_lightbox(Y, section);
|
||||
lightbox.show();
|
||||
this.send_request(data, lightbox);
|
||||
}
|
||||
}, {
|
||||
NAME: 'course-section-toolbox',
|
||||
ATTRS: {
|
||||
}
|
||||
});
|
||||
|
||||
M.course.init_section_toolbox = function(config) {
|
||||
return new SECTIONTOOLBOX(config);
|
||||
};
|
||||
|
||||
|
||||
}, '@VERSION@', {"requires": ["node", "base", "event-key", "node", "io", "moodle-course-coursebase", "moodle-course-util"]});
|
||||
+2
-1
@@ -108,7 +108,8 @@ Y.extend(DRAGRESOURCE, M.core.dragdrop, {
|
||||
// Replace move icons
|
||||
var move = resourcesnode.one('a.' + CSS.EDITINGMOVE);
|
||||
if (move) {
|
||||
move.replace(this.resourcedraghandle.cloneNode(true));
|
||||
var sr = move.getData('sr');
|
||||
move.replace(this.resourcedraghandle.cloneNode(true).setAttribute('data-sectionreturn', sr));
|
||||
}
|
||||
}, this);
|
||||
},
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"name": "moodle-course-toolboxes",
|
||||
"builds": {
|
||||
"moodle-course-toolboxes": {
|
||||
"jsfiles": [
|
||||
"toolbox.js",
|
||||
"resource.js",
|
||||
"section.js"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
-552
@@ -1,552 +0,0 @@
|
||||
/* global TOOLBOX, BODY, SELECTOR, INDENTLIMITS */
|
||||
|
||||
/**
|
||||
* Resource and activity toolbox class.
|
||||
*
|
||||
* This class is responsible for managing AJAX interactions with activities and resources
|
||||
* when viewing a course in editing mode.
|
||||
*
|
||||
* @module moodle-course-toolboxes
|
||||
* @namespace M.course.toolboxes
|
||||
*/
|
||||
|
||||
/**
|
||||
* Resource and activity toolbox class.
|
||||
*
|
||||
* This is a class extending TOOLBOX containing code specific to resources
|
||||
*
|
||||
* This class is responsible for managing AJAX interactions with activities and resources
|
||||
* when viewing a course in editing mode.
|
||||
*
|
||||
* @class resources
|
||||
* @constructor
|
||||
* @extends M.course.toolboxes.toolbox
|
||||
*/
|
||||
var RESOURCETOOLBOX = function() {
|
||||
RESOURCETOOLBOX.superclass.constructor.apply(this, arguments);
|
||||
};
|
||||
|
||||
Y.extend(RESOURCETOOLBOX, TOOLBOX, {
|
||||
/**
|
||||
* No groups are being used.
|
||||
*
|
||||
* @property GROUPS_NONE
|
||||
* @protected
|
||||
* @type Number
|
||||
*/
|
||||
GROUPS_NONE: 0,
|
||||
|
||||
/**
|
||||
* Separate groups are being used.
|
||||
*
|
||||
* @property GROUPS_SEPARATE
|
||||
* @protected
|
||||
* @type Number
|
||||
*/
|
||||
GROUPS_SEPARATE: 1,
|
||||
|
||||
/**
|
||||
* Visible groups are being used.
|
||||
*
|
||||
* @property GROUPS_VISIBLE
|
||||
* @protected
|
||||
* @type Number
|
||||
*/
|
||||
GROUPS_VISIBLE: 2,
|
||||
|
||||
/**
|
||||
* Initialize the resource toolbox
|
||||
*
|
||||
* For each activity the commands are updated and a reference to the activity is attached.
|
||||
* This way it doesn't matter where the commands are going to called from they have a reference to the
|
||||
* activity that they relate to.
|
||||
* This is essential as some of the actions are displayed in an actionmenu which removes them from the
|
||||
* page flow.
|
||||
*
|
||||
* This function also creates a single event delegate to manage all AJAX actions for all activities on
|
||||
* the page.
|
||||
*
|
||||
* @method initializer
|
||||
* @protected
|
||||
*/
|
||||
initializer: function() {
|
||||
M.course.coursebase.register_module(this);
|
||||
BODY.delegate('key', this.handle_data_action, 'down:enter', SELECTOR.ACTIVITYACTION, this);
|
||||
Y.delegate('click', this.handle_data_action, BODY, SELECTOR.ACTIVITYACTION, this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles the delegation event. When this is fired someone has triggered an action.
|
||||
*
|
||||
* Note not all actions will result in an AJAX enhancement.
|
||||
*
|
||||
* @protected
|
||||
* @method handle_data_action
|
||||
* @param {EventFacade} ev The event that was triggered.
|
||||
* @return {boolean}
|
||||
*/
|
||||
handle_data_action: function(ev) {
|
||||
// We need to get the anchor element that triggered this event.
|
||||
var node = ev.target;
|
||||
if (!node.test('a')) {
|
||||
node = node.ancestor(SELECTOR.ACTIVITYACTION);
|
||||
}
|
||||
|
||||
// From the anchor we can get both the activity (added during initialisation) and the action being
|
||||
// performed (added by the UI as a data attribute).
|
||||
var action = node.getData('action'),
|
||||
activity = node.ancestor(SELECTOR.ACTIVITYLI);
|
||||
|
||||
if (!node.test('a') || !action || !activity) {
|
||||
// It wasn't a valid action node.
|
||||
return;
|
||||
}
|
||||
|
||||
// Switch based upon the action and do the desired thing.
|
||||
switch (action) {
|
||||
case 'moveleft':
|
||||
case 'moveright':
|
||||
// The user changing the indent of the activity.
|
||||
this.change_indent(ev, node, activity, action);
|
||||
break;
|
||||
case 'delete':
|
||||
// The user is deleting the activity.
|
||||
this.delete_with_confirmation(ev, node, activity, action);
|
||||
break;
|
||||
case 'duplicate':
|
||||
// The user is duplicating the activity.
|
||||
this.duplicate(ev, node, activity, action);
|
||||
break;
|
||||
case 'hide':
|
||||
case 'show':
|
||||
// The user is changing the visibility of the activity.
|
||||
this.change_visibility(ev, node, activity, action);
|
||||
break;
|
||||
case 'groupsseparate':
|
||||
case 'groupsvisible':
|
||||
case 'groupsnone':
|
||||
// The user is changing the group mode.
|
||||
this.change_groupmode(ev, node, activity, action);
|
||||
break;
|
||||
case 'move':
|
||||
case 'update':
|
||||
case 'assignroles':
|
||||
break;
|
||||
default:
|
||||
// Nothing to do here!
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a loading icon to the specified activity.
|
||||
*
|
||||
* The icon is added within the action area.
|
||||
*
|
||||
* @method add_spinner
|
||||
* @param {Node} activity The activity to add a loading icon to
|
||||
* @return {Node|null} The newly created icon, or null if the action area was not found.
|
||||
*/
|
||||
add_spinner: function(activity) {
|
||||
var actionarea = activity.one(SELECTOR.ACTIONAREA);
|
||||
if (actionarea) {
|
||||
return M.util.add_spinner(Y, actionarea);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Change the indent of the activity or resource.
|
||||
*
|
||||
* @method change_indent
|
||||
* @protected
|
||||
* @param {EventFacade} ev The event that was fired.
|
||||
* @param {Node} button The button that triggered this action.
|
||||
* @param {Node} activity The activity node that this action will be performed on.
|
||||
* @param {String} action The action that has been requested. Will be 'moveleft' or 'moveright'.
|
||||
*/
|
||||
change_indent: function(ev, button, activity, action) {
|
||||
// Prevent the default button action
|
||||
ev.preventDefault();
|
||||
|
||||
var direction = (action === 'moveleft') ? -1 : 1;
|
||||
|
||||
// And we need to determine the current and new indent level
|
||||
var indentdiv = activity.one(SELECTOR.MODINDENTDIV),
|
||||
indent = indentdiv.getAttribute('class').match(/mod-indent-(\d{1,})/),
|
||||
oldindent = 0,
|
||||
newindent;
|
||||
|
||||
if (indent) {
|
||||
oldindent = parseInt(indent[1], 10);
|
||||
}
|
||||
newindent = oldindent + parseInt(direction, 10);
|
||||
|
||||
if (newindent < INDENTLIMITS.MIN || newindent > INDENTLIMITS.MAX) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (indent) {
|
||||
indentdiv.removeClass(indent[0]);
|
||||
}
|
||||
|
||||
// Perform the move
|
||||
indentdiv.addClass(CSS.MODINDENTCOUNT + newindent);
|
||||
var data = {
|
||||
'class': 'resource',
|
||||
'field': 'indent',
|
||||
'value': newindent,
|
||||
'id': Y.Moodle.core_course.util.cm.getId(activity)
|
||||
};
|
||||
var spinner = this.add_spinner(activity);
|
||||
this.send_request(data, spinner);
|
||||
|
||||
var remainingmove;
|
||||
|
||||
// Handle removal/addition of the moveleft button.
|
||||
if (newindent === INDENTLIMITS.MIN) {
|
||||
button.addClass('hidden');
|
||||
remainingmove = activity.one('.editing_moveright');
|
||||
} else if (newindent > INDENTLIMITS.MIN && oldindent === INDENTLIMITS.MIN) {
|
||||
button.ancestor('.menu').one('[data-action=moveleft]').removeClass('hidden');
|
||||
}
|
||||
|
||||
if (newindent === INDENTLIMITS.MAX) {
|
||||
button.addClass('hidden');
|
||||
remainingmove = activity.one('.editing_moveleft');
|
||||
} else if (newindent < INDENTLIMITS.MAX && oldindent === INDENTLIMITS.MAX) {
|
||||
button.ancestor('.menu').one('[data-action=moveright]').removeClass('hidden');
|
||||
}
|
||||
|
||||
// Handle massive indentation to match non-ajax display
|
||||
var hashugeclass = indentdiv.hasClass(CSS.MODINDENTHUGE);
|
||||
if (newindent > 15 && !hashugeclass) {
|
||||
indentdiv.addClass(CSS.MODINDENTHUGE);
|
||||
} else if (newindent <= 15 && hashugeclass) {
|
||||
indentdiv.removeClass(CSS.MODINDENTHUGE);
|
||||
}
|
||||
|
||||
if (ev.type && ev.type === "key" && remainingmove) {
|
||||
remainingmove.focus();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Deletes the given activity or resource after confirmation.
|
||||
*
|
||||
* @protected
|
||||
* @method delete_with_confirmation
|
||||
* @param {EventFacade} ev The event that was fired.
|
||||
* @param {Node} button The button that triggered this action.
|
||||
* @param {Node} activity The activity node that this action will be performed on.
|
||||
* @chainable
|
||||
*/
|
||||
delete_with_confirmation: function(ev, button, activity) {
|
||||
// Prevent the default button action
|
||||
ev.preventDefault();
|
||||
|
||||
// Get the element we're working on
|
||||
var element = activity,
|
||||
// Create confirm string (different if element has or does not have name)
|
||||
confirmstring = '',
|
||||
plugindata = {
|
||||
type: M.util.get_string('pluginname', element.getAttribute('class').match(/modtype_([^\s]*)/)[1])
|
||||
};
|
||||
if (Y.Moodle.core_course.util.cm.getName(element) !== null) {
|
||||
plugindata.name = Y.Moodle.core_course.util.cm.getName(element);
|
||||
confirmstring = M.util.get_string('deletechecktypename', 'moodle', plugindata);
|
||||
} else {
|
||||
confirmstring = M.util.get_string('deletechecktype', 'moodle', plugindata);
|
||||
}
|
||||
|
||||
// Create the confirmation dialogue.
|
||||
var confirm = new M.core.confirm({
|
||||
question: confirmstring,
|
||||
modal: true,
|
||||
visible: false
|
||||
});
|
||||
confirm.show();
|
||||
|
||||
// If it is confirmed.
|
||||
confirm.on('complete-yes', function() {
|
||||
|
||||
// Actually remove the element.
|
||||
element.remove();
|
||||
var data = {
|
||||
'class': 'resource',
|
||||
'action': 'DELETE',
|
||||
'id': Y.Moodle.core_course.util.cm.getId(element)
|
||||
};
|
||||
this.send_request(data);
|
||||
if (M.core.actionmenu && M.core.actionmenu.instance) {
|
||||
M.core.actionmenu.instance.hideMenu(ev);
|
||||
}
|
||||
|
||||
}, this);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Duplicates the activity.
|
||||
*
|
||||
* @method duplicate
|
||||
* @protected
|
||||
* @param {EventFacade} ev The event that was fired.
|
||||
* @param {Node} button The button that triggered this action.
|
||||
* @param {Node} activity The activity node that this action will be performed on.
|
||||
* @chainable
|
||||
*/
|
||||
duplicate: function(ev, button, activity) {
|
||||
// Prevent the default button action
|
||||
ev.preventDefault();
|
||||
|
||||
// Get the element we're working on
|
||||
var element = activity;
|
||||
|
||||
// Add the lightbox.
|
||||
var section = activity.ancestor(M.course.format.get_section_selector(Y)),
|
||||
lightbox = M.util.add_lightbox(Y, section).show();
|
||||
|
||||
// Build and send the request.
|
||||
var data = {
|
||||
'class': 'resource',
|
||||
'field': 'duplicate',
|
||||
'id': Y.Moodle.core_course.util.cm.getId(element),
|
||||
'sr': button.getData('sr')
|
||||
};
|
||||
this.send_request(data, lightbox, function(response) {
|
||||
var newcm = Y.Node.create(response.fullcontent);
|
||||
|
||||
// Append to the section?
|
||||
activity.insert(newcm, 'after');
|
||||
Y.use('moodle-course-coursebase', function() {
|
||||
M.course.coursebase.invoke_function('setup_for_resource', newcm);
|
||||
});
|
||||
if (M.core.actionmenu && M.core.actionmenu.newDOMNode) {
|
||||
M.core.actionmenu.newDOMNode(newcm);
|
||||
}
|
||||
});
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Changes the visibility of this activity or resource.
|
||||
*
|
||||
* @method change_visibility
|
||||
* @protected
|
||||
* @param {EventFacade} ev The event that was fired.
|
||||
* @param {Node} button The button that triggered this action.
|
||||
* @param {Node} activity The activity node that this action will be performed on.
|
||||
* @param {String} action The action that has been requested.
|
||||
* @chainable
|
||||
*/
|
||||
change_visibility: function(ev, button, activity, action) {
|
||||
// Prevent the default button action
|
||||
ev.preventDefault();
|
||||
|
||||
// Get the element we're working on
|
||||
var element = activity;
|
||||
var value = this.handle_resource_dim(button, activity, action);
|
||||
|
||||
// Send the request
|
||||
var data = {
|
||||
'class': 'resource',
|
||||
'field': 'visible',
|
||||
'value': value,
|
||||
'id': Y.Moodle.core_course.util.cm.getId(element)
|
||||
};
|
||||
var spinner = this.add_spinner(element);
|
||||
this.send_request(data, spinner);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles the UI aspect of dimming the activity or resource.
|
||||
*
|
||||
* @method handle_resource_dim
|
||||
* @protected
|
||||
* @param {Node} button The button that triggered the action.
|
||||
* @param {Node} activity The activity node that this action will be performed on.
|
||||
* @param {String} action 'show' or 'hide'.
|
||||
* @return {Number} 1 if we changed to visible, 0 if we were hiding.
|
||||
*/
|
||||
handle_resource_dim: function(button, activity, action) {
|
||||
var toggleclass = CSS.DIMCLASS,
|
||||
dimarea = activity.one([
|
||||
SELECTOR.ACTIVITYLINK,
|
||||
SELECTOR.CONTENTWITHOUTLINK
|
||||
].join(', ')),
|
||||
availabilityinfo = activity.one(CSS.AVAILABILITYINFODIV),
|
||||
nextaction = (action === 'hide') ? 'show' : 'hide',
|
||||
buttontext = button.one('span'),
|
||||
newstring = M.util.get_string(nextaction, 'moodle'),
|
||||
buttonimg = button.one('img');
|
||||
|
||||
// Update button info.
|
||||
buttonimg.setAttrs({
|
||||
'src': M.util.image_url('t/' + nextaction)
|
||||
});
|
||||
|
||||
if (Y.Lang.trim(button.getAttribute('title'))) {
|
||||
button.setAttribute('title', newstring);
|
||||
}
|
||||
|
||||
if (Y.Lang.trim(buttonimg.getAttribute('alt'))) {
|
||||
buttonimg.setAttribute('alt', newstring);
|
||||
}
|
||||
|
||||
button.replaceClass('editing_' + action, 'editing_' + nextaction);
|
||||
button.setData('action', nextaction);
|
||||
if (buttontext) {
|
||||
buttontext.set('text', newstring);
|
||||
}
|
||||
|
||||
if (activity.one(SELECTOR.CONTENTWITHOUTLINK)) {
|
||||
dimarea = activity.one(SELECTOR.CONTENTWITHOUTLINK);
|
||||
toggleclass = CSS.DIMMEDTEXT;
|
||||
}
|
||||
|
||||
// If activity is conditionally hidden, then don't toggle.
|
||||
if (!dimarea.hasClass(CSS.CONDITIONALHIDDEN)) {
|
||||
if (action === 'hide') {
|
||||
// Change the UI.
|
||||
dimarea.addClass(toggleclass);
|
||||
// We need to toggle dimming on the description too.
|
||||
activity.all(SELECTOR.CONTENTAFTERLINK).addClass(CSS.DIMMEDTEXT);
|
||||
activity.all(SELECTOR.GROUPINGLABEL).addClass(CSS.DIMMEDTEXT);
|
||||
} else {
|
||||
// Change the UI.
|
||||
dimarea.removeClass(toggleclass);
|
||||
// We need to toggle dimming on the description too.
|
||||
activity.all(SELECTOR.CONTENTAFTERLINK).removeClass(CSS.DIMMEDTEXT);
|
||||
activity.all(SELECTOR.GROUPINGLABEL).removeClass(CSS.DIMMEDTEXT);
|
||||
}
|
||||
}
|
||||
// Toggle availablity info for conditional activities.
|
||||
if (availabilityinfo) {
|
||||
availabilityinfo.toggleClass(CSS.HIDE);
|
||||
}
|
||||
return (action === 'hide') ? 0 : 1;
|
||||
},
|
||||
|
||||
/**
|
||||
* Changes the groupmode of the activity to the next groupmode in the sequence.
|
||||
*
|
||||
* @method change_groupmode
|
||||
* @protected
|
||||
* @param {EventFacade} ev The event that was fired.
|
||||
* @param {Node} button The button that triggered this action.
|
||||
* @param {Node} activity The activity node that this action will be performed on.
|
||||
* @chainable
|
||||
*/
|
||||
change_groupmode: function(ev, button, activity) {
|
||||
// Prevent the default button action.
|
||||
ev.preventDefault();
|
||||
|
||||
// Current Mode
|
||||
var groupmode = parseInt(button.getData('nextgroupmode'), 10),
|
||||
newtitle = '',
|
||||
iconsrc = '',
|
||||
newtitlestr,
|
||||
data,
|
||||
spinner,
|
||||
nextgroupmode = groupmode + 1,
|
||||
buttonimg = button.one('img');
|
||||
|
||||
if (nextgroupmode > 2) {
|
||||
nextgroupmode = 0;
|
||||
}
|
||||
|
||||
if (groupmode === this.GROUPS_NONE) {
|
||||
newtitle = 'groupsnone';
|
||||
iconsrc = M.util.image_url('i/groupn', 'moodle');
|
||||
} else if (groupmode === this.GROUPS_SEPARATE) {
|
||||
newtitle = 'groupsseparate';
|
||||
iconsrc = M.util.image_url('i/groups', 'moodle');
|
||||
} else if (groupmode === this.GROUPS_VISIBLE) {
|
||||
newtitle = 'groupsvisible';
|
||||
iconsrc = M.util.image_url('i/groupv', 'moodle');
|
||||
}
|
||||
newtitlestr = M.util.get_string('clicktochangeinbrackets', 'moodle', M.util.get_string(newtitle, 'moodle'));
|
||||
|
||||
// Change the UI
|
||||
var oldAction = button.getData('action');
|
||||
button.replaceClass('editing_' + oldAction, 'editing_' + newtitle);
|
||||
buttonimg.setAttrs({
|
||||
'src': iconsrc
|
||||
});
|
||||
if (Y.Lang.trim(button.getAttribute('title'))) {
|
||||
button.setAttribute('title', newtitlestr).setData('action', newtitle).setData('nextgroupmode', nextgroupmode);
|
||||
}
|
||||
|
||||
if (Y.Lang.trim(buttonimg.getAttribute('alt'))) {
|
||||
buttonimg.setAttribute('alt', newtitlestr);
|
||||
}
|
||||
|
||||
// And send the request
|
||||
data = {
|
||||
'class': 'resource',
|
||||
'field': 'groupmode',
|
||||
'value': groupmode,
|
||||
'id': Y.Moodle.core_course.util.cm.getId(activity)
|
||||
};
|
||||
|
||||
spinner = this.add_spinner(activity);
|
||||
this.send_request(data, spinner);
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the visibility of the specified resource to match the visible parameter.
|
||||
*
|
||||
* Note: This is not a toggle function and only changes the visibility
|
||||
* in the browser (no ajax update is performed).
|
||||
*
|
||||
* @method set_visibility_resource_ui
|
||||
* @param {object} args An object containing the required information to trigger a change.
|
||||
* @param {Node} args.element The resource to toggle
|
||||
* @param {Boolean} args.visible The target visibility
|
||||
*/
|
||||
set_visibility_resource_ui: function(args) {
|
||||
var element = args.element,
|
||||
buttonnode = element.one(SELECTOR.HIDE),
|
||||
// By default we assume that the item is visible and we're going to hide it.
|
||||
currentVisibility = true,
|
||||
targetVisibility = false;
|
||||
|
||||
if (!buttonnode) {
|
||||
// If the buttonnode was not found, try to find the HIDE button
|
||||
// and change the target visibility setting to false.
|
||||
buttonnode = element.one(SELECTOR.SHOW);
|
||||
currentVisibility = false;
|
||||
targetVisibility = true;
|
||||
}
|
||||
|
||||
if (typeof args.visible !== 'undefined') {
|
||||
// If we were provided with a visibility argument, use that instead.
|
||||
targetVisibility = args.visible;
|
||||
}
|
||||
|
||||
// Only trigger a change if necessary.
|
||||
if (currentVisibility !== targetVisibility) {
|
||||
var action = 'hide';
|
||||
if (targetVisibility) {
|
||||
action = 'show';
|
||||
}
|
||||
|
||||
this.handle_resource_dim(buttonnode, element, action);
|
||||
}
|
||||
}
|
||||
}, {
|
||||
NAME: 'course-resource-toolbox',
|
||||
ATTRS: {
|
||||
}
|
||||
});
|
||||
|
||||
M.course.resource_toolbox = null;
|
||||
M.course.init_resource_toolbox = function(config) {
|
||||
M.course.resource_toolbox = new RESOURCETOOLBOX(config);
|
||||
return M.course.resource_toolbox;
|
||||
};
|
||||
-189
@@ -1,189 +0,0 @@
|
||||
/* global SELECTOR, TOOLBOX */
|
||||
|
||||
/**
|
||||
* Resource and activity toolbox class.
|
||||
*
|
||||
* This class is responsible for managing AJAX interactions with activities and resources
|
||||
* when viewing a course in editing mode.
|
||||
*
|
||||
* @module moodle-course-toolboxes
|
||||
* @namespace M.course.toolboxes
|
||||
*/
|
||||
|
||||
/**
|
||||
* Section toolbox class.
|
||||
*
|
||||
* This class is responsible for managing AJAX interactions with sections
|
||||
* when viewing a course in editing mode.
|
||||
*
|
||||
* @class section
|
||||
* @constructor
|
||||
* @extends M.course.toolboxes.toolbox
|
||||
*/
|
||||
var SECTIONTOOLBOX = function() {
|
||||
SECTIONTOOLBOX.superclass.constructor.apply(this, arguments);
|
||||
};
|
||||
|
||||
Y.extend(SECTIONTOOLBOX, TOOLBOX, {
|
||||
/**
|
||||
* Initialize the section toolboxes module.
|
||||
*
|
||||
* Updates all span.commands with relevant handlers and other required changes.
|
||||
*
|
||||
* @method initializer
|
||||
* @protected
|
||||
*/
|
||||
initializer: function() {
|
||||
M.course.coursebase.register_module(this);
|
||||
|
||||
// Section Highlighting.
|
||||
Y.delegate('click', this.toggle_highlight, SELECTOR.PAGECONTENT, SELECTOR.SECTIONLI + ' ' + SELECTOR.HIGHLIGHT, this);
|
||||
|
||||
// Section Visibility.
|
||||
Y.delegate('click', this.toggle_hide_section, SELECTOR.PAGECONTENT, SELECTOR.SECTIONLI + ' ' + SELECTOR.SHOWHIDE, this);
|
||||
},
|
||||
|
||||
toggle_hide_section: function(e) {
|
||||
// Prevent the default button action.
|
||||
e.preventDefault();
|
||||
|
||||
// Get the section we're working on.
|
||||
var section = e.target.ancestor(M.course.format.get_section_selector(Y)),
|
||||
button = e.target.ancestor('a', true),
|
||||
hideicon = button.one('img'),
|
||||
buttontext = button.one('span'),
|
||||
|
||||
// The value to submit
|
||||
value,
|
||||
|
||||
// The text for strings and images. Also determines the icon to display.
|
||||
action,
|
||||
nextaction;
|
||||
|
||||
if (!section.hasClass(CSS.SECTIONHIDDENCLASS)) {
|
||||
section.addClass(CSS.SECTIONHIDDENCLASS);
|
||||
value = 0;
|
||||
action = 'hide';
|
||||
nextaction = 'show';
|
||||
} else {
|
||||
section.removeClass(CSS.SECTIONHIDDENCLASS);
|
||||
value = 1;
|
||||
action = 'show';
|
||||
nextaction = 'hide';
|
||||
}
|
||||
|
||||
var newstring = M.util.get_string(nextaction + 'fromothers', 'format_' + this.get('format'));
|
||||
hideicon.setAttrs({
|
||||
'alt': newstring,
|
||||
'src': M.util.image_url('i/' + nextaction)
|
||||
});
|
||||
button.set('title', newstring);
|
||||
if (buttontext) {
|
||||
buttontext.set('text', newstring);
|
||||
}
|
||||
|
||||
// Change the show/hide status
|
||||
var data = {
|
||||
'class': 'section',
|
||||
'field': 'visible',
|
||||
'id': Y.Moodle.core_course.util.section.getId(section.ancestor(M.course.format.get_section_wrapper(Y), true)),
|
||||
'value': value
|
||||
};
|
||||
|
||||
var lightbox = M.util.add_lightbox(Y, section);
|
||||
lightbox.show();
|
||||
|
||||
this.send_request(data, lightbox, function(response) {
|
||||
var activities = section.all(SELECTOR.ACTIVITYLI);
|
||||
activities.each(function(node) {
|
||||
var button;
|
||||
if (node.one(SELECTOR.SHOW)) {
|
||||
button = node.one(SELECTOR.SHOW);
|
||||
} else {
|
||||
button = node.one(SELECTOR.HIDE);
|
||||
}
|
||||
var activityid = Y.Moodle.core_course.util.cm.getId(node);
|
||||
|
||||
// NOTE: resourcestotoggle is returned as a string instead
|
||||
// of a Number so we must cast our activityid to a String.
|
||||
if (Y.Array.indexOf(response.resourcestotoggle, "" + activityid) !== -1) {
|
||||
M.course.resource_toolbox.handle_resource_dim(button, node, action);
|
||||
}
|
||||
}, this);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggle highlighting the current section.
|
||||
*
|
||||
* @method toggle_highlight
|
||||
* @param {EventFacade} e
|
||||
*/
|
||||
toggle_highlight: function(e) {
|
||||
// Prevent the default button action.
|
||||
e.preventDefault();
|
||||
|
||||
// Get the section we're working on.
|
||||
var section = e.target.ancestor(M.course.format.get_section_selector(Y));
|
||||
var button = e.target.ancestor('a', true);
|
||||
var buttonicon = button.one('img');
|
||||
var buttontext = button.one('span');
|
||||
|
||||
// Determine whether the marker is currently set.
|
||||
var togglestatus = section.hasClass('current');
|
||||
var value = 0;
|
||||
|
||||
// Set the current highlighted item text.
|
||||
var old_string = M.util.get_string('markthistopic', 'moodle');
|
||||
|
||||
var selectedpage = Y.one(SELECTOR.PAGECONTENT);
|
||||
selectedpage
|
||||
.all(M.course.format.get_section_selector(Y) + '.current ' + SELECTOR.HIGHLIGHT)
|
||||
.set('title', old_string);
|
||||
selectedpage
|
||||
.all(M.course.format.get_section_selector(Y) + '.current ' + SELECTOR.HIGHLIGHT + ' span')
|
||||
.set('text', M.util.get_string('highlight', 'moodle'));
|
||||
selectedpage
|
||||
.all(M.course.format.get_section_selector(Y) + '.current ' + SELECTOR.HIGHLIGHT + ' img')
|
||||
.set('alt', old_string)
|
||||
.set('src', M.util.image_url('i/marker'));
|
||||
|
||||
// Remove the highlighting from all sections.
|
||||
selectedpage.all(M.course.format.get_section_selector(Y))
|
||||
.removeClass('current');
|
||||
|
||||
// Then add it if required to the selected section.
|
||||
if (!togglestatus) {
|
||||
section.addClass('current');
|
||||
value = Y.Moodle.core_course.util.section.getId(section.ancestor(M.course.format.get_section_wrapper(Y), true));
|
||||
var new_string = M.util.get_string('markedthistopic', 'moodle');
|
||||
button
|
||||
.set('title', new_string);
|
||||
buttonicon
|
||||
.set('alt', new_string)
|
||||
.set('src', M.util.image_url('i/marked'));
|
||||
if (buttontext) {
|
||||
buttontext
|
||||
.set('text', M.util.get_string('highlightoff', 'moodle'));
|
||||
}
|
||||
}
|
||||
|
||||
// Change the highlight status.
|
||||
var data = {
|
||||
'class': 'course',
|
||||
'field': 'marker',
|
||||
'value': value
|
||||
};
|
||||
var lightbox = M.util.add_lightbox(Y, section);
|
||||
lightbox.show();
|
||||
this.send_request(data, lightbox);
|
||||
}
|
||||
}, {
|
||||
NAME: 'course-section-toolbox',
|
||||
ATTRS: {
|
||||
}
|
||||
});
|
||||
|
||||
M.course.init_section_toolbox = function(config) {
|
||||
return new SECTIONTOOLBOX(config);
|
||||
};
|
||||
-201
@@ -1,201 +0,0 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
/**
|
||||
* Resource and activity toolbox class.
|
||||
*
|
||||
* This class is responsible for managing AJAX interactions with activities and resources
|
||||
* when viewing a course in editing mode.
|
||||
*
|
||||
* @module moodle-course-toolboxes
|
||||
* @namespace M.course.toolboxes
|
||||
*/
|
||||
|
||||
// The CSS classes we use.
|
||||
var CSS = {
|
||||
ACTIVITYINSTANCE: 'activityinstance',
|
||||
AVAILABILITYINFODIV: 'div.availabilityinfo',
|
||||
CONTENTWITHOUTLINK: 'contentwithoutlink',
|
||||
CONDITIONALHIDDEN: 'conditionalhidden',
|
||||
DIMCLASS: 'dimmed',
|
||||
DIMMEDTEXT: 'dimmed_text',
|
||||
EDITINSTRUCTIONS: 'editinstructions',
|
||||
HIDE: 'hide',
|
||||
MODINDENTCOUNT: 'mod-indent-',
|
||||
MODINDENTHUGE: 'mod-indent-huge',
|
||||
MODULEIDPREFIX: 'module-',
|
||||
SECTIONHIDDENCLASS: 'hidden',
|
||||
SECTIONIDPREFIX: 'section-',
|
||||
SHOW: 'editing_show'
|
||||
},
|
||||
// The CSS selectors we use.
|
||||
SELECTOR = {
|
||||
ACTIONAREA: '.actions',
|
||||
ACTIONLINKTEXT: '.actionlinktext',
|
||||
ACTIVITYACTION: 'a.cm-edit-action[data-action]',
|
||||
ACTIVITYICON: 'img.activityicon',
|
||||
ACTIVITYINSTANCE: '.' + CSS.ACTIVITYINSTANCE,
|
||||
ACTIVITYLINK: '.' + CSS.ACTIVITYINSTANCE + ' > a, .' + CSS.ACTIVITYINSTANCE +
|
||||
' > span[data-inplaceeditable] > a:not([data-inplaceeditablelink])',
|
||||
ACTIVITYLI: 'li.activity',
|
||||
COMMANDSPAN: '.commands',
|
||||
CONTENTAFTERLINK: 'div.contentafterlink',
|
||||
CONTENTWITHOUTLINK: 'div.contentwithoutlink',
|
||||
GROUPINGLABEL: '.' + CSS.ACTIVITYINSTANCE + ' .groupinglabel',
|
||||
HIDE: 'a.editing_hide',
|
||||
HIGHLIGHT: 'a.editing_highlight',
|
||||
INSTANCENAME: 'span.instancename',
|
||||
MODINDENTDIV: '.mod-indent',
|
||||
MODINDENTOUTER: '.mod-indent-outer',
|
||||
PAGECONTENT: 'body',
|
||||
SECTIONLI: 'li.section',
|
||||
SHOW: 'a.' + CSS.SHOW,
|
||||
SHOWHIDE: 'a.editing_showhide'
|
||||
},
|
||||
INDENTLIMITS = {
|
||||
MIN: 0,
|
||||
MAX: 16
|
||||
},
|
||||
BODY = Y.one(document.body);
|
||||
|
||||
// Setup the basic namespace.
|
||||
M.course = M.course || {};
|
||||
|
||||
/**
|
||||
* The toolbox class is a generic class which should never be directly
|
||||
* instantiated. Please extend it instead.
|
||||
*
|
||||
* @class toolbox
|
||||
* @constructor
|
||||
* @protected
|
||||
* @extends Base
|
||||
*/
|
||||
var TOOLBOX = function() {
|
||||
TOOLBOX.superclass.constructor.apply(this, arguments);
|
||||
};
|
||||
|
||||
Y.extend(TOOLBOX, Y.Base, {
|
||||
/**
|
||||
* Send a request using the REST API
|
||||
*
|
||||
* @method send_request
|
||||
* @param {Object} data The data to submit with the AJAX request
|
||||
* @param {Node} [statusspinner] A statusspinner which may contain a section loader
|
||||
* @param {Function} success_callback The callback to use on success
|
||||
* @param {Object} [optionalconfig] Any additional configuration to submit
|
||||
* @chainable
|
||||
*/
|
||||
send_request: function(data, statusspinner, success_callback, optionalconfig) {
|
||||
// Default data structure
|
||||
if (!data) {
|
||||
data = {};
|
||||
}
|
||||
// Handle any variables which we must pass back through to
|
||||
var pageparams = this.get('config').pageparams,
|
||||
varname;
|
||||
for (varname in pageparams) {
|
||||
data[varname] = pageparams[varname];
|
||||
}
|
||||
|
||||
data.sesskey = M.cfg.sesskey;
|
||||
data.courseId = this.get('courseid');
|
||||
|
||||
var uri = M.cfg.wwwroot + this.get('ajaxurl');
|
||||
|
||||
// Define the configuration to send with the request
|
||||
var responsetext = [];
|
||||
var config = {
|
||||
method: 'POST',
|
||||
data: data,
|
||||
on: {
|
||||
success: function(tid, response) {
|
||||
try {
|
||||
responsetext = Y.JSON.parse(response.responseText);
|
||||
if (responsetext.error) {
|
||||
new M.core.ajaxException(responsetext);
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignore.
|
||||
}
|
||||
|
||||
// Run the callback if we have one.
|
||||
if (success_callback) {
|
||||
Y.bind(success_callback, this, responsetext)();
|
||||
}
|
||||
|
||||
if (statusspinner) {
|
||||
window.setTimeout(function() {
|
||||
statusspinner.hide();
|
||||
}, 400);
|
||||
}
|
||||
},
|
||||
failure: function(tid, response) {
|
||||
if (statusspinner) {
|
||||
statusspinner.hide();
|
||||
}
|
||||
new M.core.ajaxException(response);
|
||||
}
|
||||
},
|
||||
context: this
|
||||
};
|
||||
|
||||
// Apply optional config
|
||||
if (optionalconfig) {
|
||||
for (varname in optionalconfig) {
|
||||
config[varname] = optionalconfig[varname];
|
||||
}
|
||||
}
|
||||
|
||||
if (statusspinner) {
|
||||
statusspinner.show();
|
||||
}
|
||||
|
||||
// Send the request
|
||||
Y.io(uri, config);
|
||||
return this;
|
||||
}
|
||||
},
|
||||
{
|
||||
NAME: 'course-toolbox',
|
||||
ATTRS: {
|
||||
/**
|
||||
* The ID of the Moodle Course being edited.
|
||||
*
|
||||
* @attribute courseid
|
||||
* @default 0
|
||||
* @type Number
|
||||
*/
|
||||
courseid: {
|
||||
'value': 0
|
||||
},
|
||||
|
||||
/**
|
||||
* The Moodle course format.
|
||||
*
|
||||
* @attribute format
|
||||
* @default 'topics'
|
||||
* @type String
|
||||
*/
|
||||
format: {
|
||||
'value': 'topics'
|
||||
},
|
||||
/**
|
||||
* The URL to use when submitting requests.
|
||||
* @attribute ajaxurl
|
||||
* @default null
|
||||
* @type String
|
||||
*/
|
||||
ajaxurl: {
|
||||
'value': null
|
||||
},
|
||||
/**
|
||||
* Any additional configuration passed when creating the instance.
|
||||
*
|
||||
* @attribute config
|
||||
* @default {}
|
||||
* @type Object
|
||||
*/
|
||||
config: {
|
||||
'value': {}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"moodle-course-toolboxes": {
|
||||
"requires": [
|
||||
"node",
|
||||
"base",
|
||||
"event-key",
|
||||
"node",
|
||||
"io",
|
||||
"moodle-course-coursebase",
|
||||
"moodle-course-util"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -133,6 +133,8 @@ $string['alllogs'] = 'All logs';
|
||||
$string['allmods'] = 'All {$a}';
|
||||
$string['allow'] = 'Allow';
|
||||
$string['allowinternal'] = 'Allow internal methods as well';
|
||||
$string['allowstealthmodules'] = 'Allow hidden but available activities';
|
||||
$string['allowstealthmodules_help'] = 'If enabled, activities can be marked as hidden on the course page but available by following a link from elsewhere. These activities are listed in the gradebook and other reports';
|
||||
$string['allownone'] = 'Allow none';
|
||||
$string['allownot'] = 'Do not allow';
|
||||
$string['allparticipants'] = 'All participants';
|
||||
@@ -918,6 +920,7 @@ $string['helpprefix2'] = 'Help with {$a}';
|
||||
$string['helpwiththis'] = 'Help with this';
|
||||
$string['hiddenassign'] = 'Hidden assignment';
|
||||
$string['hiddenfromstudents'] = 'Hidden from students';
|
||||
$string['hiddenoncoursepage'] = 'Available but not displayed on course page';
|
||||
$string['hiddensections'] = 'Hidden sections';
|
||||
$string['hiddensections_help'] = 'This setting determines whether hidden sections are displayed to students in collapsed form (perhaps for a course in weekly format to indicate holidays) or are completely hidden.';
|
||||
$string['hiddensectionscollapsed'] = 'Hidden sections are shown in collapsed form';
|
||||
@@ -925,6 +928,7 @@ $string['hiddensectionsinvisible'] = 'Hidden sections are completely invisible';
|
||||
$string['hide'] = 'Hide';
|
||||
$string['hideadvancedsettings'] = 'Hide advanced settings';
|
||||
$string['hidechartdata'] = 'Hide chart data';
|
||||
$string['hideonfrontpage'] = 'Hide from front page';
|
||||
$string['hidepicture'] = 'Hide picture';
|
||||
$string['hidesection'] = 'Hide section {$a}';
|
||||
$string['hidesettings'] = 'Hide settings';
|
||||
@@ -1084,8 +1088,10 @@ $string['mailstudents'] = 'Inform students';
|
||||
$string['mailteachers'] = 'Inform teachers';
|
||||
$string['maincoursepage'] = 'Main course page';
|
||||
$string['makeafolder'] = 'Create folder';
|
||||
$string['makeavailable'] = 'Make available';
|
||||
$string['makeeditable'] = 'If you make \'{$a}\' editable by the web server process (eg apache) then you could edit this file directly from this page';
|
||||
$string['makethismyhome'] = 'Make this my default home page';
|
||||
$string['makeunavailable'] = 'Make unavailable';
|
||||
$string['manageblocks'] = 'Blocks';
|
||||
$string['managecategorythis'] = 'Manage this category';
|
||||
$string['managecourses'] = 'Manage courses';
|
||||
@@ -1640,6 +1646,7 @@ $string['secondstotime86400'] = '1 day';
|
||||
$string['secretalreadyused'] = 'Change password confirmation link was already used, password was not changed.';
|
||||
$string['secs'] = 'secs';
|
||||
$string['section'] = 'Section';
|
||||
$string['sectionactionnotsupported'] = 'Section action "{$a}" is not supported here';
|
||||
$string['sectionname'] = 'Section name';
|
||||
$string['sections'] = 'Sections';
|
||||
$string['sectionusedefaultname'] = 'Use default section name';
|
||||
|
||||
+2
-1
@@ -291,6 +291,7 @@
|
||||
<FIELD NAME="score" TYPE="int" LENGTH="4" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
<FIELD NAME="indent" TYPE="int" LENGTH="5" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
<FIELD NAME="visible" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="1" SEQUENCE="false"/>
|
||||
<FIELD NAME="visibleoncoursepage" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="1" SEQUENCE="false" COMMENT="If stealth visibility is allowed for the course, this controls whether activity is visible on course page"/>
|
||||
<FIELD NAME="visibleold" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="1" SEQUENCE="false"/>
|
||||
<FIELD NAME="groupmode" TYPE="int" LENGTH="4" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
<FIELD NAME="groupingid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
@@ -3442,4 +3443,4 @@
|
||||
</INDEXES>
|
||||
</TABLE>
|
||||
</TABLES>
|
||||
</XMLDB>
|
||||
</XMLDB>
|
||||
|
||||
@@ -250,6 +250,30 @@ $functions = array(
|
||||
'type' => 'read',
|
||||
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
|
||||
),
|
||||
'core_course_get_module' => array(
|
||||
'classname' => 'core_course_external',
|
||||
'methodname' => 'get_module',
|
||||
'classpath' => 'course/externallib.php',
|
||||
'description' => 'Returns html with one activity module on course page',
|
||||
'type' => 'read',
|
||||
'ajax' => true,
|
||||
),
|
||||
'core_course_edit_module' => array(
|
||||
'classname' => 'core_course_external',
|
||||
'methodname' => 'edit_module',
|
||||
'classpath' => 'course/externallib.php',
|
||||
'description' => 'Performs an action on course module (change visibility, duplicate, delete, etc.)',
|
||||
'type' => 'write',
|
||||
'ajax' => true,
|
||||
),
|
||||
'core_course_edit_section' => array(
|
||||
'classname' => 'core_course_external',
|
||||
'methodname' => 'edit_section',
|
||||
'classpath' => 'course/externallib.php',
|
||||
'description' => 'Performs an action on course section (change visibility, set marker, delete)',
|
||||
'type' => 'write',
|
||||
'ajax' => true,
|
||||
),
|
||||
'core_course_get_courses' => array(
|
||||
'classname' => 'core_course_external',
|
||||
'methodname' => 'get_courses',
|
||||
|
||||
@@ -2541,5 +2541,19 @@ function xmldb_main_upgrade($oldversion) {
|
||||
upgrade_main_savepoint(true, 2017021300.00);
|
||||
}
|
||||
|
||||
if ($oldversion < 2017021400.00) {
|
||||
// Define field visibleoncoursepage to be added to course_modules.
|
||||
$table = new xmldb_table('course_modules');
|
||||
$field = new xmldb_field('visibleoncoursepage', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '1', 'visible');
|
||||
|
||||
// Conditionally launch add field visibleoncoursepage.
|
||||
if (!$dbman->field_exists($table, $field)) {
|
||||
$dbman->add_field($table, $field);
|
||||
}
|
||||
|
||||
// Main savepoint reached.
|
||||
upgrade_main_savepoint(true, 2017021400.00);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
+77
-7
@@ -40,6 +40,15 @@ require_once "$CFG->libdir/form/select.php";
|
||||
*/
|
||||
class MoodleQuickForm_modvisible extends MoodleQuickForm_select{
|
||||
|
||||
/** @var int activity state: visible=0, visibleoncoursepage=any */
|
||||
const HIDE = 0;
|
||||
|
||||
/** @var int activity state: visible=1, visibleoncoursepage=1 */
|
||||
const SHOW = 1;
|
||||
|
||||
/** @var int activity state: visible=1, visibleoncoursepage=0 */
|
||||
const STEALTH = -1;
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*
|
||||
@@ -49,8 +58,7 @@ class MoodleQuickForm_modvisible extends MoodleQuickForm_select{
|
||||
* @param array $options ignored
|
||||
*/
|
||||
public function __construct($elementName=null, $elementLabel=null, $attributes=null, $options=null) {
|
||||
// TODO MDL-52313 Replace with the call to parent::__construct().
|
||||
HTML_QuickForm_element::__construct($elementName, $elementLabel, $attributes, null);
|
||||
parent::__construct($elementName, $elementLabel, null, $attributes);
|
||||
$this->_type = 'modvisible';
|
||||
}
|
||||
|
||||
@@ -72,17 +80,79 @@ class MoodleQuickForm_modvisible extends MoodleQuickForm_select{
|
||||
* @param object $caller calling object
|
||||
* @return bool
|
||||
*/
|
||||
function onQuickFormEvent($event, $arg, &$caller)
|
||||
{
|
||||
public function onQuickFormEvent($event, $arg, &$caller) {
|
||||
switch ($event) {
|
||||
case 'createElement':
|
||||
$choices=array();
|
||||
$choices[1] = get_string('show');
|
||||
$choices[0] = get_string('hide');
|
||||
$options = is_array($arg[3]) ? $arg[3] : [];
|
||||
$sectionvisible = array_key_exists('sectionvisible', $options) ? $options['sectionvisible'] : 1;
|
||||
$cm = !empty($options['cm']) ? cm_info::create($options['cm']) : null;
|
||||
$choices = array();
|
||||
if (!$sectionvisible) {
|
||||
// If section is not visible the activity is hidden by default but it can also be made available.
|
||||
$choices[self::HIDE] = get_string('hiddenfromstudents');
|
||||
if (!$cm || $cm->has_view()) {
|
||||
$choices[self::SHOW] = get_string('hiddenoncoursepage');
|
||||
}
|
||||
} else if (!empty($options['allowstealth']) && (!$cm || $cm->has_view())) {
|
||||
// If allowed in this course/section, add a third visibility option
|
||||
// "Available but not displayed on course page".
|
||||
$choices[self::SHOW] = get_string('show');
|
||||
$choices[self::HIDE] = get_string('hiddenfromstudents');
|
||||
$choices[self::STEALTH] = get_string('hiddenoncoursepage');
|
||||
} else {
|
||||
// In the visible section without "stealth" activities allowed it's just "Show" or "Hide".
|
||||
$choices[self::SHOW] = get_string('show');
|
||||
$choices[self::HIDE] = get_string('hide');
|
||||
}
|
||||
$this->load($choices);
|
||||
break;
|
||||
case 'updateValue':
|
||||
// Given two bool values of 'visible' and 'visibleoncoursepage' convert to a single
|
||||
// three-state value (show, hide, hide-on-course-page).
|
||||
$name = $this->getName();
|
||||
$value = $this->_findValue($caller->_constantValues);
|
||||
if (!empty($value) && isset($caller->_constantValues[$name.'oncoursepage']) &&
|
||||
!$caller->_constantValues[$name.'oncoursepage']) {
|
||||
$value = self::STEALTH;
|
||||
}
|
||||
if (null === $value) {
|
||||
if ($caller->isSubmitted()) {
|
||||
break;
|
||||
}
|
||||
$value = $this->_findValue($caller->_defaultValues);
|
||||
if (!empty($value) && isset($caller->_defaultValues[$name.'oncoursepage']) &&
|
||||
!$caller->_defaultValues[$name.'oncoursepage']) {
|
||||
$value = self::STEALTH;
|
||||
}
|
||||
}
|
||||
if ($value !== null) {
|
||||
$this->setSelected($value);
|
||||
}
|
||||
return true;
|
||||
|
||||
}
|
||||
return parent::onQuickFormEvent($event, $arg, $caller);
|
||||
}
|
||||
|
||||
/**
|
||||
* As usual, to get the group's value we access its elements and call
|
||||
* their exportValue() methods
|
||||
*
|
||||
* @param array $submitvalues submitted values
|
||||
* @param bool $assoc if true the retured value is associated array
|
||||
* @return mixed
|
||||
*/
|
||||
public function exportValue(&$submitvalues, $assoc = false) {
|
||||
if ($assoc) {
|
||||
$value = parent::exportValue($submitvalues, $assoc);
|
||||
$key = key($value);
|
||||
$v = $value[$key];
|
||||
// Convert three-state dropdown value (show, hide, hide-on-course-page) into the array of two bool values:
|
||||
// array('visible' => x, 'visibleoncoursepage' => y).
|
||||
return array($key => ($v == self::HIDE ? 0 : 1),
|
||||
$key . 'oncoursepage' => ($v == self::STEALTH ? 0 : 1));
|
||||
} else {
|
||||
return parent::exportValue($submitvalues, $assoc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+66
-2
@@ -690,6 +690,8 @@ class course_modinfo {
|
||||
* @property-read int $added Time that this course-module was added (unix time) - from course_modules table
|
||||
* @property-read int $visible Visible setting (0 or 1; if this is 0, students cannot see/access the activity) - from
|
||||
* course_modules table
|
||||
* @property-read int $visibleoncoursepage Visible on course page setting - from course_modules table, adjusted to
|
||||
* whether course format allows this module to have the "stealth" mode
|
||||
* @property-read int $visibleold Old visible setting (if the entire section is hidden, the previous value for
|
||||
* visible is stored in this field) - from course_modules table
|
||||
* @property-read int $groupmode Group mode (one of the constants NOGROUPS, SEPARATEGROUPS, or VISIBLEGROUPS) - from
|
||||
@@ -833,6 +835,12 @@ class cm_info implements IteratorAggregate {
|
||||
*/
|
||||
private $visible;
|
||||
|
||||
/**
|
||||
* Visible on course page setting - from course_modules table
|
||||
* @var int
|
||||
*/
|
||||
private $visibleoncoursepage;
|
||||
|
||||
/**
|
||||
* Old visible setting (if the entire section is hidden, the previous value for
|
||||
* visible is stored in this field) - from course_modules table
|
||||
@@ -998,6 +1006,12 @@ class cm_info implements IteratorAggregate {
|
||||
*/
|
||||
private $uservisible;
|
||||
|
||||
/**
|
||||
* True if this course-module is visible to the CURRENT user on the course page
|
||||
* @var bool
|
||||
*/
|
||||
private $uservisibleoncoursepage;
|
||||
|
||||
/**
|
||||
* @var moodle_url
|
||||
*/
|
||||
@@ -1093,6 +1107,7 @@ class cm_info implements IteratorAggregate {
|
||||
'showdescription' => false,
|
||||
'uservisible' => 'get_user_visible',
|
||||
'visible' => false,
|
||||
'visibleoncoursepage' => false,
|
||||
'visibleold' => false,
|
||||
'deletioninprogress' => false
|
||||
);
|
||||
@@ -1388,7 +1403,8 @@ class cm_info implements IteratorAggregate {
|
||||
*/
|
||||
public function get_grouping_label($textclasses = '') {
|
||||
$groupinglabel = '';
|
||||
if (!empty($this->groupingid) && has_capability('moodle/course:managegroups', context_course::instance($this->course))) {
|
||||
if ($this->effectivegroupmode != NOGROUPS && !empty($this->groupingid) &&
|
||||
has_capability('moodle/course:managegroups', context_course::instance($this->course))) {
|
||||
$groupings = groups_get_all_groupings($this->course);
|
||||
$groupinglabel = html_writer::tag('span', '('.format_string($groupings[$this->groupingid]->name).')',
|
||||
array('class' => 'groupinglabel '.$textclasses));
|
||||
@@ -1427,6 +1443,15 @@ class cm_info implements IteratorAggregate {
|
||||
return $this->modinfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the section this module belongs to
|
||||
*
|
||||
* @return section_info
|
||||
*/
|
||||
public function get_section_info() {
|
||||
return $this->modinfo->get_section_info($this->sectionnum);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns course object that was used in the first {@link get_fast_modinfo()} call.
|
||||
*
|
||||
@@ -1509,7 +1534,7 @@ class cm_info implements IteratorAggregate {
|
||||
|
||||
// Standard fields from table course_modules.
|
||||
static $cmfields = array('id', 'course', 'module', 'instance', 'section', 'idnumber', 'added',
|
||||
'score', 'indent', 'visible', 'visibleold', 'groupmode', 'groupingid',
|
||||
'score', 'indent', 'visible', 'visibleoncoursepage', 'visibleold', 'groupmode', 'groupingid',
|
||||
'completion', 'completiongradeitemnumber', 'completionview', 'completionexpected',
|
||||
'showdescription', 'availability', 'deletioninprogress');
|
||||
foreach ($cmfields as $key) {
|
||||
@@ -1685,6 +1710,7 @@ class cm_info implements IteratorAggregate {
|
||||
$this->idnumber = isset($mod->idnumber) ? $mod->idnumber : '';
|
||||
$this->name = $mod->name;
|
||||
$this->visible = $mod->visible;
|
||||
$this->visibleoncoursepage = $mod->visibleoncoursepage;
|
||||
$this->sectionnum = $mod->section; // Note weirdness with name here
|
||||
$this->groupmode = isset($mod->groupmode) ? $mod->groupmode : 0;
|
||||
$this->groupingid = isset($mod->groupingid) ? $mod->groupingid : 0;
|
||||
@@ -1823,6 +1849,34 @@ class cm_info implements IteratorAggregate {
|
||||
return $this->uservisible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this module is visible to the current user on course page
|
||||
*
|
||||
* Activity may be visible on the course page but not available, for example
|
||||
* when it is hidden conditionally but the condition information is displayed.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_visible_on_course_page() {
|
||||
$this->obtain_dynamic_data();
|
||||
return $this->uservisibleoncoursepage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this module is available but hidden from course page
|
||||
*
|
||||
* "Stealth" modules are the ones that are not shown on course page but available by following url.
|
||||
* They are normally also displayed in grade reports and other reports.
|
||||
* Module will be stealth either if visibleoncoursepage=0 or it is a visible module inside the hidden
|
||||
* section.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_stealth() {
|
||||
return !$this->visibleoncoursepage ||
|
||||
($this->visible && ($section = $this->get_section_info()) && !$section->visible);
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter method for property $available, ensures that dynamic data is retrieved
|
||||
* @return bool
|
||||
@@ -1889,6 +1943,16 @@ class cm_info implements IteratorAggregate {
|
||||
// Ensure activity is completely hidden from the user.
|
||||
$this->availableinfo = '';
|
||||
}
|
||||
|
||||
$this->uservisibleoncoursepage = $this->uservisible &&
|
||||
($this->visibleoncoursepage ||
|
||||
has_capability('moodle/course:manageactivities', $this->get_context(), $userid) ||
|
||||
has_capability('moodle/course:activityvisibility', $this->get_context(), $userid));
|
||||
// Activity that is not available, not hidden from course page and has availability
|
||||
// info is actually visible on the course page (with availability info and without a link).
|
||||
if (!$this->uservisible && $this->visibleoncoursepage && $this->availableinfo) {
|
||||
$this->uservisibleoncoursepage = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+2
-1
@@ -2893,7 +2893,8 @@ function require_login($courseorid = null, $autologinguest = true, $cm = null, $
|
||||
} else {
|
||||
$url = new moodle_url('/');
|
||||
}
|
||||
redirect($url, get_string('activityiscurrentlyhidden'));
|
||||
redirect($url, get_string('activityiscurrentlyhidden'), null,
|
||||
\core\output\notification::NOTIFY_ERROR);
|
||||
}
|
||||
|
||||
// Set the global $COURSE.
|
||||
|
||||
@@ -1991,7 +1991,7 @@ class global_navigation extends navigation_node {
|
||||
$activity->display = false;
|
||||
} else {
|
||||
$activity->url = $url->out();
|
||||
$activity->display = $cm->uservisible ? true : false;
|
||||
$activity->display = $cm->is_visible_on_course_page() ? true : false;
|
||||
if (self::module_extends_navigation($cm->modname)) {
|
||||
$activity->nodetype = navigation_node::NODETYPE_BRANCH;
|
||||
}
|
||||
@@ -2139,7 +2139,7 @@ class global_navigation extends navigation_node {
|
||||
$activitynode = $coursenode->add(format_string($cm->name), $url, navigation_node::TYPE_ACTIVITY, null, $cm->id, $icon);
|
||||
$activitynode->title(get_string('modulename', $cm->modname));
|
||||
$activitynode->hidden = (!$cm->visible);
|
||||
if (!$cm->uservisible) {
|
||||
if (!$cm->is_visible_on_course_page()) {
|
||||
// Do not show any error here, let the page handle exception that activity is not visible for the current user.
|
||||
// Also there may be no exception at all in case when teacher is logged in as student.
|
||||
$activitynode->display = false;
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
{{!
|
||||
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/>.
|
||||
}}
|
||||
{{!
|
||||
@template core/availability_info
|
||||
|
||||
Moodle template for the course or section availability information.
|
||||
|
||||
Classes required for JS:
|
||||
* none
|
||||
|
||||
Data attributes required for JS:
|
||||
* none
|
||||
|
||||
Example context (json):
|
||||
{ "classes": "", "text": "This activity is not available" }
|
||||
}}
|
||||
{{#text}}
|
||||
<div class="availabilityinfo {{classes}}">{{{text}}}</div>
|
||||
{{/text}}
|
||||
@@ -187,6 +187,7 @@ abstract class testing_module_generator extends component_generator_base {
|
||||
$defaults = array(
|
||||
'section' => 0,
|
||||
'visible' => 1,
|
||||
'visibleoncoursepage' => 1,
|
||||
'cmidnumber' => '',
|
||||
'groupmode' => 0,
|
||||
'groupingid' => 0,
|
||||
|
||||
@@ -55,6 +55,13 @@ class behat_action_menu extends behat_base {
|
||||
}
|
||||
// Gets the node based on the requested selector type and locator.
|
||||
$node = $this->get_node_in_container("css_element", "[role=menuitem][aria-haspopup=true]", $selectortype, $element);
|
||||
|
||||
// Check if it is not already opened.
|
||||
$menunode = $this->find('css', '[aria-labelledby='.$node->getAttribute('id').']');
|
||||
if ($menunode->getAttribute('aria-hidden') === 'false') {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->ensure_node_is_visible($node);
|
||||
$node->click();
|
||||
}
|
||||
|
||||
@@ -144,6 +144,7 @@ if ($xml = glossary_read_imported_file($result)) {
|
||||
// New glossary is to be inserted in section 0, it is always visible.
|
||||
$glossary->section = 0;
|
||||
$glossary->visible = 1;
|
||||
$glossary->visibleoncoursepage = 1;
|
||||
|
||||
// Include new glossary and return the new ID
|
||||
if ( !($glossary = add_moduleinfo($glossary, $course)) ) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
@mod @mod_label
|
||||
|
||||
Feature: Check label visibility works
|
||||
In order to check label visibility works
|
||||
As a teacher
|
||||
@@ -9,29 +10,89 @@ Feature: Check label visibility works
|
||||
Given the following "courses" exist:
|
||||
| fullname | shortname | category |
|
||||
| Test | C1 | 0 |
|
||||
And the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher | Teacher | Frist | teacher1@example.com |
|
||||
| student | Student | First | student1@example.com |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| admin | C1 | editingteacher |
|
||||
Given I log in as "admin"
|
||||
| teacher | C1 | editingteacher |
|
||||
| student | C1 | student |
|
||||
Given I log in as "teacher"
|
||||
And I follow "Test"
|
||||
And I turn editing mode on
|
||||
When I add a "label" to section "1" and I fill the form with:
|
||||
| Label text | Swanky label |
|
||||
| Visible | Hide |
|
||||
Then "Swanky label" activity should be hidden
|
||||
And I turn editing mode off
|
||||
And "Swanky label" activity should be hidden
|
||||
And I log out
|
||||
And I log in as "student"
|
||||
And I follow "Test"
|
||||
And I should not see "Swanky label"
|
||||
And I log out
|
||||
|
||||
@javascript
|
||||
Scenario: Visible label activity should be shown as visible.
|
||||
Given the following "courses" exist:
|
||||
| fullname | shortname | category |
|
||||
| Test | C1 | 0 |
|
||||
And the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher | Teacher | Frist | teacher1@example.com |
|
||||
| student | Student | First | student1@example.com |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| admin | C1 | editingteacher |
|
||||
Given I log in as "admin"
|
||||
| teacher | C1 | editingteacher |
|
||||
| student | C1 | student |
|
||||
Given I log in as "teacher"
|
||||
And I follow "Test"
|
||||
And I turn editing mode on
|
||||
When I add a "label" to section "1" and I fill the form with:
|
||||
| Label text | Swanky label |
|
||||
| Visible | Show |
|
||||
Then "Swanky label" activity should be visible
|
||||
And I log out
|
||||
And I log in as "student"
|
||||
And I follow "Test"
|
||||
And "Swanky label" activity should be visible
|
||||
And I log out
|
||||
|
||||
@javascript
|
||||
Scenario: Teacher can not show label inside the hidden section
|
||||
Given the following "courses" exist:
|
||||
| fullname | shortname | category |
|
||||
| Test | C1 | 0 |
|
||||
And the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher | Teacher | Frist | teacher1@example.com |
|
||||
| student | Student | First | student1@example.com |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher | C1 | editingteacher |
|
||||
| student | C1 | student |
|
||||
Given I log in as "teacher"
|
||||
And I follow "Test"
|
||||
And I turn editing mode on
|
||||
When I add a "label" to section "1" and I fill the form with:
|
||||
| Label text | Swanky label |
|
||||
| Visible | Show |
|
||||
And I hide section "1"
|
||||
Then "Swanky label" activity should be dimmed
|
||||
And I open "Swanky label" actions menu
|
||||
And "Swanky label" actions menu should not have "Show" item
|
||||
And "Swanky label" actions menu should not have "Hide" item
|
||||
And "Swanky label" actions menu should not have "Make available" item
|
||||
And "Swanky label" actions menu should not have "Make unavailable" item
|
||||
And I click on "Edit settings" "link" in the "Swanky label" activity
|
||||
And I expand all fieldsets
|
||||
And the "Visible" select box should contain "Hidden from students"
|
||||
And the "Visible" select box should not contain "Available but not displayed on course page"
|
||||
And the "Visible" select box should not contain "Hide"
|
||||
And the "Visible" select box should not contain "Show"
|
||||
And I log out
|
||||
And I log in as "student"
|
||||
And I follow "Test"
|
||||
And I should not see "Swanky label"
|
||||
And I log out
|
||||
|
||||
@@ -77,10 +77,6 @@ a.dimmed_text:visited,
|
||||
@extend .text-muted;
|
||||
}
|
||||
|
||||
.activity.label .dimmed_text {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.unlist,
|
||||
.unlist li,
|
||||
.inline-list,
|
||||
|
||||
@@ -96,6 +96,14 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.stealth {
|
||||
@extend .text-muted;
|
||||
}
|
||||
a.stealth,
|
||||
a.stealth:hover {
|
||||
color: lighten($link-color, 25%) !important; /* stylelint-disable-line declaration-no-important */
|
||||
}
|
||||
}
|
||||
|
||||
.label {
|
||||
@@ -305,15 +313,9 @@
|
||||
|
||||
.course-content ul li.section.hidden {
|
||||
.sectionname > span,
|
||||
.content > div,
|
||||
.content > div.summary,
|
||||
.activity .activityinstance {
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
.sectionname > span,
|
||||
.activity .activityinstance {
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
@extend .text-muted;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -329,6 +331,11 @@
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.summary,
|
||||
.content > .availabilityinfo {
|
||||
margin-left: 25px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -336,10 +343,6 @@
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.course-content ul.topics li.section .summary {
|
||||
margin-left: 25px;
|
||||
}
|
||||
|
||||
.course-content .hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
{{!
|
||||
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/>.
|
||||
}}
|
||||
{{!
|
||||
Availability info.
|
||||
|
||||
Example context (json):
|
||||
{ "classes": "", "text": "This activity is not available" }
|
||||
}}
|
||||
{{#text}}
|
||||
<div class="availabilityinfo {{classes}}">
|
||||
<span class="tag tag-info">{{{text}}}</span>
|
||||
</div>
|
||||
{{/text}}
|
||||
@@ -40,6 +40,12 @@ class behat_theme_boost_behat_action_menu extends behat_action_menu {
|
||||
public function i_open_the_action_menu_in($element, $selectortype) {
|
||||
// Gets the node based on the requested selector type and locator.
|
||||
$node = $this->get_node_in_container("css_element", "[role=button][aria-haspopup=true]", $selectortype, $element);
|
||||
|
||||
// Check if it is not already opened.
|
||||
if ($node->getAttribute('aria-expanded') === 'true') {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->ensure_node_is_visible($node);
|
||||
$node->click();
|
||||
}
|
||||
|
||||
@@ -109,9 +109,6 @@ a.dimmed_text:visited,
|
||||
.dimmed_category a {
|
||||
.muted
|
||||
}
|
||||
.activity.label .dimmed_text {
|
||||
.opacity(50)
|
||||
}
|
||||
.unlist,
|
||||
.unlist li,
|
||||
.inline-list,
|
||||
|
||||
@@ -71,13 +71,6 @@
|
||||
display: table-cell;
|
||||
padding-right: 4px;
|
||||
min-height: 2em;
|
||||
|
||||
.dimmed {
|
||||
img.activityicon {
|
||||
opacity: 0.5;
|
||||
filter: alpha(opacity=50);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.label {
|
||||
@@ -119,10 +112,24 @@
|
||||
}
|
||||
}
|
||||
|
||||
.activity img.activityicon {
|
||||
margin-right: 6px;
|
||||
vertical-align: text-bottom;
|
||||
.activity {
|
||||
img.activityicon {
|
||||
margin-right: 6px;
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
|
||||
.dimmed {
|
||||
img.activityicon {
|
||||
opacity: 0.5;
|
||||
filter: alpha(opacity=50);
|
||||
}
|
||||
}
|
||||
|
||||
.stealth:not(.dimmed) {
|
||||
opacity: .5;
|
||||
}
|
||||
}
|
||||
|
||||
.section .activity .activityinstance,
|
||||
.section .activity .activityinstance div {
|
||||
display: inline-block;
|
||||
@@ -245,36 +252,33 @@
|
||||
}
|
||||
.course-content ul li.section.hidden {
|
||||
.sectionname > span,
|
||||
.content > div,
|
||||
/* All the divs but the activities which are in a UL. */
|
||||
.activity .activityinstance {
|
||||
.content > div.summary {
|
||||
opacity: .5;
|
||||
}
|
||||
.sectionname > span,
|
||||
.activity .activityinstance {
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
.course-content ul.topics li.section .content,
|
||||
.course-content ul.weeks li.section .content {
|
||||
margin-right: 20px;
|
||||
margin-left: 20px;
|
||||
padding: 0;
|
||||
.course-content ul.topics,
|
||||
.course-content ul.weeks {
|
||||
|
||||
li.section {
|
||||
padding-bottom: 20px;
|
||||
|
||||
.content {
|
||||
margin-right: 20px;
|
||||
margin-left: 20px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.summary,
|
||||
.content > .availabilityinfo {
|
||||
margin-left: 25px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.course-content {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.course-content ul.topics li.section {
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.course-content ul.topics li.section .summary {
|
||||
margin-left: 25px;
|
||||
}
|
||||
|
||||
.course-content li {
|
||||
&.section {
|
||||
ul {
|
||||
|
||||
@@ -79,10 +79,6 @@ a.dimmed_text:visited,
|
||||
.dimmed_category a {
|
||||
color: #999;
|
||||
}
|
||||
.activity.label .dimmed_text {
|
||||
opacity: 0.5;
|
||||
filter: alpha(opacity=50);
|
||||
}
|
||||
.unlist,
|
||||
.unlist li,
|
||||
.inline-list,
|
||||
@@ -3544,11 +3540,6 @@ img.iconsmall {
|
||||
padding-right: 4px;
|
||||
min-height: 2em;
|
||||
}
|
||||
.section .activity .contentwithoutlink .dimmed img.activityicon,
|
||||
.section .activity .activityinstance .dimmed img.activityicon {
|
||||
opacity: 0.5;
|
||||
filter: alpha(opacity=50);
|
||||
}
|
||||
.section .label .contentwithoutlink,
|
||||
.section .label .activityinstance {
|
||||
padding-right: 32px;
|
||||
@@ -3582,6 +3573,13 @@ img.iconsmall {
|
||||
margin-right: 6px;
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
.activity .dimmed img.activityicon {
|
||||
opacity: 0.5;
|
||||
filter: alpha(opacity=50);
|
||||
}
|
||||
.activity .stealth:not(.dimmed) {
|
||||
opacity: .5;
|
||||
}
|
||||
.section .activity .activityinstance,
|
||||
.section .activity .activityinstance div {
|
||||
display: inline-block;
|
||||
@@ -3695,14 +3693,12 @@ img.iconsmall {
|
||||
margin-top: 0;
|
||||
}
|
||||
.course-content ul li.section.hidden .sectionname > span,
|
||||
.course-content ul li.section.hidden .content > div,
|
||||
.course-content ul li.section.hidden .activity .activityinstance {
|
||||
.course-content ul li.section.hidden .content > div.summary {
|
||||
opacity: .5;
|
||||
}
|
||||
.course-content ul li.section.hidden .sectionname > span,
|
||||
.course-content ul li.section.hidden .activity .activityinstance {
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
.course-content ul.topics li.section,
|
||||
.course-content ul.weeks li.section {
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
.course-content ul.topics li.section .content,
|
||||
.course-content ul.weeks li.section .content {
|
||||
@@ -3710,15 +3706,15 @@ img.iconsmall {
|
||||
margin-left: 20px;
|
||||
padding: 0;
|
||||
}
|
||||
.course-content ul.topics li.section .summary,
|
||||
.course-content ul.weeks li.section .summary,
|
||||
.course-content ul.topics li.section .content > .availabilityinfo,
|
||||
.course-content ul.weeks li.section .content > .availabilityinfo {
|
||||
margin-left: 25px;
|
||||
}
|
||||
.course-content {
|
||||
margin-top: 0;
|
||||
}
|
||||
.course-content ul.topics li.section {
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
.course-content ul.topics li.section .summary {
|
||||
margin-left: 25px;
|
||||
}
|
||||
.course-content li.section ul {
|
||||
list-style: disc;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
{{!
|
||||
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/>.
|
||||
}}
|
||||
{{!
|
||||
Availability info.
|
||||
|
||||
Example context (json):
|
||||
{ "classes": "", "text": "This activity is not available" }
|
||||
}}
|
||||
{{#text}}
|
||||
<div class="availabilityinfo {{classes}}">
|
||||
<span class="label label-info">{{{text}}}</span>
|
||||
</div>
|
||||
{{/text}}
|
||||
+1
-1
@@ -29,7 +29,7 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$version = 2017021300.00; // YYYYMMDD = weekly release date of this DEV branch.
|
||||
$version = 2017021400.00; // YYYYMMDD = weekly release date of this DEV branch.
|
||||
// RR = release increments - 00 in DEV branches.
|
||||
// .XX = incremental changes.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user