Commit b7606a0a authored by Tobias Zeuch's avatar Tobias Zeuch Committed by Tobias Zeuch

New plugin as user-message system to multiple recipients

Bug 1246702: This is a plugin that replaces the current user notifications with a
message system for multiple recipients.

- The plugin redirects calls to sendmessage.php and inbox.php.
- New Usermessages are stored in a separate table from the userrelation, where there
  is also kept track of whether the notification has been read or deleted.
- Messages aren't deleted from the database, until all participantas (sender and all
  recievers) delete it.
- The plugin comes with an outbox that shows messages that have been sent by the
  logged in user.
- Both inbox and outbox show new messages and old messages alike.

Note that the code is at pre-release state:
The message-count per user is kept up to date with database-triggers, as is done for
the old system.
For this new system, you might not want to install it on a productive system or it
might mess up your message count when you deactivate the plugin.

Change-Id: I586af86266b5fb0e2d676bd1401b7d879d0122ef
Signed-off-by: default avatarTobias Zeuch <tobias.zeuch@kic-innoenergy.com>
parent a789090c
<?php
/**
*
* @package mahara
* @subpackage artefact-multirecipientnotification
* @author David Ballhausen, Tobias Zeuch
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL version 3 or later
* @copyright For copyright information on Mahara, please see the README file distributed with this software.
*
*/
/**
* artefacttype - plugin class. Used for corresponding with the Mahara API and containing all needed functionality for Multirecipientnotification function
*/
class ArtefactTypeMultiRecipientNotification extends ArtefactType {
/**
* API-Function. This Artefact does not provide any blocks or items to render
* @see ArtefactType::render_self()
*/
public function render_self($options) {
return get_string('nothingtorender', 'artefact.multirecipientnotification');
}
/**
* API-Function. Get Plugin-Artefacttype Icon. Here (No icon provided)
* @param string $options
*/
public static function get_icon($options=null) {
}
/**
* API-Function. Is it possible to use more Artefacttypes than one of this type? Here: No!
*/
public static function is_singular() {
return true;
}
/**
* API-Function: Get plugin-Artefacttype specific links: Here: None
* @param integer $id
*/
public static function get_links($id) {
}
} // ArtefactTypeMultiRecipientNotification
<?php
/**
*
* @package mahara
* @subpackage artefact-multirecipientnotification
* @author David Ballhausen, Tobias Zeuch
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL version 3 or later
* @copyright For copyright information on Mahara, please see the README file distributed with this software.
*
*/
/**
* artefacttype - plugin class. Used for corresponding with the Mahara API and containing all needed functionality for Multirecipientnotification function
*/
class ArtefactTypeOutbox extends ArtefactType {
/**
* API-Function. This Artefact does not provide any blocks or items to render
* @see ArtefactType::render_self()
*/
public function render_self($options) {
return get_string('nothingtorender', 'artefact.multirecipientnotification');
}
/**
* API-Function. Get Plugin-Artefacttype Icon. Here (No icon provided)
* @param string $options
*/
public static function get_icon($options=null) {
}
/**
* API-Function. Is it possible to use more Artefacttypes than one of this type? Here: No!
*/
public static function is_singular() {
return true;
}
/**
* API-Function: Get plugin-Artefacttype specific links: Here: None
* @param integer $id
*/
public static function get_links($id) {
}
} // ArtefactTypeOutbox
# sql script for deleting the plugin from the database, mainly intended
# for developing/testing scenarios when you want to remove/reinstall the
# script without reinstalling the complete database
drop table IF EXISTS `multirecipientnotification_internal_recipients`;
drop table IF EXISTS `multirecipientnotification_internal_activity`;
DELETE FROM `artefact_cron` WHERE plugin='notificationoutbox';
DELETE FROM `artefact_event_subscription` WHERE plugin='notificationoutbox';
DELETE FROM `artefact_config` WHERE `plugin`='notificationoutbox';
delete from `artefact_installed_type` where plugin='notificationoutbox';
delete from `artefact_installed` where name='notificationoutbox';
drop table IF EXISTS `artefact_multirecipient_userrelation`;
drop table IF EXISTS `artefact_multirecipient_notification`;
DELETE FROM `artefact_cron` WHERE plugin='multirecipientnotification';
DELETE FROM `artefact_event_subscription` WHERE plugin='multirecipientnotification';
DELETE FROM `artefact_config` WHERE `plugin`='multirecipientnotification';
delete from `artefact_installed_type` where plugin='multirecipientnotification';
delete from `artefact_installed` where name='multirecipientnotification';
<?xml version="1.0" encoding="UTF-8" ?>
<XMLDB PATH="lib/db" VERSION="2013010705" COMMENT="XMLDB file for Multirecipientnotification mahara tables"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../lib/xmldb/xmldb.xsd"
>
<TABLES>
<TABLE NAME="artefact_multirecipient_notification" COMMENT="notifications to (potentially) multiple recipients ">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true" COMMENT="Primary Key"/>
<FIELD NAME="type" TYPE="int" LENGTH="10" NOTNULL="false" COMMENT="The type of message, e.g. notification"/>
<FIELD NAME="ctime" TYPE="datetime" LENGTH="10" NOTNULL="false" COMMENT="the date when the message was sent"/>
<FIELD NAME="subject" TYPE="text" NOTNULL="false" COMMENT="the subject of the message"/>
<FIELD NAME="message" TYPE="text" NOTNULL="false" COMMENT="the content of the message"/>
<FIELD NAME="parent" TYPE="int" LENGTH="10" NOTNULL="false" COMMENT="link to the predecessor/reply-to of the message, if any"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="typefk" TYPE="foreign" FIELDS="type" REFTABLE="activity_type" REFFIELDS="id"/>
<KEY NAME="parentfk" TYPE="foreign" FIELDS="parent" REFTABLE="artefact_multirecipient_notification" REFFIELDS="id"/>
</KEYS>
<INDEXES>
<INDEX NAME="ctimeix" FIELDS="ctime" UNIQUE="false"/>
</INDEXES>
</TABLE>
<TABLE NAME="artefact_multirecipient_userrelation" COMMENT="notifications to (potentially) multiple recipients ">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true" COMMENT="Primary Key"/>
<FIELD NAME="role" TYPE="char" LENGTH="9" ENUM="true" ENUMVALUES="'sender','recipient'" NOTNULL="true" DEFAULT="recipient" COMMENT="the role of user-relation"/>
<FIELD NAME="usr" TYPE="int" LENGTH="10" NOTNULL="true" COMMENT="relation to the user"/>
<FIELD NAME="notification" TYPE="int" LENGTH="10" NOTNULL="true" COMMENT="the message-relation"/>
<FIELD NAME="read" TYPE="char" LENGTH="1" ENUM="true" ENUMVALUES="'0','1'" DEFAULT="0" NOTNULL="true" COMMENT="boolean value indicating if the message has been read"/>
<FIELD NAME="deleted" TYPE="char" LENGTH="1" ENUM="true" ENUMVALUES="'0','1'" DEFAULT="0" NOTNULL="true" COMMENT="boolean value indicating if the message has been deleted"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="notificationfk" TYPE="foreign" FIELDS="notification" REFTABLE="artefact_multirecipient_notification" REFFIELDS="id"/>
<KEY NAME="usrfk" TYPE="foreign" FIELDS="usr" REFTABLE="usr" REFFIELDS="id"/>
</KEYS>
</TABLE>
</TABLES>
</XMLDB>
\ No newline at end of file
<?php
/**
*
* @package mahara
* @subpackage artefact-multirecipientnotification
* @author David Ballhausen, Tobias Zeuch
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL version 3 or later
* @copyright For copyright information on Mahara, please see the README file distributed with this software.
*
*/
define('INTERNAL', 1);
define('MENUITEM', 'inbox');
define('SECTION_PLUGINTYPE', 'artefact');
define('SECTION_PLUGINNAME', 'multirecipientnotification');
define('SECTION_PAGE', 'inbox');
require(dirname(dirname(dirname(__FILE__))) . '/init.php');
require_once('pieforms/pieform.php');
safe_require('artefact', 'multirecipientnotification');
global $USER;
global $THEME;
// Add new title
define('TITLE', get_string('notification', 'artefact.multirecipientnotification'));
// Make sure the unread message count is up to date whenever the
// user hits this page.
$USER->reload_background_fields();
$installedtypes = get_records_assoc(
'activity_type', '', '',
'plugintype,pluginname,name',
'name,admin,plugintype,pluginname'
);
$options = array(
'all' => get_string('alltypes', 'activity'),
);
foreach ($installedtypes as &$t) {
if (!$t->admin) {
$section = $t->pluginname ? "{$t->plugintype}.{$t->pluginname}" : 'activity';
$options[$t->name] = get_string('type' . $t->name, $section);
}
}
if ($USER->get('admin')) {
$options['adminmessages'] = get_string('typeadminmessages', 'activity');
}
$type = param_variable('type', 'all');
if ($type == '') {
$type = 'all';
}
if (!isset($options[$type])) {
// Comma-separated list; filter out anything that's not an installed type
$type = join(',', array_unique(array_filter(
split(',', $type),
function ($a) {global $installedtypes; return isset($installedtypes[$a]);}
)));
}
require_once(get_config('docroot') . 'lib/activity.php');
// add the new function for outgoing notification
// use the new function to show from - and to user
$activitylist = activitylistin_html($type);
$star = json_encode($THEME->get_url('images/star.png'));
$readicon = json_encode($THEME->get_url('images/readusermessage.png'));
$strread = json_encode(get_string('read', 'activity'));
$javascript = <<<JAVASCRIPT
function markread(form, action) {
var e = getElementsByTagAndClassName(null,'tocheck'+action,form);
var pd = {};
for (cb in e) {
if (e[cb].checked == true) {
pd[e[cb].name] = 1;
}
}
if (action == 'read') {
pd['markasread'] = 1;
}
else if (action == 'del') {
// If deleting, also pass the ids of unread messages, so we can update
// the unread message count as accurately as possible.
forEach(getElementsByTagAndClassName('input', 'tocheckread', form), function(cb) {
pd[cb.name] = 0;
});
pd['delete'] = 1;
}
if (paginatorData) {
for (p in paginatorData.params) {
pd[p] = paginatorData.params[p];
}
}
sendjsonrequest('indexin.json.php', pd, 'GET', function (data) {
paginator.updateResults(data);
updateUnreadCount(data);
});
}
function showHideMessage(id, table) {
var message = $('message-' + table + '-' + id);
if (!message) {
return;
}
jQuery(message).parents("tr.unread").removeClass("unread")
if (hasElementClass(message, 'hidden')) {
var unread = getFirstElementByTagAndClassName(
'input', 'tocheckread', message.parentNode.parentNode
);
var unreadicon = getFirstElementByTagAndClassName(
'img', 'unreadmessage', message.parentNode.parentNode
);
if (unread) {
var pd = {'readone':id, 'table':table};
sendjsonrequest('indexin.json.php', pd, 'GET', function(data) {
swapDOM(unread, IMG({'src' : {$star}, 'alt' : {$strread}}));
if (unreadicon) {
swapDOM(unreadicon, IMG({'src' : {$readicon}, 'alt' : getNodeAttribute(unreadicon, 'alt') + ' - ' + {$strread}}));
};
updateUnreadCount(data);
});
}
removeElementClass(message, 'hidden');
}
else {
addElementClass(message, 'hidden');
}
}
function changeactivitytype() {
var delallform = document.forms['delete_all_notifications'];
delallform.elements['type'].value = this.options[this.selectedIndex].value;
var params = {'type': this.options[this.selectedIndex].value};
sendjsonrequest('indexin.json.php', params, 'GET', function(data) {
paginator.updateResults(data);
});
}
// We want the paginator to tell us when a page gets changed.
// @todo: remember checked/unchecked state when changing pages
function PaginatorData() {
var self = this;
var params = {};
this.pageChanged = function(data) {
self.params = {
'offset': data.offset,
'limit': data.limit,
'type': data.type
}
}
paginatorProxy.addObserver(self);
connect(self, 'pagechanged', self.pageChanged);
}
var paginator;
var paginatorData = new PaginatorData();
addLoadEvent(function () {
paginator = {$activitylist['pagination_js']}
connect('notifications_type', 'onchange', changeactivitytype);
});
JAVASCRIPT;
$deleteall = pieform(array(
'name' => 'delete_all_notifications',
'method' => 'post',
'plugintype' => 'core',
'pluginname' => 'account',
'elements' => array(
'type' => array(
'type' => 'hidden',
'value' => $type,
),
'submit' => array(
'type' => 'submit',
'value' => get_string('deleteallnotifications', 'activity'),
'confirm' => get_string('reallydeleteallnotifications', 'activity'),
),
),
));
function delete_all_notifications_submit() {
global $USER, $SESSION;
$userid = $USER->get('id');
$type = param_variable('type', 'all');
db_begin();
// delete multirecipient-message separately
$count = 0;
if (in_array($type, array('all', 'usermessage'))) {
if ($type !== 'all') {
$at = activity_locate_typerecord($type);
$typecond = 'AND {msg}.{type} = ' . $at->id;
}
else {
$typecond = '';
}
$query = 'SELECT msg.id AS id
FROM {artefact_multirecipient_notification} as msg
INNER JOIN {artefact_multirecipient_userrelation} as rel
ON msg.id = rel.notification
AND rel.usr = ?
AND rel.role = ?
AND rel.deleted = \'0\'
' . $typecond;
$result = get_records_sql_array($query, array($userid, 'recipient'));
$msgids = array();
if (is_array($result)) {
foreach ($result as $record) {
$msgids[] = $record->id;
}
delete_messages_mr($msgids, $userid);
}
$count = count($msgids);
}
$typesql = '';
if ($type != 'all') {
// Treat as comma-separated list of activity type names
$types = split(',', preg_replace('/[^a-z,]+/', '', $type));
if ($types) {
$typesql = ' at.name IN (' . join(',', array_map('db_quote', $types)) . ')';
if (in_array('adminmessages', $types)) {
$typesql = '(' . $typesql . ' OR at.admin = 1)';
}
$typesql = ' AND ' . $typesql;
}
}
// changed to meesage from usr
$fromexpression = "FROM {notification_internal_activity} a
INNER JOIN {activity_type} at ON a.type = at.id
WHERE a.usr = ? $typesql";
$values = array($userid);
$records = get_records_sql_array('SELECT a.id ' . $fromexpression, $values);
if ($records) {
$count += sizeof($records);
$ids = array();
foreach ($records as $row) {
$ids[] = $row->id;
}
// Remove parent pointers to messages we're about to delete
execute_sql('
UPDATE {notification_internal_activity}
SET parent = NULL
WHERE parent IN (' . join(',', array_map('db_quote', $ids)) . ')'
);
// delete
execute_sql('
DELETE FROM {notification_internal_activity}
WHERE id IN (' . join(',', array_map('db_quote', $ids)) . ')'
);
// The update_unread_delete db trigger on notification_internal_activity
// will update the unread column on the usr table.
}
db_commit();
$SESSION->add_ok_msg(get_string('deletednotifications', 'activity', $count));
redirect(get_config('wwwroot') . 'artefact/multirecipientnotification/inbox.php?type=' . $type);
}
$extrastylesheets = $THEME->get_url('style.css', false, 'artefact/multirecipientnotification');
$smarty = smarty(array('paginator'),
array('<link rel="stylesheet" type="text/css" href="' . $extrastylesheets . '">')
);
$smarty->assign('options', $options);
$smarty->assign('type', $type);
$smarty->assign('INLINEJAVASCRIPT', $javascript);
// Adding the links to out- and inbox
$smarty->assign('PAGEHEADING', TITLE);
// Add urls and titles
$pages = array();
$pages[0]["url"] = "artefact/multirecipientnotification/inbox.php";
$pages[0]["title"] = get_string('labelinbox', 'artefact.multirecipientnotification');
$pages[0]["selected"] = 1;
$pages[1]["url"] = "artefact/multirecipientnotification/outbox.php";
$pages[1]["title"] = get_string('labeloutbox', 'artefact.multirecipientnotification');
// show urls and titles
$smarty->assign('SUBPAGENAV', $pages);
$smarty->assign('deleteall', $deleteall);
$smarty->assign('activitylist', $activitylist);
// Changed to new tpl
$smarty->display('artefact:multirecipientnotification:indexin.tpl');
<?php
/**
*
* @package mahara
* @subpackage artefact-multirecipientnotification
* @author David Ballhausen, Tobias Zeuch
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL version 3 or later
* @copyright For copyright information on Mahara, please see the README file distributed with this software.
*
*/
define('INTERNAL', 1);
define('JSON', 1);
require(dirname(dirname(dirname(__FILE__))) . '/init.php');
safe_require('artefact', 'multirecipientnotification');
global $USER;
global $THEME;
$readone = param_integer('readone', 0);
$table = param_alphanumext('table', 'notification_internal_activity');
$markasread = param_integer('markasread', 0);
$delete = param_integer('delete', 0);
if ($readone) {
if ('notification_internal_activity' === $table) {
set_field($table, 'read', 1, 'id', $readone, 'usr', $USER->get('id'));
}
else if ('artefact_multirecipient_notification' === $table) {
mark_as_read_mr(array($readone), $USER->get('id'));
}
$unread = $USER->add_unread(-1);
$data = array(
'newunreadcount' => $unread,
'newimage' => $THEME->get_url($unread ? 'images/newmail.png' : 'images/message.png'),
);
json_reply(false, array('data' => $data));
}
require_once(get_config('libroot') . 'activity.php');
$type = param_variable('type', 'all');
$limit = param_integer('limit', 10);
$offset = param_integer('offset', 0);
$message = false;
if ($markasread) {
$ids = array();
$m = array();
foreach ($_GET as $k => $v) {
if (preg_match('/^unread\-([a-zA-Z_]+)\-(\d+)$/',$k,$m)) {
$table = $m[1];
$ids[$table][] = $m[2];
}
}
foreach ($ids as $table => $idspertable) {
if ($idspertable) {
if ('artefact_multirecipient_notification' === $table) {
$table = 'artefact_multirecipient_userrelation';
$column = 'notification';
}
else {
$column = 'id';
}
set_field_select(
$table, 'read', '1',
$column . ' IN (' . join(',', array_map('db_quote', $idspertable)) . ') AND usr = ?',
array($USER->get('id'))
);
$newunread = $USER->add_unread(-count($idspertable));
}
}
$message = get_string('markedasread', 'activity');
}
else if ($delete) {
$ids = array();
$deleteunread = 0; // Remember the number of unread messages being deleted
foreach ($_GET as $k => $v) {
if (preg_match('/^delete\-([a-zA-Z_]+)\-(\d+)$/',$k,$m)) {
$table = $m[1];
$ids[$table][] = $m[2];
if (isset($_GET['unread-' . $table . '-' . $m[2]])) {
$deleteunread++;
}
}
}
db_begin();
$countdeleted = 0;
foreach ($ids as $table => $idspertable) {
if ('artefact_multirecipient_notification' === $table) {
delete_messages_mr($idspertable, $USER->get('id'));
}
else if ('notification_internal_activity' === $table) {
$strids = join(',', array_map('db_quote', $idspertable));
$userid = $USER->get('id');
// Remove parent pointers to messages we're about to delete
// Use temp table in subselect for Mysql compat.
execute_sql("
UPDATE {notification_internal_activity}
SET parent = NULL
WHERE parent IN (
SELECT id FROM (
SELECT id FROM {notification_internal_activity} WHERE id IN ($strids) AND usr = ?
) AS temp
)",
array($userid)
);
delete_records_select(
'notification_internal_activity',
"id IN ($strids) AND usr = ?",
array($userid)
);
if ($deleteunread) {
$newunread = $USER->add_unread(-$deleteunread);
}
}
$countdeleted += count($idspertable);
}
db_commit();
$message = get_string('deletednotifications', 'activity', $countdeleted);
}
// ------------ Change ------------
// use the new function to show from - and to user
$newhtml = activitylistin_html($type, $limit, $offset);
// --------- End Change -----------
if (isset($newunread)) {
$newhtml['newunreadcount'] = $newunread;
$newhtml['newimage'] = $THEME->get_url($newunread ? 'images/newmail.png' : 'images/message.png');
}
json_reply(false, (object) array('message' => $message, 'data' => $newhtml));
<?php
/**
*
* @package mahara
* @subpackage artefact-multirecipientnotification
* @author David Ballhausen, Tobias Zeuch
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL version 3 or later
* @copyright For copyright information on Mahara, please see the README file distributed with this software.
*
*/