And I wait "2" seconds
And I hover ".region-content .generaltable td span" "css_element"
Then I should see "I'm the description"
+ And "Grouping" "select" in the "region-main" "region" should be visible
+ And "Group" "select" should be visible
+ And "Messaging" "link" in the "Administration" "block" should not be visible
+ And "Change password" "link" should not be visible
And I should see "Filter groups by"
And I should not see "Filter groupssss by"
And I should see "Group members" in the ".region-content table th.c1" "css_element"
$number = self::$parampages[$this->size];
$this->log('createpages', $number, true);
for ($i = 0; $i < $number; $i++) {
- $record = array('course' => $this->course->id);
+ $record = array('course' => $this->course);
$options = array('section' => $this->get_target_section());
$pagegenerator->create_instance($record, $options);
$this->dot($i, $number);
// Create resource with default textfile only.
$resourcegenerator = $this->generator->get_plugin_generator('mod_resource');
- $record = array('course' => $this->course->id,
+ $record = array('course' => $this->course,
'name' => get_string('smallfiles', 'tool_generator'));
$options = array('section' => 0);
$resource = $resourcegenerator->create_instance($record, $options);
$resourcegenerator = $this->generator->get_plugin_generator('mod_resource');
for ($i = 0; $i < $count; $i++) {
// Create resource.
- $record = array('course' => $this->course->id,
+ $record = array('course' => $this->course,
'name' => get_string('bigfile', 'tool_generator', $i));
$options = array('section' => $this->get_target_section());
$resource = $resourcegenerator->create_instance($record, $options);
// Create empty forum.
$forumgenerator = $this->generator->get_plugin_generator('mod_forum');
- $record = array('course' => $this->course->id,
+ $record = array('course' => $this->course,
'name' => get_string('pluginname', 'forum'));
$options = array('section' => 0);
$forum = $forumgenerator->create_instance($record, $options);
$fp = get_file_packer('application/zip');
$files = $fp->extract_to_pathname($zipfilepath, $targetdir);
- if ($files) {
- if (!empty($rootdir)) {
- $files = $this->rename_extracted_rootdir($targetdir, $rootdir, $files);
- }
- return $files;
-
- } else {
+ if (!$files) {
return array();
}
+
+ if (!empty($rootdir)) {
+ $files = $this->rename_extracted_rootdir($targetdir, $rootdir, $files);
+ }
+
+ // Sometimes zip may not contain all parent directories, add them to make it consistent.
+ foreach ($files as $path => $status) {
+ if ($status !== true) {
+ continue;
+ }
+ $parts = explode('/', trim($path, '/'));
+ while (array_pop($parts)) {
+ if (empty($parts)) {
+ break;
+ }
+ $dir = implode('/', $parts).'/';
+ if (!isset($files[$dir])) {
+ $files[$dir] = true;
+ }
+ }
+ }
+
+ return $files;
}
/**
$this->assertEquals(1, preg_match('~^site=(.+)$~', $query, $matches));
$site = rawurldecode($matches[1]);
$site = json_decode(base64_decode($site), true);
- $this->assertEquals('array', gettype($site));
+ $this->assertInternalType('array', $site);
$this->assertEquals(3, count($site));
$this->assertSame('Nasty site', $site['fullname']);
$this->assertSame('file:///etc/passwd', $site['url']);
$installer = tool_installaddon_installer::instance();
$files = $installer->extract_installfromzip_file($sourcedir.'/testinvalidroot.zip', $contentsdir, 'fixed_root');
- $this->assertEquals('array', gettype($files));
- $this->assertEquals(4, count($files));
+ $this->assertInternalType('array', $files);
+ $this->assertCount(4, $files);
$this->assertSame(true, $files['fixed_root/']);
$this->assertSame(true, $files['fixed_root/lang/']);
$this->assertSame(true, $files['fixed_root/lang/en/']);
* @copyright 2013 David Mudrak <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-class tool_installaddon_validator_test extends basic_testcase {
+class tool_installaddon_validator_testcase extends basic_testcase {
public function test_validate_files_layout() {
$fixtures = dirname(__FILE__).'/fixtures';
$this->assertEquals('foobar', $validator->get_rootdir());
$this->assertTrue($this->has_message($validator->get_messages(), $validator::INFO, 'rootdir', 'foobar'));
$versionphpinfo = $validator->get_versionphp_info();
- $this->assertEquals('array', gettype($versionphpinfo));
- $this->assertEquals(4, count($versionphpinfo));
+ $this->assertInternalType('array', $versionphpinfo);
+ $this->assertCount(4, $versionphpinfo);
$this->assertEquals(2013031900, $versionphpinfo['version']);
$this->assertEquals(2013031200, $versionphpinfo['requires']);
$this->assertEquals('local_foobar', $versionphpinfo['component']);
$this->assertEquals('testable_tool_installaddon_validator', get_class($validator));
$info = $validator->testable_parse_version_php($fixtures.'/version1.php');
- $this->assertEquals('array', gettype($info));
- $this->assertEquals(7, count($info));
+ $this->assertInternalType('array', $info);
+ $this->assertCount(7, $info);
$this->assertEquals('block_foobar', $info['plugin->component']); // Later in the file.
$this->assertEquals('2013010100', $info['plugin->version']); // Numeric wins over strings.
$this->assertEquals('2012122401', $info['plugin->requires']); // Commented.
$today = make_timestamp(date('Y', $today), date('m', $today), date('d', $today), 0, 0, 0);
// array of all valid fields for validation
-$STD_FIELDS = array('id', 'firstname', 'lastname', 'username', 'email',
+$STD_FIELDS = array('id', 'username', 'email',
'city', 'country', 'lang', 'timezone', 'mailformat',
'maildisplay', 'maildigest', 'htmleditor', 'autosubscribe',
'institution', 'department', 'idnumber', 'skype',
'deleted', // 1 means delete user
'mnethostid', // Can not be used for adding, updating or deleting of users - only for enrolments, groups, cohorts and suspending.
);
+// Include all name fields.
+$STD_FIELDS = array_merge($STD_FIELDS, get_all_user_name_fields());
$PRF_FIELDS = array();
defined('MOODLE_INTERNAL') || die();
require_once $CFG->libdir.'/formslib.php';
-
+require_once($CFG->dirroot . '/user/editlib.php');
/**
* Upload a file CVS file with user information.
// look for other required data
if ($optype != UU_USER_UPDATE) {
- if (!in_array('firstname', $columns)) {
- $errors['uutype'] = get_string('missingfield', 'error', 'firstname');
- }
-
- if (!in_array('lastname', $columns)) {
- if (isset($errors['uutype'])) {
- $errors['uutype'] = '';
- } else {
- $errors['uutype'] = ' ';
+ $requiredusernames = useredit_get_required_name_fields();
+ $missing = array();
+ foreach ($requiredusernames as $requiredusername) {
+ if (!in_array($requiredusername, $columns)) {
+ $missing[] = get_string('missingfield', 'error', $requiredusername);;
}
- $errors['uutype'] .= get_string('missingfield', 'error', 'lastname');
}
-
+ if ($missing) {
+ $errors['uutype'] = implode('<br />', $missing);
+ }
if (!in_array('email', $columns) and empty($data['email'])) {
$errors['email'] = get_string('requiredtemplate', 'tool_uploaduser');
}
}
-
return $errors;
}
backup_helper::check_and_create_backup_dir($this->get_backupid());// Create backup temp dir
backup_helper::clear_backup_dir($this->get_backupid(), $progress); // Empty temp dir, just in case
backup_helper::delete_old_backup_dirs(time() - (4 * 60 * 60), $progress); // Delete > 4 hours temp dirs
+ backup_controller_dbops::drop_backup_ids_temp_table($this->get_backupid()); // Drop ids temp table
backup_controller_dbops::create_backup_ids_temp_table($this->get_backupid()); // Create ids temp table
$progress->end_progress();
}
$dbman = $DB->get_manager(); // We are going to use database_manager services
$targettablename = 'backup_ids_temp';
- $table = new xmldb_table($targettablename);
- $dbman->drop_table($table); // And drop it
+ if ($dbman->table_exists($targettablename)) {
+ $table = new xmldb_table($targettablename);
+ $dbman->drop_table($table); // And drop it
+ }
}
/**
public static function set_state_running($running = true) {
if ($running === true) {
if (self::get_automated_backup_state() === self::STATE_RUNNING) {
- throw new backup_exception('backup_automated_already_running');
+ throw new backup_helper_exception('backup_automated_already_running');
}
set_config('backup_auto_running', '1', 'backup');
} else {
unset_config('noemailever');
+ $CFG->enablecompletion = true;
+
$user = $this->getDataGenerator()->create_user();
$fordb = new stdClass();
$this->badgeid = $DB->insert_record('badge', $fordb, true);
// Create a course with activity and auto completion tracking.
- $this->course = $this->getDataGenerator()->create_course();
+ $this->course = $this->getDataGenerator()->create_course(array('enablecompletion' => true));
$this->user = $this->getDataGenerator()->create_user();
$studentrole = $DB->get_record('role', array('shortname' => 'student'));
$this->assertNotEmpty($studentrole);
And I log in as "admin"
@javascript
- Scenario: Add criteria
+ Scenario: Award profile badge
Given I expand "Site administration" node
And I expand "Badges" node
And I follow "Add a new badge"
And I fill the moodle form with:
- | Name | Test Badge |
+ | Name | Profile Badge |
| Description | Test badge description |
| issuername | Test Badge Site |
| issuercontact | testuser@test-badge-site.com |
And I upload "badges/tests/behat/badge.png" file to "Image" filepicker
And I press "Create badge"
And I select "Profile completion" from "type"
- And I wait "5" seconds
And I check "First name"
And I check "Email address"
+ And I check "Phone"
When I press "Save"
Then I should see "Profile completion"
And I should see "First name"
And I should see "Email address"
And I should not see "Criteria for this badge have not been set up yet."
+ And I press "Enable access"
+ And I press "Continue"
+ And I expand "My profile settings" node
+ And I follow "Edit profile"
+ And I expand all fieldsets
+ And I fill in "Phone" with "123456789"
+ And I press "Update profile"
+ And I follow "My badges"
+ Then I should see "Profile Badge"
+ And I should not see "There are no badges available."
@javascript
- Scenario: Earn badge
- Given I expand "Site administration" node
+ Scenario: Award site badge
+ Given the following "users" exists:
+ | username | firstname | lastname | email |
+ | teacher | teacher | 1 | teacher1@asd.com |
+ | student | student | 1 | student1@asd.com |
+ And I expand "Site administration" node
And I expand "Badges" node
And I follow "Add a new badge"
And I fill the moodle form with:
- | Name | Profile Badge |
- | Description | Test badge description |
- | issuername | Test Badge Site |
- | issuercontact | testuser@test-badge-site.com |
+ | Name | Site Badge |
+ | Description | Site badge description |
+ | issuername | Tester of site badge |
And I upload "badges/tests/behat/badge.png" file to "Image" filepicker
And I press "Create badge"
- And I select "Profile completion" from "type"
- And I wait "5" seconds
- And I check "Phone"
+ And I select "Manual issue by role" from "type"
+ And I check "Teacher"
And I press "Save"
And I press "Enable access"
And I press "Continue"
- And I expand "My profile settings" node
- And I follow "Edit profile"
- And I expand all fieldsets
- And I fill in "Phone" with "123456789"
- And I press "Update profile"
- When I follow "My badges"
- Then I should see "Profile Badge"
- And I should not see "There are no badges available."
+ And I follow "Recipients (0)"
+ And I press "Award badge"
+ And I select "teacher 1 (teacher1@asd.com)" from "potentialrecipients[]"
+ And I press "Award badge"
+ And I select "student 1 (student1@asd.com)" from "potentialrecipients[]"
+ And I press "Award badge"
+ When I follow "Site Badge"
+ Then I should see "Recipients (2)"
+ And I log out
+ And I log in as "student"
+ And I expand "My profile" node
+ And I follow "My badges"
+ Then I should see "Site Badge"
+
+ @javascript
+ Scenario: Award course badge
+ Given the following "users" exists:
+ | username | firstname | lastname | email |
+ | teacher1 | Teacher | 1 | teacher1@asd.com |
+ | student1 | Student | 1 | student1@asd.com |
+ | student2 | Student | 2 | student2@asd.com |
+ And the following "courses" exists:
+ | fullname | shortname | category | groupmode |
+ | Course 1 | C1 | 0 | 1 |
+ And the following "course enrolments" exists:
+ | user | course | role |
+ | teacher1 | C1 | editingteacher |
+ | student1 | C1 | student |
+ | student2 | C1 | student |
+ And I log out
+ And I log in as "teacher1"
+ And I follow "Course 1"
+ And I click on "//span[text()='Badges']" "xpath_element" in the "Administration" "block"
+ And I follow "Add a new badge"
+ And I fill the moodle form with:
+ | Name | Course Badge |
+ | Description | Course badge description |
+ | issuername | Tester of course badge |
+ And I upload "badges/tests/behat/badge.png" file to "Image" filepicker
+ And I press "Create badge"
+ And I select "Manual issue by role" from "type"
+ And I check "Teacher"
+ And I press "Save"
+ And I press "Enable access"
+ And I press "Continue"
+ And I follow "Recipients (0)"
+ And I press "Award badge"
+ And I select "Student 2 (student2@asd.com)" from "potentialrecipients[]"
+ And I press "Award badge"
+ And I select "Student 1 (student1@asd.com)" from "potentialrecipients[]"
+ When I press "Award badge"
+ And I follow "Course Badge"
+ Then I should see "Recipients (2)"
+ And I log out
+ And I log in as "student1"
+ And I follow "Course 1"
+ And I expand "My profile" node
+ And I follow "My badges"
+ Then I should see "Course Badge"
+
+ @javascript
+ Scenario: Award badge on activity completion
+ Given the following "courses" exists:
+ | fullname | shortname | category |
+ | Course 1 | C1 | 0 |
+ And the following "users" exists:
+ | username | firstname | lastname | email |
+ | teacher1 | Teacher | Frist | teacher1@asd.com |
+ | student1 | Student | First | student1@asd.com |
+ And the following "course enrolments" exists:
+ | user | course | role |
+ | teacher1 | C1 | editingteacher |
+ | student1 | C1 | student |
+ And I log out
+ And I log in as "admin"
+ And I set the following administration settings values:
+ | Enable completion tracking | 1 |
+ And I follow "Home"
+ And I follow "Course 1"
+ And I follow "Edit settings"
+ And I fill the moodle form with:
+ | Enable completion tracking | Yes |
+ And I press "Save changes"
+ And I turn editing mode on
+ And I add a "Assignment" to section "1" and I fill the form with:
+ | Assignment name | Test assignment name |
+ | Description | Submit your online text |
+ And I log out
+ And I log in as "teacher1"
+ And I follow "Course 1"
+ And I click on "//span[text()='Badges']" "xpath_element" in the "Administration" "block"
+ And I follow "Add a new badge"
+ And I fill the moodle form with:
+ | Name | Course Badge |
+ | Description | Course badge description |
+ | issuername | Tester of course badge |
+ And I upload "badges/tests/behat/badge.png" file to "Image" filepicker
+ And I press "Create badge"
+ And I select "Activity completion" from "type"
+ And I check "Test assignment name"
+ And I press "Save"
+ And I press "Enable access"
+ When I press "Continue"
+ And I log out
+ And I log in as "student1"
+ And I follow "Course 1"
+ And I expand "My profile" node
+ And I follow "My badges"
+ Then I should see "There are no badges available."
+ And I follow "Home"
+ And I follow "Course 1"
+ And I press "Mark as complete: Test assignment name"
+ And I expand "My profile" node
+ And I follow "My badges"
+ Then I should see "Course Badge"
+
+ @javascript
+ Scenario: Award badge on course completion
+ Given the following "courses" exists:
+ | fullname | shortname | category |
+ | Course 1 | C1 | 0 |
+ And the following "users" exists:
+ | username | firstname | lastname | email |
+ | teacher1 | Teacher | Frist | teacher1@asd.com |
+ | student1 | Student | First | student1@asd.com |
+ And the following "course enrolments" exists:
+ | user | course | role |
+ | teacher1 | C1 | editingteacher |
+ | student1 | C1 | student |
+ And I log out
+ And I log in as "admin"
+ And I set the following administration settings values:
+ | Enable completion tracking | 1 |
+ And I follow "Home"
+ And I follow "Course 1"
+ And I follow "Edit settings"
+ And I fill the moodle form with:
+ | Enable completion tracking | Yes |
+ And I press "Save changes"
+ And I turn editing mode on
+ And I add a "Assignment" to section "1" and I fill the form with:
+ | Assignment name | Test assignment name |
+ | Description | Submit your online text |
+ | assignsubmission_onlinetext_enabled | 1 |
+ And I follow "Course completion"
+ And I select "2" from "id_overall_aggregation"
+ And I click on "Condition: Activity completion" "link"
+ And I check "Assign - Test assignment name"
+ And I press "Save changes"
+ And I log out
+ And I log in as "teacher1"
+ And I follow "Course 1"
+ And I click on "//span[text()='Badges']" "xpath_element" in the "Administration" "block"
+ And I follow "Add a new badge"
+ And I fill the moodle form with:
+ | Name | Course Badge |
+ | Description | Course badge description |
+ | issuername | Tester of course badge |
+ And I upload "badges/tests/behat/badge.png" file to "Image" filepicker
+ And I press "Create badge"
+ And I select "Course completion" from "type"
+ And I fill the moodle form with:
+ | grade_2 | 0 |
+ And I press "Save"
+ And I press "Enable access"
+ When I press "Continue"
+ And I log out
+ And I log in as "student1"
+ And I follow "Course 1"
+ And I expand "My profile" node
+ And I follow "My badges"
+ Then I should see "There are no badges available."
+ And I follow "Home"
+ And I follow "Course 1"
+ And I press "Mark as complete: Test assignment name"
+ And I log out
+ And I log in as "admin"
+ # We can't wait for cron to happen, so the admin manually triggers it.
+ And I trigger cron
+ # The admin needs to trigger cron twice to see the completion status as completed.
+ And I trigger cron
+ # Finally the admin goes back to homepage to continue the user story.
+ And I am on homepage
+ And I log out
+ And I log in as "student1"
+ And I expand "My profile" node
+ And I follow "My badges"
+ Then I should see "Course Badge"
\ No newline at end of file
if ($remove != -1 and !empty($communityid) and confirm_sesskey()) {
$communitymanager->block_community_remove_course($communityid, $USER->id);
echo $OUTPUT->header();
- echo $renderer->remove_success(new moodle_url(get_referer(false)));
+ echo $renderer->remove_success(new moodle_url('/course/view.php', array('id' => $courseid)));
echo $OUTPUT->footer();
die();
}
$community = $this->block_community_get_course($course->url, $userid);
if (empty($community)) {
+ $community = new stdClass();
$community->userid = $userid;
$community->coursename = $course->name;
$community->coursedescription = $course->description;
$forcedownload = true;
}
+ // NOTE: it woudl be nice to have file revisions here, for now rely on standard file lifetime,
+ // do not lower it because the files are dispalyed very often.
\core\session\manager::write_close();
- send_stored_file($file, 60*60, 0, $forcedownload, $options);
+ send_stored_file($file, null, 0, $forcedownload, $options);
}
/**
.dir-rtl .block_settings .block_tree .tree_item.branch {background-position: center right;}
.dir-rtl .block_settings .block_tree .root_node.leaf {padding-right:0px;}
.dir-rtl .block_settings .block_tree li.item_with_icon > p img { right: 0; left: auto;}
+.jsenabled .block_settings .block_tree .tree_item.branch.loadingbranch {background-image:url([[pix:i/loading_small]]);}
.jsenabled.dir-rtl .block_settings .block_tree .tree_item.emptybranch {background-image: url([[pix:t/collapsed_empty_rtl]]);background-position: center right;}
.jsenabled.dir-rtl .block_settings .block_tree .collapsed .tree_item.branch {background-image: url([[pix:t/collapsed_rtl]]);}
// Trigger an event for the new entry.
$event = \core\event\blog_entry_created::create(array(
'objectid' => $this->id,
- 'relateduserid' => $this->userid,
- 'other' => array('subject' => $this->subject)
+ 'relateduserid' => $this->userid
));
$event->set_custom_data($this);
$event->trigger();
$event = \core\event\blog_entry_updated::create(array(
'objectid' => $entry->id,
- 'relateduserid' => $entry->userid,
- 'other' => array('subject' => $entry->subject)
+ 'relateduserid' => $entry->userid
));
$event->set_custom_data($entry);
$event->trigger();
$event = \core\event\blog_entry_deleted::create(array(
'objectid' => $this->id,
- 'relateduserid' => $this->userid,
- 'other' => array('record' => (array) $record)
- ));
+ 'relateduserid' => $this->userid
+ ));
$event->add_record_snapshot("post", $record);
$event->set_custom_data($this);
$event->trigger();
public abstract function get_months();
/**
- * Returns the minimum year of the calendar.
+ * Returns the minimum year for the calendar.
*
- * @return int the minumum year
+ * @return int The minimum year
*/
public abstract function get_min_year();
/**
- * Returns the maximum year of the calendar.
+ * Returns the maximum year for the calendar
*
- * @return int the max year
+ * @return int The maximum year
*/
public abstract function get_max_year();
+ /**
+ * Returns an array of years.
+ *
+ * @param int $minyear
+ * @param int $maxyear
+ * @return array the years
+ */
+ public abstract function get_years($minyear = null, $maxyear = null);
+
+ /**
+ * Returns a multidimensional array with information for day, month, year
+ * and the order they are displayed when selecting a date.
+ * The order in the array will be the order displayed when selecting a date.
+ * Override this function to change the date selector order.
+ *
+ * @param int $minyear The year to start with
+ * @param int $maxyear The year to finish with
+ * @return array Full date information
+ */
+ public abstract function get_date_order($minyear = null, $maxyear = null);
+
/**
* Returns the number of days in a week.
*
* @return array the converted date
*/
public abstract function convert_from_gregorian($year, $month, $day, $hour = 0, $minute = 0);
+
+ /**
+ * This return locale for windows os.
+ *
+ * @return string locale
+ */
+ public abstract function locale_win_charset();
}
}
/**
- * Returns the minimum year of the calendar.
+ * Returns the minimum year for the calendar.
*
- * @return int the minumum year
+ * @return int The minimum year
*/
public function get_min_year() {
- return 1970;
+ return 1900;
}
/**
- * Returns the maximum year of the calendar.
+ * Returns the maximum year for the calendar
*
- * @return int the max year
+ * @return int The maximum year
*/
public function get_max_year() {
return 2050;
}
+ /**
+ * Returns an array of years.
+ *
+ * @param int $minyear
+ * @param int $maxyear
+ * @return array the years.
+ */
+ public function get_years($minyear = null, $maxyear = null) {
+ if (is_null($minyear)) {
+ $minyear = $this->get_min_year();
+ }
+
+ if (is_null($maxyear)) {
+ $maxyear = $this->get_max_year();
+ }
+
+ $years = array();
+ for ($i = $minyear; $i <= $maxyear; $i++) {
+ $years[$i] = $i;
+ }
+
+ return $years;
+ }
+
+ /**
+ * Returns a multidimensional array with information for day, month, year
+ * and the order they are displayed when selecting a date.
+ * The order in the array will be the order displayed when selecting a date.
+ * Override this function to change the date selector order.
+ *
+ * @param int $minyear The year to start with.
+ * @param int $maxyear The year to finish with.
+ * @return array Full date information.
+ */
+ public function get_date_order($minyear = null, $maxyear = null) {
+ $dateinfo = array();
+ $dateinfo['day'] = $this->get_days();
+ $dateinfo['month'] = $this->get_months();
+ $dateinfo['year'] = $this->get_years($minyear, $maxyear);
+
+ return $dateinfo;
+ }
+
/**
* Returns the number of days in a week.
*
'hour' => (int) $hour,
'minute' => (int) $minute);
}
+
+ /**
+ * This return locale for windows os.
+ *
+ * @return string locale
+ */
+ public function locale_win_charset() {
+ return get_string('localewincharset', 'langconfig');
+ }
}
}
/**
- * Returns the minimum year of the calendar.
+ * Returns the minimum year for the calendar.
*
- * @return int the minumum year
+ * @return int The minimum year
*/
public function get_min_year() {
return 1900;
}
/**
- * Returns the maximum year of the calendar.
+ * Returns the maximum year for the calendar
*
- * @return int the max year
+ * @return int The maximum year
*/
public function get_max_year() {
return 2050;
}
+ /**
+ * Returns an array of years.
+ *
+ * @param int $minyear
+ * @param int $maxyear
+ * @return array the years
+ */
+ public function get_years($minyear = null, $maxyear = null) {
+ if (is_null($minyear)) {
+ $minyear = $this->get_min_year();
+ }
+
+ if (is_null($maxyear)) {
+ $maxyear = $this->get_max_year();
+ }
+
+ $years = array();
+ for ($i = $minyear; $i <= $maxyear; $i++) {
+ $years[$i] = $i;
+ }
+
+ return $years;
+ }
+
+ /**
+ * Returns a multidimensional array with information for day, month, year
+ * and the order they are displayed when selecting a date.
+ * The order in the array will be the order displayed when selecting a date.
+ * Override this function to change the date selector order.
+ *
+ * @param int $minyear The year to start with
+ * @param int $maxyear The year to finish with
+ * @return array Full date information
+ */
+ public function get_date_order($minyear = null, $maxyear = null) {
+ $dateinfo = array();
+ $dateinfo['day'] = $this->get_days();
+ $dateinfo['month'] = $this->get_months();
+ $dateinfo['year'] = $this->get_years($minyear, $maxyear);
+
+ return $dateinfo;
+ }
+
/**
* Returns the number of days in a week.
*
return $date;
}
+
+ /**
+ * This return locale for windows os.
+ *
+ * @return string locale
+ */
+ public function locale_win_charset() {
+ return get_string('localewincharset', 'langconfig');
+ }
}
"require-dev": {
"phpunit/phpunit": "3.7.*",
"phpunit/dbUnit": "1.2.*",
- "moodlehq/behat-extension": "1.25.6"
+ "moodlehq/behat-extension": "1.26.*"
}
}
//
// Seconds for files to remain in caches. Decrease this if you are worried
// about students being served outdated versions of uploaded files.
-// $CFG->filelifetime = 86400;
+// $CFG->filelifetime = 60*60*6;
//
// Some web servers can offload the file serving from PHP process,
// comment out one the following options to enable it in Moodle:
);
}
+ // Resort.
+ if ($category->can_resort_subcategories() && $category->has_children()) {
+ $actions['resortbyname'] = array(
+ 'url' => new \moodle_url($baseurl, array('action' => 'resortcategories', 'resort' => 'name')),
+ 'icon' => new \pix_icon('t/sort', new \lang_string('sort')),
+ 'string' => new \lang_string('resortsubcategoriesbyname', 'moodle')
+ );
+ $actions['resortbyidnumber'] = array(
+ 'url' => new \moodle_url($baseurl, array('action' => 'resortcategories', 'resort' => 'idnumber')),
+ 'icon' => new \pix_icon('t/sort', new \lang_string('sort')),
+ 'string' => new \lang_string('resortsubcategoriesbyidnumber', 'moodle')
+ );
+ }
+
// Delete.
if ($category->can_delete_full()) {
$actions['delete'] = array(
}
$hasitems = false;
- if ($createtoplevel || $createsubcategory) {
+ if ($createtoplevel) {
$hasitems = true;
$menu = new action_menu;
if ($createtoplevel) {
if (coursecat::can_approve_course_requests()) {
$actions[] = html_writer::link(new moodle_url('/course/pending.php'), get_string('coursespending'));
}
- if ($category->can_resort_subcategories()) {
+ if (coursecat::get(0)->can_resort_subcategories()) {
$hasitems = true;
- $params = $this->page->url->params();
+ $params = array();
$params['action'] = 'resortcategories';
$params['sesskey'] = sesskey();
+ if ($this->page->url->get_param('categoryid') !== null) {
+ $params['selectedcategoryid'] = $this->page->url->get_param('categoryid');
+ }
$baseurl = new moodle_url('/course/management.php', $params);
$menu = new action_menu(array(
new action_menu_link_secondary(
new moodle_url($baseurl, array('resort' => 'name')),
null,
- get_string('resortbyname')
+ get_string('resortcategoriesbyname')
),
new action_menu_link_secondary(
new moodle_url($baseurl, array('resort' => 'idnumber')),
null,
- get_string('resortbyidnumber')
+ get_string('resortcategoriesbyidnumber')
)
));
- if ($category->id === 0) {
- $menu->actiontext = get_string('resortcategories');
- } else {
- $menu->actiontext = get_string('resortsubcategories');
- }
+ $menu->actiontext = get_string('resortcategories');
$menu->actionicon = new pix_icon('t/contextmenu', ' ', 'moodle', array('class' => 'iconsmall', 'title' => ''));
$actions[] = $this->render($menu);
}
);
}
if (coursecat::can_change_parent_any()) {
- $options = coursecat::make_categories_list('moodle/category:manage');
+ $options = array();
+ if (has_capability('moodle/category:manage', context_system::instance())) {
+ $options[0] = coursecat::get(0)->get_formatted_name();
+ }
+ $options += coursecat::make_categories_list('moodle/category:manage');
$select = html_writer::select(
$options,
'movecategoriesto',
if (!$this->cm->id = add_course_module($this->cm)) {
throw new coding_exception("Unable to create the course module");
}
- // The following are used inside some few core functions, so may as well set them here.
$this->cm->coursemodule = $this->cm->id;
- $groupbuttons = ($this->course->groupmode or (!$this->course->groupmodeforce));
- if ($groupbuttons and plugin_supports('mod', $this->module->name, FEATURE_GROUPS, 0)) {
- $this->cm->groupmodelink = (!$this->course->groupmodeforce);
- } else {
- $this->cm->groupmodelink = false;
- $this->cm->groupmode = false;
- }
}
/**
throw new moodle_exception('errorcreatingactivity', 'moodle', '', $this->module->name);
}
$mod = $info->get_cm($this->cm->id);
- $mod->groupmodelink = $this->cm->groupmodelink;
- $mod->groupmode = $this->cm->groupmode;
// Trigger course module created event.
$event = \core\event\course_module_created::create(array(
*
* @param object $module
* @return object the created module info
+ * @throws moodle_exception if user is not allowed to perform the action or module is not allowed in this course
*/
function create_module($moduleinfo) {
global $DB, $CFG;
$course = $DB->get_record('course', array('id'=>$moduleinfo->course), '*', MUST_EXIST);
list($module, $context, $cw) = can_add_moduleinfo($course, $moduleinfo->modulename, $moduleinfo->section);
- // Load module library.
- include_modulelib($module->name);
-
// Add the module.
$moduleinfo->module = $module->id;
$moduleinfo = add_moduleinfo($moduleinfo, $course, null);
*
* @param object $module
* @return object the updated module info
+ * @throws moodle_exception if current user is not allowed to update the module
*/
function update_module($moduleinfo) {
global $DB, $CFG;
// Some checks (capaibility / existing instances).
list($cm, $context, $module, $data, $cw) = can_update_moduleinfo($cm);
- // Load module library.
- include_modulelib($module->name);
-
// Retrieve few information needed by update_moduleinfo.
$moduleinfo->modulename = $cm->modname;
if (!isset($moduleinfo->scale)) {
require_once($CFG->dirroot.'/course/lib.php');
$categoryid = optional_param('categoryid', null, PARAM_INT);
+$selectedcategoryid = optional_param('selectedcategoryid', null, PARAM_INT);
$courseid = optional_param('courseid', null, PARAM_INT);
$action = optional_param('action', false, PARAM_ALPHA);
$page = optional_param('page', 0, PARAM_INT);
navigation_node::override_active_url($url);
}
+// Check if there is a selected category param, and if there is apply it.
+if ($course === null && $selectedcategoryid !== null && $selectedcategoryid !== $categoryid) {
+ $url->param('categoryid', $selectedcategoryid);
+}
+
if ($page !== 0) {
$url->param('page', $page);
}
$a = new stdClass;
$a->count = $movecount;
$a->to = $movetocat->get_formatted_name();
- $notificationspass[] = get_string('movecategoriessuccess', 'moodle', $a);
+ $movesuccessstrkey = 'movecategoriessuccess';
+ if ($movetocatid == 0) {
+ $movesuccessstrkey = 'movecategoriestotopsuccess';
+ }
+ $notificationspass[] = get_string($movesuccessstrkey, 'moodle', $a);
} else if ($movecount === 1) {
$a = new stdClass;
$a->moved = $cattomove->get_formatted_name();
$a->to = $movetocat->get_formatted_name();
- $notificationspass[] = get_string('movecategorysuccess', 'moodle', $a);
+ $movesuccessstrkey = 'movecategorysuccess';
+ if ($movetocatid == 0) {
+ $movesuccessstrkey = 'movecategorytotopsuccess';
+ }
+ $notificationspass[] = get_string($movesuccessstrkey, 'moodle', $a);
}
} else if ($bulkresortcategories) {
// Bulk resort selected categories.
print_error('noformdesc');
}
-include_modulelib($module->name);
-
$mformclassname = 'mod_'.$module->name.'_mod_form';
$mform = new $mformclassname($data, $cw->section, $cm, $course);
$mform->set_data($data);
function add_moduleinfo($moduleinfo, $course, $mform = null) {
global $DB, $CFG;
+ // Attempt to include module library before we make any changes to DB.
+ include_modulelib($moduleinfo->modulename);
+
$moduleinfo->course = $course->id;
$moduleinfo = set_moduleinfo_defaults($moduleinfo);
$moduleinfo->groupmode = 0; // Do not set groupmode.
}
- if (!course_allowed_module($course, $moduleinfo->modulename)) {
- print_error('moduledisable', '', '', $moduleinfo->modulename);
- }
-
// First add course_module record because we need the context.
$newcm = new stdClass();
$newcm->course = $course->id;
$newcm->instance = 0; // Not known yet, will be updated later (this is similar to restore code).
$newcm->visible = $moduleinfo->visible;
$newcm->visibleold = $moduleinfo->visible;
+ if (isset($moduleinfo->cmidnumber)) {
+ $newcm->idnumber = $moduleinfo->cmidnumber;
+ }
$newcm->groupmode = $moduleinfo->groupmode;
$newcm->groupingid = $moduleinfo->groupingid;
$newcm->groupmembersonly = $moduleinfo->groupmembersonly;
print_error('cannotaddcoursemodule');
}
- if (plugin_supports('mod', $moduleinfo->modulename, FEATURE_MOD_INTRO, true)) {
+ if (plugin_supports('mod', $moduleinfo->modulename, FEATURE_MOD_INTRO, true) &&
+ isset($moduleinfo->introeditor)) {
$introeditor = $moduleinfo->introeditor;
unset($moduleinfo->introeditor);
$moduleinfo->intro = $introeditor['text'];
$DB->delete_records('course_modules', array('id'=>$moduleinfo->coursemodule));
if (!is_number($returnfromfunc)) {
- print_error('invalidfunction', '', course_get_url($course, $cw->section));
+ print_error('invalidfunction', '', course_get_url($course, $moduleinfo->section));
} else {
- print_error('cannotaddnewmodule', '', course_get_url($course, $cw->section), $moduleinfo->modulename);
+ print_error('cannotaddnewmodule', '', course_get_url($course, $moduleinfo->section), $moduleinfo->modulename);
}
}
// So we have to update one of them twice.
$sectionid = course_add_cm_to_section($course, $moduleinfo->coursemodule, $moduleinfo->section);
- // Make sure visibility is set correctly (in particular in calendar).
- // Note: allow them to set it even without moodle/course:activityvisibility.
- set_coursemodule_visible($moduleinfo->coursemodule, $moduleinfo->visible);
-
- if (isset($moduleinfo->cmidnumber)) { // Label.
- // Set cm idnumber - uniqueness is already verified by form validation.
- set_coursemodule_idnumber($moduleinfo->coursemodule, $moduleinfo->cmidnumber);
- }
-
// Set up conditions.
if ($CFG->enableavailability) {
condition_info::update_cm_from_form((object)array('id'=>$moduleinfo->coursemodule), $moduleinfo, false);
global $CFG;
$modcontext = context_module::instance($moduleinfo->coursemodule);
+ $hasgrades = plugin_supports('mod', $moduleinfo->modulename, FEATURE_GRADE_HAS_GRADE, false);
+ $hasoutcomes = plugin_supports('mod', $moduleinfo->modulename, FEATURE_GRADE_OUTCOMES, true);
// Sync idnumber with grade_item.
- if ($grade_item = grade_item::fetch(array('itemtype'=>'mod', 'itemmodule'=>$moduleinfo->modulename,
+ if ($hasgrades && $grade_item = grade_item::fetch(array('itemtype'=>'mod', 'itemmodule'=>$moduleinfo->modulename,
'iteminstance'=>$moduleinfo->instance, 'itemnumber'=>0, 'courseid'=>$course->id))) {
if ($grade_item->idnumber != $moduleinfo->cmidnumber) {
$grade_item->idnumber = $moduleinfo->cmidnumber;
}
}
- $items = grade_item::fetch_all(array('itemtype'=>'mod', 'itemmodule'=>$moduleinfo->modulename,
+ if ($hasgrades) {
+ $items = grade_item::fetch_all(array('itemtype'=>'mod', 'itemmodule'=>$moduleinfo->modulename,
'iteminstance'=>$moduleinfo->instance, 'courseid'=>$course->id));
+ } else {
+ $items = array();
+ }
// Create parent category if requested and move to correct parent category.
if ($items and isset($moduleinfo->gradecat)) {
}
// Add outcomes if requested.
- if ($outcomes = grade_outcome::fetch_all_available($course->id)) {
+ if ($hasoutcomes && $outcomes = grade_outcome::fetch_all_available($course->id)) {
$grade_items = array();
// Outcome grade_item.itemnumber start at 1000, there is nothing above outcomes.
$moduleinfo->showgradingmanagement = $showgradingmanagement;
}
- rebuild_course_cache($course->id);
- grade_regrade_final_grades($course->id);
+ rebuild_course_cache($course->id, true);
+ if ($hasgrades) {
+ grade_regrade_final_grades($course->id);
+ }
require_once($CFG->libdir.'/plagiarismlib.php');
plagiarism_save_form_elements($moduleinfo);
* @return object the completed module info
*/
function set_moduleinfo_defaults($moduleinfo) {
- global $DB;
if (empty($moduleinfo->coursemodule)) {
// Add.
* @param object $modulename the module name
* @param object $section the section of the module
* @return array list containing module, context, course section.
+ * @throws moodle_exception if user is not allowed to perform the action or module is not allowed in this course
*/
function can_add_moduleinfo($course, $modulename, $section) {
global $DB;
*
* @param object $cm course module
* @return array - list of course module, context, module, moduleinfo, and course section.
+ * @throws moodle_exception if user is not allowed to perform the action
*/
function can_update_moduleinfo($cm) {
global $DB;
function update_moduleinfo($cm, $moduleinfo, $course, $mform = null) {
global $DB, $CFG;
+ // Attempt to include module library before we make any changes to DB.
+ include_modulelib($moduleinfo->modulename);
+
$moduleinfo->course = $course->id;
$moduleinfo = set_moduleinfo_defaults($moduleinfo);
* Include once the module lib file.
*
* @param string $modulename module name of the lib to include
+ * @throws moodle_exception if lib.php file for the module does not exist
*/
function include_modulelib($modulename) {
global $CFG;
$menu = new action_menu();
$menu->set_owner_selector($ownerselector);
- $menu->set_contraint($constraint);
+ $menu->set_constraint($constraint);
$menu->set_alignment(action_menu::TL, action_menu::TR);
if (isset($CFG->modeditingmenu) && !$CFG->modeditingmenu || !empty($displayoptions['donotenhance'])) {
$menu->do_not_enhance();
$node->click();
}
+ /**
+ * Clicks on a category checkbox in the management interface.
+ *
+ * @Given /^I select category "(?P<name>[^"]*)" in the management interface$/
+ * @param string $name
+ */
+ public function i_select_category_in_the_management_interface($name) {
+ $node = $this->get_management_category_listing_node_by_name($name);
+ $node->checkField('bcat[]');
+ }
+
+ /**
+ * Clicks course checkbox in the management interface.
+ *
+ * @Given /^I select course "(?P<name>[^"]*)" in the management interface$/
+ * @param string $name
+ */
+ public function i_select_course_in_the_management_interface($name) {
+ $node = $this->get_management_course_listing_node_by_name($name);
+ $node->checkField('bc[]');
+ }
+
+ /**
+ * Move selected categories to top level in the management interface.
+ *
+ * @Given /^I move category "(?P<idnumber>[^"]*)" to top level in the management interface$/
+ * @param string $idnumber
+ * @return Given[]
+ */
+ public function i_move_category_to_top_level_in_the_management_interface($idnumber) {
+ $id = $this->get_category_id($idnumber);
+ $selector = sprintf('.listitem-category[data-id="%d"] > div', $id);
+ $node = $this->find('css', $selector);
+ $node->checkField('bcat[]');
+ return array(
+ new Given('I select "' . coursecat::get(0)->get_formatted_name() . '" from "menumovecategoriesto"'),
+ new Given('I press "bulkmovecategories"'),
+ );
+ }
+
+ /**
+ * Checks that a category is a subcategory of specific category.
+ *
+ * @Given /^I should see category "(?P<subcatidnumber>[^"]*)" as subcategory of "(?P<catidnumber>[^"]*)" in the management interface$/
+ * @throws ExpectationException
+ * @param string $subcatidnumber
+ * @param string $catidnumber
+ */
+ public function i_should_see_category_as_subcategory_of_in_the_management_interface($subcatidnumber, $catidnumber) {
+ $categorynodeid = $this->get_category_id($catidnumber);
+ $subcategoryid = $this->get_category_id($subcatidnumber);
+ $exception = new ExpectationException('The category '.$subcatidnumber.' is not a subcategory of '.$catidnumber, $this->getSession());
+ $selector = sprintf('#category-listing .listitem-category[data-id="%d"] .listitem-category[data-id="%d"]', $categorynodeid, $subcategoryid);
+ $this->find('css', $selector, $exception);
+ }
+
+ /**
+ * Checks that a category is not a subcategory of specific category.
+ *
+ * @Given /^I should not see category "(?P<subcatidnumber>[^"]*)" as subcategory of "(?P<catidnumber>[^"]*)" in the management interface$/
+ * @throws ExpectationException
+ * @param string $subcatidnumber
+ * @param string $catidnumber
+ */
+ public function i_should_not_see_category_as_subcategory_of_in_the_management_interface($subcatidnumber, $catidnumber) {
+ try {
+ $this->i_should_see_category_as_subcategory_of_in_the_management_interface($subcatidnumber, $catidnumber);
+ } catch (ExpectationException $e) {
+ // ExpectedException means that it is not highlighted.
+ return;
+ }
+ throw new ExpectationException('The category '.$subcatidnumber.' is a subcategory of '.$catidnumber, $this->getSession());
+ }
+
/**
* Click to expand a category revealing its sub categories within the management UI.
*
throw new ExpectationException("Could not find the actions for $listingtype", $this->getSession());
}
$actionnode = $actionsnode->find('css', '.action-'.$action);
- if ($this->running_javascript() && !$actionnode->isVisible()) {
- $actionsnode->find('css', 'a.toggle-display')->click();
- if ($actionnode) {
- $actionnode = $listingnode->find('css', '.action-'.$action);
- }
- }
if (!$actionnode) {
throw new ExpectationException("Expected action was not available or not found ($action)", $this->getSession());
}
+ if ($this->running_javascript() && !$actionnode->isVisible()) {
+ $actionsnode->find('css', 'a.toggle-display')->click();
+ $actionnode = $actionsnode->find('css', '.action-'.$action);
+ }
$actionnode->click();
}
}
Test we can create a sub category
Test we can edit a category
Test we can delete a category
+ Test we can move a category
Test we can assign roles within a category
Test we can set permissions on a category
Test we can manage cohorts within a category
And I should see category listing "Cat 1" before "Test category 2"
And I should see "No courses in this category"
+ @javascript
+ Scenario: Test moving a categories through the management interface.
+ Given the following "categories" exists:
+ | name | category | idnumber |
+ | Cat 1 | 0 | CAT1 |
+ | Cat 2 | 0 | CAT2 |
+ | Cat 3 | 0 | CAT3 |
+
+ And I log in as "admin"
+ And I go to the courses management page
+ And I should see the "Course categories" management page
+ And I should see "Cat 1" in the "#category-listing ul.ml" "css_element"
+ And I should see "Cat 2" in the "#category-listing ul.ml" "css_element"
+ And I should see "Cat 3" in the "#category-listing ul.ml" "css_element"
+ And I select category "Cat 2" in the management interface
+ And I select category "Cat 3" in the management interface
+ And I select "Cat 1" from "menumovecategoriesto"
+ When I press "bulkmovecategories"
+ # Redirect
+ And I click on category "Cat 1" in the management interface
+ # Redirect
+ Then I should see category "CAT3" as subcategory of "CAT1" in the management interface
+ And I move category "CAT3" to top level in the management interface
+ # Redirect
+ And I should not see category "CAT3" as subcategory of "CAT1" in the management interface
+ Then I should see category "CAT2" as subcategory of "CAT1" in the management interface
And I go to the courses management page
And I should see the "Course categories" management page
And I should see "Re-sort categories" in the ".category-listing-actions" "css_element"
- And I should see "By name" in the ".category-listing-actions" "css_element"
- And I should see "By idnumber" in the ".category-listing-actions" "css_element"
+ And I should see "Re-sort the top level categories by name" in the ".category-listing-actions" "css_element"
+ And I should see "Re-sort the top level categories by idnumber" in the ".category-listing-actions" "css_element"
And I click on <sortby> "link" in the ".category-listing-actions" "css_element"
# Redirect.
And I should see the "Course categories" management page
Examples:
| sortby | cat1 | cat2 | cat3 |
| "Re-sort categories" | "Social studies" | "Applied sciences" | "Extended social studies" |
- | "By name" | "Applied sciences" | "Extended social studies" | "Social studies" |
- | "By idnumber" | "Extended social studies" | "Social studies" | "Applied sciences" |
+ | "Re-sort the top level categories by name" | "Applied sciences" | "Extended social studies" | "Social studies" |
+ | "Re-sort the top level categories by idnumber" | "Extended social studies" | "Social studies" | "Applied sciences" |
@javascript
Scenario Outline: Test resorting categories with JS enabled.
And I should not see "By name" in the ".category-listing-actions" "css_element"
And I should not see "By idnumber" in the ".category-listing-actions" "css_element"
And I click on "Re-sort categories" "link"
- And I should see "By name" in the ".category-listing-actions" "css_element"
- And I should see "By idnumber" in the ".category-listing-actions" "css_element"
+ And I should see "Re-sort the top level categories by name" in the ".category-listing-actions" "css_element"
+ And I should see "Re-sort the top level categories by idnumber" in the ".category-listing-actions" "css_element"
And I click on <sortby> "link" in the ".category-listing-actions" "css_element"
# Redirect.
And I should see the "Course categories" management page
Examples:
| sortby | cat1 | cat2 | cat3 |
| "Re-sort categories" | "Social studies" | "Applied sciences" | "Extended social studies" |
- | "By name" | "Applied sciences" | "Extended social studies" | "Social studies" |
- | "By idnumber" | "Extended social studies" | "Social studies" | "Applied sciences" |
+ | "Re-sort the top level categories by name" | "Applied sciences" | "Extended social studies" | "Social studies" |
+ | "Re-sort the top level categories by idnumber" | "Extended social studies" | "Social studies" | "Applied sciences" |
Scenario Outline: Test resorting subcategories.
Given the following "categories" exists:
And I click on "Master cat" "link"
# Redirect.
And I should see the "Course categories and courses" management page
- And I should see "Re-sort subcategories" in the ".category-listing-actions" "css_element"
- And I should see "By name" in the ".category-listing-actions" "css_element"
- And I should see "By idnumber" in the ".category-listing-actions" "css_element"
- And I click on <sortby> "link" in the ".category-listing-actions" "css_element"
+ And I click on <sortby> action for "Master cat" in management category listing
# Redirect.
And I should see the "Course categories and courses" management page
And I should see category listing <cat1> before <cat2>
Examples:
| sortby | cat1 | cat2 | cat3 |
- | "Re-sort subcategories" | "Social studies" | "Applied sciences" | "Extended social studies" |
- | "By name" | "Applied sciences" | "Extended social studies" | "Social studies" |
- | "By idnumber" | "Extended social studies" | "Social studies" | "Applied sciences" |
+ | "resortbyname" | "Applied sciences" | "Extended social studies" | "Social studies" |
+ | "resortbyidnumber" | "Extended social studies" | "Social studies" | "Applied sciences" |
@javascript
Scenario Outline: Test resorting subcategories with JS enabled.
And I click on "Master cat" "link"
# Redirect.
And I should see the "Course categories and courses" management page
- And I should see "Re-sort subcategories" in the ".category-listing-actions" "css_element"
- And I should not see "By name" in the ".category-listing-actions" "css_element"
- And I should not see "By idnumber" in the ".category-listing-actions" "css_element"
- And I click on "Re-sort subcategories" "link"
- And I should see "By name" in the ".category-listing-actions" "css_element"
- And I should see "By idnumber" in the ".category-listing-actions" "css_element"
- And I click on <sortby> "link" in the ".category-listing-actions" "css_element"
+ And I click on <sortby> action for "Master cat" in management category listing
# Redirect.
And I should see the "Course categories and courses" management page
And I should see category listing <cat1> before <cat2>
Examples:
| sortby | cat1 | cat2 | cat3 |
- | "Re-sort subcategories" | "Social studies" | "Applied sciences" | "Extended social studies" |
- | "By name" | "Applied sciences" | "Extended social studies" | "Social studies" |
- | "By idnumber" | "Extended social studies" | "Social studies" | "Applied sciences" |
+ | "resortbyname" | "Applied sciences" | "Extended social studies" | "Social studies" |
+ | "resortbyidnumber" | "Extended social studies" | "Social studies" | "Applied sciences" |
# The scenario below this is the same but with JS enabled.
Scenario: Test moving categories up and down by one.
And I go to the courses management page
And I should see the "Course categories" management page
And I click on "Re-sort categories" "link"
- And I should see "By name" in the ".category-listing-actions" "css_element"
- And I should see "By idnumber" in the ".category-listing-actions" "css_element"
+ And I should see "Re-sort the top level categories by name" in the ".category-listing-actions" "css_element"
+ And I should see "Re-sort the top level categories by idnumber" in the ".category-listing-actions" "css_element"
And I click on <sortby> "link" in the ".category-listing-actions" "css_element"
# Redirect.
And I should see the "Course categories" management page
Examples:
| sortby | cat1 | cat2 | cat3 |
| "Re-sort categories" | "Social studies" | "Applied sciences" | "Extended social studies" |
- | "By name" | "Applied sciences" | "Extended social studies" | "Social studies" |
- | "By idnumber" | "Extended social studies" | "Social studies" | "Applied sciences" |
+ | "Re-sort the top level categories by name" | "Applied sciences" | "Extended social studies" | "Social studies" |
+ | "Re-sort the top level categories by idnumber" | "Extended social studies" | "Social studies" | "Applied sciences" |
@javascript
Scenario Outline: Sub categories are displayed correctly when resorted
And I click on "Master cat" "link"
# Redirect.
And I should see the "Course categories and courses" management page
- And I click on "Re-sort subcategories" "link"
- And I should see "By name" in the ".category-listing-actions" "css_element"
- And I should see "By idnumber" in the ".category-listing-actions" "css_element"
- And I click on <sortby> "link" in the ".category-listing-actions" "css_element"
+ And I click on <sortby> action for "Master cat" in management category listing
# Redirect.
And I should see the "Course categories and courses" management page
And I should see category listing <cat1> before <cat2>
Examples:
| sortby | cat1 | cat2 | cat3 |
- | "Re-sort subcategories" | "Social studies" | "Applied sciences" | "Extended social studies" |
- | "By name" | "Applied sciences" | "Extended social studies" | "Social studies" |
- | "By idnumber" | "Extended social studies" | "Social studies" | "Applied sciences" |
+ | "resortbyname" | "Applied sciences" | "Extended social studies" | "Social studies" |
+ | "resortbyidnumber" | "Extended social studies" | "Social studies" | "Applied sciences" |
@javascript
Scenario Outline: Test courses are displayed correctly after being resorted.
And I should see "Cat 2-1-2" in the "#course-category-listings ul.ml" "css_element"
And I should not see "Cat 2-1-1-1" in the "#course-category-listings ul.ml" "css_element"
And I should see "Cat 2-1-2-1" in the "#course-category-listings ul.ml" "css_element"
- And I click on "Re-sort subcategories" "link" in the ".category-listing-actions" "css_element"
- And I click on "By idnumber" "link" in the ".category-listing-actions" "css_element"
+ And I click on "resortbyidnumber" action for "Cat 1" in management category listing
# Redirect.
And I should see the "Course categories and courses" management page
And I should see "Cat 1" in the "#course-category-listings ul.ml" "css_element"
# Redirect.
And I should see the "Course categories and courses" management page
And I should see "Cat A (1)" in the "#course-category-listings ul.ml" "css_element"
- And I should not see "Cat B (2)" in the "#course-category-listings ul.ml" "css_element"
\ No newline at end of file
+ And I should not see "Cat B (2)" in the "#course-category-listings ul.ml" "css_element"
},
'@VERSION@', {
- requires : ['base', 'node', 'io', 'moodle-course-coursebase']
+ requires : ['base', 'node', 'io', 'moodle-course-coursebase', 'moodle-course-util']
}
);
require_once('config.php');
require_once('lib/filelib.php');
-if (!isset($CFG->filelifetime)) {
- $lifetime = 86400; // Seconds for files to remain in caches
-} else {
- $lifetime = $CFG->filelifetime;
-}
-
$relativepath = get_file_argument();
$forcedownload = optional_param('forcedownload', 0, PARAM_BOOL);
// finally send the file
// ========================================
\core\session\manager::write_close(); // Unlock session during file serving.
-send_stored_file($file, $lifetime, $CFG->filteruploadedfiles, $forcedownload);
+send_stored_file($file, null, $CFG->filteruploadedfiles, $forcedownload);
<div class="fp-navbar">
<div class="filemanager-toolbar">
<div class="fp-toolbar">
- <div class="{!}fp-btn-add"><a role="button" href="#"><img src="'.$this->pix_url('a/add_file').'" alt="'.$straddfile.'" /></a></div>
- <div class="{!}fp-btn-mkdir"><a role="button" href="#"><img src="'.$this->pix_url('a/create_folder').'" alt="'.$strmakedir.'" /></a></div>
- <div class="{!}fp-btn-download"><a role="button" href="#"><img src="'.$this->pix_url('a/download_all').'" alt="'.$strdownload.'" /></a></div>
+ <div class="{!}fp-btn-add"><a role="button" title="'.$straddfile.'" href="#"><img src="'.$this->pix_url('a/add_file').'" alt="" /></a></div>
+ <div class="{!}fp-btn-mkdir"><a role="button" title="'.$strmakedir.'" href="#"><img src="'.$this->pix_url('a/create_folder').'" alt="" /></a></div>
+ <div class="{!}fp-btn-download"><a role="button" title="'.$strdownload.'" href="#"><img src="'.$this->pix_url('a/download_all').'" alt="" /></a></div>
</div>
<div class="{!}fp-viewbar">
<a title="'. get_string('displayicons', 'repository') .'" class="{!}fp-vb-icons" href="#"><img alt="" src="'. $this->pix_url('fp/view_icon_active', 'theme') .'" /></a>
<div class="{!}fp-toolbar">
<div class="{!}fp-tb-back"><a href="#">'.get_string('back', 'repository').'</a></div>
<div class="{!}fp-tb-search"><form></form></div>
- <div class="{!}fp-tb-refresh"><a href="#"><img alt="'. get_string('refresh', 'repository') .'" src="'.$this->pix_url('a/refresh').'" /></a></div>
- <div class="{!}fp-tb-logout"><img alt="'. get_string('logout', 'repository') .'" src="'.$this->pix_url('a/logout').'" /><a href="#"></a></div>
- <div class="{!}fp-tb-manage"><a href="#"><img alt="'. get_string('settings', 'repository') .'" src="'.$this->pix_url('a/setting').'" /></a></div>
- <div class="{!}fp-tb-help"><a href="#"><img alt="'. get_string('help', 'repository') .'" src="'.$this->pix_url('a/help').'" /></a></div>
+ <div class="{!}fp-tb-refresh"><a title="'. get_string('refresh', 'repository') .'" href="#"><img alt="" src="'.$this->pix_url('a/refresh').'" /></a></div>
+ <div class="{!}fp-tb-logout"><a title="'. get_string('logout', 'repository') .'" href="#"><img alt="" src="'.$this->pix_url('a/logout').'" /></a></div>
+ <div class="{!}fp-tb-manage"><a title="'. get_string('settings', 'repository') .'" href="#"><img alt="" src="'.$this->pix_url('a/setting').'" /></a></div>
+ <div class="{!}fp-tb-help"><a title="'. get_string('help', 'repository') .'" href="#"><img alt="" src="'.$this->pix_url('a/help').'" /></a></div>
<div class="{!}fp-tb-message"></div>
</div>
<div class="{!}fp-viewbar">
Then I should see "Legacy course files"
And I follow "Legacy course files"
And I press "Edit legacy course files"
- And I should see "Add..."
- And I should see "Create folder"
+ And "Add..." "link" should be visible
+ And "Create folder" "link" should be visible
@javascript
Scenario: Add legacy file disabled
Then I should see "Legacy course files"
And I follow "Legacy course files"
And I press "Edit legacy course files"
- And I should not see "Add..."
- And I should not see "Create folder"
+ And "Add..." "link" should not be visible
+ And "Create folder" "link" should not be visible
foreach ($concepts as $key => $concept) {
// Trim empty or unlinkable concepts
$currentconcept = trim(strip_tags($concept->concept));
+
+ // Concept must be HTML-escaped, so do the same as format_string
+ // to turn ampersands into &.
+ $currentconcept = replace_ampersands_not_followed_by_entity($currentconcept);
+
if (empty($currentconcept)) {
unset($concepts[$key]);
continue;
'&mode=cat&hook='.$concept->id.'">';
} else { // Link to entry or alias
if (!empty($concept->originalconcept)) { // We are dealing with an alias (so show and point to original)
- $title = str_replace('"', "'", strip_tags($glossaryname.': '.$concept->originalconcept));
+ $title = str_replace('"', "'", html_entity_decode(
+ strip_tags($glossaryname.': '.$concept->originalconcept)));
$concept->id = $concept->entryid;
} else { // This is an entry
- $title = str_replace('"', "'", strip_tags($glossaryname.': '.$concept->concept));
+ // We need to remove entities from the content here because it
+ // will be escaped by html_writer below.
+ $title = str_replace('"', "'", html_entity_decode(
+ strip_tags($glossaryname.': '.$concept->concept)));
}
// hardcoding dictionary format in the URL rather than defaulting
// to the current glossary format which may not work in a popup.
--- /dev/null
+<?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/>.
+
+/**
+ * Unit tests.
+ *
+ * @package filter_glossary
+ * @category test
+ * @copyright 2013 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->dirroot . '/filter/glossary/filter.php'); // Include the code to test.
+
+/**
+ * Test case for glossary.
+ */
+class filter_glossary_filter_testcase extends advanced_testcase {
+
+ /**
+ * Test ampersands.
+ */
+ public function test_ampersands() {
+ global $CFG;
+ $this->resetAfterTest(true);
+
+ // Enable glossary filter at top level.
+ filter_set_global_state('glossary', TEXTFILTER_ON);
+ $CFG->glossary_linkentries = 1;
+
+ // Create a test course.
+ $course = $this->getDataGenerator()->create_course();
+ $context = context_course::instance($course->id);
+
+ // Create a glossary.
+ $glossary = $this->getDataGenerator()->create_module('glossary',
+ array('course' => $course->id, 'mainglossary' => 1));
+
+ // Create two entries with ampersands and one normal entry.
+ $generator = $this->getDataGenerator()->get_plugin_generator('mod_glossary');
+ $normal = $generator->create_content($glossary, array('concept' => 'normal'));
+ $amp1 = $generator->create_content($glossary, array('concept' => 'A&B'));
+ $amp2 = $generator->create_content($glossary, array('concept' => 'C&D'));
+
+ // Format text with all three entries in HTML.
+ $html = '<p>A&B C&D normal</p>';
+ $filtered = format_text($html, FORMAT_HTML, array('context' => $context));
+
+ // Find all the glossary links in the result.
+ $matches = array();
+ preg_match_all('~courseid=' . $course->id . '&eid=([0-9]+).*?title="(.*?)"~', $filtered, $matches);
+
+ // There should be 3 glossary links.
+ $this->assertEquals(3, count($matches[1]));
+ $this->assertEquals($amp1->id, $matches[1][0]);
+ $this->assertEquals($amp2->id, $matches[1][1]);
+ $this->assertEquals($normal->id, $matches[1][2]);
+
+ // Check text and escaping of title attribute.
+ $this->assertEquals($glossary->name . ': A&B', $matches[2][0]);
+ $this->assertEquals($glossary->name . ': C&D', $matches[2][1]);
+ $this->assertEquals($glossary->name . ': normal', $matches[2][2]);
+ }
+}
$scope = optional_param('scope', 'custom', PARAM_ALPHA);
$PAGE->set_url('/grade/edit/outcome/import.php', array('courseid' => $courseid));
+$PAGE->set_pagelayout('admin');
/// Make sure they can even access this course
if ($courseid) {
$mform->addElement('hidden', 'action', 'upload');
$mform->setType('action', PARAM_ALPHANUMEXT);
$mform->addElement('hidden', 'courseid', $PAGE->course->id);
- $mform->setType('id', PARAM_INT);
+ $mform->setType('courseid', PARAM_INT);
$scope = array();
if (($PAGE->course->id > 1) && has_capability('moodle/grade:manage', context_system::instance())) {
<?php
+
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
- * version for mymobile theme
+ * Automatically generated strings for Moodle installer
+ *
+ * Do not edit this file manually! It contains just a subset of strings
+ * needed during the very first steps of installation. This file was
+ * generated automatically by export-installer.php (which is part of AMOS
+ * {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
+ * list of strings defined in /install/stringnames.txt.
*
- * @package theme_mymobile
- * @copyright John Stabinger
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @package installer
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-defined('MOODLE_INTERNAL') || die;
+defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2013050100; // The current module version (Date: YYYYMMDDXX)
-$plugin->requires = 2013050100; // Requires this Moodle version
-$plugin->component = 'theme_mymobile';
-$plugin->maturity = MATURITY_STABLE;
-$plugin->dependencies = array(
- 'theme_canvas' => 2013050100,
-);
+$string['language'] = 'زمان';
+$string['reload'] = 'بارکردنەوە';
defined('MOODLE_INTERNAL') || die();
$string['admindirname'] = 'Admin-map';
-$string['availablelangs'] = 'Lijst met beschikbare talen';
+$string['availablelangs'] = 'Beschikbare taalpakketten';
$string['chooselanguagehead'] = 'Kies een taal';
$string['chooselanguagesub'] = 'Kies een taal voor de installatie. Deze taal zal ook als standaardtaal voor de site gebruikt worden, maar die instelling kun je later nog wijzigen.';
$string['clialreadyconfigured'] = 'Bestand config.php bestaat al, maak aub gebruik van admin/cli/install_database.php indien je deze site wenst te installeren.';
$string['cachedef_databasemeta'] = 'Database meta information';
$string['cachedef_eventinvalidation'] = 'Event invalidation';
$string['cachedef_externalbadges'] = 'External badges for particular user';
+$string['cachedef_gradecondition'] = 'User grades cached for evaluating conditional availability';
$string['cachedef_groupdata'] = 'Course group information';
$string['cachedef_htmlpurifier'] = 'HTML Purifier - cleaned content';
$string['cachedef_langmenu'] = 'List of available languages';
$string['movecategorycontentto'] = 'Move into';
$string['movecategorysuccess'] = 'Successfully moved category \'{$a->moved}\' into category \'{$a->to}\'';
$string['movecategoriessuccess'] = 'Successfully moved {$a->count} categories into category \'{$a->to}\'';
+$string['movecategorytotopsuccess'] = 'Successfully moved category \'{$a->moved}\' to top level';
+$string['movecategoriestotopsuccess'] = 'Successfully moved {$a->count} categories to top level';
$string['movecategoryto'] = 'Move category to:';
$string['movecontentstoanothercategory'] = 'Move contents to another category';
$string['movecourseto'] = 'Move course to:';
$string['resettask'] = 'Task';
$string['resettodefaults'] = 'Reset to defaults';
$string['resortcategories'] = 'Re-sort categories';
-$string['resortsubcategories'] = 'Re-sort subcategories';
+$string['resortcategoriesbyname'] = 'Re-sort the top level categories by name';
+$string['resortcategoriesbyidnumber'] = 'Re-sort the top level categories by idnumber';
+$string['resortsubcategoriesbyname'] = 'Re-sort subcategories by name';
+$string['resortsubcategoriesbyidnumber'] = 'Re-sort subcategories by idnumber';
$string['resortcourses'] = 'Re-sort courses';
$string['resortcoursesbyname'] = 'Re-sort courses by name';
$string['resortbyname'] = 'By name';
$string['socialheadline'] = 'Social forum - latest topics';
$string['someallowguest'] = 'Some courses may allow guest access';
$string['someerrorswerefound'] = 'Some information was missing or incorrect. Look below for details.';
+$string['sort'] = 'Sort';
$string['sortby'] = 'Sort by';
$string['sortbyx'] = 'Sort by {$a} ascending';
$string['sortbyxreverse'] = 'Sort by {$a} descending';
*
* @param \blog_entry $data A reference to the active blog_entry object.
*/
- public function set_custom_data($data) {
+ public function set_custom_data(\blog_entry $data) {
$this->customobject = $data;
}
* @return string
*/
public function get_description() {
- return 'Blog entry "'. $this->other['subject']. '" was created by user with id '. $this->userid;
+ return 'Blog entry id '. $this->objectid. ' was created by userid '. $this->userid;
}
/**
*
* @param \blog_entry $data A reference to the active blog_entry object.
*/
- public function set_custom_data($data) {
+ public function set_custom_data(\blog_entry $data) {
$this->customobject = $data;
}
* @return string
*/
public function get_description() {
- return "Blog entry ".$this->other['record']['subject']." was deleted by user with id ".$this->userid;
+ return 'Blog entry id '. $this->objectid. ' was deleted by userid '. $this->userid;
}
/**
*
* @param \blog_entry $data A reference to the active blog_entry object.
*/
- public function set_custom_data($data) {
+ public function set_custom_data(\blog_entry $data) {
$this->customobject = $data;
}
* @return string
*/
public function get_description() {
- return 'User with id {$this->userid} updated blog entry {$this->other["subject"]';
+ return 'Blog entry id '. $this->objectid. ' was updated by userid '. $this->userid;
}
/**
*/
protected function get_legacy_logdata() {
return array(SITEID, 'blog', 'update', 'index.php?userid=' . $this->relateduserid . '&entryid=' . $this->objectid,
- $this->other['subject']);
+ $this->customobject->subject);
}
}
* @return string
*/
public function get_description() {
- return 'User with id ' . $this->userid . ' viewed content ' . $this->get_url();
+ return 'User with id ' . $this->userid . ' viewed content';
}
/**
* @return \moodle_url
*/
public function get_url() {
- return new moodle_url('/report/completion/index.php', array('course' => $this->courseid));
+ return new \moodle_url('/report/completion/index.php', array('course' => $this->courseid));
}
/**
* @return \moodle_url
*/
public function get_url() {
- return new moodle_url('/course/completion.php', array('id' => $this->courseid));
+ return new \moodle_url('/course/completion.php', array('id' => $this->courseid));
}
/**
* @return \moodle_url
*/
public function get_url() {
- return new moodle_url('/report/completion/index.php', array('course' => $this->courseid));
+ return new \moodle_url('/report/completion/index.php', array('course' => $this->courseid));
}
/**
* @return string
*/
public function get_description() {
- return 'The '. $this->other['modulename'] . ' module ' . $this->other['name']. ' was created by user with id '.
- $this->userid;
+ return 'The '. $this->other['modulename'] . ' module with instance id ' . $this->other['instanceid'] .
+ ' was created by user with id ' . $this->userid;
}
/**
* @return string
*/
public function get_description() {
- return 'The ' . $this->other['modulename'] . ' module ' . $this->other['name']. ' was updated by user with id '.
- $this->userid;
+ return 'The ' . $this->other['modulename'] . ' module with instance id ' . $this->other['instanceid'] .
+ ' was updated by user with id ' . $this->userid;
}
/**
* @return \moodle_url
*/
public function get_url() {
- return new moodle_url('/admin/roles/assign.php', array('contextid' => $this->contextid, 'roleid' => $this->objectid));
+ return new \moodle_url('/admin/roles/assign.php', array('contextid' => $this->contextid, 'roleid' => $this->objectid));
}
/**
* @return \moodle_url
*/
public function get_url() {
- return new moodle_url('/admin/roles/assign.php', array('contextid' => $this->contextid, 'roleid' => $this->objectid));
+ return new \moodle_url('/admin/roles/assign.php', array('contextid' => $this->contextid, 'roleid' => $this->objectid));
}
/**
*/
public function get_description() {
$user = $this->get_record_snapshot('user', $this->data['objectid']);
- return 'User profile deleted for user '.$user->firstname.' '.$user->lastname.' id ('.$user->id.')';
+ return 'User profile deleted for userid ' . $user->id;
}
/**
protected function init() {
$this->data['objecttable'] = 'user_enrolments';
$this->data['crud'] = 'c';
- $this->data['level'] = self::LEVEL_TEACHING;
+ $this->data['level'] = self::LEVEL_OTHER;
}
/**
protected function init() {
$this->data['objecttable'] = 'user_enrolments';
$this->data['crud'] = 'd';
- $this->data['level'] = self::LEVEL_TEACHING;
+ $this->data['level'] = self::LEVEL_OTHER;
}
/**
protected function init() {
$this->data['objecttable'] = 'user_enrolments';
$this->data['crud'] = 'u';
- $this->data['level'] = self::LEVEL_TEACHING;
+ $this->data['level'] = self::LEVEL_OTHER;
}
/**
$types = core_component::get_plugin_types();
+ if (!isset($types[$type])) {
+ // Orphaned subplugins!
+ $plugintypeclass = self::resolve_plugininfo_class($type);
+ $this->pluginsinfo[$type] = $plugintypeclass::get_plugins($type, null, $plugintypeclass);
+ return $this->pluginsinfo[$type];
+ }
+
/** @var \core\plugininfo\base $plugintypeclass */
$plugintypeclass = self::resolve_plugininfo_class($type);
$plugins = $plugintypeclass::get_plugins($type, $types[$type], $plugintypeclass);
foreach ($plugintypes as $plugintype => $plugintyperootdir) {
$this->pluginsinfo[$plugintype] = null;
}
+
+ // Add orphaned subplugin types.
+ $this->load_installed_plugins();
+ foreach ($this->installedplugins as $plugintype => $unused) {
+ if (!isset($plugintypes[$plugintype])) {
+ $this->pluginsinfo[$plugintype] = null;
+ }
+ }
}
/**
* @return string name of pluginfo class for give plugin type
*/
public static function resolve_plugininfo_class($type) {
+ $plugintypes = core_component::get_plugin_types();
+ if (!isset($plugintypes[$type])) {
+ return '\core\plugininfo\orphaned';
+ }
+
$parent = core_component::get_subtype_parent($type);
if ($parent) {
*/
public function get_plugin_info($component) {
list($type, $name) = core_component::normalize_component($component);
- $plugins = $this->get_plugins();
- if (isset($plugins[$type][$name])) {
- return $plugins[$type][$name];
+ $plugins = $this->get_plugins_of_type($type);
+ if (isset($plugins[$name])) {
+ return $plugins[$name];
} else {
return null;
}
'theme' => array(
'afterburner', 'anomaly', 'arialist', 'base', 'binarius', 'bootstrapbase',
'boxxie', 'brick', 'canvas', 'clean', 'formal_white', 'formfactor',
- 'fusion', 'leatherbound', 'magazine', 'mymobile', 'nimble',
+ 'fusion', 'leatherbound', 'magazine', 'nimble',
'nonzero', 'overlay', 'serenity', 'sky_high', 'splash',
'standard', 'standardold'
),
--- /dev/null
+<?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/>.
+
+/**
+ * Defines class used for orphaned subplugins.
+ *
+ * @package core
+ * @copyright 2013 Petr Skoda {@link http://skodak.org}
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace core\plugininfo;
+
+defined('MOODLE_INTERNAL') || die();
+
+
+/**
+ * Orphaned subplugins class.
+ */
+class orphaned extends base {
+ public function is_uninstall_allowed() {
+ return true;
+ }
+
+ /**
+ * We do not know if orphaned subplugins are enabled.
+ * @return bool
+ */
+ public function is_enabled() {
+ return null;
+ }
+
+ /**
+ * No lang strings are present.
+ */
+ public function init_display_name() {
+ $this->displayname = $this->component;
+ }
+
+ /**
+ * Oprhaned plugins can not be enabled.
+ * @return array|null of enabled plugins $pluginname=>$pluginname, null means unknown
+ */
+ public static function get_enabled_plugins() {
+ return null;
+ }
+
+ /**
+ * Gathers and returns the information about all plugins of the given type,
+ * either on disk or previously installed.
+ *
+ * @param string $type the name of the plugintype, eg. mod, auth or workshopform
+ * @param string $typerootdir full path to the location of the plugin dir
+ * @param string $typeclass the name of the actually called class
+ * @return array of plugintype classes, indexed by the plugin name
+ */
+ public static function get_plugins($type, $typerootdir, $typeclass) {
+ $return = array();
+ $manager = \core_plugin_manager::instance();
+ $plugins = $manager->get_installed_plugins($type);
+
+ foreach ($plugins as $name => $version) {
+ $plugin = new $typeclass();
+ $plugin->type = $type;
+ $plugin->typerootdir = $typerootdir;
+ $plugin->name = $name;
+ $plugin->rootdir = null;
+ $plugin->displayname = $name;
+ $plugin->versiondb = $version;
+ $plugin->init_is_standard();
+
+ $return[$name] = $plugin;
+ }
+
+ return $return;
+ }
+}
$a = new stdclass;
// Display the fieldname into current lang.
if (is_numeric($field)) {
+ // Is a custom profile field (will use multilang).
$translatedfieldname = $details->fieldname;
} else {
$translatedfieldname = get_user_field_name($details->fieldname);
if (!$this->is_field_condition_met($details->operator, $uservalue, $details->value)) {
// Set available to false
$available = false;
+ // Display the fieldname into current lang.
+ if (is_numeric($field)) {
+ // Is a custom profile field (will use multilang).
+ $translatedfieldname = $details->fieldname;
+ } else {
+ $translatedfieldname = get_user_field_name($details->fieldname);
+ }
$a = new stdClass();
- $a->field = format_string($details->fieldname, true, array('context' => $context));
+ $a->field = format_string($translatedfieldname, true, array('context' => $context));
$a->value = s($details->value);
$information .= html_writer::start_tag('li');
$information .= get_string('requires_user_field_'.$details->operator, 'condition', $a) . ' ';
*
* @global stdClass $USER
* @global moodle_database $DB
- * @global stdClass $SESSION
* @param int $gradeitemid Grade item ID we're interested in
* @param bool $grabthelot If true, grabs all scores for current user on
* this course, so that later ones come from cache
* or 37.21), or false if user does not have a grade yet
*/
private function get_cached_grade_score($gradeitemid, $grabthelot=false, $userid=0) {
- global $USER, $DB, $SESSION;
- if ($userid==0 || $userid==$USER->id) {
- // For current user, go via cache in session
- if (empty($SESSION->gradescorecache) || $SESSION->gradescorecacheuserid!=$USER->id) {
- $SESSION->gradescorecache = array();
- $SESSION->gradescorecacheuserid = $USER->id;
- }
- if (!array_key_exists($gradeitemid, $SESSION->gradescorecache)) {
- if ($grabthelot) {
- // Get all grades for the current course
- $rs = $DB->get_recordset_sql('
- SELECT
- gi.id,gg.finalgrade,gg.rawgrademin,gg.rawgrademax
- FROM
- {grade_items} gi
- LEFT JOIN {grade_grades} gg ON gi.id=gg.itemid AND gg.userid=?
- WHERE
- gi.courseid = ?', array($USER->id, $this->item->course));
- foreach ($rs as $record) {
- $SESSION->gradescorecache[$record->id] =
- is_null($record->finalgrade)
- // No grade = false
- ? false
- // Otherwise convert grade to percentage
- : (($record->finalgrade - $record->rawgrademin) * 100) /
- ($record->rawgrademax - $record->rawgrademin);
+ global $USER, $DB;
+ if (!$userid) {
+ $userid = $USER->id;
+ }
+ $cache = cache::make('core', 'gradecondition');
+ if (($cachedgrades = $cache->get($userid)) === false) {
+ $cachedgrades = array();
+ }
+ if (!array_key_exists($gradeitemid, $cachedgrades)) {
+ if ($grabthelot) {
+ // Get all grades for the current course
+ $rs = $DB->get_recordset_sql('
+ SELECT
+ gi.id,gg.finalgrade,gg.rawgrademin,gg.rawgrademax
+ FROM
+ {grade_items} gi
+ LEFT JOIN {grade_grades} gg ON gi.id=gg.itemid AND gg.userid=?
+ WHERE
+ gi.courseid = ?', array($userid, $this->item->course));
+ foreach ($rs as $record) {
+ $cachedgrades[$record->id] =
+ is_null($record->finalgrade)
+ // No grade = false
+ ? false
+ // Otherwise convert grade to percentage
+ : (($record->finalgrade - $record->rawgrademin) * 100) /
+ ($record->rawgrademax - $record->rawgrademin);
- }
- $rs->close();
- // And if it's still not set, well it doesn't exist (eg
- // maybe the user set it as a condition, then deleted the
- // grade item) so we call it false
- if (!array_key_exists($gradeitemid, $SESSION->gradescorecache)) {
- $SESSION->gradescorecache[$gradeitemid] = false;
- }
- } else {
- // Just get current grade
- $record = $DB->get_record('grade_grades', array(
- 'userid'=>$USER->id, 'itemid'=>$gradeitemid));
- if ($record && !is_null($record->finalgrade)) {
- $score = (($record->finalgrade - $record->rawgrademin) * 100) /
- ($record->rawgrademax - $record->rawgrademin);
- } else {
- // Treat the case where row exists but is null, same as
- // case where row doesn't exist
- $score = false;
- }
- $SESSION->gradescorecache[$gradeitemid]=$score;
}
- }
- return $SESSION->gradescorecache[$gradeitemid];
- } else {
- // Not the current user, so request the score individually
- $record = $DB->get_record('grade_grades', array(
- 'userid'=>$userid, 'itemid'=>$gradeitemid));
- if ($record && !is_null($record->finalgrade)) {
- $score = (($record->finalgrade - $record->rawgrademin) * 100) /
- ($record->rawgrademax - $record->rawgrademin);
+ $rs->close();
+ // And if it's still not set, well it doesn't exist (eg
+ // maybe the user set it as a condition, then deleted the
+ // grade item) so we call it false
+ if (!array_key_exists($gradeitemid, $cachedgrades)) {
+ $cachedgrades[$gradeitemid] = false;
+ }
} else {
- // Treat the case where row exists but is null, same as
- // case where row doesn't exist
- $score = false;
+ // Just get current grade
+ $record = $DB->get_record('grade_grades', array(
+ 'userid'=>$userid, 'itemid'=>$gradeitemid));
+ if ($record && !is_null($record->finalgrade)) {
+ $score = (($record->finalgrade - $record->rawgrademin) * 100) /
+ ($record->rawgrademax - $record->rawgrademin);
+ } else {
+ // Treat the case where row exists but is null, same as
+ // case where row doesn't exist
+ $score = false;
+ }
+ $cachedgrades[$gradeitemid]=$score;
}
- return $score;
+ $cache->set($userid, $cachedgrades);
}
+ return $cachedgrades[$gradeitemid];
+ }
+
+ /**
+ * Called by grade code to inform the completion system when a grade has
+ * been changed. Grades can be used to determine condition for
+ * the course-module or section.
+ *
+ * Note that this function may be called twice for one changed grade object.
+ *
+ * @param grade_grade $grade
+ * @param bool $deleted
+ */
+ public static function inform_grade_changed($grade, $deleted) {
+ cache::make('core', 'gradecondition')->delete($grade->userid);
}
/**
}
/**
- * For testing only. Wipes information cached in user session.
- *
- * @global stdClass $SESSION
+ * For testing only. Wipes information cached in cache.
+ * Replaced with {@link core_conditionlib_testcase::wipe_condition_cache()}
+ * @deprecated since 2.6
*/
static function wipe_session_cache() {
- global $SESSION;
- unset($SESSION->gradescorecache);
- unset($SESSION->gradescorecacheuserid);
- unset($SESSION->userfieldcache);
- unset($SESSION->userfieldcacheuserid);
+ cache::make('core', 'gradecondition')->purge();
}
/**
$context = $this->get_context();
return format_string($this->name, true, array('context' => $context) + $options);
} else {
- return ''; // TODO 'Top'?.
+ return get_string('top');
}
}
'simplekeys' => true,
'simpledata' => true
),
+ // Used to cache user grades for conditional availability purposes.
+ 'gradecondition' => array(
+ 'mode' => cache_store::MODE_APPLICATION,
+ 'staticacceleration' => true,
+ 'staticaccelerationsize' => 2, // Should not be required for more than one user at a time.
+ 'ttl' => 3600,
+ ),
);
* (it is just abusing cron to do very time consuming things which is wrong any way)
*
* TODO: this has to be moved into separate queueing framework....
+ * TODO: MDL-25508, MDL-41541
*/
'portfolio_send' => array (
- 'handlerfile' => '/lib/portfolio.php',
+ 'handlerfile' => '/lib/portfoliolib.php',
'handlerfunction' => 'portfolio_handle_event', // argument to call_user_func(), could be an array
'schedule' => 'cron',
'internal' => 0,
upgrade_main_savepoint(true, 2013102201.00);
}
+ if ($oldversion < 2013102500.01) {
+ // Find all fileareas that have missing root folder entry and add the root folder entry.
+ if (empty($CFG->filesrootrecordsfixed)) {
+ $sql = "SELECT distinct f1.contextid, f1.component, f1.filearea, f1.itemid
+ FROM {files} f1 left JOIN {files} f2
+ ON f1.contextid = f2.contextid
+ AND f1.component = f2.component
+ AND f1.filearea = f2.filearea
+ AND f1.itemid = f2.itemid
+ AND f2.filename = '.'
+ AND f2.filepath = '/'
+ WHERE (f1.component <> 'user' or f1.filearea <> 'draft')
+ and f2.id is null";
+ $rs = $DB->get_recordset_sql($sql);
+ $defaults = array('filepath' => '/',
+ 'filename' => '.',
+ 'userid' => $USER->id,
+ 'filesize' => 0,
+ 'timecreated' => time(),
+ 'timemodified' => time(),
+ 'contenthash' => sha1(''));
+ foreach ($rs as $r) {
+ $pathhash = sha1("/$r->contextid/$r->component/$r->filearea/$r->itemid".'/.');
+ $DB->insert_record('files', (array)$r + $defaults +
+ array('pathnamehash' => $pathhash));
+ }
+ $rs->close();
+ // To skip running the same script on the upgrade to the next major release.
+ set_config('filesrootrecordsfixed', 1);
+ }
+
+ // Main savepoint reached.
+ upgrade_main_savepoint(true, 2013102500.01);
+ }
+
+ if ($oldversion < 2013110400.00) {
+
+ if (!check_dir_exists($CFG->dirroot . '/theme/mymobile', false)) {
+ // Delete from config_plugins.
+ $DB->delete_records('config_plugins', array('plugin' => 'theme_mymobile'));
+ // Delete the config logs.
+ $DB->delete_records('config_log', array('plugin' => 'theme_mymobile'));
+
+ // Replace the mymobile settings.
+ $DB->set_field('course', 'theme', 'clean', array('theme' => 'mymobile'));
+ $DB->set_field('course_categories', 'theme', 'clean', array('theme' => 'mymobile'));
+ $DB->set_field('user', 'theme', 'clean', array('theme' => 'mymobile'));
+ $DB->set_field('mnet_host', 'theme', 'clean', array('theme' => 'mymobile'));
+
+ // Replace the theme configs.
+ if (get_config('core', 'theme') == 'mymobile') {
+ set_config('theme', 'clean');
+ }
+ if (get_config('core', 'thememobile') == 'mymobile') {
+ set_config('thememobile', 'clean');
+ }
+ if (get_config('core', 'themelegacy') == 'mymobile') {
+ set_config('themelegacy', 'clean');
+ }
+ if (get_config('core', 'themetablet') == 'mymobile') {
+ set_config('themetablet', 'clean');
+ }
+ }
+
+ // Main savepoint reached.
+ upgrade_main_savepoint(true, 2013110400.00);
+ }
+
return true;
}
upgrade_plugin_savepoint(true, 2013070500, 'editor', 'tinymce');
}
+ if ($oldversion < 2013102900) {
+ // Reset redesigned editor toolbar setting.
+ $currentorder = get_config('editor_tinymce', 'customtoolbar');
+ // Start with a wrap.
+ $neworder = "wrap,". $currentorder;
+ // Replace all separators with wraps to allow for proper display of groups.
+ $neworder = preg_replace('/\|\|*/', "wrap", $neworder);
+ // Insert a wrap between the format selector and the bold button.
+ $neworder = str_replace("formatselect,bold", "formatselect,wrap,bold", $neworder);
+ set_config('customtoolbar', $neworder, 'editor_tinymce');
+ upgrade_plugin_savepoint(true, 2013102900, 'editor', 'tinymce');
+ }
+
return true;
}
$string['customconfig'] = 'Custom configuration';
$string['customconfig_desc'] = 'Custom advanced TinyMCE configuration in JSON format, for example: {"option1" : "value2", "option2" : "value2"}. Any options specified here override standard and plugin settings.';
$string['customtoolbar'] = 'Editor toolbar';
-$string['customtoolbar_desc'] = 'Each line contains a list of comma separated button names, use "|" as a group separator, empty lines are ignored. See <a href="{$a}" target="_blank">{$a}</a> for the list of default TinyMCE buttons.<br />The first row will always be shown, where as the visibility of second and third toolbars can be toggled';
+$string['customtoolbar_desc'] = 'Each line contains a list of comma separated button names, use "wrap" as a group separator, empty lines are ignored. See <a href="{$a}" target="_blank">{$a}</a> for the list of default TinyMCE buttons.<br />The first row will always be shown, where as the visibility of second and third toolbars can be toggled.';
$string['fontselectlist'] = 'Available fonts list';
$string['pluginname'] = 'TinyMCE HTML editor';
$string['settings'] = 'General settings';
header('X-UA-Compatible: IE=edge');
?>
<!DOCTYPE html>
-<html <?php echo $htmllang ?>
+<html <?php echo $htmllang ?>>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title><?php print_string('title', 'tinymce_dragmath')?></title>
</object>
<form name="form" action="#">
<div class="mceActionPanel">
- <input type="submit" id="insert" name="insert" value="{#insert}" onclick="return DragMathDialog.insert();" />
- <input type="button" id="cancel" name="cancel" value="{#cancel}" onclick="return tinyMCEPopup.close();" />
+ <input type="submit" id="insert" name="insert" value="<?php print_string('common:insert', 'editor_tinymce'); ?>" onclick="return DragMathDialog.insert();" />
+ <input type="button" id="cancel" name="cancel" value="<?php print_string('cancel'); ?>" onclick="return tinyMCEPopup.close();" />
</div>
</form>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>\r
+<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In -->\r
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [\r
+ <!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">\r
+]>\r
+<svg version="1.1"\r
+ xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"\r
+ x="0px" y="0px" width="16px" height="16px" viewBox="0 0 16 16" style="overflow:visible;enable-background:new 0 0 16 16;"\r
+ xml:space="preserve" preserveAspectRatio="xMinYMid meet">\r
+<defs>\r
+</defs>\r
+<path style="fill:#999999;" d="M0,0v16h16V0H0z M3,15H1v-2h2V15z M3,11H1V9h2V11z M3,7H1V5h2V7z M3,3H1V1h2V3z M12,15H4V1h8V15z\r
+ M15,15h-2v-2h2V15z M15,11h-2V9h2V11z M15,7h-2V5h2V7z M15,3h-2V1h2V3z M10,8l-4,3V5L10,8z"/>\r
+</svg>\r
$rowsnumber = $this->count_button_rows($params);
if ($rowsnumber > 1) {
- // Add button before 'undo' in advancedbuttons1.
- $this->add_button_before($params, 1, '|', '');
$this->add_button_before($params, 1, 'pdw_toggle', '');
$params['pdw_toggle_on'] = 1;
$params['pdw_toggle_toolbars'] = join(',', range(2, $rowsnumber));
defined('MOODLE_INTERNAL') || die();
// The current plugin version (Date: YYYYMMDDXX).
-$plugin->version = 2013070500;
+$plugin->version = 2013100700;
// Required Moodle version.
$plugin->requires = 2013050100;
// Full name of the plugin (used for diagnostics).
*/
Wrap : function(id, s) {
this.parent(id, s);
- this.groupEndClass = 'mceToolbarEnd';
- this.groupStartClass = 'mceToolbarStart';
+ this.groupEndClass = 'mceGroupEnd';
+ this.toolbarEndClass = 'mceLast';
+ this.groupEndPlaceholder = 'mceToolbarEndPlaceholder';
+ this.groupStartClass = 'mceGroupStart';
this.wrapClass = 'mceWrap';
+ this.noWrapClass = 'mceNoWrap';
+ this.toolbarClass = 'mceToolbar';
+ this.selectListArrowClass = 'mceOpen';
this.setDisabled(true);
},
'aria-orientation' : 'vertical',
tabindex : '-1'});
return '</td>' +
- '<td style="position: relative" class="' + this.groupEndClass + '">' + separator + '</td>' +
+ '<td style="position: relative" class="' + this.groupEndPlaceholder + '">' + separator + '</td>' +
'<td style="position: relative" class="' + this.groupStartClass + ' ' + this.wrapClass + '">' + separator + '</td>';
- }
+ },
+
+ postRender : function() {
+ var self = this;
+ // Add a class to the item prior to the wrap.
+ YUI().use('node', function(Y) {
+ var endGroupElements = tinymce.DOM.select('td.' + self.groupEndPlaceholder),
+ index = 0, curElement, endOfLast,
+ endBarElements = tinymce.DOM.select('td.' + self.toolbarEndClass);
+ for (index = 0; index < endGroupElements.length; index++) {
+ if (!endGroupElements.hasOwnProperty(index)) {
+ continue;
+ }
+ curElement = Y.one(endGroupElements[index]);
+ endOfLast = curElement.previous('td').previous('td');
+ if (endOfLast) {
+ endOfLast.addClass(self.groupEndClass);
+ }
+ }
+ for (index = 0; index < endBarElements.length; index++) {
+ if (!endBarElements.hasOwnProperty(index)) {
+ continue;
+ }
+ curElement = Y.one(endBarElements[index]);
+ endOfLast = curElement.previous('td');
+ if (endOfLast) {
+ endOfLast.addClass(self.groupEndClass);
+ }
+ }
+ // Any separators closer together than 5 buttons get the noWrapClass.
+ var toolbars = Y.all('table.' + self.toolbarClass),
+ buttonWrapPoint = 5;
+ toolbars.each(function(toolbar) {
+ var count = 0;
+ widgets = toolbar.all('td.' + self.wrapClass + ', td > a');
+ widgets.each(function(widget) {
+ if (widget.hasClass(self.wrapClass)) {
+ if (count >= buttonWrapPoint) {
+ count = 0;
+ } else {
+ widget.addClass(self.noWrapClass);
+ }
+ } else {
+ if (widget.hasClass(self.selectListArrowClass) ||
+ (widget.getAttribute('role') === 'button')) {
+ count++;
+ } else {
+ // Count select inputs as 3 buttons. The down arrow on the select also gets counted so 2+1 = 3.
+ count += 2;
+ }
+ }
+ });
+ });
+
+ });
+ }
});
tinymce.create('tinymce.plugins.wrapPlugin', {
require_once(__DIR__.'/adminlib.php');
$settings->add(new tiynce_subplugins_settings());
$settings->add(new admin_setting_heading('tinymcegeneralheader', new lang_string('settings'), ''));
- $default = "formatselect,bold,italic,wrap,bullist,numlist,|,link,unlink,|,image
+ $default = "wrap,formatselect,wrap,bold,italic,wrap,bullist,numlist,wrap,link,unlink,wrap,image
-undo,redo,|,underline,strikethrough,sub,sup,|,justifyleft,justifycenter,justifyright,wrap,outdent,indent,|,forecolor,backcolor,|,ltr,rtl,|,nonbreaking,charmap,table
+undo,redo,wrap,underline,strikethrough,sub,sup,wrap,justifyleft,justifycenter,justifyright,wrap,outdent,indent,wrap,forecolor,backcolor,wrap,ltr,rtl,wrap,nonbreaking,charmap,table
-fontselect,fontsizeselect,wrap,code,search,replace,|,cleanup,removeformat,pastetext,pasteword,|,fullscreen";
+fontselect,fontsizeselect,wrap,code,search,replace,wrap,cleanup,removeformat,pastetext,pasteword,wrap,fullscreen";
$settings->add(new admin_setting_configtextarea('editor_tinymce/customtoolbar',
get_string('customtoolbar', 'editor_tinymce'), get_string('customtoolbar_desc', 'editor_tinymce', 'http://www.tinymce.com/wiki.php/TinyMCE3x:Buttons/controls'), $default, PARAM_RAW, 100, 8));
$settings->add(new admin_setting_configtextarea('editor_tinymce/fontselectlist',
float: left;
display: inline-block;
}
- .mceToolbar .mceWrap {
+ .moodleSkin .mceLayout .mceToolbar .mceWrap {
clear: left;
+ width: 100%;
+ height: 8px;
+ }
+ .moodleSkin .mceLayout .mceToolbar .mceNoWrap {
+ clear: none;
+ width: 0px;
}
.o2k7Skin tr.mceLast .mceToolbar tr td.mceWrap,
]>\r
<svg version="1.1"\r
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"\r
- x="0px" y="0px" width="16px" height="16px" viewBox="-0.3 -3.6 16 16"\r
- style="overflow:visible;enable-background:new -0.3 -3.6 16 16;" xml:space="preserve" preserveAspectRatio="xMinYMid meet">\r
+ x="0px" y="0px" width="16px" height="16px" viewBox="0 -3.9 16 16" style="overflow:visible;enable-background:new 0 -3.9 16 16;"\r
+ xml:space="preserve" preserveAspectRatio="xMinYMid meet">\r
<defs>\r
</defs>\r
-<path style="fill:#999999;" d="M0,8c0.3,0,0.5-0.2,0.7-0.4c0.1-0.2,0.3-0.5,0.5-1L3.8,0h0.3l2.7,6.3C7.2,7,7.4,7.5,7.5,7.7\r
- S7.9,8,8.2,8v0.3H4.3V8c0.4,0,0.6,0,0.8-0.1s0.2-0.2,0.2-0.4c0-0.1,0-0.2-0.1-0.4c0-0.1-0.1-0.2-0.2-0.4L4.7,5.9H2\r
- C1.8,6.4,1.7,6.7,1.6,6.8C1.5,7.2,1.5,7.4,1.5,7.5c0,0.2,0.1,0.3,0.3,0.4C2,7.9,2.2,8,2.4,8v0.3H0V8z M4.5,5.4L3.4,2.7H3.2L2.2,5.4\r
- H4.5z M9.8,0.2v3.1C10,3.1,10.1,3,10.3,2.9c0.3-0.2,0.6-0.2,0.9-0.2c0.7,0,1.2,0.3,1.6,0.8s0.6,1.2,0.6,1.9c0,1-0.3,1.8-0.8,2.3\r
- s-1.2,0.8-2,0.8c-0.3,0-0.6-0.1-0.8-0.2S9.4,8,9.2,7.8L8.3,8.4H8.1V1.1c0-0.3,0-0.4-0.1-0.5C7.9,0.5,7.7,0.5,7.5,0.5V0.2H9.8z\r
- M9.9,7.6c0.1,0.3,0.4,0.4,0.7,0.4c0.4,0,0.7-0.2,0.9-0.7c0.2-0.4,0.3-1,0.3-1.8c0-0.6-0.1-1.2-0.2-1.6s-0.4-0.7-0.9-0.7\r
- c-0.3,0-0.5,0.1-0.6,0.3C9.9,3.7,9.8,3.9,9.8,4v3.2C9.8,7.3,9.8,7.5,9.9,7.6z M14,6.7c0.2-0.2,0.4-0.3,0.7-0.3s0.5,0.1,0.7,0.3\r
- s0.3,0.4,0.3,0.7S15.6,8,15.4,8.1S15,8.4,14.7,8.4S14.2,8.3,14,8.1s-0.3-0.4-0.3-0.7S13.8,6.9,14,6.7z"/>\r
+<path style="fill:#999999;" d="M15,6.2c0.3,0,0.5,0.1,0.7,0.3S16,6.9,16,7.1s-0.1,0.5-0.3,0.7S15.3,8.1,15,8.1S14.5,8,14.3,7.8\r
+ S14,7.4,14,7.1s0.1-0.5,0.3-0.7S14.8,6.2,15,6.2z M10.3,0v3c0.5-0.5,1-0.7,1.5-0.7c0.4,0,0.7,0.1,1.1,0.3s0.6,0.5,0.8,0.9\r
+ s0.3,0.9,0.3,1.4c0,0.6-0.1,1.1-0.4,1.6s-0.6,0.9-1,1.1s-0.9,0.4-1.4,0.4c-0.3,0-0.6,0-0.8-0.1S10,7.7,9.8,7.5L8.8,8.1H8.7v-7\r
+ c0-0.3,0-0.5,0-0.6c0-0.1-0.1-0.2-0.2-0.2S8.2,0.2,8,0.2V0H10.3z M10.3,3.5V6c0,0.5,0,0.8,0,0.9c0,0.2,0.1,0.4,0.3,0.6\r
+ s0.3,0.2,0.6,0.2c0.2,0,0.4-0.1,0.5-0.2s0.3-0.3,0.4-0.7s0.1-0.9,0.1-1.8c0-0.8-0.1-1.4-0.3-1.7C11.7,3.1,11.5,3,11.2,3\r
+ C10.9,3,10.6,3.2,10.3,3.5z M4.9,5.8H2.1L1.8,6.6C1.6,6.9,1.6,7.1,1.6,7.3c0,0.2,0.1,0.4,0.3,0.5C2,7.8,2.2,7.9,2.6,7.9v0.2H0V7.9\r
+ c0.3,0,0.5-0.2,0.7-0.4s0.4-0.6,0.7-1.2L4.2,0h0.1l2.9,6.5c0.3,0.6,0.5,1,0.7,1.2C8,7.8,8.2,7.9,8.4,7.9v0.2H4.6V7.9h0.2\r
+ c0.3,0,0.5,0,0.7-0.1c0.1-0.1,0.1-0.2,0.1-0.3c0-0.1,0-0.1,0-0.2c0,0-0.1-0.2-0.2-0.4L4.9,5.8z M4.7,5.4L3.5,2.7L2.3,5.4H4.7z"/>\r
</svg>\r
]>\r
<svg version="1.1"\r
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"\r
- x="0px" y="0px" width="16px" height="16px" viewBox="0 -4.6 16 16" style="overflow:visible;enable-background:new 0 -4.6 16 16;"\r
+ x="0px" y="0px" width="16px" height="16px" viewBox="0 -3.9 16 16" style="overflow:visible;enable-background:new 0 -3.9 16 16;"\r
xml:space="preserve" preserveAspectRatio="xMinYMid meet">\r
<defs>\r
</defs>\r
-<path style="fill:#999999;" d="M0,6c0.2,0,0.4-0.1,0.5-0.3c0.1-0.1,0.2-0.4,0.4-0.8l2-4.9h0.3l2,4.7c0.2,0.5,0.4,0.9,0.5,1\r
- C5.8,5.9,5.9,6,6.1,6v0.2H3.2V6c0.3,0,0.5,0,0.6-0.1c0.1,0,0.1-0.1,0.1-0.3c0-0.1,0-0.2-0.1-0.3c0-0.1-0.1-0.2-0.1-0.3L3.5,4.4h-2\r
- C1.4,4.8,1.3,5,1.2,5.1C1.2,5.4,1.1,5.5,1.1,5.6c0,0.1,0.1,0.2,0.3,0.3C1.5,6,1.6,6,1.8,6v0.2H0V6z M3.4,4.1L2.5,2H2.4L1.6,4.1H3.4z\r
- M5.6,6c0.2,0,0.4,0,0.5-0.1c0.2-0.1,0.3-0.3,0.3-0.5V1c0-0.3-0.1-0.4-0.3-0.5C6,0.4,5.8,0.4,5.6,0.4V0.1h2.8c0.5,0,1,0.1,1.3,0.2\r
- c0.7,0.2,1,0.7,1,1.3c0,0.4-0.1,0.7-0.4,0.9C10,2.7,9.7,2.8,9.4,2.9V3C9.7,3,10,3.1,10.3,3.3C10.8,3.6,11,4,11,4.5\r
- c0,0.5-0.2,0.9-0.7,1.2C9.9,6.1,9.2,6.2,8.5,6.2H5.6V6z M8.9,2.6c0.2-0.2,0.3-0.5,0.3-1C9.2,1.3,9.2,1,9,0.7\r
- C8.9,0.5,8.7,0.4,8.3,0.4c-0.2,0-0.3,0-0.4,0.1S7.8,0.7,7.8,0.8v2C8.4,2.9,8.7,2.8,8.9,2.6z M7.9,5.7C8,5.9,8.1,5.9,8.3,5.9\r
- c0.4,0,0.7-0.1,0.9-0.3c0.2-0.2,0.3-0.6,0.3-1c0-0.6-0.2-1.1-0.6-1.3C8.7,3.2,8.3,3.1,7.8,3.1v2.2C7.8,5.5,7.8,5.6,7.9,5.7z\r
- M15.2,0.2c0.4,0.1,0.6,0.2,0.6,0.2c0.1,0,0.2,0,0.3-0.1c0.1-0.1,0.1-0.2,0.1-0.3h0.3v2.1h-0.2C16,1.7,15.8,1.3,15.6,1\r
- c-0.4-0.5-0.9-0.7-1.4-0.7c-0.6,0-1,0.3-1.3,0.8c-0.3,0.5-0.4,1.2-0.4,2.1c0,0.6,0.1,1.2,0.2,1.6c0.3,0.8,0.8,1.2,1.5,1.2\r
- c0.5,0,0.9-0.1,1.3-0.4c0.2-0.1,0.5-0.4,0.8-0.7l0.3,0.2c-0.4,0.4-0.7,0.7-0.9,0.9c-0.5,0.3-1,0.5-1.6,0.5c-0.9,0-1.6-0.3-2.2-0.8\r
- c-0.7-0.6-1-1.4-1-2.4c0-1,0.3-1.8,1-2.4C12.4,0.3,13.2,0,14,0C14.4,0,14.8,0.1,15.2,0.2z"/>\r
+<path style="fill:#999999;" d="M13.8,3.8C14.5,4,15,4.2,15.3,4.5C15.8,4.8,16,5.3,16,5.9c0,0.6-0.2,1.1-0.7,1.5\r
+ c-0.6,0.5-1.4,0.7-2.6,0.7h-4V7.8c0.4,0,0.6,0,0.7-0.1s0.2-0.2,0.3-0.3s0.1-0.4,0.1-0.8V1.5c0-0.4,0-0.7-0.1-0.8S9.5,0.5,9.4,0.4\r
+ S9,0.3,8.7,0.3V0.1h3.8c0.9,0,1.6,0.1,1.9,0.2s0.7,0.4,0.9,0.7s0.3,0.7,0.3,1c0,0.4-0.1,0.7-0.4,1S14.5,3.7,13.8,3.8z M11.6,4.1v2.5\r
+ l0,0.3c0,0.2,0.1,0.4,0.2,0.5s0.3,0.2,0.5,0.2c0.3,0,0.6-0.1,0.9-0.2s0.5-0.3,0.6-0.6s0.2-0.6,0.2-0.9c0-0.4-0.1-0.7-0.3-1\r
+ S13.3,4.4,13,4.3S12.2,4.1,11.6,4.1z M11.6,3.7c0.6,0,1-0.1,1.2-0.2s0.5-0.3,0.6-0.5s0.2-0.5,0.2-0.9s-0.1-0.6-0.2-0.9\r
+ s-0.3-0.4-0.6-0.5s-0.7-0.2-1.2-0.2V3.7z M4.9,5.8H2.1L1.8,6.6C1.6,6.9,1.6,7.1,1.6,7.3c0,0.2,0.1,0.4,0.3,0.5\r
+ C2,7.8,2.2,7.9,2.6,7.9v0.2H0V7.9c0.3,0,0.5-0.2,0.7-0.4s0.4-0.6,0.7-1.2L4.2,0h0.1l2.9,6.5c0.3,0.6,0.5,1,0.7,1.2\r
+ C8,7.8,8.2,7.9,8.4,7.9v0.2H4.6V7.9h0.2c0.3,0,0.5,0,0.7-0.1c0.1-0.1,0.1-0.2,0.1-0.3c0-0.1,0-0.1,0-0.2c0,0-0.1-0.2-0.2-0.4\r
+ L4.9,5.8z M4.7,5.4L3.5,2.7L2.3,5.4H4.7z"/>\r
</svg>\r
xml:space="preserve" preserveAspectRatio="xMinYMid meet">\r
<defs>\r
</defs>\r
-<path style="fill:#999999;" d="M0,0h16v2H0V0z M0,6h16v2H0V6z M0,12h16v2H0V12z M3,3h10v2H3V3z M3,9h10v2H3V9z"/>\r
+<path style="fill:#999999;" d="M0,12h16v2H0V12z M13,9H3v2h10V9z M0,8h16V6H0V8z M13,3H3v2h10V3z M16,0H0v2h16V0z"/>\r
</svg>\r
xml:space="preserve" preserveAspectRatio="xMinYMid meet">\r
<defs>\r
</defs>\r
-<path style="fill:#999999;" d="M0,0h16v2H0V0z M0,3h10v2H0V3z M0,6h16v2H0V6z M0,9h10v2H0V9z M0,12h16v2H0V12z"/>\r
+<path style="fill:#999999;" d="M0,12h16v2H0V12z M10,9H0v2h10V9z M0,8h16V6H0V8z M10,3H0v2h10V3z M16,2V0H0v2H16z"/>\r
</svg>\r