SortableGridField
SortableGridField copied to clipboard
Creating new DataObject results in database query with empty table and column names
Affected Version
{
"php": "^8.1",
"silverstripe/recipe-plugin": "~2.0.0@stable",
"silverstripe/vendor-plugin": "~2.0.0@stable",
"silverstripe/recipe-cms": "~5.0.0@stable",
"silverstripe/login-forms": "~5.0.0@stable",
"silverstripe/display-logic": "^3.0",
"undefinedoffset/sortablegridfield": "^2.2"
}
Description
A wrong query is issued to the database, having empty table name and empty column names.
UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3
If, after hitting the error, you examine the database, you'll find the many-to-many table has the new record correctly appended, but the SortOrder
column is set at 0. Manually changing it to 1 solves the problem. But anytime you need to create a new one, the problem is back.
https://github.com/UndefinedOffset/SortableGridField/assets/4563795/5e79c425-d62d-4536-a301-8e7ef5fc0c92
Stack trace:
[Emergency] Uncaught SilverStripe\ORM\Connect\DatabaseException: Couldn't run query: UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3 Incorrect table name ''
GET /admin/views/View/EditForm/field/View/item/3/ItemEditForm/field/AttachmentsCategories/item/15
Line 64 in /var/www/html/vendor/silverstripe/framework/src/ORM/Connect/DBConnector.php
Source
55 if (!empty($sql)) {
56 $formatter = new SQLFormatter();
57 $formattedSQL = $formatter->formatPlain($sql);
58 $msg = "Couldn't run query:\n\n{$formattedSQL}\n\n{$msg}";
59 }
60
61 if ($errorLevel === E_USER_ERROR) {
62 // Treating errors as exceptions better allows for responding to errors
63 // in code, such as credential checking during installation
64 throw new DatabaseException($msg, 0, null, $sql, $parameters);
65 } else {
66 user_error($msg ?? '', $errorLevel ?? 0);
67 }
68 }
69
70 /**
Trace
SilverStripe\ORM\Connect\DBConnector->databaseError(Couldn't run query: UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3 Incorrect table name '', 256, UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3)
MySQLiConnector.php:194
SilverStripe\ORM\Connect\MySQLiConnector->query(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3, 256)
Database.php:159
SilverStripe\ORM\Connect\Database->SilverStripe\ORM\Connect\{closure}(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3)
Database.php:258
SilverStripe\ORM\Connect\Database->benchmarkQuery(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3, Closure)
Database.php:160
SilverStripe\ORM\Connect\Database->query(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3, 256)
MySQLDatabase.php:381
SilverStripe\ORM\Connect\MySQLDatabase->query(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3, 256)
DB.php:341
SilverStripe\ORM\DB::query(UPDATE "" SET "SortOrder" = 1 WHERE "" = 15 AND "" = 3)
GridFieldSortableRows.php:329
UndefinedOffset\SortableGridField\Forms\GridFieldSortableRows->fixSortColumn(SilverStripe\Forms\GridField\GridField, SilverStripe\ORM\ManyManyList)
GridFieldSortableRows.php:151
UndefinedOffset\SortableGridField\Forms\GridFieldSortableRows->getManipulatedData(SilverStripe\Forms\GridField\GridField, SilverStripe\ORM\ManyManyList)
GridField.php:411
SilverStripe\Forms\GridField\GridField->getManipulatedList()
GridFieldDetailForm_ItemRequest.php:573
SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->getGridFieldItemAdjacencies()
GridFieldDetailForm_ItemRequest.php:654
SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->getAdjacentRecordID(-1)
GridFieldDetailForm_ItemRequest.php:682
SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->getPreviousRecordID()
GridFieldDetailForm_ItemRequest.php:334
SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->getRightGroupField()
GridFieldDetailForm_ItemRequest.php:420
SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->getFormActions()
VersionedGridFieldItemRequest.php:90
SilverStripe\Versioned\VersionedGridFieldItemRequest->getFormActions()
GridFieldDetailForm_ItemRequest.php:242
SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->ItemEditForm()
GridFieldDetailForm_ItemRequest.php:160
SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest->edit(SilverStripe\Control\HTTPRequest)
RequestHandler.php:323
SilverStripe\Control\RequestHandler->handleAction(SilverStripe\Control\HTTPRequest, edit)
RequestHandler.php:202
SilverStripe\Control\RequestHandler->handleRequest(SilverStripe\Control\HTTPRequest)
GridFieldDetailForm.php:149
SilverStripe\Forms\GridField\GridFieldDetailForm->handleItem(SilverStripe\Forms\GridField\GridField, SilverStripe\Control\HTTPRequest)
GridField.php:1237
SilverStripe\Forms\GridField\GridField->handleRequest(SilverStripe\Control\HTTPRequest)
RequestHandler.php:226
SilverStripe\Control\RequestHandler->handleRequest(SilverStripe\Control\HTTPRequest)
RequestHandler.php:226
SilverStripe\Control\RequestHandler->handleRequest(SilverStripe\Control\HTTPRequest)
GridFieldDetailForm.php:149
SilverStripe\Forms\GridField\GridFieldDetailForm->handleItem(SilverStripe\Forms\GridField\GridField, SilverStripe\Control\HTTPRequest)
GridField.php:1237
SilverStripe\Forms\GridField\GridField->handleRequest(SilverStripe\Control\HTTPRequest)
RequestHandler.php:226
SilverStripe\Control\RequestHandler->handleRequest(SilverStripe\Control\HTTPRequest)
RequestHandler.php:226
SilverStripe\Control\RequestHandler->handleRequest(SilverStripe\Control\HTTPRequest)
Controller.php:202
SilverStripe\Control\Controller->handleRequest(SilverStripe\Control\HTTPRequest)
LeftAndMain.php:799
SilverStripe\Admin\LeftAndMain->handleRequest(SilverStripe\Control\HTTPRequest)
AdminRootController.php:124
SilverStripe\Admin\AdminRootController->handleRequest(SilverStripe\Control\HTTPRequest)
Director.php:349
SilverStripe\Control\Director->SilverStripe\Control\{closure}(SilverStripe\Control\HTTPRequest)
VersionedHTTPMiddleware.php:41
SilverStripe\Versioned\VersionedHTTPMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
HTTPMiddlewareAware.php:62
SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
LoginSessionMiddleware.php:53
SilverStripe\SessionManager\Middleware\LoginSessionMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
HTTPMiddlewareAware.php:62
SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
ExecMetricMiddleware.php:20
SilverStripe\Control\Middleware\ExecMetricMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
HTTPMiddlewareAware.php:62
SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
ConfirmationMiddleware.php:254
SilverStripe\Control\Middleware\ConfirmationMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
HTTPMiddlewareAware.php:62
SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
ConfirmationMiddleware.php:254
SilverStripe\Control\Middleware\ConfirmationMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
HTTPMiddlewareAware.php:62
SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
PasswordExpirationMiddleware.php:84
SilverStripe\Security\PasswordExpirationMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
HTTPMiddlewareAware.php:62
SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
BasicAuthMiddleware.php:68
SilverStripe\Security\BasicAuthMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
HTTPMiddlewareAware.php:62
SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
AuthenticationMiddleware.php:61
SilverStripe\Security\AuthenticationMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
HTTPMiddlewareAware.php:62
SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
CanonicalURLMiddleware.php:245
SilverStripe\Control\Middleware\CanonicalURLMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
HTTPMiddlewareAware.php:62
SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
HTTPCacheControlMiddleware.php:41
SilverStripe\Control\Middleware\HTTPCacheControlMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
HTTPMiddlewareAware.php:62
SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
ChangeDetectionMiddleware.php:28
SilverStripe\Control\Middleware\ChangeDetectionMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
HTTPMiddlewareAware.php:62
SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
FlushMiddleware.php:31
SilverStripe\Control\Middleware\FlushMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
HTTPMiddlewareAware.php:62
SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
SessionMiddleware.php:20
SilverStripe\Control\Middleware\SessionMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
HTTPMiddlewareAware.php:62
SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
AllowedHostsMiddleware.php:60
SilverStripe\Control\Middleware\AllowedHostsMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
HTTPMiddlewareAware.php:62
SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
TrustedProxyMiddleware.php:176
SilverStripe\Control\Middleware\TrustedProxyMiddleware->process(SilverStripe\Control\HTTPRequest, Closure)
HTTPMiddlewareAware.php:62
SilverStripe\Control\Director->SilverStripe\Control\Middleware\{closure}(SilverStripe\Control\HTTPRequest)
HTTPMiddlewareAware.php:65
SilverStripe\Control\Director->callMiddleware(SilverStripe\Control\HTTPRequest, Closure)
Director.php:358
SilverStripe\Control\Director->handleRequest(SilverStripe\Control\HTTPRequest)
HTTPApplication.php:114
SilverStripe\Control\HTTPApplication::SilverStripe\Control\{closure}(SilverStripe\Control\HTTPRequest)
call_user_func(Closure, SilverStripe\Control\HTTPRequest)
HTTPApplication.php:137
SilverStripe\Control\HTTPApplication->SilverStripe\Control\{closure}(SilverStripe\Control\HTTPRequest)
HTTPMiddlewareAware.php:65
SilverStripe\Control\HTTPApplication->callMiddleware(SilverStripe\Control\HTTPRequest, Closure)
HTTPApplication.php:138
SilverStripe\Control\HTTPApplication->execute(SilverStripe\Control\HTTPRequest, Closure, )
HTTPApplication.php:113
SilverStripe\Control\HTTPApplication->handle(SilverStripe\Control\HTTPRequest)
index.php:24
Steps to Reproduce
Given the following relations, whenever you try to create a new Button
, disregarding whether you do it from the View
gridfield, or from the MetricGroup
gridfield, you get a 500 and the above error.
┌─────────────┐
│ │
│ View ├───────┐*
│ │* ├─────────┐ ┌─────────────────────┐ ┌───────────────┐
└─────────────┘ │ │ │ │ │ │
│ Button ├───────┤ AttachmentsCategory ├──────┤ Attachments │
┌──────────────┐ │ │1 *│ │* *│ │
│ │ ├─────────┘ └─────────────────────┘ └───────────────┘
│ MetricGroup │ │*
│ ├──────┘
└──────────────┘*
Please forgive me if the code is redundant.
GridFieldConfig.php
<?php
use UndefinedOffset\SortableGridField\Forms\GridFieldSortableRows;
use SilverStripe\Forms\GridField\GridFieldConfig_RelationEditor;
class SortableGridFieldConfig extends GridFieldConfig_RelationEditor {
public static function create(mixed ...$args) {
$config = GridFieldConfig_RelationEditor::create(50);
$config->addComponent(GridFieldSortableRows::create('SortOrder'));
return $config;
}
}
Attachments.php
<?php
use SilverStripe\AssetAdmin\Forms\UploadField;
use SilverStripe\Assets\File;
use Silverstripe\Forms\CheckboxField;
use Silverstripe\Forms\FieldList;
use SilverStripe\Forms\GridField\GridField;
use SilverStripe\Forms\TabSet;
use Silverstripe\Forms\TextField;
use Silverstripe\ORM\DataObject;
class Button extends DataObject {
private static $db = [
'AdminOnly' => 'Boolean(0)',
'Description' => 'Text',
'Name' => 'Text',
];
private static $has_many = [
'AttachmentsCategories' => AttachmentsCategory::class,
];
private static $belongs_many_many = [
'Views' => View::class,
'MetricGroups' => MetricGroup::class,
];
private static $searchable_fields = [
'Name',
'Description',
];
private static $summary_fields = [
'Name' => 'Name',
'Description' => 'Description',
'IsAdminOnly' => 'Admin Only',
];
private static $casting = [
'IsAdminOnly' => 'Text',
];
private function boolToText($input) {
if($input) {
return 'yes';
} else {
return 'no';
}
}
public function getIsAdminOnly() {
return $this->boolToText($this->AdminOnly);
}
public function AttachmentsCategories() {
return $this->getComponents('AttachmentsCategories')->sort('SortOrder');
}
public function getCMSfields() {
$fields = FieldList::create(TabSet::create('Root'));
$fields->addFieldsToTab('Root.Main', [
CheckboxField::create('AdminOnly', 'Visible only to Administrators'),
TextField::create('Name'),
TextField::create('Description'),
]);
$fields->addFieldsToTab('Root.Main', [
GridField::create(
'AttachmentsCategories',
'Attachments Categories',
$this->AttachmentsCategories(),
$gridConfig = SortableGridFieldConfig::create(),
),
]);
return $fields;
}
}
class AttachmentsCategory extends DataObject {
private static $db = [
'Name' => 'Text',
'SortOrder' => 'Int',
];
private static $has_one = [
'Button' => Button::class,
];
private static $many_many = [
'Attachments' => Attachment::class,
];
private static $many_many_extraFields = [
'Attachments' => [
'SortOrder' => 'Int',
],
];
public function Attachments() {
return $this->getManyManyComponents('Attachments')->sort('SortOrder');
}
public function getCMSfields() {
$fields = FieldList::create(TabSet::create('Root'));
$fields->addFieldsToTab('Root.Main', [
TextField::create('Name'),
]);
$fields->addFieldsToTab('Root.Main', [
GridField::create(
'Attachments',
'Attachments',
$this->Attachments(),
SortableGridFieldConfig::create(),
),
]);
return $fields;
}
}
class Attachment extends DataObject {
private static $db = [
'Name' => 'Text',
];
private static $has_one = [
'File' => File::class,
];
private static $belongs_many_many = [
'Category' => AttachmentsCategory::class,
];
public function getCMSfields() {
$fields = FieldList::create(TabSet::create('Root'));
$fields->addFieldsToTab('Root.Main', [
TextField::create('Name'),
UploadField::create('File'),
]);
return $fields;
}
}
MetricGroup.php
<?php
use Silverstripe\Forms\CheckboxField;
use Silverstripe\Forms\FieldList;
use SilverStripe\Forms\GridField\GridField;
use SilverStripe\Forms\GridField\GridFieldAddExistingAutocompleter;
use Silverstripe\Forms\HTMLEditor\HTMLEditorField;
use SilverStripe\Forms\TabSet;
use Silverstripe\Forms\TextField;
use Silverstripe\ORM\DataObject;
use SilverStripe\Security\Security;
class MetricGroup extends DataObject {
private static $db = [
'AdminOnly' => 'Boolean(0)',
'Description' => 'HTMLText',
'Enabled' => 'Boolean(1)',
'Subtitle' => 'Text',
'Title' => 'Text',
];
private static $defaults = [
'Enabled' => true,
];
private static $belongs_many_many = [
'View' => View::class,
];
private static $many_many = [
'Buttons' => Button::class,
'Metrics' => Metric::class,
];
private static $many_many_extraFields = [
'Metrics' => [
'SortOrder' => 'Int',
],
'Buttons' => [
'SortOrder' => 'Int',
],
];
private static $summary_fields = [
'Title' => 'Title',
'Subtitle' => 'Subtitle',
'IsEnabled' => 'Enabled',
'IsAdminOnly' => 'Admin Only',
'MetricsCount' => 'Metrics',
];
private static $casting = [
'IsEnabled' => 'Text',
'IsAdminOnly' => 'Text',
'MetricsCount' => 'Text',
];
private function boolToText($input) {
if($input) {
return 'yes';
} else {
return 'no';
}
}
public function getIsEnabled() {
return $this->boolToText($this->Enabled);
}
public function getIsAdminOnly() {
return $this->boolToText($this->AdminOnly);
}
public function getMetricsCount() {
return count($this->Metrics());
}
public function Metrics() {
$member = Security::getCurrentUser();
$metrics = $this->getManyManyComponents('Metrics')->sort('SortOrder');
if (!$member->inGroup('administrators')) {
$metrics = $metrics->filter(['AdminOnly' => false]);
}
return $metrics;
}
public function Buttons() {
$member = Security::getCurrentUser();
$buttons = $this->owner->getManyManyComponents('Buttons')->sort('SortOrder');
if (!$member->inGroup('administrators')) {
$buttons = $buttons->filter(['AdminOnly' => false]);
}
return $buttons;
}
public function getCMSfields() {
$fields = FieldList::create(TabSet::create('Root'));
$fields->addFieldsToTab('Root.Main', [
CheckboxField::create('Enabled'),
CheckboxField::create('AdminOnly', 'Visible only to Administrators'),
TextField::create('Title'),
TextField::create('Subtitle'),
HTMLEditorField::create('Description'),
GridField::create(
'Metrics',
'Metrics',
$this->Metrics(),
SortableGridFieldConfig::create()
->removeComponentsByType(SilverStripe\Forms\GridField\GridFieldAddNewButton::class),
),
]);
$fields->addFieldsToTab('Root.Buttons', [
GridField::create(
'AttachmentsCategories',
'Attachments Categories',
$this->Buttons(),
$gridConfig = SortableGridFieldConfig::create(),
),
]);
$gridConfig->getComponentByType(GridFieldAddExistingAutocompleter::class)->setResultsFormat('$Name ($Description)');
return $fields;
}
}
View.php
<?php
use SilverStripe\AssetAdmin\Forms\UploadField;
use SilverStripe\Assets\File;
use Silverstripe\Forms\CheckboxField;
use Silverstripe\Forms\FieldList;
use SilverStripe\Forms\GridField\GridField;
use SilverStripe\Forms\GridField\GridFieldAddExistingAutocompleter;
use Silverstripe\Forms\HTMLEditor\HTMLEditorField;
use SilverStripe\Forms\TabSet;
use Silverstripe\Forms\TextField;
use Silverstripe\ORM\DataObject;
use SilverStripe\Security\Member;
use SilverStripe\View\Parsers\ShortcodeParser;
use SilverStripe\Security\Security;
class View extends DataObject {
private static $db = [
'Enabled' => 'Boolean(1)',
'Name' => 'Text',
'Code' => 'Text',
'Factory' => 'Text',
'Description' => 'HTMLText',
];
private static $defaults = [
'Enabled' => true,
];
private static $has_one = [
'Picture' => File::class,
'Graphic' => File::class,
];
private static $belongs_many_many = [
'Owners' => Member::class,
];
private static $many_many = [
'Buttons' => Button::class,
'MetricGroups' => MetricGroup::class,
];
private static $many_many_extraFields = [
'MetricGroups' => [
'SortOrder' => 'Int',
],
'Buttons' => [
'SortOrder' => 'Int',
],
];
private static $summary_fields = [
'Name' => 'Name',
'Code' => 'Code',
'IsEnabled' => 'Enabled',
];
private static $casting = [
'IsEnabled' => 'Text',
];
private function boolToText($input) {
if($input) {
return 'yes';
} else {
return 'no';
}
}
public function getIsEnabled() {
return $this->boolToText($this->Enabled);
}
public function MetricGroups() {
$member = Security::getCurrentUser();
$metric_groups = $this->getManyManyComponents('MetricGroups')->sort('SortOrder');
if (!$member->inGroup('administrators')) {
$metric_groups = $metric_groups->filter(['AdminOnly' => false]);
}
return $metric_groups;
}
public function Buttons() {
$member = Security::getCurrentUser();
$buttons = $this->owner->getManyManyComponents('Buttons')->sort('SortOrder');
if (!$member->inGroup('administrators')) {
$buttons = $buttons->filter(['AdminOnly' => false]);
}
return $buttons;
}
public function getCMSfields() {
$fields = FieldList::create(TabSet::create('Root'));
$fields->addFieldsToTab('Root.Main', [
CheckboxField::create('Enabled'),
TextField::create('Name'),
TextField::create('Code'),
TextField::create('Factory'),
HTMLEditorField::create('Description'),
UploadField::create('Graphic', 'Graphic for main menu'),
UploadField::create('Picture', 'Synoptic'),
]);
$fields->addFieldsToTab('Root.MetricGroups', [
GridField::create(
'MetricGroups',
'Metric Groups',
$this->MetricGroups(),
SortableGridFieldConfig::create(),
),
]);
$fields->addFieldsToTab('Root.Buttons', [
GridField::create(
'AttachmentsCategories',
'Attachments Categories',
$this->Buttons(),
$gridConfig = SortableGridFieldConfig::create(),
),
]);
$gridConfig->getComponentByType(GridFieldAddExistingAutocompleter::class)->setResultsFormat('$Name ($Description)');
return $fields;
}
}