Skip to content

Resend Confirmation Code #308

Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Resend Confirmation Code
Ioannis committed Mar 3, 2025
commit 4847ed8e86f7262b9c4c1849cb45de9018be0673
53 changes: 53 additions & 0 deletions app/plugins/CoreEnroller/config/routes.php
@@ -0,0 +1,53 @@
<?php
/**
* ApiSource plugin specific routes.
*
* Portions licensed to the University Corporation for Advanced Internet
* Development, Inc. ("UCAID") under one or more contributor license agreements.
* See the NOTICE file distributed with this work for additional information
* regarding copyright ownership.
*
* UCAID licenses this file to you under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @link https://www.internet2.edu/comanage COmanage Project
* @package registry-plugins
* @since COmanage Registry v5.0.0
* @license Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
*/

use Cake\Routing\Route\DashedRoute;

// In general, we're probably trying to set up API routes if we're doing
// something within a plugin, but not necessarily. API routes are a subset
// of Cake routes, so either can be specified here.

// Core Enroller


$routes->plugin(
'CoreEnroller',
['path' => '/core-enroller/'],
function ($routes) {
$routes->setRouteClass(DashedRoute::class);

$routes->get(
'email-verifiers/resend',
[
'plugin' => 'CoreEnroller',
'controller' => 'EmailVerifiers',
'action' => 'resend',
])
->setPass(['id'])
->setPatterns(['id' => '[0-9]+']);
}
);
22 changes: 20 additions & 2 deletions app/plugins/CoreEnroller/resources/locales/en_US/core_enroller.po
@@ -98,7 +98,22 @@ msgid "information.EmailVerifiers.A"
msgstr "The following email addresses have been found in this Petition. You must verify all of them in order to proceed to the next Enrollment Step."

msgid "information.EmailVerifiers.code_sent"
msgstr "A code has been sent to {0}. Please enter it below. You may also request a new code if you haven't received it after a few minutes, or cancel verification and return to the list of available Email Addresses."
msgstr "A code has been sent to <strong>{0}</strong>. Please enter it below. You may also request a new code if you haven't received it after a few minutes, or cancel verification and return to the list of available Email Addresses."

msgid "information.EmailVerifiers.resend-pre-text"
msgstr "Didn't receive the code?"

msgid "information.EmailVerifiers.resend"
msgstr "Resend"

msgid "information.EmailVerifiers.sending"
msgstr "Sending"

msgid "information.EmailVerifiers.success"
msgstr "New Code Submitted!"

msgid "information.EmailVerifiers.abort"
msgstr "Abort"

msgid "field.AttributeCollectors.valid_through.default.after.desc"
msgstr "Days After Finalization"
@@ -257,4 +272,7 @@ msgid "result.InvitationAccepters.declined"
msgstr "Invitation Declined at {0}"

msgid "result.InvitationAccepters.none"
msgstr "No response to invitation yet"
msgstr "No response to invitation yet"

msgid "op.EmailVerifiers.verify"
msgstr "Verify Email"
@@ -29,11 +29,13 @@

namespace CoreEnroller\Controller;

use Cake\ORM\TableRegistry;
use App\Controller\StandardEnrollerController;
use App\Lib\Enum\PetitionStatusEnum;
use App\Lib\Util\StringUtilities;
use Cake\Http\Exception\BadRequestException;
use Cake\ORM\TableRegistry;
use CoreEnroller\Lib\Enum\VerificationModeEnum;
use \App\Lib\Enum\HttpStatusCodesEnum;

class EmailVerifiersController extends StandardEnrollerController {
public $paginate = [
@@ -60,9 +62,58 @@ public function beforeRender(\Cake\Event\EventInterface $event) {
$this->set('vv_bc_parent_primarykey', $this->EmailVerifiers->EnrollmentFlowSteps->getPrimaryKey());
}

if ($this->getRequest()->getQuery("op") == "verify" || $this->getRequest()->getQuery("op") == "index") {
// This will suppress the default behavior. By default, we print the submit button in the
// unorderedList.php element. But for the verify view we want to override and customize
$this->set('suppress_submit', true);
}

return parent::beforeRender($event);
}

/**
* Resend the email verification request.
*
* @param string $id Email Verifier ID
* @throws BadRequestException If the request is not AJAX
* @throws \InvalidArgumentException If required query parameters are missing
* @return void
* @since COmanage Registry v5.1.0
*/
public function resend($id)
{

$this->viewBuilder()->setClassName('Json');

if (!$this->getRequest()->is('ajax')) {
throw new BadRequestException(__('Bad Request'));
}

if (!$this->getRequest()->getQuery('petition_id') || !$this->getRequest()->getQuery('m')) {
throw new \InvalidArgumentException(__('error', 'invalid.request'));
}

// Generate a Verification request and send it
$Petitions = TableRegistry::getTableLocator()->get('Petitions');
$petition = $Petitions->get($this->getRequest()->getQuery('petition_id'));
$cfg = $this->EmailVerifiers->get($id);
$mail = StringUtilities::urlbase64decode($this->requestParam('m'));
$status = $this->EmailVerifiers->sendVerificationRequest($cfg, $petition, $mail, true);

if ($status) {
return $this->response
->withType('application/json')
->withStatus(HttpStatusCodesEnum::HTTP_OK)
->withStringBody(json_encode(['status' => 'ok']));
}


return $this->response
->withType('application/json')
->withStatus(HttpStatusCodesEnum::HTTP_INTERNAL_SERVER_ERROR)
->withStringBody(json_encode(['status' => 'failed']));
}

/**
* Dispatch an Enrollment Flow Step.
*
55 changes: 36 additions & 19 deletions app/plugins/CoreEnroller/src/Model/Table/EmailVerifiersTable.php
@@ -85,7 +85,7 @@ public function initialize(array $config): void {

$this->setPrimaryLink('enrollment_flow_step_id');
$this->setRequiresCO(true);
$this->setAllowLookupPrimaryLink(['dispatch', 'display']);
$this->setAllowLookupPrimaryLink(['dispatch', 'display', 'resend']);

// All the tabs share the same configuration in the ModelTable file
$this->setTabsConfig(
@@ -112,6 +112,14 @@ public function initialize(array $config): void {
'type' => 'select',
'model' => 'MessageTemplates',
'where' => ['context' => \App\Lib\Enum\MessageTemplateContextEnum::Verification]
],
'cosettings' => [
'type' => 'auxiliary',
'model' => 'CoSettings'
],
'types' => [
'type' => 'auxiliary',
'model' => 'Types'
]
]);

@@ -122,6 +130,7 @@ public function initialize(array $config): void {
'dispatch' => true,
'display' => true,
'edit' => ['platformAdmin', 'coAdmin'],
'resend' => true,
'view' => ['platformAdmin', 'coAdmin']
],
// Actions that operate over a table (ie: do not require an $id)
@@ -412,8 +421,10 @@ public function prepare(
public function sendVerificationRequest(
EmailVerifier $emailVerifier,
Petition $petition,
string $mail
) {
string $mail,
bool $resend = false,
): bool
{
// First check if there is already an existing Petition Verification.
// If so, use that to get the existing Verification.

@@ -427,21 +438,7 @@ public function sendVerificationRequest(
])
->first();

if(!empty($pVerification)) {
// Request a new code

$this->llog('debug', "Sending replacement verification code to $mail for Petition " . $petition->id);

$verificationId = $Verifications->requestCodeForPetition(
$petition->id,
$mail,
$emailVerifier->message_template_id,
$emailVerifier->request_validity,
$pVerification->verification_id
);

// There's nothing to update in the Petition Verification
} else {
if (empty($pVerification)) {
// Request Verification and create an associated Petition Verification

$this->llog('debug', "Sending verification code to $mail for Petition " . $petition->id);
@@ -458,8 +455,28 @@ public function sendVerificationRequest(
'petition_id' => $petition->id,
'mail' => $mail,
'verification_id' => $verificationId
]));
]));
return true;
}

if ($resend) {
// Request a new code

$this->llog('debug', "Sending replacement verification code to $mail for Petition " . $petition->id);

$verificationId = $Verifications->requestCodeForPetition(
$petition->id,
$mail,
$emailVerifier->message_template_id,
$emailVerifier->request_validity,
$pVerification->verification_id
);
// There's nothing to update in the Petition Verification

return true;
}

return false;
}

/**
130 changes: 15 additions & 115 deletions app/plugins/CoreEnroller/templates/EmailVerifiers/dispatch.inc
@@ -27,121 +27,21 @@

declare(strict_types = 1);

use CoreEnroller\Lib\Enum\VerificationModeEnum;
use App\Lib\Util\StringUtilities;

print $this->element('flash', []);

// This view is intended to work with dispatch
if($vv_action == 'dispatch') {
if($vv_op == 'index') {
// Render the list of known email addresses and their verification statuses.
// The configuration drives how many email addresses are required to complete this step.

print '<p>';

if($vv_all_done) {
print __d('core_enroller', 'information.EmailVerifiers.done');
} else {
switch($vv_config->mode) {
case VerificationModeEnum::All:
print __d('core_enroller', 'information.EmailVerifiers.A');
break;
case VerificationModeEnum::None:
print __d('core_enroller', 'information.EmailVerifiers.0');
break;
case VerificationModeEnum::One:
if($vv_minimum_met) {
print __d('core_enroller', 'information.EmailVerifiers.1.met');
} else {
print __d('core_enroller', 'information.EmailVerifiers.1.none');
}
break;
}
}

print '</p>';

print '
<table id="verifications-table" class="index-table list-mode">
<thead>
<tr>
<th>' . __d('controller', 'EmailAddresses', [1]) . '</th>
<th>' . __d('field', 'status') . '</th>
</tr>
</thead>
</tbody>
';

foreach(array_keys($vv_email_addresses) as $addr) {
$verified = isset($vv_verified_addresses[$addr]) && $vv_verified_addresses[$addr];

$button = "";

if(!$verified) {
// We're already in a form here, so we need to use a GET URL to not mess things up.
// This also means we need to manually insert the token and petition ID, which is
// a bit duplicative with templates/Standard/dispatch.php

$url = [
'plugin' => 'CoreEnroller',
'controller' => 'email_verifiers',
'action' => 'dispatch',
$vv_config->id,
'?' => [
'op' => 'verify',
'petition_id' => $vv_petition->id,
// We base64 encode the address partly to not have bare email addresses in URLs
// and partly to avoid special characters (like dots) messing up the URL
'm' => StringUtilities::urlbase64encode($addr)
]
];

if(isset($vv_token_ok) && $vv_token_ok && !empty($vv_petition->token)) {
$url['?']['token'] = $vv_petition->token;
}

$button = $this->Html->link(__d('operation', 'verify'), $url);
}

print '
<tr>
<td>' . $addr . '</td>
<td>' . __d('result', ($verified ? 'verified' : 'verified.not')) . $button . '</td>
</tr>
';
}

print '
</tbody>
</table>
';

if($vv_minimum_met) {
$this->Field->enableFormEditMode();

print $this->Form->hidden('op', ['default' => 'finish']);
}
} elseif($vv_op == 'verify') {
if(!empty($vv_verify_address)) {
// Render a form prompting for the code that was sent to the Enrollee

print __d('core_enroller', 'information.EmailVerifiers.code_sent', [$vv_verify_address]);

$this->Field->enableFormEditMode();

print $this->Form->hidden('op', ['default' => 'verify']);
print $this->Form->hidden('co_id', ['default' => $vv_cur_co->id]);
print $this->Form->hidden('m', ['default' => StringUtilities::urlbase64encode($vv_verify_address)]);

print $this->element('form/listItem', [
'arguments' => [
'fieldName' => 'code',
'fieldLabel' => "Code", //__d('field', 'mail')
'fieldOptions' => [
'required' => true
]
]]);
}
}
}
if ($vv_action !== 'dispatch') {
return;
}

if ($vv_op == 'index') {
$this->set('vv_include_cancel', false);
$this->set('vv_submit_button_label', __d('operation', 'finish'));
print $this->element('CoreEnroller.emailVerifiers/list');
} elseif ($vv_op == 'verify') {
$this->set('vv_submit_button_label', __d('core_enroller', 'op.EmailVerifiers.verify'));
$this->set('vv_include_cancel', true);
print $this->element('CoreEnroller.emailVerifiers/verify');
} else {
print __d('error', 'something.went.wrong');
}
127 changes: 127 additions & 0 deletions app/plugins/CoreEnroller/templates/element/emailVerifiers/list.php
@@ -0,0 +1,127 @@
<?php
/**
* COmanage Registry Email Verifiers Petition Fields
*
* Portions licensed to the University Corporation for Advanced Internet
* Development, Inc. ("UCAID") under one or more contributor license agreements.
* See the NOTICE file distributed with this work for additional information
* regarding copyright ownership.
*
* UCAID licenses this file to you under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @link https://www.internet2.edu/comanage COmanage Project
* @package registry
* @since COmanage Registry v5.1.0
* @license Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
*/

declare(strict_types = 1);

use CoreEnroller\Lib\Enum\VerificationModeEnum;
use App\Lib\Util\StringUtilities;

// Render the list of known email addresses and their verification statuses.
// The configuration drives how many email addresses are required to complete this step.

$title = '';
if($vv_all_done) {
$title = __d('core_enroller', 'information.EmailVerifiers.done');
} else {
$title = match ($vv_config->mode) {
VerificationModeEnum::All => __d('core_enroller', 'information.EmailVerifiers.A'),
VerificationModeEnum::None => __d('core_enroller', 'information.EmailVerifiers.0'),
VerificationModeEnum::One => $vv_minimum_met
? __d('core_enroller', 'information.EmailVerifiers.1.met')
: __d('core_enroller', 'information.EmailVerifiers.1.none'),
default => 'Unknown Verification Mode' // Optional fallback for unexpected cases
};

}

?>
<p><?= $title ?></p>
<table id="verifications-table" class="index-table list-mode">
<thead>
<tr>
<th><?= __d('controller', 'EmailAddresses', [1]) ?></th>
<th><?= __d('field', 'status') ?></th>
</tr>
</thead>
</tbody>

<?php foreach(array_keys($vv_email_addresses) as $addr): ?>
<?php
$verified = isset($vv_verified_addresses[$addr]) && $vv_verified_addresses[$addr];

$button = "";

if(!$verified) {
// We're already in a form here, so we need to use a GET URL to not mess things up.
// This also means we need to manually insert the token and petition ID, which is
// a bit duplicative with templates/Standard/dispatch.php

$url = [
'plugin' => 'CoreEnroller',
'controller' => 'email_verifiers',
'action' => 'dispatch',
$vv_config->id,
'?' => [
'op' => 'verify',
'petition_id' => $vv_petition->id,
// We base64 encode the address partly to not have bare email addresses in URLs
// and partly to avoid special characters (like dots) messing up the URL
'm' => StringUtilities::urlbase64encode($addr)
]
];

if(isset($vv_token_ok) && $vv_token_ok && !empty($vv_petition->token)) {
$url['?']['token'] = $vv_petition->token;
}

$materialIcon = '<em class="material-symbols" aria-hidden="true">check</em>';
$button = $this->Html->link(
$materialIcon . ' ' . __d('operation', 'verify'),
$url,
[
'class' => 'btn btn-sm btn-tertiary float-end',
'escape' => false,
]
);
}
?>

<tr>
<td><?= $addr ?></td>
<td>
<?php if($verified): ?>
<?= __d('result', 'verified') ?>
<?php else: ?>
<span class="mr-1 badge bg-warning unverified"><?= __d('field', 'unverified')?></span>
<?= $button; ?>
<?php endif; ?>
</td>
</tr>
</tbody>
</table>

<?php

if($vv_minimum_met || count($vv_email_addresses) === 0) {
$this->Field->enableFormEditMode();

print $this->Form->hidden('op', ['default' => 'finish']);

print $this->element('form/submit', ['label' => $vv_submit_button_label]);
}
?>
<?php endforeach; ?>
@@ -0,0 +1,185 @@
<?php
/**
* COmanage Registry Resend Confirmation Link Vue.js component
*
* Portions licensed to the University Corporation for Advanced Internet
* Development, Inc. ("UCAID") under one or more contributor license agreements.
* See the NOTICE file distributed with this work for additional information
* regarding copyright ownership.
*
* UCAID licenses this file to you under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @link https://www.internet2.edu/comanage COmanage Project
* @package registry
* @since COmanage Registry v5.1.0
* @license Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
*/


declare(strict_types = 1);

/*
* Required:
* - $htmlId
* - $containerClasses
* - $vv_config
* - $m (encoded email)
* - $petitionId
* */

// Load my helper functions
$vueHelper = $this->loadHelper('Vue');

$relativeUrl = "core-enroller/email-verifiers/resend/$vv_config->id"
. '?'
. "petition_id=$petitionId"
. "&m=$emailAddress"

?>

<script type="module">
import MiniLoader from "<?= $this->Url->script('comanage/components/common/mini-loader.js')?>?time=<?= time() ?>";

const app = Vue.createApp({
data() {
return {
error: '',
message: '', // Message to show (success or error)
loading: false, // Loading state for the fetch request
controller: null, // AbortController instance,
txt: JSON.parse('<?= json_encode($vueHelper->locales()) ?>'),
app: {
coId: <?= $vv_cur_co->id ?>,
types: <?= json_encode($types) ?>,
cosettings: <?= json_encode($cosettings) ?>
},
api: {
webroot: '<?= $this->request->getAttribute('webroot') ?>',
// co_id query parameter is required since it is the People's primary link
resendCode: `<?= $this->request->getAttribute('webroot') . $relativeUrl ?>`
},
txtPlugin: {
'resendPreText': "<?= __d('core_enroller','information.EmailVerifiers.resend-pre-text') ?>",
'resend': '<?= __d('core_enroller','information.EmailVerifiers.resend') ?>',
'abort': '<?= __d('core_enroller','information.EmailVerifiers.abort') ?>',
'sending': '<?= __d('core_enroller','information.EmailVerifiers.sending') ?>',
'success': '<?= __d('core_enroller','information.EmailVerifiers.success') ?>',
}
}
},
components: {
MiniLoader
},
methods: {
async fetchData() {
this.message = ""; // Clear any previous messages/errors
this.error = '';
this.loading = true; // Set loading state to true

// Create a new AbortController instance
this.controller = new AbortController();

const apiUrl = this.api.resendCode; // Replace this URL with your API endpoint

let request_init = {
headers: new Headers({
'X-Requested-With': 'XMLHttpRequest',
'Accept': 'application/json',
}),
method: 'GET',
signal: this.controller.signal,
}
// AJAX Request
let requestObj = new Request(apiUrl, request_init);

try {
// Perform the fetch request with signal
const response = await fetch(requestObj);

// Check if the response is successful
if (!response.ok) {
throw new Error(`Error: ${response.status} - ${response.statusText}`);
}

const data = await response.json();
this.message = this.txtPlugin.success;
} catch (error) {
// Handle fetch errors
if (error.name === "AbortError") {
// If fetch was aborted
this.error = "Request aborted.";
} else {
this.error = `Request Failed: ${error.message}`;
}
} finally {
this.loading = false; // Reset the loading state
}
},
abortRequest() {
// Abort the ongoing fetch request, if any
if (this.controller) {
this.controller.abort();
this.controller = null;
}
},
},
computed: {
getMiniLoaderClasses: function() {
return "co-loading-mini-container d-inline ms-1"
},
getAlertClasses: function() {
if (this.error) {
return "alert alert-danger alert-dismissible co-alert"
} else if (this.message) {
return "alert alert-success alert-dismissible co-alert"
} else {
return "alert alert-info alert-dismissible co-alert"
}
}
},
template: `
<!-- Display Success or Error Message -->
<div v-if="message || error" class="alert-container mb-3" id="flash-messages">
<div :class="getAlertClasses" role="alert">
<div class="alert-body d-flex align-items-center">
<span class="alert-title d-flex align-items-center">
<span v-if="error" class="material-symbols-outlined alert-icon">report_problem</span>
<span v-if="message" class="material-symbols-outlined alert-icon">check_circle</span>
</span>
<span v-if="error" class="alert-message">{{ error }}</span>
<span v-if="message" class="alert-message">{{ message }}</span>
<span class="alert-button">
<button type="button" class="btn-close nospin" data-bs-dismiss="alert" aria-label="Close"></button>
</span>
</div>
</div>
</div>
<div v-if="!loading">
<span class="me-1">{{ txtPlugin.resendPreText }}</span>
<a href="#" class="spin" @click="fetchData">{{ txtPlugin.resend }}</a>
</div>
<div v-if="loading">
<span class="me-1">{{ txtPlugin.sending }}</span>
<MiniLoader :isLoading="loading" :classes="getMiniLoaderClasses"/>
</div>
`
});


app.use(primevue.config.default, {unstyled: true});

// Mount the component and provide a global reference for this app instance.
window.<?= str_replace('-', '', $htmlId) ?> = app.mount("#<?= $htmlId ?>-container");
</script>

<div id="<?= $htmlId ?>-container" class="<?= $containerClasses ?>"></div>
@@ -0,0 +1,91 @@
<?php
/**
* COmanage Registry Verify Email
*
* Portions licensed to the University Corporation for Advanced Internet
* Development, Inc. ("UCAID") under one or more contributor license agreements.
* See the NOTICE file distributed with this work for additional information
* regarding copyright ownership.
*
* UCAID licenses this file to you under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @link https://www.internet2.edu/comanage COmanage Project
* @package registry
* @since COmanage Registry v5.1.0
* @license Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
*/

declare(strict_types = 1);

use App\Lib\Util\StringUtilities;

if(empty($vv_verify_address)) {
print __d('core_enroller', 'information.EmailVerifiers.done');
return;
}

// Render a form prompting for the code that was sent to the Enrollee

print __d('core_enroller', 'information.EmailVerifiers.code_sent', [$vv_verify_address]);

$this->Field->enableFormEditMode();

$m = StringUtilities::urlbase64encode($vv_verify_address);

print $this->Form->hidden('op', ['default' => 'verify']);
print $this->Form->hidden('co_id', ['default' => $vv_cur_co->id]);
print $this->Form->hidden('m', ['default' => $m]);

print $this->element('form/listItem', [
'arguments' => [
'fieldName' => 'code',
'fieldLabel' => "Code", //__d('field', 'mail')
'fieldOptions' => [
'required' => true
]
]]);

$resendLink = $this->Html->link(
__d('core_enroller', 'Resend'),
['controller' => 'email_verifiers', 'action' => 'resend', $vv_verify_address],
['class' => 'text-primary']
);

?>
<?php if($this->Field->isEditable()): ?>
<li class="fields-submit">
<div class="field">
<div class="field-name">
<span class="required">* <?= __d('field', 'required') ?></span>
</div>
<div class="field-info">
<?= $this->Form->submit($vv_submit_button_label) ?>
<?php if(!empty($vv_include_cancel)): ?>
<button type="button" onclick="history.back()" class="btn btn-cancel">
<?= __d('operation','cancel') ?>
</button>
<?php endif; ?>
</div>
</div>
</li>
<?php endif; ?>

<!-- Resend Link - SPA module -->
<?= $this->element('CoreEnroller.emailVerifiers/resendLinkSpa', [
'htmlId' => 'resend-link',
'petitionId' => $vv_petition->id,
'containerClasses' => 'border-top border-1 pt-2 text-center text-muted',
'emailAddress' => $m,
'vv_config' => $vv_config,
]) ?>

6 changes: 6 additions & 0 deletions app/resources/locales/en_US/error.po
@@ -214,6 +214,9 @@ msgstr "{0} must be provided"
msgid "invalid"
msgstr "Invalid value \"{0}\""

msgid "invalid.request"
msgstr "Invalid Request"

msgid "javascript.copy"
msgstr "Could not copy."

@@ -364,6 +367,9 @@ msgstr "No type defined for table \"{0}\" column \"{1}\""
msgid "schema.parse"
msgstr "Failed to parse file {0}"

msgid "something.went.wrong"
msgstr "Ooops... Something went wrong."

msgid "setup.co.comanage"
msgstr "Failed to setup COmanage CO"

3 changes: 3 additions & 0 deletions app/resources/locales/en_US/operation.po
@@ -180,6 +180,9 @@ msgstr "Sync Record to CO"
msgid "filter"
msgstr "Filter"

msgid "finish"
msgstr "Finish"

msgid "first"
msgstr "First"

2 changes: 1 addition & 1 deletion app/src/Command/TransmogrifyCommand.php
@@ -620,7 +620,7 @@ public function execute(Arguments $args, ConsoleIo $io) {
$err = 0;

// Loop over each row from the inbound table.
while($row = $stmt->fetch()) {
while($row = $stmt->fetchAssociative()) {
if(!empty($row[ $this->tables[$t]['displayField'] ])) {
$io->verbose("$t " . $row[ $this->tables[$t]['displayField'] ]);
}
87 changes: 87 additions & 0 deletions app/src/Lib/Enum/HttpStatusCodesEnum.php
@@ -0,0 +1,87 @@
<?php

/**
* COmanage Registry HTTP Statis Codes Enum
*
* Portions licensed to the University Corporation for Advanced Internet
* Development, Inc. ("UCAID") under one or more contributor license agreements.
* See the NOTICE file distributed with this work for additional information
* regarding copyright ownership.
*
* UCAID licenses this file to you under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @link https://www.internet2.edu/comanage COmanage Project
* @package registry
* @since COmanage Registry v5.1.0
* @license Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
*/

declare(strict_types = 1);

namespace App\Lib\Enum;

// XXX [REF]https://httpstatuses.com
class HttpStatusCodesEnum extends StandardEnum
{
// [Informational 1xx]
const HTTP_CONTINUE = 100;
const HTTP_SWITCHING_PROTOCOLS = 101;

// [Successful 2xx]
const HTTP_OK = 200;
const HTTP_CREATED = 201;
const HTTP_ACCEPTED = 202;
const HTTP_NONAUTHORITATIVE_INFORMATION = 203;
const HTTP_NO_CONTENT = 204;
const HTTP_RESET_CONTENT = 205;
const HTTP_PARTIAL_CONTENT = 206;

// [Redirection 3xx]
const HTTP_MULTIPLE_CHOICES = 300;
const HTTP_MOVED_PERMANENTLY = 301;
const HTTP_FOUND = 302;
const HTTP_SEE_OTHER = 303;
const HTTP_NOT_MODIFIED = 304;
const HTTP_USE_PROXY = 305;
const HTTP_UNUSED = 306;
const HTTP_TEMPORARY_REDIRECT = 307;

// [Client Error 4xx]
const errorCodesBeginAt = 400;
const HTTP_BAD_REQUEST = 400;
const HTTP_UNAUTHORIZED = 401;
const HTTP_PAYMENT_REQUIRED = 402;
const HTTP_FORBIDDEN = 403;
const HTTP_NOT_FOUND = 404;
const HTTP_METHOD_NOT_ALLOWED = 405;
const HTTP_NOT_ACCEPTABLE = 406;
const HTTP_PROXY_AUTHENTICATION_REQUIRED = 407;
const HTTP_REQUEST_TIMEOUT = 408;
const HTTP_CONFLICT = 409;
const HTTP_GONE = 410;
const HTTP_LENGTH_REQUIRED = 411;
const HTTP_PRECONDITION_FAILED = 412;
const HTTP_REQUEST_ENTITY_TOO_LARGE = 413;
const HTTP_REQUEST_URI_TOO_LONG = 414;
const HTTP_UNSUPPORTED_MEDIA_TYPE = 415;
const HTTP_REQUESTED_RANGE_NOT_SATISFIABLE = 416;
const HTTP_EXPECTATION_FAILED = 417;

// [Server Error 5xx]
const HTTP_INTERNAL_SERVER_ERROR = 500;
const HTTP_NOT_IMPLEMENTED = 501;
const HTTP_BAD_GATEWAY = 502;
const HTTP_SERVICE_UNAVAILABLE = 503;
const HTTP_GATEWAY_TIMEOUT = 504;
const HTTP_VERSION_NOT_SUPPORTED = 505;
}
8 changes: 7 additions & 1 deletion app/templates/Standard/dispatch.php
@@ -31,6 +31,10 @@
$modelsName = $this->name;
// $tablename = models
$tableName = \Cake\Utility\Inflector::tableize(\Cake\Utility\Inflector::singularize($this->name));
// Populate the AutoViewVars. These are the same we do for the EnrollmentAttributes configuration view
$this->Petition->populateAutoViewVars();
// We just populated the AutoViewVars. Add them to the current context
extract($this->viewVars);

// $vv_template_path will be set for plugins
$templatePath = $vv_template_path ?? ROOT . DS . 'templates' . DS . $modelsName;
@@ -76,7 +80,9 @@
// Set the Include file name
// Will be used by the unorderedList element below
$this->set('vv_fields_inc', 'dispatch.inc');
$this->set('vv_submit_button_label', __d('operation', 'continue'));
if (empty($vv_submit_button_label)) {
$this->set('vv_submit_button_label', __d('operation', 'continue'));
}

// By default, the form will POST to the current controller
// Note we need to open the form for view so Cake will autopopulate values