Skip to content

Commit

Permalink
MDL-46929 mod_forum: Implement tagging
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewhancox committed Apr 11, 2017
1 parent bd99cb9 commit d2ba493
Show file tree
Hide file tree
Showing 20 changed files with 583 additions and 26 deletions.
38 changes: 36 additions & 2 deletions lib/searchlib.php
Expand Up @@ -38,6 +38,7 @@
define("TOKEN_DATEFROM","6");
define("TOKEN_DATETO","7");
define("TOKEN_INSTANCE","8");
define("TOKEN_TAGS","9");

/**
* Class to hold token/value pairs after they're parsed.
Expand Down Expand Up @@ -110,6 +111,14 @@ public function __construct(&$parser){
$this->addExitPattern("\s","indatefrom");


// If we see the string tags: while in the base accept state, start
// parsing tags and go to the intags state.
$this->addEntryPattern("tags:\S+","accept","intags");

// Snarf everything into the tags until we see whitespace, then exit
// back to the base accept state.
$this->addExitPattern("\s","intags");

// Patterns to handle strings of the form dateto:foo

// If we see the string dateto: while in the base accept state, start
Expand Down Expand Up @@ -268,6 +277,17 @@ function indateto($content){
return true;
}

// State for handling tags:tagname,tagname constructs. Potentially emits a token.
function intags($content){
if (strlen($content) < 5) { // State exit or missing parameter.
return true;
}
// Strip off the tags: part and add the reminder to the parsed token array
$param = trim(substr($content,5));
$this->tokens[] = new search_token(TOKEN_TAGS,$param);
return true;
}

// State for handling instance:foo constructs. Potentially emits a token.
function ininstance($content){
if (strlen($content) < 10) { // State exit or missing parameter.
Expand Down Expand Up @@ -390,7 +410,8 @@ function search_generate_text_SQL($parsetree, $datafield, $metafield, $mainidfie
* @global object
*/
function search_generate_SQL($parsetree, $datafield, $metafield, $mainidfield, $useridfield,
$userfirstnamefield, $userlastnamefield, $timefield, $instancefield) {
$userfirstnamefield, $userlastnamefield, $timefield, $instancefield,
$tagfields = []) {
global $CFG, $DB;
static $p = 0;

Expand All @@ -407,7 +428,7 @@ function search_generate_SQL($parsetree, $datafield, $metafield, $mainidfield, $
}

$SQLString = '';

$nexttagfield = 0;
for ($i=0; $i<$ntokens; $i++){
if ($i > 0) {// We have more than one clause, need to tack on AND
$SQLString .= ' AND ';
Expand Down Expand Up @@ -465,6 +486,19 @@ function search_generate_SQL($parsetree, $datafield, $metafield, $mainidfield, $
$SQLString .= "($timefield >= :$name1)";
$params[$name1] = $value;
break;
case TOKEN_TAGS:
$sqlstrings = [];
foreach (explode(',', $value) as $tag) {
$paramname = $name1 . '_' . $nexttagfield;
if (!isset($tagfields[$nexttagfield])) {
throw new coding_exception('Not enough tag fields supplied for search.');
}
$sqlstrings[] = "($tagfields[$nexttagfield] = :$paramname)";
$params[$paramname] = $tag;
$nexttagfield++;
}
$SQLString .= implode(' AND ', $sqlstrings);
break;
case TOKEN_NEGATE:
$SQLString .= "(NOT ((".$DB->sql_like($datafield, ":$name1", false).") OR (".$DB->sql_like($metafield, ":$name2", false).")))";
$params[$name1] = "%$value%";
Expand Down
16 changes: 16 additions & 0 deletions mod/forum/backup/moodle2/backup_forum_stepslib.php
Expand Up @@ -60,6 +60,9 @@ protected function define_structure() {
'mailed', 'subject', 'message', 'messageformat',
'messagetrust', 'attachment', 'totalscore', 'mailnow'));

$tags = new backup_nested_element('tags');
$tag = new backup_nested_element('tag', array('id'), array('name', 'rawname'));

$ratings = new backup_nested_element('ratings');

$rating = new backup_nested_element('rating', array('id'), array(
Expand Down Expand Up @@ -113,6 +116,9 @@ protected function define_structure() {
$discussion->add_child($posts);
$posts->add_child($post);

$posts->add_child($tags);
$tags->add_child($tag);

$post->add_child($ratings);
$ratings->add_child($rating);

Expand Down Expand Up @@ -147,6 +153,16 @@ protected function define_structure() {
'ratingarea' => backup_helper::is_sqlparam('post'),
'itemid' => backup::VAR_PARENTID));
$rating->set_source_alias('rating', 'value');

$tag->set_source_sql('SELECT t.id, t.name, t.rawname
FROM {tag} t
JOIN {tag_instance} ti ON ti.tagid = t.id
WHERE ti.itemtype = ?
AND ti.component = ?
AND ti.itemid = ?', array(
backup_helper::is_sqlparam('forum_posts'),
backup_helper::is_sqlparam('mod_forum'),
backup::VAR_PARENTID));
}

// Define id annotations
Expand Down
15 changes: 15 additions & 0 deletions mod/forum/backup/moodle2/restore_forum_stepslib.php
Expand Up @@ -40,6 +40,7 @@ protected function define_structure() {
if ($userinfo) {
$paths[] = new restore_path_element('forum_discussion', '/activity/forum/discussions/discussion');
$paths[] = new restore_path_element('forum_post', '/activity/forum/discussions/discussion/posts/post');
$paths[] = new restore_path_element('forum_tag', '/activity/forum/discussions/discussion/posts/post/tags/tag');
$paths[] = new restore_path_element('forum_discussion_sub', '/activity/forum/discussions/discussion/discussion_subs/discussion_sub');
$paths[] = new restore_path_element('forum_rating', '/activity/forum/discussions/discussion/posts/post/ratings/rating');
$paths[] = new restore_path_element('forum_subscription', '/activity/forum/subscriptions/subscription');
Expand Down Expand Up @@ -117,6 +118,20 @@ protected function process_forum_post($data) {
}
}

protected function process_forum_tag($data) {
$data = (object)$data;

if (!core_tag_tag::is_enabled('mod_forum', 'forum_posts')) { // Tags disabled in server, nothing to process.
return;
}

$tag = $data->rawname;
$itemid = $this->get_new_parentid('forum_post');

$context = context_module::instance($this->task->get_moduleid());
core_tag_tag::add_item_tag('mod_forum', 'forum_posts', $itemid, $context, $tag);
}

protected function process_forum_rating($data) {
global $DB;

Expand Down
32 changes: 32 additions & 0 deletions mod/forum/classes/output/big_search_form.php
Expand Up @@ -52,6 +52,7 @@ class big_search_form implements renderable, templatable {
public $subject;
public $user;
public $words;
public $tags;
/** @var string The URL of the search form. */
public $actionurl;

Expand All @@ -64,6 +65,7 @@ class big_search_form implements renderable, templatable {
public function __construct($course) {
global $DB;
$this->course = $course;
$this->tags = [];
$this->showfullwords = $DB->get_dbfamily() == 'mysql' || $DB->get_dbfamily() == 'postgres';
$this->actionurl = new moodle_url('/mod/forum/search.php');

Expand Down Expand Up @@ -148,6 +150,15 @@ public function set_words($value) {
$this->words = $value;
}

/**
* Set tags.
*
* @param mixed $value Tags.
*/
public function set_tags($value) {
$this->tags = $value;
}

/**
* Forum ID setter search criteria.
*
Expand All @@ -158,6 +169,7 @@ public function set_forumid($forumid) {
}

public function export_for_template(renderer_base $output) {
global $DB, $CFG, $PAGE;
$data = new stdClass();

$data->courseid = $this->course->id;
Expand All @@ -172,6 +184,26 @@ public function export_for_template(renderer_base $output) {
$data->showfullwords = $this->showfullwords;
$data->actionurl = $this->actionurl->out(false);

$tagtypestoshow = \core_tag_area::get_showstandard('mod_forum', 'forum_posts');
$showstandard = ($tagtypestoshow != \core_tag_tag::HIDE_STANDARD);
$typenewtags = ($tagtypestoshow != \core_tag_tag::STANDARD_ONLY);

$PAGE->requires->js_call_amd('core/form-autocomplete', 'enhance', $params = array('#tags', $typenewtags, '',
get_string('entertags', 'tag'), false, $showstandard, get_string('noselection', 'form')));

$data->tagsenabled = \core_tag_tag::is_enabled('mod_forum', 'forum_posts');
$namefield = empty($CFG->keeptagnamecase) ? 'name' : 'rawname';
$tags = $DB->get_records('tag',
array('isstandard' => 1, 'tagcollid' => \core_tag_area::get_collection('mod_forum', 'forum_posts')),
$namefield, 'rawname,' . $namefield . ' as fieldname');
$data->tags = [];
foreach ($tags as $tag) {
$data->tagoptions[] = ['value' => $tag->rawname,
'text' => $tag->fieldname,
'selected' => in_array($tag->rawname, $this->tags)
];
}

$datefrom = $this->datefrom;
if (empty($datefrom)) {
$datefrom = make_timestamp(2000, 1, 1, 0, 0, 0);
Expand Down
7 changes: 7 additions & 0 deletions mod/forum/classes/post_form.php
Expand Up @@ -238,6 +238,13 @@ function definition() {
$mform->setConstants(array('timestart' => 0, 'timeend' => 0));
}

if (core_tag_tag::is_enabled('mod_forum', 'forum_posts')) {
$mform->addElement('header', 'tagshdr', get_string('tags', 'tag'));

$mform->addElement('tags', 'tags', get_string('tags'),
array('itemtype' => 'forum_posts', 'component' => 'mod_forum'));
}

//-------------------------------------------------------------------------------
// buttons
if (isset($post->edit)) { // hack alert
Expand Down
35 changes: 35 additions & 0 deletions mod/forum/db/tag.php
@@ -0,0 +1,35 @@
<?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 areas in component mod_forum
*
* @package mod_forum
* @copyright 2017 Andrew Hancox <andrewdchancox@googlemail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/

defined('MOODLE_INTERNAL') || die();


$tagareas = array(
array(
'itemtype' => 'forum_posts',
'component' => 'mod_forum',
'callback' => 'mod_forum_get_tagged_posts',
'callbackfile' => '/mod/forum/locallib.php',
),
);
5 changes: 5 additions & 0 deletions mod/forum/lang/en/forum.php
Expand Up @@ -430,6 +430,7 @@
$string['qandanotify'] = 'This is a question and answer forum. In order to see other responses to these questions, you must first post your answer';
$string['re'] = 'Re:';
$string['readtherest'] = 'Read the rest of this topic';
$string['removeallforumtags'] = 'Remove all forum tags';
$string['replies'] = 'Replies';
$string['repliesmany'] = '{$a} replies so far';
$string['repliesone'] = '{$a} reply so far';
Expand Down Expand Up @@ -464,6 +465,7 @@
$string['searchphrase'] = 'This exact phrase must appear in the post';
$string['searchresults'] = 'Search results';
$string['searchsubject'] = 'These words should be in the subject';
$string['searchtags'] = 'Is tagged with';
$string['searchuser'] = 'This name should match the author';
$string['searchuserid'] = 'The Moodle ID of the author';
$string['searchwhichforums'] = 'Choose which forums to search';
Expand Down Expand Up @@ -503,6 +505,9 @@
$string['subscriptionauto'] = 'Auto subscription';
$string['subscriptiondisabled'] = 'Subscription disabled';
$string['subscriptions'] = 'Subscriptions';
$string['tagarea_forum_posts'] = 'Forum posts';
$string['tagsdeleted'] = 'Forum tags have been deleted';
$string['tagtitle'] = 'See the "{$a}" tag';
$string['thisforumisthrottled'] = 'This forum has a limit to the number of forum postings you can make in a given time period - this is currently set at {$a->blockafter} posting(s) in {$a->blockperiod}';
$string['timedhidden'] = 'Timed status: Hidden from students';
$string['timedposts'] = 'Timed posts';
Expand Down
32 changes: 27 additions & 5 deletions mod/forum/lib.php
Expand Up @@ -2095,15 +2095,32 @@ function forum_search_posts($searchterms, $courseid=0, $limitfrom=0, $limitnum=5

if ($lexer->parse($searchstring)) {
$parsearray = $parser->get_parsed_array();

$tagjoins = '';
$tagfields = [];
foreach ($parsearray as $token) {
if ($token->getType() == TOKEN_TAGS) {
for ($i = substr_count($token->getValue(), ','); $i >= 0; $i--) {
$tagjoins .= "\n LEFT JOIN {tag_instance} ti_$i
ON p.id = ti_$i.itemid
AND ti_$i.component='mod_forum'
AND ti_$i.itemtype = 'forum_posts'";
$tagjoins .= "\n LEFT JOIN {tag} t_$i ON t_$i.id = ti_$i.tagid";
$tagfields[] = "t_$i.rawname";

}
}
}
list($messagesearch, $msparams) = search_generate_SQL($parsearray, 'p.message', 'p.subject',
'p.userid', 'u.id', 'u.firstname',
'u.lastname', 'p.modified', 'd.forum');
'u.lastname', 'p.modified', 'd.forum',
$tagfields);
$params = array_merge($params, $msparams);
}

$fromsql = "{forum_posts} p,
{forum_discussions} d,
{user} u";
$fromsql = "{forum_posts} p
INNER JOIN {forum_discussions} d ON d.id = p.discussion
INNER JOIN {user} u ON u.id = p.userid $tagjoins";

$selectsql = " $messagesearch
AND p.discussion = d.id
Expand Down Expand Up @@ -3446,6 +3463,10 @@ function forum_print_post($post, $discussion, $forum, &$cm, $course, $ownpost=fa
$postcontent .= html_writer::tag('div', $attachedimages, array('class'=>'attachedimages'));
}

if (\core_tag_tag::is_enabled('mod_forum', 'forum_posts')) {
$postcontent .= $OUTPUT->tag_list(core_tag_tag::get_item_tags('mod_forum', 'forum_posts', $post->id), null, 'forum-tags');
}

// Output the post content
$output .= html_writer::tag('div', $postcontent, array('class'=>'posting '.$postclass));
$output .= html_writer::end_tag('div'); // Content
Expand Down Expand Up @@ -5262,7 +5283,8 @@ function forum_user_can_see_post($forum, $discussion, $post, $user=NULL, $cm=NUL
$user = $USER;
}

$canviewdiscussion = !empty($cm->cache->caps['mod/forum:viewdiscussion']) || has_capability('mod/forum:viewdiscussion', $modcontext, $user->id);
$canviewdiscussion = (isset($cm->cache) && !empty($cm->cache->caps['mod/forum:viewdiscussion']))
|| has_capability('mod/forum:viewdiscussion', $modcontext, $user->id);
if (!$canviewdiscussion && !has_all_capabilities(array('moodle/user:viewdetails', 'moodle/user:readuserposts'), context_user::instance($post->userid))) {
return false;
}
Expand Down

0 comments on commit d2ba493

Please sign in to comment.