activity.php 23.6 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?php
/**
 * This program is part of Mahara
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
 *
 * @package    mahara
 * @subpackage core
 * @author     Penny Leach <penny@catalyst.net.nz>
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL
 * @copyright  (C) 2006,2007 Catalyst IT Ltd http://catalyst.net.nz
 *
 */

defined('INTERNAL') || die();

/**
 * This is the function to call whenever anything happens
 * that is going to end up on a user's activity page.
 * 
 * @param string $activitytype type of activity
 * @param mixed $data data 
 */
36
function activity_occurred($activitytype, $data) {
37
38
39
40
    if (!$at = get_record('activity_type', 'name', $activitytype)) {
        throw new Exception("Invalid activity type $activitytype");
    }

41
42
43
44
45
46
47
48
    if (!empty($at->delay)) {
        $delayed = new StdClass;
        $delayed->type = $activitytype;
        $delayed->data = serialize($data);
        $delayed->ctime = db_format_timestamp(time());
        insert_record('activity_queue', $delayed);
    }
    else {
49
        handle_activity($at, $data);
50
    }
51
52
53
54
55
56
57
58
59
}

/** 
 * This function dispatches all the activity stuff
 * to whatever notification plugin it needs to
 * and figures out all the implications of 
 * activity and who needs to know about it.
 * 
 * @param object $activitytype record from activity_type
60
61
62
 * @param mixed $data must contain message to save.
 * it can also contain url.
 * each activity type has different requirements of $data - 
63
64
65
66
67
68
69
70
71
72
73
74
75
76
 *  - <b>contactus</b> must contain $message, $subject (optional), $fromname, $fromaddress, $userfrom (if a logged in user)
 *  - <b>objectionable</b> must contain $message, $view and $artefact if applicable
 *  - <b>maharamessage</b> must contain $users, an array of userids. $subject and $message (contents of message)
 *  - <b>usermessage</b> must contain $userto, id of recipient user, $userfrom, id of user from 
    -       and $subject and $message (contents of message)
 *  - <b>feedback (artefact)</b> must contain both $artefact (id) and $view (id) and $message 
 *  - <b>feedback (view)</b> must contain $view (id) and $message
 *  - <b>watchlist (artefact)</b> must contain $artefact (id of artefact) 
 *  -       and should also contain $subject (or a boring default will be used)
 *  - <b>watchlist (view) </b> must contain $view (id of view) 
    -       and should also contain $subject (or a boring default will be used)
 *  - <b>watchlist (community) </b> must contain $community (id of community)
    -       and should also contain $subject (or a boring default will be used)
 *  - <b>newview</b> must contain $owner userid of view owner AND $view (id of new view)
77
 *  - <b>viewaccess</b> must contain $owner userid of view owner AND $view (id of view) and $oldusers array of userids before access change was committed.
78
 */
79
function handle_activity($activitytype, $data, $cron=false) {
80

81
    $data = (object)$data;
82
83
84
85
    if (is_string($activitytype)) {
        $activitytype = get_record('activity_type', 'name', $activitytype);
    }
    
86

87
88
89
90
    if (!is_object($activitytype)) {
        throw new InvalidArgumentException("Invalid activitytype $activitytype");
    }

91
92
93
94
95
    $users = array();
    $prefix = get_config('dbprefix');

    if (!empty($activitytype->admin)) {
        $users = activity_get_users($activitytype->name, null, null, true);
96
97
98
99
100
101
        // validation stuff
        switch ($activitytype->name) {
            case 'contactus':
                if (empty($data->message)) {
                    throw new InvalidArgumentException("Message was empty for activity type contactus");
                }
102
                $data->subject = get_string('newcontactusfrom', 'activity') . ' ' .$data->fromname 
103
104
105
106
107
108
109
110
                    . '<' . $data->fromemail .'>' . (isset($data->subject) ? ': ' . $data->subject : '');
                $data->message = $data->subject . "\n\n" . $data->message;
                $data->subject = get_string('newcontactus', 'activity');
                if (!empty($data->userfrom)) {
                    $data->url = get_config('wwwroot') . 'user/view.php?id=' . $data->userfrom;
                }
                break;
            case 'objectionable':
111
112
113
114
115
116
117
118
119
120
                if (empty($data->view)) {
                    throw new InvalidArgumentException("Objectionable content requires an id of a view");
                }
                if (empty($data->message)) {
                    throw new InvalidArgumentException("Objectionable content requires a message");
                }
                if (!$viewtitle = get_field('view', 'title', 'id', $data->view)) {
                    throw new InvalidArgumentException("Couldn't find view with id " . $data->view);
                }
                if (empty($data->artefact)) {
121
                    $data->url = get_config('wwwroot') . 'view/view.php?view=' . $data->view;
122
                    $data->subject = get_string('objectionablecontentview', 'activity') 
123
                        . ' ' . get_string('onview', 'activity') . ' ' . $viewtitle;
124
125
                }
                else {
126
                    $data->url = get_config('wwwroot') . 'view/view.php?artefact=' . $data->artefact . '&view=' . $data->view;
127
128
129
130
131
132
                    if (!$artefacttitle = get_field('artefact', 'title', 'id', $data->artefact)) {
                        throw new InvalidArgumentException("Couldn't find artefact with id " . $data->view);
                    }
                    $data->subject = get_string('objectionablecontentartefact', 'activity') 
                        . ' '  . get_string('onartefact', 'activity') . ' ' . $artefacttitle;
                }
133
134
                break;
            case 'virusrepeat':
135
136
137
                $userstring = $data->username . ' (' . $data->fullname . ') (userid:' . $data->userid . ')' ;
                $data->subject = get_string('virusrepeatsubject', 'mahara', $userstring);
                $data->message = get_string('virusrepeatmessage');
138
139
140
141
                break;
            case 'virusrelease':
                break;
        }
142
143
144
145
146
    }
    else {
        switch ($activitytype->name) {
            // easy ones first :)
            case 'maharamessage':
147
148
149
150
151
152
153
154
155
                if (!is_array($data->users) || empty($data->users)) {
                    throw new InvalidArgumentException("Mahara message activity type expects an array of users");
                }
                if (empty($data->subject)) {
                    throw new InvalidArgumentException("Mahara message activity type expects a subject");
                }
                if (empty($data->message)) {
                    throw new InvalidArgumentException("Mahara message activity type expects a message");
                }
156
                $users = activity_get_users($activitytype->name, $data->users);
157
158
                break;
            case 'usermessage':
159
160
161
162
163
164
165
166
167
                if (!is_numeric($data->userto) || !is_numeric($data->userfrom)) {
                    throw new InvalidArgumentException("User message requires userto and userfrom to be set");
                }
                if (empty($data->subject)) {
                    throw new InvalidArgumentException("User message activity type expects a subject");
                }
                if (empty($data->message)) {
                    throw new InvalidArgumentException("User message activity type expects a message");
                }
168
                $users = activity_get_users($activitytype->name, array($data->userto));
Penny Leach's avatar
Penny Leach committed
169
170
171
172
                if (empty($data->url)) {
                    // @todo when user messaging is implemented, this might change... 
                    $data->url = get_config('wwwroot') . 'user/view.php?id=' . $data->userfrom;
                }
173
174
                break;
            case 'feedback':
175
176
177
                if (empty($data->message)) {
                    throw new InvalidArgumentException("Feedbackactivity type expects a message");
                }
Penny Leach's avatar
Penny Leach committed
178
                if (empty($data->view)) {
179
                    throw new InvalidArgumentException("Feedback missing view id");
Penny Leach's avatar
Penny Leach committed
180
                }
181
                if (!empty($data->artefact)) { // feedback on artefact
182
                    $data->subject = get_string('newfeedbackonartefact', 'activity');
183
184
185
186
                    require_once('artefact.php');
                    $artefact = artefact_instance_from_id($data->artefact);
                    if ($artefact->feedback_notify_owner()) {
                        $userid = $artefact->get('owner');
187
                    }
188
189
190
191
                    else {
                        $userid = null;
                    }
                    $data->subject .= ' ' .$artefact->get('title');
Penny Leach's avatar
Penny Leach committed
192
193
                    if (empty($data->url)) {
                        // @todo this might change later
194
                        $data->url = get_config('wwwroot') . 'view/view.php?artefact=' 
195
                            . $data->artefact . '&view=' . $data->view;
Penny Leach's avatar
Penny Leach committed
196
                    }
197
                } 
198
                else { // feedback on view.
199
                    $data->subject = get_string('newfeedbackonview', 'activity');
200
201
202
                    if (!$view = get_record('view', 'id', $data->view)) {
                        throw new InvalidArgumentException("Couldn't find view with id " . $data->view);
                    }
203
                    $userid = $view->owner;
204
                    $data->subject .= ' ' .$view->title;
Penny Leach's avatar
Penny Leach committed
205
206
                    if (empty($data->url)) {
                        // @todo this might change later
207
                        $data->url = get_config('wwwroot') . 'view/view.php?view=' . $data->view;
Penny Leach's avatar
Penny Leach committed
208
                    }
209
                }
210
211
212
213
214
215
                if ($userid) {
                    $users = activity_get_users($activitytype->name, array($userid));
                } 
                else {
                    $users = array();
                }
216
217
218
                break;
            // and now the harder ones
            case 'watchlist':
219
                if (!empty($data->view)) {
220
221
222
223
                    if (empty($data->subject)) {
                        throw new InvalidArgumentException("subject must be provided for watchlist view");
                    }
                    $oldsubject = isset($data->subject) ? $data->subject : '';
224
                    $data->subject = get_string('watchlistmessageview', 'activity');
225
226
227
228
229
                    if (!$viewinfo = get_record_sql('SELECT u.*, v.title FROM ' . $prefix . 'usr u
                                                     JOIN ' . $prefix . 'view v ON v.owner = u.id
                                                     WHERE v.id = ?', array($data->view))) {
                        throw new InvalidArgumentException("Couldn't find view with id " . $data->view);
                    }
230
231
                    $data->message = $oldsubject . ' ' . get_string('onview', 'activity') 
                        . ' ' . $viewinfo->title . ' ' . get_string('ownedby', 'activity');
Penny Leach's avatar
Penny Leach committed
232
                    $sql = 'SELECT u.*, p.method, CAST(? AS TEXT)  AS url
233
                                FROM ' . $prefix . 'usr_watchlist_view wv
234
                                JOIN ' . $prefix . 'usr u
Penny Leach's avatar
Penny Leach committed
235
                                    ON wv.usr = u.id
236
                                LEFT JOIN ' . $prefix . 'usr_activity_preference p
237
                                    ON p.usr = u.id
238
                                WHERE (p.activity = ? OR p.activity IS NULL)
239
240
                                AND wv.view = ?
                           ';
241
                    $users = get_records_sql_array($sql, 
242
                                                   array(get_config('wwwroot') . 'view/view.php?view=' 
243
                                                         . $data->view, 'watchlist', $data->view));
244
245
246
                    if (empty($users)) {
                        $users = array();
                    }
247
248
                    // ick
                    foreach ($users as $user) {
249
                        $user->message = $data->message . ' ' . display_name($viewinfo, $user);
250
                    }
251
                } 
252
                else if (!empty($data->artefact)) {
253
254
                    $data->subject = get_string('watchlistmessageartefact', 'activity')
                        . (isset($data->subject) ? ': ' . $data->subject : '');
255
256
257
                    if (!$ainfo = get_record_sql('SELECT u.*, a.title FROM ' . $prefix . 'usr u
                                                  JOIN ' . $prefix . 'artefact a  ON a.owner = u.id
                                                  WHERE a.id = ?', array($data->artefact))) {
258
259
260
                        if (!empty($cron)) { // probably deleted already
                            return;
                        }
Penny Leach's avatar
Penny Leach committed
261
                        throw new InvalidArgumentException(get_string('artefactnotfound', 'error', $data->artefact));
262
                    }
263
                    $data->message = get_string('onartefact', 'activity') 
264
                        . ' ' . $ainfo->title . ' ' . get_string('ownedby', 'activity');
265
                    $sql = 'SELECT DISTINCT u.*, p.method, ?||wa.view as url
266
                                FROM ' . $prefix . 'usr_watchlist_artefact wa
267
                                LEFT JOIN ' . $prefix . 'artefact_parent_cache pc
268
269
                                    ON (pc.parent = wa.artefact OR pc.artefact = wa.artefact)
                                JOIN ' . $prefix . 'usr u 
270
                                    ON wa.usr = u.id
271
                                LEFT JOIN ' . $prefix . 'usr_activity_preference p
272
                                    ON p.usr = u.id
273
                                WHERE (p.activity = ? OR p.activity IS NULL)
274
275
                                AND (pc.parent = ? OR wa.artefact = ?)
                            ';
276
                    $users = get_records_sql_array($sql, 
277
                                                   array(get_config('wwwroot') . 'view/view.php?view=' 
278
                                                         . $data->artefact . '&view=', 'watchlist', 
279
                                                         $data->artefact, $data->artefact));
280
281
282
                    if (empty($users)) {
                        $users = array();
                    }
283
284
                    // ick
                    foreach ($users as $user) {
285
                        $user->message = $data->message . ' ' . display_name($ainfo, $user);
286
                    }
287
                }
288
                else if (!empty($data->community)) {
289
290
291
                    if (empty($data->subject)) {
                        throw new InvalidArgumentException("subject must be provided for watchlist community");
                    }
292
293
294
                    if (!$communityname = get_field('community', 'name', 'id', $data->community)) {
                        throw new InvalidArgumentException("Couldn't find community with id " . $data->community);
                    }
295
296
                    $oldsubject = $data->subject;
                    $data->subject = get_string('watchlistmessagecommunity', 'activity');
297
                    $data->message = $oldsubject . ' ' . get_string('oncommunity', 'activity') . ' ' . $communityname;
Penny Leach's avatar
Penny Leach committed
298
                    $sql = 'SELECT DISTINCT u.*, p.method, CAST(? AS TEXT) AS url
299
300
301
                                FROM ' . $prefix . 'usr_watchlist_community c
                                JOIN ' . $prefix . 'usr u
                                    ON c.usr = u.id
302
                                LEFT JOIN ' . $prefix . 'usr_activity_preference p
303
                                    ON p.usr = u.id
304
                                WHERE (p.activity = ? OR p.activity IS NULL)
305
306
                                AND c.community = ?
                            ';
307
                    $users = get_records_sql_array($sql, 
308
                                                   array(get_config('wwwroot') . 'contacts/communities/view.php?id='
309
                                                         . $data->community, 'watchlist', $data->community));
310
                }
311
312
313
314
315
                else {
                    throw new InvalidArgumentException("Invalid watchlist type");
                }
                break;
            case 'newview':
316
317
318
                if (!is_numeric($data->owner) || !is_numeric($data->view)) {
                    throw new InvalidArgumentException("New view activity type requires view and owner to be set");
                }
319
                if (!$viewinfo = get_record_sql('SELECT u.*, v.title FROM ' . $prefix . 'usr u
320
                                                 JOIN ' . $prefix . 'view v ON v.owner = u.id
321
322
323
                                                 WHERE v.id = ?', array($data->view))) {
                    throw new InvalidArgumentException("Couldn't find view with id " . $data->view);
                }
324
325

                $data->message = get_string('newviewmessage', 'activity', $viewinfo->title);
326
                $data->subject = get_string('newviewsubject', 'activity');
327
328
                $data->url = get_config('wwwroot') . 'view/view.php?view=' . $data->view;

329
                // add users on friendslist, userlist or grouplist...
330
                $users = activity_get_viewaccess_users($data->view, $data->owner);
331
332
333
                if (empty($users)) {
                    $users = array();
                }
334
335
                // ick
                foreach ($users as $user) {
336
                    $user->message = display_name($viewinfo, $user) . ' ' . $data->message;
337
                }
338

339
                break;
340
341
342
343
344
345
346
347
348
349
350
351
352
            case 'viewaccess':
                if (!is_numeric($data->owner) || !is_numeric($data->view)) {
                    throw new InvalidArgumentException("view access activity type requires view and owner to be set");
                }
                if (!isset($data->oldusers)) {
                    throw new InvalidArgumentException("view access activity type requires oldusers to be set (even if empty)");
                }
                if (!$viewinfo = get_record_sql('SELECT u.*, v.title FROM ' . $prefix . 'usr u
                                                 JOIN ' . $prefix . 'view v ON v.owner = u.id
                                                 WHERE v.id = ?', array($data->view))) {
                    throw new InvalidArgumentException("Couldn't find view with id " . $data->view);
                }
                $data->message = get_string('newviewaccessmessage', 'activity')
353
                    . ' "' . $viewinfo->title . '" ' . get_string('ownedby', 'activity');
354
                $data->subject = get_string('newviewaccesssubject', 'activity');
355
                $data->url = get_config('wwwroot') . 'view/view.php?view=' . $data->view;
356
357
358
359
360
361
362
363
                $users = array_diff_key(activity_get_viewaccess_users($data->view, $data->owner), $data->oldusers);
                if (empty($users)) {
                    $users = array();
                }
                // ick
                foreach ($users as $user) {
                    $user->message = $data->message . ' ' . display_name($viewinfo, $user);
                }
364
365
            case 'contactus':
                
366
                break;
Penny Leach's avatar
Penny Leach committed
367
                // @todo more here (admin messages!)
368
369
        }
    }
370
371
372
    if (empty($users)) {
        return;
    }
373
    safe_require('notification', 'internal', 'lib.php', 'require_once');
374
    $data->type = $activitytype->name;
375
    foreach ($users as $user) {
376
377
378
379
380
381
382
383
384
385
        $userdata = $data;
        // some stuff gets overridden by user specific stuff
        if (!empty($user->url)) {
            $userdata->url = $user->url;
        }
        if (!empty($user->message)) {
            $userdata->message = $user->message;
        }
        if (!empty($user->subject)) {
            $userdata->subject = $user->subject;
386
        }
387
388
389
        if (empty($user->method)) {
            $user->method = 'internal';
        }
390
        if ($user->method != 'internal') {
391
            $method = $user->method;
392
            safe_require('notification', $method, 'lib.php', 'require_once');
393
            call_static_method(generate_class_name('notification', $method), 'notify_user', $user, $userdata);
394
            $user->markasread = true; // if we're doing something else, don't generate unread internal ones.
395
396
        }
        // always do internal
397
        call_static_method('PluginNotificationInternal', 'notify_user', $user, $userdata);
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
    }
}

/**
 * this function returns an array of users
 * for a particular activitytype
 * including the notification method.
 *
 * @param string $activitytype the name of the activity type
 * @param array $userids an array of userids to filter by
 * @param array $userobjs an array of user objects to filterby
 * @param bool $adminonly whether to filter by admin flag
 * @return array of users
 */
function activity_get_users($activitytype, $userids=null, $userobjs=null, $adminonly=false) {
    $values = array($activitytype);
    $sql = 'SELECT u.*, p.method
                FROM ' . get_config('dbprefix') .'usr u
416
                LEFT JOIN ' . get_config('dbprefix') . 'usr_activity_preference p
417
                    ON p.usr = u.id
418
                WHERE (p.activity = ? ' . (empty($adminonly) ? ' OR p.activity IS NULL' : '') . ')';
419
420
421
422
423
424
425
426
427
428
429
430
    if (!empty($adminonly)) {
        $sql .= ' AND u.admin = ? ';
        $values[] = 1;
    }
    if (!empty($userobjs) && is_array($userobjs)) {
        $sql .= ' AND u.id IN (' . implode(',',db_array_to_ph($userobjs)) . ')';
        $values = array_merge($values, array_to_fields($userobjs));
    } 
    else if (!empty($userids) && is_array($userids)) {
        $sql .= ' AND u.id IN (' . implode(',',db_array_to_ph($userids)) . ')';
        $values = array_merge($values, $userids);
    }
431
    return get_records_sql_array($sql, $values);
432
433
}

434
435
436
437
438
439
440
/**
 * this function inserts a default set of activity preferences for a given user
 * id
 */
function activity_set_defaults($user_id) {
    $activitytypes = get_records_array('activity_type', 'admin', 0);
    foreach ($activitytypes as $type) {
441
442
443
444
445
        insert_record('usr_activity_preference', (object)array(
            'usr' => $user_id,
            'activity' => $type->name,
            'method' => 'internal',
        ));
446
447
448
449
    }
    
}

450

451
452
function activity_process_queue() {

453
    db_begin();
454
    if ($toprocess = get_records_array('activity_queue')) {
455
        foreach ($toprocess as $activity) {
456
            handle_activity($activity->type, unserialize($activity->data), true);
457
458
459
460
461
        }
        delete_records('activity_queue');
    }
    db_commit();
}
462

463
function activity_get_viewaccess_users($view, $owner) {
464

465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
    $prefix = get_config('dbprefix');

    $sql = 'SELECT userid, u.*, p.method
                FROM (
                SELECT (CASE WHEN usr1 = ? THEN usr2 ELSE usr1 END) AS userid 
                    FROM ' . $prefix . 'usr_friend 
                        WHERE (usr1 = ? OR usr2 = ?)
                UNION SELECT member AS userid 
                    FROM ' . $prefix . 'usr_group_member m
                    JOIN ' . $prefix . 'view_access_group g ON m.grp = g.grp 
                        WHERE g.view = ?
                UNION SELECT usr AS userid 
                    FROM ' . $prefix . 'view_access_usr u 
                        WHERE u.view = ?
                ) AS userlist
                JOIN ' . $prefix . 'usr u ON u.id = userlist.userid
                LEFT JOIN ' . $prefix . 'usr_activity_preference p ON p.usr = u.id';
482
483
484
485
    if (!$u = get_records_sql_assoc($sql, array($owner, $owner, $owner, $view, $view))) {
        $u = array();
    }
    return $u;
486
487
}

488
?>