diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Base/ApiMutableModelControllerBase.php b/src/opnsense/mvc/app/controllers/OPNsense/Base/ApiMutableModelControllerBase.php index 828d20d1b..2f1053c1a 100644 --- a/src/opnsense/mvc/app/controllers/OPNsense/Base/ApiMutableModelControllerBase.php +++ b/src/opnsense/mvc/app/controllers/OPNsense/Base/ApiMutableModelControllerBase.php @@ -107,6 +107,54 @@ abstract class ApiMutableModelControllerBase extends ApiControllerBase return $this->modelHandle; } + /** + * validate and save model after update or insertion. + * Use the reference node and tag to rename validation output for a specific node to a new offset, which makes + * it easier to reference specific uuids without having to use them in the frontend descriptions. + * @param $mdl The model to save + * @param $node reference node, to use as relative offset + * @param $actionHook A closure containing an action to do after validation but before saving. Must return null if successful, or an error message if not. + * @return array result / validation output + */ + protected function save($mdl, $node = null, $actionHook = null) + { + $result = array("result"=>"failed","validations" => array()); + // perform validation + $valMsgs = $mdl->performValidation(); + foreach ($valMsgs as $field => $msg) { + // replace absolute path to attribute for relative one at uuid. + if ($node != null) { + $fieldnm = str_replace($node->__reference, static::$internalModelName, $msg->getField()); + $result["validations"][$fieldnm] = $msg->getMessage(); + } else { + $result["validations"][$msg->getField()] = $msg->getMessage(); + } + } + if (count($result['validations']) != 0) { + return $result; + } + // run the action hook (default one just always returns null) + $errorMessage = $actionHook ? $actionHook($mdl) : null; + if ($errorMessage != null) { + return array("result"=>failed, "error"=>$errorMessage); + } + // serialize model to config and save when there are no validation errors + // and the action hook completed successfully + $mdl->serializeToConfig(); + Config::getInstance()->save(); + return array("result"=>"saved"); + } + + /** + * hook to be overridden if the controller is to take an action when + * setAction is called. This hook is called after a model has been + * constructed and validated but before it serialized to the configuration + * and written to disk + * @param $mdl The validated model containing the new state of the model + * @return Error message on error, or null/void on success + */ + protected function setActionHook($mdl) { } + /** * update model settings * @return array status / validation errors @@ -118,22 +166,7 @@ abstract class ApiMutableModelControllerBase extends ApiControllerBase // load model and update with provided data $mdl = $this->getModel(); $mdl->setNodes($this->request->getPost(static::$internalModelName)); - - // perform validation - $valMsgs = $mdl->performValidation(); - foreach ($valMsgs as $field => $msg) { - if (!array_key_exists("validations", $result)) { - $result["validations"] = array(); - } - $result["validations"][static::$internalModelName.".".$msg->getField()] = $msg->getMessage(); - } - - // serialize model to config and save - if ($valMsgs->count() == 0) { - $mdl->serializeToConfig(); - Config::getInstance()->save(); - $result["result"] = "saved"; - } + $result = $this->save($mdl, null, $this->setActionHook); } return $result; } diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Base/ApiMutableTableModelControllerBase.php b/src/opnsense/mvc/app/controllers/OPNsense/Base/ApiMutableTableModelControllerBase.php new file mode 100644 index 000000000..420b0a7c6 --- /dev/null +++ b/src/opnsense/mvc/app/controllers/OPNsense/Base/ApiMutableTableModelControllerBase.php @@ -0,0 +1,224 @@ +getModel()->getNodeByReference($ref); + } + private function getNodeByUUID($uuid) { + $nodes = $this->getNodes(); + return !empty($nodes) ? $nodes->$uuid : null; + } + + /** + * retrieve item or return defaults + * @param $uuid item unique id + * @return array + */ + public function getItemAction($uuid = null) + { + $mdl = $this->getModel(); + if ($uuid != null) { + $node = $this->getNodeByUUID($uuid); + if ($node != null) { + // return node + return array(static::$internalModelName => $node->getNodes()); + } + } else { + // generate new node, but don't save to disc + $node = $this->getNodes()->add(); + return array(static::$internalModelName => $node->getNodes()); + } + return array(); + } + + /** + * hook to be overridden if the controller is to take an action when + * setItemAction is called. This hook is called after a model has been + * constructed and validated but before it serialized to the configuration + * and written to disk + * @param $mdl The validated model containing the new state of the model + * @return Error message on error, or null/void on success + */ + protected function setItemActionHook($mdl) { } + + /** + * update item with given properties + * @param $uuid item unique id + * @return array + */ + public function setItemAction($uuid) + { + if ($this->request->isPost() && $this->request->hasPost(static::$internalModelName)) { + $mdl = $this->getModel(); + if ($uuid != null) { + $node = $this->getNodeByUUID($uuid); + if ($node != null) { + $node->setNodes($this->request->getPost(static::$internalModelName)); + return $this->save($mdl, $node, $this->setItemActionHook); + } + } + } + return array("result"=>"failed"); + } + + /** + * hook to be overridden if the controller is to take an action when + * addItemAction is called. This hook is called after a model has been + * constructed and validated but before it serialized to the configuration + * and written to disk + * @param $mdl The validated model containing the state of the new model + * @return Error message on error, or null/void on success + */ + protected function addItemActionHook($mdl) { } + + /** + * add new item and set with attributes from post + * @return array + */ + public function addItemAction() + { + $result = array("result"=>"failed"); + if ($this->request->isPost() && $this->request->hasPost(static::$internalModelName)) { + $mdl = $this->getModel(); + $node = $this->getNodes()->add(); + $node->setNodes($this->request->getPost(static::$internalModelName)); + return $this->save($mdl, $node, $this->addItemActionHook); + } + return $result; + } + + /** + * hook to be overridden if the controller is to take an action when + * delItemAction is called. This hook is called after a model has been + * constructed and validated but before it serialized to the configuration + * and written to disk + * @param $uuid The UUID of the item to be deleted + * @return Error message on error, or null/void on succes s + */ + protected function delItemActionHook($uuid) { } + + /** + * delete item by uuid + * @param $uuid item unique id + * @return array status + */ + public function delItemAction($uuid) + { + $result = array("result"=>"failed"); + if ($this->request->isPost()) { + $mdl = $this->getModel(); + if ($uuid != null) { + $errorMessage = delItemActionHook($uuid); + if ($errorMessage) { + $result['error'] = $errorMessage; + } else if (getNodes()->del($uuid)) { + // if item is removed, serialize to config and save + $mdl->serializeToConfig(); + Config::getInstance()->save(); + $result['result'] = 'deleted'; + } else { + $result['result'] = 'not found'; + } + } + } + return $result; + } + + /** + * hook to be overridden if the controller is to take an action when + * toggleItemAction is called. This hook is called after a model has been + * constructed and validated but before it serialized to the configuration + * and written to disk + * @param $uuid The UUID of the item to be toggled + * @param $enabled desired state enabled(1)/disabled(1), leave empty for toggle + * @return Error message on error, or null/void on succes s + */ + protected function toggleItemActionHook($uuid, $enabled) { } + + /** + * toggle item by uuid (enable/disable) + * @param $uuid item unique id + * @param $enabled desired state enabled(1)/disabled(1), leave empty for toggle + * @return array status + */ + public function toggleItemAction($uuid, $enabled = null) + { + $result = array("result" => "failed"); + if ($this->request->isPost()) { + $mdl = $this->getModel(); + if ($uuid != null) { + $node = $mdl->getNodeByUUID($uuid); + if ($node != null) { + $errorMessage = toggleItemActionHook($uuid, $enabled); + if ($errorMessage) { + $result['error'] = $errorMessage; + } else { + if ($enabled == "0" || $enabled == "1") { + $node->enabled = (string)$enabled; + } elseif ($node->enabled->__toString() == "1") { + $node->enabled = "0"; + } else { + $node->enabled = "1"; + } + $result['result'] = $node->enabled; + // if item has toggled, serialize to config and save + $mdl->serializeToConfig(); + Config::getInstance()->save(); + } + } + } + } + return $result; + } + + /** + * search items + * @return array + */ + public function searchItemsAction() + { + $this->sessionClose(); + $mdl = $this->getModel(); + $grid = new UIModelGrid($this->getNodes()); + return $grid->fetchBindRequest($this->request, static::$gridFields, static::$gridDefaultSort); + } +}