var path = require('path'),
fs = require('fs'),
tasks = {},
- cwd = process.env.PWD || process.cwd();
+ cwd = process.env.PWD || process.cwd(),
+ inAMD = path.basename(cwd) == 'amd';
// Project configuration.
grunt.initConfig({
jshint: {
options: {jshintrc: '.jshintrc'},
- files: ['**/amd/src/*.js']
+ files: [inAMD ? cwd + '/src/*.js' : '**/amd/src/*.js']
},
uglify: {
dynamic_mappings: {
if (path.basename(path.resolve(cwd, '../../')) == 'yui') {
grunt.task.run('shifter');
// Are we in an AMD directory?
- } else if (path.basename(cwd) == 'amd') {
+ } else if (inAMD) {
grunt.task.run('amd');
} else {
// Run them all!.
// Create a page for general import configuration and defaults.
$temp = new admin_settingpage('importgeneralsettings', new lang_string('importgeneralsettings', 'backup'), 'moodle/backup:backupcourse');
$temp->add(new admin_setting_configtext('backup/import_general_maxresults', new lang_string('importgeneralmaxresults', 'backup'), new lang_string('importgeneralmaxresults_desc', 'backup'), 10));
+ $temp->add(new admin_setting_configcheckbox('backup/import_general_duplicate_admin_allowed',
+ new lang_string('importgeneralduplicateadminallowed', 'backup'),
+ new lang_string('importgeneralduplicateadminallowed_desc', 'backup'), 0));
$ADMIN->add('backups', $temp);
// Create a page for automated backups configuration and defaults.
$options = array();
foreach ($availablelangs as $alang) {
if (!empty($alang[0]) and trim($alang[0]) !== 'en' and !$controller->is_installed_lang($alang[0], $alang[1])) {
- $options[$alang[0]] = $alang[2].' ('.$alang[0].')';
+ $options[$alang[0]] = $alang[2].' ‎('.$alang[0].')‎';
}
}
if (!empty($options)) {
$this->data = $coursedata;
$this->enrolmentdata = tool_uploadcourse_helper::get_enrolment_data($this->rawdata);
+ if (isset($this->rawdata['tags']) && strval($this->rawdata['tags']) !== '') {
+ $this->data['tags'] = preg_split('/\s*,\s*/', trim($this->rawdata['tags']), -1, PREG_SPLIT_NO_EMPTY);
+ }
+
// Restore data.
// TODO Speed up things by not really extracting the backup just yet, but checking that
// the backup file or shortname passed are valid. Extraction should happen in proceed().
'groupmode' => '2',
'groupmodeforce' => '1',
'enablecompletion' => '1',
+ 'tags' => 'Cat, Dog',
'role_teacher' => 'Knight',
'role_manager' => 'Jedi',
$this->assertEquals($data['groupmode'], $course->groupmode);
$this->assertEquals($data['groupmodeforce'], $course->groupmodeforce);
$this->assertEquals($data['enablecompletion'], $course->enablecompletion);
+ $this->assertEquals($data['tags'], join(', ', core_tag_tag::get_item_tags_array('core', 'course', $course->id)));
// Roles.
$roleids = array();
'suspended', // 1 means suspend user account, 0 means activate user account, nothing means keep as is for existing users
'deleted', // 1 means delete user
'mnethostid', // Can not be used for adding, updating or deleting of users - only for enrolments, groups, cohorts and suspending.
+ 'interests',
);
// Include all name fields.
$STD_FIELDS = array_merge($STD_FIELDS, get_all_user_name_fields());
}
}
+ // Update user interests.
+ if (isset($user->interests) && strval($user->interests) !== '') {
+ useredit_update_interests($user, preg_split('/\s*,\s*/', $user->interests, -1, PREG_SPLIT_NO_EMPTY));
+ }
// add to cohort first, it might trigger enrolments indirectly - do NOT create cohorts here!
foreach ($filecolumns as $column) {
$descparams = new stdClass();
$descparams->atag = $atag;
$descparams->mode = get_string('debugnormal', 'admin');
- $amfclienturl = new moodle_url('/webservice/amf/testclient/index.php');
- $amfclientatag =html_writer::tag('a', get_string('amftestclient', 'webservice'),
- array('href' => $amfclienturl));
- $descparams->amfatag = $amfclientatag;
echo get_string('testclientdescription', 'webservice', $descparams);
echo $OUTPUT->box_end();
$data = (object)$data;
- if (!empty($CFG->usetags)) { // if enabled in server
- // TODO: This is highly inneficient. Each time we add one tag
- // we fetch all the existing because tag_set() deletes them
- // so everything must be reinserted on each call
- $tags = array();
- $existingtags = tag_get_tags('course', $this->get_courseid());
- // Re-add all the existitng tags
- foreach ($existingtags as $existingtag) {
- $tags[] = $existingtag->rawname;
- }
- // Add the one being restored
- $tags[] = $data->rawname;
- // Send all the tags back to the course
- tag_set('course', $this->get_courseid(), $tags, 'core',
- context_course::instance($this->get_courseid())->id);
- }
+ core_tag_tag::add_item_tag('core', 'course', $this->get_courseid(),
+ context_course::instance($this->get_courseid()), $data->rawname);
}
public function process_allowed_module($data) {
return;
}
- if (!empty($CFG->usetags)) { // if enabled in server
- // TODO: This is highly inefficient. Each time we add one tag
- // we fetch all the existing because tag_set() deletes them
- // so everything must be reinserted on each call
- $tags = array();
- $existingtags = tag_get_tags('question', $newquestion);
- // Re-add all the existitng tags
- foreach ($existingtags as $existingtag) {
- $tags[] = $existingtag->rawname;
- }
- // Add the one being restored
- $tags[] = $data->rawname;
+ if (core_tag_tag::is_enabled('core_question', 'question')) {
+ $tagname = $data->rawname;
// Get the category, so we can then later get the context.
$categoryid = $this->get_new_parentid('question_category');
if (empty($this->cachedcategory) || $this->cachedcategory->id != $categoryid) {
$this->cachedcategory = $DB->get_record('question_categories', array('id' => $categoryid));
}
- // Send all the tags back to the question
- tag_set('question', $newquestion, $tags, 'core_question', $this->cachedcategory->contextid);
+ // Add the tag to the question.
+ core_tag_tag::add_item_tag('core_question', 'question', $newquestion,
+ context::instance_by_id($this->cachedcategory->contextid),
+ $tagname);
}
}
}
// Process tags
- if (!empty($CFG->usetags) && isset($user->tags)) { // if enabled in server and present in backup
+ if (core_tag_tag::is_enabled('core', 'user') && isset($user->tags)) { // If enabled in server and present in backup.
$tags = array();
foreach($user->tags['tag'] as $usertag) {
$usertag = (object)$usertag;
$tags[] = $usertag->rawname;
}
- if (empty($newuserctxid)) {
- $newuserctxid = null; // Tag apis expect a null contextid not 0.
- }
- tag_set('user', $newuserid, $tags, 'core', $newuserctxid);
+ core_tag_tag::set_item_tags('core', 'user', $newuserid,
+ context_user::instance($newuserid), $tags);
}
// Process preferences
* 1F - None of the above, return true => User needs to be created
*
* if restoring from another site backup (cannot match by id here, replace it by email/firstaccess combination):
- * 2A - Normal check: If match by username and mnethost and (email or non-zero firstaccess) => ok, return target user
+ * 2A - Normal check:
+ * 2A1 - If match by username and mnethost and (email or non-zero firstaccess) => ok, return target user
+ * 2A2 - Exceptional handling (MDL-21912): Match "admin" username. Then, if import_general_duplicate_admin_allowed is
+ * enabled, attempt to map the admin user to the user 'admin_[oldsiteid]' if it exists. If not,
+ * the user 'admin_[oldsiteid]' will be created in precheck_included users
* 2B - Handle users deleted in DB and "alive" in backup file:
* 2B1 - If match by mnethost and user is deleted in DB and not empty email = md5(username) and
* (username LIKE 'backup_email.%' or non-zero firstaccess) => ok, return target user
* Note: for DB deleted users md5(username) is stored *sometimes* in the email field,
* hence we are looking there for usernames if not empty. See delete_user()
*/
- protected static function precheck_user($user, $samesite) {
+ protected static function precheck_user($user, $samesite, $siteid = null) {
global $CFG, $DB;
// Handle checks from same site backups
// Handle checks from different site backups
} else {
- // 2A - If match by username and mnethost and
+ // 2A1 - If match by username and mnethost and
// (email or non-zero firstaccess) => ok, return target user
if ($rec = $DB->get_record_sql("SELECT *
FROM {user} u
return $rec; // Matching user found, return it
}
+ // 2A2 - If we're allowing conflicting admins, attempt to map user to admin_[oldsiteid].
+ if (get_config('backup', 'import_general_duplicate_admin_allowed') && $user->username === 'admin' && $siteid
+ && $user->mnethostid == $CFG->mnet_localhost_id) {
+ if ($rec = $DB->get_record('user', array('username' => 'admin_' . $siteid))) {
+ return $rec;
+ }
+ }
+
// 2B - Handle users deleted in DB and "alive" in backup file
// Note: for DB deleted users email is stored in username field, hence we
// are looking there for emails. See delete_user()
// Calculate the context we are going to use for capability checking
$context = context_course::instance($courseid);
+ // When conflicting users are detected we may need original site info.
+ $restoreinfo = restore_controller_dbops::load_controller($restoreid)->get_info();
+
// Calculate if we have perms to create users, by checking:
// to 'moodle/restore:createuser' and 'moodle/restore:userinfo'
// and also observe $CFG->disableusercreationonrestore
}
// Now, precheck that user and, based on returned results, annotate action/problem
- $usercheck = self::precheck_user($user, $samesite);
+ $usercheck = self::precheck_user($user, $samesite, $restoreinfo->original_site_identifier_hash);
if (is_object($usercheck)) { // No problem, we have found one user in DB to be mapped to
// Annotate it, for later process. Set newitemid to mapping user->id
self::set_backup_ids_record($restoreid, 'user', $recuser->itemid, $usercheck->id);
} else if ($usercheck === false) { // Found conflict, report it as problem
- $problems[] = get_string('restoreuserconflict', '', $user->username);
+ if (!get_config('backup', 'import_general_duplicate_admin_allowed')) {
+ $problems[] = get_string('restoreuserconflict', '', $user->username);
+ } else if ($user->username == 'admin') {
+ if (!$cancreateuser) {
+ $problems[] = get_string('restorecannotcreateuser', '', $user->username);
+ }
+ if ($user->mnethostid != $CFG->mnet_localhost_id) {
+ $problems[] = get_string('restoremnethostidmismatch', '', $user->username);
+ }
+ if (!$problems) {
+ // Duplicate admin allowed, append original site idenfitier to username.
+ $user->username .= '_' . $restoreinfo->original_site_identifier_hash;
+ self::set_backup_ids_record($restoreid, 'user', $recuser->itemid, 0, null, (array)$user);
+ }
+ }
} else if ($usercheck === true) { // User needs to be created, check if we are able
if ($cancreateuser) { // Can create user, set newitemid to 0 so will be created later
--- /dev/null
+@block @block_blog_menu
+Feature: Enable Block blog menu in a course
+ In order to enable the blog menu in a course
+ As a teacher
+ I can add blog menu block to a course
+
+ Background:
+ Given the following "users" exist:
+ | username | firstname | lastname | email | idnumber |
+ | teacher1 | Teacher | 1 | teacher1@example.com | T1 |
+ And the following "courses" exist:
+ | fullname | shortname | category |
+ | Course 1 | C1 | 0 |
+ And the following "course enrolments" exist:
+ | user | course | role |
+ | teacher1 | C1 | editingteacher |
+
+ Scenario: Add the block to a the course when blogs are disabled
+ Given I log in as "admin"
+ And the following config values are set as admin:
+ | enableblogs | 0 |
+ And I log out
+ And I log in as "teacher1"
+ And I follow "Course 1"
+ And I turn editing mode on
+ When I add the "Blog menu" block
+ Then I should see "Blogging is disabled!" in the "Blog menu" "block"
+
+ Scenario: Add the block to a the course when blog associations are disabled
+ Given I log in as "admin"
+ And the following config values are set as admin:
+ | useblogassociations | 0 |
+ And I log out
+ And I log in as "teacher1"
+ And I follow "Course 1"
+ And I turn editing mode on
+ When I add the "Blog menu" block
+ Then I should see "Blog entries" in the "Blog menu" "block"
+ And I should see "Add a new entry" in the "Blog menu" "block"
+ And I should not see "View all entries for this course" in the "Blog menu" "block"
+ And I should not see "View my entries about this course" in the "Blog menu" "block"
+ And I should not see "Add an entry about this course" in the "Blog menu" "block"
+
+ Scenario: Add the block to a the course when blog associations are enabled
+ Given I log in as "admin"
+ And the following config values are set as admin:
+ | useblogassociations | 1 |
+ And I log out
+ And I log in as "teacher1"
+ And I follow "Course 1"
+ And I turn editing mode on
+ When I add the "Blog menu" block
+ Then I should see "Blog entries" in the "Blog menu" "block"
+ And I should see "Add a new entry" in the "Blog menu" "block"
+ And I should see "View all entries for this course" in the "Blog menu" "block"
+ And I should see "View my entries about this course" in the "Blog menu" "block"
+ And I should see "Add an entry about this course" in the "Blog menu" "block"
+
+ Scenario: Add the block to a the course when RSS is disabled
+ Given I log in as "admin"
+ And the following config values are set as admin:
+ | enablerssfeeds | 0 |
+ And I log out
+ And I log in as "teacher1"
+ And I follow "Course 1"
+ And I turn editing mode on
+ When I add the "Blog menu" block
+ Then I should not see "Blog RSS feed" in the "Blog menu" "block"
+ And I should see "Add a new entry" in the "Blog menu" "block"
+
+ Scenario: Add the block to a the course when RSS is enabled
+ Given I log in as "admin"
+ And the following config values are set as admin:
+ | enablerssfeeds | 1 |
+ And I log out
+ And I log in as "teacher1"
+ And I follow "Course 1"
+ And I turn editing mode on
+ When I add the "Blog menu" block
+ Then I should see "Blog RSS feed" in the "Blog menu" "block"
+ And I should see "Add a new entry" in the "Blog menu" "block"
--- /dev/null
+@block @block_blog_menu
+Feature: Enable Block blog menu in a course
+ In order to enable the blog menu in a course
+ As a teacher
+ I can add blog menu block to a course
+
+ Background:
+ Given the following "users" exist:
+ | username | firstname | lastname | email | idnumber |
+ | teacher1 | Teacher | 1 | teacher1@example.com | T1 |
+ | student1 | Student | 1 | student1@example.com | S1 |
+ | student2 | Student | 2 | student2@example.com | S2 |
+ And the following "courses" exist:
+ | fullname | shortname | category |
+ | Course 1 | C1 | 0 |
+ And the following "course enrolments" exist:
+ | user | course | role |
+ | teacher1 | C1 | editingteacher |
+ | student1 | C1 | student |
+ | student2 | C1 | student |
+ And I log in as "teacher1"
+ And I follow "Course 1"
+ And I turn editing mode on
+ And I add the "Blog menu" block
+ And I log out
+
+ Scenario: Students use the blog menu block to post blogs
+ Given I log in as "student1"
+ And I follow "Course 1"
+ And I follow "Add a new entry"
+ When I set the following fields to these values:
+ | Entry title | S1 First Blog |
+ | Blog entry body | This is my awesome blog! |
+ And I press "Save changes"
+ Then I should see "S1 First Blog"
+ And I should see "This is my awesome blog!"
+ And I follow "Dashboard"
+ And I follow "Course 1"
+ And I follow "Blog entries"
+ And I should see "S1 First Blog"
+ And I should see "This is my awesome blog!"
+
+ Scenario: Students use the blog menu block to view their blogs about the course
+ Given I log in as "student1"
+ And I follow "Course 1"
+ And I follow "Add an entry about this course"
+ And I set the following fields to these values:
+ | Entry title | S1 First Blog |
+ | Blog entry body | This is my awesome blog about this course! |
+ And I press "Save changes"
+ And I should see "S1 First Blog"
+ And I should see "This is my awesome blog about this course!"
+ And I should see "Associated Course: C1"
+ And I log out
+ And I log in as "student2"
+ And I follow "Course 1"
+ And I follow "Add a new entry"
+ And I set the following fields to these values:
+ | Entry title | S2 Second Blog |
+ | Blog entry body | My unrelated blog! |
+ And I press "Save changes"
+ And I should see "S2 Second Blog"
+ And I should see "My unrelated blog!"
+ And I should not see "Associated Course: C1"
+ And I follow "Dashboard"
+ And I follow "Course 1"
+ And I follow "Add an entry about this course"
+ And I set the following fields to these values:
+ | Entry title | S2 First Blog |
+ | Blog entry body | My course blog is better! |
+ And I press "Save changes"
+ And I should see "S2 First Blog"
+ And I should see "My course blog is better!"
+ And I should see "Associated Course: C1"
+ And I follow "Dashboard"
+ And I follow "Course 1"
+ When I follow "View my entries about this course"
+ Then I should see "S2 First Blog"
+ And I should not see "S2 Second Blog"
+ And I should not see "S1 First Blog"
+
+ Scenario: Students use the blog menu block to view all blogs about the course
+ Given I log in as "student1"
+ And I follow "Course 1"
+ And I follow "Add an entry about this course"
+ And I set the following fields to these values:
+ | Entry title | S1 First Blog |
+ | Blog entry body | This is my awesome blog about this course! |
+ And I press "Save changes"
+ And I should see "S1 First Blog"
+ And I should see "This is my awesome blog about this course!"
+ And I should see "Associated Course: C1"
+ And I log out
+ And I log in as "student2"
+ And I follow "Course 1"
+ And I follow "Add a new entry"
+ And I set the following fields to these values:
+ | Entry title | S2 Second Blog |
+ | Blog entry body | My unrelated blog! |
+ And I press "Save changes"
+ And I should see "S2 Second Blog"
+ And I should see "My unrelated blog!"
+ And I should not see "Associated Course: C1"
+ And I follow "Dashboard"
+ And I follow "Course 1"
+ And I follow "Add an entry about this course"
+ And I set the following fields to these values:
+ | Entry title | S2 First Blog |
+ | Blog entry body | My course blog is better! |
+ And I press "Save changes"
+ And I should see "S2 First Blog"
+ And I should see "My course blog is better!"
+ And I should see "Associated Course: C1"
+ And I follow "Dashboard"
+ And I follow "Course 1"
+ When I follow "View all entries for this course"
+ Then I should see "S1 First Blog"
+ And I should see "S2 First Blog"
+ And I should not see "S2 Second Blog"
+
+ Scenario: Students use the blog menu block to view all their blog entries
+ Given I log in as "student1"
+ And I follow "Course 1"
+ And I follow "Add an entry about this course"
+ And I set the following fields to these values:
+ | Entry title | S1 First Blog |
+ | Blog entry body | This is my awesome blog about this course! |
+ And I press "Save changes"
+ And I should see "S1 First Blog"
+ And I should see "This is my awesome blog about this course!"
+ And I should see "Associated Course: C1"
+ And I log out
+ And I log in as "student2"
+ And I follow "Course 1"
+ And I follow "Add a new entry"
+ And I set the following fields to these values:
+ | Entry title | S2 Second Blog |
+ | Blog entry body | My unrelated blog! |
+ And I press "Save changes"
+ And I should see "S2 Second Blog"
+ And I should see "My unrelated blog!"
+ And I should not see "Associated Course: C1"
+ And I follow "Dashboard"
+ And I follow "Course 1"
+ And I follow "Add an entry about this course"
+ And I set the following fields to these values:
+ | Entry title | S2 First Blog |
+ | Blog entry body | My course blog is better! |
+ And I press "Save changes"
+ And I should see "S2 First Blog"
+ And I should see "My course blog is better!"
+ And I should see "Associated Course: C1"
+ And I follow "Dashboard"
+ And I follow "Course 1"
+ When I follow "Blog entries"
+ Then I should see "S2 First Blog"
+ And I should see "S2 Second Blog"
+ And I should not see "S1 First Blog"
+
+ Scenario: Teacher searches for student blogs
+ Given I log in as "student1"
+ And I follow "Course 1"
+ And I follow "Add an entry about this course"
+ And I set the following fields to these values:
+ | Entry title | S1 First Blog |
+ | Blog entry body | This is my awesome blog about this course! |
+ And I press "Save changes"
+ And I should see "S1 First Blog"
+ And I should see "This is my awesome blog about this course!"
+ And I should see "Associated Course: C1"
+ And I log out
+ And I log in as "student2"
+ And I follow "Course 1"
+ And I follow "Add a new entry"
+ And I set the following fields to these values:
+ | Entry title | S2 Second Blog |
+ | Blog entry body | My unrelated blog! |
+ And I press "Save changes"
+ And I should see "S2 Second Blog"
+ And I should see "My unrelated blog!"
+ And I should not see "Associated Course: C1"
+ And I follow "Dashboard"
+ And I follow "Course 1"
+ And I follow "Add an entry about this course"
+ And I set the following fields to these values:
+ | Entry title | S2 First Blog |
+ | Blog entry body | My course blog is better! |
+ And I press "Save changes"
+ And I should see "S2 First Blog"
+ And I should see "My course blog is better!"
+ And I should see "Associated Course: C1"
+ And I log out
+ When I log in as "teacher1"
+ And I follow "Course 1"
+ And I set the field "blogsearchquery" to "First"
+ And I press "Search"
+ Then I should see "S1 First Blog"
+ And I should see "S2 First Blog"
+ And I should not see "S2 Second Blog"
--- /dev/null
+@block @block_blog_menu
+Feature: Enable Block blog menu on the frontpage
+ In order to enable the blog menu on the frontpage
+ As an admin
+ I can add blog menu block to the frontpage
+
+ Background:
+ Given the following "users" exist:
+ | username | firstname | lastname | email | idnumber |
+ | student1 | Student | 1 | student1@example.com | S1 |
+ And I log in as "admin"
+ And I am on site homepage
+ And I navigate to "Turn editing on" node in "Front page settings"
+ And I add the "Blog menu" block
+ And I log out
+
+ Scenario: Students use the blog menu block to post blogs
+ Given I log in as "student1"
+ And I am on site homepage
+ And I follow "Add a new entry"
+ When I set the following fields to these values:
+ | Entry title | S1 First Blog |
+ | Blog entry body | This is my awesome blog! |
+ And I press "Save changes"
+ Then I should see "S1 First Blog"
+ And I should see "This is my awesome blog!"
+ And I am on site homepage
+ And I follow "Blog entries"
+ And I should see "S1 First Blog"
+ And I should see "This is my awesome blog!"
}
return $this->content;
- } else if (empty($CFG->usetags)) {
+ } else if (!core_tag_tag::is_enabled('core', 'post')) {
$this->content = new stdClass();
$this->content->text = '';
if ($this->page->user_is_editing()) {
WHERE t.id = ti.tagid AND p.id = ti.itemid
$type
AND ti.itemtype = 'post'
+ AND ti.component = 'core'
AND ti.timemodified > $timewithin";
if ($context->contextlevel == CONTEXT_MODULE) {
}
$blogurl->param('tagid', $tag->id);
- $link = html_writer::link($blogurl, tag_display_name($tag), array('class'=>$tag->class, 'title'=>get_string('numberofentries','blog',$tag->ct)));
+ $link = html_writer::link($blogurl, core_tag_tag::make_display_name($tag),
+ array('class' => $tag->class,
+ 'title' => get_string('numberofentries', 'blog', $tag->ct)));
$this->content->text .= '<li>' . $link . '</li> ';
}
$this->content->text .= "\n</ul>\n";
* You don't need to override this if you 're satisfied with the above
*
* @deprecated since Moodle 2.9 MDL-49385 - Please use Admin Settings functionality to save block configuration.
- * @todo MDL-49553 This will be deleted in Moodle 3.1
- * @param array $data
- * @return boolean
*/
function config_save($data) {
- debugging('config_save($data) is deprecated, use Admin Settings functionality to save block configuration.', DEBUG_DEVELOPER);
- foreach ($data as $name => $value) {
- set_config($name, $value);
- }
- return true;
+ throw new coding_exception('config_save() can not be used any more, use Admin Settings functionality to save block configuration.');
}
/**
// descending order. The call to default sort order here will use
// that unless the discussion that post is in has a timestart set
// in the future.
- $sort = forum_get_default_sort_order(true, 'p.modified');
+ // This sort will ignore pinned posts as we want the most recent.
+ $sort = forum_get_default_sort_order(true, 'p.modified', 'd', false);
if (! $discussions = forum_get_discussions($cm, $sort, false,
$currentgroup, $this->page->course->newsitems) ) {
$text .= '('.get_string('nonews', 'forum').')';
global $CFG, $USER;
//note: do NOT include files at the top of this file
- require_once($CFG->dirroot.'/tag/lib.php');
require_once($CFG->libdir . '/filelib.php');
if ($this->content !== NULL) {
$tagid = optional_param('id', 0, PARAM_INT); // tag id - for backware compatibility
$tag = optional_param('tag', '', PARAM_TAG); // tag
+ $tc = optional_param('tc', 0, PARAM_INT); // Tag collection id.
- if ($tag) {
- $tagobject = tag_get('name', $tag);
- } else if ($tagid) {
- $tagobject = tag_get('id', $tagid);
+ if ($tagid) {
+ $tagobject = core_tag_tag::get($tagid);
+ } else if ($tag) {
+ $tagobject = core_tag_tag::get_by_name($tc, $tag);
}
if (empty($tagobject)) {
//include related tags in the photo query ?
$tagscsv = $tagobject->name;
if (!empty($this->config->includerelatedtags)) {
- $tagscsv .= ',' . tag_get_related_tags_csv(tag_get_related_tags($tagobject->id), TAG_RETURN_TEXT);
+ foreach ($tagobject->get_related_tags() as $t) {
+ $tagscsv .= ',' . $t->get_display_name(false);
+ }
}
$tagscsv = urlencode($tagscsv);
$mform->setType('config_title', PARAM_TEXT);
$mform->addElement('text', 'config_numberofphotos', get_string('numberofphotos', 'block_tag_flickr'), array('size' => 5));
- $mform->setType('config_numberofvideos', PARAM_INT);
+ $mform->setType('config_numberofphotos', PARAM_INT);
$mform->addElement('selectyesno', 'config_includerelatedtags', get_string('includerelatedtags', 'block_tag_flickr'));
$mform->setDefault('config_includerelatedtags', 0);
global $CFG;
//note: do NOT include files at the top of this file
- require_once($CFG->dirroot.'/tag/lib.php');
require_once($CFG->libdir . '/filelib.php');
if ($this->content !== NULL) {
$tagid = optional_param('id', 0, PARAM_INT); // tag id - for backware compatibility
$tag = optional_param('tag', '', PARAM_TAG); // tag
+ $tc = optional_param('tc', 0, PARAM_INT); // Tag collection id.
- if ($tag) {
- $tagobject = tag_get('name', $tag);
- } else if ($tagid) {
- $tagobject = tag_get('id', $tagid);
+ if ($tagid) {
+ $tagobject = core_tag_tag::get($tagid);
+ } else if ($tag) {
+ $tagobject = core_tag_tag::get_by_name($tc, $tag);
}
if (empty($tagobject)) {
$tagid = optional_param('id', 0, PARAM_INT); // tag id - for backware compatibility
$tag = optional_param('tag', '', PARAM_TAG); // tag
+ $tc = optional_param('tc', 0, PARAM_INT); // Tag collection id.
- if ($tag) {
- $tagobject = tag_get('name', $tag);
- } else if ($tagid) {
- $tagobject = tag_get('id', $tagid);
+ if ($tagid) {
+ $tagobject = core_tag_tag::get($tagid);
+ } else if ($tag) {
+ $tagobject = core_tag_tag::get_by_name($tc, $tag);
}
if (empty($tagobject)) {
--- /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/>.
+
+/**
+ * @package block_tags
+ * @copyright 2016 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+/**
+ * Specialised restore task for the tags block
+ * (using execute_after_tasks for recoding of tag collection id)
+ *
+ * @package block_tags
+ * @copyright 2016 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class restore_tags_block_task extends restore_block_task {
+
+ protected function define_my_settings() {
+ }
+
+ protected function define_my_steps() {
+ }
+
+ public function get_fileareas() {
+ return array(); // No associated fileareas.
+ }
+
+ public function get_configdata_encoded_attributes() {
+ return array(); // No special handling of configdata.
+ }
+
+ /**
+ * This function, executed after all the tasks in the plan
+ * have been executed, will remove tag collection reference in case block was restored into another site.
+ * Also get mapping of contextid.
+ */
+ public function after_restore() {
+ global $DB;
+
+ // Get the blockid.
+ $blockid = $this->get_blockid();
+
+ // Extract block configdata and remove tag collection reference if this is another site. Also map contextid.
+ if ($configdata = $DB->get_field('block_instances', 'configdata', array('id' => $blockid))) {
+ $config = unserialize(base64_decode($configdata));
+ $changed = false;
+ if (!empty($config->tagcoll) && $config->tagcoll > 1 && !$this->is_samesite()) {
+ $config->tagcoll = 0;
+ $changed = true;
+ }
+ if (!empty($config->ctx)) {
+ if ($ctxmap = restore_dbops::get_backup_ids_record($this->get_restoreid(), 'context', $config->ctx)) {
+ $config->ctx = $ctxmap->newitemid;
+ } else {
+ $config->ctx = 0;
+ }
+ $changed = true;
+ }
+ if ($changed) {
+ $configdata = base64_encode(serialize($config));
+ $DB->set_field('block_instances', 'configdata', $configdata, array('id' => $blockid));
+ }
+ }
+ }
+
+ static public function define_decode_contents() {
+ return array();
+ }
+
+ static public function define_decode_rules() {
+ return array();
+ }
+}
$this->config->numberoftags = 80;
}
+ if (empty($this->config->tagtype)) {
+ $this->config->tagtype = '';
+ }
+
+ if (empty($this->config->ctx)) {
+ $this->config->ctx = 0;
+ }
+
+ if (empty($this->config->rec)) {
+ $this->config->rec = 1;
+ }
+
+ if (empty($this->config->tagcoll)) {
+ $this->config->tagcoll = 0;
+ }
+
if ($this->content !== NULL) {
return $this->content;
}
// Get a list of tags.
- require_once($CFG->dirroot.'/tag/locallib.php');
-
- $this->content->text = tag_print_cloud(null, $this->config->numberoftags, true);
+ $tagcloud = core_tag_collection::get_tag_cloud($this->config->tagcoll,
+ $this->config->tagtype,
+ $this->config->numberoftags,
+ 'name', '', $this->page->context->id, $this->config->ctx, $this->config->rec);
+ $this->content->text = $OUTPUT->render_from_template('core_tag/tagcloud', $tagcloud->export_for_template($OUTPUT));
return $this->content;
}
*/
class block_tags_edit_form extends block_edit_form {
protected function specific_definition($mform) {
+ global $CFG;
// Fields for editing HTML block title and contents.
$mform->addElement('header', 'configheader', get_string('blocksettings', 'block'));
$mform->setType('config_title', PARAM_TEXT);
$mform->setDefault('config_title', get_string('pluginname', 'block_tags'));
+ $this->add_collection_selector($mform);
+
$numberoftags = array();
for ($i = 1; $i <= 200; $i++) {
$numberoftags[$i] = $i;
}
$mform->addElement('select', 'config_numberoftags', get_string('numberoftags', 'blog'), $numberoftags);
$mform->setDefault('config_numberoftags', 80);
+
+ $defaults = array(
+ 'official' => get_string('officialonly', 'block_tags'),
+ '' => get_string('anytype', 'block_tags'));
+ $mform->addElement('select', 'config_tagtype', get_string('defaultdisplay', 'block_tags'), $defaults);
+ $mform->setDefault('config_tagtype', '');
+
+ $defaults = array(0 => context_system::instance()->get_context_name());
+ $parentcontext = context::instance_by_id($this->block->instance->parentcontextid);
+ if ($parentcontext->contextlevel > CONTEXT_COURSE) {
+ $coursecontext = $parentcontext->get_course_context();
+ $defaults[$coursecontext->id] = $coursecontext->get_context_name();
+ }
+ if ($parentcontext->contextlevel != CONTEXT_SYSTEM) {
+ $defaults[$parentcontext->id] = $parentcontext->get_context_name();
+ }
+ $mform->addElement('select', 'config_ctx', get_string('taggeditemscontext', 'block_tags'), $defaults);
+ $mform->addHelpButton('config_ctx', 'taggeditemscontext', 'block_tags');
+ $mform->setDefault('config_ctx', 0);
+
+ $mform->addElement('advcheckbox', 'config_rec', get_string('recursivecontext', 'block_tags'));
+ $mform->addHelpButton('config_rec', 'recursivecontext', 'block_tags');
+ $mform->setDefault('config_rec', 1);
+ }
+
+ /**
+ * Add the tag collection selector
+ *
+ * @param object $mform the form being built.
+ */
+ protected function add_collection_selector($mform) {
+ $tagcolls = core_tag_collection::get_collections_menu(false, false, get_string('anycollection', 'block_tags'));
+ if (count($tagcolls) <= 1) {
+ return;
+ }
+
+ $tagcollssearchable = core_tag_collection::get_collections_menu(false, true);
+ $hasunsearchable = false;
+ foreach ($tagcolls as $id => $name) {
+ if ($id && !array_key_exists($id, $tagcollssearchable)) {
+ $hasunsearchable = true;
+ $tagcolls[$id] = $name . '*';
+ }
+ }
+
+ $mform->addElement('select', 'config_tagcoll', get_string('tagcollection', 'block_tags'), $tagcolls);
+ if ($hasunsearchable) {
+ $mform->addHelpButton('config_tagcoll', 'tagcollection', 'block_tags');
+ }
+ $mform->setDefault('config_tagcoll', 0);
}
}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
+$string['anycollection'] = 'Any';
+$string['anytype'] = 'All';
$string['configtitle'] = 'Block title';
$string['disabledtags'] = 'Tags are disabled';
$string['defaultdisplay'] = 'Tag type to display';
+$string['officialonly'] = 'Only official';
$string['pluginname'] = 'Tags';
+$string['recursivecontext'] = 'Include child contexts';
+$string['recursivecontext_help'] = 'If unchecked, tags of items in the context specified above will be displayed excluding underlying contexts, for example, you can search on course level only without searching inside course activities';
+$string['tagcollection'] = 'Tag collection';
+$string['tagcollection_help'] = 'Select tag collection to display tags from. If you choose "Any" '
+ . 'the tags from all collections except for those marked with * will be displayed';
+$string['taggeditemscontext'] = 'Tagged items context';
+$string['taggeditemscontext_help'] = 'You can limit the tag cloud to the tags that are present in the current course category, course or module';
$string['tags:addinstance'] = 'Add a new tags block';
$string['tags:myaddinstance'] = 'Add a new tags block to Dashboard';
-// Deprecated since 3.0
+// Deprecated since 3.0.
$string['add'] = 'Add';
$string['alltags'] = 'All tags:';
And I should see "Cats" in the "Tags" "block"
And I should not see "Neverusedtag" in the "Tags" "block"
And I click on "Dogs" "link" in the "Tags" "block"
- And I should see "Users tagged with \"Dogs\": 1"
+ And I should see "User interests" in the ".tag-index-items h3" "css_element"
And I should see "Teacher 1"
And I log out
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once(dirname(dirname(__FILE__)).'/config.php');
-require_once('lib.php');
-require_once('locallib.php');
-require_once($CFG->dirroot .'/comment/lib.php');
+require_once($CFG->dirroot . '/blog/lib.php');
+require_once($CFG->dirroot . '/blog/locallib.php');
+require_once($CFG->dirroot . '/comment/lib.php');
+require_once($CFG->dirroot . '/blog/edit_form.php');
$action = required_param('action', PARAM_ALPHA);
$id = optional_param('entryid', 0, PARAM_INT);
}
}
-require_once('edit_form.php');
$summaryoptions = array('maxfiles' => 99, 'maxbytes' => $CFG->maxbytes, 'trusttext' => true, 'context' => $sitecontext,
'subdirs' => file_area_contains_subdirs($sitecontext, 'blog', 'post', $entry->id));
$attachmentoptions = array('subdirs' => false, 'maxfiles' => 99, 'maxbytes' => $CFG->maxbytes);
'attachment',
$entry->id);
-if (!empty($CFG->usetags) && !empty($entry->id)) {
- include_once($CFG->dirroot.'/tag/lib.php');
- $entry->tags = tag_get_tags_array('post', $entry->id);
+if (!empty($entry->id)) {
+ $entry->tags = core_tag_tag::get_item_tags_array('core', 'post', $entry->id);
}
$entry->action = $action;
if (empty($entry->id)) {
print_error('wrongentryid', 'blog');
}
- $entry->tags = tag_get_tags_array('post', $entry->id);
$strformheading = get_string('updateentrywithid', 'blog');
break;
$mform->addHelpButton('publishstate', 'publishto', 'blog');
$mform->setDefault('publishstate', 0);
- if (!empty($CFG->usetags)) {
+ if (core_tag_tag::is_enabled('core', 'post')) {
$mform->addElement('header', 'tagshdr', get_string('tags', 'tag'));
- $mform->addElement('tags', 'tags', get_string('tags'));
}
+ $mform->addElement('tags', 'tags', get_string('tags'),
+ array('itemtype' => 'post', 'component' => 'core'));
$allmodnames = array();
require_once('lib.php');
require_once('external_blog_edit_form.php');
require_once($CFG->libdir . '/simplepie/moodle_simplepie.php');
-require_once($CFG->dirroot.'/tag/lib.php');
require_login();
$context = context_system::instance();
print_error('wrongexternalid', 'blog');
} else if (!empty($id)) {
$external = $DB->get_record('blog_external', array('id' => $id));
+ $external->autotags = core_tag_tag::get_item_tags_array('core', 'blog_external', $id);
}
$strformheading = ($action == 'edit') ? get_string('editexternalblog', 'blog') : get_string('addnewexternalblog', 'blog');
$newexternal->timemodified = time();
$newexternal->id = $DB->insert_record('blog_external', $newexternal);
+ core_tag_tag::set_item_tags('core', 'blog_external', $newexternal->id,
+ context_user::instance($newexternal->userid), $data->autotags);
blog_sync_external_entries($newexternal);
- if ($CFG->usetags) {
- $autotags = (!empty($data->autotags)) ? $data->autotags : null;
- tag_set('blog_external', $newexternal->id, explode(',', $autotags), 'core',
- context_user::instance($newexternal->userid)->id);
- }
break;
$external->timemodified = time();
$DB->update_record('blog_external', $external);
- if ($CFG->usetags) {
- $autotags = (!empty($data->autotags)) ? $data->autotags : null;
- tag_set('blog_external', $external->id, explode(',', $autotags), 'core',
- context_user::instance($external->userid)->id);
- }
+ core_tag_tag::set_item_tags('core', 'blog_external', $external->id,
+ context_user::instance($external->userid), $data->autotags);
} else {
print_error('wrongexternalid', 'blog');
}
redirect($returnurl);
}
+navigation_node::override_active_url(new moodle_url('/blog/external_blogs.php'));
+$PAGE->navbar->add(get_string('addnewexternalblog', 'blog'));
+
$PAGE->set_heading(fullname($USER));
$PAGE->set_title("$SITE->shortname: $strblogs: $strexternalblogs");
$mform->addElement('textarea', 'description', get_string('description', 'blog'), array('cols' => 50, 'rows' => 7));
$mform->addHelpButton('description', 'description', 'blog');
- if (!empty($CFG->usetags)) {
- $mform->addElement('text', 'filtertags', get_string('filtertags', 'blog'), array('size' => 50));
- $mform->setType('filtertags', PARAM_TAGLIST);
- $mform->addHelpButton('filtertags', 'filtertags', 'blog');
- $mform->addElement('text', 'autotags', get_string('autotags', 'blog'), array('size' => 50));
- $mform->setType('autotags', PARAM_TAGLIST);
- $mform->addHelpButton('autotags', 'autotags', 'blog');
- }
+ // To filter external blogs by their tags we do not need to check if tags in moodle are enabled.
+ $mform->addElement('text', 'filtertags', get_string('filtertags', 'blog'), array('size' => 50));
+ $mform->setType('filtertags', PARAM_TAGLIST);
+ $mform->addHelpButton('filtertags', 'filtertags', 'blog');
+
+ $mform->addElement('tags', 'autotags', get_string('autotags', 'blog'),
+ array('itemtype' => 'blog_external', 'component' => 'core'));
+ $mform->addHelpButton('autotags', 'autotags', 'blog');
$this->add_action_buttons();
}
if ($id = $mform->getElementValue('id')) {
- $mform->setDefault('autotags', implode(',', tag_get_tags_array('blog_external', $id)));
$mform->freeze('url');
if ($mform->elementExists('filtertags')) {
$mform->freeze('filtertags');
require_once($CFG->dirroot .'/blog/lib.php');
require_once($CFG->dirroot .'/blog/locallib.php');
require_once($CFG->dirroot .'/course/lib.php');
-require_once($CFG->dirroot .'/tag/lib.php');
require_once($CFG->dirroot .'/comment/lib.php');
$id = optional_param('id', null, PARAM_INT);
* Library of functions and constants for blog
*/
require_once($CFG->dirroot .'/blog/rsslib.php');
-require_once($CFG->dirroot.'/tag/lib.php');
/**
* User can edit a blog entry if this is their own blog entry and they have
$id = $DB->insert_record('post', $newentry);
// Set tags.
- if ($tags = tag_get_tags_array('blog_external', $externalblog->id)) {
- tag_set('post', $id, $tags, 'core', context_user::instance($externalblog->userid)->id);
+ if ($tags = core_tag_tag::get_item_tags_array('core', 'blog_external', $externalblog->id)) {
+ core_tag_tag::set_item_tags('core', 'post', $id, context_user::instance($externalblog->userid), $tags);
}
} else {
$newentry->id = $postid;
$tree->add_node($blognode);
return true;
}
+
+/**
+ * Returns posts tagged with a specified tag.
+ *
+ * @param core_tag_tag $tag
+ * @param bool $exclusivemode if set to true it means that no other entities tagged with this tag
+ * are displayed on the page and the per-page limit may be bigger
+ * @param int $fromctx context id where the link was displayed, may be used by callbacks
+ * to display items in the same context first
+ * @param int $ctx context id where to search for records
+ * @param bool $rec search in subcontexts as well
+ * @param int $page 0-based number of page being displayed
+ * @return \core_tag\output\tagindex
+ */
+function blog_get_tagged_posts($tag, $exclusivemode = false, $fromctx = 0, $ctx = 0, $rec = true, $page = 0) {
+ global $CFG, $OUTPUT;
+ require_once($CFG->dirroot.'/user/lib.php');
+
+ $systemcontext = context_system::instance();
+ $perpage = $exclusivemode ? 20 : 5;
+ $context = $ctx ? context::instance_by_id($ctx) : context_system::instance();
+
+ $content = '';
+ if (empty($CFG->enableblogs) || !has_capability('moodle/blog:view', $systemcontext)) {
+ // Blogs are not enabled or are not visible to the current user.
+ $totalpages = 0;
+ } else if ($context->contextlevel != CONTEXT_SYSTEM && empty($CFG->useblogassociations)) {
+ // No blog entries can be associated to the non-system context.
+ $totalpages = 0;
+ } else if (!$rec && $context->contextlevel != CONTEXT_COURSE && $context->contextlevel != CONTEXT_MODULE) {
+ // No blog entries can be associated with category or block context.
+ $totalpages = 0;
+ } else {
+ require_once($CFG->dirroot.'/blog/locallib.php');
+
+ $filters = array('tag' => $tag->id);
+ if ($rec) {
+ if ($context->contextlevel != CONTEXT_SYSTEM) {
+ $filters['context'] = $context->id;
+ }
+ } else if ($context->contextlevel == CONTEXT_COURSE) {
+ $filters['course'] = $context->instanceid;
+ } else if ($context->contextlevel == CONTEXT_MODULE) {
+ $filters['module'] = $context->instanceid;
+ }
+ $bloglisting = new blog_listing($filters);
+ $blogs = $bloglisting->get_entries($page * $perpage, $perpage);
+ $totalcount = $bloglisting->count_entries();
+ $totalpages = ceil($totalcount / $perpage);
+ if (!empty($blogs)) {
+ $tagfeed = new core_tag\output\tagfeed();
+ foreach ($blogs as $blog) {
+ $user = fullclone($blog);
+ $user->id = $blog->userid;
+ $user->deleted = 0;
+ $img = $OUTPUT->user_picture($user, array('size' => 35));
+ $subject = format_string($blog->subject);
+
+ if ($blog->publishstate == 'draft') {
+ $class = 'dimmed';
+ } else {
+ $class = '';
+ }
+
+ $url = new moodle_url('/blog/index.php', array('entryid' => $blog->id));
+ $subject = html_writer::link($url, $subject, array('class' => $class));
+
+ $fullname = fullname($user);
+ if (user_can_view_profile($user)) {
+ $profilelink = new moodle_url('/user/view.php', array('id' => $blog->userid));
+ $fullname = html_writer::link($profilelink, $fullname);
+ }
+ $details = $fullname . ', ' . userdate($blog->created);
+
+ $tagfeed->add($img, $subject, $details);
+ }
+
+ $items = $tagfeed->export_for_template($OUTPUT);
+ $content = $OUTPUT->render_from_template('core_tag/tagfeed', $items);
+
+ $urlparams = array('tagid' => $tag->id);
+ if ($context->contextlevel == CONTEXT_COURSE) {
+ $urlparams['courseid'] = $context->instanceid;
+ } else if ($context->contextlevel == CONTEXT_MODULE) {
+ $urlparams['modid'] = $context->instanceid;
+ }
+ $allblogsurl = new moodle_url('/blog/index.php', $urlparams);
+
+ $rv = new core_tag\output\tagindex($tag, 'core', 'post',
+ $content,
+ $exclusivemode, $fromctx, $ctx, $rec, $page, $totalpages);
+ $rv->exclusiveurl = $allblogsurl;
+ return $rv;
+ }
+ }
+
+ $rv = new core_tag\output\tagindex($tag, 'core', 'post',
+ $content,
+ $exclusivemode, $fromctx, $ctx, $rec, $page, $totalpages);
+ $rv->exclusiveurl = null;
+ return $rv;
+}
// Insert the new blog entry.
$this->id = $DB->insert_record('post', $this);
- // Update tags.
- $this->add_tags_info();
-
if (!empty($CFG->useblogassociations)) {
$this->add_associations();
}
- tag_set('post', $this->id, $this->tags, 'core', context_user::instance($this->userid)->id);
+ core_tag_tag::set_item_tags('core', 'post', $this->id, context_user::instance($this->userid), $this->tags);
// Trigger an event for the new entry.
$event = \core\event\blog_entry_created::create(array(
// Update record.
$DB->update_record('post', $entry);
- tag_set('post', $entry->id, $entry->tags, 'core', context_user::instance($this->userid)->id);
+ core_tag_tag::set_item_tags('core', 'post', $entry->id, context_user::instance($this->userid), $entry->tags);
$event = \core\event\blog_entry_updated::create(array(
'objectid' => $entry->id,
// Get record to pass onto the event.
$record = $DB->get_record('post', array('id' => $this->id));
$DB->delete_records('post', array('id' => $this->id));
- tag_set('post', $this->id, array(), 'core', context_user::instance($this->userid)->id);
+ core_tag_tag::remove_all_item_tags('core', 'post', $this->id);
$event = \core\event\blog_entry_deleted::create(array(
'objectid' => $this->id,
$fs->delete_area_files(SYSCONTEXTID, 'blog', 'post', $this->id);
}
- /**
- * function to attach tags into an entry
- * @return void
- */
- public function add_tags_info() {
-
- $tags = array();
-
- if ($otags = optional_param('otags', '', PARAM_INT)) {
- foreach ($otags as $tagid) {
- // TODO : make this use the tag name in the form.
- if ($tag = tag_get('id', $tagid)) {
- $tags[] = $tag->name;
- }
- }
- }
-
- tag_set('post', $this->id, $tags, 'core', context_user::instance($this->userid)->id);
- }
-
/**
* User can edit a blog entry if this is their own blog entry and they have
* the capability moodle/blog:create, or if they have the capability
* Array of blog_entry objects.
* @var array $entries
*/
- public $entries = array();
+ public $entries = null;
+
+ /**
+ * Caches the total number of the entries.
+ * @var int
+ */
+ public $totalentries = null;
/**
* An array of blog_filter_* objects
public function get_entries($start=0, $limit=10) {
global $DB;
- if (empty($this->entries)) {
+ if ($this->entries === null) {
if ($sqlarray = $this->get_entry_fetch_sql(false, 'created DESC')) {
$this->entries = $DB->get_records_sql($sqlarray['sql'], $sqlarray['params'], $start, $limit);
+ if (!$start && count($this->entries) < $limit) {
+ $this->totalentries = count($this->entries);
+ }
} else {
return false;
}
return $this->entries;
}
+ /**
+ * Finds total number of blog entries
+ *
+ * @return int
+ */
+ public function count_entries() {
+ global $DB;
+ if ($this->totalentries === null) {
+ if ($sqlarray = $this->get_entry_fetch_sql(true)) {
+ $this->totalentries = $DB->count_records_sql($sqlarray['sql'], $sqlarray['params']);
+ } else {
+ $this->totalentries = 0;
+ }
+ }
+ return $this->totalentries;
+ }
+
public function get_entry_fetch_sql($count=false, $sort='lastmodified DESC', $userid = false) {
global $DB, $USER, $CFG;
$userid = $USER->id;
}
- $allnamefields = get_all_user_name_fields(true, 'u');
+ $allnamefields = \user_picture::fields('u', null, 'useridalias');
// The query used to locate blog entries is complicated. It will be built from the following components:
- $requiredfields = "p.*, $allnamefields, u.email"; // The SELECT clause.
+ $requiredfields = "p.*, $allnamefields"; // The SELECT clause.
$tables = array('p' => 'post', 'u' => 'user'); // Components of the FROM clause (table_id => table_name).
// Components of the WHERE clause (conjunction).
$conditions = array('u.deleted = 0', 'p.userid = u.id', '(p.module = \'blog\' OR p.module = \'blog_external\')');
$morelink = '<br /> ';
- if ($sqlarray = $this->get_entry_fetch_sql(true)) {
- $totalentries = $DB->count_records_sql($sqlarray['sql'], $sqlarray['params']);
- } else {
- $totalentries = 0;
- }
-
$entries = $this->get_entries($start, $limit);
+ $totalentries = $this->count_entries();
$pagingbar = new paging_bar($totalentries, $page, $limit, $this->get_baseurl());
$pagingbar->pagevar = 'blogpage';
$blogheaders = blog_get_headers();
$this->availabletypes = array('site' => get_string('site'),
'course' => get_string('course'),
- 'module' => get_string('activity'));
+ 'module' => get_string('activity'),
+ 'context' => get_string('coresystem'));
switch ($this->type) {
case 'course': // Careful of site course!
// Ignore course filter if blog associations are not enabled.
if ($this->id != $SITE->id && !empty($CFG->useblogassociations)) {
- $this->overrides = array('site');
+ $this->overrides = array('site', 'context');
$context = context_course::instance($this->id);
$this->tables['ba'] = 'blog_association';
$this->conditions[] = 'p.id = ba.blogid';
break;
case 'module':
if (!empty($CFG->useblogassociations)) {
- $this->overrides = array('course', 'site');
+ $this->overrides = array('course', 'site', 'context');
$context = context_module::instance($this->id);
$this->tables['ba'] = 'blog_association';
$this->params = array($context->id);
}
break;
+ case 'context':
+ if ($id != context_system::instance()->id && !empty($CFG->useblogassociations)) {
+ $this->overrides = array('site');
+ $context = context::instance_by_id($this->id);
+ $this->tables['ba'] = 'blog_association';
+ $this->tables['ctx'] = 'context';
+ $this->conditions[] = 'p.id = ba.blogid';
+ $this->conditions[] = 'ctx.id = ba.contextid';
+ $this->conditions[] = 'ctx.path LIKE ?';
+ $this->params = array($context->path . '%');
+ }
+ break;
+
}
}
}
$this->conditions = array('ti.tagid = t.id',
"ti.itemtype = 'post'",
+ "ti.component = 'core'",
'ti.itemid = p.id',
't.id = ?');
$this->params = array($this->id);
}
// Links to tags.
- $officialtags = tag_get_tags_csv('post', $entry->id, TAG_RETURN_HTML, 'official');
- $defaulttags = tag_get_tags_csv('post', $entry->id, TAG_RETURN_HTML, 'default');
-
- if (!empty($CFG->usetags) && ($officialtags || $defaulttags) ) {
- $o .= $this->output->container_start('tags');
-
- if ($officialtags) {
- $o .= get_string('tags', 'tag') .': '. $this->output->container($officialtags, 'officialblogtags');
- if ($defaulttags) {
- $o .= ', ';
- }
- }
- $o .= $defaulttags;
- $o .= $this->output->container_end();
- }
+ $o .= $this->output->tag_list(core_tag_tag::get_item_tags('core', 'post', $entry->id));
// Add associations.
if (!empty($CFG->useblogassociations) && !empty($entry->renderable->blogassociations)) {
$summary = file_rewrite_pluginfile_urls($blogentry->summary, 'pluginfile.php',
$sitecontext->id, 'blog', 'post', $blogentry->id);
$item->description = format_text($summary, $blogentry->format);
- if ( !empty($CFG->usetags) && ($blogtags = tag_get_tags_array('post', $blogentry->id)) ) {
- if ($blogtags) {
- $item->tags = $blogtags;
- }
+ if ($blogtags = core_tag_tag::get_item_tags_array('core', 'post', $blogentry->id)) {
+ $item->tags = $blogtags;
$item->tagscheme = $CFG->wwwroot . '/tag';
}
$items[] = $item;
));
// Create default tag.
- $tag = new stdClass();
- $tag->userid = $user->id;
- $tag->name = 'testtagname';
- $tag->rawname = 'Testtagname';
- $tag->tagtype = 'official';
- $tag->id = $DB->insert_record('tag', $tag);
+ $tag = $this->getDataGenerator()->create_tag(array('userid' => $user->id,
+ 'rawname' => 'Testtagname', 'tagtype' => 'official'));
// Create default post.
$post = new stdClass();
$post->userid = $user->id;
$post->groupid = $group->id;
$post->content = 'test post content text';
+ $post->module = 'blog';
$post->id = $DB->insert_record('post', $post);
// Grab important ids.
$nodes->setAccessible(true);
$this->assertArrayNotHasKey('blogs', $nodes->getValue($tree));
}
+
+ public function test_blog_get_listing_course() {
+ $this->setAdminUser();
+ $coursecontext = context_course::instance($this->courseid);
+ $anothercourse = $this->getDataGenerator()->create_course();
+
+ // Add blog associations with a course.
+ $blog = new blog_entry($this->postid);
+ $blog->add_association($coursecontext->id);
+
+ // There is one entry associated with a course.
+ $bloglisting = new blog_listing(array('course' => $this->courseid));
+ $this->assertCount(1, $bloglisting->get_entries());
+
+ // There is no entry associated with a wrong course.
+ $bloglisting = new blog_listing(array('course' => $anothercourse->id));
+ $this->assertCount(0, $bloglisting->get_entries());
+
+ // There is no entry associated with a module.
+ $bloglisting = new blog_listing(array('module' => $this->cmid));
+ $this->assertCount(0, $bloglisting->get_entries());
+
+ // There is one entry associated with a site (id is ignored).
+ $bloglisting = new blog_listing(array('site' => 12345));
+ $this->assertCount(1, $bloglisting->get_entries());
+
+ // There is one entry associated with course context.
+ $bloglisting = new blog_listing(array('context' => $coursecontext->id));
+ $this->assertCount(1, $bloglisting->get_entries());
+ }
+
+ public function test_blog_get_listing_module() {
+ $this->setAdminUser();
+ $coursecontext = context_course::instance($this->courseid);
+ $contextmodule = context_module::instance($this->cmid);
+ $anothermodule = $this->getDataGenerator()->create_module('page', array('course' => $this->courseid));
+
+ // Add blog associations with a course.
+ $blog = new blog_entry($this->postid);
+ $blog->add_association($contextmodule->id);
+
+ // There is no entry associated with a course.
+ $bloglisting = new blog_listing(array('course' => $this->courseid));
+ $this->assertCount(0, $bloglisting->get_entries());
+
+ // There is one entry associated with a module.
+ $bloglisting = new blog_listing(array('module' => $this->cmid));
+ $this->assertCount(1, $bloglisting->get_entries());
+
+ // There is no entry associated with a wrong module.
+ $bloglisting = new blog_listing(array('module' => $anothermodule->cmid));
+ $this->assertCount(0, $bloglisting->get_entries());
+
+ // There is one entry associated with a site (id is ignored).
+ $bloglisting = new blog_listing(array('site' => 12345));
+ $this->assertCount(1, $bloglisting->get_entries());
+
+ // There is one entry associated with course context (module is a subcontext of a course).
+ $bloglisting = new blog_listing(array('context' => $coursecontext->id));
+ $this->assertCount(1, $bloglisting->get_entries());
+ }
}
// Show ical source if needed.
if (!empty($event->subscription) && $CFG->calendar_showicalsource) {
$a = new stdClass();
- $a->name = $name;
+ $a->name = format_string($event->name, true);
$a->source = $event->subscription->name;
$name = get_string('namewithsource', 'calendar', $a);
} else {
}
// Populate course tags.
- if (!empty($CFG->usetags)) {
- include_once($CFG->dirroot.'/tag/lib.php');
- $course->tags = tag_get_tags_array('course', $course->id);
- }
+ $course->tags = core_tag_tag::get_item_tags_array('core', 'course', $course->id);
} else {
// Editor should respect category context if course context is not set.
}
}
- if (!empty($CFG->usetags) &&
+ if (core_tag_tag::is_enabled('core', 'course') &&
((empty($course->id) && guess_if_creator_will_have_course_capability('moodle/course:tag', $categorycontext))
|| (!empty($course->id) && has_capability('moodle/course:tag', $coursecontext)))) {
$mform->addElement('header', 'tagshdr', get_string('tags', 'tag'));
- $mform->addElement('tags', 'tags', get_string('tags'));
+ $mform->addElement('tags', 'tags', get_string('tags'),
+ array('itemtype' => 'course', 'component' => 'core'));
}
// When two elements we need a group.
require_once($CFG->libdir.'/questionlib.php');
require_once($CFG->dirroot.'/blog/lib.php');
require_once($CFG->dirroot.'/calendar/lib.php');
- require_once($CFG->dirroot.'/tag/lib.php');
// Get the course module.
if (!$cm = $DB->get_record('course_modules', array('id' => $cmid))) {
'criteriatype' => COMPLETION_CRITERIA_TYPE_ACTIVITY));
// Delete all tag instances associated with the instance of this module.
- tag_delete_instances('mod_' . $modulename, $modcontext->id);
+ core_tag_tag::delete_instances('mod_' . $modulename, null, $modcontext->id);
// Delete the context.
context_helper::delete_instance(CONTEXT_MODULE, $cm->id);
*/
function create_course($data, $editoroptions = NULL) {
global $DB, $CFG;
- require_once($CFG->dirroot.'/tag/lib.php');
//check the categoryid - must be given for all new courses
$category = $DB->get_record('course_categories', array('id'=>$data->category), '*', MUST_EXIST);
enrol_course_updated(true, $course, $data);
// Update course tags.
- if ($CFG->usetags && isset($data->tags)) {
- tag_set('course', $course->id, $data->tags, 'core', context_course::instance($course->id)->id);
+ if (isset($data->tags)) {
+ core_tag_tag::set_item_tags('core', 'course', $course->id, context_course::instance($course->id), $data->tags);
}
return $course;
*/
function update_course($data, $editoroptions = NULL) {
global $DB, $CFG;
- require_once($CFG->dirroot.'/tag/lib.php');
$data->timemodified = time();
enrol_course_updated(false, $course, $data);
// Update course tags.
- if ($CFG->usetags && isset($data->tags)) {
- tag_set('course', $course->id, $data->tags, 'core', context_course::instance($course->id)->id);
+ if (isset($data->tags)) {
+ core_tag_tag::set_item_tags('core', 'course', $course->id, context_course::instance($course->id), $data->tags);
}
// Trigger a course updated event.
$event = \core\event\course_viewed::create($eventdata);
$event->trigger();
}
+
+/**
+ * Returns courses tagged with a specified tag.
+ *
+ * @param core_tag_tag $tag
+ * @param bool $exclusivemode if set to true it means that no other entities tagged with this tag
+ * are displayed on the page and the per-page limit may be bigger
+ * @param int $fromctx context id where the link was displayed, may be used by callbacks
+ * to display items in the same context first
+ * @param int $ctx context id where to search for records
+ * @param bool $rec search in subcontexts as well
+ * @param int $page 0-based number of page being displayed
+ * @return \core_tag\output\tagindex
+ */
+function course_get_tagged_courses($tag, $exclusivemode = false, $fromctx = 0, $ctx = 0, $rec = 1, $page = 0) {
+ global $CFG, $PAGE;
+ require_once($CFG->libdir . '/coursecatlib.php');
+
+ $perpage = $exclusivemode ? $CFG->coursesperpage : 5;
+ $displayoptions = array(
+ 'limit' => $perpage,
+ 'offset' => $page * $perpage,
+ 'viewmoreurl' => null,
+ );
+
+ $courserenderer = $PAGE->get_renderer('core', 'course');
+ $totalcount = coursecat::search_courses_count(array('tagid' => $tag->id, 'ctx' => $ctx, 'rec' => $rec));
+ $content = $courserenderer->tagged_courses($tag->id, $exclusivemode, $ctx, $rec, $displayoptions);
+ $totalpages = ceil($totalcount / $perpage);
+
+ return new core_tag\output\tagindex($tag, 'core', 'course', $content,
+ $exclusivemode, $fromctx, $ctx, $rec, $page, $totalpages);
+}
* Renders html to print list of courses tagged with particular tag
*
* @param int $tagid id of the tag
+ * @param bool $exclusivemode if set to true it means that no other entities tagged with this tag
+ * are displayed on the page and the per-page limit may be bigger
+ * @param int $fromctx context id where the link was displayed, may be used by callbacks
+ * to display items in the same context first
+ * @param int $ctx context id where to search for records
+ * @param bool $rec search in subcontexts as well
+ * @param array $displayoptions
* @return string empty string if no courses are marked with this tag or rendered list of courses
*/
- public function tagged_courses($tagid) {
+ public function tagged_courses($tagid, $exclusivemode = true, $ctx = 0, $rec = true, $displayoptions = null) {
global $CFG;
- require_once($CFG->libdir. '/coursecatlib.php');
- $displayoptions = array('limit' => $CFG->coursesperpage);
- $displayoptions['viewmoreurl'] = new moodle_url('/course/search.php',
- array('tagid' => $tagid, 'page' => 1, 'perpage' => $CFG->coursesperpage));
- $displayoptions['viewmoretext'] = new lang_string('findmorecourses');
+ require_once($CFG->libdir . '/coursecatlib.php');
+ if (empty($displayoptions)) {
+ $displayoptions = array();
+ }
+ $showcategories = coursecat::count_all() > 1;
+ $displayoptions += array('limit' => $CFG->coursesperpage, 'offset' => 0);
$chelper = new coursecat_helper();
- $searchcriteria = array('tagid' => $tagid);
- $chelper->set_show_courses(self::COURSECAT_SHOW_COURSES_EXPANDED_WITH_CAT)->
- set_search_criteria(array('tagid' => $tagid))->
+ $searchcriteria = array('tagid' => $tagid, 'ctx' => $ctx, 'rec' => $rec);
+ $chelper->set_show_courses($showcategories ? self::COURSECAT_SHOW_COURSES_EXPANDED_WITH_CAT :
+ self::COURSECAT_SHOW_COURSES_EXPANDED)->
+ set_search_criteria($searchcriteria)->
set_courses_display_options($displayoptions)->
set_attributes(array('class' => 'course-search-result course-search-result-tagid'));
// (we set the same css class as in search results by tagid)
- $courses = coursecat::search_courses($searchcriteria, $chelper->get_courses_display_options());
- $totalcount = coursecat::search_courses_count($searchcriteria);
- $content = $this->coursecat_courses($chelper, $courses, $totalcount);
- if ($totalcount) {
- require_once $CFG->dirroot.'/tag/lib.php';
- $heading = get_string('courses') . ' ' . get_string('taggedwith', 'tag', tag_get_name($tagid)) .': '. $totalcount;
- return $this->heading($heading, 3). $content;
+ if ($totalcount = coursecat::search_courses_count($searchcriteria)) {
+ $courses = coursecat::search_courses($searchcriteria, $chelper->get_courses_display_options());
+ if ($exclusivemode) {
+ return $this->coursecat_courses($chelper, $courses, $totalcount);
+ } else {
+ $tagfeed = new core_tag\output\tagfeed();
+ $img = $this->output->pix_icon('i/course', '');
+ foreach ($courses as $course) {
+ $url = course_get_url($course);
+ $imgwithlink = html_writer::link($url, $img);
+ $coursename = html_writer::link($url, $course->get_formatted_name());
+ $details = '';
+ if ($showcategories && ($cat = coursecat::get($course->category, IGNORE_MISSING))) {
+ $details = get_string('category').': '.
+ html_writer::link(new moodle_url('/course/index.php', array('categoryid' => $cat->id)),
+ $cat->get_formatted_name(), array('class' => $cat->visible ? '' : 'dimmed'));
+ }
+ $tagfeed->add($imgwithlink, $coursename, $details);
+ }
+ return $this->output->render_from_template('core_tag/tagfeed', $tagfeed->export_for_template($this->output));
+ }
}
return '';
}
*/
require_once("../config.php");
-require_once($CFG->dirroot . '/tag/lib.php');
require_once($CFG->dirroot . '/course/tags_form.php');
$id = required_param('id', PARAM_INT); // Course id.
print_error('coursehidden', '', $CFG->wwwroot .'/');
}
require_capability('moodle/course:tag', $context);
-if (empty($CFG->usetags)) {
+if (!core_tag_tag::is_enabled('core', 'course')) {
print_error('tagsaredisabled', 'tag');
}
$PAGE->set_heading($course->fullname);
$form = new coursetags_form();
-$data = array('id' => $course->id, 'tags' => tag_get_tags_array('course', $course->id));
+$data = array('id' => $course->id, 'tags' => core_tag_tag::get_item_tags_array('core', 'course', $course->id));
$form->set_data($data);
$redirecturl = $returnurl ? new moodle_url($returnurl) : course_get_url($course);
if ($form->is_cancelled()) {
redirect($redirecturl);
} else if ($data = $form->get_data()) {
- tag_set('course', $course->id, $data->tags, 'core', context_course::instance($course->id)->id);
+ core_tag_tag::set_item_tags('core', 'course', $course->id, context_course::instance($course->id), $data->tags);
redirect($redirecturl);
}
public function definition() {
$mform = $this->_form;
- $mform->addElement('tags', 'tags', get_string('tags'));
+ $mform->addElement('tags', 'tags', get_string('tags'),
+ array('itemtype' => 'course', 'component' => 'core'));
$mform->addElement('hidden', 'id', null);
$mform->setType('id', PARAM_INT);
require_once($CFG->dirroot . '/course/lib.php');
require_once($CFG->dirroot . '/course/tests/fixtures/course_capability_assignment.php');
require_once($CFG->dirroot . '/enrol/imsenterprise/tests/imsenterprise_test.php');
-require_once($CFG->dirroot . '/tag/lib.php');
class core_course_courselib_testcase extends advanced_testcase {
*/
public function test_course_delete_module($type, $options) {
global $DB;
+
$this->resetAfterTest(true);
$this->setAdminUser();
switch ($type) {
case 'assign':
// Add some tags to this assignment.
- tag_set('assign', $module->id, array('Tag 1', 'Tag 2', 'Tag 3'), 'mod_assign', $modcontext->id);
+ core_tag_tag::set_item_tags('mod_assign', 'assign', $module->id, $modcontext, array('Tag 1', 'Tag 2', 'Tag 3'));
// Confirm the tag instances were added.
$criteria = array('component' => 'mod_assign', 'contextid' => $modcontext->id);
*/
public function test_search_courses () {
- global $DB, $CFG;
- require_once($CFG->dirroot . '/tag/lib.php');
+ global $DB;
$this->resetAfterTest(true);
$this->setAdminUser();
// Enable coursetag option.
set_config('block_tags_showcoursetags', true);
// Add tag 'TAG-LABEL ON SECOND COURSE' to Course2.
- tag_set('course', $course2->id, array('TAG-LABEL ON SECOND COURSE'), 'core', context_course::instance($course2->id)->id);
- $taginstance = $DB->get_record('tag_instance', array('itemtype' => 'course', 'itemid' => $course2->id), '*', MUST_EXIST);
+ core_tag_tag::set_item_tags('core', 'course', $course2->id, context_course::instance($course2->id),
+ array('TAG-LABEL ON SECOND COURSE'));
+ $taginstance = $DB->get_record('tag_instance',
+ array('itemtype' => 'course', 'itemid' => $course2->id), '*', MUST_EXIST);
// Search by tagid.
$results = core_course_external::search_courses('tagid', $taginstance->tagid);
$results = external_api::clean_returnvalue(core_course_external::search_courses_returns(), $results);
y = parseInt(base.get('winHeight'))*0.1;
}
base.setXY([x,y]);
+ var zindex = 0;
+ Y.all('.moodle-has-zindex').each(function() {
+ if (parseInt(this.getComputedStyle('zIndex'), 10) > zindex) {
+ zindex = parseInt(this.getComputedStyle('zIndex'), 10);
+ }
+ });
+ base.setStyle('zIndex', zindex + 1);
if (this.get(UEP.USERS)===null) {
this.search(e, false);
foreach ($_POST as $key => $value) {
$req .= "&$key=".urlencode($value);
- $data->$key = $value;
+ $data->$key = fix_utf8($value);
}
$custom = explode('-', $data->custom);
die;
}
+ // Use the queried course's full name for the item_name field.
+ $data->item_name = $course->fullname;
// ALL CLEAR !
--- /dev/null
+@core_enrol
+Feature: Manage enrollments from participants page
+ In order to manage course participants
+ As a teacher
+ In need to get to the enrolment page from the course participants page
+
+ Background:
+ Given the following "users" exist:
+ | username | firstname | lastname | email |
+ | student1 | Student | 1 | student1@example.com |
+ | student2 | Student | 2 | student2@example.com |
+ | teacher1 | teacher | 1 | teacher1@example.com |
+ And the following "courses" exist:
+ | fullname | shortname | format |
+ | Course 1 | C1 | topics |
+ And the following "course enrolments" exist:
+ | user | course | role |
+ | student1 | C1 | student |
+ | student2 | C1 | student |
+ | teacher1 | C1 | editingteacher |
+ And I log in as "teacher1"
+ And I follow "Course 1"
+ And I navigate to "Participants" node in "Current course > C1"
+
+ Scenario: Check the participants link when "All partipants" selected
+ Given I select "All participants" from the "roleid" singleselect
+ When I follow "Edit"
+ Then I should see "Enrolled users" in the "h2" "css_element"
+ And the field "Role" matches value "All"
+
+ Scenario: Check the participants link when "Student" selected
+ Given I select "Student" from the "roleid" singleselect
+ When I follow "Edit"
+ Then I should see "Enrolled users" in the "h2" "css_element"
+ And the field "Role" matches value "Student"
--- /dev/null
+// 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/>.
+
+/**
+ * AMD code for the frequently used comments chooser for the marking guide grading form.
+ *
+ * @module gradingform_guide/comment_chooser
+ * @class comment_chooser
+ * @package core
+ * @copyright 2015 Jun Pataleta <jun@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+define(['jquery', 'core/templates', 'core/notification', 'core/yui'], function ($, templates, notification) {
+
+ // Private variables and functions.
+
+ return /** @alias module:gradingform_guide/comment_chooser */ {
+ // Public variables and functions.
+ /**
+ * Initialises the module.
+ *
+ * Basically, it performs the binding and handling of the button click event for
+ * the 'Insert frequently used comment' button.
+ *
+ * @param criterionId The criterion ID.
+ * @param buttonId The element ID of the button which the handler will be bound to.
+ * @param remarkId The element ID of the remark text area where the text of the selected comment will be copied to.
+ * @param commentOptions The array of frequently used comments to be used as options.
+ */
+ initialise: function (criterionId, buttonId, remarkId, commentOptions) {
+ var chooserDialog;
+
+ /**
+ * Display the chooser dialog using the compiled HTML from the mustache template
+ * and binds onclick events for the generated comment options.
+ *
+ * @param compiledSource The compiled HTML from the mustache template
+ * @param comments Array containing comments.
+ */
+ function displayChooserDialog(compiledSource, comments) {
+ var titleLabel = '<label>' + M.util.get_string('insertcomment', 'gradingform_guide') + '</label>';
+ var cancelButtonId = 'comment-chooser-' + criterionId + '-cancel';
+ var cancelButton = '<button id="' + cancelButtonId + '">' + M.util.get_string('cancel', 'moodle') + '</button>';
+
+ if (typeof chooserDialog === 'undefined') {
+ // Set dialog's body content.
+ chooserDialog = new M.core.dialogue({
+ modal: true,
+ headerContent: titleLabel,
+ bodyContent: compiledSource,
+ footerContent: cancelButton,
+ focusAfterHide: '#' + remarkId,
+ id: "comments-chooser-dialog-" + criterionId
+ });
+
+ // Bind click event to the cancel button.
+ $("#" + cancelButtonId).click(function() {
+ if (typeof chooserDialog !== 'undefined') {
+ chooserDialog.hide();
+ }
+ });
+
+ // Loop over each comment item and bind click events.
+ $.each(comments, function (index, comment) {
+ var commentOptionId = '#comment-option-' + criterionId + '-' + comment.id;
+
+ // Delegate click event for the generated option link.
+ $(commentOptionId).click(function () {
+ var remarkTextArea = $('#' + remarkId);
+ var remarkText = remarkTextArea.val();
+
+ // Add line break if the current value of the remark text is not empty.
+ if ($.trim(remarkText) !== '') {
+ remarkText += '\n';
+ }
+ remarkText += comment.description;
+
+ remarkTextArea.val(remarkText);
+
+ if (typeof chooserDialog !== 'undefined') {
+ chooserDialog.hide();
+ }
+ });
+
+ // Handle keypress on list items.
+ $(document).off('keypress', commentOptionId).on('keypress', commentOptionId, function () {
+ var keyCode = event.which || event.keyCode;
+
+ // Enter or space key.
+ if (keyCode == 13 || keyCode == 32) {
+ // Trigger click event.
+ $(commentOptionId).click();
+ }
+ });
+ });
+ }
+
+ // Show dialog.
+ chooserDialog.show();
+ }
+
+ /**
+ * Generates the comments chooser dialog from the grading_form/comment_chooser mustache template.
+ */
+ function generateCommentsChooser() {
+ // Template context.
+ var context = {
+ criterionId: criterionId,
+ comments: commentOptions
+ };
+
+ // Render the template and display the comment chooser dialog.
+ templates.render('gradingform_guide/comment_chooser', context)
+ .done(function (compiledSource) {
+ displayChooserDialog(compiledSource, commentOptions);
+ })
+ .fail(notification.exception);
+ }
+
+ // Bind click event for the comments chooser button.
+ $("#" + buttonId).click(function (e) {
+ e.preventDefault();
+ generateCommentsChooser();
+ });
+ }
+ };
+});
// Name.
$form->addElement('text', 'name', get_string('name', 'gradingform_guide'),
array('size' => 52, 'maxlength' => 255));
- $form->addRule('name', get_string('required'), 'required');
+ $form->addRule('name', get_string('required'), 'required', null, 'client');
$form->setType('name', PARAM_TEXT);
$form->addRule('name', null, 'maxlength', 255, 'client');
$html .= $renderer->display_regrade_confirmation($this->getName(), $this->regradeconfirmation, $data['regrade']);
}
if ($this->validationerrors) {
- $html .= $renderer->notification($this->validationerrors, 'error');
+ $html .= html_writer::div($renderer->notification($this->validationerrors, 'error'), '', array('role' => 'alert'));
}
$html .= $renderer->display_guide($data['criteria'], $data['comments'], $data['options'], $mode, $this->getName());
return $html;
return;
}
// prepare the id of the next inserted criterion
-
+ var elements_str;
if (section == 'criteria') {
elements_str = '#guide-'+name+' .criteria .criterion'
} else if (section == 'comments') {
elements_str = '#guide-'+name+' .comments .criterion'
}
+ var newid = 0;
if (action == 'addcriterion' || action == 'addcomment') {
- var newid = M.gradingform_guideeditor.calculatenewid(elements_str)
+ newid = M.gradingform_guideeditor.calculatenewid(elements_str);
}
var dialog_options = {
'scope' : this,
M.gradingform_guideeditor.addhandlers();
M.gradingform_guideeditor.disablealleditors()
M.gradingform_guideeditor.assignclasses(elements_str)
- //M.gradingform_guideeditor.editmode(Y.one('#guide-'+name+' #'+name+'-'+section+'-NEWID'+newid+'-shortname'),true)
+
+ // Enable edit mode of the newly added criterion/comment entry.
+ var inputTarget = 'shortname';
+ if (action == 'addcomment') {
+ inputTarget = 'description';
+ }
+ var inputTargetId = '#guide-' + name + ' #' + name + '-' + section + '-NEWID' + newid + '-' + inputTarget;
+ M.gradingform_guideeditor.editmode(Y.one(inputTargetId), true);
} else if (chunks.length == 4 && action == 'moveup') {
// MOVE UP
el = Y.one('#'+name+'-'+section+'-'+chunks[2])
$string['clicktocopy'] = 'Click to copy this text into the criteria feedback';
$string['clicktoedit'] = 'Click to edit';
$string['clicktoeditname'] = 'Click to edit criterion name';
+$string['comment'] = 'Comment';
$string['comments'] = 'Frequently used comments';
$string['commentsdelete'] = 'Delete comment';
$string['commentsempty'] = 'Click to edit comment';
$string['commentsmoveup'] = 'Move up';
$string['confirmdeletecriterion'] = 'Are you sure you want to delete this item?';
$string['confirmdeletelevel'] = 'Are you sure you want to delete this level?';
-$string['criterion'] = 'Criterion';
+$string['criterion'] = 'Criterion name';
$string['criteriondelete'] = 'Delete criterion';
$string['criterionempty'] = 'Click to edit criterion';
$string['criterionmovedown'] = 'Move down';
$string['criterionmoveup'] = 'Move up';
$string['criterionname'] = 'Criterion name';
+$string['criterionremark'] = '{$a} criterion remark';
$string['definemarkingguide'] = 'Define marking guide';
$string['description'] = 'Description';
$string['descriptionmarkers'] = 'Description for Markers';
$string['err_shortnametoolong'] = 'Criterion name must be less than 256 characters';
$string['err_scoreinvalid'] = 'The score given to {$a->criterianame} is not valid, the max score is: {$a->maxscore}';
$string['gradingof'] = '{$a} grading';
+$string['guide'] = 'Marking guide';
$string['guidemappingexplained'] = 'WARNING: Your marking guide has a maximum grade of <b>{$a->maxscore} points</b>Â but the maximum grade set in your activity is {$a->modulegrade} The maximum score set in your marking guide will be scaled to the maximum grade in the module.<br />
Intermediate scores will be converted respectively and rounded to the nearest available grade.';
$string['guidenotcompleted'] = 'Please provide a valid grade for each criterion';
$string['guidestatus'] = 'Current marking guide status';
$string['hidemarkerdesc'] = 'Hide marker criterion descriptions';
$string['hidestudentdesc'] = 'Hide student criterion descriptions';
+$string['insertcomment'] = 'Insert frequently used comment';
$string['maxscore'] = 'Maximum mark';
$string['name'] = 'Name';
$string['needregrademessage'] = 'The marking guide definition was changed after this student had been graded. The student can not see this marking guide until you check the marking guide and update the grade.';
$currentinstance = $this->get_current_instance();
if ($currentinstance && $currentinstance->get_status() == gradingform_instance::INSTANCE_STATUS_NEEDUPDATE) {
$html .= html_writer::tag('div', get_string('needregrademessage', 'gradingform_guide'),
- array('class' => 'gradingform_guide-regrade'));
+ array('class' => 'gradingform_guide-regrade', 'role' => 'alert'));
}
$haschanges = false;
if ($currentinstance) {
* @param array $criterion criterion data
* @param array $value (only in view mode) teacher's feedback on this criterion
* @param array $validationerrors An array containing validation errors to be shown
+ * @param array $comments Array of frequently used comments.
* @return string
*/
public function criterion_template($mode, $options, $elementname = '{NAME}', $criterion = null, $value = null,
- $validationerrors = null) {
+ $validationerrors = null, $comments = null) {
+ global $PAGE;
+
if ($criterion === null || !is_array($criterion) || !array_key_exists('id', $criterion)) {
$criterion = array('id' => '{CRITERION-id}',
'description' => '{CRITERION-description}',
$value = get_string('criterion'.$key, 'gradingform_guide');
$button = html_writer::empty_tag('input', array('type' => 'submit',
'name' => '{NAME}[criteria][{CRITERION-id}]['.$key.']',
- 'id' => '{NAME}-criteria-{CRITERION-id}-'.$key, 'value' => $value, 'title' => $value, 'tabindex' => -1));
+ 'id' => '{NAME}-criteria-{CRITERION-id}-'.$key, 'value' => $value, 'title' => $value));
$criteriontemplate .= html_writer::tag('div', $button, array('class' => $key));
}
$criteriontemplate .= html_writer::end_tag('td'); // Controls.
$criteriontemplate .= html_writer::empty_tag('input', array('type' => 'hidden',
'name' => '{NAME}[criteria][{CRITERION-id}][sortorder]', 'value' => $criterion['sortorder']));
- $shortname = html_writer::empty_tag('input', array('type'=> 'text',
- 'name' => '{NAME}[criteria][{CRITERION-id}][shortname]', 'value' => $criterion['shortname'],
- 'id ' => '{NAME}[criteria][{CRITERION-id}][shortname]'));
- $shortname = html_writer::tag('div', $shortname, array('class'=>'criterionname'));
- $description = html_writer::tag('textarea', s($criterion['description']),
- array('name' => '{NAME}[criteria][{CRITERION-id}][description]', 'cols' => '65', 'rows' => '5'));
- $description = html_writer::tag('div', $description, array('class'=>'criteriondesc'));
-
- $descriptionmarkers = html_writer::tag('textarea', s($criterion['descriptionmarkers']),
- array('name' => '{NAME}[criteria][{CRITERION-id}][descriptionmarkers]', 'cols' => '65', 'rows' => '5'));
- $descriptionmarkers = html_writer::tag('div', $descriptionmarkers, array('class'=>'criteriondescmarkers'));
+ $shortnameinput = html_writer::empty_tag('input', array('type' => 'text',
+ 'name' => '{NAME}[criteria][{CRITERION-id}][shortname]',
+ 'id ' => '{NAME}-criteria-{CRITERION-id}-shortname',
+ 'value' => $criterion['shortname'],
+ 'aria-labelledby' => '{NAME}-criterion-name-label'));
+ $shortname = html_writer::tag('div', $shortnameinput, array('class' => 'criterionname'));
+ $descriptioninput = html_writer::tag('textarea', s($criterion['description']),
+ array('name' => '{NAME}[criteria][{CRITERION-id}][description]',
+ 'id' => '{NAME}[criteria][{CRITERION-id}][description]', 'cols' => '65', 'rows' => '5'));
+ $description = html_writer::tag('div', $descriptioninput, array('class' => 'criteriondesc'));
+
+ $descriptionmarkersinput = html_writer::tag('textarea', s($criterion['descriptionmarkers']),
+ array('name' => '{NAME}[criteria][{CRITERION-id}][descriptionmarkers]',
+ 'id' => '{NAME}[criteria][{CRITERION-id}][descriptionmarkers]', 'cols' => '65', 'rows' => '5'));
+ $descriptionmarkers = html_writer::tag('div', $descriptionmarkersinput, array('class' => 'criteriondescmarkers'));
$maxscore = html_writer::empty_tag('input', array('type'=> 'text',
'name' => '{NAME}[criteria][{CRITERION-id}][maxscore]', 'size' => '3',
$mode == gradingform_guide_controller::DISPLAY_VIEW) {
$descriptionclass = 'descriptionreadonly';
}
- $shortname = html_writer::tag('div', s($criterion['shortname']),
- array('class'=>'criterionshortname', 'name' => '{NAME}[criteria][{CRITERION-id}][shortname]'));
+
+ $shortnameparams = array(
+ 'name' => '{NAME}[criteria][{CRITERION-id}][shortname]',
+ 'id' => '{NAME}[criteria][{CRITERION-id}][shortname]',
+ 'aria-describedby' => '{NAME}-criterion-name-label'
+ );
+ $shortname = html_writer::div(s($criterion['shortname']), 'criterionshortname', $shortnameparams);
+
$descmarkerclass = '';
$descstudentclass = '';
if ($mode == gradingform_guide_controller::DISPLAY_EVAL) {
$descriptionclass .= ' error';
}
- $title = html_writer::tag('label', get_string('criterion', 'gradingform_guide'),
- array('for'=>'{NAME}[criteria][{CRITERION-id}][shortname]', 'class' => 'criterionnamelabel'));
- $title .= $shortname;
+ $title = $shortname;
if ($mode == gradingform_guide_controller::DISPLAY_EDIT_FULL ||
$mode == gradingform_guide_controller::DISPLAY_PREVIEW) {
$title .= html_writer::tag('label', get_string('descriptionstudents', 'gradingform_guide'),
$mode == gradingform_guide_controller::DISPLAY_VIEW) {
$title .= $description;
if (!empty($options['showmarkspercriterionstudents'])) {
- $title .= html_writer::tag('label', get_string('maxscore', 'gradingform_guide'),
- array('for' => '{NAME}[criteria][{CRITERION-id}][maxscore]'));
+ $title .= html_writer::label(get_string('maxscore', 'gradingform_guide'), null);
$title .= $maxscore;
}
} else {
$title .= $description . $descriptionmarkers;
}
- $criteriontemplate .= html_writer::tag('td', $title, array('class' => $descriptionclass,
- 'id' => '{NAME}-criteria-{CRITERION-id}-shortname'));
+
+ // Title cell params.
+ $titletdparams = array(
+ 'class' => $descriptionclass,
+ 'id' => '{NAME}-criteria-{CRITERION-id}-shortname-cell'
+ );
+
+ if ($mode != gradingform_guide_controller::DISPLAY_EDIT_FULL &&
+ $mode != gradingform_guide_controller::DISPLAY_EDIT_FROZEN) {
+ // Set description's cell as tab-focusable.
+ $titletdparams['tabindex'] = '0';
+ }
+
+ $criteriontemplate .= html_writer::tag('td', $title, $titletdparams);
$currentremark = '';
$currentscore = '';
if (isset($value['score'])) {
$currentscore = $value['score'];
}
+
+ // Element ID of the remark text area.
+ $remarkid = $elementname . '-criteria-' . $criterion['id'] . '-remark';
+
if ($mode == gradingform_guide_controller::DISPLAY_EVAL) {
$scoreclass = '';
if (!empty($validationerrors[$criterion['id']]['score'])) {
$scoreclass = 'error';
$currentscore = $validationerrors[$criterion['id']]['score']; // Show invalid score in form.
}
- $input = html_writer::tag('textarea', s($currentremark),
- array('name' => '{NAME}[criteria][{CRITERION-id}][remark]', 'cols' => '65', 'rows' => '5',
- 'class' => 'markingguideremark'));
- $criteriontemplate .= html_writer::tag('td', $input, array('class' => 'remark'));
- $score = html_writer::tag('label', get_string('score', 'gradingform_guide'),
- array('for'=>'{NAME}[criteria][{CRITERION-id}][score]', 'class' => $scoreclass));
- $score .= html_writer::empty_tag('input', array('type'=> 'text',
- 'name' => '{NAME}[criteria][{CRITERION-id}][score]', 'class' => $scoreclass,
- 'id' => '{NAME}[criteria][{CRITERION-id}][score]',
- 'size' => '3', 'value' => $currentscore));
- $score .= '/'.$maxscore;
+
+ // Grading remark text area parameters.
+ $remarkparams = array(
+ 'name' => '{NAME}[criteria][{CRITERION-id}][remark]',
+ 'id' => $remarkid,
+ 'cols' => '65', 'rows' => '5', 'class' => 'markingguideremark',
+ 'aria-labelledby' => '{NAME}-remarklabel{CRITERION-id}'
+ );
+
+ // Grading remark text area.
+ $input = html_writer::tag('textarea', s($currentremark), $remarkparams);
+
+ // Frequently used comments chooser.
+ $chooserbuttonid = 'criteria-' . $criterion['id'] . '-commentchooser';
+ $commentchooserparams = array('id' => $chooserbuttonid, 'class' => 'commentchooser');
+ $commentchooser = html_writer::tag('button', get_string('insertcomment', 'gradingform_guide'), $commentchooserparams);
+
+ // Option items for the frequently used comments chooser dialog.
+ $commentoptions = array();
+ foreach ($comments as $id => $comment) {
+ $commentoption = new stdClass();
+ $commentoption->id = $id;
+ $commentoption->description = s($comment['description']);
+ $commentoptions[] = $commentoption;
+ }
+
+ // Include string for JS for the comment chooser title.
+ $PAGE->requires->string_for_js('insertcomment', 'gradingform_guide');
+ // Include comment_chooser module.
+ $PAGE->requires->js_call_amd('gradingform_guide/comment_chooser', 'initialise',
+ array($criterion['id'], $chooserbuttonid, $remarkid, $commentoptions));
+
+ // Hidden marking guide remark label.
+ $remarklabelparams = array(
+ 'class' => 'hidden',
+ 'id' => '{NAME}-remarklabel{CRITERION-id}'
+ );
+ $remarklabeltext = get_string('criterionremark', 'gradingform_guide', $criterion['shortname']);
+ $remarklabel = html_writer::label($remarklabeltext, $remarkid, false, $remarklabelparams);
+
+ $criteriontemplate .= html_writer::tag('td', $remarklabel . $input . $commentchooser, array('class' => 'remark'));
+
+ // Score input and max score.
+ $scoreinputparams = array(
+ 'type' => 'text',
+ 'name' => '{NAME}[criteria][{CRITERION-id}][score]',
+ 'class' => $scoreclass,
+ 'id' => '{NAME}-criteria-{CRITERION-id}-score',
+ 'size' => '3',
+ 'value' => $currentscore,
+ 'aria-labelledby' => '{NAME}-score-label'
+ );
+ $score = html_writer::empty_tag('input', $scoreinputparams);
+ $score .= html_writer::div('/' . s($criterion['maxscore']));
$criteriontemplate .= html_writer::tag('td', $score, array('class' => 'score'));
} else if ($mode == gradingform_guide_controller::DISPLAY_EVAL_FROZEN) {
'name' => '{NAME}[criteria][{CRITERION-id}][remark]', 'value' => $currentremark));
} else if ($mode == gradingform_guide_controller::DISPLAY_REVIEW ||
$mode == gradingform_guide_controller::DISPLAY_VIEW) {
- $criteriontemplate .= html_writer::tag('td', s($currentremark), array('class' => 'remark'));
+
+ // Hidden marking guide remark description.
+ $remarkdescparams = array(
+ 'id' => '{NAME}-criteria-{CRITERION-id}-remark-desc'
+ );
+ $remarkdesctext = get_string('criterionremark', 'gradingform_guide', $criterion['shortname']);
+ $remarkdesc = html_writer::div($remarkdesctext, 'hidden', $remarkdescparams);
+
+ // Remarks cell.
+ $remarkdiv = html_writer::div(s($currentremark));
+ $remarkcellparams = array(
+ 'class' => 'remark',
+ 'tabindex' => '0',
+ 'id' => '{NAME}-criteria-{CRITERION-id}-remark',
+ 'aria-describedby' => '{NAME}-criteria-{CRITERION-id}-remark-desc'
+ );
+ $criteriontemplate .= html_writer::tag('td', $remarkdesc . $remarkdiv, $remarkcellparams);
+
+ // Score cell.
if (!empty($options['showmarkspercriterionstudents'])) {
- $criteriontemplate .= html_writer::tag('td', s($currentscore). ' / '.$maxscore,
- array('class' => 'score'));
+ $scorecellparams = array(
+ 'class' => 'score',
+ 'tabindex' => '0',
+ 'id' => '{NAME}-criteria-{CRITERION-id}-score',
+ 'aria-describedby' => '{NAME}-score-label'
+ );
+ $scorediv = html_writer::div(s($currentscore) . ' / ' . s($criterion['maxscore']));
+ $criteriontemplate .= html_writer::tag('td', $scorediv, $scorecellparams);
}
}
$criteriontemplate .= html_writer::end_tag('tr'); // Criterion.
}
}
}
- $criteriontemplate = html_writer::start_tag('tr', array('class' => 'criterion'. $comment['class'],
+ $commenttemplate = html_writer::start_tag('tr', array('class' => 'criterion'. $comment['class'],
'id' => '{NAME}-comments-{COMMENT-id}'));
if ($mode == gradingform_guide_controller::DISPLAY_EDIT_FULL) {
- $criteriontemplate .= html_writer::start_tag('td', array('class' => 'controls'));
+ $commenttemplate .= html_writer::start_tag('td', array('class' => 'controls'));
foreach (array('moveup', 'delete', 'movedown') as $key) {
$value = get_string('comments'.$key, 'gradingform_guide');
$button = html_writer::empty_tag('input', array('type' => 'submit',
'name' => '{NAME}[comments][{COMMENT-id}]['.$key.']', 'id' => '{NAME}-comments-{COMMENT-id}-'.$key,
- 'value' => $value, 'title' => $value, 'tabindex' => -1));
- $criteriontemplate .= html_writer::tag('div', $button, array('class' => $key));
+ 'value' => $value, 'title' => $value));
+ $commenttemplate .= html_writer::tag('div', $button, array('class' => $key));
}
- $criteriontemplate .= html_writer::end_tag('td'); // Controls.
- $criteriontemplate .= html_writer::empty_tag('input', array('type' => 'hidden',
+ $commenttemplate .= html_writer::end_tag('td'); // Controls.
+ $commenttemplate .= html_writer::empty_tag('input', array('type' => 'hidden',
'name' => '{NAME}[comments][{COMMENT-id}][sortorder]', 'value' => $comment['sortorder']));
$description = html_writer::tag('textarea', s($comment['description']),
- array('name' => '{NAME}[comments][{COMMENT-id}][description]', 'cols' => '65', 'rows' => '5'));
+ array('name' => '{NAME}[comments][{COMMENT-id}][description]',
+ 'id' => '{NAME}-comments-{COMMENT-id}-description',
+ 'aria-labelledby' => '{NAME}-comment-label', 'cols' => '65', 'rows' => '5'));
$description = html_writer::tag('div', $description, array('class'=>'criteriondesc'));
} else {
if ($mode == gradingform_guide_controller::DISPLAY_EDIT_FROZEN) {
- $criteriontemplate .= html_writer::empty_tag('input', array('type' => 'hidden',
+ $commenttemplate .= html_writer::empty_tag('input', array('type' => 'hidden',
'name' => '{NAME}[comments][{COMMENT-id}][sortorder]', 'value' => $comment['sortorder']));
- $criteriontemplate .= html_writer::empty_tag('input', array('type' => 'hidden',
+ $commenttemplate .= html_writer::empty_tag('input', array('type' => 'hidden',
'name' => '{NAME}[comments][{COMMENT-id}][description]', 'value' => $comment['description']));
}
if ($mode == gradingform_guide_controller::DISPLAY_EVAL) {
if (isset($comment['error_description'])) {
$descriptionclass .= ' error';
}
- $criteriontemplate .= html_writer::tag('td', $description, array('class' => $descriptionclass,
- 'id' => '{NAME}-comments-{COMMENT-id}-description'));
- $criteriontemplate .= html_writer::end_tag('tr'); // Criterion.
+ $descriptioncellparams = array(
+ 'class' => $descriptionclass,
+ 'id' => '{NAME}-comments-{COMMENT-id}-description-cell'
+ );
+ // Make description cell tab-focusable when in review mode.
+ if ($mode != gradingform_guide_controller::DISPLAY_EDIT_FULL &&
+ $mode != gradingform_guide_controller::DISPLAY_EDIT_FROZEN) {
+ $descriptioncellparams['tabindex'] = '0';
+ }
+ $commenttemplate .= html_writer::tag('td', $description, $descriptioncellparams);
+ $commenttemplate .= html_writer::end_tag('tr'); // Criterion.
- $criteriontemplate = str_replace('{NAME}', $elementname, $criteriontemplate);
- $criteriontemplate = str_replace('{COMMENT-id}', $comment['id'], $criteriontemplate);
- return $criteriontemplate;
+ $commenttemplate = str_replace('{NAME}', $elementname, $commenttemplate);
+ $commenttemplate = str_replace('{COMMENT-id}', $comment['id'], $commenttemplate);
+ return $commenttemplate;
}
/**
* This function returns html code for displaying guide template (content before and after
$guidetemplate = html_writer::start_tag('div', array('id' => 'guide-{NAME}',
'class' => 'clearfix gradingform_guide'.$classsuffix));
- $guidetemplate .= html_writer::tag('table', $criteriastr, array('class' => 'criteria', 'id' => '{NAME}-criteria'));
+
+ // Hidden guide label.
+ $guidedescparams = array(
+ 'id' => 'guide-{NAME}-desc',
+ 'aria-hidden' => 'true'
+ );
+ $guidetemplate .= html_writer::div(get_string('guide', 'gradingform_guide'), 'hidden', $guidedescparams);
+
+ // Hidden criterion name label/description.
+ $guidetemplate .= html_writer::div(get_string('criterionname', 'gradingform_guide'), 'hidden',
+ array('id' => '{NAME}-criterion-name-label'));
+
+ // Hidden score label/description.
+ $guidetemplate .= html_writer::div(get_string('score', 'gradingform_guide'), 'hidden', array('id' => '{NAME}-score-label'));
+
+ // Criteria table parameters.
+ $criteriatableparams = array(
+ 'class' => 'criteria',
+ 'id' => '{NAME}-criteria',
+ 'aria-describedby' => 'guide-{NAME}-desc');
+ $guidetemplate .= html_writer::tag('table', $criteriastr, $criteriatableparams);
if ($mode == gradingform_guide_controller::DISPLAY_EDIT_FULL) {
$value = get_string('addcriterion', 'gradingform_guide');
$input = html_writer::empty_tag('input', array('type' => 'submit', 'name' => '{NAME}[criteria][addcriterion]',
}
if (!empty($commentstr)) {
- $guidetemplate .= html_writer::tag('label', get_string('comments', 'gradingform_guide'),
- array('for' => '{NAME}-comments', 'class' => 'commentheader'));
- $guidetemplate .= html_writer::tag('table', $commentstr, array('class' => 'comments', 'id' => '{NAME}-comments'));
+ $guidetemplate .= html_writer::div(get_string('comments', 'gradingform_guide'), 'commentheader',
+ array('id' => '{NAME}-comments-label'));
+ $guidetemplate .= html_writer::div(get_string('comment', 'gradingform_guide'), 'hidden',
+ array('id' => '{NAME}-comment-label', 'aria-hidden' => 'true'));
+ $commentstableparams = array(
+ 'class' => 'comments',
+ 'id' => '{NAME}-comments',
+ 'aria-describedby' => '{NAME}-comments-label');
+ $guidetemplate .= html_writer::tag('table', $commentstr, $commentstableparams);
}
if ($mode == gradingform_guide_controller::DISPLAY_EDIT_FULL) {
$value = get_string('addcomment', 'gradingform_guide');
$criterionvalue = null;
}
$criteriastr .= $this->criterion_template($mode, $options, $elementname, $criterion, $criterionvalue,
- $validationerrors);
+ $validationerrors, $comments);
}
+
$cnt = 0;
$commentstr = '';
// Check if comments should be displayed.
if ($mode == gradingform_guide_controller::DISPLAY_EDIT_FULL ||
$mode == gradingform_guide_controller::DISPLAY_EDIT_FROZEN ||
$mode == gradingform_guide_controller::DISPLAY_PREVIEW ||
- $mode == gradingform_guide_controller::DISPLAY_EVAL ||
$mode == gradingform_guide_controller::DISPLAY_EVAL_FROZEN) {
foreach ($comments as $id => $comment) {
$comment['id'] = $id;
$comment['class'] = $this->get_css_class_suffix($cnt++, count($comments) -1);
- $commentstr .= $this->comment_template($mode, $elementname, $comment);
+ $commentstr .= $this->comment_template($mode, $elementname, $comment);
}
}
$output = $this->guide_template($mode, $options, $elementname, $criteriastr, $commentstr);
* @return string
*/
public function display_regrade_confirmation($elementname, $changelevel, $value) {
- $html = html_writer::start_tag('div', array('class' => 'gradingform_guide-regrade'));
+ $html = html_writer::start_tag('div', array('class' => 'gradingform_guide-regrade', 'role' => 'alert'));
if ($changelevel<=2) {
$html .= get_string('regrademessage1', 'gradingform_guide');
$selectoptions = array(
--- /dev/null
+{{!
+ This file is part of Moodle - http://moodle.org/
+
+ Moodle is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Moodle is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+}}
+{{!
+ @template gradingform_guide/comment_chooser
+
+ Moodle comment chooser template for marking guide.
+
+ The purpose of this template is to render a list of frequently used comments that can be used by the comment chooser dialog.
+
+ Classes required for JS:
+ * none
+
+ Data attributes required for JS:
+ * none
+
+ Context variables required for this template:
+ * criterionId The criterion ID this chooser template is being generated for.
+ * comments Array of id / description pairs.
+
+ Example context (json):
+ {
+ "criterionId": "1",
+ "comments": [
+ {
+ "id": "1",
+ "description": "Test comment description 1"
+ },
+ {
+ "id": "2",
+ "description": "Test comment description 2"
+ }
+ ]
+ }
+}}
+<div class="gradingform_guide_comment_chooser" id="comment_chooser">
+ <ul role="list">
+ {{#comments}}
+ <li role="listitem">
+ <button id="comment-option-{{criterionId}}-{{id}}" class="btn btn-link" tabindex="0">
+ {{description}}
+ </button>
+ </li>
+ {{/comments}}
+ </ul>
+</div>
--- /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/>.
+
+/**
+ * Steps definitions for marking guides.
+ *
+ * @package gradingform_guide
+ * @category test
+ * @copyright 2015 Jun Pataleta
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once(__DIR__ . '/../../../../../../lib/behat/behat_base.php');
+
+use Behat\Gherkin\Node\TableNode as TableNode,
+ Behat\Behat\Context\Step\Given as Given,
+ Behat\Behat\Context\Step\When as When,
+ Behat\Behat\Context\Step\Then as Then,
+ Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException,
+ Behat\Mink\Exception\ExpectationException as ExpectationException;
+
+/**
+ * Steps definitions to help with marking guides.
+ *
+ * @package gradingform_guide
+ * @category test
+ * @copyright 2015 Jun Pataleta
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class behat_gradingform_guide extends behat_base {
+
+ /**
+ * Defines the marking guide with the provided data, following marking guide's definition grid cells.
+ *
+ * This method fills the marking guide of the marking guide definition
+ * form; the provided TableNode should contain one row for
+ * each criterion and each cell of the row should contain:
+ * # Criterion name, a.k.a. shortname
+ * # Description for students
+ * # Description for markers
+ * # Max score
+ *
+ * Works with both JS and non-JS.
+ *
+ * @When /^I define the following marking guide:$/
+ * @throws ExpectationException
+ * @param TableNode $guide
+ */
+ public function i_define_the_following_marking_guide(TableNode $guide) {
+ $steptableinfo = '| Criterion name | Description for students | Description for markers | Maximum mark |';
+
+ if ($criteria = $guide->getHash()) {
+ $addcriterionbutton = $this->find_button(get_string('addcriterion', 'gradingform_guide'));
+
+ foreach ($criteria as $index => $criterion) {
+ // Make sure the criterion array has 4 elements.
+ if (count($criterion) != 4) {
+ throw new ExpectationException(
+ 'The criterion definition should contain name, description for students and markers, and maximum points. ' .
+ 'Please follow this format: ' . $steptableinfo,
+ $this->getSession()
+ );
+ }
+
+ // On load, there's already a criterion template ready.
+ $shortnamevisible = false;
+ if ($index > 0) {
+ // So if the index is greater than 0, we click the Add new criterion button to add a new criterion.
+ $addcriterionbutton->click();
+ $shortnamevisible = true;
+ }
+
+ $criterionroot = 'guide[criteria][NEWID' . ($index + 1) . ']';
+
+ // Set the field value for the Criterion name.
+ $this->set_guide_field_value($criterionroot . '[shortname]', $criterion['Criterion name'], $shortnamevisible);
+
+ // Set the field value for the Description for students field.
+ $this->set_guide_field_value($criterionroot . '[description]', $criterion['Description for students']);
+
+ // Set the field value for the Description for markers field.
+ $this->set_guide_field_value($criterionroot . '[descriptionmarkers]', $criterion['Description for markers']);
+
+ // Set the field value for the Max score field.
+ $this->set_guide_field_value($criterionroot . '[maxscore]', $criterion['Maximum mark']);
+ }
+ }
+ }
+
+ /**
+ * Defines the marking guide with the provided data, following marking guide's definition grid cells.
+ *
+ * This method fills the table of frequently used comments of the marking guide definition form.
+ * The provided TableNode should contain one row for each frequently used comment.
+ * Each row contains:
+ * # Comment
+ *
+ * Works with both JS and non-JS.
+ *
+ * @When /^I define the following frequently used comments:$/
+ * @throws ExpectationException
+ * @param TableNode $commentstable
+ */
+ public function i_define_the_following_frequently_used_comments(TableNode $commentstable) {
+ $steptableinfo = '| Comment |';
+
+ if ($comments = $commentstable->getRows()) {
+ $addcommentbutton = $this->find_button(get_string('addcomment', 'gradingform_guide'));
+
+ foreach ($comments as $index => $comment) {
+ // Make sure the comment array has only 1 element.
+ if (count($comment) != 1) {
+ throw new ExpectationException(
+ 'The comment cannot be empty. Please follow this format: ' . $steptableinfo,
+ $this->getSession()
+ );
+ }
+
+ // On load, there's already a comment template ready.
+ $commentfieldvisible = false;
+ if ($index > 0) {
+ // So if the index is greater than 0, we click the Add frequently used comment button to add a new criterion.
+ $addcommentbutton->click();
+ $commentfieldvisible = true;
+ }
+
+ $commentroot = 'guide[comments][NEWID' . ($index + 1) . ']';
+
+ // Set the field value for the frequently used comment.
+ $this->set_guide_field_value($commentroot . '[description]', $comment[0], $commentfieldvisible);
+ }
+ }
+ }
+
+ /**
+ * Performs grading of the student by filling out the marking guide.
+ * Set one line per criterion and for each criterion set "| Criterion name | Points | Remark |".
+ *
+ * @When /^I grade by filling the marking guide with:$/
+ *
+ * @throws ExpectationException
+ * @param TableNode $guide
+ * @return void
+ */
+ public function i_grade_by_filling_the_marking_guide_with(TableNode $guide) {
+
+ $criteria = $guide->getRowsHash();
+
+ $stepusage = '"I grade by filling the rubric with:" step needs you to provide a table where each row is a criterion' .
+ ' and each criterion has 3 different values: | Criterion name | Number of points | Remark text |';
+
+ // To fill with the steps to execute.
+ $steps = array();
+
+ // First element -> name, second -> points, third -> Remark.
+ foreach ($criteria as $name => $criterion) {
+
+ // We only expect the points and the remark, as the criterion name is $name.
+ if (count($criterion) !== 2) {
+ throw new ExpectationException($stepusage, $this->getSession());
+ }
+
+ // Numeric value here.
+ $points = $criterion[0];
+ if (!is_numeric($points)) {
+ throw new ExpectationException($stepusage, $this->getSession());
+ }
+
+ $criterionid = 0;
+ if ($criterionnamediv = $this->find('xpath', "//div[@class='criterionshortname'][text()='$name']")) {
+ $criteriondivname = $criterionnamediv->getAttribute('name');
+ // Criterion's name is of the format "advancedgrading[criteria][ID][shortname]".
+ // So just explode the string with "][" as delimiter to extract the criterion ID.
+ if ($nameparts = explode('][', $criteriondivname)) {
+ $criterionid = $nameparts[1];
+ }
+ }
+
+ if ($criterionid) {
+ $criterionroot = 'advancedgrading[criteria]' . '[' . $criterionid . ']';
+
+ $steps[] = new Given('I set the field "' . $criterionroot . '[score]' . '" to "' . $points . '"');
+ $steps[] = new Given('I set the field "' . $criterionroot . '[remark]' . '" to "' . $criterion[1] . '"');
+ }
+ }
+
+ return $steps;
+ }
+
+ /**
+ * Makes a hidden marking guide field visible (if necessary) and sets a value on it.
+ *
+ * @param string $name The name of the field
+ * @param string $value The value to set
+ * @param bool $visible
+ * @return void
+ */
+ protected function set_guide_field_value($name, $value, $visible = false) {
+ // Fields are hidden by default.
+ if ($this->running_javascript() && $visible === false) {
+ $xpath = "//*[@name='$name']/following-sibling::*[contains(concat(' ', normalize-space(@class), ' '), ' plainvalue ')]";
+ $textnode = $this->find('xpath', $xpath);
+ $textnode->click();
+ }
+
+ // Set the value now.
+ $field = $this->find_field($name);
+ $field->setValue($value);
+ }
+}
--- /dev/null
+@gradingform @gradingform_guide
+Feature: Marking guides can be created and edited
+ In order to use and refine marking guide to grade students
+ As a teacher
+ I need to edit previously used marking guides
+
+ Background:
+ Given the following "users" exist:
+ | username | firstname | lastname | email |
+ | teacher1 | Teacher | 1 | teacher1@example.com |
+ | student1 | Student | 1 | student1@example.com |
+ And the following "courses" exist:
+ | fullname | shortname | format |
+ | Course 1 | C1 | topics |
+ And the following "course enrolments" exist:
+ | user | course | role |
+ | teacher1 | C1 | editingteacher |
+ | student1 | C1 | student |
+ And I log in as "teacher1"
+ And I follow "Course 1"
+ And I turn editing mode on
+ And I add a "Assignment" to section "1" and I fill the form with:
+ | Assignment name | Test assignment 1 name |
+ | Description | Test assignment description |
+ | Grading method | Marking guide |
+ # Defining a marking guide
+ When I go to "Test assignment 1 name" advanced grading definition page
+ And I set the following fields to these values:
+ | Name | Assignment 1 marking guide |
+ | Description | Marking guide test description |
+ And I define the following marking guide:
+ | Criterion name | Description for students | Description for markers | Maximum mark |
+ | Guide criterion A | Guide A description for students | Guide A description for markers | 30 |
+ | Guide criterion B | Guide B description for students | Guide B description for markers | 30 |
+ | Guide criterion C | Guide C description for students | Guide C description for markers | 40 |
+ And I define the following frequently used comments:
+ | Comment 1 |
+ | Comment 2 |
+ | Comment 3 |
+ | Comment 4 |
+ And I press "Save marking guide and make it ready"
+ Then I should see "Ready for use"
+ And I should see "Guide criterion A"
+ And I should see "Guide criterion B"
+ And I should see "Guide criterion C"
+ And I should see "Comment 1"
+ And I should see "Comment 2"
+ And I should see "Comment 3"
+ And I should see "Comment 4"
+
+ @javascript
+ Scenario: Deleting criterion and comment
+ # Deleting criterion
+ When I go to "Test assignment 1 name" advanced grading definition page
+ And I click on "Delete criterion" "button" in the "Guide criterion B" "table_row"
+ And I press "Yes"
+ And I press "Save"
+ Then I should see "Guide criterion A"
+ And I should see "Guide criterion C"
+ And I should see "WARNING: Your marking guide has a maximum grade of 70 points"
+ But I should not see "Guide criterion B"
+ # Deleting a frequently used comment
+ When I go to "Test assignment 1 name" advanced grading definition page
+ And I click on "Delete comment" "button" in the "Comment 3" "table_row"
+ And I press "Yes"
+ And I press "Save"
+ Then I should see "Comment 1"
+ And I should see "Comment 2"
+ And I should see "Comment 4"
+ But I should not see "Comment 3"
+
+ @javascript
+ Scenario: Grading and viewing graded marking guide
+ # Grading a student.
+ When I go to "Student 1" "Test assignment 1 name" activity advanced grading page
+ And I grade by filling the marking guide with:
+ | Guide criterion A | 25 | Very good |
+ | Guide criterion B | 20 | |
+ | Guide criterion C | 35 | Nice! |
+ # Inserting frequently used comment.
+ And I click on "Insert frequently used comment" "button" in the "Guide criterion B" "table_row"
+ And I wait "1" seconds
+ And I press "Comment 4"
+ And I wait "1" seconds
+ Then the field "Guide criterion B criterion remark" matches value "Comment 4"
+ When I press "Save changes"
+ Then I should see "The grade changes were saved"
+ # Checking that the user grade is correct.
+ When I press "Continue"
+ Then I should see "80" in the "Student 1" "table_row"
+ And I log out
+ # Viewing it as a student.
+ And I log in as "student1"
+ And I follow "Course 1"
+ And I follow "Test assignment 1 name"
+ And I should see "80" in the ".feedback" "css_element"
+ And I should see "Marking guide test description" in the ".feedback" "css_element"
+ And I should see "Very good"
+ And I should see "Comment 4"
+ And I should see "Nice!"
+
+ Scenario: I can use marking guides to grade and edit them later updating students grades with Javascript disabled
* Given a category element returns collapsing +/- icon if available
*
* @deprecated since Moodle 2.9 MDL-46662 - please do not use this function any more.
- * @todo MDL-49021 This will be deleted in Moodle 3.1
- * @see grade_report_grader::get_course_header()
- * @param object $element
- * @return string HTML
*/
protected function get_collapsing_icon($element) {
- global $OUTPUT;
- debugging('get_collapsing_icon is deprecated, please use get_course_header instead.', DEBUG_DEVELOPER);
-
- $icon = '';
- // If object is a category, display expand/contract icon
- if ($element['type'] == 'category') {
- // Load language strings
- $strswitchminus = $this->get_lang_string('aggregatesonly', 'grades');
- $strswitchplus = $this->get_lang_string('gradesonly', 'grades');
- $strswitchwhole = $this->get_lang_string('fullmode', 'grades');
-
- $url = new moodle_url($this->gpr->get_return_url(null, array('target'=>$element['eid'], 'sesskey'=>sesskey())));
-
- if (in_array($element['object']->id, $this->collapsed['aggregatesonly'])) {
- $url->param('action', 'switch_plus');
- $icon = $OUTPUT->action_icon($url, new pix_icon('t/switch_plus', $strswitchplus));
-
- } else if (in_array($element['object']->id, $this->collapsed['gradesonly'])) {
- $url->param('action', 'switch_whole');
- $icon = $OUTPUT->action_icon($url, new pix_icon('t/switch_whole', $strswitchwhole));
-
- } else {
- $url->param('action', 'switch_minus');
- $icon = $OUTPUT->action_icon($url, new pix_icon('t/switch_minus', $strswitchminus));
- }
- }
- return $icon;
+ throw new coding_exception('get_collapsing_icon() can not be used any more, please use get_course_header() instead.');
}
/**
$string['importgeneralsettings'] = 'General import defaults';
$string['importgeneralmaxresults'] = 'Maximum number of courses listed for import';
$string['importgeneralmaxresults_desc'] = 'This controls the number of courses that are listed during the first step of the import process';
+$string['importgeneralduplicateadminallowed'] = 'Allow admin conflict resolution';
+$string['importgeneralduplicateadminallowed_desc'] = 'If the site has an account with username \'admin\', then attempting to restore a backup file containing an account with username \'admin\' can cause a conflict. If this setting is enabled, the conflict will be resolved by changing the username in the backup file to \'admin_xyz\'.';
$string['importfile'] = 'Import a backup file';
$string['importbackupstage1action'] = 'Next';
$string['importbackupstage2action'] = 'Next';
$string['cachedef_questiondata'] = 'Question definitions';
$string['cachedef_repositories'] = 'Repositories instances data';
$string['cachedef_string'] = 'Language string cache';
+$string['cachedef_tags'] = 'Tags collections and areas';
$string['cachedef_userselections'] = 'Data used to persist user selections throughout Moodle';
$string['cachedef_yuimodules'] = 'YUI Module definitions';
$string['cachelock_file_default'] = 'Default file locking';
withselectedtags,core_tag
tag:create,core_role
categoriesanditems,core_grades
+taggedwith,core_tag
$string['restorecannotassignroles'] = 'Restore needs to assign roles and you do not have permission to do so';
$string['restorecannotcreateorassignroles'] = 'Restore needs to create or assign roles and you do not have permission to do so';
$string['restorecannotcreateuser'] = 'Restore needs to create user \'{$a}\' from backup file and you do not have permission to do so';
+$string['restoremnethostidmismatch'] = 'MNet host id of user \'{$a}\' does not match local MNet host ID.';
$string['restorecannotoverrideperms'] = 'Restore needs to override permissions and you do not have permission to do so';
$string['restorecoursenow'] = 'Restore this course now!';
$string['restoredaccount'] = 'Restored account';
$string['submit'] = 'Submit';
$string['submitandfinish'] = 'Submit and finish';
$string['submitted'] = 'Submit: {$a}';
+$string['tagarea_question'] = 'Questions';
$string['technicalinfo'] = 'Technical information';
$string['technicalinfo_help'] = 'This technical information is probably only useful for developers working on new question types. It may also be helpful when trying to diagnose problems with questions.';
$string['technicalinfominfraction'] = 'Minimum fraction: {$a}';
$string['added'] = 'Official tag(s) added';
$string['addotags'] = 'Add official tags';
+$string['addtagcoll'] = 'Add tag collection';
$string['addtagtomyinterests'] = 'Add "{$a}" to my interests';
$string['alltagpages'] = 'All tag pages';
+$string['backtoallitems'] = 'Back to all items tagged with "{$a}"';
+$string['changessaved'] = 'Changes saved';
+$string['changetagcoll'] = 'Change tag collection of area {$a}';
+$string['collnameexplained'] = 'Leave the field empty to use the default value: {$a}';
+$string['component'] = 'Component';
$string['confirmdeletetag'] = 'Are you sure you want to delete this tag?';
$string['confirmdeletetags'] = 'Are you sure you want to delete selected tags?';
$string['count'] = 'Count';
$string['coursetags'] = 'Course tags';
+$string['defautltagcoll'] = 'Default collection';
$string['delete'] = 'Delete';
$string['deleteselected'] = 'Delete selected';
$string['deleted'] = 'Tag(s) deleted';
$string['editname'] = 'Edit tag name';
$string['edittag'] = 'Edit this tag';
$string['entertags'] = 'Enter tags...';
+$string['edittagcoll'] = 'Edit tag collection {$a}';
$string['errortagfrontpage'] = 'Tagging the site main page is not allowed';
$string['errorupdatingrecord'] = 'Error updating tag record';
$string['eventtagadded'] = 'Tag added to an item';
+$string['eventtagcolldeleted'] = 'Tag collection deleted';
+$string['eventtagcollcreated'] = 'Tag collection created';
+$string['eventtagcollupdated'] = 'Tag collection updatedhp ';
$string['eventtagcreated'] = 'Tag created';
$string['eventtagdeleted'] = 'Tag deleted';
$string['eventtagflagged'] = 'Tag flagged';
$string['eventtagremoved'] = 'Tag removed from an item';
$string['eventtagunflagged'] = 'Tag unflagged';
$string['eventtagupdated'] = 'Tag updated';
+$string['exclusivemode'] = 'Show only tagged {$a->tagarea}';
$string['flag'] = 'Flag';
$string['flagged'] = 'Tag flagged';
$string['flagasinappropriate'] = 'Flag as inappropriate';
$string['changename'] = 'Change tag name';
$string['changetype'] = 'Change tag type';
$string['id'] = 'id';
+$string['inalltagcoll'] = 'Everywhere';
+$string['itemstaggedwith'] = '{$a->tagarea} tagged with "{$a->tag}"';
+$string['lesstags'] = 'less...';
$string['manageofficialtags'] = 'Manage official tags';
$string['managetags'] = 'Manage tags';
+$string['managetagcolls'] = 'Manage tag collections';
+$string['moretags'] = 'more...';
$string['name'] = 'Tag name';
$string['namesalreadybeeingused'] = 'Tag names already being used';
$string['newnamefor'] = 'New name for tag {$a}';
+$string['nextpage'] = 'More';
+$string['notagsfound'] = 'No tags matching "{$a}" found';
$string['noresultsfor'] = 'No results for "{$a}"';
$string['nothingtoupdate'] = 'Nothing to update';
$string['officialtag'] = 'Official';
$string['otags'] = 'Official tags';
$string['othertags'] = 'Other tags';
$string['owner'] = 'Owner';
+$string['prevpage'] = 'Back';
$string['ptags'] = 'User defined tags (Comma separated)';
$string['relatedblogs'] = 'Most recent blog entries';
$string['relatedtags'] = 'Related tags';
$string['rssdesc'] = 'This RSS feed was automatically generated by Moodle and contains user generated tags for courses.';
$string['rsstitle'] = 'Course tags RSS feed for user: {$a}';
$string['search'] = 'Search';
+$string['searchable'] = 'Searchable';
+$string['searchable_help'] = 'Tags in this tag collection can be searched for on "Search tags" page. If unchecked, tags can still be accessed by clicking on them or via different search interfaces.';
$string['searchresultsfor'] = 'Search results for "{$a}"';
$string['searchtags'] = 'Search tags';
-$string['seeallblogs'] = 'See all blog entries tagged with "{$a}"...';
+$string['seeallblogs'] = 'See all blog entries tagged with "{$a}"';
$string['select'] = 'Select';
+$string['selectcoll'] = 'Select tag collection';
$string['selecttag'] = 'Select tag {$a}';
$string['settypedefault'] = 'Remove from official tags';
$string['settypeofficial'] = 'Make official';
+$string['showingfirsttags'] = 'Showing {$a} most popular tags';
+$string['suredeletecoll'] = 'Are you sure you want to delete tag collection "{$a}"?';
$string['tag'] = 'Tag';
+$string['tagarea_blog_external'] = 'External blog posts';
+$string['tagarea_post'] = 'Blog posts';
+$string['tagarea_user'] = 'User interests';
+$string['tagarea_course'] = 'Courses';
+$string['tagareaenabled'] = 'Enabled';
+$string['tagareaname'] = 'Name';
+$string['tagareas'] = 'Tag areas';
+$string['tagcollection'] = 'Tag collection';
+$string['tagcollections'] = 'Tag collections';
$string['tagdescription'] = 'Tag description';
-$string['taggedwith'] = 'tagged with "{$a}"';
$string['tags'] = 'Tags';
$string['tagsaredisabled'] = 'Tags are disabled';
$string['tagtype'] = 'Tag type';
$string['thistaghasnodesc'] = 'This tag currently has no description.';
$string['updated'] = 'Updated';
$string['withselectedtags'] = 'With selected tags...';
+
+// Deprecated since 3.1 .
+
+$string['taggedwith'] = 'tagged with "{$a}"';
$string['addservice'] = 'Add a new service: {$a->name} (id: {$a->id})';
$string['addservicefunction'] = 'Add functions to the service "{$a}"';
$string['allusers'] = 'All users';
-$string['amftestclient'] = 'AMF test client';
$string['apiexplorer'] = 'API explorer';
$string['apiexplorernotavalaible'] = 'API explorer not available yet.';
$string['arguments'] = 'Arguments';
$string['supplyinfo'] = 'More details';
$string['testauserwithtestclientdescription'] = 'Simulate external access to the service using the web service test client. Before doing so, log in as a user with the moodle/webservice:createtoken capability and obtain the security key (token) via the user\'s preferences page. You will use this token in the test client. In the test client, also choose an enabled protocol with the token authentication. <strong>WARNING: The functions that you test WILL BE EXECUTED for this user, so be careful what you choose to test!</strong>';
$string['testclient'] = 'Web service test client';
-$string['testclientdescription'] = '* The web service test client <strong>executes</strong> the functions for <strong>REAL</strong>. Do not test functions that you don\'t know. <br/>* All existing web service functions are not yet implemented into the test client. <br/>* In order to check that a user cannot access some functions, you can test some functions that you didn\'t allow.<br/>* To see clearer error messages set the debugging to <strong>{$a->mode}</strong> into {$a->atag}<br/>* Access the {$a->amfatag}.';
+$string['testclientdescription'] = '* The web service test client <strong>executes</strong> the functions for <strong>REAL</strong>. Do not test functions that you don\'t know. <br/>* All existing web service functions are not yet implemented into the test client. <br/>* In order to check that a user cannot access some functions, you can test some functions that you didn\'t allow.<br/>* To see clearer error messages set the debugging to <strong>{$a->mode}</strong> into {$a->atag}.';
$string['testwithtestclient'] = 'Test the service';
$string['testwithtestclientdescription'] = 'Simulate external access to the service using the web service test client. Use an enabled protocol with token authentication. <strong>WARNING: The functions that you test WILL BE EXECUTED, so be careful what you choose to test!</strong>';
$string['token'] = 'Token';
echo $OUTPUT->heading($pluginname);
- // Delete all tag instances associated with this plugin.
- require_once($CFG->dirroot . '/tag/lib.php');
- tag_delete_instances($component);
+ // Delete all tag areas, collections and instances associated with this plugin.
+ core_tag_area::uninstall($component);
// Custom plugin uninstall.
$plugindirectory = core_component::get_plugin_directory($type, $name);
*/
class admin_setting_enablemobileservice extends admin_setting_configcheckbox {
- /** @var boolean True means that the capability 'webservice/xmlrpc:use' is set for authenticated user role */
- private $xmlrpcuse;
/** @var boolean True means that the capability 'webservice/rest:use' is set for authenticated user role */
private $restuse;
/**
- * Return true if Authenticated user role has the capability 'webservice/xmlrpc:use' and 'webservice/rest:use', otherwise false.
+ * Return true if Authenticated user role has the capability 'webservice/rest:use', otherwise false.
*
* @return boolean
*/
private function is_protocol_cap_allowed() {
global $DB, $CFG;
- // We keep xmlrpc enabled for backward compatibility.
- // If the $this->xmlrpcuse variable is not set, it needs to be set.
- if (empty($this->xmlrpcuse) and $this->xmlrpcuse!==false) {
- $params = array();
- $params['permission'] = CAP_ALLOW;
- $params['roleid'] = $CFG->defaultuserroleid;
- $params['capability'] = 'webservice/xmlrpc:use';
- $this->xmlrpcuse = $DB->record_exists('role_capabilities', $params);
- }
-
// If the $this->restuse variable is not set, it needs to be set.
if (empty($this->restuse) and $this->restuse!==false) {
$params = array();
$this->restuse = $DB->record_exists('role_capabilities', $params);
}
- return ($this->xmlrpcuse && $this->restuse);
+ return $this->restuse;
}
/**
- * Set the 'webservice/xmlrpc:use'/'webservice/rest:use' to the Authenticated user role (allow or not)
+ * Set the 'webservice/rest:use' to the Authenticated user role (allow or not)
* @param type $status true to allow, false to not set
*/
private function set_protocol_cap($status) {
}
if (!empty($assign)) {
$systemcontext = context_system::instance();
- assign_capability('webservice/xmlrpc:use', $permission, $CFG->defaultuserroleid, $systemcontext->id, true);
assign_capability('webservice/rest:use', $permission, $CFG->defaultuserroleid, $systemcontext->id, true);
}
}
$mobileservice->enabled = 1;
$webservicemanager->update_external_service($mobileservice);
- //enable xml-rpc server
+ // Enable REST server.
$activeprotocols = empty($CFG->webserviceprotocols) ? array() : explode(',', $CFG->webserviceprotocols);
- if (!in_array('xmlrpc', $activeprotocols)) {
- $activeprotocols[] = 'xmlrpc';
- $updateprotocol = true;
- }
-
if (!in_array('rest', $activeprotocols)) {
$activeprotocols[] = 'rest';
$updateprotocol = true;
set_config('webserviceprotocols', implode(',', $activeprotocols));
}
- //allow xml-rpc:use capability for authenticated user
+ // Allow rest:use capability for authenticated user.
$this->set_protocol_cap(true);
} else {
if (empty($otherenabledservices)) {
set_config('enablewebservices', false);
- //also disable xml-rpc server
+ // Also disable REST server.
$activeprotocols = empty($CFG->webserviceprotocols) ? array() : explode(',', $CFG->webserviceprotocols);
- $protocolkey = array_search('xmlrpc', $activeprotocols);
- if ($protocolkey !== false) {
- unset($activeprotocols[$protocolkey]);
- $updateprotocol = true;
- }
$protocolkey = array_search('rest', $activeprotocols);
if ($protocolkey !== false) {
set_config('webserviceprotocols', implode(',', $activeprotocols));
}
- //disallow xml-rpc:use capability for authenticated user
+ // Disallow rest:use capability for authenticated user.
$this->set_protocol_cap(false);
}
return /** @alias module:core/tag */ {
/**
- * Initialises handlers for AJAX methods.
+ * Initialises tag index page.
*
- * @method init
+ * @method init_tagindex_page
+ */
+ init_tagindex_page: function() {
+ // Click handler for changing tag type.
+ $('body').delegate('.tagarea[data-ta] a[data-quickload=1]', 'click', function(e) {
+ e.preventDefault();
+ var target = $( this ),
+ query = target.context.search.replace(/^\?/, ''),
+ tagarea = target.closest('.tagarea[data-ta]'),
+ args = query.split('&').reduce(function(s,c){var t=c.split('=');s[t[0]]=decodeURIComponent(t[1]);return s;},{});
+
+ var promises = ajax.call([{
+ methodname: 'core_tag_get_tagindex',
+ args: { tagindex: args }
+ }], true);
+
+ $.when.apply($, promises)
+ .done( function(data) {
+ templates.render('core_tag/index', data).done(function(html) {
+ tagarea.replaceWith(html);
+ });
+ });
+ });
+ },
+
+ /**
+ * Initialises tag management page.
+ *
+ * @method init_manage_page
*/
init_manage_page: function() {
3/ replaced explode in iCalendar_component::unserialize() with preg_split to support various line breaks (20 Nov 2012)
4/ updated rfc2445_is_valid_value() to accept single part rrule as a valid value (16 Jun 2014)
5/ updated DTEND;TZID and DTSTAR;TZID values to support quotations (7 Nov 2014)
-6/ added calendar_normalize_tz function to convert region timezone to php supported timezone (7 Nov 2014)
-7/ MDL-49032: fixed rfc2445_fold() to fix incorrect RFC2445_WSP definition (16 Sep 2015)
-8/ added timestamp_to_date function to support zero duration events (16 Sept 2015)
+6/ MDL-49032: fixed rfc2445_fold() to fix incorrect RFC2445_WSP definition (16 Sep 2015)
+7/ added timestamp_to_date function to support zero duration events (16 Sept 2015)
if ($bits[0] == 'tag' && !empty($this->page->subpage)) {
// better navbar for tag pages
$editpage->navbar->add(get_string('tags'), new moodle_url('/tag/'));
- $tag = tag_get('id', $this->page->subpage, '*');
+ $tag = core_tag_tag::get($this->page->subpage);
// tag search page doesn't have subpageid
if ($tag) {
- $editpage->navbar->add($tag->name, new moodle_url('/tag/index.php', array('id'=>$tag->id)));
+ $editpage->navbar->add($tag->get_display_name(), $tag->get_view_url());
}
}
$editpage->navbar->add($block->get_title());
s($this->other['itemtype']) . "' with id '{$this->other['itemid']}'.";
}
+ /**
+ * Creates an event from taginstance object
+ *
+ * @since Moodle 3.1
+ * @param stdClass $taginstance
+ * @param string $tagname
+ * @param string $tagrawname
+ * @param bool $addsnapshot trust that $taginstance has all necessary fields and add it as a record snapshot
+ * @return tag_added
+ */
+ public static function create_from_tag_instance($taginstance, $tagname, $tagrawname, $addsnapshot = false) {
+ $event = self::create(array(
+ 'objectid' => $taginstance->id,
+ 'contextid' => $taginstance->contextid,
+ 'other' => array(
+ 'tagid' => $taginstance->tagid,
+ 'tagname' => $tagname,
+ 'tagrawname' => $tagrawname,
+ 'itemid' => $taginstance->itemid,
+ 'itemtype' => $taginstance->itemtype
+ )
+ ));
+ if ($addsnapshot) {
+ $event->add_record_snapshot('tag_instance', $taginstance);
+ }
+ return $event;
+ }
+
/**
* Return legacy data for add_to_log().
*
--- /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/>.
+
+/**
+ * Tag collection created event.
+ *
+ * @package core
+ * @copyright 2015 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Tag collection created event class.
+ *
+ * @package core
+ * @since Moodle 3.0
+ * @copyright 2015 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class tag_collection_created extends base {
+
+ /**
+ * Init method.
+ *
+ * @return void
+ */
+ protected function init() {
+ $this->data['objecttable'] = 'tag_coll';
+ $this->data['crud'] = 'c';
+ $this->data['edulevel'] = self::LEVEL_OTHER;
+ }
+
+ /**
+ * Utility method to create new event.
+ *
+ * @param object $tagcoll
+ * @return user_graded
+ */
+ public static function create_from_record($tagcoll) {
+ $event = self::create(array(
+ 'objectid' => $tagcoll->id,
+ 'context' => \context_system::instance(),
+ ));
+ $event->add_record_snapshot('tag_coll', $tagcoll);
+ return $event;
+ }
+
+ /**
+ * Return localised event name.
+ *
+ * @return string
+ */
+ public static function get_name() {
+ return get_string('eventtagcollcreated', 'core_tag');
+ }
+
+ /**
+ * Returns description of what happened.
+ *
+ * @return string
+ */
+ public function get_description() {
+ return "The user with id '$this->userid' created the tag collection with id '$this->objectid'";
+ }
+}
--- /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/>.
+
+/**
+ * Tag collection deleted event.
+ *
+ * @package core
+ * @copyright 2015 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Tag collection deleted event class.
+ *
+ * @package core
+ * @since Moodle 3.0
+ * @copyright 2015 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class tag_collection_deleted extends base {
+
+ /**
+ * Init method.
+ *
+ * @return void
+ */
+ protected function init() {
+ $this->data['objecttable'] = 'tag_coll';
+ $this->data['crud'] = 'd';
+ $this->data['edulevel'] = self::LEVEL_OTHER;
+ }
+
+ /**
+ * Utility method to create new event.
+ *
+ * @param object $tagcoll
+ * @return user_graded
+ */
+ public static function create_from_record($tagcoll) {
+ $event = self::create(array(
+ 'objectid' => $tagcoll->id,
+ 'context' => \context_system::instance(),
+ ));
+ $event->add_record_snapshot('tag_coll', $tagcoll);
+ return $event;
+ }
+
+ /**
+ * Return localised event name.
+ *
+ * @return string
+ */
+ public static function get_name() {
+ return get_string('eventtagcolldeleted', 'core_tag');
+ }
+
+ /**
+ * Returns description of what happened.
+ *
+ * @return string
+ */
+ public function get_description() {
+ return "The user with id '$this->userid' deleted the tag collection with id '$this->objectid'";
+ }
+}
--- /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/>.
+
+/**
+ * Tag collection updated event.
+ *
+ * @package core
+ * @copyright 2015 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Tag collection updated event class.
+ *
+ * @package core
+ * @since Moodle 3.0
+ * @copyright 2015 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class tag_collection_updated extends base {
+
+ /**
+ * Init method.
+ *
+ * @return void
+ */
+ protected function init() {
+ $this->data['objecttable'] = 'tag_coll';
+ $this->data['crud'] = 'u';
+ $this->data['edulevel'] = self::LEVEL_OTHER;
+ }
+
+ /**
+ * Utility method to create new event.
+ *
+ * @param object $tagcoll
+ * @return user_graded
+ */
+ public static function create_from_record($tagcoll) {
+ $event = self::create(array(
+ 'objectid' => $tagcoll->id,
+ 'context' => \context_system::instance(),
+ ));
+ $event->add_record_snapshot('tag_coll', $tagcoll);
+ return $event;
+ }
+
+ /**
+ * Return localised event name.
+ *
+ * @return string
+ */
+ public static function get_name() {
+ return get_string('eventtagcollupdated', 'core_tag');
+ }
+
+ /**
+ * Returns description of what happened.
+ *
+ * @return string
+ */
+ public function get_description() {
+ return "The user with id '$this->userid' updated the tag collection with id '$this->objectid'";
+ }
+}
$this->data['edulevel'] = self::LEVEL_OTHER;
}
+ /**
+ * Creates an event from tag object
+ *
+ * @since Moodle 3.1
+ * @param \core_tag_tag|\stdClass $tag
+ * @return tag_created
+ */
+ public static function create_from_tag($tag) {
+ $event = self::create(array(
+ 'objectid' => $tag->id,
+ 'relateduserid' => $tag->userid,
+ 'context' => \context_system::instance(),
+ 'other' => array(
+ 'name' => $tag->name,
+ 'rawname' => $tag->rawname
+ )
+ ));
+ return $event;
+ }
+
/**
* Returns localised general event name.
*
s($this->other['itemtype']) . "' with id '{$this->other['itemid']}'.";
}
+ /**
+ * Creates an event from taginstance object
+ *
+ * @since Moodle 3.1
+ * @param stdClass $taginstance
+ * @param string $tagname
+ * @param string $tagrawname
+ * @param bool $addsnapshot trust that $taginstance has all necessary fields and add it as a record snapshot
+ * @return tag_removed
+ */
+ public static function create_from_tag_instance($taginstance, $tagname, $tagrawname, $addsnapshot = false) {
+ $event = self::create(array(
+ 'objectid' => $taginstance->id,
+ 'contextid' => $taginstance->contextid,
+ 'other' => array(
+ 'tagid' => $taginstance->tagid,
+ 'tagname' => $tagname,
+ 'tagrawname' => $tagrawname,
+ 'itemid' => $taginstance->itemid,
+ 'itemtype' => $taginstance->itemtype
+ )
+ ));
+ if ($addsnapshot) {
+ $event->add_record_snapshot('tag_instance', $taginstance);
+ }
+ return $event;
+ }
+
/**
* Custom validation.
*
'theme' => array('mymobile', 'afterburner', 'anomaly', 'arialist', 'binarius', 'boxxie', 'brick', 'formal_white',
'formfactor', 'fusion', 'leatherbound', 'magazine', 'nimble', 'nonzero', 'overlay', 'serenity', 'sky_high',
'splash', 'standard', 'standardold'),
+ 'webservice' => array('amf'),
);
if (!isset($plugins[$type])) {
),
'webservice' => array(
- 'amf', 'rest', 'soap', 'xmlrpc'
+ 'rest', 'soap', 'xmlrpc'
),
'workshopallocation' => array(
defined('MOODLE_INTERNAL') || die();
/**
- * Class for admin tool plugins
+ * Class for cache store plugins
*/
class cachestore extends base {
public function is_uninstall_allowed() {
- return false;
+ $instance = \cache_config::instance();
+ foreach ($instance->get_all_stores() as $store) {
+ if ($store['plugin'] == $this->name) {
+ return false;
+ }
+ }
+ return true;
}
}
$langdirs = get_list_of_plugins('', 'en', $this->otherroot);
$langdirs["$CFG->dirroot/lang/en"] = 'en';
+ // We use left to right mark to demark the shortcodes contained in LTR brackets, but we need to do
+ // this hacky thing to have the utf8 char until we go php7 minimum and can simply put \u200E in
+ // a double quoted string.
+ $lrm = json_decode('"\u200E"');
+
// Loop through all langs and get info.
foreach ($langdirs as $lang) {
if (strrpos($lang, '_local') !== false) {
}
$string = $this->load_component_strings('langconfig', $lang);
if (!empty($string['thislanguage'])) {
- $languages[$lang] = $string['thislanguage'].' ('. $lang .')';
+ $languages[$lang] = $string['thislanguage'].' '.$lrm.'('. $lang .')'.$lrm;
}
}
*/
namespace core\task;
+use core_tag_collection, core_tag_tag, core_tag_area, stdClass;
+
/**
* Simple task to run the tag cron.
*/
global $CFG;
if (!empty($CFG->usetags)) {
- require_once($CFG->dirroot.'/tag/lib.php');
- tag_cron();
+ $this->compute_correlations();
+ $this->cleanup();
+ }
+ }
+
+ /**
+ * Calculates and stores the correlated tags of all tags.
+ *
+ * The correlations are stored in the 'tag_correlation' table.
+ *
+ * Two tags are correlated if they appear together a lot. Ex.: Users tagged with "computers"
+ * will probably also be tagged with "algorithms".
+ *
+ * The rationale for the 'tag_correlation' table is performance. It works as a cache
+ * for a potentially heavy load query done at the 'tag_instance' table. So, the
+ * 'tag_correlation' table stores redundant information derived from the 'tag_instance' table.
+ *
+ * @param int $mincorrelation Only tags with more than $mincorrelation correlations will be identified.
+ */
+ public function compute_correlations($mincorrelation = 2) {
+ global $DB;
+
+ // This mighty one line query fetches a row from the database for every
+ // individual tag correlation. We then need to process the rows collecting
+ // the correlations for each tag id.
+ // The fields used by this query are as follows:
+ // tagid : This is the tag id, there should be at least $mincorrelation
+ // rows for each tag id.
+ // correlation : This is the tag id that correlates to the above tagid field.
+ // correlationid : This is the id of the row in the tag_correlation table that
+ // relates to the tagid field and will be NULL if there are no
+ // existing correlations.
+ $sql = 'SELECT pairs.tagid, pairs.correlation, pairs.ocurrences, co.id AS correlationid
+ FROM (
+ SELECT ta.tagid, tb.tagid AS correlation, COUNT(*) AS ocurrences
+ FROM {tag_instance} ta
+ JOIN {tag} tga ON ta.tagid = tga.id
+ JOIN {tag_instance} tb ON (ta.itemtype = tb.itemtype AND ta.component = tb.component
+ AND ta.itemid = tb.itemid AND ta.tagid <> tb.tagid)
+ JOIN {tag} tgb ON tb.tagid = tgb.id AND tgb.tagcollid = tga.tagcollid
+ GROUP BY ta.tagid, tb.tagid
+ HAVING COUNT(*) > :mincorrelation
+ ) pairs
+ LEFT JOIN {tag_correlation} co ON co.tagid = pairs.tagid
+ ORDER BY pairs.tagid ASC, pairs.ocurrences DESC, pairs.correlation ASC';
+ $rs = $DB->get_recordset_sql($sql, array('mincorrelation' => $mincorrelation));
+
+ // Set up an empty tag correlation object.
+ $tagcorrelation = new stdClass;
+ $tagcorrelation->id = null;
+ $tagcorrelation->tagid = null;
+ $tagcorrelation->correlatedtags = array();
+
+ // We store each correlation id in this array so we can remove any correlations
+ // that no longer exist.
+ $correlations = array();
+
+ // Iterate each row of the result set and build them into tag correlations.
+ // We add all of a tag's correlations to $tagcorrelation->correlatedtags[]
+ // then save the $tagcorrelation object.
+ foreach ($rs as $row) {
+ if ($row->tagid != $tagcorrelation->tagid) {
+ // The tag id has changed so we have all of the correlations for this tag.
+ $tagcorrelationid = $this->process_computed_correlation($tagcorrelation);
+ if ($tagcorrelationid) {
+ $correlations[] = $tagcorrelationid;
+ }
+ // Now we reset the tag correlation object so we can reuse it and set it
+ // up for the current record.
+ $tagcorrelation = new stdClass;
+ $tagcorrelation->id = $row->correlationid;
+ $tagcorrelation->tagid = $row->tagid;
+ $tagcorrelation->correlatedtags = array();
+ }
+ // Save the correlation on the tag correlation object.
+ $tagcorrelation->correlatedtags[] = $row->correlation;
+ }
+ // Update the current correlation after the last record.
+ $tagcorrelationid = $this->process_computed_correlation($tagcorrelation);
+ if ($tagcorrelationid) {
+ $correlations[] = $tagcorrelationid;
+ }
+
+ // Close the recordset.
+ $rs->close();
+
+ // Remove any correlations that weren't just identified.
+ if (empty($correlations)) {
+ // There are no tag correlations.
+ $DB->delete_records('tag_correlation');
+ } else {
+ list($sql, $params) = $DB->get_in_or_equal($correlations,
+ SQL_PARAMS_NAMED, 'param0000', false);
+ $DB->delete_records_select('tag_correlation', 'id '.$sql, $params);
+ }
+ }
+
+ /**
+ * Clean up the tag tables, making sure all tagged object still exists.
+ *
+ * This method is called from cron.
+ *
+ * This should normally not be necessary, but in case related tags are not deleted
+ * when the tagged record is removed, this should be done once in a while, perhaps
+ * on an occasional cron run. On a site with lots of tags, this could become an
+ * expensive function to call.
+ */
+ public function cleanup() {
+ global $DB;
+
+ // Get ids to delete from instances where the tag has been deleted. This should never happen apparently.
+ $sql = "SELECT ti.id
+ FROM {tag_instance} ti
+ LEFT JOIN {tag} t ON t.id = ti.tagid
+ WHERE t.id IS null";
+ $tagids = $DB->get_records_sql($sql);
+ $tagarray = array();
+ foreach ($tagids as $tagid) {
+ $tagarray[] = $tagid->id;
}
+
+ // Next get ids from instances that have an owner that has been deleted.
+ $sql = "SELECT ti.id
+ FROM {tag_instance} ti, {user} u
+ WHERE ti.itemid = u.id
+ AND ti.itemtype = 'user'
+ AND ti.component = 'core'
+ AND u.deleted = 1";
+ $tagids = $DB->get_records_sql($sql);
+ foreach ($tagids as $tagid) {
+ $tagarray[] = $tagid->id;
+ }
+
+ // Get the other itemtypes.
+ $sql = "SELECT DISTINCT component, itemtype
+ FROM {tag_instance}
+ WHERE itemtype <> 'user' or component <> 'core'";
+ $tagareas = $DB->get_records_sql($sql);
+ foreach ($tagareas as $tagarea) {
+ $sql = 'SELECT ti.id
+ FROM {tag_instance} ti
+ LEFT JOIN {' . $tagarea->itemtype . '} it ON it.id = ti.itemid
+ WHERE it.id IS null
+ AND ti.itemtype = ? AND ti.component = ?';
+ $tagids = $DB->get_records_sql($sql, array($tagarea->itemtype, $tagarea->component));
+ foreach ($tagids as $tagid) {
+ $tagarray[] = $tagid->id;
+ }
+ }
+
+ // Get instances for each of the ids to be deleted.
+ if (count($tagarray) > 0) {
+ list($sqlin, $params) = $DB->get_in_or_equal($tagarray);
+ $sql = "SELECT ti.*, COALESCE(t.name, 'deleted') AS name, COALESCE(t.rawname, 'deleted') AS rawname
+ FROM {tag_instance} ti
+ LEFT JOIN {tag} t ON t.id = ti.tagid
+ WHERE ti.id $sqlin";
+ $instances = $DB->get_records_sql($sql, $params);
+ $this->bulk_delete_instances($instances);
+ }
+
+ core_tag_collection::cleanup_unused_tags();
+ }
+
+ /**
+ * This function processes a tag correlation and makes changes in the database as required.
+ *
+ * The tag correlation object needs have both a tagid property and a correlatedtags property that is an array.
+ *
+ * @param stdClass $tagcorrelation
+ * @return int/bool The id of the tag correlation that was just processed or false.
+ */
+ public function process_computed_correlation(stdClass $tagcorrelation) {
+ global $DB;
+
+ // You must provide a tagid and correlatedtags must be set and be an array.
+ if (empty($tagcorrelation->tagid) || !isset($tagcorrelation->correlatedtags) ||
+ !is_array($tagcorrelation->correlatedtags)) {
+ return false;
+ }
+
+ $tagcorrelation->correlatedtags = join(',', $tagcorrelation->correlatedtags);
+ if (!empty($tagcorrelation->id)) {
+ // The tag correlation already exists so update it.
+ $DB->update_record('tag_correlation', $tagcorrelation);
+ } else {
+ // This is a new correlation to insert.
+ $tagcorrelation->id = $DB->insert_record('tag_correlation', $tagcorrelation);
+ }
+ return $tagcorrelation->id;
}
+ /**
+ * This function will delete numerous tag instances efficiently.
+ * This removes tag instances only. It doesn't check to see if it is the last use of a tag.
+ *
+ * @param array $instances An array of tag instance objects with the addition of the tagname and tagrawname
+ * (used for recording a delete event).
+ */
+ public function bulk_delete_instances($instances) {
+ global $DB;
+
+ $instanceids = array();
+ foreach ($instances as $instance) {
+ $instanceids[] = $instance->id;
+ }
+
+ // This is a multi db compatible method of creating the correct sql when using the 'IN' value.
+ // $insql is the sql statement, $params are the id numbers.
+ list($insql, $params) = $DB->get_in_or_equal($instanceids);
+ $sql = 'id ' . $insql;
+ $DB->delete_records_select('tag_instance', $sql, $params);
+
+ // Now go through and record each tag individually with the event system.
+ foreach ($instances as $instance) {
+ // Trigger tag removed event (i.e. The tag instance has been removed).
+ \core\event\tag_removed::create_from_tag_instance($instance, $instance->name,
+ $instance->rawname, true)->trigger();
+ }
+ }
}
} else if (!empty($search['tagid'])) {
// Search courses that are tagged with the specified tag.
$where = "c.id IN (SELECT t.itemid ".
- "FROM {tag_instance} t WHERE t.tagid = :tagid AND t.itemtype = :itemtype)";
- $params = array('tagid' => $search['tagid'], 'itemtype' => 'course');
+ "FROM {tag_instance} t WHERE t.tagid = :tagid AND t.itemtype = :itemtype AND t.component = :component)";
+ $params = array('tagid' => $search['tagid'], 'itemtype' => 'course', 'component' => 'core');
+ if (!empty($search['ctx'])) {
+ $rec = isset($search['rec']) ? $search['rec'] : true;
+ $parentcontext = context::instance_by_id($search['ctx']);
+ if ($parentcontext->contextlevel == CONTEXT_SYSTEM && $rec) {
+ // Parent context is system context and recursive is set to yes.
+ // Nothing to filter - all courses fall into this condition.
+ } else if ($rec) {
+ // Filter all courses in the parent context at any level.
+ $where .= ' AND ctx.path LIKE :contextpath';
+ $params['contextpath'] = $parentcontext->path . '%';
+ } else if ($parentcontext->contextlevel == CONTEXT_COURSECAT) {
+ // All courses in the given course category.
+ $where .= ' AND c.category = :category';
+ $params['category'] = $parentcontext->instanceid;
+ } else {
+ // No courses will satisfy the context criterion, do not bother searching.
+ $where = '1=0';
+ }
+ }
} else {
debugging('No criteria is specified while searching courses', DEBUG_DEVELOPER);
return array();
'simpledata' => true,
'staticacceleration' => true,
'staticaccelerationsize' => 5
+ ),
+
+ // Caches data about tag collections and areas.
+ 'tags' => array(
+ 'mode' => cache_store::MODE_REQUEST,
+ 'simplekeys' => true,
)
);
<?xml version="1.0" encoding="UTF-8" ?>
-<XMLDB PATH="lib/db" VERSION="20150922" COMMENT="XMLDB file for core Moodle tables"
+<XMLDB PATH="lib/db" VERSION="20160111" COMMENT="XMLDB file for core Moodle tables"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../lib/xmldb/xmldb.xsd"
>
<KEY NAME="importer" TYPE="foreign" FIELDS="importer" REFTABLE="user" REFFIELDS="id" COMMENT="user who is importing"/>
</KEYS>
</TABLE>
+ <TABLE NAME="tag_coll" COMMENT="Defines different set of tags">
+ <FIELDS>
+ <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
+ <FIELD NAME="name" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false"/>
+ <FIELD NAME="isdefault" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
+ <FIELD NAME="component" TYPE="char" LENGTH="100" NOTNULL="false" SEQUENCE="false"/>
+ <FIELD NAME="sortorder" TYPE="int" LENGTH="5" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
+ <FIELD NAME="searchable" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="1" SEQUENCE="false" COMMENT="Whether the tag collection is searchable"/>
+ <FIELD NAME="customurl" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false" COMMENT="Custom URL for the tag page instead of /tag/index.php"/>
+ </FIELDS>
+ <KEYS>
+ <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
+ </KEYS>
+ </TABLE>
+ <TABLE NAME="tag_area" COMMENT="Defines various tag areas, one area is identified by component and itemtype">
+ <FIELDS>
+ <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
+ <FIELD NAME="component" TYPE="char" LENGTH="100" NOTNULL="true" SEQUENCE="false"/>
+ <FIELD NAME="itemtype" TYPE="char" LENGTH="100" NOTNULL="true" SEQUENCE="false"/>
+ <FIELD NAME="enabled" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="1" SEQUENCE="false"/>
+ <FIELD NAME="tagcollid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
+ <FIELD NAME="callback" TYPE="char" LENGTH="100" NOTNULL="false" SEQUENCE="false"/>
+ <FIELD NAME="callbackfile" TYPE="char" LENGTH="100" NOTNULL="false" SEQUENCE="false"/>
+ </FIELDS>
+ <KEYS>
+ <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
+ <KEY NAME="tagcollid" TYPE="foreign" FIELDS="tagcollid" REFTABLE="tag_coll" REFFIELDS="id"/>
+ </KEYS>
+ <INDEXES>
+ <INDEX NAME="compitemtype" UNIQUE="true" FIELDS="component, itemtype"/>
+ </INDEXES>
+ </TABLE>
<TABLE NAME="tag" COMMENT="Tag table - this generic table will replace the old "tags" table.">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
+ <FIELD NAME="tagcollid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="name" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="rawname" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" COMMENT="The raw, unnormalised name for the tag as entered by users"/>
<FIELD NAME="tagtype" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false"/>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="userid" TYPE="foreign" FIELDS="userid" REFTABLE="user" REFFIELDS="id"/>
+ <KEY NAME="tagcollid" TYPE="foreign" FIELDS="tagcollid" REFTABLE="tag_coll" REFFIELDS="id"/>
</KEYS>
<INDEXES>
- <INDEX NAME="name" UNIQUE="true" FIELDS="name" COMMENT="tag names are unique"/>
+ <INDEX NAME="tagcollname" UNIQUE="true" FIELDS="tagcollid, name"/>
+ <INDEX NAME="tagcolltype" UNIQUE="false" FIELDS="tagcollid, tagtype"/>
</INDEXES>
</TABLE>
<TABLE NAME="tag_correlation" COMMENT="The rationale for the 'tag_correlation' table is performance. It works as a cache for a potentially heavy load query done at the 'tag_instance' table. So, the 'tag_correlation' table stores redundant information derived from the 'tag_instance' table">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="tagid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
- <FIELD NAME="component" TYPE="char" LENGTH="100" NOTNULL="false" SEQUENCE="false" COMMENT="Defines the Moodle component which the tag was added to"/>
- <FIELD NAME="itemtype" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
+ <FIELD NAME="component" TYPE="char" LENGTH="100" NOTNULL="true" SEQUENCE="false" COMMENT="Defines the Moodle component which the tag was added to"/>
+ <FIELD NAME="itemtype" TYPE="char" LENGTH="100" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="itemid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="contextid" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="The context id of the item that was tagged"/>
<FIELD NAME="tiuserid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<KEY NAME="contextid" TYPE="foreign" FIELDS="contextid" REFTABLE="context" REFFIELDS="id"/>
</KEYS>
<INDEXES>
- <INDEX NAME="itemtype-itemid-tagid-tiuserid" UNIQUE="true" FIELDS="itemtype, itemid, tagid, tiuserid"/>
+ <INDEX NAME="taggeditem" UNIQUE="true" FIELDS="component, itemtype, itemid, tiuserid, tagid"/>
+ <INDEX NAME="taglookup" UNIQUE="false" FIELDS="itemtype, component, tagid, contextid"/>
</INDEXES>
</TABLE>
<TABLE NAME="groups" COMMENT="Each record represents a group.">
'ajax' => true
),
+ 'core_tag_get_tagindex' => array(
+ 'classname' => 'core_tag_external',
+ 'methodname' => 'get_tagindex',
+ 'description' => 'Gets tag index page for one tag and one tag area',
+ 'type' => 'read',
+ 'ajax' => true
+ ),
+
);
$services = array(
--- /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/>.
+
+/**
+ * Tag area definitions
+ *
+ * File db/tag.php lists all available tag areas in core or a plugin.
+ *
+ * Each tag area may have the following attributes:
+ * - itemtype (required) - what is tagged. Must be name of the existing DB table
+ * - component - component responsible for tagging, if the tag area is inside a
+ * plugin the component must be the full frankenstyle name of the plugin
+ * - collection - name of the custom tag collection that will be used to store
+ * tags in this area. If specified aministrator will be able to neither add
+ * any other tag areas to this collection nor move this tag area elsewhere
+ * - searchable (only if collection is specified) - wether the tag collection
+ * should be searchable on /tag/search.php
+ * - customurl (only if collection is specified) - custom url to use instead of
+ * /tag/search.php to display information about one tag
+ * - callback - name of the function that returns items tagged with this tag,
+ * see core_tag_tag::get_tag_index() and existing callbacks for more details,
+ * callback should return instance of core_tag\output\tagindex
+ * - callbackfile - file where callback is located (if not an autoloaded location)
+ *
+ * Language file must contain the human-readable names of the tag areas and
+ * collections (either in plugin language file or in component language file or
+ * lang/en/tag.php in case of core):
+ * - for item type "user":
+ * $string['tagarea_user'] = 'Users';
+ * - for tag collection "mycollection":
+ * $string['tagcollection_mycollection'] = 'My tag collection';
+ *
+ * @package core
+ * @copyright 2015 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$tagareas = array(
+ array(
+ 'itemtype' => 'user', // Users.
+ 'component' => 'core',
+ 'callback' => 'user_get_tagged_users',
+ 'callbackfile' => '/user/lib.php',
+ ),
+ array(
+ 'itemtype' => 'course', // Courses.
+ 'component' => 'core',
+ 'callback' => 'course_get_tagged_courses',
+ 'callbackfile' => '/course/lib.php',
+ ),
+ array(
+ 'itemtype' => 'question', // Questions.
+ 'component' => 'core_question',
+ ),
+ array(
+ 'itemtype' => 'post', // Blog posts.
+ 'component' => 'core',
+ 'callback' => 'blog_get_tagged_posts',
+ 'callbackfile' => '/blog/lib.php',
+ ),
+ array(
+ 'itemtype' => 'blog_external', // External blogs.
+ 'component' => 'core',
+ ),
+);
array(
'classname' => 'core\task\tag_cron_task',
'blocking' => 0,
- 'minute' => '20',
- 'hour' => '*',
+ 'minute' => 'R',
+ 'hour' => '3',
'day' => '*',
'dayofweek' => '*',
'month' => '*'
// Moodle v3.0.0 release upgrade line.
// Put any upgrade step following this.
+ if ($oldversion < 2016011300.01) {
+
+ // This is a big upgrade script. We create new table tag_coll and the field
+ // tag.tagcollid pointing to it.
+
+ // Define table tag_coll to be created.
+ $table = new xmldb_table('tag_coll');
+
+ // Adding fields to table tagcloud.
+ $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
+ $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, null);
+ $table->add_field('isdefault', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '0');
+ $table->add_field('component', XMLDB_TYPE_CHAR, '100', null, null, null, null);
+ $table->add_field('sortorder', XMLDB_TYPE_INTEGER, '5', null, XMLDB_NOTNULL, null, '0');
+ $table->add_field('searchable', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '1');
+ $table->add_field('customurl', XMLDB_TYPE_CHAR, '255', null, null, null, null);
+
+ // Adding keys to table tagcloud.
+ $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
+
+ // Conditionally launch create table for tagcloud.
+ if (!$dbman->table_exists($table)) {
+ $dbman->create_table($table);
+ }
+
+ // Table {tag}.
+ // Define index name (unique) to be dropped form tag - we will replace it with index on (tagcollid,name) later.
+ $table = new xmldb_table('tag');
+ $index = new xmldb_index('name', XMLDB_INDEX_UNIQUE, array('name'));
+
+ // Conditionally launch drop index name.
+ if ($dbman->index_exists($table, $index)) {
+ $dbman->drop_index($table, $index);
+ }
+
+ // Define field tagcollid to be added to tag, we create it as null first and will change to notnull later.
+ $table = new xmldb_table('tag');
+ $field = new xmldb_field('tagcollid', XMLDB_TYPE_INTEGER, '10', null, null, null, null, 'userid');
+
+ // Conditionally launch add field tagcloudid.
+ if (!$dbman->field_exists($table, $field)) {
+ $dbman->add_field($table, $field);
+ }
+
+ // Main savepoint reached.
+ upgrade_main_savepoint(true, 2016011300.01);
+ }
+
+ if ($oldversion < 2016011300.02) {
+ // Create a default tag collection if not exists and update the field tag.tagcollid to point to it.
+ if (!$tcid = $DB->get_field_sql('SELECT id FROM {tag_coll} ORDER BY isdefault DESC, sortorder, id', null,
+ IGNORE_MULTIPLE)) {
+ $tcid = $DB->insert_record('tag_coll', array('isdefault' => 1, 'sortorder' => 0));
+ }
+ $DB->execute('UPDATE {tag} SET tagcollid = ? WHERE tagcollid IS NULL', array($tcid));
+
+ // Define index tagcollname (unique) to be added to tag.
+ $table = new xmldb_table('tag');
+ $index = new xmldb_index('tagcollname', XMLDB_INDEX_UNIQUE, array('tagcollid', 'name'));
+ $field = new xmldb_field('tagcollid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null, 'userid');
+
+ // Conditionally launch add index tagcollname.
+ if (!$dbman->index_exists($table, $index)) {
+ // Launch change of nullability for field tagcollid.
+ $dbman->change_field_notnull($table, $field);
+ $dbman->add_index($table, $index);
+ }
+
+ // Define key tagcollid (foreign) to be added to tag.
+ $table = new xmldb_table('tag');
+ $key = new xmldb_key('tagcollid', XMLDB_KEY_FOREIGN, array('tagcollid'), 'tag_coll', array('id'));
+
+ // Launch add key tagcloudid.
+ $dbman->add_key($table, $key);
+
+ // Define index tagcolltype (not unique) to be added to tag.
+ $table = new xmldb_table('tag');
+ $index = new xmldb_index('tagcolltype', XMLDB_INDEX_NOTUNIQUE, array('tagcollid', 'tagtype'));
+
+ // Conditionally launch add index tagcolltype.
+ if (!$dbman->index_exists($table, $index)) {
+ $dbman->add_index($table, $index);
+ }
+
+ // Main savepoint reached.
+ upgrade_main_savepoint(true, 2016011300.02);
+ }
+
+ if ($oldversion < 2016011300.03) {
+
+ // Define table tag_area to be created.
+ $table = new xmldb_table('tag_area');
+
+ // Adding fields to table tag_area.
+ $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
+ $table->add_field('component', XMLDB_TYPE_CHAR, '100', null, XMLDB_NOTNULL, null, null);
+ $table->add_field('itemtype', XMLDB_TYPE_CHAR, '100', null, XMLDB_NOTNULL, null, null);
+ $table->add_field('enabled', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '1');
+ $table->add_field('tagcollid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+ $table->add_field('callback', XMLDB_TYPE_CHAR, '100', null, null, null, null);
+ $table->add_field('callbackfile', XMLDB_TYPE_CHAR, '100', null, null, null, null);
+
+ // Adding keys to table tag_area.
+ $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
+ $table->add_key('tagcollid', XMLDB_KEY_FOREIGN, array('tagcollid'), 'tag_coll', array('id'));
+
+ // Adding indexes to table tag_area.
+ $table->add_index('compitemtype', XMLDB_INDEX_UNIQUE, array('component', 'itemtype'));
+
+ // Conditionally launch create table for tag_area.
+ if (!$dbman->table_exists($table)) {
+ $dbman->create_table($table);
+ }
+
+ // Main savepoint reached.
+ upgrade_main_savepoint(true, 2016011300.03);
+ }
+
+ if ($oldversion < 2016011300.04) {
+
+ // Define index itemtype-itemid-tagid-tiuserid (unique) to be dropped form tag_instance.
+ $table = new xmldb_table('tag_instance');
+ $index = new xmldb_index('itemtype-itemid-tagid-tiuserid', XMLDB_INDEX_UNIQUE,
+ array('itemtype', 'itemid', 'tagid', 'tiuserid'));
+
+ // Conditionally launch drop index itemtype-itemid-tagid-tiuserid.
+ if ($dbman->index_exists($table, $index)) {
+ $dbman->drop_index($table, $index);
+ }
+
+ // Main savepoint reached.
+ upgrade_main_savepoint(true, 2016011300.04);
+ }
+
+ if ($oldversion < 2016011300.05) {
+
+ $DB->execute("UPDATE {tag_instance} SET component = ? WHERE component IS NULL", array(''));
+
+ // Changing nullability of field component on table tag_instance to not null.
+ $table = new xmldb_table('tag_instance');
+ $field = new xmldb_field('component', XMLDB_TYPE_CHAR, '100', null, XMLDB_NOTNULL, null, null, 'tagid');
+
+ // Launch change of nullability for field component.
+ $dbman->change_field_notnull($table, $field);
+
+ // Changing type of field itemtype on table tag_instance to char.
+ $table = new xmldb_table('tag_instance');
+ $field = new xmldb_field('itemtype', XMLDB_TYPE_CHAR, '100', null, XMLDB_NOTNULL, null, null, 'component');
+
+ // Launch change of type for field itemtype.
+ $dbman->change_field_type($table, $field);
+
+ // Main savepoint reached.
+ upgrade_main_savepoint(true, 2016011300.05);
+ }
+
+ if ($oldversion < 2016011300.06) {
+
+ // Define index taggeditem (unique) to be added to tag_instance.
+ $table = new xmldb_table('tag_instance');
+ $index = new xmldb_index('taggeditem', XMLDB_INDEX_UNIQUE, array('component', 'itemtype', 'itemid', 'tiuserid', 'tagid'));
+
+ // Conditionally launch add index taggeditem.
+ if (!$dbman->index_exists($table, $index)) {
+ $dbman->add_index($table, $index);
+ }
+
+ // Main savepoint reached.
+ upgrade_main_savepoint(true, 2016011300.06);
+ }
+
+ if ($oldversion < 2016011300.07) {
+
+ // Define index taglookup (not unique) to be added to tag_instance.
+ $table = new xmldb_table('tag_instance');
+ $index = new xmldb_index('taglookup', XMLDB_INDEX_NOTUNIQUE, array('itemtype', 'component', 'tagid', 'contextid'));
+
+ // Conditionally launch add index taglookup.
+ if (!$dbman->index_exists($table, $index)) {
+ $dbman->add_index($table, $index);
+ }
+
+ // Main savepoint reached.
+ upgrade_main_savepoint(true, 2016011300.07);
+ }
+
+ if ($oldversion < 2016011301.00) {
+
+ // Force uninstall of deleted tool.
+ if (!file_exists("$CFG->dirroot/webservice/amf")) {
+ // Remove capabilities.
+ capabilities_cleanup('webservice_amf');
+ // Remove all other associated config.
+ unset_all_config_for_plugin('webservice_amf');
+ }
+ upgrade_main_savepoint(true, 2016011301.00);
+ }
+
return true;
}
* errors and changes of column data types.
*
* @deprecated since Moodle 2.9 MDL-49723 - please do not use this function any more.
- * @param xmldb_field[]|database_column_info[] $columns
- * @return int approximate row size in bytes
*/
public function guess_antolope_row_size(array $columns) {
- debugging('guess_antolope_row_size() is deprecated, please use guess_antelope_row_size() instead.', DEBUG_DEVELOPER);
- return $this->guess_antelope_row_size($columns);
+ throw new coding_exception('guess_antolope_row_size() can not be used any more, please use guess_antelope_row_size() instead.');
}
/**
* $user2 will be null if viewing a user's recent conversations
*
* @deprecated since Moodle 2.9 MDL-49371 - please do not use this function any more.
- * @todo MDL-49290 This will be deleted in Moodle 3.1.
- * @param stdClass the first user
- * @param stdClass the second user or null
- * @return bool True if the current user is one of either $user1 or $user2
*/
function message_current_user_is_involved($user1, $user2) {
- global $USER;
-
- debugging('message_current_user_is_involved() is deprecated, please do not use this function.', DEBUG_DEVELOPER);
-
- if (empty($user1->id) || (!empty($user2) && empty($user2->id))) {
- throw new coding_exception('Invalid user object detected. Missing id.');
- }
-
- if ($user1->id != $USER->id && (empty($user2) || $user2->id != $USER->id)) {
- return false;
- }
- return true;
+ throw new coding_exception('message_current_user_is_involved() can not be used any more.');
}
/**
* Adds user preferences elements to user edit form.
*
* @deprecated since Moodle 2.9 MDL-45774 - Please do not use this function any more.
- * @todo MDL-49784 Remove this function in Moodle 3.1
- * @param stdClass $user
- * @param moodleform $mform
- * @param array|null $editoroptions
- * @param array|null $filemanageroptions
*/
function useredit_shared_definition_preferences($user, &$mform, $editoroptions = null, $filemanageroptions = null) {
- global $CFG;
-
- debugging('useredit_shared_definition_preferences() is deprecated.', DEBUG_DEVELOPER, backtrace);
-
- $choices = array();
- $choices['0'] = get_string('emaildisplayno');
- $choices['1'] = get_string('emaildisplayyes');
- $choices['2'] = get_string('emaildisplaycourse');
- $mform->addElement('select', 'maildisplay', get_string('emaildisplay'), $choices);
- $mform->setDefault('maildisplay', $CFG->defaultpreference_maildisplay);
-
- $choices = array();
- $choices['0'] = get_string('textformat');
- $choices['1'] = get_string('htmlformat');
- $mform->addElement('select', 'mailformat', get_string('emailformat'), $choices);
- $mform->setDefault('mailformat', $CFG->defaultpreference_mailformat);
-
- if (!empty($CFG->allowusermailcharset)) {
- $choices = array();
- $charsets = get_list_of_charsets();
- if (!empty($CFG->sitemailcharset)) {
- $choices['0'] = get_string('site').' ('.$CFG->sitemailcharset.')';
- } else {
- $choices['0'] = get_string('site').' (UTF-8)';
- }
- $choices = array_merge($choices, $charsets);
- $mform->addElement('select', 'preference_mailcharset', get_string('emailcharset'), $choices);
- }
-
- $choices = array();
- $choices['0'] = get_string('emaildigestoff');
- $choices['1'] = get_string('emaildigestcomplete');
- $choices['2'] = get_string('emaildigestsubjects');
- $mform->addElement('select', 'maildigest', get_string('emaildigest'), $choices);
- $mform->setDefault('maildigest', $CFG->defaultpreference_maildigest);
- $mform->addHelpButton('maildigest', 'emaildigest');
-
- $choices = array();
- $choices['1'] = get_string('autosubscribeyes');
- $choices['0'] = get_string('autosubscribeno');
- $mform->addElement('select', 'autosubscribe', get_string('autosubscribe'), $choices);
- $mform->setDefault('autosubscribe', $CFG->defaultpreference_autosubscribe);
-
- if (!empty($CFG->forum_trackreadposts)) {
- $choices = array();
- $choices['0'] = get_string('trackforumsno');
- $choices['1'] = get_string('trackforumsyes');
- $mform->addElement('select', 'trackforums', get_string('trackforums'), $choices);
- $mform->setDefault('trackforums', $CFG->defaultpreference_trackforums);
- }
-
- $editors = editors_get_enabled();
- if (count($editors) > 1) {
- $choices = array('' => get_string('defaulteditor'));
- $firsteditor = '';
- foreach (array_keys($editors) as $editor) {
- if (!$firsteditor) {
- $firsteditor = $editor;
- }
- $choices[$editor] = get_string('pluginname', 'editor_' . $editor);
- }
- $mform->addElement('select', 'preference_htmleditor', get_string('textediting'), $choices);
- $mform->setDefault('preference_htmleditor', '');
- } else {
- // Empty string means use the first chosen text editor.
- $mform->addElement('hidden', 'preference_htmleditor');
- $mform->setDefault('preference_htmleditor', '');
- $mform->setType('preference_htmleditor', PARAM_PLUGIN);
- }
-
- $mform->addElement('select', 'lang', get_string('preferredlanguage'), get_string_manager()->get_list_of_translations());
- $mform->setDefault('lang', $CFG->lang);
-
+ throw new coding_exception('useredit_shared_definition_preferences() can not be used any more.');
}
* Convert region timezone to php supported timezone
*
* @deprecated since Moodle 2.9
- * @param string $tz value from ical file
- * @return string $tz php supported timezone
*/
function calendar_normalize_tz($tz) {
- debugging('calendar_normalize_tz() is deprecated, use core_date::normalise_timezone() instead', DEBUG_DEVELOPER);
- return core_date::normalise_timezone($tz);
+ throw new coding_exception('calendar_normalize_tz() can not be used any more, please use core_date::normalise_timezone() instead.');
}
/**
debugging('Function coursetag_store_keywords() is deprecated. Userid is no longer used for tagging courses.', DEBUG_DEVELOPER);
global $CFG;
- require_once $CFG->dirroot.'/tag/lib.php';
if (is_array($tags) and !empty($tags)) {
+ if ($tagtype === 'official') {
+ $tagcoll = core_tag_area::get_collection('core', 'course');
+ // We don't normally need to create tags, they are created automatically when added to items. but we do here because we want them to be official.
+ core_tag_tag::create_if_missing($tagcoll, $tags, true);
+ }
foreach ($tags as $tag) {
$tag = trim($tag);
if (strlen($tag) > 0) {
- //tag_set_add('course', $courseid, $tag, $userid); //deletes official tags
-
- //add tag if does not exist
- if (!$tagid = tag_get_id($tag)) {
- $tag_id_array = tag_add(array($tag), $tagtype);
- $tagid = $tag_id_array[core_text::strtolower($tag)];
- }
- //ordering
- $ordering = 0;
- if ($current_ids = tag_get_tags_ids('course', $courseid)) {
- end($current_ids);
- $ordering = key($current_ids) + 1;
- }
- //set type
- tag_type_set($tagid, $tagtype);
-
- //tag_instance entry
- tag_assign('course', $courseid, $tagid, $ordering, $userid, 'core', context_course::instance($courseid)->id);
+ core_tag_tag::add_item_tag('core', 'course', $courseid, context_course::instance($courseid), $tag, $userid);
}
}
}
function coursetag_delete_keyword($tagid, $userid, $courseid) {
debugging('Function coursetag_delete_keyword() is deprecated. Userid is no longer used for tagging courses.', DEBUG_DEVELOPER);
- tag_delete_instance('course', $courseid, $tagid, $userid);
+ $tag = core_tag_tag::get($tagid);
+ core_tag_tag::remove_item_tag('core', 'course', $courseid, $tag->rawname, $userid);
}
/**
* @param bool $showfeedback if we should output a notification of the delete to the end user
*/
function coursetag_delete_course_tags($courseid, $showfeedback=false) {
- debugging('Function coursetag_delete_course_tags() is deprecated. Userid is no longer used for tagging courses.', DEBUG_DEVELOPER);
+ debugging('Function coursetag_delete_course_tags() is deprecated. Use core_tag_tag::remove_all_item_tags().', DEBUG_DEVELOPER);
+
+ global $OUTPUT;
+ core_tag_tag::remove_all_item_tags('core', 'course', $courseid);
+
+ if ($showfeedback) {
+ echo $OUTPUT->notification(get_string('deletedcoursetags', 'tag'), 'notifysuccess');
+ }
+}
+
+/**
+ * Set the type of a tag. At this time (version 2.2) the possible values are 'default' or 'official'. Official tags will be
+ * displayed separately "at tagging time" (while selecting the tags to apply to a record).
+ *
+ * @package core_tag
+ * @deprecated since 3.1
+ * @param string $tagid tagid to modify
+ * @param string $type either 'default' or 'official'
+ * @return bool true on success, false otherwise
+ */
+function tag_type_set($tagid, $type) {
+ debugging('Function tag_type_set() is deprecated and can be replaced with use core_tag_tag::get($tagid)->update().', DEBUG_DEVELOPER);
+ if ($tag = core_tag_tag::get($tagid, '*')) {
+ return $tag->update(array('tagtype' => $type));
+ }
+ return false;
+}
- global $DB, $OUTPUT;
+/**
+ * Set the description of a tag
+ *
+ * @package core_tag
+ * @deprecated since 3.1
+ * @param int $tagid the id of the tag
+ * @param string $description the tag's description string to be set
+ * @param int $descriptionformat the moodle text format of the description
+ * {@link http://docs.moodle.org/dev/Text_formats_2.0#Database_structure}
+ * @return bool true on success, false otherwise
+ */
+function tag_description_set($tagid, $description, $descriptionformat) {
+ debugging('Function tag_type_set() is deprecated and can be replaced with core_tag_tag::get($tagid)->update().', DEBUG_DEVELOPER);
+ if ($tag = core_tag_tag::get($tagid, '*')) {
+ return $tag->update(array('description' => $description, 'descriptionformat' => $descriptionformat));
+ }
+ return false;
+}
+
+/**
+ * Get the array of db record of tags associated to a record (instances).
+ *
+ * @package core_tag
+ * @deprecated since 3.1
+ * @param string $record_type the record type for which we want to get the tags
+ * @param int $record_id the record id for which we want to get the tags
+ * @param string $type the tag type (either 'default' or 'official'). By default, all tags are returned.
+ * @param int $userid (optional) only required for course tagging
+ * @return array the array of tags
+ */
+function tag_get_tags($record_type, $record_id, $type=null, $userid=0) {
+ debugging('Method tag_get_tags() is deprecated and replaced with core_tag_tag::get_item_tags(). ' .
+ 'Component is now required when retrieving tag instances.', DEBUG_DEVELOPER);
+ $official = ($type === 'official' ? true : (!empty($type) ? false : null));
+ $tags = core_tag_tag::get_item_tags(null, $record_type, $record_id, $official, $userid);
+ $rv = array();
+ foreach ($tags as $id => $t) {
+ $rv[$id] = $t->to_object();
+ }
+ return $rv;
+}
+
+/**
+ * Get the array of tags display names, indexed by id.
+ *
+ * @package core_tag
+ * @deprecated since 3.1
+ * @param string $record_type the record type for which we want to get the tags
+ * @param int $record_id the record id for which we want to get the tags
+ * @param string $type the tag type (either 'default' or 'official'). By default, all tags are returned.
+ * @return array the array of tags (with the value returned by core_tag_tag::make_display_name), indexed by id
+ */
+function tag_get_tags_array($record_type, $record_id, $type=null) {
+ debugging('Method tag_get_tags_array() is deprecated and replaced with core_tag_tag::get_item_tags_array(). ' .
+ 'Component is now required when retrieving tag instances.', DEBUG_DEVELOPER);
+ $official = ($type === 'official' ? true : (!empty($type) ? false : null));
+ return core_tag_tag::get_item_tags_array('', $record_type, $record_id, $official);
+}
- if ($taginstances = $DB->get_recordset_select('tag_instance', "itemtype = 'course' AND itemid = :courseid",
- array('courseid' => $courseid), '', 'tagid, tiuserid')) {
+/**
+ * Get a comma-separated string of tags associated to a record.
+ *
+ * Use {@link tag_get_tags()} to get the same information in an array.
+ *
+ * @package core_tag
+ * @deprecated since 3.1
+ * @param string $record_type the record type for which we want to get the tags
+ * @param int $record_id the record id for which we want to get the tags
+ * @param int $html either TAG_RETURN_HTML or TAG_RETURN_TEXT, depending on the type of output desired
+ * @param string $type either 'official' or 'default', if null, all tags are returned
+ * @return string the comma-separated list of tags.
+ */
+function tag_get_tags_csv($record_type, $record_id, $html=null, $type=null) {
+ global $CFG, $OUTPUT;
+ debugging('Method tag_get_tags_csv() is deprecated. Instead you should use either ' .
+ 'core_tag_tag::get_item_tags_array() or $OUTPUT->tag_list(core_tag_tag::get_item_tags()). ' .
+ 'Component is now required when retrieving tag instances.', DEBUG_DEVELOPER);
+ $official = ($type === 'official' ? true : (!empty($type) ? false : null));
+ if ($html != TAG_RETURN_TEXT) {
+ return $OUTPUT->tag_list(core_tag_tag::get_item_tags('', $record_type, $record_id, $official), '');
+ } else {
+ return join(', ', core_tag_tag::get_item_tags_array('', $record_type, $record_id, $official, 0, false));
+ }
+}
- foreach ($taginstances as $record) {
- tag_delete_instance('course', $courseid, $record->tagid, $record->tiuserid);
+/**
+ * Get an array of tag ids associated to a record.
+ *
+ * @package core_tag
+ * @deprecated since 3.1
+ * @param string $record_type the record type for which we want to get the tags
+ * @param int $record_id the record id for which we want to get the tags
+ * @return array tag ids, indexed and sorted by 'ordering'
+ */
+function tag_get_tags_ids($record_type, $record_id) {
+ debugging('Method tag_get_tags_ids() is deprecated. Please consider using core_tag_tag::get_item_tags() or similar methods.', DEBUG_DEVELOPER);
+ $tag_ids = array();
+ $tagobjects = core_tag_tag::get_item_tags(null, $record_type, $record_id);
+ foreach ($tagobjects as $tagobject) {
+ $tag = $tagobject->to_object();
+ if ( array_key_exists($tag->ordering, $tag_ids) ) {
+ $tag->ordering++;
}
- $taginstances->close();
+ $tag_ids[$tag->ordering] = $tag->id;
}
+ ksort($tag_ids);
+ return $tag_ids;
+}
- if ($showfeedback) {
- echo $OUTPUT->notification(get_string('deletedcoursetags', 'tag'), 'notifysuccess');
+/**
+ * Returns the database ID of a set of tags.
+ *
+ * @deprecated since 3.1
+ * @param mixed $tags one tag, or array of tags, to look for.
+ * @param bool $return_value specify the type of the returned value. Either TAG_RETURN_OBJECT, or TAG_RETURN_ARRAY (default).
+ * If TAG_RETURN_ARRAY is specified, an array will be returned even if only one tag was passed in $tags.
+ * @return mixed tag-indexed array of ids (or objects, if second parameter is TAG_RETURN_OBJECT), or only an int, if only one tag
+ * is given *and* the second parameter is null. No value for a key means the tag wasn't found.
+ */
+function tag_get_id($tags, $return_value = null) {
+ global $CFG, $DB;
+ debugging('Method tag_get_id() is deprecated and can be replaced with core_tag_tag::get_by_name() or core_tag_tag::get_by_name_bulk(). ' .
+ 'You need to specify tag collection when retrieving tag by name', DEBUG_DEVELOPER);
+
+ if (!is_array($tags)) {
+ if(is_null($return_value) || $return_value == TAG_RETURN_OBJECT) {
+ if ($tagobject = core_tag_tag::get_by_name(core_tag_collection::get_default(), $tags)) {
+ return $tagobject->id;
+ } else {
+ return 0;
+ }
+ }
+ $tags = array($tags);
+ }
+
+ $records = core_tag_tag::get_by_name_bulk(core_tag_collection::get_default(), $tags,
+ $return_value == TAG_RETURN_OBJECT ? '*' : 'id, name');
+ foreach ($records as $name => $record) {
+ if ($return_value != TAG_RETURN_OBJECT) {
+ $records[$name] = $record->id ? $record->id : null;
+ } else {
+ $records[$name] = $record->to_object();
+ }
+ }
+ return $records;
+}
+
+/**
+ * Change the "value" of a tag, and update the associated 'name'.
+ *
+ * @package core_tag
+ * @deprecated since 3.1
+ * @param int $tagid the id of the tag to modify
+ * @param string $newrawname the new rawname
+ * @return bool true on success, false otherwise
+ */
+function tag_rename($tagid, $newrawname) {
+ debugging('Function tag_rename() is deprecated and may be replaced with core_tag_tag::get($tagid)->update().', DEBUG_DEVELOPER);
+ if ($tag = core_tag_tag::get($tagid, '*')) {
+ return $tag->update(array('rawname' => $newrawname));
+ }
+ return false;
+}
+
+/**
+ * Delete one instance of a tag. If the last instance was deleted, it will also delete the tag, unless its type is 'official'.
+ *
+ * @package core_tag
+ * @deprecated since 3.1
+ * @param string $record_type the type of the record for which to remove the instance
+ * @param int $record_id the id of the record for which to remove the instance
+ * @param int $tagid the tagid that needs to be removed
+ * @param int $userid (optional) the userid
+ * @return bool true on success, false otherwise
+ */
+function tag_delete_instance($record_type, $record_id, $tagid, $userid = null) {
+ debugging('Function tag_delete_instance() is deprecated and replaced with core_tag_tag::remove_item_tag() instead. ' .
+ 'Component is required for retrieving instances', DEBUG_DEVELOPER);
+ $tag = core_tag_tag::get($tagid);
+ core_tag_tag::remove_item_tag('', $record_type, $record_id, $tag->rawname, $userid);
+}
+
+/**
+ * Find all records tagged with a tag of a given type ('post', 'user', etc.)
+ *
+ * @package core_tag
+ * @category tag
+ * @param string $tag tag to look for
+ * @param string $type type to restrict search to. If null, every matching record will be returned
+ * @param int $limitfrom (optional, required if $limitnum is set) return a subset of records, starting at this point.
+ * @param int $limitnum (optional, required if $limitfrom is set) return a subset comprising this many records.
+ * @return array of matching objects, indexed by record id, from the table containing the type requested
+ */
+function tag_find_records($tag, $type, $limitfrom='', $limitnum='') {
+ debugging('Function tag_find_records() is deprecated and replaced with core_tag_tag::get_by_name()->get_tagged_items(). '.
+ 'You need to specify tag collection when retrieving tag by name', DEBUG_DEVELOPER);
+
+ if (!$tag || !$type) {
+ return array();
+ }
+
+ $tagobject = core_tag_tag::get_by_name(core_tag_area::get_collection('', $type), $tag);
+ return $tagobject->get_tagged_items('', $type, $limitfrom, $limitnum);
+}
+
+/**
+ * Adds one or more tag in the database. This function should not be called directly : you should
+ * use tag_set.
+ *
+ * @package core_tag
+ * @deprecated since 3.1
+ * @param mixed $tags one tag, or an array of tags, to be created
+ * @param string $type type of tag to be created ("default" is the default value and "official" is the only other supported
+ * value at this time). An official tag is kept even if there are no records tagged with it.
+ * @return array $tags ids indexed by their lowercase normalized names. Any boolean false in the array indicates an error while
+ * adding the tag.
+ */
+function tag_add($tags, $type="default") {
+ debugging('Function tag_add() is deprecated. You can use core_tag_tag::create_if_missing(), however it should not be necessary ' .
+ 'since tags are created automatically when assigned to items', DEBUG_DEVELOPER);
+ if (!is_array($tags)) {
+ $tags = array($tags);
+ }
+ $objects = core_tag_tag::create_if_missing(core_tag_collection::get_default(), $tags, $type === 'official');
+
+ // New function returns the tags in different format, for BC we keep the format that this function used to have.
+ $rv = array();
+ foreach ($objects as $name => $tagobject) {
+ if (isset($tagobject->id)) {
+ $rv[$tagobject->name] = $tagobject->id;
+ } else {
+ $rv[$name] = false;
+ }
+ }
+ return $rv;
+}
+
+/**
+ * Assigns a tag to a record; if the record already exists, the time and ordering will be updated.
+ *
+ * @package core_tag
+ * @deprecated since 3.1
+ * @param string $record_type the type of the record that will be tagged
+ * @param int $record_id the id of the record that will be tagged
+ * @param string $tagid the tag id to set on the record.
+ * @param int $ordering the order of the instance for this record
+ * @param int $userid (optional) only required for course tagging
+ * @param string|null $component the component that was tagged
+ * @param int|null $contextid the context id of where this tag was assigned
+ * @return bool true on success, false otherwise
+ */
+function tag_assign($record_type, $record_id, $tagid, $ordering, $userid = 0, $component = null, $contextid = null) {
+ global $DB;
+ $message = 'Function tag_assign() is deprecated. Use core_tag_tag::set_item_tags() or core_tag_tag::add_item_tag() instead. ' .
+ 'Tag instance ordering should not be set manually';
+ if ($component === null || $contextid === null) {
+ $message .= '. You should specify the component and contextid of the item being tagged in your call to tag_assign.';
+ }
+ debugging($message, DEBUG_DEVELOPER);
+
+ if ($contextid) {
+ $context = context::instance_by_id($contextid);
+ } else {
+ $context = context_system::instance();
+ }
+
+ // Get the tag.
+ $tag = $DB->get_record('tag', array('id' => $tagid), 'name, rawname', MUST_EXIST);
+
+ $taginstanceid = core_tag_tag::add_item_tag($component, $record_type, $record_id, $context, $tag->rawname, $userid);
+
+ // Alter the "ordering" of tag_instance. This should never be done manually and only remains here for the backward compatibility.
+ $taginstance = new stdClass();
+ $taginstance->id = $taginstanceid;
+ $taginstance->ordering = $ordering;
+ $taginstance->timemodified = time();
+
+ $DB->update_record('tag_instance', $taginstance);
+
+ return true;
+}
+
+/**
+ * Count how many records are tagged with a specific tag.
+ *
+ * @package core_tag
+ * @deprecated since 3.1
+ * @param string $record_type record to look for ('post', 'user', etc.)
+ * @param int $tagid is a single tag id
+ * @return int number of mathing tags.
+ */
+function tag_record_count($record_type, $tagid) {
+ debugging('Method tag_record_count() is deprecated and replaced with core_tag_tag::get($tagid)->count_tagged_items(). '.
+ 'Component is now required when retrieving tag instances.', DEBUG_DEVELOPER);
+ return core_tag_tag::get($tagid)->count_tagged_items('', $record_type);
+}
+
+/**
+ * Determine if a record is tagged with a specific tag
+ *
+ * @package core_tag
+ * @deprecated since 3.1
+ * @param string $record_type the record type to look for
+ * @param int $record_id the record id to look for
+ * @param string $tag a tag name
+ * @return bool/int true if it is tagged, 0 (false) otherwise
+ */
+function tag_record_tagged_with($record_type, $record_id, $tag) {
+ debugging('Method tag_record_tagged_with() is deprecated and replaced with core_tag_tag::get($tagid)->is_item_tagged_with(). '.
+ 'Component is now required when retrieving tag instances.', DEBUG_DEVELOPER);
+ return core_tag_tag::is_item_tagged_with('', $record_type, $record_id, $tag);
+}
+
+/**
+ * Flag a tag as inappropriate.
+ *
+ * @deprecated since 3.1
+ * @param int|array $tagids a single tagid, or an array of tagids
+ */
+function tag_set_flag($tagids) {
+ debugging('Function tag_set_flag() is deprecated and replaced with core_tag_tag::get($tagid)->flag().', DEBUG_DEVELOPER);
+ $tagids = (array) $tagids;
+ foreach ($tagids as $tagid) {
+ if ($tag = core_tag_tag::get($tagid, '*')) {
+ $tag->flag();
+ }
+ }
+}
+
+/**
+ * Remove the inappropriate flag on a tag.
+ *
+ * @deprecated since 3.1
+ * @param int|array $tagids a single tagid, or an array of tagids
+ */
+function tag_unset_flag($tagids) {
+ debugging('Function tag_unset_flag() is deprecated and replaced with core_tag_tag::get($tagid)->reset_flag().', DEBUG_DEVELOPER);
+ $tagids = (array) $tagids;
+ foreach ($tagids as $tagid) {
+ if ($tag = core_tag_tag::get($tagid, '*')) {
+ $tag->reset_flag();
+ }
+ }
+}
+
+/**
+ * Prints or returns a HTML tag cloud with varying classes styles depending on the popularity and type of each tag.
+ *
+ * @deprecated since 3.1
+ *
+ * @param array $tagset Array of tags to display
+ * @param int $nr_of_tags Limit for the number of tags to retu