Commit a9985519 authored by Robert Lyon's avatar Robert Lyon Committed by Cecilia Vela Gurovic

Bug 1846412: Self-changing authentication method for SAML

For moving accounts from one external SAML auth to another across
institution boundaries so that learners can move their account into
another institution within the same Mahara instance with as little
admin involvement as possible.

behatnotneeded: Can't be tested due to reliance on SAML and the receipt
of an email

Change-Id: I7f39ec0ab894d665cd9a97943b3c78b83528c674
Signed-off-by: Robert Lyon's avatarRobert Lyon <robertl@catalyst.net.nz>
parent e660923d
......@@ -10,7 +10,7 @@
*/
define('INTERNAL', 1);
define('MENUITEM', 'engage/institutions');
define('MENUITEM', 'settings/institutions');
define('SECTION_PLUGINTYPE', 'core');
define('SECTION_PLUGINNAME', 'account');
define('SECTION_PAGE', 'institutions');
......@@ -84,8 +84,7 @@ if (!empty($requested)) {
'type' => 'submit',
'name' => '_cancelrequest_' . $i,
'class' => 'btn-secondary',
'title' => get_string('youhaverequestedmembershipof', 'mahara',
$institutions[$i]->displayname),
'title' => $institutions[$i]->displayname,
'value' => get_string('cancelrequest')
);
unset($institutions[$i]);
......@@ -129,8 +128,7 @@ if (!empty($invited)) {
'name' => 'invite_' . $i,
'options' => array('confirm', 'decline'),
'primarychoice' => 'confirm',
'title' => get_string('youhavebeeninvitedtojoin', 'mahara',
$institutions[$i]->displayname),
'title' => $institutions[$i]->displayname,
'class' => 'btn-secondary',
'value' => array(get_string('joininstitution'), get_string('decline'))
);
......@@ -235,4 +233,7 @@ $smarty->assign('memberform', $memberform);
$smarty->assign('requestedform', $requestedform);
$smarty->assign('invitedform', $invitedform);
$smarty->assign('joinform', $joinform);
$smarty->assign('migrateurl', get_config('wwwroot') . 'account/migrateinstitution.php');
$smarty->assign('sitename', get_config('sitename'));
$smarty->assign('SUBPAGENAV', account_institution_get_menu_tabs());
$smarty->display('account/institutions.tpl');
This diff is collapsed.
<?php
/**
*
* @package mahara
* @subpackage core
* @author Catalyst IT Ltd
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL version 3 or later
* @copyright For copyright information on Mahara, please see the README file distributed with this software.
*
*/
define('INTERNAL', 1);
define('MENUITEM', 'settings/institutions');
define('SECTION_PLUGINTYPE', 'core');
define('SECTION_PLUGINNAME', 'account');
define('SECTION_PAGE', 'migrateinstitution');
require(dirname(dirname(__FILE__)) . '/init.php');
define('TITLE', get_string('institutionmembership'));
define('SUBSECTIONHEADING', get_string('selfmigration', 'mahara'));
$key = param_alphanum('key');
$token = param_alphanum('token');
$migrate_record = get_record('usr_institution_migrate', 'key', $key, 'token', $token);
if ($USER->get('id') != $migrate_record->usr) {
// Only migrating user can complete migration so need to be logged in as that user
$USER->logout();
$SESSION->add_error_msg(get_string('cannotcompletemigrationwithuser'));
redirect(get_config('wwwroot'));
}
$error = $errorlink = null;
$message = null;
$form = null;
$formmessage = null;
if (!$migrate_record || (strtotime($migrate_record->ctime) < strtotime('-30 mins'))) {
$error = get_string('invalidkeytoken');
$errorlink = get_string('restartmigration', 'mahara', get_config('wwwroot') . 'account/migrateinstitution.php');
delete_records('usr_institution_migrate', 'id', $migrate_record->id);
}
else {
// We are wanting to migrate the user so show the confirm form
$form = array(
'name' => 'migrateconfirm',
'elements' => array(
'key' => array(
'type' => 'hidden',
'value' => $key,
),
'token' => array(
'type' => 'hidden',
'value' => $token,
),
'submit' => array(
'type' => 'submitcancel',
'class' => 'btn-primary',
'goto' => get_config('wwwroot') . 'account/migrateinstitutionconfirm.php?key=' . $key . '&token=' . $token,
'value' => array(get_string('confirmmigration'), get_string('cancel')),
),
),
);
$form = pieform($form);
$toauth = get_record('auth_instance', 'id', $migrate_record->new_authinstance);
$toinstitutionname = get_field('institution', 'displayname', 'name', $toauth->institution);
$fromauth = get_record('auth_instance', 'id', $migrate_record->old_authinstance);
$frominstitutionname = get_field('institution', 'displayname', 'name', $fromauth->institution);
$formmessage = get_string('migrateaccountconfirminfo', 'mahara', $frominstitutionname, $toinstitutionname);
}
function migrateconfirm_cancel_submit(Pieform $form) {
global $migrate_record, $SESSION;
$SESSION->set('postmigrateresponse', false);
delete_records('usr_institution_migrate', 'id', $migrate_record->id);
$SESSION->add_ok_msg(get_string('migrationcancelled'));
redirect(get_config('wwwroot') . 'account/migrateinstitution.php');
}
function migrateconfirm_submit(Pieform $form, $values) {
global $USER, $SESSION, $migrate_record;
$user = new User();
$user->find_by_id($migrate_record->usr);
$toauth = get_record('auth_instance', 'id', $migrate_record->new_authinstance);
$fromauth = get_record('auth_instance', 'id', $migrate_record->old_authinstance);
$toinstitutionname = get_field('institution', 'displayname', 'name', $toauth->institution);
if ($fromauth->institution != 'mahara') {
$user->leave_institution($fromauth->institution);
}
if ($toauth->institution != 'mahara') {
$user->join_institution($toauth->institution);
}
$user->authinstance = $migrate_record->new_authinstance;
$user->commit();
set_user_primary_email($user->get('id'), $migrate_record->email);
// Add username to auth_remote_user table but make sure this user doesn't have an existing remoteuser link
// and make sure the IdP username doesn't already have a link to another authinstance
delete_records('auth_remote_user', 'authinstance', $migrate_record->old_authinstance, 'localusr', $migrate_record->usr);
delete_records('auth_remote_user', 'remoteusername', $migrate_record->new_username);
ensure_record_exists('auth_remote_user',
(object) array('authinstance' => $migrate_record->new_authinstance,
'localusr' => $migrate_record->usr),
(object) array('authinstance' => $migrate_record->new_authinstance,
'remoteusername' => $migrate_record->new_username,
'localusr' => $migrate_record->usr)
);
delete_records('usr_institution_migrate', 'id', $migrate_record->id);
$USER->logout();
$SESSION->add_ok_msg(get_string('migratesuccess', 'mahara', $toinstitutionname));
redirect(get_config('wwwroot'));
}
$smarty = smarty();
setpageicon($smarty, 'icon-university');
$smarty->assign('message', $message);
$smarty->assign('error', $error);
$smarty->assign('errorlink', $errorlink);
$smarty->assign('confirmform', $form);
$smarty->assign('sitename', get_config('sitename'));
$smarty->assign('confirmforminfo', $formmessage);
$smarty->assign('SUBPAGENAV', account_institution_get_menu_tabs());
$smarty->display('account/migrateinstitutionconfirm.tpl');
\ No newline at end of file
......@@ -2227,6 +2227,14 @@ function auth_clean_expired_password_requests() {
WHERE expiry < ?', array(db_format_timestamp(time())));
}
/**
* Removes self-migration requests that were not completed in the allowed amount of time
*/
function auth_clean_expired_migrations() {
delete_records_sql('DELETE FROM {usr_institution_migrate}
WHERE ctime < ?', array(db_format_timestamp(strtotime('-30 mins'))));
}
function _email_or_notify($user, $subject, $bodytext, $bodyhtml) {
try {
email_user($user, null, $subject, $bodytext, $bodyhtml);
......
......@@ -82,12 +82,17 @@ if (!validateUrlSyntax($wantsurl, 's?H?S?F?E?u-P-a?I?p?f?q?r?')) {
$wantsurl = $CFG->wwwroot;
}
$migratecheck = param_variable("migratecheck", false);
if ($migratecheck) {
$SESSION->set('migratecheck', $migratecheck);
}
// trim off any reference to login and stash
$SESSION->wantsurl = preg_replace('/\&login$/', '', $wantsurl);
$as = new SimpleSAML\Auth\Simple($sp);
$idp_entityid = null;
if (! $as->isAuthenticated()) {
if (($USER->is_logged_in() && $migratecheck) || !$as->isAuthenticated()) {
if (param_variable("idpentityid", false)) {
$idp_entityid = param_variable("idpentityid", false);
}
......@@ -115,7 +120,12 @@ if (! $as->isAuthenticated()) {
// reinitialise config to pickup idp entityID
SimpleSAML_Configuration::init(get_config('docroot') . 'auth/saml/config');
$as = new SimpleSAML\Auth\Simple('default-sp');
$as->requireAuth(array('ReturnTo' => get_config('wwwroot') . "auth/saml/index.php"));
if ($migratecheck) {
$as->login(array('ReturnTo' => get_config('wwwroot') . "auth/saml/index.php", 'KeepPost' => FALSE));
}
else {
$as->requireAuth(array('ReturnTo' => get_config('wwwroot') . "auth/saml/index.php", 'KeepPost' => FALSE));
}
// ensure that $_SESSION is cleared for simplesamlphp
if (isset($_SESSION['wantsurl'])) {
......@@ -152,6 +162,15 @@ if (!$instance) {
throw new UserNotFoundException(get_string('errorbadinstitution', 'auth.saml'));
}
if ($SESSION->get('migratecheck')) {
$has_access = auth_saml_migrate_check($instance, $saml_attributes);
$SESSION->set('migrateidp', null);
$SESSION->set('migrateidpkey', null);
$SESSION->set('migratecheck', null);
$SESSION->set('migrateresponse', $has_access);
redirect(get_config('wwwroot') . 'account/migrateinstitution.php');
}
// stash the existing logged in user - if we have one
$current_user = $USER;
$is_loggedin = $USER->is_logged_in();
......@@ -595,3 +614,15 @@ function auth_saml_login_submit(Pieform $form, $values) {
// all happy - carry on now
redirect('/auth/saml/index.php');
}
function auth_saml_migrate_check($instance, $saml_attributes) {
global $USER, $SESSION;
$idp = $SESSION->get('migrateidp');
$idpattribute = $SESSION->get('migrateidpkey');
if (isset($saml_attributes[$idpattribute]) && $saml_attributes[$idpattribute][0] == $idp) {
// successfully proved they can log into the IdP so return the saml_attribure array
return array('instance' => $instance, 'saml_attributes' => $saml_attributes);
}
return false;
}
......@@ -117,3 +117,9 @@ $string['ssolabelfor'] = '%s login';
// or create a htdocs/local/lang/en.utf8/auth.saml.php file and add them there.
// They need to have the key 'login' + shortname of institution, eg:
// For 'testinstitution' it would be: $string['logintestinstitution'] = 'Special label';
$string['noentityidpfound'] = 'No Identity Provider ID found';
$string['novalidauthinstanceprovided'] = 'Your selection is not possible. Please select a different institution.';
$string['identityprovider'] = 'Identity Provider';
$string['selectmigrateto'] = 'Select institution to move to ...';
......@@ -43,8 +43,8 @@ if (get_field('auth_installed', 'active', 'name', 'saml') != 1) {
PluginAuthSaml::init_simplesamlphp();
// Bug #1693426: destroy mahara session when Single Logout is initiated by IdP
if ($USER->is_logged_in()) {
if ($USER->is_logged_in() && !$SESSION->get('postmigrateresponse')) {
$USER->logout();
}
$SESSION->set('saml_logout', true);
require('../extlib/simplesamlphp/modules/saml/www/sp/saml2-logout.php');
......@@ -274,7 +274,7 @@ class User {
. '
)';
$user = get_record_sql($sql, array($username, $instanceid));
if ($user) {
if ($user && $user->id > 0) {
$this->populate($user);
return $this;
}
......@@ -1646,6 +1646,15 @@ class User {
}
}
}
/**
* Gets the primary institution
* @return institution id or 'mahara' if not set
*/
public function get_primary_institution() {
$institutions = array_keys($this->get('institutions'));
return !empty($institutions[0]) ? $institutions[0] : 'mahara';
}
}
......@@ -1981,15 +1990,6 @@ class LiveUser extends User {
return $this->defaults[$key];
}
/**
* Gets the primary institution
* @return institution id or 'mahara' if not set
*/
public function get_primary_institution() {
$institutions = array_keys($this->get('institutions'));
return !empty($institutions[0]) ? $institutions[0] : 'mahara';
}
/**
* Sets the property keyed by $key
*/
......
......@@ -145,4 +145,9 @@ $smarty->assign('PAGEHEADING', null);
$smarty->assign('pagename', $pagename);
$smarty->assign('url', $urls);
$smarty->assign('page_content', get_site_page_content($pagename));
if ($SESSION->get('saml_logout')) {
// Allow the template call the iframe breaker
$SESSION->set('saml_logout', null);
$smarty->assign('saml_logout', true);
}
$smarty->display('index.tpl');
......@@ -388,6 +388,10 @@ if (!defined('CLI')) {
header("Content-Security-Policy: frame-ancestors 'self' $csp_ancestor_exemption");
header('X-Frame-Options: ALLOW-FROM '. $csp_ancestor_exemption);
}
else if ($saml_logout = $SESSION->get('saml_logout')) {
// To allow IDP SAML to logout within an iframe we temporarily ignore content security policy
// This is set via auth/saml/sp/saml2-logout.php
}
else {
header("Content-Security-Policy: frame-ancestors 'self'");
header('X-Frame-Options: SAMEORIGIN');
......
......@@ -815,9 +815,7 @@ $string['institutionmembershipdescription'] = 'If you are a member of any instit
$string['youareamemberof'] = 'You are a member of %s';
$string['leaveinstitution'] = 'Leave institution';
$string['reallyleaveinstitution'] = 'Are you sure you want to leave this institution?';
$string['youhaverequestedmembershipof'] = 'You have requested membership of %s.';
$string['cancelrequest'] = 'Cancel request';
$string['youhavebeeninvitedtojoin'] = 'You have been invited to join %s.';
$string['confirminvitation'] = 'Confirm invitation';
$string['joininstitution'] = 'Join institution';
$string['decline'] = 'Decline';
......@@ -1371,3 +1369,81 @@ $string['viewartefact'] = 'View ';
//Accessing an outdated url that will now redirect with session message
$string['viewartefactdatavuamodal'] = 'The data for %s artefact "%s" cannot be viewed this way anymore. Please go to page "%s" to view it.';
$string['chooselanguage'] = 'Choose language';
$string['currentinstitutionmembership'] = 'Current institution';
$string['selfmigration'] = 'Move account';
$string['migrateaccounttoinstitution'] = 'Move account to another institution';
$string['migrateinstitutiondescription'] = '<div class="lead">You can choose a new institution to move to.</div><p>You will be required to verify your login details and redirected back to %s. You will receive an email to confirm your account move. Please check your emails for the confirmation message to finalise your account move.</p>';
$string['migrateemailsubject'] = '%s account move confirmation';
$string['migrateemailtext'] = 'Hello %s,
You want to move your account on %s from institution "%s" to institution "%s". Please confirm this action by clicking the link
%s
This link is valid for 30 minutes.
Regards,
The %s Team';
$string['migrateemailtext_html'] = '<p>Hello %s,</p>
<p>You want to move your account on %s from institution "%s" to institution "%s". Please <a href="%s">confirm your account move</a>.</p>
<p>This link is valid for 30 minutes.</p>
<p>Regards,<br>
The %s Team</p>';
$string['migrateemailtextexistinguser'] = 'Hello %s,
You want to move your account on %s from institution "%s" to institution "%s".
You cannot do so because there is another account with the same username and email credentials.
To sort out this problem, please contact the administrator of institution "%s":
%s
Regards,
The %s Team';
$string['migrateemailtextexistinguser_html'] = '<p>Hello %s,</p>
<p>You want to move your account on %s from institution "%s" to institution "%s".</p>
<p>You cannot do so because there is another account with the same username and email credentials.</p>
<p>To sort out this problem, please <a href="%s">contact the administrator</a> of institution "%s".</p>
<p>Regards,<br>
The %s Team</p>';
$string['migrateaccountconfirm'] = 'Confirm your account move to another institution on %s.';
$string['migrateaccountconfirminfo'] = 'Your account will be moved from "%s" to "%s" and you will not be able to log in with your old credentials to your account anymore. If you do use your old login again, a new account will be created.';
$string['nomigrateoptions'] = 'There are no migration options to choose from.';
$string['invalidkeytoken'] = 'Something went wrong. Either the link in the confirmation email has expired or another confirmation email has been sent. Please check for the latest confirmation email or request a new one.';
$string['migratecancelled'] = 'Your account move has been cancelled';
$string['migratesuccess'] = 'Your account move to institution "%s" was successful. Please log in with your login details for that institution. Remember, if you log into your former institution, a new empty account will be created and you will have two accounts.';
$string['restartmigration'] = 'Please begin the account moving process again on <a href="%s">choose institution</a> page';
$string['confirmmigration'] = 'Move account';
$string['migrationcancelled'] = 'Your account move was cancelled.';
$string['cannotcompletemigrationwithuser'] = 'You need to be logged in with the credentials for your current institution, not the credentials of the institution to which you want to move. Then follow link in the email again.';
$string['migrateinstitutionpagelink'] = 'Go to the <a href="%s">Move account</a> page to change your institution and how you log into %s.';
$string['institutionmaxusersexceededrequest'] = 'This institution is full. You cannot join it at this point. The institution administrator has been informed. Please try again later. If the problem persists, <a href="%s">contact the institution administrator</a>.';
$string['postformresponse'] = 'A confirmation email has been sent to your primary email address. Please follow the link in that email to confirm the move to your new institution.';
$string['selfmigrate'] = 'Move account to another institution';
$string['institutionmembershipfullmessagetextuser'] = 'Hello %s,
%s wants to move to institution "%s" on %s but the maximum number of institution members has been reached.
Please either remove unused accounts or ask a site administrator to increase the number of accounts for this institution.
Once you have made the changes, please let %s know that they can try again by clicking the following link
%s
Regards,
The %s Team';
$string['institutionfilledreplysubject'] = 'Account move is possible again';
$string['institutionfilledreplymessage'] = 'Hello %s,
Thank you for your patience. You can move your account to institution "%s" now by following this link
%s
Regards,
The %s Team';
$string['institutionmembershipdescription'] = 'You can see your institution membership status and any actions you can perform that relate to institution membership.';
$string['institutionmembershiprequestsdescription'] = 'You have requested to join the following institutions.';
$string['institutionmembershipinvitedescription'] = 'An administrator has invited you to join the following institutions.';
$string['institutionmembershipjoindescription'] = 'You can send a request to join an institution. The "Institution ID" field is for setting the student ID for this institution.';
......@@ -1421,5 +1421,24 @@
<KEY NAME="primary" TYPE="primary" FIELDS="id" />
</KEYS>
</TABLE>
<TABLE NAME="usr_institution_migrate">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true" />
<FIELD NAME="usr" TYPE="int" LENGTH="10" NOTNULL="true" />
<FIELD NAME="old_authinstance" TYPE="int" LENGTH="10" NOTNULL="true" />
<FIELD NAME="new_authinstance" TYPE="int" LENGTH="10" NOTNULL="true" />
<FIELD NAME="new_username" TYPE="char" LENGTH="255" NOTNULL="true" />
<FIELD NAME="ctime" TYPE="datetime" NOTNULL="true" />
<FIELD NAME="key" TYPE="char" LENGTH="16" NOTNULL="false" />
<FIELD NAME="token" TYPE="char" LENGTH="6" NOTNULL="false" />
<FIELD NAME="email" TYPE="char" LENGTH="255" NOTNULL="false" />
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id" />
<KEY NAME="usrfk" TYPE="foreign" FIELDS="usr" REFTABLE="usr" REFFIELDS="id"/>
<KEY NAME="oldauthfk" TYPE="foreign" FIELDS="old_authinstance" REFTABLE="auth_instance" REFFIELDS="id"/>
<KEY NAME="newauthfk" TYPE="foreign" FIELDS="new_authinstance" REFTABLE="auth_instance" REFFIELDS="id"/>
</KEYS>
</TABLE>
</TABLES>
</XMLDB>
......@@ -1575,5 +1575,44 @@ function xmldb_core_upgrade($oldversion=0) {
create_table($table);
}
if ($oldversion < 2019122700) {
log_debug('Create an "usr_institution_migrate" table to hold pending migrations');
$table = new XMLDBTable('usr_institution_migrate');
if (!table_exists($table)) {
$table->addFieldInfo('id', XMLDB_TYPE_INTEGER, 10, null, XMLDB_NOTNULL, XMLDB_SEQUENCE);
$table->addFieldInfo('usr', XMLDB_TYPE_INTEGER, 10, null, XMLDB_NOTNULL);
$table->addFieldInfo('old_authinstance', XMLDB_TYPE_INTEGER, 10, null, XMLDB_NOTNULL);
$table->addFieldInfo('new_authinstance', XMLDB_TYPE_INTEGER, 10, null, XMLDB_NOTNULL);
$table->addFieldInfo('new_username', XMLDB_TYPE_CHAR, 255, null, XMLDB_NOTNULL);
$table->addFieldInfo('ctime', XMLDB_TYPE_DATETIME, null, null, XMLDB_NOTNULL);
$table->addFieldInfo('key', XMLDB_TYPE_CHAR, 16);
$table->addFieldInfo('token', XMLDB_TYPE_CHAR, 6);
$table->addFieldInfo('email', XMLDB_TYPE_CHAR, 255);
$table->addKeyInfo('primary', XMLDB_KEY_PRIMARY, array('id'));
$table->addKeyInfo('usrfk', XMLDB_KEY_FOREIGN, array('usr'), 'usr', array('id'));
$table->addKeyInfo('oldauthfk', XMLDB_KEY_FOREIGN, array('old_authinstance'), 'auth_instance', array('id'));
$table->addKeyInfo('newauthfk', XMLDB_KEY_FOREIGN, array('new_authinstance'), 'auth_instance', array('id'));
create_table($table);
}
}
if ($oldversion < 2019122701) {
if (!get_record('cron', 'callfunction', 'auth_clean_expired_migrations')) {
log_debug('Add cron job to clean expired self-migration entries');
$cron = new stdClass();
$cron->callfunction = 'auth_clean_expired_migrations';
$cron->minute = '0';
$cron->hour = '2,14';
$cron->day = '*';
$cron->month = '*';
$cron->dayofweek = '*';
insert_record('cron', $cron);
}
}
return $status;
}
......@@ -461,7 +461,7 @@ class Institution {
}
}
public function send_admin_institution_is_full_message(){
public function send_admin_institution_is_full_message($applicantid=null) {
// get the site admin and institution admin user records.
$admins = $this->institution_and_site_admins();
// check if there are admins - otherwise there are no site admins?!?!?
......@@ -472,12 +472,25 @@ class Institution {
$lang = get_user_language($id);
$user = new User();
$user->find_by_id($id);
$message = (object) array(
'users' => array($id),
'subject' => get_string_from_language($lang, 'institutionmembershipfullsubject'),
'message' => get_string_from_language($lang, 'institutionmembershipfullmessagetext', 'mahara',
$user->firstname, $this->displayname, get_config('sitename'), get_config('sitename')),
);
if ($applicantid) {
$applicant = new User();
$applicant->find_by_id($applicantid);
$url = get_config('wwwroot') . 'module/multirecipientnotification/sendmessage.php?id=' . $applicantid . '&cm=institutionfilledreply&i=' . $this->name;
$message = (object) array(
'users' => array($id),
'subject' => get_string_from_language($lang, 'institutionmembershipfullsubject'),
'message' => get_string_from_language($lang, 'institutionmembershipfullmessagetextuser', 'mahara',
$user->firstname, display_name($applicant), $this->displayname, get_config('sitename'), display_name($applicant), $url, get_config('sitename')),
);
}
else {
$message = (object) array(
'users' => array($id),
'subject' => get_string_from_language($lang, 'institutionmembershipfullsubject'),
'message' => get_string_from_language($lang, 'institutionmembershipfullmessagetext', 'mahara',
$user->firstname, $this->displayname, get_config('sitename'), get_config('sitename')),
);
}
activity_occurred('maharamessage', $message);
}
}
......
......@@ -1047,6 +1047,7 @@ function core_install_firstcoredata_defaults() {
'cron_event_log_expire' => array('7', '23', '*', '*', '*'),
'watchlist_process_notifications' => array('*', '*', '*', '*', '*'),
'cron_email_reset_rebounce' => array(rand(0, 59), rand(0, 23), '*', '*', '*'),
'auth_clean_expired_migrations' => array('0', '2,14', '*', '*', '*'),
);
foreach ($cronjobs as $callfunction => $times) {
$cron = new stdClass();
......
......@@ -16,7 +16,7 @@ $config = new stdClass();
// See https://wiki.mahara.org/wiki/Developer_Area/Version_Numbering_Policy
// For upgrades on stable branches, increment the version by one. On master, use the date.
$config->version = 2019120600;
$config->version = 2019122701;
$config->series = '20.04';
$config->release = '20.04dev';
$config->minupgradefrom = 2017031605;
......
......@@ -2956,12 +2956,6 @@ function mahara_standard_nav() {
'title' => get_string('groups'),
'weight' => 30,
),
'engage/institutionmembership' => array(
'path' => 'engage/institutions',
'url' => 'account/institutions.php',
'title' => get_string('institutionmembership'),
'weight' => 60,
),
);
if (can_use_skins()) {
......@@ -3141,6 +3135,12 @@ function right_nav() {
'weight' => 40,
'iconclass' => 'flag'
),
'settings/institutionmembership' => array(
'path' => 'settings/institutions',
'url' => 'account/institutions.php',
'title' => get_string('institutionmembership'),
'weight' => 60,
),
);
// enable plugins to augment the menu structure
......@@ -5017,3 +5017,32 @@ function is_valid_url($url) {
}
return true;
}
function account_institution_get_menu_tabs() {
$menu = array(
'institutions' => array(
'path' => 'settings/institutions',
'url' => 'account/institutions.php',
'title' => get_string('currentinstitutionmembership'),
'weight' => 10,
),
'migrateinstitution' => array(
'path' => 'settings/institutions',
'url' => 'account/migrateinstitution.php',
'title' => get_string('selfmigration'),
'weight' => 20,
),
);
if (defined('SECTION_PAGE')) {
$key = SECTION_PAGE;
if ($key && isset($menu[$key])) {
$menu[$key]['selected'] = true;
}
}
// Sort the menu items by weight
uasort($menu, "sort_menu_by_weight");
return $menu;
}
\ No newline at end of file
......@@ -447,3 +447,15 @@ function get_messages_by_ids_mr($usr, array $msgids) {
}
return $return;
}
function valid_canned_messages() {
// Add valid canned message functions here
return array('institutionfilledreply');
}
function institutionfilledreply($users) {
$i = param_alphanum('i', 'mahara');
$institution = get_field('institution', 'displayname', 'name', $i);
return array(get_string('institutionfilledreplysubject', 'mahara'),
get_string('institutionfilledreplymessage', 'mahara', display_name($users[0], $users[0]), $institution, get_config('wwwroot') . 'account/migrateinstitution.php', get_config('sitename')));
}
......@@ -20,6 +20,7 @@ $id = param_integer('id', null);
$oldreplytoid = param_integer('oldreplyto', null);
$replytoid = param_integer('replyto', null);
$messages = null;
$cannedmessage = param_alphanum('cm', null);
$users = array();
$user = null;
......@@ -28,7 +29,7 @@ global $THEME;
global $SESSION;
$subject = '';
$defaultmessage = '';
if (null !== $id) {
$user = get_record('usr', 'id', $id);
......@@ -43,6 +44,10 @@ if (null !== $id) {
}
$users[] = $id;
if ($cannedmessage && in_array($cannedmessage, valid_canned_messages()) && is_callable($cannedmessage)) {
// Fetch the canned message to populate the subject / message boxes
list($subject, $defaultmessage) = call_user_func_array($cannedmessage, array($users));
}
}
if (!is_null($oldreplytoid)) {
......@@ -225,6 +230,7 @@ $form = pieform(array(
'title' => $messages ? get_string('Reply', 'group') : get_string('message'),
'cols' => 80,
'rows' => 10,
'defaultvalue' => $defaultmessage,
'rules' => array('maxlength' => 65536, 'required'