Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upload field multi selection auto detection breaking with inline editor #1104

Closed
mfendeksilverstripe opened this issue Sep 28, 2023 · 1 comment

Comments

@mfendeksilverstripe
Copy link
Contributor

mfendeksilverstripe commented Sep 28, 2023

CMS version: 4.13
Elemental version 4.9.0

UploadField::getIsMultiUpload() has auto-detection capability - it can guess if multiple fields are allowed or not based on the related DB record.

To reproduce this, create a has_one relation to File on a new elemental block, and add an UploadField without explicitly setting isMultiUpload. It will accept multiple files.

More specific details
public function getIsMultiUpload()
{
    if (isset($this->multiUpload)) {
        return $this->multiUpload;
    }
    // Guess from record
    $record = $this->getRecord();
    $name = $this->getName(); // <------- this breaks

    // Disabled for has_one components
    if ($record && DataObject::getSchema()->hasOneComponent(get_class($record), $name)) {
        return false;
    }
    return true;
}

Using the upload field within a content block that uses inline editing will break this feature unfortunately. This is caused by EditFormFactory::namespaceFields() which changes the field names.

protected function namespaceFields(FieldList $fields, array $context)
{
    $elementID = $context['Record']->ID;

    foreach ($fields->dataFields() as $field) {
        $namespacedName = sprintf(self::FIELD_NAMESPACE_TEMPLATE ?? '', $elementID, $field->getName());
        $field->setName($namespacedName);// <------ field name change
    }
}

This will change for example Image to PageElements_153_Image. This will break the auto-detection feature of the UploadField as it can no longer recognise the field by name, so it falls back to default.

Workaround

This is my workaround - sublcass upload field and override the getIsMultiUpload() method.

/**
 * Can't use directly @see EditFormFactory::FIELD_NAMESPACE_TEMPLATE
 */
private const NAMESPACE_PREFIX = 'PageElements_';

/**
 * Copied from @see UploadField::getIsMultiUpload()
 * Fixed the issue when this field is used in the elemental inline editor context
 * The field names are prefixed which breaks the auto-detection feature of this field
 * Issue caused by @see EditFormFactory::namespaceFields()
 * GitHub issue raised: https://github.com/silverstripe/silverstripe-elemental/issues/1104
 *
 * @return bool
 */
public function getIsMultiUpload(): bool
{
    if ($this->multiUpload !== null) {
        // Explicit settings
        return (bool) $this->multiUpload;
    }

    // Guess from record
    $record = $this->getRecord();
    $name = $this->getName();
    $name = $this->removeElementalNamespace($name);

    // Disabled for has_one components
    if ($record && DataObject::getSchema()->hasOneComponent($record::class, $name)) {
        return false;
    }

    return true;
}

private function removeElementalNamespace(string $fieldName): string
{
    if (mb_strpos($fieldName, self::NAMESPACE_PREFIX) === 0) {
        // Extract the last segment of the namespaced field which is the original field name
        $segments = explode('_', $fieldName);

        return array_pop($segments);
    }

    return $fieldName;
}

Test for this:

class UploadFieldTest extends SapphireTest
{
    /**
     * @param string $fieldName
     * @param string $expected
     * @return void
     * @throws ReflectionException
     * @dataProvider fieldNamesDataProvider
     */
    public function testRemoveElementalNameSpace(string $fieldName, string $expected): void
    {
        $field = UploadField::create('Test', 'Test');

        /** @see UploadField::removeElementalNamespace() */
        $removeElementalNamespaceMethod = new ReflectionMethod($field, 'removeElementalNamespace');
        $removeElementalNamespaceMethod->setAccessible(true);

        $result = $removeElementalNamespaceMethod->invoke($field, $fieldName);
        $this->assertEquals($expected, $result, 'We expect a specific sanitised value');
    }

    public function fieldNamesDataProvider(): array
    {
        return [
            'standard field name' => [
                'Image',
                'Image',
            ],
            'namespaced field name' => [
                'PageElements_153_Image',
                'Image',
            ],
        ];
    }
}

Acceptance criteria

  • Review the weirdness around elemental's field naming convention and try to identify alternative ways of getting a multi file upload field working.
  • If there's no elegant solution we can put directly in the CMS, document the limitation and alternative ways developers can bypass this problem.
  • Otherwise, implement a fix to allow the upload field to auto detect if it's pointed at a HasMany relation.

PRs

@emteknetnz
Copy link
Member

Linked PR has been merged, it will be tagged automatically

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants