From dbc002451a46e6ef2752b6c3b9c6acf06959e6fa Mon Sep 17 00:00:00 2001 From: Benn Oshrin Date: Thu, 29 Dec 2022 17:35:17 -0500 Subject: [PATCH] Improve handling of Matchgrid deletion (CO-1705) --- app/resources/locales/en_US/default.po | 15 +++++ app/src/Controller/MatchgridsController.php | 61 ++++++++++++++++++++- app/src/Controller/StandardController.php | 21 +++---- app/src/Lib/Match/MatchgridBuilder.php | 45 +++++++++++++++ app/src/Model/Table/MatchgridsTable.php | 56 +++++++++++++++++++ app/templates/Matchgrids/delete.php | 47 ++++++++++++++++ 6 files changed, 232 insertions(+), 13 deletions(-) create mode 100644 app/templates/Matchgrids/delete.php diff --git a/app/resources/locales/en_US/default.po b/app/resources/locales/en_US/default.po index 4a6ba8e40..f9e95f59e 100644 --- a/app/resources/locales/en_US/default.po +++ b/app/resources/locales/en_US/default.po @@ -288,6 +288,9 @@ msgstr "When this value is selected, {0} cannot be empty" msgid "match.er.input.invalid" msgstr "Invalid character found" +msgid "match.er.mg.active" +msgstr "This matchgrid is in Active status and cannot be deleted" + msgid "match.er.mg.inuse" msgstr "A Matchgrid with table name {0} already exists" @@ -571,6 +574,9 @@ msgid "match.home.welcome" msgstr "Welcome to {0}." ### Informational Messages +msgid "match.in.delete.matchgrid.info" +msgstr "The database table for this Matchgrid currently exists. You may delete it, or leave it in place. If you delete it, the table CAN NOT BE RESTORED except via database backups. Do you want to delete the physical Matchgrid table ({0})?" + msgid "match.in.matchgrid.display" msgstr "Display all records associated with this Matchgrid." @@ -638,6 +644,15 @@ msgstr "Delete" msgid "match.op.delete.confirm" msgstr "Are you sure you wish to delete this record ({0})?" +msgid "match.op.delete.matchgrid" +msgstr "Delete Matchgrid \"{0}\"" + +msgid "match.op.delete.matchgrid.leave" +msgstr "Leave the table {0} in the database" + +msgid "match.op.delete.matchgrid.remove" +msgstr "Remove the table {0} from the database" + msgid "match.op.display" msgstr "Display" diff --git a/app/src/Controller/MatchgridsController.php b/app/src/Controller/MatchgridsController.php index 550ebdf75..fc9ae378f 100644 --- a/app/src/Controller/MatchgridsController.php +++ b/app/src/Controller/MatchgridsController.php @@ -31,6 +31,7 @@ use \Cake\Log\Log; use \Cake\Event\EventInterface; + use \App\Lib\Enum\PermissionEnum; use \App\Lib\Enum\MatchgridActionEnum; @@ -77,6 +78,64 @@ public function build(string $id) { return $this->redirect(['action' => 'manage', $id]); } + /** + * Handle a delete action for a Matchgrid object. + * + * @since COmanage Match v1.1.0 + * @param Integer $id Object ID + */ + + public function delete($id) { + // Deleting a Matchgrid is a bit more complicated than other objects. + + try { + // First, we'll pull the requested object. + $obj = $this->Matchgrids->findById($id)->firstOrFail(); + + // We manually apply the status check rule here, since otherwise + // it won't get applied until we actually try to remove the Matchgrid, + // which could be _after_ we remove the table, below, which is confusing. + $err = $this->Matchgrids->ruleIsActive($obj, []); + + if(is_string($err)) { + throw new \Exception($err); + } + + // See if the Matchgrid table exists. + if(!$this->Matchgrids->tableExists($obj)) { + // Just execute the delete + parent::delete($id); + } + + // Determine what the admin wants to do with the matchgrid table. + // The first time through this will be empty, so we provide the form. + $action = $this->request->getData('remove'); + + if($action === 'leave') { + // Leave the Matchgrid table in place + parent::delete($id); + } elseif($action === 'remove') { + // Remove the Matchgrid table, then execute the standard delete + $this->Matchgrids->removeTable($obj); + parent::delete($id); + } else { + // The table exists, require the admin to indicate whether or + // not the table should also be deleted. Set some view variables. + + $this->set('vv_title', __('match.op.delete.matchgrid', [$obj->table_name])); + $this->set('vv_matchgrid', $obj->prefixed_table_name); + } + } + catch(\Exception $e) { + // findById throws Cake\Datasource\Exception\RecordNotFoundException + + $this->Flash->error($e->getMessage()); + return $this->generateRedirect(); + } + + // If we get here, we need to render the form for table deletion + } + /** * Authorization for this Controller, called by Auth component * - postcondition: $vv_permissions set with calculated permissions for this Controller @@ -286,7 +345,7 @@ public function reconcile(string $id) { $this->request->getSession()->read('Auth.User.username')); } } - + $MatchService->disconnectDatabase(); // Redirect back to list of pending requests diff --git a/app/src/Controller/StandardController.php b/app/src/Controller/StandardController.php index ea6b8743c..62ed41730 100644 --- a/app/src/Controller/StandardController.php +++ b/app/src/Controller/StandardController.php @@ -98,19 +98,16 @@ public function delete($id) { try { $obj = $this->$modelsName->findById($id)->firstOrFail(); - if($this->$modelsName->delete($obj)) { - // Use the display field to generate the flash message - - $field = $this->$modelsName->getDisplayField(); - - if(!empty($obj->$field)) { - $this->Flash->success(__('match.rs.deleted.a', [$obj->$field])); - } else { - $this->Flash->success(__('match.rs.deleted')); - } + $this->$modelsName->deleteOrFail($obj); + + // Use the display field to generate the flash message + + $field = $this->$modelsName->getDisplayField(); + + if(!empty($obj->$field)) { + $this->Flash->success(__('match.rs.deleted.a', [$obj->$field])); } else { - // It's hard to get a specific failure reason to render... - $this->Flash->error(__('match.er.delete')); + $this->Flash->success(__('match.rs.deleted')); } } catch(\Exception $e) { diff --git a/app/src/Lib/Match/MatchgridBuilder.php b/app/src/Lib/Match/MatchgridBuilder.php index 903d329f3..104ad5930 100644 --- a/app/src/Lib/Match/MatchgridBuilder.php +++ b/app/src/Lib/Match/MatchgridBuilder.php @@ -202,4 +202,49 @@ protected function configToSchema( $stmt = $dbc->executeQuery($sql); } } + + /** + * Remove the Matchgrid table. + * + * @since COmanage Match v1.1.0 + * @param EntityInterface $Matchgrid Matchgrid Object + * @return bool True on success + */ + + public function removeTable(EntityInterface $Matchgrid) { + // Connect to the database + $dbc = $this->connectDatabase(); + + // We just construct the drop statement here since it's simple and the + // table_name is constrained from having any special characters + $sql = "DROP TABLE " . $Matchgrid->prefixed_table_name; + + // $stmt just returns the query string so we don't bother examining it + $stmt = $dbc->executeQuery($sql); + + $this->disconnectDatabase(); + + return true; + } + + /** + * Determine if the Matchgrid table exists. + * + * @since COmanage Match v1.1.0 + * @param EntityInterface $Matchgrid Matchgrid Object + * @return bool True if the Matchgrid table exists, false otherwise + */ + + public function tableExists(EntityInterface $Matchgrid) { + // Connect to the database + $dbc = $this->connectDatabase(); + + $sm = $dbc->getSchemaManager(); + + $ret = $sm->tablesExist($Matchgrid->prefixed_table_name); + + $this->disconnectDatabase(); + + return $ret; + } } diff --git a/app/src/Model/Table/MatchgridsTable.php b/app/src/Model/Table/MatchgridsTable.php index 9911a4ad4..260f6eb4a 100644 --- a/app/src/Model/Table/MatchgridsTable.php +++ b/app/src/Model/Table/MatchgridsTable.php @@ -29,6 +29,7 @@ namespace App\Model\Table; +use \Cake\Datasource\EntityInterface; use \Cake\Event\EventInterface; use \Cake\ORM\Query; use \Cake\ORM\RulesChecker; @@ -151,6 +152,14 @@ public function buildRules(RulesChecker $rules): RulesChecker { 'isUnique', ['errorField' => 'table_name']); + // A Matchgrid cannot be deleted if it is in Active status. + // Similar to COs in Registry PE, this requires two steps to delete a CO + // reducing the likelihood of accidentally deleting a CO. + // Note this rule is also called manually in MatchgridsController::delete. + $rules->addDelete([$this, 'ruleIsActive'], + 'isActive', + ['errorField' => 'status']); + return $rules; } @@ -203,6 +212,39 @@ public function getMatchgridConfig($id) { ]]); } + /** + * Remove the Matchgrid table. + * + * @since COmanage Match v1.1.0 + * @param EntityInterface $Matchgrid Matchgrid entity + * @return bool True on success + */ + + public function removeTable(EntityInterface $Matchgrid) { + $Builder = new MatchgridBuilder(); + + return $Builder->removeTable($Matchgrid); + } + + /** + * Application Rule to determine if the current entity is not Active. + * + * @param Entity $entity Entity to be validated + * @param array $options Application rule options + * + * @return bool|string true if the Rule check passes, false otherwise + * @since COmanage Match v1.1.0 + */ + + public function ruleIsActive($entity, array $options): bool|string { + // We want negative logic since we want to fail if the record is Active + if($entity->status === StatusEnum::Active) { + return __('match.er.mg.active'); + } + + return true; + } + /** * Application Rule to determine if the current entity is uniquely named * (case insensitive). @@ -234,6 +276,20 @@ public function ruleIsUnique($entity, array $options): bool|string { return true; } + /** + * Determine if the Matchgrid table exists. + * + * @since COmanage Match v1.1.0 + * @param EntityInterface $Matchgrid Matchgrid entity + * @return bool True if the Matchgrid table exists, false otherwise + */ + + public function tableExists(EntityInterface $Matchgrid) { + $Builder = new MatchgridBuilder(); + + return $Builder->tableExists($Matchgrid); + } + /** * Set validation rules. * diff --git a/app/templates/Matchgrids/delete.php b/app/templates/Matchgrids/delete.php new file mode 100644 index 000000000..5a5990a7d --- /dev/null +++ b/app/templates/Matchgrids/delete.php @@ -0,0 +1,47 @@ + +
+
+

+
+
+ +

+ +Form->create(); //$vv_delete_matchgrid_form); + print $this->Form->radio('remove', + [ + ['value' => 'leave', 'text' => __('match.op.delete.matchgrid.leave', [$vv_matchgrid])], + ['value' => 'remove', 'text' => __('match.op.delete.matchgrid.remove', [$vv_matchgrid])] + ]); + print $this->Form->submit(__('match.op.confirm')); + print $this->Form->end();