Commit 6fe99d5f authored by Tobias Zeuch's avatar Tobias Zeuch Committed by Robert Lyon
Browse files

New watchlistnotification Plugin (Bug 1041228)



Introducing a new plugin watchlistnotification that responds to the
events saveartefact, blockinstancecommit and deleteblockinstance. It
stores the changed view and the blockinstance in a table watchlist_queue
and checks via cron if there were any changes on a view and if for that
view the last change has happened some time ago (the minutes are stored
in config under watchlistnotification_delay, the default is 20min).

If so, a message is generated that informs the watchlist recipient about
which view and which block-instances on this view have been touched
(added or changed).

As there is no way to disable the built-in/old watchlist-notification-
system, this is disabled in the mahara-core code, that is,
artefact/lib.php and lib/view.php

Change-Id: I039c5285cdd1b09ed9eb38a647e0c1510c3cabb9
Signed-off-by: default avatarTobias Zeuch <tobias.zeuch@kit.edu>
parent aae3189c
......@@ -667,6 +667,13 @@ $siteoptionform = array(
'defaultvalue' => get_config('sitefilesaccess'),
'disabled' => in_array('sitefilesaccess', $OVERRIDDEN),
),
'watchlistnotification_delay' => array(
'type' => 'text',
'title' => get_string('watchlistdelaytitle', 'admin'),
'description' => get_string('watchlistdelaydescription', 'admin'),
'defaultvalue' => get_config('watchlistnotification_delay'),
'disabled' => in_array('watchlistnotification_delay', $OVERRIDDEN),
),
),
),
'loggingsettings' => array(
......@@ -728,7 +735,7 @@ function siteoptions_submit(Pieform $form, $values) {
'proxyaddress', 'proxyauthmodel', 'proxyauthcredentials', 'smtphosts', 'smtpport', 'smtpuser', 'smtppass', 'smtpsecure',
'noreplyaddress', 'defaultnotificationmethod', 'homepageinfo', 'showonlineuserssideblock', 'onlineuserssideblockmaxusers',
'registerterms', 'licensemetadata', 'licenseallowcustom', 'allowmobileuploads', 'creategroups', 'createpublicgroups', 'allowgroupcategories', 'wysiwyg',
'staffreports', 'staffstats', 'userscandisabledevicedetection',
'staffreports', 'staffstats', 'userscandisabledevicedetection', 'watchlistnotification_delay',
'masqueradingreasonrequired', 'masqueradingnotified', 'searchuserspublic',
'eventloglevel', 'eventlogexpiry', 'sitefilesaccess',
);
......
......@@ -1580,13 +1580,8 @@ function artefact_instance_from_type($artefact_type, $user_id=null) {
}
function artefact_watchlist_notification($artefactids) {
// gets all the views containing this artefact or a parent of this artefact and creates a watchlist activity for each view
if ($views = get_column_sql('SELECT DISTINCT "view" FROM {view_artefact} WHERE artefact IN (' . implode(',', array_merge(array_keys(artefact_get_parents_for_cache($artefactids)), array_map('intval', $artefactids))) . ')')) {
require_once('activity.php');
foreach ($views as $view) {
activity_occurred('watchlist', (object)array('view' => $view));
}
}
// the notification will be handled by plugin watchlistnotification
// responding to the event saveartefact
}
function artefact_get_descendants($new) {
......
......@@ -1112,6 +1112,10 @@ class BlockInstance {
$this->dirty = false;
return;
}
//Propagate the deletion of the block
handle_event('deleteblockinstance', $this);
db_begin();
safe_require('blocktype', $this->get('blocktype'));
call_static_method(generate_class_name('blocktype', $this->get('blocktype')), 'delete_instance', $this);
......
......@@ -56,6 +56,8 @@ $string['stopmonitoringfailed'] = 'Failed to stop monitoring';
$string['newwatchlistmessage'] = 'New activity on your watchlist';
$string['newwatchlistmessageview1'] = 'The page "%s" belonging to %s has been changed';
$string['blockinstancenotification'] = 'The block "%s" has been added or changed';
$string['nonamegiven'] = 'no name given';
$string['newviewsubject'] = 'New page created';
$string['newviewmessage'] = '%s has created a new page "%s"';
......
......@@ -422,6 +422,8 @@ $string['eventloglevelmasq'] = 'Masquerading';
$string['eventloglevelall'] = 'All';
$string['sitefilesaccess'] = 'Access to site files';
$string['sitefilesaccessdescription'] = 'If checked, logged-in users will have access to site files in subfolders. By default, only files in the top level directory are accessible to them.';
$string['watchlistdelaydescription'] = 'The delay in minutes between sending emails regarding watchlist changes.';
$string['watchlistdelaytitle'] = 'Watchlist notification delay';
// Site content
$string['about'] = 'About';
......
......@@ -224,6 +224,232 @@ function activity_process_queue() {
}
}
/**
* event-listener is called when an artefact is changed or a block instance
* is commited. Saves the view, the block instance, user and time into the
* database
*
* @global User $USER
* @param string $event
* @param object $eventdata
*/
function watchlist_record_changes($event){
global $USER;
// don't catch root's changes, especially not when installing...
if ($USER->get('id') <= 0) {
return;
}
if ($event instanceof BlockInstance) {
if (record_exists('usr_watchlist_view', 'view', $event->get('view'))) {
$whereobj = new stdClass();
$whereobj->block = $event->get('id');
$whereobj->view = $event->get('view');
$whereobj->usr = $USER->get('id');
$dataobj = clone $whereobj;
$dataobj->changed_on = date('Y-m-d H:i:s');
ensure_record_exists('watchlist_queue', $whereobj, $dataobj);
}
}
else if ($event instanceof ArtefactType) {
$blockid = $event->get('id');
$getcolumnquery = '
SELECT DISTINCT
"view", "block"
FROM
{view_artefact}
WHERE
artefact =' . $blockid;
$relations = get_records_sql_array($getcolumnquery, array());
// fix unnecessary type-inconsistency of get_records_sql_array
if (false === $relations) {
$relations = array();
}
foreach ($relations as $rel) {
if (!record_exists('usr_watchlist_view', 'view', $rel->view)) {
continue;
}
$whereobj = new stdClass();
$whereobj->block = $rel->block;
$whereobj->view = $rel->view;
$whereobj->usr = $USER->get('id');
$dataobj = clone $whereobj;
$dataobj->changed_on = date('Y-m-d H:i:s');
ensure_record_exists('watchlist_queue', $whereobj, $dataobj);
}
}
else if (!is_object($event) && !empty($event['id'])) {
$viewid = $event['id'];
if (record_exists('usr_watchlist_view', 'view', $viewid)) {
$whereobj = new stdClass();
$whereobj->view = $viewid;
$whereobj->usr = $USER->get('id');
$whereobj->block = null;
$dataobj = clone $whereobj;
$dataobj->changed_on = date('Y-m-d H:i:s');
ensure_record_exists('watchlist_queue', $whereobj, $dataobj);
}
}
else {
return;
}
}
/**
* is triggered when a blockinstance is deleted. Deletes all watchlist_queue
* entries that refer to this blockinstance
*
* @param BlockInstance $blockinstance
*/
function watchlist_block_deleted(BlockInstance $block) {
global $USER;
// don't catch root's changes, especially not when installing...
if ($USER->get('id') <= 0) {
return;
}
delete_records('watchlist_queue', 'block', $block->get('id'));
if (record_exists('usr_watchlist_view', 'view', $block->get('view'))) {
$whereobj = new stdClass();
$whereobj->view = $block->get('view');
$whereobj->block = null;
$whereobj->usr = $USER->get('id');
$dataobj = clone $whereobj;
$dataobj->changed_on = date('Y-m-d H:i:s');
ensure_record_exists('watchlist_queue', $whereobj, $dataobj);
}
}
/**
* is called by the cron-job to process the notifications stored into
* watchlist_queue.
*/
function watchlist_process_notifications() {
$delayMin = get_config('watchlistnotification_delay');
$comparetime = time() - $delayMin * 60;
$sql = "SELECT usr, view, MAX(changed_on) AS time
FROM {watchlist_queue}
GROUP BY usr, view";
$results = get_records_sql_array($sql, array());
if (false === $results) {
return;
}
foreach ($results as $viewuserdaterow) {
if ($viewuserdaterow->time > date('Y-m-d H:i:s', $comparetime)) {
continue;
}
// don't send a notification if only blockinstances are referenced
// that were deleted (block exists but corresponding
// block_instance doesn't)
$sendnotification = false;
$blockinstance_ids = get_column('watchlist_queue', 'block', 'usr', $viewuserdaterow->usr, 'view', $viewuserdaterow->view);
if (is_array($blockinstance_ids)) {
$blockinstance_ids = array_unique($blockinstance_ids);
}
$viewuserdaterow->blocktitles = array();
// need to check if view has an owner, group or institution
$view = get_record('view', 'id', $viewuserdaterow->view);
if (empty($view->owner) && empty($view->group) && empty($view->institution)) {
continue;
}
// ignore root pages, owner = 0, this account is not meant to produce content
if (isset($view->owner) && empty($view->owner)) {
continue;
}
foreach ($blockinstance_ids as $blockinstance_id) {
if (empty($blockinstance_id)) {
// if no blockinstance is given, assume that the form itself
// was changed, e.g. the theme, or a block was removed
$sendnotification = true;
continue;
}
require_once(get_config('docroot') . 'blocktype/lib.php');
try {
$block = new BlockInstance($blockinstance_id);
}
catch (BlockInstanceNotFoundException $exc) {
// maybe the block was deleted
continue;
}
$blocktype = $block->get('blocktype');
$title = '';
// try to get title rendered by plugin-class
safe_require('blocktype', $blocktype);
if (class_exists(generate_class_name('blocktype', $blocktype))) {
$title = $block->get_title();
}
else {
log_warn('class for blocktype could not be loaded: ' . $blocktype);
$title = $block->get('title');
}
// if no title was given to the blockinstance, try to get one
// from the artefact
if (empty($title)) {
$configdata = $block->get('configdata');
if (array_key_exists('artefactid', $configdata)) {
try {
$artefact = $block->get_artefact_instance($configdata['artefactid']);
$title = $artefact->get('title');
}
catch(Exception $exc) {
log_warn('couldn\'t identify title of blockinstance ' .
$block->get('id') . $exc->getMessage());
}
}
}
// still no title, maybe the default-name for the blocktype
if (empty($title)) {
$title = get_string('title', 'blocktype.' . $blocktype);
}
// no title could be retrieved, so let's tell the user at least
// what type of block was changed
if (empty($title)) {
$title = '[' . $blocktype . '] (' .
get_string('nonamegiven', 'activity') . ')';
}
$viewuserdaterow->blocktitles[] = $title;
$sendnotification = true;
}
// only send notification if there is something to talk about (don't
// send notification for example when new blockelement was aborted)
if ($sendnotification) {
try{
$watchlistnotification = new ActivityTypeWatchlistnotification($viewuserdaterow, false);
$watchlistnotification->notify_users();
}
catch (ViewNotFoundException $exc) {
// Seems like the view has been deleted, don't do anything
}
catch (SystemException $exc) {
// if the view that was changed doesn't have an owner
}
}
delete_records('watchlist_queue', 'usr', $viewuserdaterow->usr, 'view', $viewuserdaterow->view);
}
}
function activity_get_viewaccess_users($view, $owner, $type) {
$type = activity_locate_typerecord($type);
$sql = "SELECT userid, u.*, p.method, ap.value AS lang
......@@ -935,6 +1161,62 @@ class ActivityTypeWatchlist extends ActivityType {
}
}
/**
* extending ActivityTypeWatchlist to reuse the funcinality and structure
*/
class ActivityTypeWatchlistnotification extends ActivityTypeWatchlist{
protected $view;
protected $viewinfo;
protected $blocktitles = array();
protected $usr;
/**
* @param array $data Parameters:
* - view (int)
* - blocktitles (array: int)
* - usr (int)
*/
public function __construct($data, $cron) {
parent::__construct($data, $cron);
$this->blocktitles = $data->blocktitles;
$this->usr = $data->usr;
$this->viewinfo = new View($this->view);
}
/**
* override function get_message to add information about the changed
* blockinstances
*
* @param type $user
* @return type
*/
public function get_message($user) {
$message = get_string_from_language($user->lang, 'newwatchlistmessageview1', 'activity',
$this->viewinfo->get('title'), display_name($this->usr, $user));
try {
foreach ($this->blocktitles as $blocktitle) {
$message .= "\n" . get_string_from_language($user->lang, 'blockinstancenotification', 'activity', $blocktitle);
}
}
catch(Exception $exc) {
var_log(var_export($exc, true));
}
return $message;
}
/**
* overwrite get_type to obfuscate that we are not really an Activity_type
*/
public function get_type() {
return('watchlist');
}
}
class ActivityTypeNewview extends ActivityType {
protected $owner;
......
......@@ -1203,5 +1203,20 @@
<KEY NAME="nameuk" TYPE="unique" FIELDS="name"/>
</KEYS>
</TABLE>
<TABLE NAME="watchlist_queue">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true" COMMENT="Primary Key"/>
<FIELD NAME="usr" TYPE="int" LENGTH="10" NOTNULL="true" COMMENT="The User whose data are appended"/>
<FIELD NAME="block" TYPE="int" LENGTH="10" NOTNULL="false" COMMENT="the block that has been comitted"/>
<FIELD NAME="view" TYPE="int" LENGTH="10" NOTNULL="true" COMMENT="the view where the block resides"/>
<FIELD NAME="changed_on" TYPE="datetime" NOTNULL="true" COMMENT="the time of the change"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="viewfk" TYPE="foreign" FIELDS="view" REFTABLE="view" REFFIELDS="id"/>
<KEY NAME="blockfk" TYPE="foreign" FIELDS="block" REFTABLE="block_instance" REFFIELDS="id"/>
<KEY NAME="usrfk" TYPE="foreign" FIELDS="usr" REFTABLE="usr" REFFIELDS="id"/>
</KEYS>
</TABLE>
</TABLES>
</XMLDB>
......@@ -3190,5 +3190,9 @@ function xmldb_core_upgrade($oldversion=0) {
}
}
if ($oldversion < 2014032600) {
install_watchlist_notification();
}
return $status;
}
......@@ -748,6 +748,9 @@ function core_install_lastcoredata_defaults() {
// the order of installation stuff.
install_blocktype_extras();
// also install the new watchlist notification
install_watchlist_notification();
}
function core_install_firstcoredata_defaults() {
......@@ -1437,3 +1440,62 @@ function site_warnings() {
return $warnings;
}
function install_watchlist_notification() {
if (!record_exists('config', 'field', 'watchlistnotification_delay')) {
set_config('watchlistnotification_delay', 20);
}
if (!table_exists(new XMLDBTable('watchlist_queue'))) {
$table = new XMLDBTable('watchlist_queue');
$table->addFieldInfo('id', XMLDB_TYPE_INTEGER, 10, null, XMLDB_NOTNULL, XMLDB_SEQUENCE);
$table->addFieldInfo('usr', XMLDB_TYPE_INTEGER, 10, null, XMLDB_NOTNULL);
$table->addFieldInfo('block', XMLDB_TYPE_INTEGER, 10, null, false);
$table->addFieldInfo('view', XMLDB_TYPE_INTEGER, 10, null, XMLDB_NOTNULL);
$table->addFieldInfo('changed_on', XMLDB_TYPE_DATETIME, null, null, XMLDB_NOTNULL);
$table->addKeyInfo('viewfk', XMLDB_KEY_FOREIGN, array('view'), 'view', array('id'));
$table->addKeyInfo('blockfk', XMLDB_KEY_FOREIGN, array('block'), 'block_instance', array('id'));
$table->addKeyInfo('usrfk', XMLDB_KEY_FOREIGN, array('usr'), 'usr', array('id'));
$table->addKeyInfo('primary', XMLDB_KEY_PRIMARY, array('id'));
create_table($table);
}
// new event type: delete blockinstance
$e = new StdClass;
$e->name = 'deleteblockinstance';
ensure_record_exists('event_type', $e, $e);
// install the core event subscriptions
$subs = array(
array(
'event' => 'blockinstancecommit',
'callfunction' => 'watchlist_record_changes',
),
array(
'event' => 'deleteblockinstance',
'callfunction' => 'watchlist_block_deleted',
),
array(
'event' => 'saveartefact',
'callfunction' => 'watchlist_record_changes',
),
array(
'event' => 'saveview',
'callfunction' => 'watchlist_record_changes',
),
);
foreach ($subs as $sub) {
ensure_record_exists('event_subscription', (object)$sub, (object)$sub);
}
// install the cronjobs...
$cron = new StdClass;
$cron->callfunction = 'watchlist_process_notifications';
$cron->minute = '*';
$cron->hour = '*';
$cron->day = '*';
$cron->month = '*';
$cron->dayofweek = '*';
ensure_record_exists('cron', $cron, $cron);
}
\ No newline at end of file
......@@ -15,7 +15,7 @@ $config = new stdClass();
// See https://wiki.mahara.org/index.php/Developer_Area/Version_Numbering_Policy
// For upgrades on stable branches, increment the version by one. On master, use the date.
$config->version = 2014032500;
$config->version = 2014032600;
$config->release = '1.9.0dev';
$config->minupgradefrom = 2009022600;
$config->minupgraderelease = '1.1.0 (release tag 1.1.0_RELEASE)';
......
......@@ -1767,8 +1767,6 @@ class View {
$data = (object)array(
'view' => $this->get('id'),
);
require_once('activity.php');
activity_occurred('watchlist', $data);
if (!defined('JSON')) {
$message = $this->get_viewcontrol_ok_string($action);
......@@ -2700,6 +2698,7 @@ class View {
}
$this->set('theme', $theme);
$this->commit();
handle_event('saveview', $this->get('id'));
}
}
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment