diff --git a/app/plugins/CoreEnroller/config/routes.php b/app/plugins/CoreEnroller/config/routes.php new file mode 100644 index 00000000..1cfe2b47 --- /dev/null +++ b/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]+']); + } +); \ No newline at end of file diff --git a/app/plugins/CoreEnroller/resources/locales/en_US/core_enroller.po b/app/plugins/CoreEnroller/resources/locales/en_US/core_enroller.po index 5a41e73c..461f50a1 100644 --- a/app/plugins/CoreEnroller/resources/locales/en_US/core_enroller.po +++ b/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" \ No newline at end of file +msgstr "No response to invitation yet" + +msgid "op.EmailVerifiers.verify" +msgstr "Verify Email" \ No newline at end of file diff --git a/app/plugins/CoreEnroller/src/Controller/EmailVerifiersController.php b/app/plugins/CoreEnroller/src/Controller/EmailVerifiersController.php index 23dd271d..52c4ec62 100644 --- a/app/plugins/CoreEnroller/src/Controller/EmailVerifiersController.php +++ b/app/plugins/CoreEnroller/src/Controller/EmailVerifiersController.php @@ -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. * diff --git a/app/plugins/CoreEnroller/src/Model/Table/EmailVerifiersTable.php b/app/plugins/CoreEnroller/src/Model/Table/EmailVerifiersTable.php index c515f1f0..4f64ed4e 100644 --- a/app/plugins/CoreEnroller/src/Model/Table/EmailVerifiersTable.php +++ b/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; } /** diff --git a/app/plugins/CoreEnroller/templates/EmailVerifiers/dispatch.inc b/app/plugins/CoreEnroller/templates/EmailVerifiers/dispatch.inc index 62b3b9c4..c7613261 100644 --- a/app/plugins/CoreEnroller/templates/EmailVerifiers/dispatch.inc +++ b/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 - ] - ]]); - } - } -} \ No newline at end of file +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'); +} diff --git a/app/plugins/CoreEnroller/templates/element/emailVerifiers/list.php b/app/plugins/CoreEnroller/templates/element/emailVerifiers/list.php new file mode 100644 index 00000000..b438d356 --- /dev/null +++ b/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; ?> diff --git a/app/plugins/CoreEnroller/templates/element/emailVerifiers/resendLinkSpa.php b/app/plugins/CoreEnroller/templates/element/emailVerifiers/resendLinkSpa.php new file mode 100644 index 00000000..cfed64fe --- /dev/null +++ b/app/plugins/CoreEnroller/templates/element/emailVerifiers/resendLinkSpa.php @@ -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> diff --git a/app/plugins/CoreEnroller/templates/element/emailVerifiers/verify.php b/app/plugins/CoreEnroller/templates/element/emailVerifiers/verify.php new file mode 100644 index 00000000..8dcc7a4c --- /dev/null +++ b/app/plugins/CoreEnroller/templates/element/emailVerifiers/verify.php @@ -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, +]) ?> + diff --git a/app/resources/locales/en_US/error.po b/app/resources/locales/en_US/error.po index dc8c648e..7f0408c5 100644 --- a/app/resources/locales/en_US/error.po +++ b/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" diff --git a/app/resources/locales/en_US/operation.po b/app/resources/locales/en_US/operation.po index 7a5c2ff2..908a83da 100644 --- a/app/resources/locales/en_US/operation.po +++ b/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" diff --git a/app/src/Command/TransmogrifyCommand.php b/app/src/Command/TransmogrifyCommand.php index 37871a6b..93a72c00 100644 --- a/app/src/Command/TransmogrifyCommand.php +++ b/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'] ]); } diff --git a/app/src/Lib/Enum/HttpStatusCodesEnum.php b/app/src/Lib/Enum/HttpStatusCodesEnum.php new file mode 100644 index 00000000..7a6617fc --- /dev/null +++ b/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; +} \ No newline at end of file diff --git a/app/templates/Standard/dispatch.php b/app/templates/Standard/dispatch.php index be78db43..c65fc128 100644 --- a/app/templates/Standard/dispatch.php +++ b/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