Merge pull request #6206 from marc1706/ticket/13713

[ticket/13713] User Mentions
This commit is contained in:
Máté Bartus 2021-05-23 17:49:33 +02:00 committed by GitHub
commit f3c426389c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
61 changed files with 3449 additions and 35 deletions

View file

@ -56,6 +56,44 @@
"jquery" "jquery"
] ]
}, },
"eslintConfig": {
"extends": "xo",
"rules": {
"quotes": [
"error",
"single"
],
"comma-dangle": [
"error",
"always-multiline"
],
"block-spacing": "error",
"array-bracket-spacing": [
"error",
"always"
],
"multiline-comment-style": "off",
"computed-property-spacing": "off",
"space-in-parens": "off",
"capitalized-comments": "off",
"object-curly-spacing": [
"error",
"always"
],
"no-lonely-if": "off",
"unicorn/prefer-module": "off",
"space-before-function-paren": [
"error",
"never"
]
},
"env": {
"es6": true,
"browser": true,
"node": true,
"jquery": true
}
},
"browserslist": [ "browserslist": [
"> 1%", "> 1%",
"not ie 11", "not ie 11",

View file

@ -8,10 +8,14 @@
// ]]> // ]]>
</script> </script>
{% include 'mentions_templates.html' %}
<!-- INCLUDEJS {T_ASSETS_PATH}/javascript/editor.js --> <!-- INCLUDEJS {T_ASSETS_PATH}/javascript/editor.js -->
<!-- INCLUDEJS {T_ASSETS_PATH}/javascript/tribute.min.js -->
<!-- INCLUDEJS {T_ASSETS_PATH}/javascript/mentions.js -->
<!-- EVENT acp_posting_buttons_before --> <!-- EVENT acp_posting_buttons_before -->
<div id="format-buttons"> <div id="format-buttons"<!-- IF S_ALLOW_MENTIONS --> data-mention-url="{U_MENTION_URL}" data-mention-names-limit="{S_MENTION_NAMES_LIMIT}" data-topic-id="{S_TOPIC_ID}" data-user-id="{S_USER_ID}"<!-- ENDIF -->>
<input type="button" class="button2" accesskey="b" name="addbbcode0" value=" B " style="font-weight:bold; width: 30px" onclick="bbstyle(0)" title="{L_BBCODE_B_HELP}" /> <input type="button" class="button2" accesskey="b" name="addbbcode0" value=" B " style="font-weight:bold; width: 30px" onclick="bbstyle(0)" title="{L_BBCODE_B_HELP}" />
<input type="button" class="button2" accesskey="i" name="addbbcode2" value=" i " style="font-style:italic; width: 30px" onclick="bbstyle(2)" title="{L_BBCODE_I_HELP}" /> <input type="button" class="button2" accesskey="i" name="addbbcode2" value=" i " style="font-style:italic; width: 30px" onclick="bbstyle(2)" title="{L_BBCODE_I_HELP}" />
<input type="button" class="button2" accesskey="u" name="addbbcode4" value=" u " style="text-decoration: underline; width: 30px" onclick="bbstyle(4)" title="{L_BBCODE_U_HELP}" /> <input type="button" class="button2" accesskey="u" name="addbbcode4" value=" u " style="text-decoration: underline; width: 30px" onclick="bbstyle(4)" title="{L_BBCODE_U_HELP}" />

View file

@ -1670,6 +1670,103 @@ fieldset.submit-buttons legend {
} }
} }
/* Mentions and mention dropdown
---------------------------------------- */
.mention {
font-weight: bold;
}
.mention-container {
text-align: left;
background-color: #ffffff;
border-radius: 2px;
box-shadow:
0 3px 1px -2px rgba(0, 0, 0, 0.2),
0 2px 2px 0 rgba(0, 0, 0, 0.14),
0 1px 5px 0 rgba(0, 0, 0, 0.12);
position: absolute;
z-index: 999;
overflow: auto; /* placed here for list to scroll with arrow key press */
max-height: 200px;
transition: all 0.2s ease;
}
.rtl .mention-container {
text-align: right;
}
.mention-list {
margin: 0;
padding: 0;
list-style-type: none;
}
.mention-media {
color: #757575;
display: inline-flex;
flex-shrink: 0;
justify-content: center;
align-items: center;
margin-right: 8px;
margin-left: 0;
}
.rtl .mention-media {
margin-right: 0;
margin-left: 16px;
}
.mention-media-avatar {
width: 40px;
height: 40px;
}
.mention-item {
font-size: 16px;
font-weight: 400;
line-height: 1.5;
letter-spacing: 0.04em;
border-bottom: 1px solid #dddddd;
color: #212121;
position: relative;
display: flex;
overflow: hidden;
justify-content: flex-start;
align-items: center;
padding: 8px;
cursor: pointer;
}
.mention-item:hover,
.mention-item.is-active {
text-decoration: none;
background-color: #eeeeee;
color: #2d80d2;
}
.mention-item:hover .mention-media-avatar,
.mention-item.is-active .mention-media-avatar {
color: #2d80d2;
}
.mention-name,
.mention-rank {
display: block;
}
.mention-name {
line-height: 1.25;
margin-right: 20px; /* needed to account for scrollbar bug on Firefox for Windows */
}
.mention-rank {
font-size: 14px;
font-weight: 400;
line-height: 1.2871;
letter-spacing: 0.04em;
color: #757575;
}
/* Input field styles /* Input field styles
---------------------------------------- */ ---------------------------------------- */
input.radio, input.radio,

View file

@ -1745,6 +1745,31 @@ phpbb.lazyLoadAvatars = function loadAvatars() {
}); });
}; };
/**
* Get editor text area element
*
* @param {string} formName Name of form
* @param {string} textareaName Textarea name
*
* @return {HTMLElement|null} Text area element or null if textarea couldn't be found
*/
phpbb.getEditorTextArea = function(formName, textareaName) {
let doc;
// find textarea, make sure browser supports necessary functions
if (document.forms[formName]) {
doc = document;
} else {
doc = opener.document;
}
if (!doc.forms[formName]) {
return;
}
return doc.forms[formName].elements[textareaName];
}
phpbb.recaptcha = { phpbb.recaptcha = {
button: null, button: null,
ready: false, ready: false,

View file

@ -384,28 +384,22 @@ function getCaretPosition(txtarea) {
return caretPos; return caretPos;
} }
/**
* Allow to use tab character when typing code
* Keep indentation of last line of code when typing code
*/
(function($) { (function($) {
$(document).ready(function() { 'use strict';
var doc, textarea;
// find textarea, make sure browser supports necessary functions $(document).ready(() => {
if (document.forms[form_name]) { const textarea = phpbb.getEditorTextArea(form_name, text_name);
doc = document;
} else {
doc = opener.document;
}
if (!doc.forms[form_name]) { if (typeof textarea === 'undefined') {
return; return;
} }
textarea = doc.forms[form_name].elements[text_name]; /**
* Allow to use tab character when typing code
* Keep indentation of last line of code when typing code
*/
phpbb.applyCodeEditor(textarea); phpbb.applyCodeEditor(textarea);
if ($('#attach-panel').length) { if ($('#attach-panel').length) {
phpbb.showDragNDrop(textarea); phpbb.showDragNDrop(textarea);
} }
@ -417,4 +411,3 @@ function getCaretPosition(txtarea) {
}); });
}); });
})(jQuery); })(jQuery);

View file

@ -0,0 +1,327 @@
/* global phpbb */
/* import Tribute from './tribute.min'; */
(function($) {
'use strict';
/**
* Mentions data returned from ajax requests
* @typedef {Object} MentionsData
* @property {string} name User/group name
* @property {string} id User/group ID
* @property {{img: string, group: string}} avatar Avatar data
* @property {string} rank User rank or empty string for groups
* @property {number} priority Priority of data entry
*/
/**
* Mentions class
* @constructor
*/
function Mentions() {
const $mentionDataContainer = $('[data-mention-url]:first');
const mentionURL = $mentionDataContainer.data('mentionUrl');
const mentionNamesLimit = $mentionDataContainer.data('mentionNamesLimit');
const mentionTopicId = $mentionDataContainer.data('topicId');
const mentionUserId = $mentionDataContainer.data('userId');
let queryInProgress = null;
const cachedNames = [];
const cachedAll = [];
const cachedSearchKey = 'name';
let tribute = null;
/**
* Get default avatar
* @param {string} type Type of avatar; either 'g' for group or user on any other value
* @returns {string} Default avatar svg code
*/
function defaultAvatar(type) {
if (type === 'g') {
return $('[data-id="mention-default-avatar-group"]').html();
}
return $('[data-id="mention-default-avatar"]').html();
}
/**
* Get avatar HTML for data and type of avatar
*
* @param {object} data
* @param {string} type
* @return {string} Avatar HTML
*/
function getAvatar(data, type) {
if (data.html === '' && data.src === '') {
return defaultAvatar(type);
}
if (data.html === '') {
const $avatarImg = $($('[data-id="mention-media-avatar-img"]').html());
$avatarImg.attr({
src: data.src,
width: data.width,
height: data.height,
alt: data.title,
});
return $avatarImg.get(0).outerHTML;
}
const $avatarImg = $(data.html);
$avatarImg.addClass('mention-media-avatar');
return $avatarImg.get(0).outerHTML;
}
/**
* Get cached keyword for query string
* @param {string} query Query string
* @returns {?string} Cached keyword if one fits query, else empty string if cached keywords exist, null if cached keywords do not exist
*/
function getCachedKeyword(query) {
if (!cachedNames) {
return null;
}
let i;
for (i = query.length; i > 0; i--) {
const startString = query.slice(0, i);
if (cachedNames[startString]) {
return startString;
}
}
return '';
}
/**
* Get names matching query
* @param {string} query Query string
* @param {Object.<number, MentionsData>} items List of {@link MentionsData} items
* @param {string} searchKey Key to use for matching items
* @returns {Object.<number, MentionsData>} List of {@link MentionsData} items filtered with query and by searchKey
*/
function getMatchedNames(query, items, searchKey) {
let i;
let itemsLength;
const matchedNames = [];
for (i = 0, itemsLength = items.length; i < itemsLength; i++) {
const item = items[i];
if (isItemMatched(query, item, searchKey)) {
matchedNames.push(item);
}
}
return matchedNames;
}
/**
* Return whether item is matched by query
*
* @param {string} query Search query string
* @param {MentionsData} item Mentions data item
* @param {string }searchKey Key to use for matching items
* @return {boolean} True if items is matched, false otherwise
*/
function isItemMatched(query, item, searchKey) {
return String(item[searchKey]).toLowerCase().indexOf(query.toLowerCase()) === 0;
}
/**
* Filter items by search query
*
* @param {string} query Search query string
* @param {Object.<number, MentionsData>} items List of {@link MentionsData} items
* @return {Object.<number, MentionsData>} List of {@link MentionsData} items filtered with query and by searchKey
*/
function itemFilter(query, items) {
let i;
let itemsLength;
const highestPriorities = { u: 1, g: 1 };
const _unsorted = { u: {}, g: {} };
const _exactMatch = [];
let _results = [];
// Reduce the items array to the relevant ones
items = getMatchedNames(query, items, 'name');
// Group names by their types and calculate priorities
for (i = 0, itemsLength = items.length; i < itemsLength; i++) {
const item = items[i];
// Check for unsupported type - in general, this should never happen
if (!_unsorted[item.type]) {
continue;
}
// Current user doesn't want to mention themselves with "@" in most cases -
// do not waste list space with their own name
if (item.type === 'u' && item.id === String(mentionUserId)) {
continue;
}
// Exact matches should not be prioritised - they always come first
if (item.name === query) {
_exactMatch.push(items[i]);
continue;
}
// If the item hasn't been added yet - add it
if (!_unsorted[item.type][item.id]) {
_unsorted[item.type][item.id] = item;
continue;
}
// Priority is calculated as the sum of priorities from different sources
_unsorted[item.type][item.id].priority += Number.parseFloat(item.priority.toString());
// Calculate the highest priority - we'll give it to group names
highestPriorities[item.type] = Math.max(highestPriorities[item.type], _unsorted[item.type][item.id].priority);
}
// All types of names should come at the same level of importance,
// otherwise they will be unlikely to be shown
// That's why we normalize priorities and push names to a single results array
$.each([ 'u', 'g' ], (key, type) => {
if (_unsorted[type]) {
$.each(_unsorted[type], (name, value) => {
// Normalize priority
value.priority /= highestPriorities[type];
// Add item to all results
_results.push(value);
});
}
});
// Sort names by priorities - higher values come first
_results = _results.sort((a, b) => {
return b.priority - a.priority;
});
// Exact match is the most important - should come above anything else
$.each(_exactMatch, (name, value) => {
_results.unshift(value);
});
return _results;
}
/**
* remoteFilter callback filter function
* @param {string} query Query string
* @param {function} callback Callback function for filtered items
*/
function remoteFilter(query, callback) {
/*
* Do not make a new request until the previous one for the same query is returned
* This fixes duplicate server queries e.g. when arrow keys are pressed
*/
if (queryInProgress === query) {
setTimeout(() => {
remoteFilter(query, callback);
}, 1000);
return;
}
const cachedKeyword = getCachedKeyword(query);
const cachedNamesForQuery = cachedKeyword === null ? null : cachedNames[cachedKeyword];
/*
* Use cached values when we can:
* 1) There are some names in the cache relevant for the query
* (cache for the query with the same first characters contains some data)
* 2) We have enough names to display OR
* all relevant names have been fetched from the server
*/
if (cachedNamesForQuery &&
(getMatchedNames(query, cachedNamesForQuery, cachedSearchKey).length >= mentionNamesLimit ||
cachedAll[cachedKeyword])) {
callback(cachedNamesForQuery);
return;
}
queryInProgress = query;
// eslint-disable-next-line camelcase
const parameters = { keyword: query, topic_id: mentionTopicId, _referer: location.href };
$.getJSON(mentionURL, parameters, data => {
cachedNames[query] = data.names;
cachedAll[query] = data.all;
callback(data.names);
}).always(() => {
queryInProgress = null;
});
}
/**
* Generate menu item HTML representation. Also ensures that mention-list
* class is set for unordered list in mention container
*
* @param {object} data Item data
* @returns {string} HTML representation of menu item
*/
function menuItemTemplate(data) {
const itemData = data;
const avatar = getAvatar(itemData.avatar, itemData.type);
const rank = (itemData.rank) ? $($('[data-id="mention-rank-span"]').html()).text(itemData.rank).get(0).outerHTML : '';
const $mentionContainer = $('.' + tribute.current.collection.containerClass);
if (typeof $mentionContainer !== 'undefined' && $mentionContainer.children('ul').hasClass('mention-list') === false) {
$mentionContainer.children('ul').addClass('mention-list');
}
const $avatarSpan = $($('[data-id="mention-media-span"]').html());
$avatarSpan.html(avatar);
const $nameSpan = $($('[data-id="mention-name-span"]').html());
$nameSpan.html(itemData.name + rank);
return $avatarSpan.get(0).outerHTML + $nameSpan.get(0).outerHTML;
}
this.isEnabled = function() {
return $mentionDataContainer.length;
};
/* global Tribute */
this.handle = function(textarea) {
tribute = new Tribute({
trigger: '@',
allowSpaces: true,
containerClass: 'mention-container',
selectClass: 'is-active',
itemClass: 'mention-item',
menuItemTemplate,
selectTemplate(item) {
return '[mention=' + item.type + ':' + item.id + ']' + item.name + '[/mention]';
},
menuItemLimit: mentionNamesLimit,
values(text, cb) {
remoteFilter(text, users => cb(users));
},
lookup(element) {
return Object.prototype.hasOwnProperty.call(element, 'name') ? element.name : '';
},
});
tribute.search.filter = itemFilter;
tribute.attach($(textarea));
};
}
phpbb.mentions = new Mentions();
$(document).ready(() => {
/* global form_name, text_name */
const textarea = phpbb.getEditorTextArea(form_name, text_name);
if (typeof textarea === 'undefined') {
return;
}
if (phpbb.mentions.isEnabled()) {
phpbb.mentions.handle(textarea);
}
});
})(jQuery);

File diff suppressed because one or more lines are too long

View file

@ -15,6 +15,7 @@ imports:
- { resource: services_help.yml } - { resource: services_help.yml }
- { resource: services_http.yml } - { resource: services_http.yml }
- { resource: services_language.yml } - { resource: services_language.yml }
- { resource: services_mention.yml }
- { resource: services_migrator.yml } - { resource: services_migrator.yml }
- { resource: services_mimetype_guesser.yml } - { resource: services_mimetype_guesser.yml }
- { resource: services_module.yml } - { resource: services_module.yml }

View file

@ -0,0 +1,75 @@
services:
# ----- Controller -----
mention.controller:
class: phpbb\mention\controller\mention
arguments:
- '@mention.source_collection'
- '@request'
- '%core.root_path%'
- '%core.php_ext%'
# ----- Sources for mention -----
mention.source_collection:
class: phpbb\di\service_collection
arguments:
- '@service_container'
tags:
- { name: service_collection, tag: mention.source }
mention.source.base_group:
abstract: true
arguments:
- '@dbal.conn'
- '@config'
- '@group_helper'
- '@user'
- '@auth'
- '%core.root_path%'
- '%core.php_ext%'
mention.source.base_user:
abstract: true
arguments:
- '@dbal.conn'
- '@config'
- '@user_loader'
- '%core.root_path%'
- '%core.php_ext%'
mention.source.friend:
class: phpbb\mention\source\friend
parent: mention.source.base_user
calls:
- [set_user, ['@user']]
tags:
- { name: mention.source }
mention.source.group:
class: phpbb\mention\source\group
parent: mention.source.base_group
tags:
- { name: mention.source }
mention.source.team:
class: phpbb\mention\source\team
parent: mention.source.base_user
tags:
- { name: mention.source }
mention.source.topic:
class: phpbb\mention\source\topic
parent: mention.source.base_user
tags:
- { name: mention.source }
mention.source.user:
class: phpbb\mention\source\user
parent: mention.source.base_user
tags:
- { name: mention.source }
mention.source.usergroup:
class: phpbb\mention\source\usergroup
parent: mention.source.base_group
tags:
- { name: mention.source }

View file

@ -95,6 +95,15 @@ services:
tags: tags:
- { name: notification.type } - { name: notification.type }
notification.type.mention:
class: phpbb\notification\type\mention
shared: false
parent: notification.type.post
calls:
- [set_helper, ['@text_formatter.s9e.mention_helper']]
tags:
- { name: notification.type }
notification.type.pm: notification.type.pm:
class: phpbb\notification\type\pm class: phpbb\notification\type\pm
shared: false shared: false

View file

@ -52,6 +52,15 @@ services:
text_formatter.s9e.link_helper: text_formatter.s9e.link_helper:
class: phpbb\textformatter\s9e\link_helper class: phpbb\textformatter\s9e\link_helper
text_formatter.s9e.mention_helper:
class: phpbb\textformatter\s9e\mention_helper
arguments:
- '@dbal.conn'
- '@auth'
- '@user'
- '%core.root_path%'
- '%core.php_ext%'
text_formatter.s9e.parser: text_formatter.s9e.parser:
class: phpbb\textformatter\s9e\parser class: phpbb\textformatter\s9e\parser
arguments: arguments:
@ -76,6 +85,7 @@ services:
- '@text_formatter.s9e.factory' - '@text_formatter.s9e.factory'
- '@dispatcher' - '@dispatcher'
calls: calls:
- [configure_mention_helper, ['@text_formatter.s9e.mention_helper']]
- [configure_quote_helper, ['@text_formatter.s9e.quote_helper']] - [configure_quote_helper, ['@text_formatter.s9e.quote_helper']]
- [configure_smilies_path, ['@config', '@path_helper']] - [configure_smilies_path, ['@config', '@path_helper']]
- [configure_user, ['@user', '@config', '@auth']] - [configure_user, ['@user', '@config', '@auth']]

View file

@ -24,6 +24,11 @@ phpbb_help_routing:
resource: help.yml resource: help.yml
prefix: /help prefix: /help
phpbb_mention_controller:
path: /mention
methods: [GET, POST]
defaults: { _controller: mention.controller:handle }
phpbb_report_routing: phpbb_report_routing:
resource: report.yml resource: report.yml

View file

@ -195,7 +195,7 @@ class acp_bbcodes
$data = $this->build_regexp($bbcode_match, $bbcode_tpl); $data = $this->build_regexp($bbcode_match, $bbcode_tpl);
// Make sure the user didn't pick a "bad" name for the BBCode tag. // Make sure the user didn't pick a "bad" name for the BBCode tag.
$hard_coded = array('code', 'quote', 'quote=', 'attachment', 'attachment=', 'b', 'i', 'url', 'url=', 'img', 'size', 'size=', 'color', 'color=', 'u', 'list', 'list=', 'email', 'email=', 'flash', 'flash='); $hard_coded = array('code', 'quote', 'quote=', 'attachment', 'attachment=', 'b', 'i', 'url', 'url=', 'img', 'size', 'size=', 'color', 'color=', 'u', 'list', 'list=', 'email', 'email=', 'flash', 'flash=', 'mention');
if (($action == 'modify' && strtolower($data['bbcode_tag']) !== strtolower($row['bbcode_tag'])) || ($action == 'create')) if (($action == 'modify' && strtolower($data['bbcode_tag']) !== strtolower($row['bbcode_tag'])) || ($action == 'create'))
{ {

View file

@ -220,7 +220,12 @@ class acp_board
'max_post_img_width' => array('lang' => 'MAX_POST_IMG_WIDTH', 'validate' => 'int:0:9999', 'type' => 'number:0:9999', 'explain' => true, 'append' => ' ' . $user->lang['PIXEL']), 'max_post_img_width' => array('lang' => 'MAX_POST_IMG_WIDTH', 'validate' => 'int:0:9999', 'type' => 'number:0:9999', 'explain' => true, 'append' => ' ' . $user->lang['PIXEL']),
'max_post_img_height' => array('lang' => 'MAX_POST_IMG_HEIGHT', 'validate' => 'int:0:9999', 'type' => 'number:0:9999', 'explain' => true, 'append' => ' ' . $user->lang['PIXEL']), 'max_post_img_height' => array('lang' => 'MAX_POST_IMG_HEIGHT', 'validate' => 'int:0:9999', 'type' => 'number:0:9999', 'explain' => true, 'append' => ' ' . $user->lang['PIXEL']),
'legend3' => 'ACP_SUBMIT_CHANGES', 'legend3' => 'MENTIONS',
'allow_mentions' => array('lang' => 'ALLOW_MENTIONS', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => false),
'mention_names_limit' => array('lang' => 'MENTION_NAMES_LIMIT', 'validate' => 'int:1:9999', 'type' => 'number:1:9999', 'explain' => false),
'mention_batch_size' => array('lang' => 'MENTION_BATCH_SIZE', 'validate' => 'int:1:9999', 'type' => 'number:1:9999', 'explain' => true),
'legend4' => 'ACP_SUBMIT_CHANGES',
) )
); );
break; break;

View file

@ -575,6 +575,7 @@ function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $
// Mark all topic notifications read for this user // Mark all topic notifications read for this user
$phpbb_notifications->mark_notifications(array( $phpbb_notifications->mark_notifications(array(
'notification.type.topic', 'notification.type.topic',
'notification.type.mention',
'notification.type.quote', 'notification.type.quote',
'notification.type.bookmark', 'notification.type.bookmark',
'notification.type.post', 'notification.type.post',
@ -660,6 +661,7 @@ function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $
$db->sql_freeresult($result); $db->sql_freeresult($result);
$phpbb_notifications->mark_notifications_by_parent(array( $phpbb_notifications->mark_notifications_by_parent(array(
'notification.type.mention',
'notification.type.quote', 'notification.type.quote',
'notification.type.bookmark', 'notification.type.bookmark',
'notification.type.post', 'notification.type.post',
@ -771,6 +773,7 @@ function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $
), $topic_id, $user->data['user_id'], $post_time); ), $topic_id, $user->data['user_id'], $post_time);
$phpbb_notifications->mark_notifications_by_parent(array( $phpbb_notifications->mark_notifications_by_parent(array(
'notification.type.mention',
'notification.type.quote', 'notification.type.quote',
'notification.type.bookmark', 'notification.type.bookmark',
'notification.type.post', 'notification.type.post',
@ -3943,6 +3946,10 @@ function page_header($page_title = '', $display_online_list = false, $item_id =
'U_RESTORE_PERMISSIONS' => ($user->data['user_perm_from'] && $auth->acl_get('a_switchperm')) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=restore_perm') : '', 'U_RESTORE_PERMISSIONS' => ($user->data['user_perm_from'] && $auth->acl_get('a_switchperm')) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=restore_perm') : '',
'U_FEED' => $controller_helper->route('phpbb_feed_index'), 'U_FEED' => $controller_helper->route('phpbb_feed_index'),
'S_ALLOW_MENTIONS' => ($config['allow_mentions'] && $auth->acl_get('u_mention') && (empty($forum_id) || $auth->acl_get('f_mention', $forum_id))) ? true : false,
'S_MENTION_NAMES_LIMIT' => $config['mention_names_limit'],
'U_MENTION_URL' => $controller_helper->route('phpbb_mention_controller'),
'S_USER_LOGGED_IN' => ($user->data['user_id'] != ANONYMOUS) ? true : false, 'S_USER_LOGGED_IN' => ($user->data['user_id'] != ANONYMOUS) ? true : false,
'S_AUTOLOGIN_ENABLED' => ($config['allow_autologin']) ? true : false, 'S_AUTOLOGIN_ENABLED' => ($config['allow_autologin']) ? true : false,
'S_BOARD_DISABLED' => ($config['board_disable']) ? true : false, 'S_BOARD_DISABLED' => ($config['board_disable']) ? true : false,
@ -3964,6 +3971,7 @@ function page_header($page_title = '', $display_online_list = false, $item_id =
'S_REGISTER_ENABLED' => ($config['require_activation'] != USER_ACTIVATION_DISABLE) ? true : false, 'S_REGISTER_ENABLED' => ($config['require_activation'] != USER_ACTIVATION_DISABLE) ? true : false,
'S_FORUM_ID' => $forum_id, 'S_FORUM_ID' => $forum_id,
'S_TOPIC_ID' => $topic_id, 'S_TOPIC_ID' => $topic_id,
'S_USER_ID' => $user->data['user_id'],
'S_LOGIN_ACTION' => ((!defined('ADMIN_START')) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=login') : append_sid("{$phpbb_admin_path}index.$phpEx", false, true, $user->session_id)), 'S_LOGIN_ACTION' => ((!defined('ADMIN_START')) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=login') : append_sid("{$phpbb_admin_path}index.$phpEx", false, true, $user->session_id)),
'S_LOGIN_REDIRECT' => $s_login_redirect, 'S_LOGIN_REDIRECT' => $s_login_redirect,

View file

@ -24,7 +24,7 @@ if (!defined('IN_PHPBB'))
*/ */
function adm_page_header($page_title) function adm_page_header($page_title)
{ {
global $config, $user, $template; global $config, $user, $template, $auth;
global $phpbb_root_path, $phpbb_admin_path, $phpEx, $SID, $_SID; global $phpbb_root_path, $phpbb_admin_path, $phpEx, $SID, $_SID;
global $phpbb_dispatcher, $phpbb_container; global $phpbb_dispatcher, $phpbb_container;
@ -66,6 +66,9 @@ function adm_page_header($page_title)
} }
} }
/** @var \phpbb\controller\helper $controller_helper */
$controller_helper = $phpbb_container->get('controller.helper');
$phpbb_version_parts = explode('.', PHPBB_VERSION, 3); $phpbb_version_parts = explode('.', PHPBB_VERSION, 3);
$phpbb_major = $phpbb_version_parts[0] . '.' . $phpbb_version_parts[1]; $phpbb_major = $phpbb_version_parts[0] . '.' . $phpbb_version_parts[1];
@ -86,6 +89,10 @@ function adm_page_header($page_title)
'U_ADM_INDEX' => append_sid("{$phpbb_admin_path}index.$phpEx"), 'U_ADM_INDEX' => append_sid("{$phpbb_admin_path}index.$phpEx"),
'U_INDEX' => append_sid("{$phpbb_root_path}index.$phpEx"), 'U_INDEX' => append_sid("{$phpbb_root_path}index.$phpEx"),
'S_ALLOW_MENTIONS' => ($config['allow_mentions'] && $auth->acl_get('u_mention')) ? true : false,
'S_MENTION_NAMES_LIMIT' => $config['mention_names_limit'],
'U_MENTION_URL' => $controller_helper->route('phpbb_mention_controller'),
'T_IMAGES_PATH' => "{$phpbb_root_path}images/", 'T_IMAGES_PATH' => "{$phpbb_root_path}images/",
'T_SMILIES_PATH' => "{$phpbb_root_path}{$config['smilies_path']}/", 'T_SMILIES_PATH' => "{$phpbb_root_path}{$config['smilies_path']}/",
'T_AVATAR_GALLERY_PATH' => "{$phpbb_root_path}{$config['avatar_gallery_path']}/", 'T_AVATAR_GALLERY_PATH' => "{$phpbb_root_path}{$config['avatar_gallery_path']}/",
@ -108,6 +115,7 @@ function adm_page_header($page_title)
'ICON_SYNC' => '<i class="icon acp-icon acp-icon-resync fa-refresh fa-fw" title="' . $user->lang('RESYNC') . '"></i>', 'ICON_SYNC' => '<i class="icon acp-icon acp-icon-resync fa-refresh fa-fw" title="' . $user->lang('RESYNC') . '"></i>',
'ICON_SYNC_DISABLED' => '<i class="icon acp-icon acp-icon-disabled fa-refresh fa-fw" title="' . $user->lang('RESYNC') . '"></i>', 'ICON_SYNC_DISABLED' => '<i class="icon acp-icon acp-icon-disabled fa-refresh fa-fw" title="' . $user->lang('RESYNC') . '"></i>',
'S_USER_ID' => $user->data['user_id'],
'S_USER_LANG' => $user->lang['USER_LANG'], 'S_USER_LANG' => $user->lang['USER_LANG'],
'S_CONTENT_DIRECTION' => $user->lang['DIRECTION'], 'S_CONTENT_DIRECTION' => $user->lang['DIRECTION'],
'S_CONTENT_ENCODING' => 'UTF-8', 'S_CONTENT_ENCODING' => 'UTF-8',

View file

@ -908,6 +908,7 @@ function delete_posts($where_type, $where_ids, $auto_sync = true, $posted_sync =
// Notifications types to delete // Notifications types to delete
$delete_notifications_types = array( $delete_notifications_types = array(
'notification.type.mention',
'notification.type.quote', 'notification.type.quote',
'notification.type.approve_post', 'notification.type.approve_post',
'notification.type.post_in_queue', 'notification.type.post_in_queue',

View file

@ -2443,6 +2443,7 @@ function submit_post($mode, $subject, $username, $topic_type, &$poll_ary, &$data
{ {
case 'post': case 'post':
$phpbb_notifications->add_notifications(array( $phpbb_notifications->add_notifications(array(
'notification.type.mention',
'notification.type.quote', 'notification.type.quote',
'notification.type.topic', 'notification.type.topic',
), $notification_data); ), $notification_data);
@ -2451,6 +2452,7 @@ function submit_post($mode, $subject, $username, $topic_type, &$poll_ary, &$data
case 'reply': case 'reply':
case 'quote': case 'quote':
$phpbb_notifications->add_notifications(array( $phpbb_notifications->add_notifications(array(
'notification.type.mention',
'notification.type.quote', 'notification.type.quote',
'notification.type.bookmark', 'notification.type.bookmark',
'notification.type.post', 'notification.type.post',
@ -2465,6 +2467,7 @@ function submit_post($mode, $subject, $username, $topic_type, &$poll_ary, &$data
if ($user->data['user_id'] == $poster_id) if ($user->data['user_id'] == $poster_id)
{ {
$phpbb_notifications->update_notifications(array( $phpbb_notifications->update_notifications(array(
'notification.type.mention',
'notification.type.quote', 'notification.type.quote',
), $notification_data); ), $notification_data);
} }

View file

@ -810,10 +810,14 @@ class mcp_queue
), $post_data); ), $post_data);
} }
} }
$phpbb_notifications->add_notifications(array('notification.type.quote'), $post_data); $phpbb_notifications->add_notifications(array(
'notification.type.mention',
'notification.type.quote',
), $post_data);
$phpbb_notifications->delete_notifications('notification.type.post_in_queue', $post_id); $phpbb_notifications->delete_notifications('notification.type.post_in_queue', $post_id);
$phpbb_notifications->mark_notifications(array( $phpbb_notifications->mark_notifications(array(
'notification.type.mention',
'notification.type.quote', 'notification.type.quote',
'notification.type.bookmark', 'notification.type.bookmark',
'notification.type.post', 'notification.type.post',
@ -1045,12 +1049,13 @@ class mcp_queue
if ($topic_data['topic_visibility'] == ITEM_UNAPPROVED) if ($topic_data['topic_visibility'] == ITEM_UNAPPROVED)
{ {
$phpbb_notifications->add_notifications(array( $phpbb_notifications->add_notifications(array(
'notification.type.mention',
'notification.type.quote', 'notification.type.quote',
'notification.type.topic', 'notification.type.topic',
), $topic_data); ), $topic_data);
} }
$phpbb_notifications->mark_notifications('quote', $topic_data['post_id'], $user->data['user_id']); $phpbb_notifications->mark_notifications(array('mention', 'quote'), $topic_data['post_id'], $user->data['user_id']);
$phpbb_notifications->mark_notifications('topic', $topic_id, $user->data['user_id']); $phpbb_notifications->mark_notifications('topic', $topic_id, $user->data['user_id']);
if ($notify_poster) if ($notify_poster)

View file

@ -21,6 +21,7 @@ INSERT INTO phpbb_config (config_name, config_value) VALUES ('allow_emailreuse',
INSERT INTO phpbb_config (config_name, config_value) VALUES ('allow_forum_notify', '1'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('allow_forum_notify', '1');
INSERT INTO phpbb_config (config_name, config_value) VALUES ('allow_live_searches', '1'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('allow_live_searches', '1');
INSERT INTO phpbb_config (config_name, config_value) VALUES ('allow_mass_pm', '1'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('allow_mass_pm', '1');
INSERT INTO phpbb_config (config_name, config_value) VALUES ('allow_mentions', '1');
INSERT INTO phpbb_config (config_name, config_value) VALUES ('allow_name_chars', 'USERNAME_CHARS_ANY'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('allow_name_chars', 'USERNAME_CHARS_ANY');
INSERT INTO phpbb_config (config_name, config_value) VALUES ('allow_namechange', '0'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('allow_namechange', '0');
INSERT INTO phpbb_config (config_name, config_value) VALUES ('allow_nocensors', '0'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('allow_nocensors', '0');
@ -234,6 +235,8 @@ INSERT INTO phpbb_config (config_name, config_value) VALUES ('max_sig_img_height
INSERT INTO phpbb_config (config_name, config_value) VALUES ('max_sig_img_width', '0'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('max_sig_img_width', '0');
INSERT INTO phpbb_config (config_name, config_value) VALUES ('max_sig_smilies', '0'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('max_sig_smilies', '0');
INSERT INTO phpbb_config (config_name, config_value) VALUES ('max_sig_urls', '5'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('max_sig_urls', '5');
INSERT INTO phpbb_config (config_name, config_value) VALUES ('mention_batch_size', '50');
INSERT INTO phpbb_config (config_name, config_value) VALUES ('mention_names_limit', '10');
INSERT INTO phpbb_config (config_name, config_value) VALUES ('mime_triggers', 'body|head|html|img|plaintext|a href|pre|script|table|title'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('mime_triggers', 'body|head|html|img|plaintext|a href|pre|script|table|title');
INSERT INTO phpbb_config (config_name, config_value) VALUES ('min_name_chars', '3'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('min_name_chars', '3');
INSERT INTO phpbb_config (config_name, config_value) VALUES ('min_pass_chars', '6'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('min_pass_chars', '6');
@ -380,6 +383,7 @@ INSERT INTO phpbb_acl_options (auth_option, is_local) VALUES ('f_ignoreflood', 1
INSERT INTO phpbb_acl_options (auth_option, is_local) VALUES ('f_img', 1); INSERT INTO phpbb_acl_options (auth_option, is_local) VALUES ('f_img', 1);
INSERT INTO phpbb_acl_options (auth_option, is_local) VALUES ('f_list', 1); INSERT INTO phpbb_acl_options (auth_option, is_local) VALUES ('f_list', 1);
INSERT INTO phpbb_acl_options (auth_option, is_local) VALUES ('f_list_topics', 1); INSERT INTO phpbb_acl_options (auth_option, is_local) VALUES ('f_list_topics', 1);
INSERT INTO phpbb_acl_options (auth_option, is_local) VALUES ('f_mention', 1);
INSERT INTO phpbb_acl_options (auth_option, is_local) VALUES ('f_noapprove', 1); INSERT INTO phpbb_acl_options (auth_option, is_local) VALUES ('f_noapprove', 1);
INSERT INTO phpbb_acl_options (auth_option, is_local) VALUES ('f_poll', 1); INSERT INTO phpbb_acl_options (auth_option, is_local) VALUES ('f_poll', 1);
INSERT INTO phpbb_acl_options (auth_option, is_local) VALUES ('f_post', 1); INSERT INTO phpbb_acl_options (auth_option, is_local) VALUES ('f_post', 1);
@ -478,6 +482,7 @@ INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_hideonline', 1
INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_ignoreflood', 1); INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_ignoreflood', 1);
INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_masspm', 1); INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_masspm', 1);
INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_masspm_group', 1); INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_masspm_group', 1);
INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_mention', 1);
INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_pm_attach', 1); INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_pm_attach', 1);
INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_pm_bbcode', 1); INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_pm_bbcode', 1);
INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_pm_delete', 1); INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_pm_delete', 1);
@ -590,7 +595,7 @@ INSERT INTO phpbb_acl_roles_data (role_id, auth_option_id, auth_setting) SELECT
INSERT INTO phpbb_acl_roles_data (role_id, auth_option_id, auth_setting) SELECT 7, auth_option_id, 1 FROM phpbb_acl_options WHERE auth_option LIKE 'u_%' AND auth_option NOT IN ('u_attach', 'u_viewonline', 'u_chggrp', 'u_chgname', 'u_ignoreflood', 'u_pm_attach', 'u_pm_emailpm', 'u_pm_flash', 'u_savedrafts', 'u_search', 'u_sendemail', 'u_sendim', 'u_masspm', 'u_masspm_group'); INSERT INTO phpbb_acl_roles_data (role_id, auth_option_id, auth_setting) SELECT 7, auth_option_id, 1 FROM phpbb_acl_options WHERE auth_option LIKE 'u_%' AND auth_option NOT IN ('u_attach', 'u_viewonline', 'u_chggrp', 'u_chgname', 'u_ignoreflood', 'u_pm_attach', 'u_pm_emailpm', 'u_pm_flash', 'u_savedrafts', 'u_search', 'u_sendemail', 'u_sendim', 'u_masspm', 'u_masspm_group');
# No Private Messages (u_) # No Private Messages (u_)
INSERT INTO phpbb_acl_roles_data (role_id, auth_option_id, auth_setting) SELECT 8, auth_option_id, 1 FROM phpbb_acl_options WHERE auth_option LIKE 'u_%' AND auth_option IN ('u_', 'u_chgavatar', 'u_chgcensors', 'u_chgemail', 'u_chgpasswd', 'u_download', 'u_hideonline', 'u_sig', 'u_viewprofile'); INSERT INTO phpbb_acl_roles_data (role_id, auth_option_id, auth_setting) SELECT 8, auth_option_id, 1 FROM phpbb_acl_options WHERE auth_option LIKE 'u_%' AND auth_option IN ('u_', 'u_chgavatar', 'u_chgcensors', 'u_chgemail', 'u_chgpasswd', 'u_download', 'u_hideonline', 'u_mention', 'u_sig', 'u_viewprofile');
INSERT INTO phpbb_acl_roles_data (role_id, auth_option_id, auth_setting) SELECT 8, auth_option_id, 0 FROM phpbb_acl_options WHERE auth_option LIKE 'u_%' AND auth_option IN ('u_readpm', 'u_sendpm', 'u_masspm', 'u_masspm_group'); INSERT INTO phpbb_acl_roles_data (role_id, auth_option_id, auth_setting) SELECT 8, auth_option_id, 0 FROM phpbb_acl_options WHERE auth_option LIKE 'u_%' AND auth_option IN ('u_readpm', 'u_sendpm', 'u_masspm', 'u_masspm_group');
# No Avatar (u_) # No Avatar (u_)

View file

@ -157,6 +157,7 @@ $lang = array_merge($lang, array(
// Post Settings // Post Settings
$lang = array_merge($lang, array( $lang = array_merge($lang, array(
'ACP_POST_SETTINGS_EXPLAIN' => 'Here you can set all default settings for posting.', 'ACP_POST_SETTINGS_EXPLAIN' => 'Here you can set all default settings for posting.',
'ALLOW_MENTIONS' => 'Allow mentions of users and groups boardwide',
'ALLOW_POST_LINKS' => 'Allow links in posts/private messages', 'ALLOW_POST_LINKS' => 'Allow links in posts/private messages',
'ALLOW_POST_LINKS_EXPLAIN' => 'If disallowed the <code>[URL]</code> BBCode tag and automatic/magic URLs are disabled.', 'ALLOW_POST_LINKS_EXPLAIN' => 'If disallowed the <code>[URL]</code> BBCode tag and automatic/magic URLs are disabled.',
'ALLOWED_SCHEMES_LINKS' => 'Allowed schemes in links', 'ALLOWED_SCHEMES_LINKS' => 'Allowed schemes in links',
@ -187,6 +188,10 @@ $lang = array_merge($lang, array(
'MAX_POST_IMG_WIDTH_EXPLAIN' => 'Maximum width of a flash file in postings. Set to 0 for unlimited size.', 'MAX_POST_IMG_WIDTH_EXPLAIN' => 'Maximum width of a flash file in postings. Set to 0 for unlimited size.',
'MAX_POST_URLS' => 'Maximum links per post', 'MAX_POST_URLS' => 'Maximum links per post',
'MAX_POST_URLS_EXPLAIN' => 'Maximum number of URLs in a post. Set to 0 for unlimited links.', 'MAX_POST_URLS_EXPLAIN' => 'Maximum number of URLs in a post. Set to 0 for unlimited links.',
'MENTIONS' => 'Mentions',
'MENTION_BATCH_SIZE' => 'Maximum number of names fetched from each source of names for a single request',
'MENTION_BATCH_SIZE_EXPLAIN' => 'Examples of sources: friends, topic repliers, group members etc.',
'MENTION_NAMES_LIMIT' => 'Maximum number of names in dropdown list',
'MIN_CHAR_LIMIT' => 'Minimum characters per post/message', 'MIN_CHAR_LIMIT' => 'Minimum characters per post/message',
'MIN_CHAR_LIMIT_EXPLAIN' => 'The minimum number of characters the user need to enter within a post/private message. The minimum for this setting is 1.', 'MIN_CHAR_LIMIT_EXPLAIN' => 'The minimum number of characters the user need to enter within a post/private message. The minimum for this setting is 1.',
'POSTING' => 'Posting', 'POSTING' => 'Posting',

View file

@ -76,6 +76,7 @@ $lang = array_merge($lang, array(
'ACL_U_ATTACH' => 'Can attach files', 'ACL_U_ATTACH' => 'Can attach files',
'ACL_U_DOWNLOAD' => 'Can download files', 'ACL_U_DOWNLOAD' => 'Can download files',
'ACL_U_MENTION' => 'Can mention users and groups',
'ACL_U_SAVEDRAFTS' => 'Can save drafts', 'ACL_U_SAVEDRAFTS' => 'Can save drafts',
'ACL_U_CHGCENSORS' => 'Can disable word censors', 'ACL_U_CHGCENSORS' => 'Can disable word censors',
'ACL_U_SIG' => 'Can use signature', 'ACL_U_SIG' => 'Can use signature',
@ -123,6 +124,7 @@ $lang = array_merge($lang, array(
'ACL_F_STICKY' => 'Can post stickies', 'ACL_F_STICKY' => 'Can post stickies',
'ACL_F_ANNOUNCE' => 'Can post announcements', 'ACL_F_ANNOUNCE' => 'Can post announcements',
'ACL_F_ANNOUNCE_GLOBAL' => 'Can post global announcements', 'ACL_F_ANNOUNCE_GLOBAL' => 'Can post global announcements',
'ACL_F_MENTION' => 'Can mention users and groups',
'ACL_F_REPLY' => 'Can reply to topics', 'ACL_F_REPLY' => 'Can reply to topics',
'ACL_F_EDIT' => 'Can edit own posts', 'ACL_F_EDIT' => 'Can edit own posts',
'ACL_F_DELETE' => 'Can permanently delete own posts', 'ACL_F_DELETE' => 'Can permanently delete own posts',

View file

@ -475,6 +475,9 @@ $lang = array_merge($lang, array(
'NOTIFICATION_FORUM' => '<em>Forum:</em> %1$s', 'NOTIFICATION_FORUM' => '<em>Forum:</em> %1$s',
'NOTIFICATION_GROUP_REQUEST' => '<strong>Group request</strong> from %1$s to join the group %2$s.', 'NOTIFICATION_GROUP_REQUEST' => '<strong>Group request</strong> from %1$s to join the group %2$s.',
'NOTIFICATION_GROUP_REQUEST_APPROVED' => '<strong>Group request approved</strong> to join the group %1$s.', 'NOTIFICATION_GROUP_REQUEST_APPROVED' => '<strong>Group request approved</strong> to join the group %1$s.',
'NOTIFICATION_MENTION' => array(
1 => '<strong>Mentioned</strong> by %1$s in:',
),
'NOTIFICATION_METHOD_INVALID' => 'The method "%s" does not refer to a valid notification method.', 'NOTIFICATION_METHOD_INVALID' => 'The method "%s" does not refer to a valid notification method.',
'NOTIFICATION_PM' => '<strong>Private Message</strong> from %1$s:', 'NOTIFICATION_PM' => '<strong>Private Message</strong> from %1$s:',
'NOTIFICATION_POST' => array( 'NOTIFICATION_POST' => array(

View file

@ -0,0 +1,20 @@
Subject: Topic reply notification - "{{ TOPIC_TITLE }}"
Hello {{ USERNAME }},
You are receiving this notification because "{{ AUTHOR_NAME }}" mentioned you in the topic "{{ TOPIC_TITLE }}" at "{{ SITENAME }}". You can use the following link to view the reply made.
If you want to view the post where you have been mentioned, click the following link:
{{ U_VIEW_POST }}
If you want to view the topic, click the following link:
{{ U_TOPIC }}
If you want to view the forum, click the following link:
{{ U_FORUM }}
If you no longer wish to receive updates about replies mentioning you, please update your notification settings here:
{{ U_NOTIFICATION_SETTINGS }}
{{ EMAIL_SIG }}

View file

@ -332,6 +332,7 @@ $lang = array_merge($lang, array(
'NOTIFICATION_TYPE_GROUP_REQUEST' => 'Someone requests to join a group you lead', 'NOTIFICATION_TYPE_GROUP_REQUEST' => 'Someone requests to join a group you lead',
'NOTIFICATION_TYPE_FORUM' => 'Someone replies to a topic in a forum to which you are subscribed', 'NOTIFICATION_TYPE_FORUM' => 'Someone replies to a topic in a forum to which you are subscribed',
'NOTIFICATION_TYPE_IN_MODERATION_QUEUE' => 'A post or topic needs approval', 'NOTIFICATION_TYPE_IN_MODERATION_QUEUE' => 'A post or topic needs approval',
'NOTIFICATION_TYPE_MENTION' => 'Someone mentions you in a post',
'NOTIFICATION_TYPE_MODERATION_QUEUE' => 'Your topics/posts are approved or disapproved by a moderator', 'NOTIFICATION_TYPE_MODERATION_QUEUE' => 'Your topics/posts are approved or disapproved by a moderator',
'NOTIFICATION_TYPE_PM' => 'Someone sends you a private message', 'NOTIFICATION_TYPE_PM' => 'Someone sends you a private message',
'NOTIFICATION_TYPE_POST' => 'Someone replies to a topic to which you are subscribed', 'NOTIFICATION_TYPE_POST' => 'Someone replies to a topic to which you are subscribed',

View file

@ -0,0 +1,43 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @license GNU General Public License, version 2 (GPL-2.0)
*
* For full copyright and license information, please see
* the docs/CREDITS.txt file.
*
*/
namespace phpbb\db\migration\data\v330;
class add_mention_settings extends \phpbb\db\migration\migration
{
public function update_data()
{
return array(
array('config.add', array('allow_mentions', true)),
array('config.add', array('mention_batch_size', 50)),
array('config.add', array('mention_names_limit', 10)),
// Set up user permissions
array('permission.add', array('u_mention', true)),
array('permission.permission_set', array('ROLE_USER_FULL', 'u_mention')),
array('permission.permission_set', array('ROLE_USER_STANDARD', 'u_mention')),
array('permission.permission_set', array('ROLE_USER_LIMITED', 'u_mention')),
array('permission.permission_set', array('ROLE_USER_NOPM', 'u_mention')),
array('permission.permission_set', array('ROLE_USER_NOAVATAR', 'u_mention')),
// Set up forum permissions
array('permission.add', array('f_mention', false)),
array('permission.permission_set', array('ROLE_FORUM_FULL', 'f_mention')),
array('permission.permission_set', array('ROLE_FORUM_STANDARD', 'f_mention')),
array('permission.permission_set', array('ROLE_FORUM_LIMITED', 'f_mention')),
array('permission.permission_set', array('ROLE_FORUM_ONQUEUE', 'f_mention')),
array('permission.permission_set', array('ROLE_FORUM_POLLS', 'f_mention')),
array('permission.permission_set', array('ROLE_FORUM_LIMITED_POLLS', 'f_mention')),
);
}
}

View file

@ -0,0 +1,78 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @license GNU General Public License, version 2 (GPL-2.0)
*
* For full copyright and license information, please see
* the docs/CREDITS.txt file.
*
*/
namespace phpbb\mention\controller;
use phpbb\di\service_collection;
use phpbb\request\request_interface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
class mention
{
/** @var service_collection */
protected $mention_sources;
/** @var request_interface */
protected $request;
/** @var string */
protected $phpbb_root_path;
/** @var string */
protected $php_ext;
/**
* Constructor
*
* @param service_collection|array $mention_sources
* @param request_interface $request
* @param string $phpbb_root_path
* @param string $phpEx
*/
public function __construct($mention_sources, request_interface $request, string $phpbb_root_path, string $phpEx)
{
$this->mention_sources = $mention_sources;
$this->request = $request;
$this->phpbb_root_path = $phpbb_root_path;
$this->php_ext = $phpEx;
}
/**
* Handle requests to mention controller
*
* @return JsonResponse|RedirectResponse
*/
public function handle()
{
if (!$this->request->is_ajax())
{
return new RedirectResponse(append_sid($this->phpbb_root_path . 'index.' . $this->php_ext));
}
$keyword = $this->request->variable('keyword', '', true);
$topic_id = $this->request->variable('topic_id', 0);
$names = [];
$has_names_remaining = false;
foreach ($this->mention_sources as $source)
{
$has_names_remaining = !$source->get($names, $keyword, $topic_id) || $has_names_remaining;
}
return new JsonResponse([
'names' => array_values($names),
'all' => !$has_names_remaining,
]);
}
}

View file

@ -0,0 +1,185 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @license GNU General Public License, version 2 (GPL-2.0)
*
* For full copyright and license information, please see
* the docs/CREDITS.txt file.
*
*/
namespace phpbb\mention\source;
use phpbb\auth\auth;
use phpbb\config\config;
use phpbb\db\driver\driver_interface;
use phpbb\group\helper;
abstract class base_group implements source_interface
{
/** @var driver_interface */
protected $db;
/** @var config */
protected $config;
/** @var helper */
protected $helper;
/** @var \phpbb\user */
protected $user;
/** @var auth */
protected $auth;
/** @var string */
protected $phpbb_root_path;
/** @var string */
protected $php_ext;
/** @var string|false */
protected $cache_ttl = false;
/** @var array Fetched groups' data */
protected $groups = null;
/**
* base_group constructor.
*
* @param driver_interface $db
* @param config $config
* @param helper $helper
* @param \phpbb\user $user
* @param auth $auth
* @param string $phpbb_root_path
* @param string $phpEx
*/
public function __construct(driver_interface $db, config $config, helper $helper, \phpbb\user $user, auth $auth, string $phpbb_root_path, string $phpEx)
{
$this->db = $db;
$this->config = $config;
$this->helper = $helper;
$this->user = $user;
$this->auth = $auth;
$this->phpbb_root_path = $phpbb_root_path;
$this->php_ext = $phpEx;
if (!function_exists('phpbb_get_user_rank'))
{
include($this->phpbb_root_path . 'includes/functions_display.' . $this->php_ext);
}
}
/**
* Returns data for all board groups
*
* @return array Array of groups' data
*/
protected function get_groups(): array
{
if (is_null($this->groups))
{
$query = $this->db->sql_build_query('SELECT', [
'SELECT' => 'g.*, ug.user_id as ug_user_id',
'FROM' => [
GROUPS_TABLE => 'g',
],
'LEFT_JOIN' => [
[
'FROM' => [USER_GROUP_TABLE => 'ug'],
'ON' => 'ug.group_id = g.group_id AND ug.user_pending = 0 AND ug.user_id = ' . (int) $this->user->data['user_id'],
],
],
]);
// Cache results for 5 minutes
$result = $this->db->sql_query($query, 600);
$this->groups = [];
while ($row = $this->db->sql_fetchrow($result))
{
if ($row['group_type'] == GROUP_SPECIAL && !in_array($row['group_name'], ['ADMINISTRATORS', 'GLOBAL_MODERATORS']) || $row['group_type'] == GROUP_HIDDEN && !$this->auth->acl_gets('a_group', 'a_groupadd', 'a_groupdel') && $row['ug_user_id'] != $this->user->data['user_id'])
{
// Skip the group that we should not be able to mention.
continue;
}
$group_name = $this->helper->get_name($row['group_name']);
$this->groups['names'][$row['group_id']] = $group_name;
$this->groups[$row['group_id']] = $row;
$this->groups[$row['group_id']]['group_name'] = $group_name;
}
$this->db->sql_freeresult($result);
}
return $this->groups;
}
/**
* Builds a query for getting group IDs based on user input
*
* @param string $keyword Search string
* @param int $topic_id Current topic ID
* @return string Query ready for execution
*/
abstract protected function query(string $keyword, int $topic_id): string;
/**
* {@inheritdoc}
*/
public function get_priority(array $row): int
{
// By default every result from the source increases the priority by a fixed value
return 1;
}
/**
* {@inheritdoc}
*/
public function get(array &$names, string $keyword, int $topic_id): bool
{
// Grab all group IDs and cache them if needed
$result = $this->db->sql_query($this->query($keyword, $topic_id), $this->cache_ttl);
$group_ids = [];
while ($row = $this->db->sql_fetchrow($result))
{
$group_ids[] = $row['group_id'];
}
$this->db->sql_freeresult($result);
// Grab group data
$groups = $this->get_groups();
$matches = preg_grep('/^' . preg_quote($keyword) . '.*/i', $groups['names']);
$group_ids = array_intersect($group_ids, array_flip($matches));
$i = 0;
foreach ($group_ids as $group_id)
{
if ($i >= $this->config['mention_batch_size'])
{
// Do not exceed the names limit
return false;
}
$group_rank = phpbb_get_user_rank($groups[$group_id], false);
array_push($names, [
'name' => $groups[$group_id]['group_name'],
'type' => 'g',
'id' => $group_id,
'avatar' => $this->helper->get_avatar($groups[$group_id]),
'rank' => (isset($group_rank['title'])) ? $group_rank['title'] : '',
'priority' => $this->get_priority($groups[$group_id]),
]);
$i++;
}
return true;
}
}

View file

@ -0,0 +1,173 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @license GNU General Public License, version 2 (GPL-2.0)
*
* For full copyright and license information, please see
* the docs/CREDITS.txt file.
*
*/
namespace phpbb\mention\source;
use phpbb\config\config;
use phpbb\db\driver\driver_interface;
use phpbb\user_loader;
abstract class base_user implements source_interface
{
/** @var driver_interface */
protected $db;
/** @var config */
protected $config;
/** @var user_loader */
protected $user_loader;
/** @var string */
protected $phpbb_root_path;
/** @var string */
protected $php_ext;
/** @var string|false */
protected $cache_ttl = false;
/**
* base_user constructor.
*
* @param driver_interface $db
* @param config $config
* @param user_loader $user_loader
* @param string $phpbb_root_path
* @param string $phpEx
*/
public function __construct(driver_interface $db, config $config, user_loader $user_loader, string $phpbb_root_path, string $phpEx)
{
$this->db = $db;
$this->config = $config;
$this->user_loader = $user_loader;
$this->phpbb_root_path = $phpbb_root_path;
$this->php_ext = $phpEx;
if (!function_exists('phpbb_get_user_rank'))
{
include($this->phpbb_root_path . 'includes/functions_display.' . $this->php_ext);
}
}
/**
* Builds a query based on user input
*
* @param string $keyword Search string
* @param int $topic_id Current topic ID
* @return string Query ready for execution
*/
abstract protected function query(string $keyword, int $topic_id): string;
/**
* {@inheritdoc}
*/
public function get_priority(array $row): int
{
// By default every result from the source increases the priority by a fixed value
return 1;
}
/**
* {@inheritdoc}
*/
public function get(array &$names, string $keyword, int $topic_id): bool
{
$fetched_all = false;
$keyword = utf8_clean_string($keyword);
$i = 0;
$users = [];
$user_ids = [];
// Grab all necessary user IDs and cache them if needed
if ($this->cache_ttl)
{
$result = $this->db->sql_query($this->query($keyword, $topic_id), $this->cache_ttl);
while ($i < $this->config['mention_batch_size'])
{
$row = $this->db->sql_fetchrow($result);
if (!$row)
{
$fetched_all = true;
break;
}
if (!empty($keyword) && strpos($row['username_clean'], $keyword) !== 0)
{
continue;
}
$i++;
$users[] = $row;
$user_ids[] = $row['user_id'];
}
// Determine whether all usernames were fetched in current batch
if (!$fetched_all)
{
$fetched_all = true;
while ($row = $this->db->sql_fetchrow($result))
{
if (!empty($keyword) && strpos($row['username_clean'], $keyword) !== 0)
{
continue;
}
// At least one username hasn't been fetched - exit loop
$fetched_all = false;
break;
}
}
}
else
{
$result = $this->db->sql_query_limit($this->query($keyword, $topic_id), $this->config['mention_batch_size'], 0);
while ($row = $this->db->sql_fetchrow($result))
{
$users[] = $row;
$user_ids[] = $row['user_id'];
}
// Determine whether all usernames were fetched in current batch
if (count($user_ids) < $this->config['mention_batch_size'])
{
$fetched_all = true;
}
}
$this->db->sql_freeresult($result);
// Load all user data with a single SQL query, needed for ranks and avatars
$this->user_loader->load_users($user_ids);
foreach ($users as $user)
{
$user_rank = $this->user_loader->get_rank($user['user_id']);
array_push($names, [
'name' => $this->user_loader->get_username($user['user_id'], 'username'),
'type' => 'u',
'id' => $user['user_id'],
'avatar' => $this->user_loader->get_avatar($user['user_id']),
'rank' => (isset($user_rank['rank_title'])) ? $user_rank['rank_title'] : '',
'priority' => $this->get_priority($user),
]);
}
return $fetched_all;
}
}

View file

@ -0,0 +1,58 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @license GNU General Public License, version 2 (GPL-2.0)
*
* For full copyright and license information, please see
* the docs/CREDITS.txt file.
*
*/
namespace phpbb\mention\source;
class friend extends base_user
{
/** @var \phpbb\user */
protected $user;
/**
* Set the user service used to retrieve current user ID
*
* @param \phpbb\user $user
*/
public function set_user(\phpbb\user $user): void
{
$this->user = $user;
}
/**
* {@inheritdoc}
*/
protected function query(string $keyword, int $topic_id): string
{
/*
* For optimization purposes all friends are returned regardless of the keyword
* Names filtering is done on the frontend
* Results will be cached on a per-user basis
*/
return $this->db->sql_build_query('SELECT', [
'SELECT' => 'u.username_clean, u.user_id',
'FROM' => [
USERS_TABLE => 'u',
],
'LEFT_JOIN' => [
[
'FROM' => [ZEBRA_TABLE => 'z'],
'ON' => 'u.user_id = z.zebra_id'
]
],
'WHERE' => 'z.friend = 1 AND z.user_id = ' . (int) $this->user->data['user_id'] . '
AND ' . $this->db->sql_in_set('u.user_type', [USER_NORMAL, USER_FOUNDER]) . '
AND u.username_clean ' . $this->db->sql_like_expression($keyword . $this->db->get_any_char()),
'ORDER_BY' => 'u.user_lastvisit DESC'
]);
}
}

View file

@ -0,0 +1,48 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @license GNU General Public License, version 2 (GPL-2.0)
*
* For full copyright and license information, please see
* the docs/CREDITS.txt file.
*
*/
namespace phpbb\mention\source;
class group extends base_group
{
/** @var string|false */
protected $cache_ttl = 300;
/**
* {@inheritdoc}
*/
public function get_priority(array $row): int
{
/*
* Presence in array with all names for this type should not increase the priority
* Otherwise names will not be properly sorted because we fetch them in batches
* and the name from 'special' source can be absent from the array with all names
* and therefore it will appear lower than needed
*/
return 0;
}
/**
* {@inheritdoc}
*/
protected function query(string $keyword, int $topic_id): string
{
return $this->db->sql_build_query('SELECT', [
'SELECT' => 'g.group_id',
'FROM' => [
GROUPS_TABLE => 'g',
],
'ORDER_BY' => 'g.group_name',
]);
}
}

View file

@ -0,0 +1,38 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @license GNU General Public License, version 2 (GPL-2.0)
*
* For full copyright and license information, please see
* the docs/CREDITS.txt file.
*
*/
namespace phpbb\mention\source;
interface source_interface
{
/**
* Searches database for names to mention
* and alters the passed array of found items
*
* @param array $names Array of already fetched data with names
* @param string $keyword Search string
* @param int $topic_id Current topic ID
* @return bool Whether there are no more satisfying names left
*/
public function get(array &$names, string $keyword, int $topic_id): bool;
/**
* Returns the priority of the currently selected name
* Please note that simple inner priorities for a certain source
* can be set with ORDER BY SQL clause
*
* @param array $row Array of fetched data for the name type (e.g. user row)
* @return int Priority (defaults to 1)
*/
public function get_priority(array $row): int;
}

View file

@ -0,0 +1,46 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @license GNU General Public License, version 2 (GPL-2.0)
*
* For full copyright and license information, please see
* the docs/CREDITS.txt file.
*
*/
namespace phpbb\mention\source;
class team extends base_user
{
/** @var string|false */
protected $cache_ttl = 300;
/**
* {@inheritdoc}
*/
protected function query(string $keyword, int $topic_id): string
{
/*
* Select unique names of team members: each name should be selected only once
* regardless of the number of groups the certain user is a member of
*
* For optimization purposes all team members are returned regardless of the keyword
* Names filtering is done on the frontend
* Results will be cached in a single file
*/
return $this->db->sql_build_query('SELECT_DISTINCT', [
'SELECT' => 'u.username_clean, u.user_id',
'FROM' => [
USERS_TABLE => 'u',
USER_GROUP_TABLE => 'ug',
TEAMPAGE_TABLE => 't',
],
'WHERE' => 'ug.group_id = t.group_id AND ug.user_id = u.user_id AND ug.user_pending = 0
AND ' . $this->db->sql_in_set('u.user_type', [USER_NORMAL, USER_FOUNDER]),
'ORDER_BY' => 'u.username_clean'
]);
}
}

View file

@ -0,0 +1,64 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @license GNU General Public License, version 2 (GPL-2.0)
*
* For full copyright and license information, please see
* the docs/CREDITS.txt file.
*
*/
namespace phpbb\mention\source;
class topic extends base_user
{
/**
* {@inheritdoc}
*/
public function get_priority(array $row): int
{
/*
* Topic's open poster is probably the most mentionable user in the topic
* so we give him a significant priority
*/
return $row['user_id'] === $row['topic_poster'] ? 5 : 1;
}
/**
* {@inheritdoc}
*/
protected function query(string $keyword, int $topic_id): string
{
/*
* Select poster's username together with topic author's ID
* that will be later used for prioritisation
*
* For optimization purposes all users are returned regardless of the keyword
* Names filtering is done on the frontend
* Results will be cached on a per-topic basis
*/
return $this->db->sql_build_query('SELECT', [
'SELECT' => 'u.username_clean, u.user_id, t.topic_poster',
'FROM' => [
USERS_TABLE => 'u',
],
'LEFT_JOIN' => [
[
'FROM' => [POSTS_TABLE => 'p'],
'ON' => 'u.user_id = p.poster_id'
],
[
'FROM' => [TOPICS_TABLE => 't'],
'ON' => 't.topic_id = p.topic_id'
],
],
'WHERE' => 'p.topic_id = ' . (int) $topic_id . '
AND ' . $this->db->sql_in_set('u.user_type', [USER_NORMAL, USER_FOUNDER]) . '
AND u.username_clean ' . $this->db->sql_like_expression($keyword . $this->db->get_any_char()),
'ORDER_BY' => 'p.post_time DESC'
]);
}
}

View file

@ -0,0 +1,47 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @license GNU General Public License, version 2 (GPL-2.0)
*
* For full copyright and license information, please see
* the docs/CREDITS.txt file.
*
*/
namespace phpbb\mention\source;
class user extends base_user
{
/**
* {@inheritdoc}
*/
public function get_priority(array $row): int
{
/*
* Presence in array with all names for this type should not increase the priority
* Otherwise names will not be properly sorted because we fetch them in batches
* and the name from 'special' source can be absent from the array with all names
* and therefore it will appear lower than needed
*/
return 0;
}
/**
* {@inheritdoc}
*/
protected function query(string $keyword, int $topic_id): string
{
return $this->db->sql_build_query('SELECT', [
'SELECT' => 'u.username_clean, u.user_id',
'FROM' => [
USERS_TABLE => 'u',
],
'WHERE' => $this->db->sql_in_set('u.user_type', [USER_NORMAL, USER_FOUNDER]) . '
AND u.username_clean ' . $this->db->sql_like_expression($keyword . $this->db->get_any_char()),
'ORDER_BY' => 'u.user_lastvisit DESC'
]);
}
}

View file

@ -0,0 +1,38 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @license GNU General Public License, version 2 (GPL-2.0)
*
* For full copyright and license information, please see
* the docs/CREDITS.txt file.
*
*/
namespace phpbb\mention\source;
class usergroup extends base_group
{
/**
* {@inheritdoc}
*/
protected function query(string $keyword, int $topic_id): string
{
return $this->db->sql_build_query('SELECT', [
'SELECT' => 'g.group_id',
'FROM' => [
GROUPS_TABLE => 'g',
],
'LEFT_JOIN' => [
[
'FROM' => [USER_GROUP_TABLE => 'ug'],
'ON' => 'g.group_id = ug.group_id'
]
],
'WHERE' => 'ug.user_pending = 0 AND ug.user_id = ' . (int) $this->user->data['user_id'],
'ORDER_BY' => 'g.group_name',
]);
}
}

View file

@ -0,0 +1,157 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @license GNU General Public License, version 2 (GPL-2.0)
*
* For full copyright and license information, please see
* the docs/CREDITS.txt file.
*
*/
namespace phpbb\notification\type;
use phpbb\textformatter\s9e\mention_helper;
/**
* Post mentioning notifications class
* This class handles notifying users when they have been mentioned in a post
*/
class mention extends post
{
/**
* @var mention_helper
*/
protected $helper;
/**
* {@inheritDoc}
*/
public function get_type()
{
return 'notification.type.mention';
}
/**
* {@inheritDoc}
*/
protected $language_key = 'NOTIFICATION_MENTION';
/**
* {@inheritDoc}
*/
public static $notification_option = [
'lang' => 'NOTIFICATION_TYPE_MENTION',
'group' => 'NOTIFICATION_GROUP_POSTING',
];
/**
* {@inheritDoc}
*/
public function is_available()
{
return $this->config['allow_mentions'] && $this->auth->acl_get('u_mention');
}
/**
* {@inheritDoc}
*/
public function find_users_for_notification($post, $options = array())
{
$options = array_merge(array(
'ignore_users' => array(),
), $options);
$user_ids = $this->helper->get_mentioned_user_ids($post['post_text']);
$user_ids = array_unique($user_ids);
$user_ids = array_diff($user_ids, [(int) $post['poster_id']]);
if (empty($user_ids))
{
return array();
}
return $this->get_authorised_recipients($user_ids, $post['forum_id'], $options, true);
}
/**
* Update a notification
*
* @param array $post Data specific for this type that will be updated
* @return true
*/
public function update_notifications($post)
{
$old_notifications = $this->notification_manager->get_notified_users($this->get_type(), array(
'item_id' => static::get_item_id($post),
));
// Find the new users to notify
$notifications = $this->find_users_for_notification($post);
// Find the notifications we must delete
$remove_notifications = array_diff(array_keys($old_notifications), array_keys($notifications));
// Find the notifications we must add
$add_notifications = array();
foreach (array_diff(array_keys($notifications), array_keys($old_notifications)) as $user_id)
{
$add_notifications[$user_id] = $notifications[$user_id];
}
// Add the necessary notifications
$this->notification_manager->add_notifications_for_users($this->get_type(), $post, $add_notifications);
// Remove the necessary notifications
if (!empty($remove_notifications))
{
$this->notification_manager->delete_notifications($this->get_type(), static::get_item_id($post), false, $remove_notifications);
}
// return true to continue with the update code in the notifications service (this will update the rest of the notifications)
return true;
}
/**
* {@inheritDoc}
*/
public function get_redirect_url()
{
return $this->get_url();
}
/**
* {@inheritDoc}
*/
public function get_email_template()
{
return 'mention';
}
/**
* {@inheritDoc}
*/
public function get_email_template_variables()
{
$user_data = $this->user_loader->get_user($this->get_data('poster_id'));
return array_merge(parent::get_email_template_variables(), array(
'AUTHOR_NAME' => htmlspecialchars_decode($user_data['username']),
));
}
/**
* Set the helper service used to retrieve mentioned used
*
* @param mention_helper $helper
*/
public function set_helper(mention_helper $helper): void
{
$this->helper = $helper;
}
}

View file

@ -231,6 +231,7 @@ class permissions
'u_attach' => array('lang' => 'ACL_U_ATTACH', 'cat' => 'post'), 'u_attach' => array('lang' => 'ACL_U_ATTACH', 'cat' => 'post'),
'u_download' => array('lang' => 'ACL_U_DOWNLOAD', 'cat' => 'post'), 'u_download' => array('lang' => 'ACL_U_DOWNLOAD', 'cat' => 'post'),
'u_mention' => array('lang' => 'ACL_U_MENTION', 'cat' => 'post'),
'u_savedrafts' => array('lang' => 'ACL_U_SAVEDRAFTS', 'cat' => 'post'), 'u_savedrafts' => array('lang' => 'ACL_U_SAVEDRAFTS', 'cat' => 'post'),
'u_chgcensors' => array('lang' => 'ACL_U_CHGCENSORS', 'cat' => 'post'), 'u_chgcensors' => array('lang' => 'ACL_U_CHGCENSORS', 'cat' => 'post'),
'u_sig' => array('lang' => 'ACL_U_SIG', 'cat' => 'post'), 'u_sig' => array('lang' => 'ACL_U_SIG', 'cat' => 'post'),
@ -276,6 +277,7 @@ class permissions
'f_sticky' => array('lang' => 'ACL_F_STICKY', 'cat' => 'post'), 'f_sticky' => array('lang' => 'ACL_F_STICKY', 'cat' => 'post'),
'f_announce' => array('lang' => 'ACL_F_ANNOUNCE', 'cat' => 'post'), 'f_announce' => array('lang' => 'ACL_F_ANNOUNCE', 'cat' => 'post'),
'f_announce_global' => array('lang' => 'ACL_F_ANNOUNCE_GLOBAL', 'cat' => 'post'), 'f_announce_global' => array('lang' => 'ACL_F_ANNOUNCE_GLOBAL', 'cat' => 'post'),
'f_mention' => array('lang' => 'ACL_F_MENTION', 'cat' => 'post'),
'f_reply' => array('lang' => 'ACL_F_REPLY', 'cat' => 'post'), 'f_reply' => array('lang' => 'ACL_F_REPLY', 'cat' => 'post'),
'f_edit' => array('lang' => 'ACL_F_EDIT', 'cat' => 'post'), 'f_edit' => array('lang' => 'ACL_F_EDIT', 'cat' => 'post'),
'f_delete' => array('lang' => 'ACL_F_DELETE', 'cat' => 'post'), 'f_delete' => array('lang' => 'ACL_F_DELETE', 'cat' => 'post'),

View file

@ -89,4 +89,12 @@ interface renderer_interface
* @return null * @return null
*/ */
public function set_viewsmilies($value); public function set_viewsmilies($value);
/**
* Set the "usemention" option
*
* @param bool $value Option's value
* @return null
*/
public function set_usemention($value);
} }

View file

@ -84,6 +84,12 @@ class factory implements \phpbb\textformatter\cache_interface
'img' => '[IMG src={IMAGEURL;useContent}]', 'img' => '[IMG src={IMAGEURL;useContent}]',
'list' => '[LIST type={HASHMAP=1:decimal,a:lower-alpha,A:upper-alpha,i:lower-roman,I:upper-roman;optional;postFilter=#simpletext} #createChild=LI]{TEXT}[/LIST]', 'list' => '[LIST type={HASHMAP=1:decimal,a:lower-alpha,A:upper-alpha,i:lower-roman,I:upper-roman;optional;postFilter=#simpletext} #createChild=LI]{TEXT}[/LIST]',
'li' => '[* $tagName=LI]{TEXT}[/*]', 'li' => '[* $tagName=LI]{TEXT}[/*]',
'mention' =>
"[MENTION={PARSE=/^g:(?'group_id'\d+)|u:(?'user_id'\d+)$/}
group_id={UINT;optional}
profile_url={URL;optional;postFilter=#false}
user_id={UINT;optional}
]{TEXT}[/MENTION]",
'quote' => 'quote' =>
"[QUOTE "[QUOTE
author={TEXT1;optional} author={TEXT1;optional}
@ -108,13 +114,13 @@ class factory implements \phpbb\textformatter\cache_interface
* @var array Default templates, taken from bbcode::bbcode_tpl() * @var array Default templates, taken from bbcode::bbcode_tpl()
*/ */
protected $default_templates = array( protected $default_templates = array(
'b' => '<span style="font-weight: bold"><xsl:apply-templates/></span>', 'b' => '<span style="font-weight: bold"><xsl:apply-templates/></span>',
'i' => '<span style="font-style: italic"><xsl:apply-templates/></span>', 'i' => '<span style="font-style: italic"><xsl:apply-templates/></span>',
'u' => '<span style="text-decoration: underline"><xsl:apply-templates/></span>', 'u' => '<span style="text-decoration: underline"><xsl:apply-templates/></span>',
'img' => '<img src="{IMAGEURL}" class="postimage" alt="{L_IMAGE}"/>', 'img' => '<img src="{IMAGEURL}" class="postimage" alt="{L_IMAGE}"/>',
'size' => '<span><xsl:attribute name="style"><xsl:text>font-size: </xsl:text><xsl:value-of select="substring(@size, 1, 4)"/><xsl:text>%; line-height: normal</xsl:text></xsl:attribute><xsl:apply-templates/></span>', 'size' => '<span><xsl:attribute name="style"><xsl:text>font-size: </xsl:text><xsl:value-of select="substring(@size, 1, 4)"/><xsl:text>%; line-height: normal</xsl:text></xsl:attribute><xsl:apply-templates/></span>',
'color' => '<span style="color: {COLOR}"><xsl:apply-templates/></span>', 'color' => '<span style="color: {COLOR}"><xsl:apply-templates/></span>',
'email' => '<a> 'email' => '<a>
<xsl:attribute name="href"> <xsl:attribute name="href">
<xsl:text>mailto:</xsl:text> <xsl:text>mailto:</xsl:text>
<xsl:value-of select="@email"/> <xsl:value-of select="@email"/>
@ -126,6 +132,19 @@ class factory implements \phpbb\textformatter\cache_interface
</xsl:attribute> </xsl:attribute>
<xsl:apply-templates/> <xsl:apply-templates/>
</a>', </a>',
'mention' => '<xsl:text>@</xsl:text>
<xsl:choose>
<xsl:when test="@profile_url">
<a class="mention" href="{@profile_url}">
<xsl:apply-templates/>
</a>
</xsl:when>
<xsl:otherwise>
<span class="mention">
<xsl:apply-templates/>
</span>
</xsl:otherwise>
</xsl:choose>',
); );
/** /**
@ -287,8 +306,8 @@ class factory implements \phpbb\textformatter\cache_interface
$configurator->tags['QUOTE']->nestingLimit = PHP_INT_MAX; $configurator->tags['QUOTE']->nestingLimit = PHP_INT_MAX;
} }
// Modify the template to disable images/flash depending on user's settings // Modify the template to disable images/flash/mentions depending on user's settings
foreach (array('FLASH', 'IMG') as $name) foreach (array('FLASH', 'IMG', 'MENTION') as $name)
{ {
$tag = $configurator->tags[$name]; $tag = $configurator->tags[$name];
$tag->template = '<xsl:choose><xsl:when test="$S_VIEW' . $name . '">' . $tag->template . '</xsl:when><xsl:otherwise><xsl:apply-templates/></xsl:otherwise></xsl:choose>'; $tag->template = '<xsl:choose><xsl:when test="$S_VIEW' . $name . '">' . $tag->template . '</xsl:when><xsl:otherwise><xsl:apply-templates/></xsl:otherwise></xsl:choose>';

View file

@ -0,0 +1,203 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @license GNU General Public License, version 2 (GPL-2.0)
*
* For full copyright and license information, please see
* the docs/CREDITS.txt file.
*
*/
namespace phpbb\textformatter\s9e;
use s9e\TextFormatter\Utils as TextFormatterUtils;
class mention_helper
{
/**
* @var \phpbb\db\driver\driver_interface
*/
protected $db;
/**
* @var \phpbb\auth\auth
*/
protected $auth;
/**
* @var \phpbb\user
*/
protected $user;
/**
* @var string Base URL for a user profile link, uses {USER_ID} as placeholder
*/
protected $user_profile_url;
/**
* @var string Base URL for a group profile link, uses {GROUP_ID} as placeholder
*/
protected $group_profile_url;
/**
* @var array Array of group IDs allowed to be mentioned by current user
*/
protected $mentionable_groups = null;
/**
* Constructor
*
* @param \phpbb\db\driver\driver_interface $db
* @param \phpbb\auth\auth $auth
* @param \phpbb\user $user
* @param string $root_path
* @param string $php_ext
*/
public function __construct($db, $auth, $user, $root_path, $php_ext)
{
$this->db = $db;
$this->auth = $auth;
$this->user = $user;
$this->user_profile_url = append_sid($root_path . 'memberlist.' . $php_ext, 'mode=viewprofile&u={USER_ID}', false);
$this->group_profile_url = append_sid($root_path . 'memberlist.' . $php_ext, 'mode=group&g={GROUP_ID}', false);
}
/**
* Inject dynamic metadata into MENTION tags in given XML
*
* @param string $xml Original XML
* @return string Modified XML
*/
public function inject_metadata($xml)
{
$profile_urls = [
'u' => $this->user_profile_url,
'g' => $this->group_profile_url,
];
return TextFormatterUtils::replaceAttributes(
$xml,
'MENTION',
function ($attributes) use ($profile_urls)
{
if (isset($attributes['user_id']))
{
$attributes['profile_url'] = str_replace('{USER_ID}', $attributes['user_id'], $profile_urls['u']);
}
else if (isset($attributes['group_id']))
{
$attributes['profile_url'] = str_replace('{GROUP_ID}', $attributes['group_id'], $profile_urls['g']);
}
return $attributes;
}
);
}
/**
* Get group IDs allowed to be mentioned by current user
*
* @return array
*/
protected function get_mentionable_groups()
{
if (is_array($this->mentionable_groups))
{
return $this->mentionable_groups;
}
$hidden_restriction = (!$this->auth->acl_gets('a_group', 'a_groupadd', 'a_groupdel')) ? ' AND (g.group_type <> ' . GROUP_HIDDEN . ' OR (ug.user_pending = 0 AND ug.user_id = ' . (int) $this->user->data['user_id'] . '))' : '';
$query = $this->db->sql_build_query('SELECT', [
'SELECT' => 'g.group_id',
'FROM' => [
GROUPS_TABLE => 'g',
],
'LEFT_JOIN' => [[
'FROM' => [
USER_GROUP_TABLE => 'ug',
],
'ON' => 'g.group_id = ug.group_id',
]],
'WHERE' => '(g.group_type <> ' . GROUP_SPECIAL . ' OR ' . $this->db->sql_in_set('g.group_name', ['ADMINISTRATORS', 'GLOBAL_MODERATORS']) . ')' . $hidden_restriction,
]);
$result = $this->db->sql_query($query);
$this->mentionable_groups = [];
while ($row = $this->db->sql_fetchrow($result))
{
$this->mentionable_groups[] = $row['group_id'];
}
$this->db->sql_freeresult($result);
return $this->mentionable_groups;
}
/**
* Selects IDs of user members of a certain group
*
* @param array $user_ids Array of already selected user IDs
* @param int $group_id ID of the group to search members in
*/
protected function get_user_ids_for_group(&$user_ids, $group_id)
{
if (!in_array($group_id, $this->get_mentionable_groups()))
{
return;
}
$query = $this->db->sql_build_query('SELECT', [
'SELECT' => 'ug.user_id, ug.group_id',
'FROM' => [
USER_GROUP_TABLE => 'ug',
GROUPS_TABLE => 'g',
],
'WHERE' => 'g.group_id = ug.group_id',
]);
// Cache results for 5 minutes
$result = $this->db->sql_query($query, 300);
while ($row = $this->db->sql_fetchrow($result))
{
if ($row['group_id'] == $group_id)
{
$user_ids[] = (int) $row['user_id'];
}
}
$this->db->sql_freeresult($result);
}
/**
* Get a list of mentioned user IDs
*
* @param string $xml Parsed text
* @return int[] List of user IDs
*/
public function get_mentioned_user_ids($xml)
{
$ids = array();
if (strpos($xml, '<MENTION ') === false)
{
return $ids;
}
// Add IDs of users mentioned directly
$user_ids = TextFormatterUtils::getAttributeValues($xml, 'MENTION', 'user_id');
$ids = array_merge($ids, array_map('intval', $user_ids));
// Add IDs of users mentioned as group members
$group_ids = TextFormatterUtils::getAttributeValues($xml, 'MENTION', 'group_id');
foreach ($group_ids as $group_id)
{
$this->get_user_ids_for_group($ids, (int) $group_id);
}
return $ids;
}
}

View file

@ -28,6 +28,11 @@ class renderer implements \phpbb\textformatter\renderer_interface
*/ */
protected $dispatcher; protected $dispatcher;
/**
* @var mention_helper
*/
protected $mention_helper;
/** /**
* @var quote_helper * @var quote_helper
*/ */
@ -58,6 +63,11 @@ class renderer implements \phpbb\textformatter\renderer_interface
*/ */
protected $viewsmilies = false; protected $viewsmilies = false;
/**
* @var bool Whether the user is allowed to use mentions
*/
protected $usemention = false;
/** /**
* Constructor * Constructor
* *
@ -117,6 +127,16 @@ class renderer implements \phpbb\textformatter\renderer_interface
extract($dispatcher->trigger_event('core.text_formatter_s9e_renderer_setup', compact($vars))); extract($dispatcher->trigger_event('core.text_formatter_s9e_renderer_setup', compact($vars)));
} }
/**
* Configure the mention_helper object used to display extended information in mentions
*
* @param mention_helper $mention_helper
*/
public function configure_mention_helper(mention_helper $mention_helper)
{
$this->mention_helper = $mention_helper;
}
/** /**
* Configure the quote_helper object used to display extended information in quotes * Configure the quote_helper object used to display extended information in quotes
* *
@ -162,6 +182,7 @@ class renderer implements \phpbb\textformatter\renderer_interface
$this->set_viewflash($user->optionget('viewflash')); $this->set_viewflash($user->optionget('viewflash'));
$this->set_viewimg($user->optionget('viewimg')); $this->set_viewimg($user->optionget('viewimg'));
$this->set_viewsmilies($user->optionget('viewsmilies')); $this->set_viewsmilies($user->optionget('viewsmilies'));
$this->set_usemention($config['allow_mentions'] && $auth->acl_get('u_mention'));
// Set the stylesheet parameters // Set the stylesheet parameters
foreach (array_keys($this->renderer->getParameters()) as $param_name) foreach (array_keys($this->renderer->getParameters()) as $param_name)
@ -229,6 +250,11 @@ class renderer implements \phpbb\textformatter\renderer_interface
*/ */
public function render($xml) public function render($xml)
{ {
if (isset($this->mention_helper))
{
$xml = $this->mention_helper->inject_metadata($xml);
}
if (isset($this->quote_helper)) if (isset($this->quote_helper))
{ {
$xml = $this->quote_helper->inject_metadata($xml); $xml = $this->quote_helper->inject_metadata($xml);
@ -310,4 +336,13 @@ class renderer implements \phpbb\textformatter\renderer_interface
$this->viewsmilies = $value; $this->viewsmilies = $value;
$this->renderer->setParameter('S_VIEWSMILIES', $value); $this->renderer->setParameter('S_VIEWSMILIES', $value);
} }
/**
* {@inheritdoc}
*/
public function set_usemention($value)
{
$this->usemention = $value;
$this->renderer->setParameter('S_VIEWMENTION', $value);
}
} }

View file

@ -0,0 +1,6 @@
<template data-id="mention-media-span"><span class="mention-media"></span></template>
<template data-id="mention-media-avatar-img"><img class="avatar mention-media-avatar" src="" alt=""></template>
<template data-id="mention-name-span"><span class="mention-name"></span></template>
<template data-id="mention-default-avatar"><svg class="mention-media-avatar" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path fill-rule="evenodd" d="M12,19.2C9.5,19.2 7.29,17.92 6,16C6.03,14 10,12.9 12,12.9C14,12.9 17.97,14 18,16C16.71,17.92 14.5,19.2 12,19.2M12,5A3,3 0 0,1 15,8A3,3 0 0,1 12,11A3,3 0 0,1 9,8A3,3 0 0,1 12,5M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12C22,6.47 17.5,2 12,2Z"/></svg></template>
<template data-id="mention-default-avatar-group"><svg class="mention-media-avatar" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path fill-rule="evenodd" d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z"/></svg></template>
<template data-id="mention-rank-span"><span class="mention-rank"></span></template>

View file

@ -25,7 +25,12 @@
} }
} }
</script> </script>
{% include 'mentions_templates.html' %}
<!-- INCLUDEJS {T_ASSETS_PATH}/javascript/editor.js --> <!-- INCLUDEJS {T_ASSETS_PATH}/javascript/editor.js -->
<!-- INCLUDEJS {T_ASSETS_PATH}/javascript/tribute.min.js -->
<!-- INCLUDEJS {T_ASSETS_PATH}/javascript/mentions.js -->
<!-- IF S_BBCODE_ALLOWED --> <!-- IF S_BBCODE_ALLOWED -->
<div id="colour_palette" style="display: none;"> <div id="colour_palette" style="display: none;">
@ -36,7 +41,7 @@
</div> </div>
<!-- EVENT posting_editor_buttons_before --> <!-- EVENT posting_editor_buttons_before -->
<div id="format-buttons" class="format-buttons"> <div id="format-buttons" class="format-buttons"<!-- IF S_ALLOW_MENTIONS --> data-mention-url="{U_MENTION_URL}" data-mention-names-limit="{S_MENTION_NAMES_LIMIT}" data-topic-id="{S_TOPIC_ID}" data-user-id="{S_USER_ID}"<!-- ENDIF -->>
<button type="button" class="button button-icon-only bbcode-b" accesskey="b" name="addbbcode0" value=" B " onclick="bbstyle(0)" title="{L_BBCODE_B_HELP}"> <button type="button" class="button button-icon-only bbcode-b" accesskey="b" name="addbbcode0" value=" B " onclick="bbstyle(0)" title="{L_BBCODE_B_HELP}">
{{ Icon('iconify', 'mdi:format-bold', '', true, 'c-button-icon') }} {{ Icon('iconify', 'mdi:format-bold', '', true, 'c-button-icon') }}
</button> </button>

View file

@ -365,6 +365,24 @@
float: left; float: left;
} }
/**
* mentions.css
*/
/* Mention block
---------------------------------------- */
/* Mention dropdown
---------------------------------------- */
.rtl .mention-container { /* mention-container */
text-align: right;
}
.rtl .mention-media {
margin-right: 0;
margin-left: 16px;
}
/** /**
* content.css * content.css
*/ */

View file

@ -369,6 +369,41 @@ p.post-notice {
background-image: none; background-image: none;
} }
/* colours and backgrounds for mentions.css */
/* mention dropdown */
.mention-container { /* mention-container */
background-color: #ffffff;
box-shadow:
0 3px 1px -2px rgba(0, 0, 0, 0.2),
0 2px 2px 0 rgba(0, 0, 0, 0.14),
0 1px 5px 0 rgba(0, 0, 0, 0.12);
}
.mention-media {
color: #757575;
}
.mention-item {
border-bottom-color: #dddddd;
color: #212121;
}
.mention-item:hover,
.mention-item.is-active {
background-color: #eeeeee;
color: #2d80d2;
}
.mention-item:hover .mention-media-avatar,
.mention-item.is-active .mention-media-avatar {
color: #2d80d2;
}
.mention-rank {
color: #757575;
}
/* colours and backgrounds for content.css */ /* colours and backgrounds for content.css */
ul.forums { ul.forums {
background-color: #edf4f7; background-color: #edf4f7;

View file

@ -0,0 +1,83 @@
/* -------------------------------------------------------------- /*
$Mentions
/* -------------------------------------------------------------- */
/* stylelint-disable selector-max-compound-selectors */
/* stylelint-disable selector-no-qualifying-type */
/* Mention block
---------------------------------------- */
.mention {
font-weight: bold;
}
/* Mention dropdown
---------------------------------------- */
.mention-container {
text-align: left;
border-radius: 2px;
position: absolute;
z-index: 999;
overflow: auto; /* placed here for list to scroll with arrow key press */
max-height: 200px;
transition: all 0.2s ease;
}
.mention-list {
margin: 0;
padding: 0;
list-style-type: none;
}
.mention-media {
display: inline-flex;
flex-shrink: 0;
justify-content: center;
align-items: center;
margin-right: 8px;
margin-left: 0;
}
.mention-media-avatar {
width: 40px;
height: 40px;
}
.mention-item {
font-size: 16px;
font-weight: 400;
line-height: 1.5;
letter-spacing: 0.04em;
border-bottom: 1px solid transparent;
position: relative;
display: flex;
overflow: hidden;
justify-content: flex-start;
align-items: center;
padding: 8px;
cursor: pointer;
}
.mention-item:hover {
text-decoration: none;
}
.mention-name,
.mention-rank {
display: block;
}
.mention-name {
line-height: 1.25;
margin-right: 20px; /* needed to account for scrollbar bug on Firefox for Windows */
}
.mention-rank {
font-size: 14px;
font-weight: 400;
line-height: 1.2871;
letter-spacing: 0.04em;
}
/* stylelint-enable selector-max-compound-selectors */
/* stylelint-enable selector-no-qualifying-type */

View file

@ -14,6 +14,7 @@
@import url("common.css?hash=658f990b"); @import url("common.css?hash=658f990b");
@import url("buttons.css?hash=eb16911f"); @import url("buttons.css?hash=eb16911f");
@import url("links.css?hash=5fec3654"); @import url("links.css?hash=5fec3654");
@import url("mentions.css?hash=a67fa183");
@import url("content.css?hash=f7bdea58"); @import url("content.css?hash=f7bdea58");
@import url("cp.css?hash=73c6f37d"); @import url("cp.css?hash=73c6f37d");
@import url("forms.css?hash=5e06dbba"); @import url("forms.css?hash=5e06dbba");

View file

@ -0,0 +1,558 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @license GNU General Public License, version 2 (GPL-2.0)
*
* For full copyright and license information, please see
* the docs/CREDITS.txt file.
*
*/
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
class phpbb_mention_controller_test extends phpbb_database_test_case
{
protected $db, $container, $user, $config, $auth, $cache;
/**
* @var \phpbb\mention\controller\mention
*/
protected $controller;
/**
* @var PHPUnit_Framework_MockObject_MockObject
*/
protected $request;
public function getDataSet()
{
return $this->createXMLDataSet(dirname(__FILE__) . '/fixtures/mention.xml');
}
public function setUp(): void
{
parent::setUp();
global $auth, $cache, $config, $db, $phpbb_container, $phpbb_dispatcher, $lang, $user, $request, $phpEx, $phpbb_root_path, $user_loader;
// Database
$this->db = $this->new_dbal();
$db = $this->db;
// Auth
$auth = $this->createMock('\phpbb\auth\auth');
$auth->expects($this->any())
->method('acl_gets')
->with('a_group', 'a_groupadd', 'a_groupdel')
->willReturn(false)
;
// Config
$config = new \phpbb\config\config(array(
'allow_mentions' => true,
'mention_batch_size' => 8,
'mention_names_limit' => 3,
));
$cache_driver = new \phpbb\cache\driver\dummy();
$cache = new \phpbb\cache\service(
$cache_driver,
$config,
$db,
$phpbb_root_path,
$phpEx
);
// Event dispatcher
$phpbb_dispatcher = new phpbb_mock_event_dispatcher();
// Language
$lang = new \phpbb\language\language(new \phpbb\language\language_file_loader($phpbb_root_path, $phpEx));
// User
$user = $this->createMock('\phpbb\user', array(), array(
$lang,
'\phpbb\datetime'
));
$user->ip = '';
$user->data = array(
'user_id' => 2,
'username' => 'myself',
'is_registered' => true,
'user_colour' => '',
);
// Request
$this->request = $request = $this->createMock('\phpbb\request\request');
$request->expects($this->any())
->method('is_ajax')
->willReturn(true);
$avatar_helper = $this->getMockBuilder('\phpbb\avatar\helper')
->disableOriginalConstructor()
->getMock();
$user_loader = new \phpbb\user_loader($avatar_helper, $db, $phpbb_root_path, $phpEx, USERS_TABLE);
// Container
$phpbb_container = new ContainerBuilder();
$loader = new YamlFileLoader($phpbb_container, new FileLocator(__DIR__ . '/fixtures'));
$loader->load('services_mention.yml');
$phpbb_container->set('user_loader', $user_loader);
$phpbb_container->set('user', $user);
$phpbb_container->set('language', $lang);
$phpbb_container->set('config', $config);
$phpbb_container->set('dbal.conn', $db);
$phpbb_container->set('auth', $auth);
$phpbb_container->set('cache.driver', $cache_driver);
$phpbb_container->set('cache', $cache);
$phpbb_container->set('request', $request);
$phpbb_container->set('group_helper', new \phpbb\group\helper(
$this->getMockBuilder('\phpbb\auth\auth')->disableOriginalConstructor()->getMock(),
$avatar_helper,
$cache,
$config,
new \phpbb\language\language(
new phpbb\language\language_file_loader($phpbb_root_path, $phpEx)
),
new phpbb_mock_event_dispatcher(),
new \phpbb\path_helper(
new \phpbb\symfony_request(
new phpbb_mock_request()
),
$this->getMockBuilder('\phpbb\request\request')->disableOriginalConstructor()->getMock(),
$phpbb_root_path,
$phpEx
),
$user
));
$phpbb_container->set('text_formatter.utils', new \phpbb\textformatter\s9e\utils());
$phpbb_container->set(
'text_formatter.s9e.mention_helper',
new \phpbb\textformatter\s9e\mention_helper(
$this->db,
$auth,
$user,
$phpbb_root_path,
$phpEx
)
);
$phpbb_container->setParameter('core.root_path', $phpbb_root_path);
$phpbb_container->setParameter('core.php_ext', $phpEx);
$phpbb_container->addCompilerPass(new phpbb\di\pass\markpublic_pass());
$phpbb_container->compile();
// Mention Sources
$mention_sources = array('friend', 'group', 'team', 'topic', 'user', 'usergroup');
$mention_sources_array = array();
foreach ($mention_sources as $source)
{
$class = $phpbb_container->get('mention.source.' . $source);
$mention_sources_array['mention.source.' . $source] = $class;
}
$this->controller = new \phpbb\mention\controller\mention($mention_sources_array, $request, $phpbb_root_path, $phpEx);
}
public function handle_data()
{
/**
* NOTE:
* 1) in production comparison with 'myself' is being done in JS
* 2) team members of hidden groups can also be mentioned (because they are shown on teampage)
*/
return [
['', 0, [
'names' => [
[
'name' => 'friend',
'type' => 'u',
'id' => 7,
'avatar' => [],
'rank' => '',
'priority' => 1,
],
[
'name' => 'Group we are a member of',
'type' => 'g',
'id' => 3,
'avatar' => [],
'rank' => '',
'priority' => 0,
],
[
'name' => 'Normal group',
'type' => 'g',
'id' => 1,
'avatar' => [],
'rank' => '',
'priority' => 0,
],
[
'name' => 'team_member_hidden',
'type' => 'u',
'id' => 6,
'avatar' => [],
'rank' => '',
'priority' => 1,
],
[
'name' => 'team_member_normal',
'type' => 'u',
'id' => 5,
'avatar' => [],
'rank' => '',
'priority' => 1,
],
[
'name' => 'myself',
'type' => 'u',
'id' => 2,
'avatar' => [],
'rank' => '',
'priority' => 0,
],
[
'name' => 'poster',
'type' => 'u',
'id' => 3,
'avatar' => [],
'rank' => '',
'priority' => 0,
],
[
'name' => 'replier',
'type' => 'u',
'id' => 4,
'avatar' => [],
'rank' => '',
'priority' => 0,
],
[
'name' => 'team_member_normal',
'type' => 'u',
'id' => 5,
'avatar' => [],
'rank' => '',
'priority' => 0,
],
[
'name' => 'team_member_hidden',
'type' => 'u',
'id' => 6,
'avatar' => [],
'rank' => '',
'priority' => 0,
],
[
'name' => 'friend',
'type' => 'u',
'id' => 7,
'avatar' => [],
'rank' => '',
'priority' => 0,
],
[
'name' => 'test',
'type' => 'u',
'id' => 8,
'avatar' => [],
'rank' => '',
'priority' => 0,
],
[
'name' => 'test1',
'type' => 'u',
'id' => 9,
'avatar' => [],
'rank' => '',
'priority' => 0,
],
[
'name' => 'Group we are a member of',
'type' => 'g',
'id' => 3,
'avatar' => [],
'rank' => '',
'priority' => 1,
],
],
'all' => false,
]],
['', 1, [
'names' => [
[
'name' => 'friend',
'type' => 'u',
'id' => 7,
'avatar' => [],
'rank' => '',
'priority' => 1,
],
[
'name' => 'Group we are a member of',
'type' => 'g',
'id' => 3,
'avatar' => [],
'rank' => '',
'priority' => 0,
],
[
'name' => 'Normal group',
'type' => 'g',
'id' => 1,
'avatar' => [],
'rank' => '',
'priority' => 0,
],
[
'name' => 'team_member_hidden',
'type' => 'u',
'id' => 6,
'avatar' => [],
'rank' => '',
'priority' => 1,
],
[
'name' => 'team_member_normal',
'type' => 'u',
'id' => 5,
'avatar' => [],
'rank' => '',
'priority' => 1,
],
[
'name' => 'replier',
'type' => 'u',
'id' => 4,
'avatar' => [],
'rank' => '',
'priority' => 1,
],
[
'name' => 'poster',
'type' => 'u',
'id' => 3,
'avatar' => [],
'rank' => '',
'priority' => 5,
],
[
'name' => 'myself',
'type' => 'u',
'id' => 2,
'avatar' => [],
'rank' => '',
'priority' => 0,
],
[
'name' => 'poster',
'type' => 'u',
'id' => 3,
'avatar' => [],
'rank' => '',
'priority' => 0,
],
[
'name' => 'replier',
'type' => 'u',
'id' => 4,
'avatar' => [],
'rank' => '',
'priority' => 0,
],
[
'name' => 'team_member_normal',
'type' => 'u',
'id' => 5,
'avatar' => [],
'rank' => '',
'priority' => 0,
],
[
'name' => 'team_member_hidden',
'type' => 'u',
'id' => 6,
'avatar' => [],
'rank' => '',
'priority' => 0,
],
[
'name' => 'friend',
'type' => 'u',
'id' => 7,
'avatar' => [],
'rank' => '',
'priority' => 0,
],
[
'name' => 'test',
'type' => 'u',
'id' => 8,
'avatar' => [],
'rank' => '',
'priority' => 0,
],
[
'name' => 'test1',
'type' => 'u',
'id' => 9,
'avatar' => [],
'rank' => '',
'priority' => 0,
],
[
'name' => 'Group we are a member of',
'type' => 'g',
'id' => 3,
'avatar' => [],
'rank' => '',
'priority' => 1,
],
],
'all' => false,
]],
['t', 1, [
'names' => [
[
'name' => 'team_member_hidden',
'type' => 'u',
'id' => 6,
'avatar' => [],
'rank' => '',
'priority' => 1,
],
[
'name' => 'team_member_normal',
'type' => 'u',
'id' => 5,
'avatar' => [],
'rank' => '',
'priority' => 1,
],
[
'name' => 'team_member_normal',
'type' => 'u',
'id' => 5,
'avatar' => [],
'rank' => '',
'priority' => 0,
],
[
'name' => 'team_member_hidden',
'type' => 'u',
'id' => 6,
'avatar' => [],
'rank' => '',
'priority' => 0,
],
[
'name' => 'test',
'type' => 'u',
'id' => 8,
'avatar' => [],
'rank' => '',
'priority' => 0,
],
[
'name' => 'test1',
'type' => 'u',
'id' => 9,
'avatar' => [],
'rank' => '',
'priority' => 0,
],
[
'name' => 'test2',
'type' => 'u',
'id' => 10,
'avatar' => [],
'rank' => '',
'priority' => 0,
],
[
'name' => 'test3',
'type' => 'u',
'id' => 11,
'avatar' => [],
'rank' => '',
'priority' => 0,
],
],
'all' => true,
]],
['test', 1, [
'names' => [
[
'name' => 'test',
'type' => 'u',
'id' => 8,
'avatar' => [],
'rank' => '',
'priority' => 0,
],
[
'name' => 'test1',
'type' => 'u',
'id' => 9,
'avatar' => [],
'rank' => '',
'priority' => 0,
],
[
'name' => 'test2',
'type' => 'u',
'id' => 10,
'avatar' => [],
'rank' => '',
'priority' => 0,
],
[
'name' => 'test3',
'type' => 'u',
'id' => 11,
'avatar' => [],
'rank' => '',
'priority' => 0,
],
],
'all' => true,
]],
['test1', 1, [
'names' => [[
'name' => 'test1',
'type' => 'u',
'id' => 9,
'avatar' => [],
'rank' => '',
'priority' => 0,
]],
'all' => true,
]],
];
}
/**
* @dataProvider handle_data
*/
public function test_handle($keyword, $topic_id, $expected_result)
{
$this->request->expects($this->atLeast(2))
->method('variable')
->withConsecutive(
['keyword', '', true],
['topic_id', 0])
->willReturnOnConsecutiveCalls(
$keyword,
$topic_id
);
$data = json_decode($this->controller->handle()->getContent(), true);
$this->assertEquals($expected_result, $data);
}
}

View file

@ -0,0 +1,204 @@
<?xml version="1.0" encoding="UTF-8" ?>
<dataset>
<table name="phpbb_groups">
<column>group_id</column>
<column>group_name</column>
<column>group_type</column>
<column>group_desc</column>
<row>
<value>1</value>
<value>Normal group</value>
<value>0</value>
<value></value>
</row>
<row>
<value>2</value>
<value>Hidden group</value>
<value>2</value>
<value></value>
</row>
<row>
<value>3</value>
<value>Group we are a member of</value>
<value>0</value>
<value></value>
</row>
</table>
<table name="phpbb_posts">
<column>post_id</column>
<column>topic_id</column>
<column>forum_id</column>
<column>poster_id</column>
<column>post_time</column>
<column>post_text</column>
<row>
<value>1</value>
<value>1</value>
<value>1</value>
<value>3</value>
<value>1</value>
<value>Topic's initial post.</value>
</row>
<row>
<value>2</value>
<value>1</value>
<value>1</value>
<value>4</value>
<value>2</value>
<value>A reply.</value>
</row>
</table>
<table name="phpbb_teampage">
<column>teampage_id</column>
<column>group_id</column>
<row>
<value>1</value>
<value>1</value>
</row>
<row>
<value>2</value>
<value>2</value>
</row>
</table>
<table name="phpbb_topics">
<column>topic_id</column>
<column>forum_id</column>
<column>topic_poster</column>
<row>
<value>1</value>
<value>1</value>
<value>3</value>
</row>
</table>
<table name="phpbb_users">
<column>user_id</column>
<column>username</column>
<column>username_clean</column>
<column>user_type</column>
<column>user_lastvisit</column>
<column>user_permissions</column>
<column>user_sig</column>
<row>
<value>2</value>
<value>myself</value>
<value>myself</value>
<value>0</value>
<value>19</value>
<value></value>
<value></value>
</row>
<row>
<value>3</value>
<value>poster</value>
<value>poster</value>
<value>0</value>
<value>18</value>
<value></value>
<value></value>
</row>
<row>
<value>4</value>
<value>replier</value>
<value>replier</value>
<value>0</value>
<value>17</value>
<value></value>
<value></value>
</row>
<row>
<value>5</value>
<value>team_member_normal</value>
<value>team_member_normal</value>
<value>0</value>
<value>16</value>
<value></value>
<value></value>
</row>
<row>
<value>6</value>
<value>team_member_hidden</value>
<value>team_member_hidden</value>
<value>0</value>
<value>15</value>
<value></value>
<value></value>
</row>
<row>
<value>7</value>
<value>friend</value>
<value>friend</value>
<value>0</value>
<value>14</value>
<value></value>
<value></value>
</row>
<row>
<value>8</value>
<value>test</value>
<value>test</value>
<value>0</value>
<value>13</value>
<value></value>
<value></value>
</row>
<row>
<value>9</value>
<value>test1</value>
<value>test1</value>
<value>0</value>
<value>12</value>
<value></value>
<value></value>
</row>
<row>
<value>10</value>
<value>test2</value>
<value>test2</value>
<value>0</value>
<value>11</value>
<value></value>
<value></value>
</row>
<row>
<value>11</value>
<value>test3</value>
<value>test3</value>
<value>0</value>
<value>10</value>
<value></value>
<value></value>
</row>
</table>
<table name="phpbb_user_group">
<column>user_id</column>
<column>group_id</column>
<column>user_pending</column>
<row>
<value>2</value>
<value>3</value>
<value>0</value>
</row>
<row>
<value>5</value>
<value>1</value>
<value>0</value>
</row>
<row>
<value>6</value>
<value>2</value>
<value>0</value>
</row>
</table>
<table name="phpbb_zebra">
<column>user_id</column>
<column>zebra_id</column>
<column>friend</column>
<column>foe</column>
<row>
<value>2</value>
<value>7</value>
<value>1</value>
<value>0</value>
</row>
</table>
</dataset>

View file

@ -0,0 +1,2 @@
imports:
- { resource: ../../../phpBB/config/default/container/services_mention.yml }

View file

@ -33,6 +33,7 @@ abstract class phpbb_tests_notification_base extends phpbb_database_test_case
'notification.type.disapprove_post', 'notification.type.disapprove_post',
'notification.type.disapprove_topic', 'notification.type.disapprove_topic',
'notification.type.forum', 'notification.type.forum',
'notification.type.mention',
'notification.type.pm', 'notification.type.pm',
'notification.type.post', 'notification.type.post',
'notification.type.post_in_queue', 'notification.type.post_in_queue',
@ -73,6 +74,7 @@ abstract class phpbb_tests_notification_base extends phpbb_database_test_case
'allow_topic_notify' => true, 'allow_topic_notify' => true,
'allow_forum_notify' => true, 'allow_forum_notify' => true,
'allow_board_notifications' => true, 'allow_board_notifications' => true,
'allow_mentions' => true,
)); ));
$lang_loader = new \phpbb\language\language_file_loader($phpbb_root_path, $phpEx); $lang_loader = new \phpbb\language\language_file_loader($phpbb_root_path, $phpEx);
$lang = new \phpbb\language\language($lang_loader); $lang = new \phpbb\language\language($lang_loader);
@ -105,6 +107,16 @@ abstract class phpbb_tests_notification_base extends phpbb_database_test_case
$phpbb_container->set('cache.driver', $cache_driver); $phpbb_container->set('cache.driver', $cache_driver);
$phpbb_container->set('cache', $cache); $phpbb_container->set('cache', $cache);
$phpbb_container->set('text_formatter.utils', new \phpbb\textformatter\s9e\utils()); $phpbb_container->set('text_formatter.utils', new \phpbb\textformatter\s9e\utils());
$phpbb_container->set(
'text_formatter.s9e.mention_helper',
new \phpbb\textformatter\s9e\mention_helper(
$this->db,
$auth,
$this->user,
$phpbb_root_path,
$phpEx
)
);
$phpbb_container->set('dispatcher', $this->phpbb_dispatcher); $phpbb_container->set('dispatcher', $this->phpbb_dispatcher);
$phpbb_container->setParameter('core.root_path', $phpbb_root_path); $phpbb_container->setParameter('core.root_path', $phpbb_root_path);
$phpbb_container->setParameter('core.php_ext', $phpEx); $phpbb_container->setParameter('core.php_ext', $phpEx);

View file

@ -44,6 +44,9 @@ services:
text_formatter.s9e.quote_helper: text_formatter.s9e.quote_helper:
synthetic: true synthetic: true
text_formatter.s9e.mention_helper:
synthetic: true
text_formatter.parser: text_formatter.parser:
synthetic: true synthetic: true

View file

@ -0,0 +1,187 @@
<?xml version="1.0" encoding="UTF-8" ?>
<dataset>
<table name="phpbb_groups">
<column>group_id</column>
<column>group_name</column>
<column>group_type</column>
<column>group_desc</column>
<row>
<value>1</value>
<value>Normal group</value>
<value>0</value>
<value></value>
</row>
<row>
<value>2</value>
<value>Hidden group</value>
<value>2</value>
<value></value>
</row>
</table>
<table name="phpbb_notifications">
<column>notification_id</column>
<column>notification_type_id</column>
<column>user_id</column>
<column>item_id</column>
<column>item_parent_id</column>
<column>notification_read</column>
<column>notification_data</column>
<row>
<value>1</value>
<value>1</value>
<value>5</value>
<value>1</value>
<value>1</value>
<value>0</value>
<value></value>
</row>
</table>
<table name="phpbb_notification_types">
<column>notification_type_id</column>
<column>notification_type_name</column>
<column>notification_type_enabled</column>
<row>
<value>1</value>
<value>notification.type.mention</value>
<value>1</value>
</row>
</table>
<table name="phpbb_posts">
<column>post_id</column>
<column>topic_id</column>
<column>forum_id</column>
<column>post_text</column>
<row>
<value>1</value>
<value>1</value>
<value>1</value>
<value></value>
</row>
</table>
<table name="phpbb_topics">
<column>topic_id</column>
<column>forum_id</column>
<row>
<value>1</value>
<value>1</value>
</row>
</table>
<table name="phpbb_users">
<column>user_id</column>
<column>username_clean</column>
<column>user_permissions</column>
<column>user_sig</column>
<row>
<value>2</value>
<value>poster</value>
<value></value>
<value></value>
</row>
<row>
<value>3</value>
<value>test</value>
<value></value>
<value></value>
</row>
<row>
<value>4</value>
<value>unauthorized</value>
<value></value>
<value></value>
</row>
<row>
<value>5</value>
<value>notified</value>
<value></value>
<value></value>
</row>
<row>
<value>6</value>
<value>disabled</value>
<value></value>
<value></value>
</row>
<row>
<value>7</value>
<value>default</value>
<value></value>
<value></value>
</row>
<row>
<value>8</value>
<value>member of normal group</value>
<value></value>
<value></value>
</row>
<row>
<value>9</value>
<value>member of hidden group</value>
<value></value>
<value></value>
</row>
</table>
<table name="phpbb_user_group">
<column>user_id</column>
<column>group_id</column>
<column>user_pending</column>
<row>
<value>8</value>
<value>1</value>
<value>0</value>
</row>
<row>
<value>9</value>
<value>2</value>
<value>0</value>
</row>
</table>
<table name="phpbb_user_notifications">
<column>item_type</column>
<column>item_id</column>
<column>user_id</column>
<column>method</column>
<column>notify</column>
<row>
<value>notification.type.mention</value>
<value>0</value>
<value>2</value>
<value>notification.method.board</value>
<value>1</value>
</row>
<row>
<value>notification.type.mention</value>
<value>0</value>
<value>3</value>
<value>notification.method.board</value>
<value>1</value>
</row>
<row>
<value>notification.type.mention</value>
<value>0</value>
<value>4</value>
<value>notification.method.board</value>
<value>1</value>
</row>
<row>
<value>notification.type.mention</value>
<value>0</value>
<value>5</value>
<value>notification.method.board</value>
<value>1</value>
</row>
<row>
<value>notification.type.mention</value>
<value>0</value>
<value>6</value>
<value>notification.method.board</value>
<value>0</value>
</row>
<row>
<value>notification.type.mention</value>
<value>0</value>
<value>8</value>
<value>notification.method.board</value>
<value>1</value>
</row>
</table>
</dataset>

View file

@ -91,6 +91,16 @@ class notification_method_email_test extends phpbb_tests_notification_base
$phpbb_container->setParameter('tables.user_notifications', 'phpbb_user_notifications'); $phpbb_container->setParameter('tables.user_notifications', 'phpbb_user_notifications');
$phpbb_container->setParameter('tables.notification_types', 'phpbb_notification_types'); $phpbb_container->setParameter('tables.notification_types', 'phpbb_notification_types');
$phpbb_container->setParameter('tables.notification_emails', 'phpbb_notification_emails'); $phpbb_container->setParameter('tables.notification_emails', 'phpbb_notification_emails');
$phpbb_container->set(
'text_formatter.s9e.mention_helper',
new \phpbb\textformatter\s9e\mention_helper(
$this->db,
$auth,
$this->user,
$phpbb_root_path,
$phpEx
)
);
$this->notification_method_email = $this->getMockBuilder('\phpbb\notification\method\email') $this->notification_method_email = $this->getMockBuilder('\phpbb\notification\method\email')
->setConstructorArgs([ ->setConstructorArgs([

View file

@ -59,6 +59,7 @@ class phpbb_notification_test extends phpbb_tests_notification_base
self::assertArrayHasKey('NOTIFICATION_GROUP_POSTING', $subscription_types); self::assertArrayHasKey('NOTIFICATION_GROUP_POSTING', $subscription_types);
self::assertArrayHasKey('notification.type.bookmark', $subscription_types['NOTIFICATION_GROUP_POSTING']); self::assertArrayHasKey('notification.type.bookmark', $subscription_types['NOTIFICATION_GROUP_POSTING']);
self::assertArrayHasKey('notification.type.mention', $subscription_types['NOTIFICATION_GROUP_POSTING']);
self::assertArrayHasKey('notification.type.post', $subscription_types['NOTIFICATION_GROUP_POSTING']); self::assertArrayHasKey('notification.type.post', $subscription_types['NOTIFICATION_GROUP_POSTING']);
self::assertArrayHasKey('notification.type.quote', $subscription_types['NOTIFICATION_GROUP_POSTING']); self::assertArrayHasKey('notification.type.quote', $subscription_types['NOTIFICATION_GROUP_POSTING']);
self::assertArrayHasKey('notification.type.topic', $subscription_types['NOTIFICATION_GROUP_POSTING']); self::assertArrayHasKey('notification.type.topic', $subscription_types['NOTIFICATION_GROUP_POSTING']);
@ -73,6 +74,7 @@ class phpbb_notification_test extends phpbb_tests_notification_base
{ {
$expected_subscriptions = array( $expected_subscriptions = array(
'notification.type.forum' => array('notification.method.board'), 'notification.type.forum' => array('notification.method.board'),
'notification.type.mention' => array('notification.method.board'),
'notification.type.post' => array('notification.method.board'), 'notification.type.post' => array('notification.method.board'),
'notification.type.topic' => array('notification.method.board'), 'notification.type.topic' => array('notification.method.board'),
'notification.type.quote' => array('notification.method.board'), 'notification.type.quote' => array('notification.method.board'),

View file

@ -70,6 +70,8 @@ abstract class phpbb_notification_submit_post_base extends phpbb_database_test_c
array('f_noapprove', 1, true), array('f_noapprove', 1, true),
array('f_postcount', 1, true), array('f_postcount', 1, true),
array('m_edit', 1, false), array('m_edit', 1, false),
array('f_mention', 1, true),
array('u_mention', 0, true),
))); )));
// Config // Config
@ -77,6 +79,7 @@ abstract class phpbb_notification_submit_post_base extends phpbb_database_test_c
'num_topics' => 1, 'num_topics' => 1,
'num_posts' => 1, 'num_posts' => 1,
'allow_board_notifications' => true, 'allow_board_notifications' => true,
'allow_mentions' => true,
)); ));
$cache_driver = new \phpbb\cache\driver\dummy(); $cache_driver = new \phpbb\cache\driver\dummy();
@ -132,6 +135,16 @@ abstract class phpbb_notification_submit_post_base extends phpbb_database_test_c
$phpbb_container->set('cache.driver', $cache_driver); $phpbb_container->set('cache.driver', $cache_driver);
$phpbb_container->set('cache', $cache); $phpbb_container->set('cache', $cache);
$phpbb_container->set('text_formatter.utils', new \phpbb\textformatter\s9e\utils()); $phpbb_container->set('text_formatter.utils', new \phpbb\textformatter\s9e\utils());
$phpbb_container->set(
'text_formatter.s9e.mention_helper',
new \phpbb\textformatter\s9e\mention_helper(
$this->db,
$auth,
$user,
$phpbb_root_path,
$phpEx
)
);
$phpbb_container->set('dispatcher', $phpbb_dispatcher); $phpbb_container->set('dispatcher', $phpbb_dispatcher);
$phpbb_container->set('storage.attachment', $storage); $phpbb_container->set('storage.attachment', $storage);
$phpbb_container->setParameter('core.root_path', $phpbb_root_path); $phpbb_container->setParameter('core.root_path', $phpbb_root_path);
@ -145,7 +158,7 @@ abstract class phpbb_notification_submit_post_base extends phpbb_database_test_c
$phpbb_container->compile(); $phpbb_container->compile();
// Notification Types // Notification Types
$notification_types = array('quote', 'bookmark', 'post', 'post_in_queue', 'topic', 'topic_in_queue', 'approve_topic', 'approve_post', 'forum'); $notification_types = array('quote', 'mention', 'bookmark', 'post', 'post_in_queue', 'topic', 'topic_in_queue', 'approve_topic', 'approve_post', 'forum');
$notification_types_array = array(); $notification_types_array = array();
foreach ($notification_types as $type) foreach ($notification_types as $type)
{ {

View file

@ -0,0 +1,129 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @license GNU General Public License, version 2 (GPL-2.0)
*
* For full copyright and license information, please see
* the docs/CREDITS.txt file.
*
*/
require_once dirname(__FILE__) . '/submit_post_base.php';
class phpbb_notification_submit_post_type_mention_test extends phpbb_notification_submit_post_base
{
protected $item_type = 'notification.type.mention';
public function setUp(): void
{
parent::setUp();
global $auth;
// Add additional permissions
$auth->expects($this->any())
->method('acl_get_list')
->with($this->anything(),
$this->stringContains('_'),
$this->greaterThan(0))
->will($this->returnValueMap(array(
array(
array(3, 4, 5, 6, 7, 8, 10),
'f_read',
1,
array(
1 => array(
'f_read' => array(3, 5, 6, 7, 8),
),
),
),
)));
$auth->expects($this->any())
->method('acl_gets')
->with('a_group', 'a_groupadd', 'a_groupdel')
->will($this->returnValue(false));
}
/**
* submit_post() Notifications test
*
* submit_post() $mode = 'reply'
* Notification item_type = 'mention'
*/
public function submit_post_data()
{
// The new mock container is needed because the data providers may be executed before phpunit call setUp()
$parser = $this->get_test_case_helpers()->set_s9e_services(new phpbb_mock_container_builder())->get('text_formatter.parser');
return array(
/**
* Normal post
*
* User => State description
* 2 => Poster, should NOT receive a notification
* 3 => mentioned, should receive a notification
* 4 => mentioned, but unauthed to read, should NOT receive a notification
* 5 => mentioned, but already notified, should STILL receive a new notification
* 6 => mentioned, but option disabled, should NOT receive a notification
* 7 => mentioned, option set to default, should receive a notification
* 8 => mentioned as a member of group 1, should receive a notification
*/
array(
array(
'message' => $parser->parse(implode(' ', array(
'[mention=u:2]poster[/mention] poster should not be notified',
'[mention=u:3]test[/mention] test should be notified',
'[mention=u:4]unauthorized[/mention] unauthorized to read, should not receive a notification',
'[mention=u:5]notified[/mention] already notified, should not receive a new notification',
'[mention=u:6]disabled[/mention] option disabled, should not receive a notification',
'[mention=u:7]default[/mention] option set to default, should receive a notification',
'[mention=g:1]normal group[/mention] group members of a normal group shoud receive a notification',
'[mention=g:2]hidden group[/mention] group members of a hidden group shoud not receive a notification from a non-member',
'[mention=u:10]doesn\'t exist[/mention] user does not exist, should not receive a notification',
))),
'bbcode_uid' => 'uid',
),
array(
array('user_id' => 5, 'item_id' => 1, 'item_parent_id' => 1),
),
array(
array('user_id' => 3, 'item_id' => 2, 'item_parent_id' => 1),
array('user_id' => 5, 'item_id' => 1, 'item_parent_id' => 1),
array('user_id' => 5, 'item_id' => 2, 'item_parent_id' => 1),
array('user_id' => 7, 'item_id' => 2, 'item_parent_id' => 1),
array('user_id' => 8, 'item_id' => 2, 'item_parent_id' => 1),
),
),
/**
* Unapproved post
*
* No new notifications
*/
array(
array(
'message' => $parser->parse(implode(' ', array(
'[mention=u:2]poster[/mention] poster should not be notified',
'[mention=u:3]test[/mention] test should be notified',
'[mention=u:4]unauthorized[/mention] unauthorized to read, should not receive a notification',
'[mention=u:5]notified[/mention] already notified, should not receive a new notification',
'[mention=u:6]disabled[/mention] option disabled, should not receive a notification',
'[mention=u:7]default[/mention] option set to default, should receive a notification',
'[mention=u:8]doesn\'t exist[/mention] user does not exist, should not receive a notification',
))),
'bbcode_uid' => 'uid',
'force_approved_state' => false,
),
array(
array('user_id' => 5, 'item_id' => 1, 'item_parent_id' => 1),
),
array(
array('user_id' => 5, 'item_id' => 1, 'item_parent_id' => 1),
),
),
);
}
}

View file

@ -579,6 +579,9 @@ class phpbb_test_case_helpers
} }
$user->add_lang('common'); $user->add_lang('common');
// Get an auth interface
$auth = ($container->has('auth')) ? $container->get('auth') : new \phpbb\auth\auth;
// Create and register a quote_helper // Create and register a quote_helper
$quote_helper = new \phpbb\textformatter\s9e\quote_helper( $quote_helper = new \phpbb\textformatter\s9e\quote_helper(
$container->get('user'), $container->get('user'),
@ -587,6 +590,16 @@ class phpbb_test_case_helpers
); );
$container->set('text_formatter.s9e.quote_helper', $quote_helper); $container->set('text_formatter.s9e.quote_helper', $quote_helper);
// Create and register a mention_helper
$mention_helper = new \phpbb\textformatter\s9e\mention_helper(
($container->has('dbal.conn')) ? $container->get('dbal.conn') : $db_driver,
$auth,
$container->get('user'),
$phpbb_root_path,
$phpEx
);
$container->set('text_formatter.s9e.mention_helper', $mention_helper);
// Create and register the text_formatter.s9e.parser service and its alias // Create and register the text_formatter.s9e.parser service and its alias
$parser = new \phpbb\textformatter\s9e\parser( $parser = new \phpbb\textformatter\s9e\parser(
$cache, $cache,
@ -607,8 +620,8 @@ class phpbb_test_case_helpers
); );
// Calls configured in services.yml // Calls configured in services.yml
$auth = ($container->has('auth')) ? $container->get('auth') : new \phpbb\auth\auth;
$renderer->configure_quote_helper($quote_helper); $renderer->configure_quote_helper($quote_helper);
$renderer->configure_mention_helper($mention_helper);
$renderer->configure_smilies_path($config, $path_helper); $renderer->configure_smilies_path($config, $path_helper);
$renderer->configure_user($user, $config, $auth); $renderer->configure_user($user, $config, $auth);

View file

@ -0,0 +1,116 @@
<?xml version="1.0" encoding="UTF-8" ?>
<dataset>
<table name="phpbb_groups">
<column>group_id</column>
<column>group_name</column>
<column>group_type</column>
<column>group_colour</column>
<column>group_desc</column>
<row>
<value>1</value>
<value>Normal group</value>
<value>0</value>
<value></value>
<value></value>
</row>
<row>
<value>2</value>
<value>Hidden group</value>
<value>2</value>
<value></value>
<value></value>
</row>
<row>
<value>3</value>
<value>Hidden group we are a member of</value>
<value>2</value>
<value></value>
<value></value>
</row>
</table>
<table name="phpbb_users">
<column>user_id</column>
<column>username</column>
<column>username_clean</column>
<column>user_type</column>
<column>user_lastvisit</column>
<column>user_colour</column>
<column>user_permissions</column>
<column>user_sig</column>
<row>
<value>2</value>
<value>myself</value>
<value>myself</value>
<value>0</value>
<value>0</value>
<value></value>
<value></value>
<value></value>
</row>
<row>
<value>3</value>
<value>test</value>
<value>test</value>
<value>0</value>
<value>0</value>
<value></value>
<value></value>
<value></value>
</row>
<row>
<value>4</value>
<value>group_member_normal</value>
<value>group_member_normal</value>
<value>0</value>
<value>0</value>
<value></value>
<value></value>
<value></value>
</row>
<row>
<value>5</value>
<value>group_member_hidden</value>
<value>group_member_hidden</value>
<value>0</value>
<value>0</value>
<value></value>
<value></value>
<value></value>
</row>
<row>
<value>6</value>
<value>group_member_visible</value>
<value>group_member_visible</value>
<value>0</value>
<value>0</value>
<value></value>
<value></value>
<value></value>
</row>
</table>
<table name="phpbb_user_group">
<column>user_id</column>
<column>group_id</column>
<column>user_pending</column>
<row>
<value>2</value>
<value>3</value>
<value>0</value>
</row>
<row>
<value>4</value>
<value>1</value>
<value>0</value>
</row>
<row>
<value>5</value>
<value>2</value>
<value>0</value>
</row>
<row>
<value>6</value>
<value>3</value>
<value>0</value>
</row>
</table>
</dataset>

View file

@ -0,0 +1,123 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @license GNU General Public License, version 2 (GPL-2.0)
*
* For full copyright and license information, please see
* the docs/CREDITS.txt file.
*
*/
use Symfony\Component\DependencyInjection\ContainerBuilder;
class mention_helper_test extends phpbb_database_test_case
{
protected $db, $container, $user, $auth;
/**
* @var \phpbb\textformatter\s9e\mention_helper
*/
protected $mention_helper;
public function getDataSet()
{
return $this->createXMLDataSet(dirname(__FILE__) . '/fixtures/mention.xml');
}
public function setUp(): void
{
parent::setUp();
global $auth, $db, $cache, $phpbb_container, $phpEx, $phpbb_root_path;
// Disable caching for this test class
$cache = null;
// Database
$this->db = $this->new_dbal();
$db = $this->db;
// Auth
$auth = $this->createMock('\phpbb\auth\auth');
$auth->expects($this->any())
->method('acl_gets')
->with('a_group', 'a_groupadd', 'a_groupdel')
->willReturn(false)
;
// Language
$lang = new \phpbb\language\language(new \phpbb\language\language_file_loader($phpbb_root_path, $phpEx));
// User
$user = $this->createMock('\phpbb\user', array(), array(
$lang,
'\phpbb\datetime'
));
$user->ip = '';
$user->data = array(
'user_id' => 2,
'username' => 'myself',
'is_registered' => true,
'user_colour' => '',
);
// Container
$phpbb_container = new phpbb_mock_container_builder();
$phpbb_container->set('dbal.conn', $db);
$phpbb_container->set('auth', $auth);
$phpbb_container->set('user', $user);
$this->get_test_case_helpers()->set_s9e_services($phpbb_container);
$this->mention_helper = $phpbb_container->get('text_formatter.s9e.mention_helper');
}
public function inject_metadata_data()
{
return [
[
'<r><MENTION user_id="3"><s>[mention=u:3]</s>test<e>[/mention]</e></MENTION></r>',
'mode=viewprofile&amp;u=3',
],
[
'<r><MENTION group_id="3"><s>[mention=g:3]</s>test<e>[/mention]</e></MENTION></r>',
'mode=group&amp;g=3',
],
];
}
/**
* @dataProvider inject_metadata_data
*/
public function test_inject_metadata($incoming_xml, $expected_profile_substring)
{
$result = $this->mention_helper->inject_metadata($incoming_xml);
$this->assertStringContainsString($expected_profile_substring, $result);
}
public function get_mentioned_user_ids_data()
{
return [
[
'<r><MENTION user_id="3"><s>[mention=u:3]</s>test<e>[/mention]</e></MENTION><MENTION user_id="4"><s>[mention=u:4]</s>test<e>[/mention]</e></MENTION><MENTION user_id="5"><s>[mention=u:5]</s>test<e>[/mention]</e></MENTION></r>',
[3, 4, 5],
],
[
'<r><MENTION group_id="1"><s>[mention=g:1]</s>test<e>[/mention]</e></MENTION><MENTION group_id="2"><s>[mention=g:2]</s>test<e>[/mention]</e></MENTION><MENTION group_id="3"><s>[mention=g:3]</s>test<e>[/mention]</e></MENTION></r>',
[4, 2, 6],
],
];
}
/**
* @dataProvider get_mentioned_user_ids_data
*/
public function test_get_mentioned_user_ids($incoming_xml, $expected_result)
{
$this->assertSame($expected_result, $this->mention_helper->get_mentioned_user_ids($incoming_xml));
}
}