Commit 506c004a authored by Richard Mansfield's avatar Richard Mansfield Committed by Gerrit Code Review
Browse files

Merge changes I9c9a81bf,Ifb24c689,Id4f87c1f,Ib9a72a9e,I81865ee9

* changes:
  Get unread message count from user table (bug #854403)
  Add column for unread message counts to the usr table (bug #854403)
  Add functions to create and remove database triggers
  Display a useful error message when PL/pgSQL is not available
  New icon for unread notifications
parents 8387f0e5 60167828
......@@ -36,7 +36,12 @@ $delete = param_integer('delete', 0);
if ($readone) {
set_field('notification_internal_activity', 'read', 1, 'id', $readone, 'usr', $USER->get('id'));
json_reply(false, null);
$unread = $USER->add_unread(-1);
$data = array(
'newunreadcount' => $unread,
'newimage' => $THEME->get_url($unread ? 'images/newemail.gif' : 'images/email.gif'),
);
json_reply(false, array('data' => $data));
}
require_once(get_config('libroot') . 'activity.php');
......@@ -60,14 +65,19 @@ if ($markasread) {
'id IN (' . join(',', $ids) . ') AND usr = ?',
array($USER->get('id'))
);
$newunread = $USER->add_unread(-count($ids));
}
$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\-(\d+)$/',$k,$m)) {
$ids[] = $m[1];
if (isset($_GET['unread-' . $m[1]])) {
$deleteunread++;
}
}
}
if ($ids) {
......@@ -92,6 +102,9 @@ else if ($delete) {
"id IN ($strids) AND usr = ?",
array($userid)
);
if ($deleteunread) {
$newunread = $USER->add_unread(-$deleteunread);
}
db_commit();
}
$message = get_string('deletednotifications', 'activity', count($ids));
......@@ -99,13 +112,9 @@ else if ($delete) {
$newhtml = activitylist_html($type, $limit, $offset);
if ($message) {
safe_require('notification', 'internal');
$newhtml['newunreadcount'] = call_static_method(
generate_class_name('notification', 'internal'),
'unread_count',
$USER->get('id')
);
if (isset($newunread)) {
$newhtml['newunreadcount'] = $newunread;
$newhtml['newimage'] = $THEME->get_url($newunread ? 'images/newemail.gif' : 'images/email.gif');
}
json_reply(false, (object) array('message' => $message, 'data' => $newhtml));
......@@ -35,6 +35,10 @@ require(dirname(dirname(dirname(__FILE__))) . '/init.php');
require_once('pieforms/pieform.php');
define('TITLE', get_string('inbox'));
// 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',
......@@ -90,6 +94,11 @@ function markread(form, action) {
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;
}
......@@ -101,9 +110,7 @@ function markread(form, action) {
sendjsonrequest('index.json.php', pd, 'GET', function (data) {
paginator.updateResults(data);
if (data.newunreadcount && typeof(data.newunreadcount) != 'undefined') {
updateUnreadCount(data.newunreadcount, 'reset');
}
updateUnreadCount(data);
});
}
......@@ -120,7 +127,7 @@ function showHideMessage(id) {
var pd = {'readone':id};
sendjsonrequest('index.json.php', pd, 'GET', function(data) {
swapDOM(unread, IMG({'src' : {$star}, 'alt' : {$strread}}));
updateUnreadCount(1, 'decrement');
updateUnreadCount(data);
});
}
removeElementClass(message, 'hidden');
......@@ -234,6 +241,8 @@ function delete_all_notifications_submit() {
' . 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();
......
......@@ -96,7 +96,8 @@ class User {
'sesskey' => '',
'ctime' => null,
'views' => array(),
'showhomeinfo' => 1
'showhomeinfo' => 1,
'unread' => 0,
);
$this->attributes = array();
......@@ -1224,29 +1225,35 @@ class LiveUser extends User {
$this->changed = false;
}
public function commit() {
if ($this->changed == false) {
return;
}
// Fields which can't be changed in the session, but which may have
// changed in the db. They should be reloaded.
/**
* Some fields may have been changed in the db by some other process, and
* should be occasionally reloaded into the session.
*/
public function reload_background_fields() {
$reload = array(
'active' => 'active',
'deleted' => 'deleted',
'expiry' => db_format_tsfield('expiry'),
'expirymailsent' => 'expirymailsent',
'inactivemailsent' => 'inactivemailsent',
'suspendedctime' => db_format_tsfield('suspendedctime'),
'suspendedreason' => 'suspendedreason',
'suspendedcusr' => 'suspendedcusr',
'quota' => 'quota',
'active', 'deleted', 'expiry', 'expirymailsent', 'inactivemailsent', 'suspendedctime', 'suspendedreason',
'suspendedcusr', 'quota', 'unread',
);
$r = get_record('usr', 'id', $this->id, null, null, null, null, join(',', $reload));
foreach (array_keys($reload) as $k) {
if ($r->$k != $this->$k) {
$this->$k = $r->$k;
$tsfields = array('expiry', 'suspendedctime');
$record = get_record('usr', 'id', $this->id);
foreach ($reload as $f) {
if (!isset($record->$f)) {
continue;
}
if (in_array($f, $tsfields)) {
$record->$f = strtotime($record->$f);
}
if ($record->$f != $this->$f) {
$this->$f = $record->$f;
}
}
}
public function commit() {
if ($this->changed == false) {
return;
}
$this->reload_background_fields();
parent::commit();
}
......@@ -1478,6 +1485,15 @@ class LiveUser extends User {
'mimetype' => $mimetype,
));
}
public function add_unread($n) {
// The unread property can change any time a notification is processed on
// cron, so it's reloaded from the db in commit(), every time the last
// access time is saved. This just updates it in the session.
$new = $this->get('unread') + $n;
$this->SESSION->set('user/unread', $new);
return $new;
}
}
function is_site_closed($adminuser) {
......
......@@ -43,7 +43,7 @@ addLoadEvent(function() {
var pd = {'readone':id};
sendjsonrequest(config.wwwroot + 'account/activity/index.json.php', pd, 'GET', function(data) {
removeElementClass(element, 'unread');
updateUnreadCount(1, 'decrement');
updateUnreadCount(data);
});
}
});
......
......@@ -708,21 +708,22 @@ function quotaUpdate(quotaused, quota) {
}
}
function updateUnreadCount(n, decrement) {
var newcount = -1;
var countnode = getFirstElementByTagAndClassName('span', 'unreadmessagecount', 'right-nav');
if (!countnode) {
function updateUnreadCount(data) {
var inboxmenu = getFirstElementByTagAndClassName(null, 'inbox', 'right-nav');
if (!inboxmenu) {
return;
}
if (decrement == 'decrement') {
var oldcount = parseInt(countnode.innerHTML);
newcount = (oldcount - n);
}
else {
newcount = n;
if (typeof(data.data.newunreadcount) != 'undefined') {
var countnode = getFirstElementByTagAndClassName('span', 'unreadmessagecount', inboxmenu);
if (countnode) {
countnode.innerHTML = data.data.newunreadcount;
}
}
if (newcount > -1) {
countnode.innerHTML = newcount;
if (data.data.newimage) {
var oldimage = getFirstElementByTagAndClassName('img', null, inboxmenu);
if (oldimage) {
setNodeAttribute(oldimage, 'src', data.data.newimage);
}
}
}
......
......@@ -70,6 +70,7 @@ The error received was:
';
$string['dbnotutf8'] = 'You are not using a UTF-8 database. Mahara stores all data as UTF-8 internally. Please drop and re-create your database using UTF-8 encoding.';
$string['dbversioncheckfailed'] = 'Your database server version is not new enough to successfully run Mahara. Your server is %s %s, but Mahara requires at least version %s.';
$string['plpgsqlnotavailable'] = 'The PL/pgSQL language is not enabled in your Postgres installation, and Mahara cannot enable it. Please install PL/pgSQL using the CREATE LANGUAGE command, or the createlang program.';
// general exception error messages
$string['blocktypenametaken'] = "Block type %s is already taken by another plugin (%s)";
......
......@@ -480,6 +480,8 @@ abstract class ActivityType {
}
}
// The user's unread message count does not need to be updated from $changes->read
// because of the db trigger on notification_internal_activity.
}
public function notify_users() {
......
......@@ -141,6 +141,7 @@
<FIELD NAME="ctime" TYPE="datetime" NOTNULL="false" />
<FIELD NAME="showhomeinfo" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="1" />
<FIELD NAME="logintries" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" />
<FIELD NAME="unread" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" />
<!-- If adding a datetime column, you should also update the relevant
functions in htdocs/auth/user.php and htdocs/lib/user.php to convert the
value to a timestamp when a record is retrieved -->
......
......@@ -2790,5 +2790,12 @@ function xmldb_core_upgrade($oldversion=0) {
add_field($table, $field);
}
if ($oldversion < 2012021000) {
$table = new XMLDBTable('usr');
$field = new XMLDBField('unread');
$field->setAttributes(XMLDB_TYPE_INTEGER, 10, null, XMLDB_NOTNULL, null, null, null, 0);
add_field($table, $field);
}
return $status;
}
......@@ -1688,3 +1688,82 @@ function db_interval($s) {
return "INTERVAL $s SECOND";
}
}
function postgres_language_exists($language) {
if (!is_postgres()) {
throw new SQLException('postgres_language_exists() expects a postgres database');
}
return get_field_sql('SELECT 1 FROM pg_catalog.pg_language WHERE lanname = ?', array($language)) == 1;
}
function postgres_create_language($language) {
if (!is_postgres()) {
throw new SQLException('postgres_create_language() expects a postgres database');
}
// CREATE LANGUAGE fails if the language already exists
if (postgres_language_exists($language)) {
return true;
}
execute_sql("CREATE LANGUAGE $language;");
return postgres_language_exists($language);
}
/**
* Creates a database row trigger
*
* @param string $name trigger name
* @param string $time trigger action time, e.g. AFTER
* @param string $event trigger event, one of (INSERT, UPDATE, DELETE)
* @param string $table table name
* @param string $body function body
*/
function db_create_trigger($name, $time, $event, $table, $body) {
if ($time != 'AFTER' || ($event != 'INSERT' && $event != 'UPDATE' && $event != 'DELETE')) {
throw new SQLException("db_create_trigger() not implemented for $time $event");
}
if (is_postgres()) {
$functionname = $name . '_function';
$triggername = $name . '_trigger';
execute_sql('
CREATE FUNCTION {' . $functionname . '}() RETURNS TRIGGER AS $$
BEGIN
' . $body . '
RETURN NULL;
END
$$ LANGUAGE plpgsql;
CREATE TRIGGER {' . $triggername . '} ' . $time . ' ' . $event . '
ON {' . $table . '} FOR EACH ROW
EXECUTE PROCEDURE {' . $functionname . '}();'
);
}
else if (is_mysql()) {
$triggername = $name . '_trigger';
execute_sql('
CREATE TRIGGER {' . $triggername . '} ' . $time . ' ' . $event . '
ON {' . $table . '} FOR EACH ROW
BEGIN
' . $body . '
END'
);
}
else {
throw new SQLException("db_create_trigger() is not implemented for your database engine");
}
}
function db_drop_trigger($name, $table) {
if (is_postgres()) {
$functionname = $name . '_function';
$triggername = $name . '_trigger';
execute_sql('DROP TRIGGER {' . $triggername . '} ON {' . $table . '}');
execute_sql('DROP FUNCTION {' . $functionname . '}()');
}
else if (is_mysql()) {
$triggername = $name . '_trigger';
execute_sql('DROP TRIGGER {' . $triggername . '}');
}
else {
throw new SQLException("db_drop_trigger() is not implemented for your database engine");
}
}
......@@ -180,6 +180,9 @@ function ensure_install_sanity() {
if (!db_is_utf8()) {
throw new ConfigSanityException(get_string('dbnotutf8', 'error'));
}
if (is_postgres() && !postgres_create_language('plpgsql')) {
throw new ConfigSanityException(get_string('plpgsqlnotavailable', 'error'));
}
}
function ensure_upgrade_sanity() {
......@@ -192,6 +195,9 @@ function ensure_upgrade_sanity() {
}
}
}
if (is_postgres() && !postgres_create_language('plpgsql')) {
throw new ConfigSanityException(get_string('plpgsqlnotavailable', 'error'));
}
}
/**
......
......@@ -28,7 +28,7 @@
defined('INTERNAL') || die();
$config = new StdClass;
$config->version = 2012011300;
$config->version = 2012021000;
$config->release = '1.5.0dev';
$config->minupgradefrom = 2008040200;
$config->minupgraderelease = '1.0.0 (release tag 1.0.0_RELEASE)';
......
......@@ -845,8 +845,6 @@ function jsstrings() {
'unknownerror',
'loading',
'showtags',
'unreadmessages',
'unreadmessage',
'couldnotgethelp',
'password',
'username',
......@@ -2245,7 +2243,7 @@ function right_nav() {
global $USER, $THEME;
safe_require('notification', 'internal');
$unread = call_static_method(generate_class_name('notification', 'internal'), 'unread_count', $USER->get('id'));
$unread = $USER->get('unread');
$menu = array(
'settings' => array(
......@@ -2259,7 +2257,7 @@ function right_nav() {
'inbox' => array(
'path' => 'inbox',
'url' => 'account/activity',
'icon' => $THEME->get_url('images/email.gif'),
'icon' => $THEME->get_url($unread ? 'images/newemail.gif' : 'images/email.gif'),
'alt' => get_string('inbox'),
'count' => $unread,
'countclass' => 'unreadmessagecount',
......
......@@ -37,6 +37,48 @@ function xmldb_notification_internal_upgrade($oldversion=0) {
);
}
if ($oldversion < 2012021000) {
// Populate the unread count on the usr table
if (is_postgres()) {
execute_sql('
UPDATE {usr} SET unread = n.unread FROM (
SELECT usr, SUM(1 - read) AS unread FROM {notification_internal_activity} GROUP BY usr
) n WHERE {usr}.id = n.usr;'
);
}
else if (is_mysql()) {
execute_sql('
UPDATE {usr} u, (SELECT usr, SUM(1 - "read") AS unread FROM {notification_internal_activity} GROUP BY usr) n
SET u.unread = n.unread
WHERE u.id = n.usr'
);
}
// Create triggers to maintain the unread count
db_create_trigger(
'update_unread_insert',
'AFTER', 'INSERT', 'notification_internal_activity', '
IF NEW.read = 0 THEN
UPDATE {usr} SET unread = unread + 1 WHERE id = NEW.usr;
END IF;'
);
db_create_trigger(
'update_unread_update',
'AFTER', 'UPDATE', 'notification_internal_activity', '
IF OLD.read = 0 AND NEW.read = 1 THEN
UPDATE {usr} SET unread = unread - 1 WHERE id = NEW.usr;
ELSEIF OLD.read = 1 AND NEW.read = 0 THEN
UPDATE {usr} SET unread = unread + 1 WHERE id = NEW.usr;
END IF;'
);
db_create_trigger(
'update_unread_delete',
'AFTER', 'DELETE', 'notification_internal_activity', '
IF OLD.read = 0 THEN
UPDATE {usr} SET unread = unread - 1 WHERE id = OLD.usr;
END IF;'
);
}
return true;
}
......@@ -59,16 +59,34 @@ class PluginNotificationInternal extends PluginNotification {
return insert_record('notification_internal_activity', $toinsert, 'id', true);
}
/**
* this method is only implemented in internal & is used for the header
*/
public static function unread_count($userid) {
static $unreadcount = array();
if (!isset($unreadcount[$userid])) {
$unreadcount[$userid] = count_records('notification_internal_activity', 'usr', $userid, 'read', 0);
public static function postinst($prevversion) {
if ($prevversion == 0) {
// Add triggers to update user unread message count when updating
// notification_internal_activity
db_create_trigger(
'update_unread_insert',
'AFTER', 'INSERT', 'notification_internal_activity', '
IF NEW.read = 0 THEN
UPDATE {usr} SET unread = unread + 1 WHERE id = NEW.usr;
END IF;'
);
db_create_trigger(
'update_unread_update',
'AFTER', 'UPDATE', 'notification_internal_activity', '
IF OLD.read = 0 AND NEW.read = 1 THEN
UPDATE {usr} SET unread = unread - 1 WHERE id = NEW.usr;
ELSEIF OLD.read = 1 AND NEW.read = 0 THEN
UPDATE {usr} SET unread = unread + 1 WHERE id = NEW.usr;
END IF;'
);
db_create_trigger(
'update_unread_delete',
'AFTER', 'DELETE', 'notification_internal_activity', '
IF OLD.read = 0 THEN
UPDATE {usr} SET unread = unread - 1 WHERE id = OLD.usr;
END IF;'
);
}
return $unreadcount[$userid];
}
public static function get_event_subscriptions() {
......
......@@ -28,5 +28,5 @@
defined('INTERNAL') || die();
$config = new StdClass;
$config->version = 2011112300;
$config->release = '1.0.2';
$config->version = 2012021000;
$config->release = '1.0.3';
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