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

ENH Tidy up permissions #141

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions _config.php
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
<?php

use SilverStripe\Core\Manifest\ModuleLoader;
use SilverStripe\Forms\HTMLEditor\TinyMCEConfig;
use SilverStripe\Admin\CMSMenu;
use SilverStripe\LinkField\Controllers\LinkFieldController;

// Avoid creating global variables
call_user_func(function () {
});
CMSMenu::remove_menu_class(LinkFieldController::class);
6 changes: 6 additions & 0 deletions babel.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"presets": [
"@babel/preset-env",
"@babel/preset-react"
]
}
GuySartorelli marked this conversation as resolved.
Show resolved Hide resolved
2 changes: 1 addition & 1 deletion client/dist/js/bundle.js

Large diffs are not rendered by default.

2 changes: 1 addition & 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/lang/src/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@
"LinkField.LINK_DRAFT_TITLE": "Link has draft changes",
"LinkField.LINK_DRAFT_LABEL": "Draft",
"LinkField.LINK_MODIFIED_TITLE": "Link has unpublished changes",
"LinkField.LINK_MODIFIED_LABEL": "Modified"
"LinkField.LINK_MODIFIED_LABEL": "Modified",
"LinkField.CANNOT_CREATE_LINK": "Cannot create link"
}
9 changes: 6 additions & 3 deletions client/src/components/LinkField/LinkField.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ const section = 'SilverStripe\\LinkField\\Controllers\\LinkFieldController';
* types - types of the Link passed from LinkField entwine
* actions - object of redux actions
* isMulti - whether this field handles multiple links or not
* canCreate - whether this field can create links or not
*/
const LinkField = ({ value = null, onChange, types = [], actions, isMulti = false }) => {
const LinkField = ({ value = null, onChange, types = [], actions, isMulti = false, canCreate }) => {
const [data, setData] = useState({});
const [editingID, setEditingID] = useState(0);

Expand Down Expand Up @@ -145,6 +146,7 @@ const LinkField = ({ value = null, onChange, types = [], actions, isMulti = fals
typeTitle={type.title || ''}
onClear={onClear}
onClick={() => { setEditingID(linkID); }}
canDelete={data[linkID]?.canDelete ? true : false}
/>);
}
return links;
Expand All @@ -154,7 +156,7 @@ const LinkField = ({ value = null, onChange, types = [], actions, isMulti = fals
const renderModal = Boolean(editingID);

return <>
{ renderPicker && <LinkPicker onModalSuccess={onModalSuccess} onModalClosed={onModalClosed} types={types} /> }
{ renderPicker && <LinkPicker canCreate={canCreate} onModalSuccess={onModalSuccess} onModalClosed={onModalClosed} types={types} /> }
<div> { renderLinks() } </div>
{ renderModal && <LinkModalContainer
types={types}
Expand All @@ -171,9 +173,10 @@ const LinkField = ({ value = null, onChange, types = [], actions, isMulti = fals
LinkField.propTypes = {
value: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.number), PropTypes.number]),
onChange: PropTypes.func.isRequired,
types: PropTypes.objectOf(LinkType).isRequired,
types: PropTypes.array.isRequired,
actions: PropTypes.object.isRequired,
isMulti: PropTypes.bool,
canCreate: PropTypes.bool.isRequired,
};

// redux actions loaded into props - used to get toast notifications
Expand Down
16 changes: 14 additions & 2 deletions client/src/components/LinkPicker/LinkPicker.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/* eslint-disable */
import i18n from 'i18n';
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
Expand All @@ -9,7 +10,7 @@ import LinkModalContainer from 'containers/LinkModalContainer';
/**
* Component which allows users to choose a type of link to create, and opens a modal form for it.
*/
const LinkPicker = ({ types, onModalSuccess, onModalClosed }) => {
const LinkPicker = ({ types, onModalSuccess, onModalClosed, canCreate }) => {
const [typeKey, setTypeKey] = useState('');

/**
Expand Down Expand Up @@ -41,6 +42,16 @@ const LinkPicker = ({ types, onModalSuccess, onModalClosed }) => {
const className = classnames('link-picker', 'form-control');
const typeArray = Object.values(types);

if (!canCreate) {
return (
<div className={className}>
<div className="link-picker__cannot-create">
{i18n._t('LinkField.CANNOT_CREATE_LINK', 'Cannot create link')}
</div>
</div>
);
}

return (
<div className={className}>
<LinkPickerMenu types={typeArray} onSelect={handleSelect} />
Expand All @@ -57,9 +68,10 @@ const LinkPicker = ({ types, onModalSuccess, onModalClosed }) => {
};

LinkPicker.propTypes = {
types: PropTypes.objectOf(LinkType).isRequired,
types: PropTypes.array.isRequired,
onModalSuccess: PropTypes.func.isRequired,
onModalClosed: PropTypes.func,
canCreate: PropTypes.bool.isRequired
};

export {LinkPicker as Component};
Expand Down
6 changes: 6 additions & 0 deletions client/src/components/LinkPicker/LinkPicker.scss
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@
}
}

.link-picker__cannot-create {
cursor: default;
flex-grow: 1;
padding: 16px 13px;
}

.link-picker__menu {
flex-grow: 1;
}
Expand Down
19 changes: 9 additions & 10 deletions client/src/components/LinkPicker/LinkPickerMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,25 @@ import LinkType from 'types/LinkType';
const LinkPickerMenu = ({ types, onSelect }) => {
const [isOpen, setIsOpen] = useState(false);
const toggle = () => setIsOpen(prevState => !prevState);

return (
<Dropdown
isOpen={isOpen}
toggle={toggle}
className="link-picker__menu"
>
<DropdownToggle className="link-picker__menu-toggle font-icon-plus-1" caret>{i18n._t('LinkField.ADD_LINK', 'Add Link')}</DropdownToggle>
return <Dropdown
isOpen={isOpen}
toggle={toggle}
className="link-picker__menu"
>
<DropdownToggle className="link-picker__menu-toggle font-icon-plus-1" caret>
{i18n._t('LinkField.ADD_LINK', 'Add Link')}
</DropdownToggle>
<DropdownMenu>
{types.map(({key, title}) =>
<DropdownItem key={key} onClick={() => onSelect(key)}>{title}</DropdownItem>
)}
</DropdownMenu>
</Dropdown>
);
};

LinkPickerMenu.propTypes = {
types: PropTypes.arrayOf(LinkType).isRequired,
onSelect: PropTypes.func.isRequired
onSelect: PropTypes.func.isRequired,
};

export default LinkPickerMenu;
16 changes: 14 additions & 2 deletions client/src/components/LinkPicker/LinkPickerTitle.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,16 @@ const getVersionedBadge = (versionState) => {
return <span className={className} title={title}>{label}</span>;
};

const LinkPickerTitle = ({ id, title, description, versionState, typeTitle, onClear, onClick }) => {
const LinkPickerTitle = ({
id,
title,
description,
versionState,
typeTitle,
onClear,
onClick,
canDelete
}) => {
const classes = {
'link-picker__link': true,
'form-control': true,
Expand All @@ -54,7 +63,9 @@ const LinkPickerTitle = ({ id, title, description, versionState, typeTitle, onCl
</small>
</div>
</Button>
<Button className="link-picker__clear" color="link" onClick={stopPropagation(() => onClear(id))}>{i18n._t('LinkField.CLEAR', 'Clear')}</Button>
{canDelete &&
<Button className="link-picker__clear" color="link" onClick={stopPropagation(() => onClear(id))}>{i18n._t('LinkField.CLEAR', 'Clear')}</Button>
}
</div>
};

Expand All @@ -66,6 +77,7 @@ LinkPickerTitle.propTypes = {
typeTitle: PropTypes.string.isRequired,
onClear: PropTypes.func.isRequired,
onClick: PropTypes.func.isRequired,
canDelete: PropTypes.bool.isRequired,
};

export default LinkPickerTitle;
31 changes: 31 additions & 0 deletions client/src/components/LinkPicker/tests/LinkPicker-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/* global jest, test */
import React from 'react';
import { render } from '@testing-library/react';
import LinkPicker from '../LinkPicker';

function makeProps(obj = {}) {
return {
types: [{ key: 'phone', title: 'Phone' }],
onModalSuccess: () => {},
onModalClosed: () => {},
...obj
};
}

test('LinkPickerMenu render() should display toggle if can create', () => {
const { container } = render(<LinkPicker {...makeProps({
canCreate: true
})}
/>);
expect(container.querySelectorAll('.link-picker__menu-toggle')).toHaveLength(1);
expect(container.querySelectorAll('.link-picker__cannot-create')).toHaveLength(0);
});

test('LinkPickerMenu render() should display cannot create message if cannot create', () => {
const { container } = render(<LinkPicker {...makeProps({
canCreate: false
})}
/>);
expect(container.querySelectorAll('.link-picker__menu-toggle')).toHaveLength(0);
expect(container.querySelectorAll('.link-picker__cannot-create')).toHaveLength(1);
});
34 changes: 34 additions & 0 deletions client/src/components/LinkPicker/tests/LinkPickerTitle-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/* global jest, test */

import React from 'react';
import { render } from '@testing-library/react';
import LinkPickerTitle from '../LinkPickerTitle';

function makeProps(obj = {}) {
return {
id: 1,
title: 'My title',
description: 'My description',
versionState: 'draft',
typeTitle: 'Phone',
onClear: () => {},
onClick: () => {},
...obj
};
}

test('LinkPickerTitle render() should display clear button if can delete', () => {
const { container } = render(<LinkPickerTitle {...makeProps({
canDelete: true
})}
/>);
expect(container.querySelectorAll('.link-picker__clear')).toHaveLength(1);
});

test('LinkPickerTitle render() should not display clear button if cannot delete', () => {
const { container } = render(<LinkPickerTitle {...makeProps({
canDelete: false
})}
/>);
expect(container.querySelectorAll('.link-picker__clear')).toHaveLength(0);
});
3 changes: 1 addition & 2 deletions client/src/containers/LinkModalContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import React from 'react';
import { loadComponent } from 'lib/Injector';
import PropTypes from 'prop-types';
import LinkType from 'types/LinkType';

/**
* Contains the LinkModal and determines which modal component to render based on the link type.
Expand All @@ -29,7 +28,7 @@ const LinkModalContainer = ({ types, typeKey, linkID = 0, isOpen, onSuccess, onC
}

LinkModalContainer.propTypes = {
types: PropTypes.objectOf(LinkType).isRequired,
types: PropTypes.array.isRequired,
typeKey: PropTypes.string.isRequired,
linkID: PropTypes.number,
isOpen: PropTypes.bool.isRequired,
Expand Down
1 change: 1 addition & 0 deletions client/src/entwine/LinkField.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ jQuery.entwine('ss', ($) => {
onChange: this.handleChange.bind(this),
isMulti: this.data('is-multi') ?? false,
types: this.data('types') ?? [],
canCreate: this.getInputField().data('can-create') ?? false,
};
},

Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"lint-sass": "sass-lint client/src"
},
"jest": {
"testEnvironment": "jsdom",
"roots": [
"client/src"
],
Expand All @@ -51,6 +52,7 @@
"@babel/runtime": "^7.20.0",
"@silverstripe/eslint-config": "^1.0.0",
"@silverstripe/webpack-config": "^2.0.0",
"@testing-library/react": "^14.0.0",
"babel-jest": "^29.2.2",
"jest-cli": "^29.2.2",
"jest-environment-jsdom": "^29.3.1",
Expand Down
1 change: 1 addition & 0 deletions src/Controllers/LinkFieldController.php
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ private function getLinkData(Link $link): array
$this->jsonError(403, _t('LinkField.UNAUTHORIZED', 'Unauthorized'));
}
$data = $link->jsonSerialize();
$data['canDelete'] = $link->canDelete();
$data['description'] = $link->getDescription();
$data['versionState'] = $link->getVersionedState();
return $data;
Expand Down
10 changes: 10 additions & 0 deletions src/Form/LinkField.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@
use SilverStripe\ORM\DataObjectInterface;
use SilverStripe\LinkField\Models\Link;
use SilverStripe\LinkField\Form\Traits\AllowedLinkClassesTrait;
use SilverStripe\LinkField\Form\Traits\LinkFieldGetOwnerTrait;

/**
* Allows CMS users to edit a Link object.
*/
class LinkField extends FormField
{
use AllowedLinkClassesTrait;
use LinkFieldGetOwnerTrait;

protected $schemaComponent = 'LinkField';

Expand Down Expand Up @@ -60,10 +62,18 @@ public function saveInto(DataObjectInterface $record)
return $this;
}

public function getSchemaStateDefaults()
{
$data = parent::getSchemaStateDefaults();
$data['canCreate'] = $this->getOwner()->canEdit();
return $data;
}

protected function getDefaultAttributes(): array
{
$attributes = parent::getDefaultAttributes();
$attributes['data-value'] = $this->Value();
$attributes['data-can-create'] = $this->getOwner()->canEdit();
return $attributes;
}

Expand Down
5 changes: 5 additions & 0 deletions src/Form/MultiLinkField.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,16 @@
use SilverStripe\ORM\RelationList;
use SilverStripe\ORM\SS_List;
use SilverStripe\ORM\UnsavedRelationList;
use SilverStripe\LinkField\Form\Traits\LinkFieldGetOwnerTrait;
use SilverStripe\LinkField\Models\Link;

/**
* Allows CMS users to edit a Link object.
*/
class MultiLinkField extends FormField
{
use AllowedLinkClassesTrait;
use LinkFieldGetOwnerTrait;

protected $schemaComponent = 'LinkField';

Expand Down Expand Up @@ -72,13 +75,15 @@ public function getSchemaStateDefaults()
{
$data = parent::getSchemaStateDefaults();
$data['value'] = $this->getValueArray();
$data['canCreate'] = $this->getOwner()->canEdit();
return $data;
}

protected function getDefaultAttributes(): array
{
$attributes = parent::getDefaultAttributes();
$attributes['data-value'] = $this->getValueArray();
$attributes['data-can-create'] = $this->getOwner()->canEdit();
return $attributes;
}

Expand Down
3 changes: 3 additions & 0 deletions src/Form/Traits/AllowedLinkClassesTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ public function getTypesProps(): string
$typeDefinitions = $this->genarateAllowedTypes();
foreach ($typeDefinitions as $key => $class) {
$type = Injector::inst()->get($class);
if (!$type->canCreate()) {
continue;
}
$typesList[$key] = [
'key' => $key,
'title' => $type->i18n_singular_name(),
Expand Down
Loading