# Changes for page Activity Stream

Last modified by superadmin on 2017/11/28 14:52

#### Export

#### Office Formats

Export as PDF Export as ODT Export as RTF

#### Other Formats

Export as HTML
From empty
To version 1.1
edited by superadmin
on 2017/11/28 14:52
Change comment: Install extension [org.xwiki.platform:xwiki-platform-activitystream-ui/9.9]

## Summary

- Page properties (6 modified, 0 added, 0 removed)
- Objects (0 modified, 12 added, 0 removed)
  - XWiki.JavaScriptExtension[0]
  - XWiki.RequiredRightClass[0]
  - XWiki.StyleSheetExtension[0]
  - XWiki.WikiMacroClass[0]
  - XWiki.WikiMacroParameterClass[0]
  - XWiki.WikiMacroParameterClass[1]
  - XWiki.WikiMacroParameterClass[2]
  - XWiki.WikiMacroParameterClass[3]
  - XWiki.WikiMacroParameterClass[4]
  - XWiki.WikiMacroParameterClass[5]
  - XWiki.WikiMacroParameterClass[6]
  - XWiki.WikiMacroParameterClass[7]

## Details

Page properties
  Title

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +$services.localization.render('xe.activity')

  Parent

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +Main.WebHome

  Author

    ... | ... | @@ -1,1 +1,1 @@
    1 |  | -XWiki.XWikiGuest
    | 1 | +xwiki:XWiki.superadmin

  Syntax

    ... | ... | @@ -1,1 +1,1 @@
    1 |  | -XWiki 2.1
    | 1 | +XWiki 2.0

  Hidden

    ... | ... | @@ -1,1 +1,1 @@
    1 |  | -false
    | 1 | +true

  Content

    ... | ... | @@ -1,0 +1,19 @@
    | 1 | +{{velocity}}
    | 2 | +##
    | 3 | +## Activity Macro
    | 4 | +##
    | 5 | +## Optional parameters :
    | 6 | +##
    | 7 | +## - entries (String set to a numerical value): number of entries to display the activity for.
    | 8 | +## - subentries (String set to a numerical value): number of activities to show for each entry.
    | 9 | +## - wikis (List of string[s]): comma separated list of wikis to display activity for.
    | 10 | +## - spaces (List of string[s]): comma separated list of local serialized references of the spaces to display the activity for.
    | 11 | +## - authors (List of string[s]): comma separated list of authors whose modifications to show.
    | 12 | +## - tags (List of string[s]): comma separated list of tags to display activity for.
    | 13 | +## - minor (String set to "true" or "false"): whether to show modifications that create minor versions or not.
    | 14 | +## - rss (String set to "true" or "false"): whether to show activity rss link or not.
    | 15 | +## - messageSender (String set to "true" or "false"): whether or not to display the message sender input.
    | 16 | +##
    | 17 | +{{/velocity}}
    | 18 | +
    | 19 | +{{activity /}}

XWiki.JavaScriptExtension[0]
  Caching policy

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +long

  Code

    ... | ... | @@ -1,0 +1,125 @@
    | 1 | +var XWiki = (function (XWiki) {
    | 2 | +// Start XWiki augmentation.
    | 3 | +XWiki.Activity = Class.create({
    | 4 | + initialize : function() {
    | 5 | + this.initHandlers();
    | 6 | + this.attachDeleteHandlers();
    | 7 | + },
    | 8 | + initHandlers: function() {
    | 9 | + $$('.activityHeader').each(function(activityHeader){
    | 10 | + if(activityHeader.up().hasClassName('collapsed')){
    | 11 | + activityHeader.observe('click', this.toggle.bind(this, activityHeader.up()));
    | 12 | + }
    | 13 | + }.bind(this));
    | 14 | +
    | 15 | + document.observe('xwiki:activity:newActivity', function(event) {
    | 16 | + // Update all activity streams to display the new message
    | 17 | + $$('.activity').each(this.updateActivity.bind(this));
    | 18 | + }.bind(this));
    | 19 | + },
    | 20 | + toggle: function() {
    | 21 | + var collapsable = arguments[0];
    | 22 | + if (collapsable.hasClassName('collapsed')) {
    | 23 | + collapsable.removeClassName('collapsed');
    | 24 | + collapsable.down('.toolExpand').update('&#9650;');
    | 25 | + } else {
    | 26 | + collapsable.addClassName('collapsed');
    | 27 | + collapsable.down('.toolExpand').update('&#9660;');
    | 28 | + }
    | 29 | + },
    | 30 | + updateActivity: function(activityContainer) {
    | 31 | + var updateURL = activityContainer.down('.updateURL').value;
    | 32 | + new Ajax.Updater(
    | 33 | + {success: activityContainer},
    | 34 | + updateURL, {
    | 35 | + onCreate: function() {
    | 36 | + // NOTE: Do we need any progress/success notification at this level or do we do it only in the "calling" component?
    | 37 | + //activityContainer._notification = new XWiki.widgets.Notification("$services.localization.render('core.widgets.confirmationBox.notification.inProgress')", 'inprogress');
    | 38 | + },
    | 39 | + onSuccess: function() {
    | 40 | + if (activityContainer && activityContainer._notification) {
    | 41 | + activityContainer._notification.hide();
    | 42 | + }
    | 43 | + },
    | 44 | + onFailure: function(response) {
    | 45 | + var failureReason = '';
    | 46 | + if (response.statusText == '' /* No response */ || response.status == 12031 /* In IE */) {
    | 47 | + failureReason = 'Server not responding';
    | 48 | + } else {
    | 49 | + failureReason = response.statusText;
    | 50 | + }
    | 51 | + if (activityContainer) {
    | 52 | + if (activityContainer._notification) {
    | 53 | + activityContainer._notification.hide();
    | 54 | + }
    | 55 | + activityContainer._notification = new XWiki.widgets.Notification("$services.localization.render('core.widgets.confirmationBox.notification.failed')" + failureReason, 'error');
    | 56 | + }
    | 57 | + },
    | 58 | + on1223 : function(response) { /*SUCCESS*/
    | 59 | + response.request.options.onSuccess(response);
    | 60 | + },
    | 61 | + on0 : function(response) { /*FAILURE*/
    | 62 | + response.request.options.onFailure(response);
    | 63 | + },
    | 64 | + onComplete: function(response) {
    | 65 | + // The newly retrieved activity element is inserted after onSuccess is called, so we must handle it here instead of in onSuccess.
    | 66 | + if (response.request.success()) {
    | 67 | + // The remplacement activity has a default updateURL. We discard that and replace it with the updateURL of the original activity so that, at each update, the URL stays the same.
    | 68 | + var newActivity = activityContainer.next();
    | 69 | + if (newActivity) {
    | 70 | + newActivity.down('.updateURL').value = updateURL;
    | 71 | + }
    | 72 | +
    | 73 | + // Remove the old activity element.
    | 74 | + activityContainer.remove();
    | 75 | +
    | 76 | + this.attachDeleteHandlers();
    | 77 | + }
    | 78 | + }.bind(this),
    | 79 | + insertion: 'after'
    | 80 | + }
    | 81 | + );
    | 82 | + },
    | 83 | + attachDeleteHandlers : function () {
    | 84 | + $$('.activity .activityItem .toolDelete').invoke('observe', 'click', function (event) {
    | 85 | + event.stop();
    | 86 | + var trigger = event.element();
    | 87 | + trigger.blur();
    | 88 | + if (trigger.disabled) {
    | 89 | + // Do nothing if the button was already clicked and it's waiting for a response from the server.
    | 90 | + return;
    | 91 | + } else {
    | 92 | + new XWiki.widgets.ConfirmedAjaxRequest(
    | 93 | + /* Ajax request URL */
    | 94 | + trigger.href.replace(/xredirect=.*$/, 'xpage=plain&confirm=1'),
    | 95 | + /* Ajax request parameters */
    | 96 | + {
    | 97 | + onCreate : function() {
    | 98 | + // Disable the button, to avoid a cascade of clicks from impatient users
    | 99 | + trigger.disabled = true;
    | 100 | + },
    | 101 | + onSuccess : function() {
    | 102 | + // Remove the corresponding HTML element from the UI
    | 103 | + trigger.up("li").remove();
    | 104 | + },
    | 105 | + onComplete : function() {
    | 106 | + // In the end: re-enable the button
    | 107 | + trigger.disabled = false;
    | 108 | + }
    | 109 | + },
    | 110 | + /* Interaction parameters */
    | 111 | + {
    | 112 | + confirmationText: "$services.localization.render('xe.activity.messages.delete.confirm')"
    | 113 | + }
    | 114 | + );
    | 115 | + }
    | 116 | + });
    | 117 | + }
    | 118 | +});
    | 119 | +// End XWiki augmentation.
    | 120 | +return XWiki;
    | 121 | +}(XWiki || {}));
    | 122 | +
    | 123 | +document.observe('xwiki:dom:loaded', function() {
    | 124 | + new XWiki.Activity();
    | 125 | +});

  Name

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +Expand and Collapse

  Parse content

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +Yes

  Use this extension

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +onDemand

XWiki.RequiredRightClass[0]
  level

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +programming

XWiki.StyleSheetExtension[0]
  Caching policy

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +long

  Code

    ... | ... | @@ -1,0 +1,280 @@
    | 1 | +#template('colorThemeInit.vm')
    | 2 | +.rssURL{
    | 3 | + padding-top: 5px;
    | 4 | + text-align: right;
    | 5 | +}
    | 6 | +
    | 7 | +.rssURL img {
    | 8 | + margin-right: 5px;
    | 9 | +}
    | 10 | +
    | 11 | +ul.activityList {
    | 12 | + list-style: none;
    | 13 | + margin: 0;
    | 14 | + padding: 0;
    | 15 | +}
    | 16 | +
    | 17 | +.activityItem {
    | 18 | + margin: 0 0 5px;
    | 19 | + padding: 5px 0;
    | 20 | + position: relative;
    | 21 | +}
    | 22 | +
    | 23 | +.activityItem .activityItem {
    | 24 | + margin: 0;
    | 25 | +}
    | 26 | +
    | 27 | +.activityPage:hover , .activityApplication:hover , .activityUser:hover {
    | 28 | + background: $theme.highlightColor;
    | 29 | +}
    | 30 | +
    | 31 | +.activityApplication, .activityUser, .activityPage .activityItem {
    | 32 | + display: block;
    | 33 | + padding-left: 75px;
    | 34 | + position: relative;
    | 35 | +}
    | 36 | +
    | 37 | +.activityWiki {
    | 38 | + color: $theme.textSecondaryColor;
    | 39 | + font-size: .875em;
    | 40 | + padding-right: .2em;
    | 41 | +}
    | 42 | +
    | 43 | +.activityUser {
    | 44 | + padding-left: 0;
    | 45 | + width: 100%;
    | 46 | +}
    | 47 | +.activityUser .activityContent {
    | 48 | + padding: 0.5em 0 0.5em 75px;
    | 49 | +}
    | 50 | +
    | 51 | +.activitySummary {
    | 52 | + min-height: 35px;
    | 53 | + padding-top: 5px;
    | 54 | +}
    | 55 | +
    | 56 | +.activitySummary , .activityPage .activityAction {
    | 57 | + padding-left: 25px;
    | 58 | +}
    | 59 | +
    | 60 | +.activityPage ul .activityAction {
    | 61 | + padding-left: 0;
    | 62 | +}
    | 63 | +
    | 64 | +.activityAuthor , .activityAction{
    | 65 | + display: block;
    | 66 | +}
    | 67 | +
    | 68 | +.activityAuthor {
    | 69 | + padding-right: 8em;
    | 70 | +}
    | 71 | +
    | 72 | +.activityPage .activityAuthor {
    | 73 | + padding-left: 25px;
    | 74 | + text-indent: -25px;
    | 75 | +}
    | 76 | +
    | 77 | +.activityPage .activityContent .activityAuthor {
    | 78 | + padding-left: 0;
    | 79 | + text-indent: 0;
    | 80 | +}
    | 81 | +
    | 82 | +.activityAction, .activityTime {
    | 83 | + color: $theme.textSecondaryColor;
    | 84 | + font-size: .8em;
    | 85 | +}
    | 86 | +
    | 87 | +.activityTime {
    | 88 | + padding: 5px 15px 0 0;
    | 89 | + position: absolute;
    | 90 | + right: 0;
    | 91 | + top: 0;
    | 92 | +}
    | 93 | +
    | 94 | +.activitySnapshot {
    | 95 | + height: 30px;
    | 96 | + left: 0;
    | 97 | + margin: 5px 0 0 25px;
    | 98 | + position: absolute;
    | 99 | +}
    | 100 | +
    | 101 | +.activitySummary .activitySnapshot {
    | 102 | + display: inline-block;
    | 103 | + margin: 0 17px 0 0;
    | 104 | + position: relative;
    | 105 | +}
    | 106 | +
    | 107 | +.activityItem .activityItem .activitySnapshot {
    | 108 | + margin-top: 2px;
    | 109 | +}
    | 110 | +
    | 111 | +.activitySnapshot img {
    | 112 | + border-radius: 4px;
    | 113 | + height: 30px;
    | 114 | + width: 30px;
    | 115 | +}
    | 116 | +
    | 117 | +.activitySnapshot .activityActionAvatar {
    | 118 | + background: $theme.pageContentBackgroundColor;
    | 119 | + border-color: $theme.pageContentBackgroundColor;
    | 120 | + border-style: solid;
    | 121 | + border-width: 4px 1px 1px 3px;
    | 122 | + box-sizing: content-box;
    | 123 | + bottom: -5px;
    | 124 | + height: 16px;
    | 125 | + position: absolute;
    | 126 | + right: -10px;
    | 127 | + width: 16px;
    | 128 | +}
    | 129 | +
    | 130 | +/** <start> Inside activityContent **/
    | 131 | +.activityApplication .activityContent,
    | 132 | +.activityUser .activityContent,
    | 133 | +.activityList .annotated,
    | 134 | +.activityList .annotation,
    | 135 | +.activityList .attachment,
    | 136 | +.activityList .comment {
    | 137 | + font-size: .9em;
    | 138 | + padding-right: 15px;
    | 139 | +}
    | 140 | +
    | 141 | +.collapsed .activityContent, .activitySummary {
    | 142 | + display: none;
    | 143 | +}
    | 144 | +
    | 145 | +.collapsed .activitySummary {
    | 146 | + display: block;
    | 147 | +}
    | 148 | +
    | 149 | +.activityList .annotated {
    | 150 | + background-color: #FFEE99;
    | 151 | + padding: 3px 15px 3px 5px;
    | 152 | +}
    | 153 | +
    | 154 | +.activityList .attachment {
    | 155 | + margin: 0;
    | 156 | + padding: 3px 0 0;
    | 157 | +}
    | 158 | +
    | 159 | +.activityList .message {
    | 160 | + /* Preserve the white-space because messages are written in plain text. */
    | 161 | + white-space: pre-wrap;
    | 162 | +}
    | 163 | +/** <end> **/
    | 164 | +
    | 165 | +/** <start> Tools **/
    | 166 | +/** <start> Expand Tool **/
    | 167 | +.toolExpand{
    | 168 | + color: $theme.textColor;
    | 169 | + cursor: pointer;
    | 170 | + font-size: .6em;
    | 171 | + padding: 0 3px;
    | 172 | + position: absolute;
    | 173 | + right: 0;
    | 174 | + top: 7px;
    | 175 | + visibility: hidden;
    | 176 | +}
    | 177 | +
    | 178 | +.toolExpand:hover {
    | 179 | + text-decoration: none;
    | 180 | +}
    | 181 | +
    | 182 | +.activityList:hover .toolExpand {
    | 183 | + visibility: visible;
    | 184 | +}
    | 185 | +/** </end> Expand Tool **/
    | 186 | +/** <start> Filter Tool **/
    | 187 | +.toolFilter {
    | 188 | + cursor: pointer;
    | 189 | + float: right;
    | 190 | + padding-right: 13px;
    | 191 | + position: relative;
    | 192 | +}
    | 193 | +
    | 194 | +.toolFilter .toolExpand{
    | 195 | + color: transparent;
    | 196 | +}
    | 197 | +
    | 198 | +h1:hover .toolFilter .toolExpand {
    | 199 | + color: $theme.textColor;
    | 200 | +}
    | 201 | +
    | 202 | +.toolFilter .toolExpand {
    | 203 | + font-size: 0.3em;
    | 204 | + line-height: 4em;
    | 205 | + top: 5px;
    | 206 | +}
    | 207 | +/** </end> Filter Tool **/
    | 208 | +/** <start> Delete Tool **/
    | 209 | +.toolDelete {
    | 210 | + color: $theme.notificationErrorColor !important;
    | 211 | + cursor: pointer;
    | 212 | + font-size: .9em;
    | 213 | + font-weight: bold;
    | 214 | + padding: 0 4px;
    | 215 | + position: absolute;
    | 216 | + right: 0;
    | 217 | + top: 2px;
    | 218 | + visibility: hidden;
    | 219 | + z-index: 1;
    | 220 | +}
    | 221 | +
    | 222 | +.activityItem:hover .toolDelete {
    | 223 | + visibility: visible;
    | 224 | +}
    | 225 | +
    | 226 | +.toolDelete:hover {
    | 227 | + text-decoration: none;
    | 228 | +}
    | 229 | +/** </end> Delete Tool **/
    | 230 | +/** </end> Tools **/
    | 231 | +
    | 232 | +/** <start> Action Types **/
    | 233 | +.type {
    | 234 | + background: none no-repeat scroll 4px 0 transparent;
    | 235 | + padding-left: 25px;
    | 236 | +}
    | 237 | +
    | 238 | +.typePage {
    | 239 | + background-image:url("$xwiki.getSkinFile('icons/silk/page_white_text.png')");
    | 240 | +}
    | 241 | +
    | 242 | +/** </end> Action Types **/
    | 243 | +
    | 244 | +/** CSS 3**/
    | 245 | +.activitySnapshot .activityActionAvatar {
    | 246 | + border-radius: 10px 10px 0 10px;
    | 247 | + box-shadow: -1px 1px 1px rgba(128, 128, 128, 0.6);
    | 248 | +}
    | 249 | +
    | 250 | +.activitySnapshot img {
    | 251 | + box-shadow: 0 0 2px 1px rgba(128, 128, 128, 0.6);
    | 252 | +}
    | 253 | +
    | 254 | +/** IE7 **/
    | 255 | +* html .activityItem , * html .activityHeader, * html .activityContent ul{
    | 256 | + display: inline-block;
    | 257 | +}
    | 258 | +
    | 259 | +* html .activityAuthor {
    | 260 | + float: left;
    | 261 | +}
    | 262 | +*+html .activityAuthor {
    | 263 | + float: left;
    | 264 | +}
    | 265 | +
    | 266 | +* html .activityTime {
    | 267 | + float: right;
    | 268 | +}
    | 269 | +
    | 270 | +* html .activityItem {
    | 271 | + float: right;
    | 272 | +}
    | 273 | +
    | 274 | +* html .activityContent {
    | 275 | + line-height: 1%;
    | 276 | +}
    | 277 | +
    | 278 | +* html .activityAction {
    | 279 | + clear: left;
    | 280 | +}

  Content Type

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +CSS

  Name

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +Activity CSS

  Parse content

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +Yes

  Use this extension

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +onDemand

XWiki.WikiMacroClass[0]
  Macro code

    ... | ... | @@ -1,0 +1,996 @@
    | 1 | +{{template name="hierarchy_macros.vm" /}}
    | 2 | +
    | 3 | +{{velocity output=false}}
    | 4 | +##
    | 5 | +##
    | 6 | +## Skin Extensions
    | 7 | +##--------------------------------------------------------------
    | 8 | +$xwiki.jsx.use('Main.Activity')##
    | 9 | +$xwiki.ssx.use('Main.Activity')##
    | 10 | +##
    | 11 | +## Icons
    | 12 | +##--------------------------------------------------------------
    | 13 | +## Some events are not implemented yet in the activitystream plugin: TODO: application | activityApplication, user | activityUser
    | 14 | +#set ($activityActionsMap = {
    | 15 | + 'create': 'icons/silk/page_white_add.png',
    | 16 | + 'delete': 'icons/silk/page_white_delete.png',
    | 17 | + 'update': 'icons/silk/page_white_edit.png',
    | 18 | + 'addAnnotation': 'icons/silk/note_add.png',
    | 19 | + 'deleteAnnotation': 'icons/silk/note_delete.png',
    | 20 | + 'updateAnnotation': 'icons/silk/note_edit.png',
    | 21 | + 'addAttachment': 'icons/xwiki/attach_add.png',
    | 22 | + 'deleteAttachment': 'icons/xwiki/attach_delete.png',
    | 23 | + 'updateAttachment': 'icons/xwiki/attach_edit.png',
    | 24 | + 'addComment': 'icons/silk/comment_add.png',
    | 25 | + 'deleteComment': 'icons/silk/comment_delete.png',
    | 26 | + 'updateComment': 'icons/silk/comment_edit.png',
    | 27 | + 'publicMessage': 'icons/silk/user_comment.png',
    | 28 | + 'personalMessage': 'icons/silk/user_comment.png',
    | 29 | + 'directMessage': 'icons/silk/user_go.png',
    | 30 | + 'groupMessage': 'icons/silk/group_go.png'
    | 31 | +})
    | 32 | +#macro (initDateVariables)
    | 33 | +##
    | 34 | +## Date Time
    | 35 | +##--------------------------------------------------------------
    | 36 | +#set ($currentTime = $xwiki.jodatime.getDateTime())
    | 37 | +#set ($now = $datetool.date)
    | 38 | +#set ($todayYear = $datetool.getYear($now))
    | 39 | +#set ($todayMonth = $datetool.getMonth($now) + 1)
    | 40 | +#set ($todayMidnight = $currentTime.withTimeAtStartOfDay())
    | 41 | +#set ($yesterdayMidnight = $todayMidnight.minusDays(1))
    | 42 | +#end
    | 43 | +##
    | 44 | +## Formatters
    | 45 | +##--------------------------------------------------------------
    | 46 | +#set ($eventDateFormatter = 'dd MMM, HH:mm')
    | 47 | +#set ($eventFullDateFormatter = 'yyyy/MM/dd HH:mm')
    | 48 | +#set ($activityListDateFormatter = 'dd MMM')
    | 49 | +##
    | 50 | +## User status configuration
    | 51 | +## -------------------------------------------------------------
    | 52 | +#set ($isMessageStreamActive = $services.messageStream.isActive())
    | 53 | +## TODO: filter out user statuses from the activity stream if inactive
    | 54 | +##
    | 55 | +##
    | 56 | +#set ($currentUser = $services.model.serialize($xcontext.userReference, 'default'))
    | 57 | +##
    | 58 | +## Filter clause
    | 59 | +##--------------------------------------------------------------
    | 60 | +#set ($filterClause = '1=1')
    | 61 | +#set ($filterParameters = [])
    | 62 | +##
    | 63 | +## Get values from request/macro param
    | 64 | +##--------------------------------------------------------------
    | 65 | +## Number of entries to display
    | 66 | +#readVariableFromRequest('aEntries' 'entries' 20)
    | 67 | +#if ($aEntries == 20 && $xcontext.macro.params.get('entries'))
    | 68 | + #set ($aEntries = $mathtool.toInteger($xcontext.macro.params.get('entries')))
    | 69 | +#end
    | 70 | +## Number of activities to display for each entry
    | 71 | +#readVariableFromRequest('aSubEntries' 'subentries' 10)
    | 72 | +#if ($aSubEntries == 10 && $xcontext.macro.params.get('subentries'))
    | 73 | + #set ($aSubEntries = $mathtool.toInteger($xcontext.macro.params.get('subentries')))
    | 74 | +#end
    | 75 | +## Wikis
    | 76 | +#readVariableFromRequest('aWikis' 'wikis' [])
    | 77 | +#if ($aWikis.size() == 0)
    | 78 | + #set ($aWikisString = $xcontext.macro.params.get('wikis'))
    | 79 | + #asList('aWikis' $aWikisString)
    | 80 | + #if ($aWikis.size() == 0)
    | 81 | + ## if no wikis has been set and we are in the main wiki, then we should try to list every wikis where the user has view right
    | 82 | + #if ("$!services.wiki" != '' && $services.wiki.mainWikiId == $services.wiki.currentWikiId)
    | 83 | + #set ($allWikis = $services.wiki.getAll())
    | 84 | + #foreach ($wiki in $allWikis)
    | 85 | + #if ($xwiki.hasAccessLevel('view', $xcontext.userReference, $wiki.mainPageReference))
    | 86 | + $aWikis.add($wiki.id)
    | 87 | + #end
    | 88 | + #end
    | 89 | + #else
    | 90 | + ## else, only check the current wiki
    | 91 | + $aWikis.add($xcontext.database)
    | 92 | + #end
    | 93 | + #end
    | 94 | +#end
    | 95 | +## Spaces
    | 96 | +#readVariableFromRequest('aSpaces' 'spaces' [])
    | 97 | +#if ($aSpaces.size() == 0)
    | 98 | + #set ($aSpacesString = $xcontext.macro.params.get('spaces'))
    | 99 | + #asList('aSpaces' $aSpacesString)
    | 100 | +#end
    | 101 | +## Authors
    | 102 | +#readVariableFromRequest('aAuthors' 'authors' [])
    | 103 | +#if ($aAuthors.size() == 0)
    | 104 | + #set ($aAuthorsString = $xcontext.macro.params.get('authors'))
    | 105 | + #asList('aAuthors' $aAuthorsString)
    | 106 | +#end
    | 107 | +## Tags
    | 108 | +#readVariableFromRequest('aTags' 'tags' [])
    | 109 | +#if ($aTags.size() == 0)
    | 110 | + #set ($aTagsString = $xcontext.macro.params.get('tags'))
    | 111 | + #asList('aTags' $aTagsString)
    | 112 | +#end
    | 113 | +## Minor versions
    | 114 | +#readVariableFromRequest('aMinor' 'minor' false)
    | 115 | +#if (!$aMinor && $xcontext.macro.params.get('minor'))
    | 116 | + #set ($aMinor = $xcontext.macro.params.get('minor'))
    | 117 | +#end
    | 118 | +## RSS link
    | 119 | +#readVariableFromRequest('aRSS' 'rss' false)
    | 120 | +#if (!$aRSS && $xcontext.macro.params.get('rss'))
    | 121 | + #set ($aRSS = $xcontext.macro.params.get('rss'))
    | 122 | +#end
    | 123 | +##
    | 124 | +## Filter by
    | 125 | +## ------------------------------------------------------------
    | 126 | +## Authors
    | 127 | +#if ($aAuthors.size() > 0)
    | 128 | + #set ($toUsersParameters = [])
    | 129 | + #set ($fromUsersParameters = [])
    | 130 | + #set ($usersQuery = '')
    | 131 | + #set ($hasDifferentUsers = false)
    | 132 | + #foreach ($u in $aAuthors)
    | 133 | + #if ("$!u" != '')
    | 134 | + #set ($usersQuery = $usersQuery.concat(',?'))
    | 135 | + #set ($resolvedUser = $services.model.serialize($services.model.resolveDocument($u), 'default'))
    | 136 | + #set ($discard = $fromUsersParameters.add($resolvedUser))
    | 137 | + #set ($discard = $toUsersParameters.add("${resolvedUser}^XWiki.XWikiUsers"))
    | 138 | + #if ($resolvedUser != $currentUser)
    | 139 | + #set ($hasDifferentUsers = true)
    | 140 | + #end
    | 141 | + #end
    | 142 | + #end
    | 143 | + #if ($usersQuery.length() > 1)
    | 144 | + ## Remove the first comma
    | 145 | + #set ($usersQuery = $usersQuery.substring(1))
    | 146 | + #if (!$isGuest)
    | 147 | + #if ($hasDifferentUsers)
    | 148 | + ## Direct messages from the current user to the filtered users
    | 149 | + #set ($filterClause = $filterClause + " and ((act.application = 'MessageStream' and act.type = 'directMessage' and act.user = ? and act.param2 in (${usersQuery})) or act.user in (${usersQuery}))")
    | 150 | + #set ($discard = $filterParameters.add($currentUser))
    | 151 | + #set ($discard = $filterParameters.addAll($toUsersParameters))
    | 152 | + #else
    | 153 | + ## All direct messages to the current user
    | 154 | + #set ($filterClause = $filterClause + " and ((act.application = 'MessageStream' and act.type = 'directMessage' and act.param2 = ?) or act.user in (${usersQuery}))")
    | 155 | + #set ($discard = $filterParameters.add("${currentUser}^XWiki.XWikiUsers"))
    | 156 | + #end
    | 157 | + #else
    | 158 | + #set ($filterClause = $filterClause + " and act.user in (${usersQuery})")
    | 159 | + #end
    | 160 | + #set ($discard = $filterParameters.addAll($fromUsersParameters))
    | 161 | + #end
    | 162 | +#end
    | 163 | +## Spaces
    | 164 | +#updateFilterClause('space' $aSpaces false)
    | 165 | +## BLACKLISTED SPACES - exclude them
    | 166 | +#if ($blacklistedSpaces && $blacklistedSpaces.size() > 0)
    | 167 | + #set ($filterClause = $filterClause + ' AND (act.application = ''MessageStream'' OR 1=1 ')
    | 168 | + #updateFilterClause('space' $blacklistedSpaces true)
    | 169 | + #set ($filterClause = $filterClause + ')')
    | 170 | +#end
    | 171 | +## Wikis
    | 172 | +#updateFilterClause('wiki' $aWikis false)
    | 173 | +## Tags
    | 174 | +#if ($xwiki.getPlugin('tag') && $aTags.size() > 0)
    | 175 | + #set ($docsWithTag = [])
    | 176 | + #foreach ($item in $aTags)
    | 177 | + #set ($discard = $docsWithTag.addAll($xwiki.tag.getDocumentsWithTag($item)))
    | 178 | + #end
    | 179 | + ## Since there is no 'tag' field in the database schema, we can only use the page field in order to filter the query. In order to get no results when there are no documents with the requested tags, we use a fake document name
    | 180 | + #if ($docsWithTag.size() == 0)
    | 181 | + #set ($discard = $docsWithTag.add("XWiki.FakeDocumentToMakeQueryReturnNoResults${mathtool.random(1,999)}"))
    | 182 | + #end
    | 183 | + #updateFilterClause('page' $docsWithTag false)
    | 184 | +#end
    | 185 | +## Minor versions
    | 186 | +#if ("$!aMinor" == 'false')
    | 187 | + #set ($filterClause = $filterClause + " and not (act.version not like '%.1' and act.type = 'update')")
    | 188 | +#end
    | 189 | +##
    | 190 | +## MessageStream filtering
    | 191 | +## ------------------------------------------------------------
    | 192 | +## The scope filtering is already handled by the "Filter by" section: specific space, specific user...
    | 193 | +##
    | 194 | +## Public messages should be visible to everyone
    | 195 | +#set ($messageFilter = "act.type = 'publicMessage'")
    | 196 | +#if ($isMessageStreamActive)
    | 197 | + #if (!$isGuest)
    | 198 | + ##
    | 199 | + ## A user should see all his messages
    | 200 | + #set ($messageFilter = "$messageFilter or act.user = ?")
    | 201 | + #set ($discard = $filterParameters.add(${currentUser}))
    | 202 | + ##
    | 203 | + ## A user should see direct messages for him
    | 204 | + #set ($messageFilter = "$messageFilter or (act.type = 'directMessage' and act.param2 = ?)")
    | 205 | + #set ($discard = $filterParameters.add("${currentUser}^XWiki.XWikiUsers"))
    | 206 | + ##
    | 207 | + ## A user should see personal messages from followed users
    | 208 | + #set ($followedUsers = $services.watchlist.getWatchedElements("USER"))
    | 209 | + #if ($followedUsers.size() > 0)
    | 210 | + #set ($messageFilter = "$messageFilter or (act.type = 'personalMessage' and act.user in (")
    | 211 | + #foreach ($u in $followedUsers)
    | 212 | + #if ($velocityCount > 1)
    | 213 | + #set ($messageFilter = $messageFilter.concat(','))
    | 214 | + #end
    | 215 | + #set ($messageFilter = $messageFilter.concat('?'))
    | 216 | + #set ($discard = $filterParameters.add($u))
    | 217 | + #end
    | 218 | + #set ($messageFilter = $messageFilter.concat('))'))
    | 219 | + #end
    | 220 | + ##
    | 221 | + ## A user should see messages for his groups
    | 222 | + #set ($usersGroups = $xwiki.rightsmanager.getAllGroupsNamesForMember($xcontext.user))
    | 223 | + #if ($usersGroups.size() > 0)
    | 224 | + #set ($messageFilter = "$messageFilter or (act.type = 'groupMessage' and act.param2 in (")
    | 225 | + #foreach ($g in $usersGroups)
    | 226 | + #if ($velocityCount > 1)
    | 227 | + #set ($messageFilter = $messageFilter.concat(','))
    | 228 | + #end
    | 229 | + #set ($messageFilter = $messageFilter.concat('?'))
    | 230 | + #set ($discard = $filterParameters.add("${services.model.serialize($services.model.resolveDocument($g), 'default')}^XWiki.XWikiGroups"))
    | 231 | + #end
    | 232 | + #set ($messageFilter = $messageFilter.concat('))'))
    | 233 | + #end
    | 234 | + #end
    | 235 | + #set ($filterClause = $filterClause + " and (act.application <> 'MessageStream' or (act.application = 'MessageStream' and (${messageFilter})))")
    | 236 | +#else
    | 237 | + #set ($filterClause = $filterClause + " and act.application <> 'MessageStream'")
    | 238 | +#end
    | 239 | +##
    | 240 | +## Macros
    | 241 | +## -------------------------------------------------------------
    | 242 | +#*
    | 243 | + * Gets the list (java.util.List) of elements in $arrayAsString, and puts them in the $list.
    | 244 | + *
    | 245 | + * @param $arrayAsString the string form of the array, similar to the java syntax for a macro ({elt1, elt2, etc}), or without the {}
    | 246 | + * @param $resultVariableName the name of the variable to put the resulted list into.
    | 247 | + * FIXME: this is gonna fail for lists with '{' in the string or ','. Need to escape
    | 248 | + *#
    | 249 | +#macro(asList $resultVariableName $arrayAsString)
    | 250 | + #set ($list = [])
    | 251 | + #if ("$!arrayAsString" != '')
    | 252 | + ## if the string is surrounded by {}, eliminate and then parse as list
    | 253 | + #if ($arrayAsString.startsWith('{') && $arrayAsString.endWith('}'))
    | 254 | + #set ($endIndex = $arrayAsString.length() - 1)
    | 255 | + #set ($arrayAsString = $arrayAsString.substring(1, $endIndex))
    | 256 | + #end
    | 257 | + ## parse the array serialized as string and fill in the list
    | 258 | + #set ($valuesArray = $arrayAsString.split(','))
    | 259 | + #foreach ($value in $valuesArray)
    | 260 | + #if ($value.trim().length())
    | 261 | + #set ($dispose = $list.add($value.trim()))
    | 262 | + #end
    | 263 | + #end
    | 264 | + #end
    | 265 | + #setVariable("$resultVariableName", $list)
    | 266 | +#end
    | 267 | +#**
    | 268 | + * Build query filter clause
    | 269 | + * @colName The name of the collumn in the database table
    | 270 | + * @colValues The values to match with
    | 271 | + * @exclude If true, exclude the given column values
    | 272 | + *#
    | 273 | +#macro(updateFilterClause $colName $colValues $exclude)
    | 274 | + #if ($colValues.class == 'class java.lang.String')
    | 275 | + ## Transform the input to an array to handle it uniformly, below.
    | 276 | + #set ($colValues = [$colValues])
    | 277 | + #end
    | 278 | +
    | 279 | + #if ($colValues.size() > 0)
    | 280 | + ## Filter out invalid values.
    | 281 | + #set ($validColValues = [])
    | 282 | + #foreach ($colValue in $colValues)
    | 283 | + #if ("$!colValue" != '')
    | 284 | + #set ($discard = $validColValues.add($colValue))
    | 285 | + #end
    | 286 | + #end
    | 287 | +
    | 288 | + ## Work with the remaining valid values.
    | 289 | + #if ($validColValues.size() > 0)
    | 290 | + ## Open wrapping subclause.
    | 291 | + #set ($filterClause = $filterClause + ' AND (')
    | 292 | +
    | 293 | + ## Determine the sign to use for the filterCondition.
    | 294 | + #set ($maybeNegated = '')
    | 295 | + #if ($exclude)
    | 296 | + #set ($maybeNegated = 'NOT ')
    | 297 | + #end
    | 298 | +
    | 299 | + ## Exact match clause for the $validColValues.
    | 300 | + #set ($filterCondition = "${maybeNegated}IN")
    | 301 | + #set ($filterClause = $filterClause + "act.${colName} ${filterCondition} (${stringtool.repeat('?,', $mathtool.sub($validColValues.size(),1))}?)")
    | 302 | + #set ($discard = $filterParameters.addAll($validColValues))
    | 303 | +
    | 304 | + ## Extra clauses for the 'space' colName to match any nested spaces inside the specified spaces.
    | 305 | + #if ($colName == 'space')
    | 306 | + ## In an inclusion clause, we use OR to join the prefix queries, since we want at leas one match.
    | 307 | + #set ($booleanOperation = 'OR')
    | 308 | + #if ($exclude)
    | 309 | + ## In an exclusion clause, we to use AND to join the prefix queries, since we don`t want any match.
    | 310 | + #set ($booleanOperation = 'AND')
    | 311 | + #end
    | 312 | +
    | 313 | + ## Prefix match clauses for the $validColValues.
    | 314 | + #set ($filterCondition = "${maybeNegated}LIKE")
    | 315 | + #foreach ($validColValue in $validColValues)
    | 316 | + #set ($filterClause = $filterClause + " ${booleanOperation} act.${colName} ${filterCondition} ?")
    | 317 | + #set ($discard = $filterParameters.add("${validColValue}.%"))
    | 318 | + #end
    | 319 | + #end
    | 320 | +
    | 321 | + ## Close wrapping subclause.
    | 322 | + #set ($filterClause = $filterClause + ')')
    | 323 | + #end
    | 324 | + #end
    | 325 | +#end
    | 326 | +#**
    | 327 | + * Check if the document version exists
    | 328 | + *#
    | 329 | +#macro(isDocumentVersionValid $event)
    | 330 | + #set ($isDocumentVersionValid = false)
    | 331 | + #set ($pageDoc = $xwiki.getDocument($event.page))
    | 332 | + #set ($versions = $pageDoc.getRevisions())
    | 333 | + #foreach ($version in $versions)
    | 334 | + #if ($event.version == $version)
    | 335 | + #set ($isDocumentVersionValid = true)
    | 336 | + #end
    | 337 | + #end
    | 338 | +#end
    | 339 | +#**
    | 340 | + * Check if the current page event list entry is expandable (there are at least 2 events for the current page) or not.
    | 341 | + *#
    | 342 | +#macro(isPageEntryExpandable)
    | 343 | + #set ($isPageEntryExpandable = false)
    | 344 | + #if ($events.size() > 1)
    | 345 | + #set ($isPageEntryExpandable = true)
    | 346 | + #end
    | 347 | +#end
    | 348 | +#**
    | 349 | + * Get unique page event users
    | 350 | + * -> used to display the message: 'X changes by Y user(s)'
    | 351 | + *#
    | 352 | +#macro (getUniqueEventUsers)
    | 353 | + #set ($eventUsers = [])
    | 354 | + #foreach ($event in $events)
    | 355 | + #if (!$eventUsers.contains($xwiki.getUserName($event.user)))
    | 356 | + #set ($discard = $eventUsers.add($xwiki.getUserName($event.user)))
    | 357 | + #end
    | 358 | + #end
    | 359 | +#end
    | 360 | +#**
    | 361 | + * Check if the current page event happen today
    | 362 | + *#
    | 363 | +#macro(isToday)
    | 364 | + #set ($isToday = false)
    | 365 | + #set ($timeAgoMsg = '')
    | 366 | + #if ($mathtool.sub($eventTimeDiff.getDayOfYear(), 1) == 0 &&
    | 367 | + ($eventDateTime.isAfter($todayMidnight) ||
    | 368 | + $eventDateTime.isEqual($todayMidnight)))
    | 369 | + #if ($eventTimeDiff.getHourOfDay() == 0)
    | 370 | + ##
    | 371 | + ## Few seconds ago
    | 372 | + ## ---------------------------------------------------------------
    | 373 | + #set ($timeAgoMsg = $services.localization.render('timeAgo.minutesAgo', [$eventTimeDiff.getMinuteOfHour()]))
    | 374 | + #else
    | 375 | + ##
    | 376 | + ## Number of minutes ago
    | 377 | + ## ---------------------------------------------------------------
    | 378 | + #set ($hoursAgo = $eventTimeDiff.getHourOfDay())
    | 379 | + #set ($timeAgoMsg = $services.localization.render('timeAgo.hoursAgo', [$hoursAgo]))
    | 380 | + #end
    | 381 | + #set ($isToday = true)
    | 382 | + #end
    | 383 | +#end
    | 384 | +#**
    | 385 | + * Check if the current page event happen yesterday
    | 386 | + *#
    | 387 | +#macro(isYesterday)
    | 388 | + #set ($isYesterday = false)
    | 389 | + #if (($mathtool.sub($eventTimeDiff.getDayOfYear(), 1) == 0 &&
    | 390 | + $eventDateTime.isBefore($todayMidnight)
    | 391 | + ) ||
    | 392 | + ($mathtool.sub($eventTimeDiff.getDayOfYear(), 2) == 0 &&
    | 393 | + ($eventDateTime.isAfter($yesterdayMidnight) ||
    | 394 | + $eventDateTime.isEqual($yesterdayMidnight)
    | 395 | + )))
    | 396 | + #set ($isYesterday = true)
    | 397 | + #end
    | 398 | +#end
    | 399 | +#**
    | 400 | + * Check if the current event is of type 'addAttachment' or 'updateAttachment' or 'deleteAttachment'
    | 401 | + * @param event The current event
    | 402 | + *#
    | 403 | +#macro(isAttachmentEvent $event)
    | 404 | + #set ($isAttachmentEvent = false)
    | 405 | + #if ($event.type == 'addAttachment' || $event.type == 'updateAttachment' || $event.type == 'deleteAttachment')
    | 406 | + #set ($isAttachmentEvent = true)
    | 407 | + #end
    | 408 | +#end
    | 409 | +#**
    | 410 | + * Check if the current event is of type 'addComment' or 'updateComment' or 'deleteComment'
    | 411 | + *#
    | 412 | +#macro(isCommentEvent)
    | 413 | + #set ($isCommentEvent = false)
    | 414 | + #if ($event.type == 'addComment' || $event.type == 'updateComment' || $event.type == 'deleteComment')
    | 415 | + #set ($isCommentEvent = true)
    | 416 | + #end
    | 417 | +#end
    | 418 | +#**
    | 419 | + * Check if the current event is of type 'addAnnotation' or 'updateAnnotation' or 'deleteAnnotation'
    | 420 | + *#
    | 421 | +#macro(isAnnotationEvent)
    | 422 | + #set ($isAnnotationEvent = false)
    | 423 | + #if ($event.type == 'addAnnotation' || $event.type == 'updateAnnotation' || $event.type == 'deleteAnnotation')
    | 424 | + #set ($isAnnotationEvent = true)
    | 425 | + #end
    | 426 | +#end
    | 427 | +#**
    | 428 | + * Check if the current event is a message in the MessageStream application
    | 429 | + *#
    | 430 | +#macro(isMessageEvent)
    | 431 | + #set ($isMessageEvent = false)
    | 432 | + #if ($event.application == 'MessageStream')
    | 433 | + #set ($isMessageEvent = true)
    | 434 | + #end
    | 435 | +#end
    | 436 | +#**
    | 437 | + * Get comment content
    | 438 | + *#
    | 439 | +#macro(getCommentContent $event $commentContent)
    | 440 | + #set ($commentContent = '')
    | 441 | + #isDocumentVersionValid($event)
    | 442 | + #if ($isDocumentVersionValid)
    | 443 | + #set ($pageDocRevision = $xwiki.getDocument($xwiki.getDocument($event.page), $event.version))
    | 444 | + #set ($commentContent = $!pageDocRevision.getObject('XWiki.XWikiComments', $mathtool.toInteger($event.param2)).getProperty('comment').value)
    | 445 | + #end
    | 446 | +#end
    | 447 | +#**
    | 448 | + * Get annotation text and selection
    | 449 | + *#
    | 450 | +#macro(getAnnotation $event $annotationText $annotationSelection $annotationTextSyntaxId)
    | 451 | + #set ($annotationText = '')
    | 452 | + #set ($annotationSelection = '')
    | 453 | + #set ($annotationTextSyntaxId = '')
    | 454 | + #isDocumentVersionValid($event)
    | 455 | + #if ($isDocumentVersionValid)
    | 456 | + #set ($pageDocRevision = $xwiki.getDocument($xwiki.getDocument($event.page), $event.version))
    | 457 | + ## FIXME: Should we query the Annotations application's' configuration for the configured annotation class instead of hardcoding the default one? What about custom annotations?
    | 458 | + #set ($annotationObject = $pageDocRevision.getObject('XWiki.XWikiComments', $mathtool.toInteger($event.param2)))
    | 459 | + #set ($annotationSelection = $annotationObject.getProperty('selection').value)
    | 460 | + #set ($annotationText = $annotationObject.getProperty('comment').value)
    | 461 | + #set ($annotationTextSyntaxId = $pageDocRevision.getSyntax().toIdString())
    | 462 | + #end
    | 463 | +#end
    | 464 | +#**
    | 465 | + * Remove duplicated 'update' and 'addAttachment' events for the current page entry
    | 466 | + * NOTE: each time an event != 'update' (like 'addAttachment, updateComment' ...) is fired, an extra 'update' event is fired too (for backward compatibility reasons)
    | 467 | + *#
    | 468 | +#macro(removeDuplicatedEvents)
    | 469 | + ## TO DO - fix XWIKI-5632
    | 470 | + ## Because of this issue, related 'updateAnnotation' events are not included in the list (for flooding reasons)
    | 471 | + #set ($newList = [])
    | 472 | + #set ($previousEventInOldList = $NULL)
    | 473 | + #set ($previousEventInNewList = $NULL)
    | 474 | + #foreach ($event in $events)
    | 475 | + #set ($isUpdateRelatedEvent = false)
    | 476 | + #set ($isConsecutiveUpdateEvent = false)
    | 477 | + #set ($isCreateOrDeleteRelatedEvent = false)
    | 478 | + ##
    | 479 | + ## Get consecutive 'update' events having the same user (-> only the latest such event will be taken in account)
    | 480 | + ## ---------------------------------------------------------------
    | 481 | + #if ($event.type == 'update'
    | 482 | + && $event.requestId != $previousEventInNewList.requestId
    | 483 | + && $event.type == $previousEventInNewList.type
    | 484 | + && $event.user == $previousEventInNewList.user)
    | 485 | + #set ($isConsecutiveUpdateEvent = true)
    | 486 | + #end
    | 487 | + #if (!$isConsecutiveUpdateEvent)
    | 488 | + #checkRelatedEvents()
    | 489 | + ##
    | 490 | + ## Build the clean event list
    | 491 | + ## ---------------------------------------------------------------
    | 492 | + #if (!$isUpdateRelatedEvent
    | 493 | + && !($isCreateOrDeleteRelatedEvent && $event.type != 'create' && $event.type != 'delete')
    | 494 | + && $newList.size() < $aSubEntries)
    | 495 | + #set ($discard = $newList.add($event))
    | 496 | + #set ($previousEventInNewList = $event)
    | 497 | + #end
    | 498 | + #if ($newList.size() >= $aSubEntries)
    | 499 | + ## number of subentries reached
    | 500 | + #break
    | 501 | + #end
    | 502 | + #end
    | 503 | + #set ($previousEventInOldList = $event)
    | 504 | + #end
    | 505 | + ## Set the $events reference to the new list
    | 506 | + #set ($events = $newList)
    | 507 | +#end
    | 508 | +#**
    | 509 | + * checks the condition of the related events
    | 510 | + *#
    | 511 | +#macro(checkRelatedEvents)
    | 512 | + ## load related events
    | 513 | + #set ($relatedEvents = $xwiki.activitystream.getRelatedEvents($event))
    | 514 | + #if ($relatedEvents.size() > 1)
    | 515 | + #if ($event.type == 'update')
    | 516 | + #foreach ($relatedEvent in $relatedEvents)
    | 517 | + #if ($relatedEvent.type != 'update')
    | 518 | + #set ($isUpdateRelatedEvent = true)
    | 519 | + #break
    | 520 | + #end
    | 521 | + #end
    | 522 | + #end
    | 523 | + ## Get 'create' and 'delete' related event (-> in order to remove duplicated events in the case of import, rename, delete page)
    | 524 | + ## ---------------------------------------------------------------
    | 525 | + #if ($event.type != 'create' && $event.type != 'delete')
    | 526 | + #foreach ($relatedEvent in $relatedEvents)
    | 527 | + #if ($relatedEvent.type == 'create' || $relatedEvent.type == 'delete')
    | 528 | + #set ($isCreateOrDeleteRelatedEvent = true)
    | 529 | + #break
    | 530 | + #end
    | 531 | + #end
    | 532 | + #end
    | 533 | + #end
    | 534 | +#end
    | 535 | +#**
    | 536 | + * Initialize event variables
    | 537 | + *#
    | 538 | +#macro(initializeEvent)
    | 539 | + #set ($yearMsg = '')
    | 540 | + #set ($timeAgoMsg = '')
    | 541 | + #set ($eventDateTime = $xwiki.jodatime.getDateTime($event.date.getTime()))
    | 542 | + #set ($eventTimeDiff = $currentTime.minus($eventDateTime.millis).withZone($xwiki.jodatime.getUTCTimezone()))
    | 543 | + #set ($eventYear = $eventDateTime.year)
    | 544 | + #if ($eventYear != $todayYear)
    | 545 | + #set ($yearMsg = $eventYear)
    | 546 | + #end
    | 547 | + #isToday()
    | 548 | + #isYesterday()
    | 549 | + #isMessageEvent()
    | 550 | + #isCommentEvent()
    | 551 | + #isAnnotationEvent()
    | 552 | + #if("$!timeAgoMsg" == '')
    | 553 | + #set ($timeAgoMsg = $xwiki.formatDate($event.date, $eventDateFormatter) + " $!yearMsg")
    | 554 | + #end
    | 555 | + #set ($iterationDate = $xwiki.formatDate($event.date, $activityListDateFormatter) + " $!yearMsg")
    | 556 | + #set ($eventFullDate = $xwiki.formatDate($event.date, $eventFullDateFormatter) + " $!yearMsg")
    | 557 | +#end
    | 558 | +#**
    | 559 | + * Display the arrow used to expand the event list
    | 560 | + *#
    | 561 | +#macro(toolExpand)
    | 562 | + <a class='toolExpand'>&#9660;</a>
    | 563 | +#end
    | 564 | +#**
    | 565 | + * Display the delete tool for own event messages
    | 566 | + *#
    | 567 | +#macro(toolDelete $eventID)
    | 568 | + <a class='toolDelete' href="$doc.getURL($xcontext.action, "$!{request.queryString}&amp;xaction=deleteMessage&amp;id=$eventID&amp;xredirect=$escapetool.url($doc.getURL($xcontext.action, $request.queryString))")" title="$services.localization.render('xe.activity.messages.delete')">x</a>
    | 569 | +#end
    | 570 | +#**
    | 571 | + * Display RSS URL
    | 572 | + *#
    | 573 | +#macro(displayRSSURL)
    | 574 | + #set ($rssURL = '')
    | 575 | + #set ($parameters = 'xpage=plain&outputSyntax=plain')
    | 576 | + #foreach ($tag in $aTags)
    | 577 | + #set ($parameters = "${parameters}&tag=$escapetool.url($tag)")
    | 578 | + #end
    | 579 | + #foreach ($space in $aSpaces)
    | 580 | + #set ($parameters = "${parameters}&space=$escapetool.url($space)")
    | 581 | + #end
    | 582 | + #set ($rssURL = $xwiki.getURL('Main.WebRss', 'view', $parameters))
    | 583 | + <div class="rssURL">
    | 584 | + <a href="${rssURL}"><img src="$xwiki.getSkinFile('icons/silk/feed.png')" alt="RSS icon"/>$services.localization.render('xe.activity.rssfeed')</a>
    | 585 | + </div>
    | 586 | +#end
    | 587 | +#**
    | 588 | + * Display mimetype and name for the given event of type attachment
    | 589 | + *#
    | 590 | +#macro(displayAttachment $event)
    | 591 | + #set ($pageDoc = $xwiki.getDocument($event.page))
    | 592 | + #set ($attach = $pageDoc.getAttachment($event.param2))
    | 593 | + <div class="attachment">
    | 594 | + #mimetypeimg($attach.getMimeType().toLowerCase() $event.param2)
    | 595 | + #if ($attach.version)
    | 596 | + <a href="$pageDoc.getAttachmentURL($event.param2)">$!{escapetool.xml($event.param2)}</a>
    | 597 | + #else
    | 598 | + $!{escapetool.xml($event.param2)}
    | 599 | + #end
    | 600 | + </div>
    | 601 | +#end
    | 602 | +#**
    | 603 | + * Display a user message
    | 604 | + *#
    | 605 | +#macro(displayMessage $event)
    | 606 | + <div class="message">$!{escapetool.xml($event.getBody())}</div>
    | 607 | +#end
    | 608 | +#**
    | 609 | + * Display comment
    | 610 | + *#
    | 611 | +#macro(displayComment $event)
    | 612 | + #getCommentContent($event $commentContent)
    | 613 | + #if ("$!commentContent" != '')
    | 614 | + <div class="comment">
    | 615 | + $doc.getRenderedContentRestricted($commentContent, $doc.getSyntax().toIdString())
    | 616 | + </div>
    | 617 | + #end
    | 618 | +#end
    | 619 | +#**
    | 620 | + * Display annotation
    | 621 | + *#
    | 622 | +#macro(displayAnnotation $event)
    | 623 | + #getAnnotation($event $annotationText $annotationSelection)
    | 624 | + #if ("$!annotationText" != '' || "$!annotationSelection" != '')
    | 625 | + <div class="annotated">$!{escapetool.xml($annotationSelection)}</div>
    | 626 | + <div class="annotation">$!{doc.getRenderedContentRestricted($annotationText, $annotationTextSyntaxId)}</div>
    | 627 | + #end
    | 628 | +#end
    | 629 | +#**
    | 630 | + * Display user avatar and action message
    | 631 | + *#
    | 632 | +#macro(activitySnapshot $eventUser $eventType)
    | 633 | + <span class="activitySnapshot">
    | 634 | + #if ($eventUser != 'XWiki.XWikiGuest')
    | 635 | + <a href="$xwiki.getURL($eventUser)">#mediumUserAvatar("$!eventUser")</a>
    | 636 | + #else
    | 637 | + #mediumUserAvatar("$!eventUser")
    | 638 | + #end
    | 639 | + <img class="activityActionAvatar" src="$xwiki.getSkinFile($activityActionsMap.get($eventType), true)" alt="$services.localization.render("xe.activity.action.${eventType}", [$relatedEventsNo])" title="$services.localization.render("xe.activity.action.${eventType}", [$relatedEventsNo])" />
    | 640 | + </span>
    | 641 | +#end
    | 642 | +#**
    | 643 | + * Display the summary (avatars with action icon) for the current entry
    | 644 | + *#
    | 645 | +#macro(displayActivityListItemSummary)
    | 646 | + <div class="activitySummary">
    | 647 | + #foreach ($event in $events)
    | 648 | + #set ($relatedEventsNo = $xwiki.activitystream.getRelatedEvents($event).size() / 2)
    | 649 | + #activitySnapshot($event.user $event.type)
    | 650 | + #end
    | 651 | + </div>
    | 652 | +#end
    | 653 | +#**
    | 654 | + * Check if the current subentry is expandable
    | 655 | + *#
    | 656 | +#macro(isEventEntryExpandable $event)
    | 657 | + #set ($isEventEntryExpandable = false)
    | 658 | + #if ($isAnnotationEvent)
    | 659 | + #getAnnotation($event $annotationText $annotationSelection)
    | 660 | + #if ("$!annotationText" != '' || "$!annotationSelection" != '')
    | 661 | + #set ($isEventEntryExpandable = true)
    | 662 | + #end
    | 663 | + #elseif ($isAttachmentEvent && "$!event.param2" != '')
    | 664 | + #set ($isEventEntryExpandable = true)
    | 665 | + #elseif ($isCommentEvent)
    | 666 | + #getCommentContent($event $commentContent)
    | 667 | + #if ("$!commentContent" != '')
    | 668 | + #set ($isEventEntryExpandable = true)
    | 669 | + #end
    | 670 | + #end
    | 671 | +#end
    | 672 | +#**
    | 673 | + * Display the content for the current event
    | 674 | + *#
    | 675 | +#macro(displayEventContent)
    | 676 | + #set ($isEventEntryExpandable = false)
    | 677 | + #isAttachmentEvent($event)
    | 678 | + #isEventEntryExpandable($event)
    | 679 | + <li class="activityItem #if($isEventEntryExpandable) collapsed #end">
    | 680 | + #displayActivityListItemHeader(false false $isEventEntryExpandable)
    | 681 | + #if ($isEventEntryExpandable || $isMessageEvent)
    | 682 | + <div class="activityContent">
    | 683 | + #if ($isAttachmentEvent)
    | 684 | + #foreach ($event in $xwiki.activitystream.getRelatedEvents($event))
    | 685 | + #isAttachmentEvent($event)
    | 686 | + #if ($isAttachmentEvent)
    | 687 | + #displayAttachment($event)
    | 688 | + #end
    | 689 | + #end
    | 690 | + #end
    | 691 | + #if ($isMessageEvent)
    | 692 | + #displayMessage($event)
    | 693 | + #elseif ($isCommentEvent)
    | 694 | + #displayComment($event)
    | 695 | + #elseif ($isAnnotationEvent && $isEventEntryExpandable)
    | 696 | + #displayAnnotation($event)
    | 697 | + #end
    | 698 | + </div>
    | 699 | + #end
    | 700 | + </li>
    | 701 | + #set ($previousEventType = $event.type)
    | 702 | + #set ($previousEventUser = $event.user)
    | 703 | +#end
    | 704 | +#**
    | 705 | + * Display the header structure for the given event
    | 706 | + * @param isPageEntryExpandable if true, the curent item is expandable
    | 707 | + * @param isFirstEntry if true, the current item is the first entry of a list formed by a single event -> do not display user avatar
    | 708 | + *#
    | 709 | +#macro(displayActivityListItemHeader $isPageEntryExpandable $isFirstEntry $isEventEntryExpandable)
    | 710 | + #set ($relatedEventsNo = $xwiki.activitystream.getRelatedEvents($event).size() / 2)
    | 711 | + <div class="activityHeader">
    | 712 | + #if (!$isPageEntryExpandable && !$isFirstEntry)
    | 713 | + #activitySnapshot($event.user $event.type)
    | 714 | + #end
    | 715 | + <span class="activityAuthor">
    | 716 | + #if ($isFirstEntry)
    | 717 | + #if ($event.application != 'MessageStream')
    | 718 | + #set ($pageFullName = "$!{event.wiki}:$!{pageName}")
    | 719 | + #set ($document = $xwiki.getDocument($pageFullName))
    | 720 | + #set ($pageTitle = "$!{document.plainTitle}")
    | 721 | + #if ($pageTitle == '')
    | 722 | + #set ($pageTitle = $pageName)
    | 723 | + #end
    | 724 | +
    | 725 | + ##
    | 726 | + ## Build the page's location
    | 727 | + ##
    | 728 | + #set ($pageLocation = "#hierarchy($document.documentReference, {'plain' : true})")
    | 729 | + ## Trim the result since the macro's output contains unwanted spaces from the velocity code's indentation.
    | 730 | + #set ($pageLocation = $pageLocation.trim())
    | 731 | +
    | 732 | + <a href="$xwiki.getURL(${pageFullName})" class="typePage type" title="$!{escapetool.xml($services.localization.render('activity.page.link.title', [$pageLocation]))}">$!{escapetool.xml($pageTitle)}</a>
    | 733 | + #if ($event.wiki != $services.wiki.currentWikiId)
    | 734 | + #set($wikiPrettyName = $services.wiki.getById($event.wiki).prettyName)
    | 735 | + <span class="activityWiki">($!{escapetool.xml($wikiPrettyName)})</span>
    | 736 | + #end
    | 737 | + #end
    | 738 | + #else
    | 739 | + $xwiki.getUserName($!event.user)
    | 740 | + #end
    | 741 | + </span>
    | 742 | + #if(!$isFirstEntry || $isPageEntryExpandable)
    | 743 | + <span class="activityAction">
    | 744 | + #isAttachmentEvent($event)
    | 745 | + #if ($isPageEntryExpandable)## display entry summary
    | 746 | + $!services.localization.render('xe.activity.action.summary', [$events.size(), $eventUsers.size()])
    | 747 | + #else
    | 748 | + $!services.localization.render("xe.activity.action.${event.type}", [$relatedEventsNo])
    | 749 | + #if ($event.type == 'update' && $event.version != '1.1')
    | 750 | + #set ($pageFullName = "$!{event.wiki}:$!{pageName}")
    | 751 | + &#183; <a href="$xwiki.getDocument($pageFullName).getURL('view', "viewer=changes&rev2=${event.version}")">$services.localization.render('xe.activity.action.seechanges')</a>
    | 752 | + #end
    | 753 | + #end
    | 754 | + </span>
    | 755 | + #end
    | 756 | + #if ("$!timeAgoMsg" != '')
    | 757 | + <span class='activityTime' title="$eventFullDate">$!timeAgoMsg</span>
    | 758 | + #end
    | 759 | + #if ($isPageEntryExpandable)
    | 760 | + #toolExpand('')
    | 761 | + #displayActivityListItemSummary()
    | 762 | + #end
    | 763 | + #if ($isEventEntryExpandable)
    | 764 | + #toolExpand('')
    | 765 | + #end
    | 766 | + ## Users can delete their own messages from the stream
    | 767 | + #if (($event.application == 'MessageStream') && $isFirstEntry && ($xwiki.getDocument("$!{event.user}").prefixedFullName == $xwiki.getDocument("$!{xcontext.user}").prefixedFullName))
    | 768 | + #toolDelete($event.getEventId())
    | 769 | + #end
    | 770 | + </div>
    | 771 | +#end
    | 772 | +#**
    | 773 | + * Display the list of events for the current page
    | 774 | + *#
    | 775 | +#macro(displayEvents)
    | 776 | + ##
    | 777 | + ## Display events for the current page
    | 778 | + ## -------------------------------------------------------------------
    | 779 | + #foreach ($event in $events)
    | 780 | + ##
    | 781 | + ## Event variables
    | 782 | + ## -------------------------------------------------------------------
    | 783 | + #initializeEvent()
    | 784 | + ##
    | 785 | + ## Date header + Start list
    | 786 | + ## -------------------------------------------------------------------
    | 787 | + #if ($isToday && $displayToday)
    | 788 | + #if ($closeList)## there are events from the future
    | 789 | + </ul>
    | 790 | + #else
    | 791 | + #set ($closeList = true)## a new list will be opened
    | 792 | + #end
    | 793 | + <h2>$services.localization.render('timeAgo.today')</h2><ul class='activityList'> ## display 'Today' message
    | 794 | + #set ($displayToday = false)
    | 795 | + #elseif ($isYesterday && $displayYesterday)## display 'Yesterday' message
    | 796 | + #if ($closeList)## there are events today
    | 797 | + </ul>
    | 798 | + #else
    | 799 | + #set ($closeList = true)## a new list will be opened
    | 800 | + #end
    | 801 | + <h2>$services.localization.render('timeAgo.yesterday')</h2><ul class='activityList'>
    | 802 | + #set ($displayYesterday = false)
    | 803 | + #elseif (!$isToday && !$isYesterday && $iterationDate != $displayDate)## display date message
    | 804 | + #set ($displayDate = $iterationDate)
    | 805 | + #if ($closeList)## there are events today or/and yesterday
    | 806 | + </ul>
    | 807 | + #else
    | 808 | + #set ($closeList = true)## a new list will be opened
    | 809 | + #end
    | 810 | + <h2>$!iterationDate</h2><ul class='activityList'>
    | 811 | + #end
    | 812 | + ##
    | 813 | + ## Open event list + Display first event
    | 814 | + ## -------------------------------------------------------------------
    | 815 | + #if ($velocityCount == 1)
    | 816 | + <li class="activityItem activityPage #if($isPageEntryExpandable)collapsed#end">
    | 817 | + #displayActivityListItemHeader($isPageEntryExpandable true false)
    | 818 | + #if (!$isPageEntryExpandable)
    | 819 | + ## single entry
    | 820 | + #set($timeAgoMsg = '')
    | 821 | + #end
    | 822 | + <div class="activityContent">
    | 823 | + <ul class="activityList">
    | 824 | + #end
    | 825 | + ##
    | 826 | + ## Display other events (inside the expanded list)
    | 827 | + ## -------------------------------------------------------------------
    | 828 | + #displayEventContent()
    | 829 | + ##
    | 830 | + ## Close page event list
    | 831 | + ## -------------------------------------------------------------------
    | 832 | + #if ($velocityCount == $events.size())
    | 833 | + </ul>
    | 834 | + </div>
    | 835 | + </li>
    | 836 | + #end
    | 837 | + #end
    | 838 | +#end
    | 839 | +#**
    | 840 | + * Display activity event list
    | 841 | + *#
    | 842 | +#macro (displayActivity)
    | 843 | + #set ($iterationDate = '')
    | 844 | + #set ($displayDate = '')
    | 845 | + #set ($iterationDocuments = [])
    | 846 | + #set ($displayToday = true)
    | 847 | + #set ($displayYesterday = true)
    | 848 | + #set ($closeList = false)
    | 849 | + ##
    | 850 | + ## Get distinct pages with activity
    | 851 | + ## -------------------------------------------------------------------
    | 852 | + #set ($noEventsShown = true)
    | 853 | + #set ($pages = $xwiki.activitystream.searchDailyPages("$!filterClause", $filterParameters, $aEntries, 0))
    | 854 | + #if ($pages.size() > 0)
    | 855 | + #foreach ($page in $pages)
    | 856 | + ##
    | 857 | + ## Extract page name and event date
    | 858 | + ## -------------------------------------------------------------------
    | 859 | + #set ($pageName = $page.get(0))
    | 860 | + #set ($pageDate = $page.get(1))
    | 861 | + #set ($pageWiki = $page.get(2))
    | 862 | + ## If the affected document name matches ".*:XWiki[.][0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12}" then it is a GUID corresponding to message stream events, and not a real document.
    | 863 | + ## Always assume access to messages. For the rest of the documents, check access.
    | 864 | + #if ($pageName.matches(".*:XWiki[.][0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12}") || ($xwiki.hasAccessLevel('view', $xcontext.user, "${xcontext.database}:${pageName}") && !$iterationDocuments.contains($pageName)))
    | 865 | + ##
    | 866 | + ## Get event list for the current page
    | 867 | + ## -------------------------------------------------------------------
    | 868 | + #set ($whereClause = " AND $!filterClause AND day(date)=" + ${xwiki.formatDate($pageDate, 'd')})
    | 869 | + #set ($whereClause = $whereClause + " AND month(date)=" + ${xwiki.formatDate($pageDate, 'M')})
    | 870 | + #set ($whereClause = $whereClause + " AND year(date)=" + ${xwiki.formatDate($pageDate, 'yyyy')})
    | 871 | + #set ($whereClause = "act.wiki=? AND act.page=? AND act.space<>'Scheduler'" + $!whereClause)
    | 872 | + #set ($subqueryParameters = [$pageWiki, $pageName])
    | 873 | + #set ($discard = $subqueryParameters.addAll($filterParameters))
    | 874 | + #set ($events= $xwiki.activitystream.searchEvents("$!whereClause", false, 0, 0, $subqueryParameters))
    | 875 | + #if ($events.size() > 0)
    | 876 | + ##
    | 877 | + ## Initialize page event vars
    | 878 | + ## -------------------------------------------------------------------
    | 879 | + #removeDuplicatedEvents()
    | 880 | + #isPageEntryExpandable()
    | 881 | + #getUniqueEventUsers()
    | 882 | + #displayEvents()
    | 883 | + #set($noEventsShown = false)
    | 884 | + #end
    | 885 | + #end
    | 886 | + #end
    | 887 | + #if ($closeList)## close the last event list
    | 888 | + </ul>
    | 889 | + #end
    | 890 | + #end
    | 891 | + #if ($noEventsShown)
    | 892 | + <span class="noitems">$services.localization.render('xe.activity.noentries')</span>
    | 893 | + #end
    | 894 | + ##
    | 895 | + ## Display RSS URL
    | 896 | + ## -------------------------------------------------------------------
    | 897 | + #if ("$!aRSS" == 'true')
    | 898 | + #displayRSSURL()
    | 899 | + #end
    | 900 | +#end
    | 901 | +#**
    | 902 | + * Handle a message (delete) request if the MessageStream module is active.
    | 903 | + * @param displayUI output parameter that is true if the Activity Stream UI should be displayed, false otherwise.
    | 904 | + * Deppending on the current request, handling the message action could not require displaying the UI (and making all the Activity Stream queries).
    | 905 | + *#
    | 906 | +#macro(handleMessageAction $displayUI)
    | 907 | + #set ($displayUI = $NULL)
    | 908 | + #setVariable("$displayUI", true)
    | 909 | + #if ($isMessageStreamActive)
    | 910 | + #set ($xredirect = $doc.getURL($xcontext.action, $!{request.queryString}))
    | 911 | + #set ($errorMessageKeyPrefix = "activity.deleteMessage.error.")
    | 912 | + #set ($errorMessage = $request.session.getAttribute("${errorMessageKeyPrefix}${xredirect}"))
    | 913 | + #if ("$!errorMessage" != '')
    | 914 | + ## Clean the error and display the message.
    | 915 | + #set ($discard = $request.session.removeAttribute("${errorMessageKeyPrefix}${xredirect}"))
    | 916 | + {{error}}$errorMessage{{/error}}
    | 917 | +
    | 918 | + #elseif ("$!{request.xaction}" == 'deleteMessage')
    | 919 | + #if ($request.confirm == '1')
    | 920 | + #if (!$services.messageStream.deleteMessage(${request.id}))
    | 921 | + #if ($request.xpage == 'plain')
    | 922 | + $response.setStatus(403)## Forbidden
    | 923 | + {{translation key="xe.activity.messages.delete.failed"/}}
    | 924 | + #elseif ("$!{request.xredirect}" != '')
    | 925 | + ## Pass the error message in the xredirect so that we display it in the calling page and not in the macro page.
    | 926 | + $request.session.setAttribute("${errorMessageKeyPrefix}${request.xredirect}", $services.localization.render('xe.activity.messages.delete.failed'))
    | 927 | + $response.sendRedirect($request.xredirect)
    | 928 | + #else
    | 929 | + {{error}}{{translation key="xe.activity.messages.delete.failed"/}}{{/error}}
    | 930 | +
    | 931 | + #end
    | 932 | + #elseif ("$!{request.xredirect}" != '')
    | 933 | + $response.sendRedirect(${request.xredirect})
    | 934 | + #elseif ($request.xpage == 'plain')
    | 935 | + ## Just print something for confirmation.
    | 936 | + {{translation key="xe.activity.messages.delete.success"/}}
    | 937 | + #end
    | 938 | + ## Suppress the UI to avoid useless DB or network access.
    | 939 | + #if ($request.xpage == 'plain' || "$!{request.xredirect}" != '')
    | 940 | + #set ($displayUI = $NULL)
    | 941 | + #setVariable("$displayUI", false)
    | 942 | + #end
    | 943 | + #else
    | 944 | + {{html}}
    | 945 | + <form action="$doc.getURL($xcontext.action, $!request.queryString))">
    | 946 | + <div class='xdialog-box xdialog-box-confirmation'>
    | 947 | + <div class='xdialog-content'>
    | 948 | + <div class='question'>$services.localization.render('xe.activity.messages.delete.confirm')</div>
    | 949 | + <div class='buttons'>
    | 950 | + <input type='hidden' name='confirm' value='1'/>
    | 951 | + <input type='hidden' name='xaction' value='deleteMessage'/>
    | 952 | + <input type='hidden' name='id' value="$!request.id"/>
    | 953 | + <input type='hidden' name='xredirect' value="$!request.xredirect"/>
    | 954 | + <span class='xbuttonwrapper'><input type='submit' value="$services.localization.render('core.widgets.confirmationBox.button.yes')" class='button'/></span>
    | 955 | + <a class='secondary button' href="$!request.xredirect"/>$services.localization.render('core.widgets.confirmationBox.button.no')</a>
    | 956 | + </div>
    | 957 | + </div>
    | 958 | + </div>
    | 959 | + </form>
    | 960 | + {{/html}}
    | 961 | +
    | 962 | + #end
    | 963 | + #end
    | 964 | + #end
    | 965 | +#end
    | 966 | +#**
    | 967 | + * Preserves Activity Stream's state (values of macro parameters) in the updateURL that is to be used if the ActivityStream needs to be refreshed.
    | 968 | + **#
    | 969 | +#macro (addStateHiddenInput)
    | 970 | + #set ($extraQueryString = '')
    | 971 | + #foreach ($param in $xcontext.macro.params.getParameterNames())
    | 972 | + #set ($value = "$!{xcontext.macro.params.get($param)}")
    | 973 | + #if ($value != '')
    | 974 | + #foreach($piece in $value.split('\s*,\s*'))
    | 975 | + #set($extraQueryString = "${param}=$!{escapetool.url($piece)}&$!{extraQueryString}")
    | 976 | + #end
    | 977 | + #end
    | 978 | + #end
    | 979 | + #set ($updateURL = $xwiki.getURL('Main.Activity', 'view', "$!{extraQueryString}&xpage=plain"))
    | 980 | + <input type='hidden' class='updateURL' value="$updateURL" />
    | 981 | +#end
    | 982 | +{{/velocity}}
    | 983 | +
    | 984 | +{{velocity}}
    | 985 | +#set ($displayUI = true)
    | 986 | +#handleMessageAction($displayUI)
    | 987 | +#if ($displayUI)
    | 988 | + {{html}}
    | 989 | + <div class='activity'>
    | 990 | + #initDateVariables()
    | 991 | + #addStateHiddenInput()
    | 992 | + #displayActivity()
    | 993 | + </div>
    | 994 | + {{/html}}
    | 995 | +#end
    | 996 | +{{/velocity}}

  Macro content type

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +No content

  Default category

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +Content

  Macro description

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +The Activity Macro provides information about recent activities done by the users inside the XWiki instance. It lists the create, edit and delete events for pages, as well as comments, attachments and annotations.

  Macro id

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +activity

  Macro name

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +Activity

  Supports inline mode

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +No

  Macro visibility

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +Current Wiki

XWiki.WikiMacroParameterClass[0]
  Parameter default value

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +20

  Parameter description

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +Number of entries to display the activity for.

  Parameter mandatory

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +No

  Parameter name

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +entries

XWiki.WikiMacroParameterClass[1]
  Parameter default value

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +10

  Parameter description

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +Number of activities to show for each entry.

  Parameter mandatory

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +No

  Parameter name

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +subentries

XWiki.WikiMacroParameterClass[2]
  Parameter description

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +Comma separated list of wikis to display activity for.

  Parameter mandatory

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +No

  Parameter name

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +wikis

XWiki.WikiMacroParameterClass[3]
  Parameter description

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +Comma separated list of local serialized references of the spaces to display the activity for.

  Parameter mandatory

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +No

  Parameter name

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +spaces

XWiki.WikiMacroParameterClass[4]
  Parameter description

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +Comma separated list of authors whose modifications to show.

  Parameter mandatory

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +No

  Parameter name

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +authors

XWiki.WikiMacroParameterClass[5]
  Parameter description

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +Comma separated list of tags to display activity for.

  Parameter mandatory

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +No

  Parameter name

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +tags

XWiki.WikiMacroParameterClass[6]
  Parameter default value

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +false

  Parameter description

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +Whether to show modifications that create minor versions or not.

  Parameter mandatory

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +No

  Parameter name

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +minor

XWiki.WikiMacroParameterClass[7]
  Parameter default value

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +false

  Parameter description

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +Whether to show activity RSS link or not.

  Parameter mandatory

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +No

  Parameter name

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +rss