Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
// This file is part of Moodle - http://moodle.org/
3
//
4
// Moodle is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// Moodle is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
 
17
/**
18
 * This page lists all the instances of reengagement in a particular course
19
 *
20
 * @package    mod_reengagement
21
 * @author     Peter Bulmer
22
 * @copyright  2016 Catalyst IT {@link http://www.catalyst.net.nz}
23
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24
 */
25
 
26
defined('MOODLE_INTERNAL') || die();
27
require_once($CFG->libdir."/completionlib.php");
28
 
29
define('REENGAGEMENT_EMAILUSER_NEVER', 0);
30
define('REENGAGEMENT_EMAILUSER_COMPLETION', 1);
31
define('REENGAGEMENT_EMAILUSER_TIME', 2);
32
define('REENGAGEMENT_EMAILUSER_RESERVED1', 3);
33
 
34
define('REENGAGEMENT_RECIPIENT_USER', 0);
35
define('REENGAGEMENT_RECIPIENT_MANAGER', 1);
36
define('REENGAGEMENT_RECIPIENT_BOTH', 2);
37
 
38
/**
39
 * Given an object containing all the necessary data,
40
 * (defined by the form in mod_form.php) this function
41
 * will create a new instance and return the id number
42
 * of the new instance.
43
 *
44
 * @param object $reengagement An object from the form in mod_form.php
45
 * @return int The id of the newly inserted reengagement record
46
 */
47
function reengagement_add_instance($reengagement) {
48
    global $DB;
49
 
50
    $reengagement->timecreated = time();
51
    if (empty($reengagement->suppressemail)) {
52
        // User didn't tick the box indicating they wanted to suppress email if a certain activity was complete.
53
        // Force the 'target activity' field to be 0 (ie no target).
54
        $reengagement->suppresstarget = 0;
55
    }
56
    unset($reengagement->suppressemail);
57
 
58
    // Check course has completion enabled, and enable it if not, and user has permission to do so.
59
    $course = $DB->get_record('course', array('id' => $reengagement->course));
60
    if (empty($course->enablecompletion)) {
61
        $coursecontext = context_course::instance($course->id);
62
        if (has_capability('moodle/course:update', $coursecontext)) {
63
            $data = array('id' => $course->id, 'enablecompletion' => '1');
64
            $DB->update_record('course', $data);
65
            rebuild_course_cache($course->id);
66
        }
67
    }
68
 
69
    return $DB->insert_record('reengagement', $reengagement);
70
}
71
 
72
 
73
/**
74
 * Given an object containing all the necessary data,
75
 * (defined by the form in mod_form.php) this function
76
 * will update an existing instance with new data.
77
 *
78
 * @param object $reengagement An object from the form in mod_form.php
79
 * @return boolean Success/Fail
80
 */
81
function reengagement_update_instance($reengagement) {
82
    global $DB;
83
 
84
    $reengagement->timemodified = time();
85
    $reengagement->id = $reengagement->instance;
86
 
87
    // If they didn't chose to suppress email, do nothing.
88
    if (!$reengagement->suppressemail) {
89
        $reengagement->suppresstarget = 0;// No target to be set.
90
    }
91
    unset($reengagement->suppressemail);
92
    $result = $DB->update_record('reengagement', $reengagement);
93
    return $result;
94
}
95
 
96
 
97
/**
98
 * Given an ID of an instance of this module,
99
 * this function will permanently delete the instance
100
 * and any data that depends on it.
101
 *
102
 * @param int $id Id of the module instance
103
 * @return boolean Success/Failure
104
 */
105
function reengagement_delete_instance($id) {
106
    global $DB;
107
 
108
    if (! $reengagement = $DB->get_record('reengagement', array('id' => $id))) {
109
        return false;
110
    }
111
 
112
    $result = true;
113
 
114
    // Delete any dependent records here.
115
    if (! $DB->delete_records('reengagement_inprogress', array('reengagement' => $reengagement->id))) {
116
        $result = false;
117
    }
118
 
119
    if (! $DB->delete_records('reengagement', array('id' => $reengagement->id))) {
120
        $result = false;
121
    }
122
 
123
    return $result;
124
}
125
 
126
/**
127
 * Print the grade information for this user.
128
 *
129
 * @param stdClass $course
130
 * @param stdClass $user
131
 * @param stdClass $mod
132
 * @param stdClass $reengagement
133
 */
134
function reengagement_user_outline($course, $user, $mod, $reengagement) {
135
    return;
136
}
137
 
138
 
139
/**
140
 * Prints the complete info about a user's interaction.
141
 *
142
 * @param stdClass $course
143
 * @param stdClass $user
144
 * @param stdClass $mod
145
 * @param stdClass $reengagement
146
 */
147
function reengagement_user_complete($course, $user, $mod, $reengagement) {
148
    return true;
149
}
150
 
151
 
152
/**
153
 * Prints the recent activity.
154
 *
155
 * @param stdClass $course
156
 * @param stdClass $isteacher
157
 * @param stdClass $timestart
158
 */
159
function reengagement_print_recent_activity($course, $isteacher, $timestart) {
160
    return false;  // True if anything was printed, otherwise false.
161
}
162
 
163
 
164
/**
165
 * Function to be run periodically according to the moodle cron
166
 * * Add users who can start this module to the 'reengagement_inprogress' table
167
 *   and add an entry to the activity completion table to indicate that they have started
168
 * * Check the reengagement_inprogress table for users who have completed thieir reengagement
169
 *   and mark their activity completion as being complete
170
 *   and send an email if the reengagement instance calls for it.
171
 * @return boolean
172
 */
173
function reengagement_crontask() {
174
    global $CFG, $DB;
175
 
176
    require_once($CFG->libdir."/completionlib.php");
177
 
178
    // Get a consistent 'timenow' value across this whole function.
179
    $timenow = time();
180
 
181
    $reengagementssql = "SELECT cm.id as id, cm.id as cmid, cm.availability, r.id as rid, r.course as courseid,
182
                                r.duration, r.emaildelay
183
                          FROM {reengagement} r
184
                    INNER JOIN {course_modules} cm on cm.instance = r.id
185
                          JOIN {modules} m on m.id = cm.module
186
                         WHERE m.name = 'reengagement' AND cm.deletioninprogress = 0
187
                      ORDER BY r.id ASC";
188
 
189
    $reengagements = $DB->get_recordset_sql($reengagementssql);
190
    if (!$reengagements->valid()) {
191
        // No reengagement module instances in a course.
192
        mtrace("No reengagement instances found - nothing to do :)");
193
        return true;
194
    }
195
 
196
    // First: add 'in-progress' records for those users who are able to start.
197
    foreach ($reengagements as $reengagementcm) {
198
        // Get a list of users who are eligible to start this module.
199
        $startusers = reengagement_get_startusers($reengagementcm);
200
 
201
        // Prepare some objects for later db insertion.
202
        $reengagementinprogress = new stdClass();
203
        $reengagementinprogress->reengagement = $reengagementcm->rid;
204
        $reengagementinprogress->completiontime = $timenow + $reengagementcm->duration;
205
        $reengagementinprogress->emailtime = $timenow + $reengagementcm->emaildelay;
206
        $activitycompletion = new stdClass();
207
        $activitycompletion->coursemoduleid = $reengagementcm->cmid;
208
        $activitycompletion->completionstate = COMPLETION_INCOMPLETE;
209
        $activitycompletion->timemodified = $timenow;
210
        $userlist = array_keys($startusers);
211
        $newripcount = count($userlist); // Count of new reengagements-in-progress.
212
        if (debugging('', DEBUG_DEVELOPER) || ($newripcount && debugging('', DEBUG_ALL))) {
213
            mtrace("Adding $newripcount reengagements-in-progress to reengagementid " . $reengagementcm->rid);
214
        }
215
 
216
        foreach ($userlist as $userid) {
217
            $reengagementinprogress->userid = $userid;
218
            $DB->insert_record('reengagement_inprogress', $reengagementinprogress);
219
            $activitycompletion->userid = $userid;
220
            $DB->insert_record('course_modules_completion', $activitycompletion);
221
        }
222
    }
223
    $reengagements->close();
224
    // All new users have now been recorded as started.
225
    // See if any previous users are due to finish, &/or be emailed.
226
 
227
    // Get more info about the activity, & prepare to update db
228
    // and email users.
229
 
230
    $reengagementssql = "SELECT r.id as id, cm.id as cmid, r.emailcontent, r.emailcontentformat, r.emailsubject,
231
                                r.thirdpartyemails, r.emailcontentmanager, r.emailcontentmanagerformat, r.emailsubjectmanager,
232
                                r.emailcontentthirdparty, r.emailcontentthirdpartyformat, r.emailsubjectthirdparty,
233
                                r.emailuser, r.name, r.suppresstarget, r.remindercount, c.shortname as courseshortname,
234
                                c.fullname as coursefullname, c.id as courseid, r.emailrecipient, r.emaildelay
235
                          FROM {reengagement} r
236
                    INNER JOIN {course_modules} cm ON cm.instance = r.id
237
                    INNER JOIN {course} c ON cm.course = c.id
238
                          JOIN {modules} m on m.id = cm.module
239
                         WHERE m.name = 'reengagement'
240
                      ORDER BY r.id ASC";
241
 
242
    $reengagements = $DB->get_records_sql($reengagementssql);
243
 
244
    $inprogresssql = 'SELECT ri.*
245
                        FROM {reengagement_inprogress} ri
246
                        JOIN {reengagement} r ON r.id = ri.reengagement
247
                        JOIN {user} u ON u.id = ri.userid
248
                       WHERE u.deleted = 0 AND
249
                       completiontime < ? AND completed = 0';
250
    $inprogresses = $DB->get_recordset_sql($inprogresssql, array($timenow));
251
    $completeripcount = 0;
252
    foreach ($inprogresses as $inprogress) {
253
        $completeripcount++;
254
        // A user has completed an instance of the reengagement module.
255
        $inprogress->timedue = $inprogress->completiontime;
256
        $reengagement = $reengagements[$inprogress->reengagement];
257
        $cmid = $reengagement->cmid; // The cm id of the module which was completed.
258
        $userid = $inprogress->userid; // The userid which completed the module.
259
 
260
        // Check if user is still enrolled in the course.
261
        $context = context_module::instance($reengagement->cmid);
262
        if (!is_enrolled($context, $userid, 'mod/reengagement:startreengagement', true)) {
263
            $DB->delete_records('reengagement_inprogress', array('id' => $inprogress->id));
264
            continue;
265
        }
266
 
267
        // Update completion record to indicate completion so the user can continue with any dependant activities.
268
        $completionrecord = $DB->get_record('course_modules_completion', array('coursemoduleid' => $cmid, 'userid' => $userid));
269
        if (empty($completionrecord)) {
270
            mtrace("Could not find completion record to update complete state, userid: $userid, cmid: $cmid - recreating record.");
271
            // This might happen when reset_all_state has been triggered, deleting an "in-progress" record. so recreate it.
272
            $completionrecord = new stdClass();
273
            $completionrecord->coursemoduleid = $cmid;
274
            $completionrecord->completionstate = COMPLETION_COMPLETE_PASS;
275
            $completionrecord->viewed = COMPLETION_VIEWED;
276
            $completionrecord->overrideby = null;
277
            $completionrecord->timemodified = $timenow;
278
            $completionrecord->userid = $userid;
279
            $completionrecord->id = $DB->insert_record('course_modules_completion', $completionrecord);
280
        } else {
281
            $updaterecord = new stdClass();
282
            $updaterecord->id = $completionrecord->id;
283
            $updaterecord->completionstate = COMPLETION_COMPLETE_PASS;
284
            $updaterecord->timemodified = $timenow;
285
            $DB->update_record('course_modules_completion', $updaterecord) . " \n";
286
        }
287
        $completioncache = cache::make('core', 'completion');
288
        $completioncache->delete($userid . '_' . $reengagement->courseid);
289
 
290
        $cmcontext = context_module::instance($cmid, MUST_EXIST);
291
        // Trigger an event for course module completion changed.
292
        $event = \core\event\course_module_completion_updated::create(array(
293
            'objectid' => $completionrecord->id,
294
            'context' => $cmcontext,
295
            'relateduserid' => $userid,
296
            'other' => array(
297
                'relateduserid' => $userid
298
            )
299
        ));
300
        $event->add_record_snapshot('course_modules_completion', $completionrecord);
301
        $event->trigger();
302
 
303
        $result = false;
304
        if (($reengagement->emailuser == REENGAGEMENT_EMAILUSER_COMPLETION) ||
305
                ($reengagement->emailuser == REENGAGEMENT_EMAILUSER_NEVER) ||
306
                ($reengagement->emailuser == REENGAGEMENT_EMAILUSER_TIME && !empty($inprogress->emailsent))) {
307
            // No need to keep 'inprogress' record for later emailing
308
            // Delete inprogress record.
309
            debugging('', DEBUG_DEVELOPER) && mtrace("mode $reengagement->emailuser reengagementid $reengagement->id.
310
                      User marked complete, deleting inprogress record for user $userid");
311
            $result = $DB->delete_records('reengagement_inprogress', array('id' => $inprogress->id));
312
        } else {
313
            // Update inprogress record to indicate completion done.
314
            debugging('', DEBUG_DEVELOPER) && mtrace("mode $reengagement->emailuser reengagementid $reengagement->id
315
                      updating inprogress record for user $userid to indicate completion");
316
            $updaterecord = new stdClass();
317
            $updaterecord->id = $inprogress->id;
318
            $updaterecord->completed = COMPLETION_COMPLETE;
319
            $result = $DB->update_record('reengagement_inprogress', $updaterecord);
320
        }
321
        if (empty($result)) {
322
            // Skip emailing. Go on to next completion record so we don't risk emailing users continuously each cron.
323
            debugging('', DEBUG_ALL) && mtrace("Reengagement: not sending email to $userid regarding reengagementid
324
                      $reengagement->id due to failuer to update db");
325
            continue;
326
        }
327
        if ($reengagement->emailuser == REENGAGEMENT_EMAILUSER_COMPLETION) {
328
            debugging('', DEBUG_ALL) && mtrace("Reengagement: sending email to $userid regarding reengagementid
329
                      $reengagement->id due to completion.");
330
            reengagement_email_user($reengagement, $inprogress);
331
        }
332
    }
333
    $inprogresses->close();
334
 
335
    if (debugging('', DEBUG_DEVELOPER) || ($completeripcount && debugging('', DEBUG_ALL))) {
336
        mtrace("Found $completeripcount complete reengagements.");
337
    }
338
 
339
    // Get inprogress records where the user has reached their email time, and module is email 'after delay'.
340
    $inprogresssql = "SELECT ip.*, ip.emailtime as timedue
341
                        FROM {reengagement_inprogress} ip
342
                  INNER JOIN {reengagement} r on r.id = ip.reengagement
343
                        JOIN {user} u ON u.id = ip.userid
344
                       WHERE ip.emailtime < :emailtime
345
                             AND r.emailuser = " . REENGAGEMENT_EMAILUSER_TIME . '
346
                             AND ip.emailsent < r.remindercount
347
                             AND u.deleted = 0
348
                    ORDER BY r.id ASC';
349
    $params = array('emailtime' => $timenow);
350
 
351
    $inprogresses = $DB->get_recordset_sql($inprogresssql, $params);
352
    $emailduecount = 0;
353
    foreach ($inprogresses as $inprogress) {
354
        $emailduecount++;
355
        $reengagement = $reengagements[$inprogress->reengagement];
356
        $userid = $inprogress->userid; // The userid which completed the module.
357
 
358
        // Check if user is still enrolled in the course.
359
        $context = context_module::instance($reengagement->cmid);
360
        if (!is_enrolled($context, $userid, 'mod/reengagement:startreengagement', true)) {
361
            $DB->delete_records('reengagement_inprogress', array('id' => $inprogress->id));
362
            continue;
363
        }
364
 
365
        if ($inprogress->completed == COMPLETION_COMPLETE) {
366
            debugging('', DEBUG_DEVELOPER) && mtrace("mode $reengagement->emailuser reengagementid $reengagement->id.
367
                      User already marked complete. Deleting inprogress record for user $userid");
368
            $result = $DB->delete_records('reengagement_inprogress', array('id' => $inprogress->id));
369
        } else {
370
            debugging('', DEBUG_DEVELOPER) && mtrace("mode $reengagement->emailuser reengagementid $reengagement->id.
371
                      Updating inprogress record to indicate email sent for user $userid");
372
            $updaterecord = new stdClass();
373
            $updaterecord->id = $inprogress->id;
374
            if ($reengagement->remindercount > $inprogress->emailsent) {
375
                $updaterecord->emailtime = $timenow + $reengagement->emaildelay;
376
            }
377
            $updaterecord->emailsent = $inprogress->emailsent + 1;
378
            $result = $DB->update_record('reengagement_inprogress', $updaterecord);
379
        }
380
        if (!empty($result)) {
381
            debugging('', DEBUG_ALL) && mtrace("Reengagement: sending email to $userid regarding reengagementid
382
                      $reengagement->id due to emailduetime.");
383
            reengagement_email_user($reengagement, $inprogress);
384
        }
385
    }
386
    $inprogresses->close();
387
 
388
    if (debugging('', DEBUG_DEVELOPER) || ($emailduecount && debugging('', DEBUG_ALL))) {
389
        mtrace("Found $emailduecount reengagements due to be emailed.");
390
    }
391
 
392
    return true;
393
}
394
 
395
/**
396
 * Email is due to be sent to reengage user in course.
397
 * Check if there is any reason to not send, then email user.
398
 *
399
 * @param object $reengagement db record of details for this activity
400
 * @param object $inprogress record of user participation in this activity.
401
 * @return boolean true if everything we wanted to do worked. False otherwise.
402
 */
403
function reengagement_email_user($reengagement, $inprogress) {
404
    global $DB, $SITE, $CFG;
405
    $istotara = false;
406
    if (file_exists($CFG->dirroot.'/totara')) {
407
        $istotara = true;
408
    }
409
    $user = $DB->get_record('user', array('id' => $inprogress->userid));
410
    if (!empty($user->deleted)) {
411
        // User has been deleted - don't send an e-mail.
412
        return true;
413
    }
414
    if (!empty($reengagement->suppresstarget)) {
415
        $targetcomplete = reengagement_check_target_completion($user->id, $reengagement->suppresstarget);
416
        if ($targetcomplete) {
417
            debugging('', DEBUG_DEVELOPER) && mtrace('Reengagement modules: User:'.$user->id.
418
                      ' has completed target activity:'.$reengagement->suppresstarget.' suppressing email.');
419
            return true;
420
        }
421
    }
422
    // Where cron isn't run regularly, we could get a glut requests to send email that are either ancient, or too late to be useful.
423
    if (!empty($inprogress->timedue) && (($inprogress->timedue + 2 * DAYSECS) < time())) {
424
        // We should have sent this email more than two days ago.
425
        // Don't send.
426
        debugging('', DEBUG_ALL) && mtrace('Reengagement: ip id ' . $inprogress->id . 'User:'.$user->id.
427
                  ' Email not sent - was due more than 2 days ago.');
428
        return true;
429
    }
430
    if (!empty($inprogress->timeoverdue) && ($inprogress->timeoverdue < time())) {
431
        // There's a deadline hint provided, and we're past it.
432
        // Don't send.
433
        debugging('', DEBUG_ALL) && mtrace('Reengagement: ip id ' . $inprogress->id . 'User:'.$user->id.
434
                  ' Email not sent - past usefulness deadline.');
435
        return true;
436
    }
437
 
438
    debugging('', DEBUG_DEVELOPER) && mtrace('Reengagement modules: User:'.$user->id.' Sending email.');
439
 
440
    $templateddetails = reengagement_template_variables($reengagement, $inprogress, $user);
441
    $plaintext = html_to_text($templateddetails['emailcontent']);
442
 
443
    $emailresult = true;
444
    if ($istotara &&
445
        ($reengagement->emailrecipient == REENGAGEMENT_RECIPIENT_MANAGER) ||
446
        ($reengagement->emailrecipient == REENGAGEMENT_RECIPIENT_BOTH)) {
447
        // We're supposed to email the user's manager(s).
448
        $managerids = \totara_job\job_assignment::get_all_manager_userids($user->id);
449
        if (empty($managerids)) {
450
            // User has no manager(s).
451
            debugging('', DEBUG_ALL) && mtrace("user $user->id has no managers - not sending any manager emails.");
452
        } else {
453
            // User has manager(s).
454
            foreach ($managerids as $managerid) {
455
                $manager = $DB->get_record('user', array('id' => $managerid));
456
                $managersendresult = reengagement_send_notification($manager,
457
                    $templateddetails['emailsubjectmanager'],
458
                    html_to_text($templateddetails['emailcontentmanager']),
459
                    $templateddetails['emailcontentmanager'],
460
                    $reengagement
461
                );
462
                if (!$managersendresult) {
463
                    mtrace("failed to send manager of user $user->id email for reengagement $reengagement->id");
464
                }
465
                $emailresult = $emailresult && $managersendresult;
466
            }
467
        }
468
    }
469
    if (($reengagement->emailrecipient == REENGAGEMENT_RECIPIENT_USER) ||
470
        ($reengagement->emailrecipient == REENGAGEMENT_RECIPIENT_BOTH)) {
471
        // We are supposed to send email to the user.
472
        $usersendresult = reengagement_send_notification($user,
473
            $templateddetails['emailsubject'],
474
            $plaintext,
475
            $templateddetails['emailcontent'],
476
            $reengagement
477
        );
478
        if (!$usersendresult) {
479
            mtrace("failed to send user $user->id email for reengagement $reengagement->id");
480
        }
481
        $emailresult = $emailresult && $usersendresult;
482
    }
483
 
484
    if (!empty($reengagement->thirdpartyemails)) {
485
        // Process third-party emails.
486
        $emails = array_map('trim', explode(',', $reengagement->thirdpartyemails));
487
        foreach ($emails as $emailaddress) {
488
            if (!validate_email($emailaddress)) {
489
                debugging('', DEBUG_ALL) && mtrace("invalid third-party email: $email - skipping send");
490
                continue;
491
            }
492
            if ($istotara) {
493
                $thirdpartyuser = \totara_core\totara_user::get_external_user($emailaddress);
494
            } else {
495
                $thirdpartyuser = core_user::get_noreply_user();
496
                $thirdpartyuser->firstname = $emailaddress;
497
                $thirdpartyuser->email = $emailaddress;
498
                $thirdpartyuser->maildisplay = 1;
499
                $thirdpartyuser->emailstop = 0;
500
            }
501
 
502
            debugging('', DEBUG_ALL) && mtrace("sending third-party email to: $emailaddress");
503
 
504
            $usersendresult = reengagement_send_notification($thirdpartyuser,
505
                    $templateddetails['emailsubjectthirdparty'],
506
                    html_to_text($templateddetails['emailcontentthirdparty']),
507
                    $templateddetails['emailcontentthirdparty'],
508
                    $reengagement
509
                );
510
            if (!$usersendresult) {
511
                mtrace("failed to send user $user->id email for reengagement $reengagement->id");
512
            }
513
            $emailresult = $emailresult && $usersendresult;
514
 
515
        }
516
    }
517
 
518
    return $emailresult;
519
}
520
 
521
 
522
/**
523
 * Send reengagement notifications using the messaging system.
524
 *
525
 * @param object $userto User we are sending the notification to
526
 * @param string $subject message subject
527
 * @param string $messageplain plain text message
528
 * @param string $messagehtml html message
529
 * @param object $reengagement database record
530
 */
531
function reengagement_send_notification($userto, $subject, $messageplain, $messagehtml, $reengagement) {
532
    $eventdata = new \core\message\message();
533
    $eventdata->courseid = $reengagement->courseid;
534
    $eventdata->modulename = 'reengagement';
535
    $eventdata->userfrom = core_user::get_support_user();
536
    $eventdata->userto = $userto;
537
    $eventdata->subject = $subject;
538
    $eventdata->fullmessage = $messageplain;
539
    $eventdata->fullmessageformat = FORMAT_HTML;
540
    $eventdata->fullmessagehtml = $messagehtml;
541
    $eventdata->smallmessage = $subject;
542
 
543
    // Required for messaging framework.
544
    $eventdata->name = 'mod_reengagement';
545
    $eventdata->component = 'mod_reengagement';
546
 
547
    return message_send($eventdata);
548
}
549
 
550
 
551
/**
552
 * Template variables into place in supplied email content.
553
 *
554
 * @param object $reengagement db record of details for this activity
555
 * @param object $inprogress record of user participation in this activity - semiplanned future enhancement.
556
 * @param object $user record of user being reengaged.
557
 * @return array - the content of the fields after templating.
558
 */
559
function reengagement_template_variables($reengagement, $inprogress, $user) {
560
    global $CFG, $DB;
561
 
562
    require_once($CFG->dirroot.'/user/profile/lib.php');
563
 
564
    $templatevars = array(
565
        '/%courseshortname%/' => $reengagement->courseshortname,
566
        '/%coursefullname%/' => $reengagement->coursefullname,
567
        '/%courseid%/' => $reengagement->courseid,
568
        '/%userfirstname%/' => $user->firstname,
569
        '/%userlastname%/' => $user->lastname,
570
        '/%userid%/' => $user->id,
571
        '/%usercity%/' => $user->city,
572
        '/%userinstitution%/' => $user->institution,
573
        '/%userdepartment%/' => $user->department,
574
    );
575
    // Add the users course groups as a template item.
576
    $groups = $DB->get_records_sql_menu("SELECT g.id, g.name
577
                                   FROM {groups_members} gm
578
                                   JOIN {groups} g
579
                                    ON g.id = gm.groupid
580
                                  WHERE gm.userid = ? AND g.courseid = ?
581
                                   ORDER BY name ASC", array($user->id, $reengagement->courseid));
582
 
583
    if (!empty($groups)) {
584
        $templatevars['/%usergroups%/'] = implode(', ', $groups);
585
    } else {
586
        $templatevars['/%usergroups%/'] = '';
587
    }
588
 
589
    // Now do custom user fields.
590
    $fields = profile_get_custom_fields();
591
    if (!empty($fields)) {
592
        $userfielddata = $DB->get_records('user_info_data', array('userid' => $user->id), '', 'fieldid, data, dataformat');
593
        foreach ($fields as $field) {
594
            if (!empty($userfielddata[$field->id])) {
595
                if ($field->datatype == 'datetime') {
596
                    if (!empty($field->param3)) {
597
                        $format = get_string('strftimedaydatetime', 'langconfig');
598
                    } else {
599
                        $format = get_string('strftimedate', 'langconfig');
600
                    }
601
 
602
                    $templatevars['/%profilefield_'.$field->shortname.'%/'] = userdate($userfielddata[$field->id]->data, $format);
603
                } else {
604
                    $templatevars['/%profilefield_'.$field->shortname.'%/'] = format_text($userfielddata[$field->id]->data,
605
                                                                                          $userfielddata[$field->id]->dataformat);
606
                }
607
 
608
            } else {
609
                $templatevars['/%profilefield_'.$field->shortname.'%/'] = '';
610
            }
611
        }
612
    }
613
    $patterns = array_keys($templatevars); // The placeholders which are to be replaced.
614
    $replacements = array_values($templatevars); // The values which are to be templated in for the placeholders.
615
 
616
    // Array to describe which fields in reengagement object should have a template replacement.
617
    $replacementfields = array('emailsubject', 'emailcontent', 'emailsubjectmanager', 'emailcontentmanager',
618
                               'emailsubjectthirdparty', 'emailcontentthirdparty');
619
 
620
    $results = array();
621
    // Replace %variable% with relevant value everywhere it occurs in reengagement->field.
622
    foreach ($replacementfields as $field) {
623
        $results[$field] = preg_replace($patterns, $replacements, $reengagement->$field);
624
    }
625
 
626
    // Apply enabled filters to email content.
627
    $options = array(
628
            'context' => context_course::instance($reengagement->courseid),
629
            'noclean' => true,
630
            'trusted' => true
631
    );
632
    $subjectfields = array('emailsubject', 'emailsubjectmanager', 'emailsubjectthirdparty');
633
    foreach ($subjectfields as $field) {
634
        $results[$field] = format_text($results[$field], FORMAT_PLAIN, $options);
635
    }
636
    $contentfields = array('emailcontent', 'emailcontentmanager', 'emailcontentthirdparty');
637
    foreach ($contentfields as $field) {
638
        $results[$field] = format_text($results[$field], FORMAT_MOODLE, $options);
639
    }
640
 
641
    return $results;
642
}
643
 
644
/**
645
 * Must return an array of user records (all data) who are participants
646
 * for a given instance of reengagement. Must include every user involved
647
 * in the instance, independient of his role (student, teacher, admin...)
648
 * See other modules as example.
649
 *
650
 * @param int $reengagementid ID of an instance of this module
651
 * @return mixed boolean/array of students
652
 */
653
function reengagement_get_participants($reengagementid) {
654
    return false;
655
}
656
 
657
 
658
/**
659
 * Checks if a scale is being used.
660
 *
661
 * This is used by the backup code to decide whether to back up a scale
662
 * @param int $reengagementid
663
 * @param int $scaleid
664
 * @return boolean True if the scale is used by the assignment
665
 */
666
function reengagement_scale_used($reengagementid, $scaleid) {
667
    $return = false;
668
 
669
    return $return;
670
}
671
 
672
 
673
/**
674
 * This is used to find out if scale used anywhere.
675
 *
676
 * @param int $scaleid
677
 * @return boolean True if the scale is used by any reengagement
678
 */
679
function reengagement_scale_used_anywhere($scaleid) {
680
    return false;
681
}
682
 
683
 
684
/**
685
 * Execute post-install custom actions for the module
686
 * This function was added in 1.9
687
 *
688
 * @return boolean true if success, false on error
689
 */
690
function reengagement_install() {
691
    return true;
692
}
693
 
694
 
695
/**
696
 * Implementation of the function for printing the form elements that control
697
 * whether the course reset functionality affects the choice.
698
 *
699
 * @param object $mform form passed by reference
700
 */
701
function reengagement_reset_course_form_definition(&$mform) {
702
    $mform->addElement('header', 'reengagementheader', get_string('modulenameplural', 'reengagement'));
703
}
704
 
705
/**
706
 * Course reset form defaults.
707
 *
708
 * @param object $course
709
 * @return array
710
 */
711
function reengagement_reset_course_form_defaults($course) {
712
    return array('reset_reengagement' => 1);
713
}
714
 
715
/**
716
 * Actual implementation of the reset course functionality, delete all the
717
 * choice responses for course $data->courseid.
718
 *
719
 * @param object $data the data submitted from the reset course.
720
 * @return array status array
721
 */
722
function reengagement_reset_userdata($data) {
723
    global $DB;
724
 
725
    $componentstr = get_string('modulenameplural', 'reengagement');
726
    $status = array();
727
 
728
    if (!empty($data->reset_reengagement)) {
729
        $reengagementsql = "SELECT ch.id
730
                       FROM {reengagement} ch
731
                       WHERE ch.course=?";
732
 
733
        $DB->delete_records_select('reengagement_inprogress', "reengagement IN ($reengagementsql)", array($data->courseid));
734
        $status[] = array('component' => $componentstr, 'item' => get_string('removeresponses', 'reengagement'), 'error' => false);
735
    }
736
 
737
    return $status;
738
}
739
 
740
/**
741
 * Get array of users who can start supplied reengagement module
742
 *
743
 * @param object $reengagement - reengagement record.
744
 * @return array
745
 */
746
function reengagement_get_startusers($reengagement) {
747
    global $DB;
748
    $context = context_module::instance($reengagement->cmid);
749
 
750
    list($esql, $params) = get_enrolled_sql($context, 'mod/reengagement:startreengagement', 0, true);
751
 
752
    // Get a list of people who already started this reengagement (finished users are included in this list)
753
    // (based on activity completion records).
754
    $alreadycompletionsql = "SELECT userid
755
                               FROM {course_modules_completion}
756
                              WHERE coursemoduleid = :alcmoduleid";
757
    $params['alcmoduleid'] = $reengagement->id;
758
 
759
    // Get a list of people who already started this reengagement
760
    // (based on reengagement_inprogress records).
761
    $alreadyripsql = "SELECT userid
762
                        FROM {reengagement_inprogress}
763
                       WHERE reengagement = :ripmoduleid";
764
    $params['ripmoduleid'] = $reengagement->rid;
765
 
766
    $sql = "SELECT u.*
767
              FROM {user} u
768
              JOIN ($esql) je ON je.id = u.id
769
             WHERE u.deleted = 0
770
             AND u.id NOT IN ($alreadycompletionsql)
771
             AND u.id NOT IN ($alreadyripsql)";
772
 
773
    $startusers = $DB->get_records_sql($sql, $params);
774
    foreach ($startusers as $startcandidate) {
775
        $modinfo = get_fast_modinfo($reengagement->courseid, $startcandidate->id);
776
        $cm = $modinfo->get_cm($reengagement->cmid);
777
        $ainfomod = new \core_availability\info_module($cm);
778
        $information = '';
779
        if (empty($startcandidate->confirmed)) {
780
            // Exclude unconfirmed users. Typically this shouldn't happen, but if an unconfirmed user
781
            // has been enrolled to a course we shouldn't e-mail them about activities they can't access yet.
782
            unset($startusers[$startcandidate->id]);
783
            continue;
784
        }
785
        // Exclude users who can't see this activity.
786
        if (!$ainfomod->is_available($information, false, $startcandidate->id, $modinfo)) {
787
            unset($startusers[$startcandidate->id]);
788
        }
789
    }
790
 
791
    return $startusers;
792
}
793
 
794
 
795
/**
796
 * Return the list of Moodle features this module supports
797
 *
798
 * @param string $feature FEATURE_xx constant for requested feature
799
 * @return mixed True if module supports feature, null if doesn't know
800
 */
801
function reengagement_supports($feature) {
802
    switch($feature) {
803
        case FEATURE_GROUPS:
804
            return false;
805
        case FEATURE_GROUPINGS:
806
            return false;
807
        case FEATURE_MOD_INTRO:
808
            return false;
809
        case FEATURE_COMPLETION_TRACKS_VIEWS:
810
            return false;
811
        case FEATURE_COMPLETION_HAS_RULES:
812
            return true;
813
        case FEATURE_GRADE_HAS_GRADE:
814
            return false;
815
        case FEATURE_GRADE_OUTCOMES:
816
            return false;
817
        case FEATURE_BACKUP_MOODLE2:
818
            return true;
819
        case FEATURE_SHOW_DESCRIPTION:
820
            return true;
821
        case FEATURE_MOD_PURPOSE:
822
            return MOD_PURPOSE_COMMUNICATION;
823
        default:
824
            return null;
825
    }
826
}
827
 
828
/**
829
 * Process an arbitary number of seconds, and prepare to display it as 'W seconds', 'X minutes', or Y hours or Z weeks.
830
 *
831
 * @param int $duration FEATURE_xx constant for requested feature
832
 * @param boolean $periodstring - return period as string.
833
 * @return array
834
 */
835
function reengagement_get_readable_duration($duration, $periodstring = false) {
836
    $period = 1; // Default to dealing in seconds.
837
    $periodcount = $duration; // Default to dealing in seconds.
838
    $periods = array(WEEKSECS, DAYSECS, HOURSECS, MINSECS);
839
    foreach ($periods as $period) {
840
        if (($duration % $period) == 0) {
841
            // Duration divides exactly into periods.
842
            $periodcount = floor((int)$duration / (int)$period);
843
            break;
844
        }
845
    }
846
    if ($periodstring) {
847
        // Caller wants function to return in the format (30, 'minutes'), not (30, 60).
848
        if ($period == MINSECS) {
849
            $period = get_string('minutes', 'reengagement');
850
        } else if ($period == HOURSECS) {
851
            $period = get_string('hours', 'reengagement');
852
        } else if ($period == DAYSECS) {
853
            $period = get_string('days', 'reengagement');
854
        } else if ($period == WEEKSECS) {
855
            $period = get_string('weeks', 'reengagement');
856
        } else {
857
            $period = get_string('weeks', 'reengagement');
858
        }
859
    }
860
    return array($periodcount, $period); // Example 5, 60 is 5 minutes.
861
}
862
 
863
/**
864
 * Check if user has completed the named course moduleid
865
 * @param int $userid idnumber of the user to be checked.
866
 * @param int $targetcmid the id of the coursemodule we should be checking.
867
 * @return bool true if user has completed the target activity, false otherwise.
868
 */
869
function reengagement_check_target_completion($userid, $targetcmid) {
870
    global $DB;
871
    // This reengagement is focused on getting people to do a particular (ie targeted) activity.
872
    // Behaviour of the module changes depending on whether the target activity is already complete.
873
    $conditions = array('userid' => $userid, 'coursemoduleid' => $targetcmid);
874
    $activitycompletion = $DB->get_record('course_modules_completion', $conditions);
875
    if ($activitycompletion) {
876
        // There is a target activity, and completion is enabled in that activity.
877
        $userstate = $activitycompletion->completionstate;
878
        if (in_array($userstate, array(COMPLETION_COMPLETE, COMPLETION_COMPLETE_PASS, COMPLETION_COMPLETE_FAIL))) {
879
            return true;
880
        }
881
    }
882
    return false;
883
}
884
 
885
/**
886
 * Method to check if existing user is eligble and cron hasn't run yet.
887
 * @param stdclass $course the course record.
888
 * @param stdclass $cm the coursemodule we should be checking.
889
 * @param stdclass $reengagement the full record.
890
 * @return string
891
 */
892
function reengagement_checkstart($course, $cm, $reengagement) {
893
    global $DB, $USER, $OUTPUT;
894
    $output = '';
895
    $modinfo = get_fast_modinfo($course->id);
896
    $cminfo = $modinfo->get_cm($cm->id);
897
 
898
    $ainfomod = new \core_availability\info_module($cminfo);
899
 
900
    // User could have arrived here eligible to start, but before cron had a chance to start them in the activity.
901
    // Check for that scenario.
902
    $completion = $DB->get_record('course_modules_completion', array('userid' => $USER->id, 'coursemoduleid' => $cm->id));
903
    if (empty($completion)) {
904
        // User hasn't yet started this activity.
905
        $availabilityinfo = '';
906
        if (!$ainfomod->is_available($availabilityinfo)) {
907
            // User has satisfied all activity completion preconditions, start them on this activity.
908
            // Set a RIP record, so we know when to send an email/mark activity as complete by cron later.
909
            $reengagementinprogress = new stdClass();
910
            $reengagementinprogress->reengagement = $reengagement->id;
911
            $reengagementinprogress->completiontime = time() + $reengagement->duration;
912
            $reengagementinprogress->emailtime = time() + $reengagement->emaildelay;
913
            $reengagementinprogress->userid = $USER->id;
914
            $DB->insert_record('reengagement_inprogress', $reengagementinprogress);
915
 
916
            // Set activity completion in-progress record to fit in with normal activity completion requirements.
917
            $activitycompletion = new stdClass();
918
            $activitycompletion->coursemoduleid = $cm->id;
919
            $activitycompletion->completionstate = COMPLETION_INCOMPLETE;
920
            $activitycompletion->timemodified = time();
921
            $activitycompletion->userid = $USER->id;
922
            $DB->insert_record('course_modules_completion', $activitycompletion);
923
            // Re-load that same info.
924
            $completion = $DB->get_record('course_modules_completion', array('userid' => $USER->id, 'coursemoduleid' => $cm->id));
925
 
926
        } else {
927
            // The user has permission to start a reengagement, but not this one (likely due to incomplete prerequiste activities).
928
            $report = "This reengagement is not available";
929
            if ($availabilityinfo) {
930
                $report .= " ( $availabilityinfo ) ";
931
            }
932
            $output .= $OUTPUT->box($report);
933
        }
934
    }
935
    if (!empty($completion)) {
936
        $rip = $DB->get_record('reengagement_inprogress', array('userid' => $USER->id, 'reengagement' => $reengagement->id));
937
    }
938
    $dateformat = get_string('strftimedatetime', 'langconfig'); // Description of how to format times in user's language.
939
    if (!empty($completion) && !empty($rip)) {
940
        // User is genuinely in-progress.
941
        if ($reengagement->emailuser == REENGAGEMENT_EMAILUSER_TIME && empty($rip->emailsent)) {
942
            $emailpending = true;
943
            $emailtime = $rip->emailtime;
944
        } else if ($reengagement->emailuser == REENGAGEMENT_EMAILUSER_COMPLETION && empty($rip->completed)) {
945
            $emailpending = true;
946
            $emailtime = $rip->completiontime;
947
        } else {
948
            $emailpending = false;
949
        }
950
 
951
        $datestr = userdate($rip->emailtime, $dateformat);
952
        if ($emailpending) {
953
            if (empty($reengagement->suppresstarget)) {
954
                // You'll get an email at xyz time.
955
                $emailmessage = get_string('receiveemailattimex', 'reengagement', $datestr);
956
            } else {
957
                // There is a target activity, if the target activity is complete, we won't send the email.
958
                $targetcomplete = reengagement_check_target_completion($USER->id, $cm->id);
959
                if (!$targetcomplete) {
960
                    // Message will be sent at xyz time unless you complete target activity.
961
                    $emailmessage = get_string('receiveemailattimexunless', 'reengagement', $datestr);
962
                } else {
963
                    // Message scheduled for xyz time will not be sent because you have completed the target activity.
964
                    $emailmessage = get_string('noemailattimex', 'reengagement', $datestr);
965
                }
966
            }
967
            $output .= $OUTPUT->box($emailmessage);
968
        }
969
 
970
        // Activity completion can be independent of email time. Show completion time too.
971
        if ($completion->completionstate == COMPLETION_INCOMPLETE) {
972
            $datestr = userdate($rip->completiontime, $dateformat);
973
            // This activity will complete at XYZ time.
974
            $completionmessage = get_string('completeattimex', 'reengagement', $datestr);
975
        } else {
976
            // This activity has been marked as complete.
977
            $completionmessage = get_string('activitycompleted', 'reengagement');
978
        }
979
        $output .= $OUTPUT->box($completionmessage);
980
    }
981
    return $output;
982
}