TYPO3 Extension - Redirect to another page in show action if no record is set or not available - typo3

How can I redirect to another page when someone access the detail page but without a record or if record is not available?
I have detail records like
domain.com/abc/ABC1234
When somone enters
domain.com/abc/
... I get:
Uncaught TYPO3 Exception
#1298012500: Required argument "record" is not set for Vendor\Extension\Controller\ActionController->show. (More information)
TYPO3\CMS\Extbase\Mvc\Controller\Exception\RequiredArgumentMissingException thrown in file
/is/htdocs/www/typo3_src-8.7.11/typo3/sysext/extbase/Classes/Mvc/Controller/AbstractController.php in line 425.
... in this case I want it to redirect to:
domain.com/other-page/
... I also need it if a specific record is not available.
... how to do so?
/**
* action show
*
* #param \Action $record
* #return void
*/
public function showAction(Action $record) {
$this->view->assign('record', $record);
}
Here are some examples TYPO3 Extbase - redirect to pid ... but not sure how to implement it
Edit: What works is ...
/**
* action show
*
* #param \Action $record
* #return void
*/
public function showAction(Action $record=null) {
if ($record === null) {
$pageUid = 75;
$uriBuilder = $this->uriBuilder;
$uri = $uriBuilder
->setTargetPageUid($pageUid)
->build();
$this->redirectToUri($uri, 0, 404);
} else {
$this->view->assign('record', $record);
}
}

The redirect method needs an action and controller parameter. So your redirect code is wrong.
$this->redirect($actionName, $controllerName = NULL, $extensionName = NULL, array $arguments = NULL, $pageUid = NULL, $delay = 0, $statusCode = 303);
To redirect to an PageUID you need to use the uriBuilder and the redirectToUri method. See here for an example.

This should do the trick:
public function showAction(Action $record=null) {
if ($record === null) {
$this->redirect(/* add parameters as needed */);
} else {
// other code
}
Alternative Solution (from Simon Oberländer)
public function intializeShowAction() {
if (!$this->request->hasArgument('record')) {
$this->redirect(/* add parameters as needed */); // stops further execution
}
}
Your question suggests that there should be an other action without arguments, probably a listAction, that is the DEFAULT action. The default action gets called when no action is specified. It is the first action enlisted in the ExtensionUtility::configurePlugin() call.
\TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin(
'Vendor.' . $_EXTKEY,
'Pluginname',
array(
'Domainobject' => 'list, show',
),
// non-cacheable actions
array(
'Domainobject' => 'list, show',
)
);
Regarding > The identity property "TTTT" is no UID
You have to distinguish between no parameter and an invalid parameter. For the latter you can add #ignorevalidation to the showAction comments and do your validation testing within the action - or you can leave it to extbase that displays the error message you have seen.
Where would you get a link like domain.com/abc/TTTT/ from anyhow? Unless the link is expired.
BTW: in a production system you would disable the display of exceptions, thus the display of the website would work.

This could be a solution:
```
/**
* Show a booking object
*
* #return void
* #throws \TYPO3\CMS\Extbase\Mvc\Exception\NoSuchArgumentException
*/
public function showAction()
{
$bookingObject = null;
$bookingObjectUid = 0;
if ($this->request->hasArgument('bookingObject')) {
$bookingObjectUid = (int)$this->request->getArgument('bookingObject');
}
if ($bookingObjectUid > 0) {
$bookingObject = $this->bookingObjectRepository->findByIdentifier($bookingObjectUid);
}
if (!($bookingObject instanceof BookingObject)) {
$messageBody = 'Booking object can\'t be displayed.';
$messageTitle = 'Error';
$this->addFlashMessage($messageBody, $messageTitle, AbstractMessage::ERROR);
$this->redirect('list');
}
$this->view->assign('bookingObject', $bookingObject);
}
```

Related

TYPO3/Extbase How to create unique slug within create action?

I have slug field in my TCA and in general it works, when adding via Backend > List module, even if I won't input any value the unique eval ensures that slug will be unique, so when I'll create many rows with the same name Foo TYPO3 backend will enshure that it will resolve to unique slugs like foo, foo-1, foo-2, etc. Kudos!:
'slug' => [
'exclude' => true,
'label' => 'Slug',
'displayCond' => 'VERSION:IS:false',
'config' => [
'type' => 'slug',
'generatorOptions' => [
'fields' => ['name'],
'fieldSeparator' => '/',
'replacements' => [
'/' => '',
],
],
'fallbackCharacter' => '-',
'eval' => 'unique',
'default' => '',
'appearance' => [
'prefix' => \BIESIOR\Garage\UserFunctions\SlugPrefix::class . '->getPrefix'
],
],
],
However when creating a new object from my form within new/create actions (typical Extbase CRUD from extension_builder as you can see) like:
public function createAction(Car $newCar)
{
$this->addFlashMessage(
'The object was created. Please be aware that this action is publicly accessible unless you implement an access check. See https://docs.typo3.org/typo3cms/extensions/extension_builder/User/Index.html',
'',
\TYPO3\CMS\Core\Messaging\AbstractMessage::WARNING);
$this->carRepository->add($newCar);
$this->redirect('list');
}
of course slug is note set.
My first idea is to duplicate the logic of TCA type='slug' and just add this functionality with some own JS, AJAX and PHP, however that sounds as overload and time consumption. Especially that I don't want the user to care about slug part at all. Is there any simple API for lookup for a unique slug of the given table that can be used in custom action instead?
Note this question is not about how to handle it with JS, that's just concept. I would like to skip this part for FE user at all, he doesn't need to know what the slug is. Just during creating a new object, I want to get unique value like foo-123 instead.
In addition to Jonas Eberles answer here's another example which also respects the eval configuration of the slug field (can be uniqueInSite, uniqueInPid or simply unique).
use TYPO3\CMS\Core\DataHandling\Model\RecordStateFactory;
use TYPO3\CMS\Core\DataHandling\SlugHelper;
use TYPO3\CMS\Core\Utility\GeneralUtility;
...
public function createAction(Car $newCar)
{
$this->carRepository->add($newCar);
GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager::class)->persistAll();
$record = $this->carRepository->findByUidAssoc($newCar->getUid())[0];
$tableName = 'tx_garage_domain_model_car';
$slugFieldName = 'slug';
// Get field configuration
$fieldConfig = $GLOBALS['TCA'][$tableName]['columns'][$slugFieldName]['config'];
$evalInfo = GeneralUtility::trimExplode(',', $fieldConfig['eval'], true);
// Initialize Slug helper
/** #var SlugHelper $slugHelper */
$slugHelper = GeneralUtility::makeInstance(
SlugHelper::class,
$tableName,
$slugFieldName,
$fieldConfig
);
// Generate slug
$slug = $slugHelper->generate($record, $record['pid']);
$state = RecordStateFactory::forName($tableName)
->fromArray($record, $record['pid'], $record['uid']);
// Build slug depending on eval configuration
if (in_array('uniqueInSite', $evalInfo)) {
$slug = $slugHelper->buildSlugForUniqueInSite($slug, $state);
} else if (in_array('uniqueInPid', $evalInfo)) {
$slug = $slugHelper->buildSlugForUniqueInPid($slug, $state);
} else if (in_array('unique', $evalInfo)) {
$slug = $slugHelper->buildSlugForUniqueInTable($slug, $state);
}
$newCar->setSlug($slug);
$this->carRepository->update($newCar);
}
with custom finder in the repository to fetch assoc array instead of the mapped object for $racord argument
public function findByUidAssoc($uid)
{
$query = $this->createQuery();
$query->matching(
$query->equals('uid', $uid)
);
return $query->execute(true)[0];
}
Note that the record needs to be persisted before executing above code.
References:
SlugHelper::generate
SlugHelper::buildSlugForUniqueInSite
SlugHelper::buildSlugForUniqueInPid
SlugHelper::buildSlugForUniqueInTable
According to answers from Elias and Jonas, I created a class which simplifies things especially when you have more models to handle
typo3conf/ext/sitepackage/Classes/Utility/SlugUtility.php
<?php
namespace VENDOR\Sitepackage\Utility; // <- to be replaced with your namespace
use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\DataHandling\Model\RecordStateFactory;
use TYPO3\CMS\Core\DataHandling\SlugHelper;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/***
*
* This file is part of the "Sitepackage" Extension for TYPO3 CMS.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* (c) 2020 Marcus Biesioroff <biesior#gmail.com>
* Concept by: Elias Häußler
* Jonas Eberle
*
***/
class SlugUtility
{
/**
* #param int $uid UID of record saved in DB
* #param string $tableName Name of the table to lookup for uniques
* #param string $slugFieldName Name of the slug field
*
* #return string Resolved unique slug
* #throws \TYPO3\CMS\Core\Exception\SiteNotFoundException
*/
public static function generateUniqueSlug(int $uid, string $tableName, string $slugFieldName): string
{
/** #var Connection $connection */
$connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($tableName);
$queryBuilder = $connection->createQueryBuilder();
$record = $queryBuilder
->select('*')
->from($tableName)
->where('uid=:uid')
->setParameter(':uid', $uid)
->execute()
->fetch();
if (!$record) return false;
// Get field configuration
$fieldConfig = $GLOBALS['TCA'][$tableName]['columns'][$slugFieldName]['config'];
$evalInfo = GeneralUtility::trimExplode(',', $fieldConfig['eval'], true);
// Initialize Slug helper
/** #var SlugHelper $slugHelper */
$slugHelper = GeneralUtility::makeInstance(
SlugHelper::class,
$tableName,
$slugFieldName,
$fieldConfig
);
// Generate slug
$slug = $slugHelper->generate($record, $record['pid']);
$state = RecordStateFactory::forName($tableName)
->fromArray($record, $record['pid'], $record['uid']);
// Build slug depending on eval configuration
if (in_array('uniqueInSite', $evalInfo)) {
$slug = $slugHelper->buildSlugForUniqueInSite($slug, $state);
} else if (in_array('uniqueInPid', $evalInfo)) {
$slug = $slugHelper->buildSlugForUniqueInPid($slug, $state);
} else if (in_array('unique', $evalInfo)) {
$slug = $slugHelper->buildSlugForUniqueInTable($slug, $state);
}
return $slug;
}
}
Usage in any place, like controller. Scheduler task, repository, etc. Keep in mind that record should be saved before (it may be created by Extbase, or just with plain SQL), just need to have created uid and be valid TYPO3 record.
use VENDOR\Sitepackage\Utility\SlugUtility;
use \TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager;
...
$pageSlug = SlugUtility::generateUniqueSlug(
5, // int $uid UID of record saved in DB
'pages', // string $tableName Name of the table to lookup for uniques
'slug' // string $slugFieldName Name of the slug field
)
// or
$uniqueSlug = SlugUtility::generateUniqueSlug(
123,
'tx_garage_domain_model_car',
'slug'
);
// or according to the original question,
// if you created new model object with Extbase,
// persist it, create unique slug with SlugUtility
// set the slug property to the created model object and finally update
public function createAction(Car $newCar)
{
$this->carRepository->add($newCar);
GeneralUtility::makeInstance(PersistenceManager::class)->persistAll();
$uniqueSlug = SlugUtility::generateUniqueSlug(
$newCar->getUid(),
'tx_garage_domain_model_car',
'slug'
);
if($uniqueSlug) {
$newCar->setSlug($uniqueSlug);
$this->carRepository->update($newCar);
}
$this->redirect('list');
}
// no need for second call to persistAll()
// as Extbase will call it at action's finalizing.
// etc.
You can use the SlugHelper directly. The API was obviously not made very fluent for that use case but it works...
$this->carRepository->add($newCar);
// probably you need to persist first - I am not sure if this is really necessary
$this->objectManager()->get(
\TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager::class
)->persistAll();
$table = 'tx_garage_domain_model_car';
$field = 'slug';
// a stripped down record with just the necessary fields is enough
$record = ['name' => $newCar->getName()];
$pid = $this->settings->persistence->...
$slugHelper = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(
\TYPO3\CMS\Core\DataHandling\SlugHelper::class,
$table,
$field,
$GLOBALS['TCA'][$table]['columns'][$field]['config']
);
$newCar->slug = $slugHelper->generate($record, $pid);

How to get the url for admin page (including the key) in magento 2x custom module controller

I need the page url in Magento 2x including key in my custom module controller.
here something similar but this is for magento 1x. i need for magento 2x.
for magento 1x : Mage::helper('adminhtml')->getUrl('adminhtml/framexport/index') but i need similar for magento 2x.
The right way is, inject the UrlInterface in you model block or whatever class constructor
Then call the getUrl() function
class SomeClass extends \Some\Other\Class
{
protected $_backendUrl;
public function __construct(
...........
...........
\Magento\Backend\Model\UrlInterface $backendUrl,
...........
) {
$this->_backendUrl = $backendUrl;
}
public function someFunction()
{
$params = array('some'=>'url_parameters');
$url = $this->_backendUrl->getUrl("the/url/path", $params);
}
}
You can easily get Admin url By calling
$this->getUrl('adminhtml/module/action');
Please not that "Context" type of object is loaded in the $this object
You can get admin url as follows:
public function __construct(\Magento\Backend\Helper\Data $HelperBackend
) {
$this->HelperBackend = $HelperBackend;
}
/**
*
* #param \Magento\Framework\Event\Observer $observer
* #return void
*/
public function getAdminUrl()
{
echo $this->HelperBackend->getHomePageUrl();
}
Somehow adminhtml/module creates an extra admin slug which does not work.
My solution is:
// $this->urlBuilder is defined in __constructor() \Magento\Framework\UrlInterface $urlBuilder
$query = [
'method' => 'confirm',
'id' => $order->getEntityId()
];
$url = $this->urlBuilder->getUrl('module', $query);
It works well for secure URLs (with keys) and non-default /admin URLs, such as /backend.

notBlank constraint not working for File input - Form with OneToMany relationship Symfony2

I have a OneToMany relationship between Article and Image entities. I created the Article's form like:
class ArticleType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('articleTitle','text',array('required' => false))
->add('articlePrice','number',array('required' => false))
->add('images', new ImageType())
;
}
....
}
And
class ImageType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('file', 'file',array('required' => false))
;
}
....
}
I have a problem in validating the file attribute. My goal is to return an error message when user chooses no file (at least one image should be associated to an Article). I tried by asserting the NotBlank but it doesn't work.
<?php
....
use Symfony\Component\Validator\Constraints as Assert;
class Image
{
/**
* Image file
*
* #var File
* #Assert\NotBlank
*/
private $file;
....
}
I don't embed collections while creating my form, since project requirements oblige AJAX uploading for each image separately before submission of the whole form. In other words, a javascript event listener, bound to input changeevent, creates the AJAX call which uploads the image after validation (ie. file is validated for size, extension, in controller). But when I send the overall form, only the other fields are validated and form is submitted even if no file is choosed (I see in headers all formData elements except the file one).
I want to notice that the form is rendered correcly when I inspect element in browser.
I spent a lot of time trying to resolve this issues but to no avail, your help is a rescue.
Edit: As requested by Mr Nawfal Serrar
The AJAX call is performed like:
$(':file').change(function(){
var file = this.files[0];
var url= $('.article-form').attr('data-iu-url');
var formData = new FormData($('form')[0]);
$.ajax({
url: url,
type: 'POST',
success: completeHandler,
data: formData,
});
});
url contains the route image_upload respecting following config:
image_upload:
pattern: /{id}/image_upload
defaults: { _controller: ShopManagementBundle:Image:uploads}
requirements: { _method: post|put }
Controller:
public function uploadsAction(Request $request, $id) {
if ($request->isMethod('POST')) {
$image = $request->files->get('articletype')['images']['file'];
$status='success';
$message='';
$uploadedURL='';
$the_id=0;
if (($image instanceof UploadedFile) && ($image->getError() == 0)) {
if ($image->getSize() < 50000000000) {
$originalName = $image->getClientOriginalName();
$name_array = explode('.', $originalName);
$extension = $name_array[sizeof($name_array) - 1];
$valid_file_types = array('jpeg', 'jpg', 'bmp', 'png', 'gif');
if (in_array(strtolower($extension), $valid_file_types)) {
$imagee= new Image();
$em = $this->getDoctrine()->getManager();
$imagee->setFile($image);
$imagee->setSubDir('hg');
$imagee->upload();
$entity = $em->getRepository('ShopManagementBundle:Article')->find($id);
$imagee->setAricle($entity);
$uploadedURL= $imagee->getUploadDir(). DIRECTORY_SEPARATOR . $imagee->getSubDir(). DIRECTORY_SEPARATOR . $image->getBasename();
$em->persist($entity);
$em->persist($imagee);
$em->flush();
$the_id=$imagee->getId();
} else {
$status = "fail";
$message = "extension problem";
}
} else {
$status = "fail";
$message = "Image size too big";
}
} else {
$status = "fail";
$message = "Error uploading";
}
return $this->render('ShopManagementBundle:Image:image_portion.html.twig', array(
'status' => $status,
'message' => $message,
'uploadedURL' => $uploadedURL,
'image_id'=>$the_id,
));
}
else
return new Response('RE try uploading');
}
As you can see I am not validation using isValid in controller, I validate with if-else statements assuming that the file is already sent.
After all we said in comment zone, I would recommand you to use #Assert\File for your file upload validation. It will prevent your if/else stack.
Since you told me you were attaching the uploaded files to an already existing Article, then using an embedded collection of forms would be the solution as I mentionned.
Since you'll retrieve the article and then create a form type with it, all the Images will be linked to it.
Add the count constraint on the Images list of your:
/**
* #Assert\Count(
* min = "1",
* minMessage = "You must upload at least one image",
* )
*/
private $images;
Since you will retrieve the Article which is already linked to uploaded Image instances, the validation will not throw an error. Otherwise, the user is trying to submit the form without images. The validation constraint will then make the form invalid.
There might be other little stuff to do to make it work, but it should help you to go ahead.
To properly handle file uploads better to follow this method :
http://symfony.com/doc/current/cookbook/doctrine/file_uploads.html
Its the official one, then at the return of the upload method you check if there is no file return your error message else continue with your upload.

Laravel 4 list and detail route

I want to achieve the following:
devices > Controller#devices
devices/{id} > Controller#devices
Is this possible with Laravel ? I'm trying to map a domotic box with an android application ImperiHome, and they expect me to have the same route for devices list and for any device action.
So far I've tried this:
Route::get('devices/{deviceId}/action/{actionName}/{actionParam?}', 'DomoticzController#devices');
Route::get('devices', 'DomoticzController#devices');
But I cannot retrieve the argument when I call the devices/id url
Ok, so to solve the php strict standard error I just splitted the routes to two methods as follows:
routes.php
Route::get('devices/{deviceId}/action/{actionName}/{actionParam?}', 'DomoticzController#device');
Route::get('devices', 'DomoticzController#devices');
Route::get('rooms', 'DomoticzController#rooms');
//Route::get('action_ret', 'DomoticzController#action_ret');
Route::get('system', 'DomoticzController#system');
Route::get('/', 'DomoticzController#system');
DomoticzController.php
/**
* Call for an action on the device identified by $deviceId.
* #return string Json formated action status.
*/
public function device($deviceId, $actionName, $actionParam = null)
{
$client = $this->getClient();
$request = $client->getClient()->createRequest('GET', get_url("json.htm?type=command&param={$actionName}&idx={$deviceId}}&switchcmd=$actionParam"));
$response = $request->send();
$input = $response->json();
// convert to app format
$output = array('success' => ('OK' === $input['status'] ? true : false), 'errormsg' => ('ERR' === $input['status'] ? 'An error occured' : ''));
return Response::json($output);
}
/**
* Retrieve the list of the available devices.
* #return string Json formatted devices list.
*/
public function devices()
{
$client = $this->getClient();
$request = $client->getClient()->createRequest('GET', get_url('json.htm?type=devices&used=true'));
$response = $request->send();
$input = $response->json();
// convert to app format
$output = new stdClass();
$output->devices = array();
foreach ($input['result'] as $device) {
$output->devices[] = array (
'id' => $device['idx'],
'name' => $device['Name'],
'type' => 'DevSwitch',
'room' => null,
'params' => array(),
);
}
return Response::json($output);
}
maybe there is a better way to solve this, I would be glad to hear it.
If you let both routes use the same controller action, you need to make the parameters optional in the controller I think.
Try this public function device($deviceId = null, $actionName = null, $actionParam = null) and see if you still get the PHP strict error.
You can not have a route without parameters be redirected to a controller action that expects parameters. You can, on the other hand, make a route with parameters be redirected to a controller action with optional parameters (this does not mean that your route parameters need to be optional).

Zend Framework - Router - Creating Aliases

I'm building a Zend Framework 1.11.11 application and would like to make the routes and content database driven.
I've written a FrontController Plugin that retrieves the 'paths' from the database and creates an entry in the Router for each one, with the associated controller and action.
However, I'd like to be able to use 'aliases' - a URL that behaves like a normal URL, but is an alias.
For example, if I create the following:
// Create the Zend Route
$entry = new Zend_Controller_Router_Route_Static(
$route->getUrl(), // The string/url to match
array('controller' => $route->getControllers()->getName(),
'action' => $route->getActions()->getName())
);
// Add the route to the router
$router->addRoute($route->getUrl(), $entry);
Then a route for /about/ for example can goto the staticController, indexAction.
However, what's the best way for me to create an alias of this route? So if I went to /abt/ it would render the same Controller and Action?
To me it doesn't make sense to recreate the same route as I'll be using the route as the page 'identifier' to then load content from the database for the page...
you can extend static router:
class My_Route_ArrayStatic extends Zend_Controller_Router_Route_Static
{
protected $_routes = array();
/**
* Prepares the array of routes for mapping
* first route in array will become primary, all others
* aliases
*
* #param array $routes array of routes
* #param array $defaults
*/
public function __construct(array $routes, $defaults = array())
{
$this->_routes = $routes;
$route = reset($routes);
parent::__construct($route, $defaults);
}
/**
* Matches a user submitted path with a previously specified array of routes
*
* #param string $path
* #param boolean $partial
* #return array|false
*/
public function match($path, $partial = false)
{
$return = false;
foreach ($this->_routes as $route) {
$this->setRoute($route);
$success = parent::match($path, $partial);
if (false !== $success) {
$return = $success;
break;
}
}
$this->setRoute(reset($this->_routes));
return $return;
}
public function setRoute($route)
{
$this->_route = trim($route, '/');
}
}
and add new router this way:
$r = My_Route_ArrayStatic(array('about', 'abt'), $defaults);