Skip to content

Commit

Permalink
NEW LinkFieldController to handle FormSchema
Browse files Browse the repository at this point in the history
  • Loading branch information
emteknetnz committed Oct 24, 2023
1 parent f0a3b25 commit 3c6d2c1
Show file tree
Hide file tree
Showing 9 changed files with 1,256 additions and 18 deletions.
1 change: 0 additions & 1 deletion _config.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,4 @@

// Avoid creating global variables
call_user_func(function () {

});
2 changes: 1 addition & 1 deletion _config/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Name: linkfield

SilverStripe\Admin\LeftAndMain:
extensions:
- SilverStripe\LinkField\Extensions\LeftAndMain
- SilverStripe\LinkField\Extensions\LeftAndMainExtension

SilverStripe\Admin\ModalController:
extensions:
Expand Down
954 changes: 953 additions & 1 deletion client/dist/js/bundle.js

Large diffs are not rendered by default.

69 changes: 68 additions & 1 deletion client/dist/styles/bundle.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion client/src/components/LinkModal/LinkModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ const buildSchemaUrl = (key, data) => {
const parsedQs = qs.parse(parsedURL.query);
parsedQs.key = key;
if (data) {
parsedQs.data = JSON.stringify(data);
// parsedQs.data = JSON.stringify(data);
parsedQs.id = data.ID;
}
return url.format({ ...parsedURL, search: qs.stringify(parsedQs)});
}
Expand Down
216 changes: 216 additions & 0 deletions src/Controllers/LinkFieldController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
<?php

namespace SilverStripe\LinkField\Controllers;

use SilverStripe\Admin\LeftAndMain;
use SilverStripe\Control\HTTPRequest;
use SilverStripe\Control\HTTPResponse;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Forms\DefaultFormFactory;
use SilverStripe\LinkField\Form\FormFactory;
use SilverStripe\Forms\Form;
use SilverStripe\LinkField\Models\Link;
use SilverStripe\LinkField\Type\Registry;
use SilverStripe\Security\SecurityToken;
use SilverStripe\ORM\ValidationResult;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\FormAction;

/**
* this will hopefully get replaced by a more generic centralised FormSchemaController
*/
class LinkFieldController extends LeftAndMain
{
private static $url_segment = 'linkfield';

// copying ElementalAreaController
private static $url_handlers = [
// API access points with structured data
'POST api/saveForm/$ID' => 'apiSaveForm',
# '$FormName/field/$FieldName' => 'formAction', // do we need this?
];

private static $allowed_actions = [
'apiSaveForm',
// 'linkForm' needs to defined for the benefit of LeftAndMain::schema() even though
// 'getLinkForm()' is the method that's ultimately called
// it's pretty unintuitive and could be made better, though it works for now
'linkForm',
];

/**
* TODO have temporarily disabled for easier dev
*/
public function init()
{
parent::init();
SecurityToken::disable();
}

/**
* Routed to be LeftAndMain::schema()
* /admin/linkfield/schema/linkForm/<linkID>
*
* Adapted from ElementalAreaController::getElementForm()
*
* @param int $elementID -- TODO not sure if this is required or not
* @return Form
*/
public function getLinkForm(): Form
{
// $formFactory = FormFactory::create(); // << extends LinkFormFactory
$formFactory = new DefaultFormFactory; // << feed it a DataObject with $context['Record'] = $obj

// this is allowed to be not present
$id = $this->getRequest()->getVar('id') ?: '0';

// this is a hackish way to get the correct DataObject type, I'm not sure what the clean method is
// maybe ditch this and just rely on the $_GET version below
// $obj = Link::get()->byID($linkID);
// if ($obj && $obj->exists()) {
// $className = $obj->ClassName;
// $obj = $className::get()->byID($linkID);
// } else {
// $obj = Link::create();
// }

// ignore all of that if passing ?key=cms etc
// obviously code is not great right now, it'll get tidied up later
$key = $this->getRequest()->getVar('key');
if (!$key) {
throw new \Exception("need to pass key in querystring");
}
/** Type|DataObject $type */
$type = (new Registry)->byKey($key);
if (!$type) {
throw new \Exception("$key not valid sorry");
}
if ((int) $id) {
$obj = $type::get()->byID($id);
} else {
$obj = $type::create();
}

$list = (new Registry)->list();
array_walk($list, function (&$item, $key) {
$item = $item->ClassName;
});
$typesToTypeKeys = array_flip($list);
$typesToTypeKeys[Link::class] = 'link';
$className = $obj->ClassName;
$linkTypeKey = $typesToTypeKeys[$className];

// add these headers to make dev life easier (should be default anyway)
$this->getRequest()->addHeader('X-Formschema-Request', 'auto,schema,state,errors');

/** @var Form $form */
$form = $formFactory->getForm($this, "LinkForm_$id", [
// 'LinkType' => $obj,
// 'LinkTypeKey' => $linkTypeKey,

'Record' => $obj,

// the following is here for (silverstripe/admin) LinkFormFactory::getRequiredContext()
// 'RequireLinkText' => false
]);
$form->addExtraClass('link-editor-editform__form');

// Adding button "manually" - need this otherwise there's no submit button in the modal
if ((int) $id) {
$title = _t('Admin.EDIT_LINK', 'Edit link');
} else {
$title = _t(__CLASS__.'.INSERT_LINK', 'Insert link');
}
// copied from LinkFormFactory::getFormActions()
$actions = FieldList::create([
FormAction::create('insert', $title)
->setSchemaData(['data' => ['buttonStyle' => 'primary']]),
]);
$form->setActions($actions);

if (!$obj->canEdit()) {
$form->makeReadonly();
}

return $form;
}

/**
* Code adapted from https://github.com/silverstripe/silverstripe-elemental/pull/1113/files#diff-942115e4b8f6030e4d72ebc2b3a0772ec65e5dfcd08fbd0e677c70d1231daf28R189
*
* Save an inline edit form for a Link
*/
public function apiSaveForm(HTTPRequest $request): HTTPResponse
{
$id = $this->param('ID'); // $this->urlParams['ID']
// Validate required input data
if (!$id) {
$this->jsonError(400);
}

$data = $request->postVars();
// $data = json_decode($request->getBody(), true);
if (empty($data)) {
$this->jsonError(400);
}

// Check security token
if (!SecurityToken::inst()->checkRequest($request)) {
$this->jsonError(400);
}

/** @var BaseElement $element */
$link = Link::get()->byID($id);
// Ensure the element can be edited by the current user
if (!$link || !$link->canEdit()) {
$this->jsonError(403);
}

// get form and populate it with POSTed data
$form = $this->getLinkForm($id);
$form->loadDataFrom($data);

// validate the Form
$validationResult = $form->validationResult();

// validate the DataObject
$element->updateFromFormData($data);
$validationResult->combineAnd($element->validate());

// handle validation failure and sent json formschema as response
if (!$validationResult->isValid()) {
// add headers to the request here so you don't need to do it in the client
// in the future I'd like these be the default response from formschema if
// the header wasn't defined
$request->addHeader('X-Formschema-Request', 'auto,schema,state,errors');
// generate schema response
$url = $this->getRequest()->getURL(); // admin/elemntal-area/api/saveForm/3
$response = $this->getSchemaResponse($url, $form, $validationResult);
// returning a 400 means that FormBuilder.js::handleSubmit() submitFn()
// that will end up in the catch() .. throw error block and the error
// will just end up in the javascript console
// $response->setStatusCode(400);
//
// return a 200 for now just to get things to work even though it's
// clearly the wrong code. Will require a PR to admin to fix this
$response->setStatusCode(200);
return $response;
}

$updated = false;
if ($element->isChanged()) {
$element->write();
// Track changes so we can return to the client
$updated = true;
}

// create and send success json response
$response = $this->getResponse();
$response->setBody(json_encode([
'status' => 'success',
'updated' => $updated,
]));
$response->addHeader('Content-Type', 'application/json');
return $response;
}
}
22 changes: 12 additions & 10 deletions src/Extensions/AjaxField.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,20 @@ class AjaxField extends Extension
{
public function updateLink(&$link, $action)
{
/** @var FormField $owner */
$owner = $this->getOwner();
$formName = $owner->getForm()->getName();
return;

if ($formName !== 'Modals/DynamicLink') {
return;
}
// /** @var FormField $owner */
// $owner = $this->getOwner();
// $formName = $owner->getForm()->getName();

$request = $owner->getForm()->getController()->getRequest();
$key = $request->getVar('key');
// if ($formName !== 'Modals/DynamicLink') {
// return;
// }

$link .= strpos($link, '?') === false ? '?' : '&';
$link .= "key={$key}";
// $request = $owner->getForm()->getController()->getRequest();
// $key = $request->getVar('key');

// $link .= strpos($link, '?') === false ? '?' : '&';
// $link .= "key={$key}";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
/**
* Register a new Form Schema in LeftAndMain.
*/
class LeftAndMain extends Extension
class LeftAndMainExtension extends Extension
{
public function init()
{
Expand All @@ -19,7 +19,8 @@ public function init()
public function updateClientConfig(&$clientConfig)
{
$clientConfig['form']['DynamicLink'] = [
'schemaUrl' => $this->getOwner()->Link('methodSchema/Modals/DynamicLink'),
// 'schemaUrl' => $this->getOwner()->Link('methodSchema/Modals/DynamicLink'),
'schemaUrl' => $this->getOwner()->Link('linkfield/schema/linkForm'),
];
}
}
2 changes: 1 addition & 1 deletion src/Form/FormFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ protected function getFormFields($controller, $name, $context)
/** @var Type $type */
$type = $context['LinkType'];

if (!$type instanceof Type) {
if (!is_a($type, Type::class)) {
throw new LogicException(sprintf('%s: LinkType must be provided and must be an instance of Type', static::class));
}

Expand Down

0 comments on commit 3c6d2c1

Please sign in to comment.