diff --git a/lib/dashboard/Notification.inc.php b/lib/dashboard/Notification.inc.php index 80948aa..26fb2de 100644 --- a/lib/dashboard/Notification.inc.php +++ b/lib/dashboard/Notification.inc.php @@ -1,8 +1,13 @@ getHandler($this->sType); return $handler->handleNotification($this); } + + function resolve() { + $notificationRegistry =& KTNotificationRegistry::getSingleton(); + $handler = $notificationRegistry->getHandler($this->sType); + return $handler->resolveNotification($this); + } // Static function function &get($iId) { return KTEntityUtil::get('KTNotification', $iId); } @@ -83,6 +94,9 @@ $notificationRegistry =& KTNotificationRegistry::getSingleton(); // abstract base-class for notification handler. class KTNotificationHandler { + + // FIXME rename this to renderNotification + // called to _render_ the notification. function handleNotification($oKTNotification) { $oTemplating = new KTTemplating; $oTemplate = $oTemplating->loadTemplate("kt3/notifications/generic"); @@ -91,17 +105,210 @@ class KTNotificationHandler { ); return $oTemplate->render($aTemplateData); } + + // called to resolve the notification (typically from /notify.php?id=xxxxx + function resolveNotification($oKTNotification) { + $_SESSION['KTErrorMessage'][] = 'This notification handler does not support publication.'; + exit(redirect(generateControllerLink('dashboard'))); + } } +// FIXME consider refactoring this into plugins/ktcore/ktstandard/KTSubscriptions.php + class KTSubscriptionNotification extends KTNotificationHandler { + /* Subscription Notifications + * + * Subscriptions are a large part of the notification volume. + * That said, notifications cater to a larger group, so there is some + * degree of mismatch between the two. + * + * Mapping the needs of subscriptions onto the provisions of notifications + * works as: + * + * $oKTN->label: object name [e.g. Document Name] + * $oKTN->strData1: event type [AddFolder, AddDocument, etc.] + * $oKTN->strData2: _location_ name. (e.g. folder of the subscription.) + * $oKTN->intData1: object id (e.g. document_id, folder_id) + * $oKTN->intData2: actor id (e.g. user_id) + * + */ + + var $notificationType = 'ktcore/subscriptions'; + + var $_eventObjectMap = array( + "AddFolder" => 'folder', + "RemoveSubscribedFolder" => '', // nothing. your subscription is now gone. + "RemoveChildFolder" => 'folder', + "AddDocument" => 'document', + "RemoveSubscribedDocument" => '', // nothing. your subscription is now gone. + "RemoveChildDocument" => 'folder', + "ModifyDocument" => 'document', + "CheckInDocument" => 'document', + "CheckOutDocument" => 'document', + "MovedDocument" => 'document', + "ArchivedDocument" => 'document', // can go through and request un-archival (?) + "RestoredArchivedDocument" => 'document'); + + var $_eventTypeNames = array( + "AddFolder" => 'Folder added', + "RemoveSubscribedFolder" => 'Folder removed', // nothing. your subscription is now gone. + "RemoveChildFolder" => 'Folder removed', + "AddDocument" => 'Document added', + "RemoveSubscribedDocument" => 'Document removed', // nothing. your subscription is now gone. + "RemoveChildDocument" => 'Document removed', + "ModifyDocument" => 'Document modified', + "CheckInDocument" => 'Document checked in', + "CheckOutDocument" => 'Document checked out', + "MovedDocument" => 'Document moved', + "ArchivedDocument" => 'Document archived', // can go through and request un-archival (?) + "RestoredArchivedDocument" => 'Document restored'); + + // helper method to extract / set the various pieces of information + function _getSubscriptionData($oKTNotification) { + $info = array( + 'object_name' => $oKTNotification->getLabel(), + 'event_type' => $oKTNotification->getStrData1(), + 'location_name' => $oKTNotification->getStrData2(), + 'object_id' => $oKTNotification->getIntData1(), + 'actor_id' => $oKTNotification->getIntData2(), + 'has_actor' => false, + 'notify_id' => $oKTNotification->getId(), + ); + + $info['title'] = KTUtil::arrayGet($this->_eventTypeNames, $info['event_type'], 'Subscription alert:') .': ' . $info['object_name']; + + if ($info['actor_id'] !== null) { + $oTempUser = User::get($info['actor_id']); + if (PEAR::isError($oTempUser) || ($oTempUser == false)) { + // no-act + $info['actor'] = null; + } else { + $info['actor'] = $oTempUser; + $info['has_actor'] = true; + } + } + + if ($info['object_id'] !== null) { + $info['object'] = $this->_getEventObject($info['event_type'], $info['object_id']); + } + + return $info; + } + + // resolve the object type based on the alert type. + function _getEventObject($sAlertType, $id) { + $t = KTUtil::arrayGet($this->_eventObjectMap, $sAlertType ,''); + if ($t == 'document') { + $o = Document::get($id); + if (PEAR::isError($o) || ($o == false)) { return null; + } else { return $o; } + } else if ($t == 'folder') { + $o = Folder::get($id); + if (PEAR::isError($o) || ($o == false)) { return null; + } else { return $o; } + } else { + return null; + } + } + + function _getEventObjectType($sAlertType) { + return KTUtil::arrayGet($this->_eventObjectMap, $sAlertType ,''); + } + function handleNotification($oKTNotification) { $oTemplating = new KTTemplating; $oTemplate = $oTemplating->loadTemplate("kt3/notifications/subscriptions"); $aTemplateData = array( "context" => $oKTNotification, + "info" => $this->_getSubscriptionData($oKTNotification), ); return $oTemplate->render($aTemplateData); } + + // helper to _create_ a notification, in a way that is slightly less opaque. + function &generateSubscriptionNotification($aOptions) { + $creationInfo = array(); + /* + "iId" => "id", + "iUserId" => "user_id", + "sLabel" => "label", + "sType" => "type", + "dCreationDate" => "creation_date", + "iData1" => "data_int_1", + "iData2" => "data_int_2", + "sData1" => "data_str_1", + "sData2" => "data_str_2", + + 'object_name' => $oKTNotification->getLabel(), + 'event_type' => $oKTNotification->getStrData1(), + 'location_name' => $oKTNotification->getStrData2(), + 'object_id' => $oKTNotification->getIntData1(), + 'actor_id' => $oKTNotification->getIntData2(), + 'has_actor' => false, + + */ + $creationInfo['sLabel'] = $aOptions['target_name']; + $creationInfo['sData1'] = $aOptions['event_type']; + $creationInfo['sData2'] = $aOptions['location_name']; + $creationInfo['iData1'] = $aOptions['object_id']; + $creationInfo['iData2'] = $aOptions['actor_id']; + $creationInfo['iUserId'] = $aOptions['target_user']; + $creationInfo['sType'] = 'ktcore/subscriptions'; + $creationInfo['dCreationDate'] = getCurrentDateTime(); // erk. + + global $default; + + //$default->log->debug('subscription notification: from ' . print_r($aOptions, true)); + $default->log->debug('subscription notification: using ' . print_r($creationInfo, true)); + + $oNotification =& KTNotification::createFromArray($creationInfo); + + + $default->log->debug('subscription notification: created ' . print_r($oNotification, true)); + + return $oNotification; // $res. + } + + + + function resolveNotification($oKTNotification) { + $notify_action = KTUtil::arrayGet($_REQUEST, 'notify_action', null); + if ($notify_action == 'clear') { + $_SESSION['KTInfoMessage'][] = 'Cleared notification.'; + $oKTNotification->delete(); + exit(redirect(generateControllerLink('dashboard'))); + } + + // otherwise, we want to redirect the to object represented by the item. + // - viewDocument and viewFolder are the appropriate items. + // - object_id + $info = $this->_getSubscriptionData($oKTNotification); + + $object_type = $this->_getEventObjectType($info['event_type']); + if ($object_type == '') { + $_SESSION['KTErrorMessage'][] = 'This notification has no "target". Please report as a bug that this subscription should only have a clear action.' . $object_type; + exit(redirect(generateControllerLink('dashboard'))); + } + + if ($object_type == 'document') { + if ($info['object_id'] !== null) { // fails and generates an error with no doc-id. + $params = 'fDocumentId=' . $info['object_id']; + $url = generateControllerLink('viewDocument', $params); + $oKTNotification->delete(); // clear the alert. + exit(redirect($url)); + } + } else if ($object_type == 'folder') { + if ($info['object_id'] !== null) { // fails and generates an error with no doc-id. + $params = 'fFolderId=' . $info['object_id']; + $url = generateControllerLink('browse', $params); + $oKTNotification->delete(); // clear the alert. + exit(redirect($url)); + } + } + $_SESSION['KTErrorMessage'][] = 'This notification has no "target". Please inform the KnowledgeTree developers that there is a target bug with type: ' . $info['event_type']; + exit(redirect(generateControllerLink('dashboard'))); + } + } $notificationRegistry->registerNotificationHandler("ktcore/subscriptions","KTSubscriptionNotification"); diff --git a/lib/documentmanagement/documentutil.inc.php b/lib/documentmanagement/documentutil.inc.php index bc3ea6c..05262aa 100644 --- a/lib/documentmanagement/documentutil.inc.php +++ b/lib/documentmanagement/documentutil.inc.php @@ -393,7 +393,7 @@ class KTDocumentUtil { $count = SubscriptionEngine::fireSubscription($oDocument->getID(), SubscriptionConstants::subscriptionAlertType("AddDocument"), SubscriptionConstants::subscriptionType("DocumentSubscription"), array( "folderID" => $oDocument->getFolderID(), - "modifiedDocumentName" => $oDocument->getName() )); + "newDocumentName" => $oDocument->getName() )); global $default; $default->log->info("checkInDocumentBL.php fired $count subscription alerts for checked out document " . $oDocument->getName()); diff --git a/lib/subscriptions/SubscriptionConstants.inc b/lib/subscriptions/SubscriptionConstants.inc index 7d33609..4a2cbfc 100644 --- a/lib/subscriptions/SubscriptionConstants.inc +++ b/lib/subscriptions/SubscriptionConstants.inc @@ -58,5 +58,22 @@ class SubscriptionConstants { "RestoredArchivedDocument" => 12); return $aChangeType[$sType]; } + + function subscriptionAlertTypeString($iType) { + $aChangeType = array(1 => "AddFolder", + 2 => "RemoveSubscribedFolder", + 3 => "RemoveChildFolder", + 4 => "AddDocument", + 5 => "RemoveSubscribedDocument", + 6 => "RemoveChildDocument", + 7 => "ModifyDocument", + 8 => "CheckInDocument", + 9 => "CheckOutDocument", + 10 => "MovedDocument", + 11 => "ArchivedDocument", + 12 => "RestoredArchivedDocument", + ); + return $aChangeType[$iType]; + } } ?> \ No newline at end of file diff --git a/lib/subscriptions/SubscriptionEngine.inc b/lib/subscriptions/SubscriptionEngine.inc index d642c95..61628dc 100644 --- a/lib/subscriptions/SubscriptionEngine.inc +++ b/lib/subscriptions/SubscriptionEngine.inc @@ -1,12 +1,15 @@ fileSystemRoot/lib/users/User.inc"); -require_once("$default->fileSystemRoot/lib/documentmanagement/Document.inc"); -require_once("$default->fileSystemRoot/lib/foldermanagement/Folder.inc"); -require_once("$default->fileSystemRoot/lib/subscriptions/Subscription.inc"); -require_once("$default->fileSystemRoot/lib/alert/AlertContent.inc"); -require_once("$default->fileSystemRoot/lib/alert/delivery/EmailAlert.inc"); -require_once("$default->fileSystemRoot/lib/alert/delivery/SMSAlert.inc"); +require_once(KT_LIB_DIR . "/users/User.inc"); +require_once(KT_LIB_DIR . "/documentmanagement/Document.inc"); +require_once(KT_LIB_DIR . "/foldermanagement/Folder.inc"); +require_once(KT_LIB_DIR . "/subscriptions/Subscription.inc"); +require_once(KT_LIB_DIR . "/alert/AlertContent.inc"); +require_once(KT_LIB_DIR . "/alert/delivery/EmailAlert.inc"); +require_once(KT_LIB_DIR . "/alert/delivery/SMSAlert.inc"); + +require_once(KT_LIB_DIR . "/dashboard/Notification.inc.php"); + /** * $Id$ * @@ -41,8 +44,9 @@ class SubscriptionEngine { * @param int the alert type (document change, new folder, etc.) * @param int the subscription content type (folder, document) * @param array any dynamic values that should be sent with the alert (eg. document name, path to modified document) + * @param int the original object id (e.g. if fired on a document, and chained to a folder. */ - function fireSubscription($iExternalID, $iSubscriptionAlertType, $iSubscriptionType, $aValues) { + function fireSubscription($iExternalID, $iSubscriptionAlertType, $iSubscriptionType, $aValues, $iOriginalId = null) { global $default; $default->log->info("fireSubscription ($iExternalID, $iSubscriptionAlertType, $iSubscriptionType, values)"); // get the list of subscriber addresses that we need to alert @@ -58,7 +62,8 @@ class SubscriptionEngine { SubscriptionConstants::subscriptionAlertType("RemoveChildDocument") : $iSubscriptionAlertType), SubscriptionConstants::subscriptionType("FolderSubscription"), - $aValues); + $aValues, + $iExternalId); $default->log->info("SubscriptionEngine::fireSubscription fired folder subscribers, count=$iSubscriptionsSent"); } @@ -66,7 +71,7 @@ class SubscriptionEngine { for ($i=0; $igetID(), $oValues['folderID'], $iSubscriptionType); + $oSubscription = & Subscription::getByIDs($aSubscribers[$i]->getID(), $aValues['folderID'], $iSubscriptionType); if (empty($oSubscription) || PEAR::isError($oSubscription)) { $oSubscription =& new Subscription($aSubscribers[$i]->getID(), $aValues["folderID"], $iSubscriptionType); $res = $oSubscription->create(); @@ -96,6 +101,109 @@ class SubscriptionEngine { $sAlertContent = AlertContent::getSubscriptionAlert($iSubscriptionAlertType, $aValues); // construct alerts + + // dashboard notification. + $aNotificationOptions = array(); + + $aNotificationOptions['target_user'] = $aSubscribers[$i]->getID(); + $aNotificationOptions['actor_id'] = KTUtil::arrayGet($_SESSION,"userID", null); // _won't_ be null. + + // location name: ditto. + // target_name: ditto. + + //$default->log->debug('subscriptionengine: received ' . print_r($aValues, true)); + + // sweet lord this is _hideous_. Please, oh please kill the subscriptionsconstants + if (SubscriptionConstants::subscriptionAlertTypeString($iSubscriptionAlertType) == "AddFolder") { + $aNotificationOptions['target_name'] = $aValues["newFolderName"]; + $aNotificationOptions['location_name'] = $aValues["parentFolderName"]; + $aNotificationOptions['object_id'] = $iExternalId; // parent folder_id, in this case. + $aNotificationOptions['event_type'] = "AddFolder"; + } else if (SubscriptionConstants::subscriptionAlertTypeString($iSubscriptionAlertType) == "RemoveSubscribedFolder") { + $aNotificationOptions['target_name'] = $aValues["removedFolderName"]; + $aNotificationOptions['location_name'] = $aValues["parentFolderName"]; + $aNotificationOptions['object_id'] = $iExternalId; // parent folder_id, in this case. + $aNotificationOptions['event_type'] = "RemoveSubscribedFolder"; + } else if (SubscriptionConstants::subscriptionAlertTypeString($iSubscriptionAlertType) == "RemoveChildFolder") { + $aNotificationOptions['target_name'] = $aValues["removedFolderName"]; + $aNotificationOptions['location_name'] = $aValues["parentFolderName"]; + $aNotificationOptions['object_id'] = $iExternalId; // parent folder_id, in this case, where user is subscribed. + $aNotificationOptions['event_type'] = "RemoveChildFolder"; + } else if (SubscriptionConstants::subscriptionAlertTypeString($iSubscriptionAlertType) == "AddDocument") { + $aNotificationOptions['target_name'] = $aValues["newDocumentName"]; + $aNotificationOptions['location_name'] = $aValues["parentFolderName"]; + if ($iOriginalId !== null) { + $aNotificationOptions['object_id'] = $iOriginalId; // folder subscription, this _was_ the document. + } else { + $aNotificationOptions['object_id'] = $iExternalId; // this path _shouldn't_ be hit since its a new document - user _can't_ be subscribed to it already. + } + $aNotificationOptions['event_type'] = "AddDocument"; + } else if (SubscriptionConstants::subscriptionAlertTypeString($iSubscriptionAlertType) == "RemoveChildDocument") { + $aNotificationOptions['target_name'] = $aValues["removedDocumentName"]; + $aNotificationOptions['location_name'] = $aValues["folderName"]; + $aNotificationOptions['object_id'] = $iExternalId; // parent folder_id, in this case, where user is subscribed... is it? + $aNotificationOptions['event_type'] = "RemoveChildDocument"; + } else if (SubscriptionConstants::subscriptionAlertTypeString($iSubscriptionAlertType) == "RemoveSubscribedDocument") { + $aNotificationOptions['target_name'] = $aValues["removedDocumentName"]; + $aNotificationOptions['location_name'] = $aValues["folderName"]; + $aNotificationOptions['object_id'] = $iExternalId; // not used. + $aNotificationOptions['event_type'] = "RemoveSubscribedDocument"; + } else if (SubscriptionConstants::subscriptionAlertTypeString($iSubscriptionAlertType) == "ModifyDocument") { + $aNotificationOptions['target_name'] = $aValues["modifiedDocumentName"]; + $aNotificationOptions['location_name'] = null; // not used... why? don't we have the folder name? why not? + if ($iOriginalId !== null) { + $aNotificationOptions['object_id'] = $iOriginalId; // folder subscription, this _was_ the document. + } else { + $aNotificationOptions['object_id'] = $iExternalId; // this path _shouldn't_ be hit since its a new document - user _can't_ be subscribed to it already. + } + $aNotificationOptions['event_type'] = "ModifyDocument"; + } else if (SubscriptionConstants::subscriptionAlertTypeString($iSubscriptionAlertType) == "CheckInDocument") { + $aNotificationOptions['target_name'] = $aValues["modifiedDocumentName"]; + $aNotificationOptions['location_name'] = null; // not used... why? don't we have the folder name? why not? + if ($iOriginalId !== null) { + $aNotificationOptions['object_id'] = $iOriginalId; // folder subscription, this _was_ the document. + } else { + $aNotificationOptions['object_id'] = $iExternalId; // this path _shouldn't_ be hit since its a new document - user _can't_ be subscribed to it already. + }$aNotificationOptions['event_type'] = "CheckInDocument"; + } else if (SubscriptionConstants::subscriptionAlertTypeString($iSubscriptionAlertType) == "CheckOutDocument") { + $aNotificationOptions['target_name'] = $aValues["modifiedDocumentName"]; + $aNotificationOptions['location_name'] = null; // not used... why? don't we have the folder name? why not? + if ($iOriginalId !== null) { + $aNotificationOptions['object_id'] = $iOriginalId; // folder subscription, this _was_ the document. + } else { + $aNotificationOptions['object_id'] = $iExternalId; // this path _shouldn't_ be hit since its a new document - user _can't_ be subscribed to it already. + }$aNotificationOptions['event_type'] = "CheckOutDocument"; + } else if (SubscriptionConstants::subscriptionAlertTypeString($iSubscriptionAlertType) == "MovedDocument") { + $aNotificationOptions['target_name'] = $aValues["modifiedDocumentName"]; + $aNotificationOptions['location_name'] = $aValues["oldFolderName"]; + if ($iOriginalId !== null) { + $aNotificationOptions['object_id'] = $iOriginalId; // folder subscription, this _was_ the document. + } else { + $aNotificationOptions['object_id'] = $iExternalId; + }$aNotificationOptions['event_type'] = "MovedDocument"; + } else if (SubscriptionConstants::subscriptionAlertTypeString($iSubscriptionAlertType) == "ArchivedDocument") { + $aNotificationOptions['target_name'] = $aValues["modifiedDocumentName"]; + $aNotificationOptions['location_name'] = $aValues["folderName"]; + if ($iOriginalId !== null) { + $aNotificationOptions['object_id'] = $iOriginalId; // folder subscription, this _was_ the document. + } else { + $aNotificationOptions['object_id'] = $iExternalId; + } + $aNotificationOptions['event_type'] = "ArchivedDocument"; + } else if (SubscriptionConstants::subscriptionAlertTypeString($iSubscriptionAlertType) == "RestoredArchivedDocument") { + $aNotificationOptions['target_name'] = $aValues["modifiedDocumentName"]; + $aNotificationOptions['location_name'] = null; // $aValues["folderName"]; // not reachable. + if ($iOriginalId !== null) { + $aNotificationOptions['object_id'] = $iOriginalId; // folder subscription, this _was_ the document. + } else { + $aNotificationOptions['object_id'] = $iExternalId; + } + $aNotificationOptions['event_type'] = "RestoredArchivedDocument"; + } + + $oNotification =& KTSubscriptionNotification::generateSubscriptionNotification($aNotificationOptions); + + // email alert. if ($aSubscribers[$i]->getEmailNotification() && (strlen($aSubscribers[$i]->getEmail()) > 0)) { $oEmail = new EmailAlert($aSubscribers[$i]->getEmail(), $sAlertContent["subject"], $sAlertContent["text"]); @@ -135,7 +243,7 @@ class SubscriptionEngine { $iSubscriptionsSent += $iThisSubscriptionsSent; } } - + // return the number of processed subscriptions return $iSubscriptionsSent; } @@ -184,6 +292,7 @@ class SubscriptionEngine { $_SESSION["errorMessage"] = $lang_err_database; return false; } + $default->log->debug('retrieveSubscribers found count=' . count($aUsers)); return $aUsers; } } diff --git a/notify.php b/notify.php new file mode 100644 index 0000000..5c32728 --- /dev/null +++ b/notify.php @@ -0,0 +1,43 @@ +notification =& $oKTNotification; + + return true; + } + function do_main() { + // get the notification-handler, instantiate it, call resolveNotification. + return $this->notification->resolve(); + } +} + +$dispatcher =& new KTNotificationDispatcher(); +$dispatcher->dispatch(); \ No newline at end of file diff --git a/plugins/ktstandard/KTSubscriptions.php b/plugins/ktstandard/KTSubscriptions.php index d36e785..3e9e69e 100644 --- a/plugins/ktstandard/KTSubscriptions.php +++ b/plugins/ktstandard/KTSubscriptions.php @@ -240,7 +240,8 @@ class KTArchiveSubscriptionTrigger { SubscriptionConstants::subscriptionType("DocumentSubscription"), array( "folderID" => $oDocument->getFolderID(), - "modifiedDocumentName" => $oDocument->getName() + "modifiedDocumentName" => $oDocument->getName(), + "folderName" => $oDocument->getFolderName(), )); $default->log->info("archiveDocumentBL.php fired $count subscription alerts for archived document " . $oDocument->getName()); } diff --git a/sql/mysql/install/structure.sql b/sql/mysql/install/structure.sql index ab361f4..5ee4a10 100644 --- a/sql/mysql/install/structure.sql +++ b/sql/mysql/install/structure.sql @@ -800,10 +800,10 @@ CREATE TABLE `notifications` ( `label` varchar(255) NOT NULL default '', `type` varchar(255) NOT NULL default '', `creation_date` datetime NOT NULL default '0000-00-00 00:00:00', - `data_int_1` int(11) NOT NULL default '0', - `data_int_2` int(11) NOT NULL default '0', - `data_str_1` varchar(255) NOT NULL default '', - `data_str_2` varchar(255) NOT NULL default '', + `data_int_1` int(11), + `data_int_2` int(11), + `data_str_1` varchar(255), + `data_str_2` varchar(255), UNIQUE KEY `id` (`id`), KEY `type` (`type`), KEY `user_id` (`user_id`) diff --git a/templates/kt3/notifications/subscriptions.smarty b/templates/kt3/notifications/subscriptions.smarty index 69ee3aa..6b9bda4 100644 --- a/templates/kt3/notifications/subscriptions.smarty +++ b/templates/kt3/notifications/subscriptions.smarty @@ -1,5 +1,12 @@ -
Subscription Alert: {$context->getLabel()}
+
{$info.title}
- This document was updated in the folder "{$context->getStrData1()}", to which you are subscribed. - + +{if ($info.event_type == 'AddDocument')} + The document "{$info.object_name}" was added{if ($info.location_name !== null)} to "{$info.location_name}"{/if}. + +{/if} +
\ No newline at end of file