SilverStripe Model-Level Permissions: canCreate() doesn't work - silverstripe

I have dataobject :
class Documents extends DataObject implements PermissionProvider {
private static $db = array(
'DocType' => 'Text',
'ApprovalDate' => 'Date',
'PublicationDate' => 'Date',
'DocNumber' => 'Text',
'DocTitle' => 'Text',
'KeyWords' => 'Text'
);
private static $has_one = array(
'Member' => 'Member'
);
...
static $api_access = true;
public function canEdit($member = false) {
return (Member::currentUserID() == $this->MemberID) || parent::canEdit($member);
}
public function canDelete($member = false) {
return (Member::currentUserID() == $this->MemberID) || parent::canDelete($member);
}
public function canView($member = false) {
return Permission::check('DOCUMENTS_VIEW');
}
public function canCreate($member = false) {
return Permission::check('DOCUMENTS_CREATE');
}
function providePermissions() {
return array(
'DOCUMENTS_VIEW' => 'View Documents ',
'DOCUMENTS_EDIT' => 'Edit Documents ',
'DOCUMENTS_DELETE' => 'Delete Documents ',
'DOCUMENTS_CREATE' => 'Create Documents '
);
}
I have created group "Documents Developer" and granted it rightes DOCUMENTS_VIEW, EDIT, DELETE, CREATE. I want all users of this group could only view all documents and create new documents, and only owner (user with ID == MemberID) could edit and delete his documents.
It works OK when user tryes to edit or delete his documents, or view other documents. But when he tryes to create new document (push "create" button in CMS), a pop-up window "Forbidden" appeares: (You can see CMS Window here)
Give me please any ideas.

I made some improvement in canEdit() function:
public function canEdit($member = false) {
if ($this->MemberID) return (Member::currentUserID() == $this->MemberID) || parent::canEdit($member);
return Permission::check('DOCUMENTS_EDIT') || parent::canEdit($member) ;
}
Now users can create Document. It seems to me, that when user tryes to create new document, $this->MemberID is undefined.

Related

Custom FIlter that matches two fields

I simply want an API endpoint that matches two fields, registrationId and hash.
I cannot find any examples of this online, I can only single matches, regex matching, OR clauses etc. Nowhere is there an example of just just querying the database to return a row that matches two columns.
I have a table called registrationhashes. In the entity I have added this line:
#[ApiFilter(RegistrationSearchFilter::class, properties: ['registrationId', 'hash'])]
And then I have gotten so far with writing a custom filter:
final class RegistrationSearchFilter extends AbstractFilter
{
protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, Operation $operation = null, array $context = []): void
{
if ($property !== 'search') {
return;
}
$alias = $queryBuilder->getRootAliases()[0];
$queryBuilder->andWhere(sprintf('%s.registrationId = :search AND %s.hash = :hash', $alias, $alias));
}
public function getDescription(string $resourceClass): array
{
if (!$this->properties) {
return [];
}
$description = [];
foreach ($this->properties as $property => $strategy) {
$description["regexp_$property"] = [
'property' => $property,
'type' => Type::BUILTIN_TYPE_STRING,
'required' => false,
'description' => 'Filter using a regex. This will appear in the OpenApi documentation!',
'openapi' => [
'example' => 'Custom example that will be in the documentation and be the default value of the sandbox',
'allowReserved' => false,// if true, query parameters will be not percent-encoded
'allowEmptyValue' => true,
'explode' => false, // to be true, the type must be Type::BUILTIN_TYPE_ARRAY, ?product=blue,green will be ?product=blue&product=green
],
];
}
return $description;
}
}
I have just put the description function in there copied from somewhere else for now.
But as you can see from the WHERE clause I ned to match registrationId AND hash, but I only have one value to match with.
How can I achieve this?
Look at the search filter docs, you will see an example of combining two search filters. I believe this accomplishes your task without the need for a custom filter.
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
#[ApiResource]
#[ApiFilter(SearchFilter::class, properties: ['registrationId' => 'exact', 'hash' => 'exact'])]
class RegistrationHash
{
// ...
}
The endpoint would then be something like:
http://localhost:8000/api/registrationhashes?registrationId=10&hash=3C76B43F

How to execute a Drupal-view in a custom module and print the output (as html-markup)?

I want to print the output from a simple view in my custom module. But is doesnt work. I have tried many options from forums and stackoverflow. All of them print "array" instead of html-markup.
My controller:
class DefaultController extends ControllerBase {
public function myfunc1() {
$view = Views::getView('myfirstview');
$view->setDisplay('page_1');
$view->preExecute();
$view->execute();
// $myresults = $view->preview(); = array
// $myresults = $view->render(); = array
$myresults = $view->result; // = array
return array(
'#title' => 'Hello World!',
'#markup' => $myresults,
);
}
}
How can I print the result/output of a view programmatically?
I dont want to make it without "embed view" , because I want to set some exposed filters later.
get the view
$corresponding_view = Views::getView('myfirstview');
set exposed filters
$arguments = your arguments (exposed filter value);
$corresponding_view->setArguments($arguments);
embed your view
return [
'#type' => 'view',
'#name' => 'myfirstview',
'#view' => $corresponding_view,
'#display_id' => 'page_1',
'#embed' => TRUE,
];

Drupal Views Filter Handler

I wrote a custom views handler, that mark message private to 0 or 1 or 2; any is value of hers label [MARK_READ,ARK_NEW,...] :
function mydevel_views_data() {
$data['mydevel']['table']['group'] = t('mydevel');
$data['mydevel']['table']['join'] = array(
// Exist in all views.
'#global' => array(),
);
$data['mydevel']['mydevel_isnewmessage'] = array(
'title' => t('is new message field'),
'help' => t('is new message field.'),
'field' => array(
'handler' => 'mydevel_handler_field_isnewmessage',
'click sortable' => TRUE,
),
'filter' => array(
'handler' => 'mydevel_handler_filter_isnewmessage',
),
);
and wrote a filed handler that work properly; message_mark function is wrote on mydevel module file and work currectly; if the message is new that filed label row by "now":
class mydevel_handler_field_isnewmessage extends views_handler_field_numeric {
var $field_alias = 'mydevel_field_isnewmessage';
function query() {
}
function option_definition() {
$options = parent::option_definition();
//dsm($this);
return $options;
}
function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
}
function get_value($values, $field = NULL) {
return intval(message_mark($values->mid, $values->message_timestamp));
}
function render($values) {
$value = $this->get_value($values);
$value_theme = theme('mark', array('type' => $value));
return $value_theme;
}
}
Now, i want to write a views filter handler that filter on that filed on numeric mode [0 or 1 or 2] or on check list mode [all, read, new, updated]. but I don't want to overwite query function on filter handler and want to use from value that returned by this common handler filed (mydevel_handler_filter_isnewmessage) that added to views filed. can wrote this idea by extend the standard views handler? what i do my dears? I wrote this but not work: this is return error
class mydevel_handler_filter_isnewmessage extends views_handler_filter_numeric {
var $always_multiple = TRUE;
function option_definition() {
$options = parent::option_definition();
return $options;
}
function operators() {
$operators = parent::operators();
return $operators;
}
function query() {
}
}
tank you a lot.

display titles from many_many relation in GridField - Silverstripe

How can I display the titles from a many_many relation in a GridField Summary?
I tried it with RelationName.Title but the result was only an empty field
there should be a couple solutions:
defining $summary_fields on the DataObject that is linked:
private static $summary_fields = array(
'YourFieldName',
'AnotherField'
);
or with the GridFieldConfig on the Page/DataObject that defines the relation:
$config->getComponentByType('GridFieldDataColumns')->setDisplayFields(array(
'FieldName' => 'GridFieldColumnName',
'AnotherFieldName' => 'AnotherGridFieldColumnName',
));
$config being your GridFieldConfig instance used by GridField.
EDIT
for more advanced formatting/control over the data included in the GridField, you can use setFieldFormatting:
$config->getComponentByType('GridFieldDataColumns')->setDisplayFields(array(
'TeamLink' => 'Edit teams'
));
$config->getComponentByType('GridFieldDataColumns')->setFieldFormatting(array(
'TeamLink' => function($value, $item)
{
// $item here would be a TeamMember instance
// since the GridField displays TeamMembers
$links = 'No teams';
$teamModelAdminClass = 'TeamModelAdmin'; //change to your classname
$teams = $item->Teams(); // get the teams
if ( $teams->count() > 0 )
{
$links = '';
$teamClass = $teams->dataClass;
$teamAdminURL = Config::inst()->get($teamModelAdminClass, 'url_segment');
$teamEditAdminURL = 'admin/'.$teamAdminURL.'/'.$teamClass.'/EditForm/field/'.$teamClass.'/item/';
foreach($teams as $team)
{
$links .= 'Edit '.$team->Title.'<br/>';
}
}
return $links;
}
));
Here setFieldFormatting will output edit links to all teams that a TeamMember is part of in a TeamLink column defined by setDisplayFields (might not be the best example, but hope you get the idea, although not tested).
colymba's answer already said most of it, but in addition you can also specify a method in $summary_fields. This allows you to display image thumbnails in a GridField or as you need it, piece together your own string from the titles of a many_many relation.
class TeamMember extends DataObject {
private static $db = array(
'Title' => 'Text',
'Birthday' => 'Date',
);
private static $has_one = array(
'Photo' => 'Image'
);
private static $has_many = array(
'Teams' => 'Team'
);
private static $summary_fields = array(
'PhotoThumbnail',
'Title',
'Birthday',
'TeamsAsString',
);
public function getPhotoThumbnail() {
// display a thumbnail of the Image from the has_one relation
return $this->Photo() ? $this->Photo()->CroppedImage(50,50) : '';
}
public function getTeamsAsString() {
if (!$this->Teams()) {
return 'not in any Team';
}
return implode(', ', $this->Teams()->Column('Title'));
// or if one field is not enough for you, you can use a foreach loop:
// $teamsArray= array();
// foreach ($this->Teams() as $team) {
// $teamsArray[] = "{$team->ID} {$team->Title}";
// }
// return implode(', ', $teamsArray);
}
}
alternative you can, as colymba pointed out, also use setDisplayFields to use different fields on different grids

Sonata admin form collection

How to limit the number of embedded form with the type "sonata_type_collection" ?
$formMapper->add('phones', 'sonata_type_collection',
array(
'required' => true,
'by_reference' => false,
'label' => 'Phones',
),
array(
'edit' => 'inline',
'inline' => 'table'
)
I would like limit to last five phones, I found only this solution for now, limit the display in the template twig "edit_orm_one_to_many", but i don't like that.
I found a solution by rewriting the edit action in the controller,
such in the documentation sonataAdminBundle I created my admin controller class:
class ContactAdminController extends Controller
{
public function editAction($id = null)
{
// the key used to lookup the template
$templateKey = 'edit';
$em = $this->getDoctrine()->getEntityManager();
$id = $this->get('request')->get($this->admin->getIdParameter());
// $object = $this->admin->getObject($id);
// My custom method to reduce the queries number
$object = $em->getRepository('GestionBundle:Contact')->findOneAllJoin($id);
if (!$object)
{
throw new NotFoundHttpException(sprintf('unable to find the object with id : %s', $id));
}
if (false === $this->admin->isGranted('EDIT', $object))
{
throw new AccessDeniedException();
}
$this->admin->setSubject($object);
/** #var $form \Symfony\Component\Form\Form */
$form = $this->admin->getForm();
$form->setData($object);
// Trick is here ###############################################
// Method to find the X last phones for this Contact (x = limit)
// And set the data in form
$phones = $em->getRepository('GestionBundle:Phone')->findLastByContact($object, 5);
$form['phones']->setData($phones);
// #############################################################
if ($this->get('request')->getMethod() == 'POST')
{
$form->bindRequest($this->get('request'));
$isFormValid = $form->isValid();
// persist if the form was valid and if in preview mode the preview was approved
if ($isFormValid && (!$this->isInPreviewMode() || $this->isPreviewApproved()))
{
$this->admin->update($object);
$this->get('session')->setFlash('sonata_flash_success', 'flash_edit_success');
if ($this->isXmlHttpRequest())
{
return $this->renderJson(array(
'result' => 'ok',
'objectId' => $this->admin->getNormalizedIdentifier($object)
));
}
// redirect to edit mode
return $this->redirectTo($object);
}
// show an error message if the form failed validation
if (!$isFormValid)
{
$this->get('session')->setFlash('sonata_flash_error', 'flash_edit_error');
}
elseif ($this->isPreviewRequested())
{
// enable the preview template if the form was valid and preview was requested
$templateKey = 'preview';
}
}
$view = $form->createView();
// set the theme for the current Admin Form
$this->get('twig')->getExtension('form')->renderer->setTheme($view, $this->admin->getFormTheme());
return $this->render($this->admin->getTemplate($templateKey), array(
'action' => 'edit',
'form' => $view,
'object' => $object,
));
}
}

Resources