TYPO3: get all FE users - typo3

I am trying to create a user management module. I would like to get all FE users.
This is my Controller:
/**
* #var \TYPO3\CMS\Extbase\Domain\Repository\FrontendUserRepository
* #inject
*/
protected $feUserRepository;
then I use:
$users = $this->feUserRepository->findAll();
$this->view->assign('users', $users);
but all I get is an empty object.
EDIT:
for some reason
$this->feUserRepository->findByUId(1);
does work but findAll() not...

This is because extbase will silently disable the respectStoragePage setting on the querySettings for a findByUid($uid) call.
So, you have two options:
Provide the correct storage pid in the TypoScript configuration of your plugin (plugin.tx_myextension.persistence.storagePid). This way, you will find every frontenduser that is stored on the given page.
You could implement your own FrontendUserRepository that extends the repository from extbase but disables the respectStoragePage for all calls (this way you'll get every frontendUser regardless of the page the record is stored on). Here is how you do it:
use TYPO3\CMS\Extbase\Domain\Repository\FrontendUserRepository as ExtbaseFrontendUserRepository;
class FrontendUserRepository extends ExtbaseFrontendUserRepository
{
/**
* Disable respecting of a storage pid within queries globally.
*/
public function initializeObject()
{
$defaultQuerySettings = $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings:class);
$defaultQuerySettings->setRespectStoragePage(false);
$this->setDefaultQuerySettings($defaultQuerySettings);
}
}
In your Controller you then inject your FrontendUserRepository. Then you should do the same for the FrontendUser Model and tell extbase afterwards that you are using the fe_users table for your Model:
config.tx_extbase {
persistence {
classes {
Vendor\MyExtension\Domain\Model\FrontendUser {
mapping {
tableName = fe_users
}
}
}
}
}

Related

Linking Spatie Permissions to Backpack UI show/hide

New to Laravel and Backpack here, but trying to integrate the PermissionManager with Backpack. I've got it all installed and showing the Users/Permissions/Roles in the UI, however I was unable to figure out how to show/hide buttons and functionality in the Backpack UI based on those permissions. I'm hoping someone can comment on the solution I came up with or if there is something else that should be used.
Note, this is really about showing and hiding UI elements, not the actual policies (which I am handling separately using the "can" functions in my controllers, routes, etc.)
My solution:
In my EntityCrudController, I use a trait I made called CrudPermissionsLink, then in setup() I call the function I made:
public function setup()
{
CRUD::setModel(\App\Models\ProgramUnit::class);
CRUD::setRoute(config('backpack.base.route_prefix') . '/programunit');
CRUD::setEntityNameStrings('programunit', 'program_units');
$this->linkPermissions();
}
Then in my trait, I have it simply defined based on a naming convention, splitting on dashes.
<?php
namespace App\Http\Traits;
use Illuminate\Support\Facades\Auth;
/**
* Properties and methods used by the CrudPermissionsLink trait.
*/
trait CrudPermissionsLink
{
/**
* Remove access to all known operations by default, reset them based on permissions defined in the format
* entity_name-operation
*
*/
public function linkPermissions()
{
$ui_ops = ['list','create','delete','update'];
$user = Auth::user();
$this->crud->denyAccess($ui_ops);
foreach($ui_ops as $op){
$perm_name = "{$this->crud->entity_name}-{$op}";
if($user->can($perm_name)){
$this->crud->allowAccess($op);
}
}
}
}
What you have will work. That said, I recently created a similar solution for my apps. For my solution, I used an abstract Crud controller as below and all my specific crud controllers extend this class:
<?php
namespace App\Http\Controllers;
use Illuminate\Support\Facades\Gate;
use Illuminate\Database\Eloquent\Model;
use Backpack\CRUD\app\Http\Controllers\Operations\ListOperation;
use Backpack\CRUD\app\Http\Controllers\Operations\CreateOperation;
use Backpack\CRUD\app\Http\Controllers\Operations\UpdateOperation;
use Backpack\CRUD\app\Http\Controllers\Operations\DeleteOperation;
use Backpack\CRUD\app\Http\Controllers\CrudController as BaseCrudController;
abstract class CrudController extends BaseCrudController
{
use ListOperation, DeleteOperation;
use CreateOperation { store as traitStore; }
use UpdateOperation { update as traitUpdate; }
/**
* All possible CRUD "actions"
*/
public const CRUD_ACTION_CREATE = 'create';
public const CRUD_ACTION_LIST = 'list'; // synonymous with "read"
public const CRUD_ACTION_UPDATE = 'update';
public const CRUD_ACTION_DELETE = 'delete';
public const CRUD_ACTION_REORDER = 'reorder';
public const CRUD_ACTION_REVISIONS = 'revisions';
/**
* #var array An array of all possible CRUD "actions"
*/
public const ACTIONS = [
self::CRUD_ACTION_CREATE,
self::CRUD_ACTION_LIST,
self::CRUD_ACTION_UPDATE,
self::CRUD_ACTION_DELETE,
self::CRUD_ACTION_REORDER,
self::CRUD_ACTION_REVISIONS,
];
/**
* #var array An array of all CRUD "actions" that are not allowed for this resource
* Add any of the CRUD_ACTION_X constants to this array to prevent users accessing
* those actions for the given resource
*/
public $_prohibitedActions = [
self::CRUD_ACTION_REORDER, // not currently using this feature
self::CRUD_ACTION_REVISIONS, // not currently using this feature
];
/**
* Protect the operations of the crud controller from access by users without the proper
* permissions
*
* To give a user access to the operations of a CRUD page give that user the permissions below
* (where X is the name of the table the CRUD page works with)
*
* `X.read` permission: users can view the CRUD page and its records
* `X.create` permission: users can create records on the CRUD page
* `X.update` permission: users can update records on the CRUD page
* `X.delete` permission: users can delete records on the CRUD page
* `X.reorder` permission: users can reorder records on the CRUD page
* `X.revisions` permission: users can manage record revisions on the CRUD page
*
* #return void
*/
public function setupAccess(): void
{
// get the name of the table the crud operates on
$table = null;
if (isset($this->crud->model) && $this->crud->model instanceof Model) {
/** #var Model $this->crud->Model; */
$table = $this->crud->model->getTable();
}
// for each action, check if the user has permissions
// to perform that action and enforce the result
foreach (self::ACTIONS as $action) {
$requiredPermission = "$table.$action";
// If our model has no $table property set deny all access to this CRUD
if ($table && !$this->isProhibitedAction($action) && Gate::check($requiredPermission)) {
$this->crud->allowAccess($action);
continue;
}
$this->crud->denyAccess($action);
}
}
/**
* Check if the given action is allowed for this resource
* #param string $action One of the CRUD_ACTION_X constants
* #return bool
*/
public function isProhibitedAction($action): bool
{
return in_array($action, $this->_prohibitedActions, true);
}
/**
* Setup the CRUD page
* #throws \Exception
*/
public function setup(): void
{
$this->setupAccess();
}
}

TYPO3 Extension: How to make a relation to another extension’s model optional?

I have an events extension (for TYPO3 9 LTS and 10 LTS), say MyVendor\MyEvents and a Locations extension, say MyVendor\MyLocations.
The Model MyVendor\MyEvents\Domain\Model\Events has a property eventLocation which is defined to be an object of MyVendor\MyLocations\Domain\Model\Locations.
Now I want to make the relation to MyVendor\MyLocations\Domain\Model\Locations optional. I have found a way for the TCA to show a different form field in the backend depending on the MyLocations extension being installed. But I have no idea how to make all the type definitions in the Events model conditional. They are crucial for the extension to work:
namespace MyVendor\MyEvents\Domain\Model
class Events extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity
{
/**
* #var \MyVendor\MyLocations\Domain\Model\Locations
*/
protected $eventLocation = NULL;
/**
* #return \MyVendor\MyLocations\Domain\Model\Locations $eventLocation
*/
public function getEventLocation()
{
return $this->eventLocation;
}
/**
* #param \MyVendor\MyLocations\Domain\Model\Locations $eventLocation
* #return void
*/
public function setEventLocation(\MyVendor\MyLocations\Domain\Model\Locations $eventLocation)
{
$this->eventLocation = $eventLocation;
}
}
In case MyVendor\MyLocations is loaded it needs to be defined as above, in case it isn’t loaded it should be just an integer.
In the TCA I am using if (TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('my_locations')) for showing a different field in the backend form for an event.
The Locations Model is in a separate extension because I am using it in a third extension as well.
In your events extension you could setup a repository for locations. Then you can map this repository to your location extensions model via TypoScript.

Virtual properties in TYPO3 extbase domain models?

I'm trying to use a virtual domain model property in TYPO3 9.5.x that doesn't have a database field representation but I can't get it to work.
My model looks like this
class Project extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity {
/**
* participants
*
* #var string
*/
protected $participants;
...
/**
* Returns the participants
*
* #return string $participants
*/
public function getParticipants()
{
$this->participants = "foo";
return $this->participants;
}
}
I do see the property when I debug the model but it's always null as if it doesn't even recognise the getter method getParticipants().
Any idea what I might be doing wrong?
Already added a database field to ext_tables.sql and the TCA, but it didn't seem to make a difference.
The property is null because that's the state when the Extbase debugger inspects it. Notice that the Extbase debugger knows nothing about getters and also does not call them.
So if you want to initialize your property you must do this at the declaration time:
protected $participants = 'foo';
You can debug this property by simpy accessing it.
In Fluid, if you use <f:debug>{myModel}</f:debug>, you will see NULL for your property.
But if you directly use <f:debug>{myModel.participants}</f:debug>, you will see 'foo'.

Edit hidden records in frontend

I am building an extension to edit tt_news records in frontend.
I set setIgnoreEnableFields(TRUE) in my repository.
But if I try edit a hidden record, I get the error
Object with identity „12345" not found.
Any solution for this?
I am guessing you are using an action like
/**
* Single view of a news record
*
* #param \Vendor\Ext\Domain\Model\News $news news item
*/
public function detailAction(\Vendor\Ext\Domain\Model\News $news = null)
Your problem is, that the Repository is not used to fetch the record.
As a solution, remove the argument, clear the caches and try something like that
/**
* Single view of a news record
*
* #param \Vendor\Ext\Domain\Model\News $news news item
*/
public function detailAction() {
$id = (int)$this->request->getArgument('news');
if ($id) {
$news = $this->newsRepository->findByUid($previewNewsId);
}
}
Now you can manipulate the QuerySettings and use those.
The problem is the PropertyMapping. If extbase try to assign an uid (12345) to an Domain Object (tt_news) the "setEnableFields" setting of the Repository isn't respected. So you must fetch the object by yourself.
the simple solution is to do this in an initialize*Action for each "show" action. For editAction an example:
public function initializeEditAction() {
if ($this->request->hasArgument('news')) {
$newsUid = $this->request->getArgument('news');
if (!$this->newsRepository->findByUid($newsUid)) {
$defaultQuerySettings = $this->newsRepository->createQuery()->getQuerySettings();
$defaultQuerySettings->setIgnoreEnableFields(TRUE);
$this->newsRepository->setDefaultQuerySettings($defaultQuerySettings);
if ($news = $this->newsRepository->findByUid($newsUid)) {
$this->request->setArgument('news', $news);
}
}
}
}
The Hard Part is to get the object to update. As I never try this I have found an TypeConverter to fetch also hidden Records at https://gist.github.com/helhum/58a406fbb846b56a8b50
Maybe Instead to register the TypeConverter for everything (like the example in ext_localconf.php) you can try to assign it only in the initializeUpdateAction
public function initializeUpdateAction() {
if ($this->arguments->hasArgument('news')) {
$this->arguments->getArgument('news')->getPropertyMappingConfiguration()
->setTypeConverter('MyVendor\\MyExtension\\Property\\TypeConverters\\MyPersistenObjectConverter')
}
}

Can I use ORM without connecting entity to a database table

I have an Entity that pulls it's data from a REST web service. To keep thing consistent with the entities in my app that pull data from the database I have used ORM and overridden the find functions in the repository.
My problem is that ORM seems to demand a database table. When I run doctrine:schema:update it moans about needing an index for the entity then when I add one it creates me a table for the entity. I guess this will be a problem in the future as ORM will want to query the database and not the web service.
So... am I doing this wrong?
1, If I continue to use ORM how can I get it to stop needing the database table for a single entity.
2, If I forget ORM where do I put my data loading functions? Can I connect the entity to a repository without using ORM?
So... am I doing this wrong?
Yes. It doesn't make sense to use the ORM interfaces if you don't really want to use an ORM.
I think the best approach is NOT to think about implementation details at all. Introduce your own interfaces for repositories:
interface Products
{
/**
* #param string $slug
*
* #return Product[]
*/
public function findBySlug($slug);
}
interface Orders
{
/**
* #param Product $product
*
* #return Order[]
*/
public function findProductOrders(Product $product);
}
And implement them with either an ORM:
class DoctrineProducts implements Products
{
private $em;
public function __construct(EntityManager $em)
{
$this->em = $em;
}
public function findBySlug($slug)
{
return $this->em->createQueryBuilder()
->select()
// ...
}
}
or a Rest client:
class RestOrders implements Orders
{
private $httpClient;
public function __construct(HttpClient $httpClient)
{
$this->httpClient = $httpClient;
}
public function findProductOrders(Product $product)
{
$orders = $this->httpClient->get(sprintf('/product/%d/orders', $product->getId()));
$orders = $this->hydrateResponseToOrdersInSomeWay($orders);
return $orders;
}
}
You can even make some methods use the http client and some use the database in a single repository.
Register your repositories as services and use them rather then calling Doctrine::getRepository() directly:
services:
repository.orders:
class: DoctrineOrders
arguments:
- #doctrine.orm.entity_manager
Always rely on your repository interfaces and never on a specific implementation. In other words, always use a repository interface type hint:
class DefaultController
{
private $orders;
public function __construct(Orders $orders)
{
$this->orders = $orders;
}
public function indexAction(Product $product)
{
$orders = $this->orders->findProductOrders($product);
// ...
}
}
If you don't register controllers as services:
class DefaultController extends Controller
{
public function indexAction(Product $product)
{
$orders = $this->get('repository.orders')->findProductOrders($product);
// ...
}
}
A huge advantage of this approach is that you can always change the implementation details later on. Mysql is not good enough for search anymore? Let's use elastic search, it's only a single repository!
If you need to call $product->getOrders() and fetch orders from the API behind the scenes it should still be possible with some help of doctrine's lazy loading and event listeners.