Custom Home page sonata page - sonata-admin

I create my custom controller for the page home.
controller:
class FrontPageController extends Controller
{
public function homeAction()
{
return $this->render('FrontPageBundle:Page:home.html.twig');
}
}
routing.yml
front_page_home:
path: /
defaults: { _controller: FrontPageBundle:FrontPage:home }
but the url to my controller redirects to the controller sonata.page.page_service_manager:execute
route: "page_slug"

I found a solution but I don't know if it is good practice or not.
I create my customer RoutePageGenerator class and edit the class which will change the route to the homepage. Like this:
// Iterate over declared routes from the routing mechanism
foreach ($this->router->getRouteCollection()->all() as $name => $route) {
if($route->getPath() === "/")
{
$name = trim($name);
$root = $this->pageManager->create(array(
'routeName' => $name,
'name' => $name,
'url' => $route->getPath(),
'site' => $site,
'requestMethod' => isset($requirements['_method']) ? $requirements['_method'] : 'GET|POST|HEAD|DELETE|PUT',
'slug' => '/',
));
}
I delete this part of code:
$root = $this->pageManager->getPageByUrl($site, '/');
// no root url for the given website, create one
if (!$root) {
$root = $this->pageManager->create(array(
'routeName' => PageInterface::PAGE_ROUTE_CMS_NAME,
'name' => 'Homepage',
'url' => '/',
'site' => $site,
'requestMethod' => isset($requirements['_method']) ? $requirements['_method'] : 'GET|POST|HEAD|DELETE|PUT',
'slug' => '/',
));
$this->pageManager->save($root);
}
I create my customer UpdateCoreRoutesCommand and I call my RoutePageGenerator:
just execute this command php app/console sonata:page:create-snapshots --site=1
and it works.

Related

cakedc/users autologin after registration

I'm using the cakephp 4.2.6 with the cakedc/users plugin. I'd like to automatically login the user after registration without the need to first activate the account by clicking on the link in e-mail.
My idea is to creating an Event-Listener and listening to the Users.Global.afterRegister Event, but then I don't know how to login the user with the new Authentication.
this is what I ended up with and it seems to work so far:
<?php
namespace App\Event;
use Authentication\AuthenticationService;
use Authentication\AuthenticationServiceInterface;
use Authentication\AuthenticationServiceProviderInterface;
use Authentication\Middleware\AuthenticationMiddleware;
use Psr\Http\Message\ServerRequestInterface;
use Cake\Event\EventListenerInterface;
use Cake\Datasource\FactoryLocator;
use Cake\Log\Log;
class UserEventListener implements EventListenerInterface
{
public function implementedEvents(): array
{
return [
'Users.Global.afterRegister' => 'autoLogin',
];
}
public function autoLogin($event)
{
$user = $usersTable->get($event->getData('user')->id);
$request = $event->getSubject()->getRequest();
$response = $event->getSubject()->getResponse();
$authenticationService = $this->getAuthenticationService($request);
$authenticationService->persistIdentity($request, $response, $user);
}
public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface
{
$authenticationService = new AuthenticationService([
'unauthenticatedRedirect' => \Cake\Routing\Router::url([
'controller' => 'Users',
'action' => 'login',
'plugin' => null,
'prefix' => null
]),
'queryParam' => 'redirect',
]);
// Load identifiers, ensure we check email and password fields
$authenticationService->loadIdentifier('Authentication.Password', [
'fields' => [
'username' => 'email',
'password' => 'password',
]
]);
// Load the authenticators, you want session first
$authenticationService->loadAuthenticator('Authentication.Session');
// Configure form data check to pick email and password
$authenticationService->loadAuthenticator('Authentication.Form', [
'fields' => [
'username' => 'email',
'password' => 'password',
],
'loginUrl' => \Cake\Routing\Router::url([
'controller' => 'Users',
'action' => 'login',
'plugin' => null,
'prefix' => null
]),
]);
return $authenticationService;
}
}
There is a similar use case covered in the plugin documentation https://github.com/CakeDC/users/blob/master/Docs/Documentation/Events.md
Your approach looks correct to me, using the afterRegister event to create the listener in a controller where you have access to the Authenticatication component,
// note this example is handling auto-login after the user validated the email sent (click the validation token link sent to his email address)
EventManager::instance()->on(
\CakeDC\Users\Plugin::EVENT_AFTER_EMAIL_TOKEN_VALIDATION,
function($event){
$users = $this->getTableLocator()->get('Users');
$user = $users->get($event->getData('user')->id);
$this->Authentication->setIdentity($user);
}
);
or using the request to retrieve the authentication service and set the identity like it's done here https://github.com/cakephp/authentication/blob/master/src/Controller/Component/AuthenticationComponent.php#L273

AppController Class is not working on Sub directory Cakephp 3

I am creating a REST Api using cakephp-jwt-auth But the AppController I create in subfolder is not called.
My App controller code inside Ca/Api code
<?php
namespace App\Controller\Ca\Api;
use Cake\Controller\Controller;
use Cake\Event\Event;
class AppController extends Controller
{
public function initialize()
{
parent::initialize();
$this->loadComponent('RequestHandler');
$this->loadComponent('Auth', [
'storage' => 'Memory',
'authenticate' => [
'Form' => [
'fields' => ['username' => 'email'],
],
'ADmad/JwtAuth.Jwt' => [
'parameter' => 'token',
'userModel' => 'Users',
'fields' => [
'username' => 'id'
],
'queryDatasource' => true
]
],
'unauthorizedRedirect' => false,
'checkAuthIn' => 'Controller.initialize'
]);
$this->loadComponent('BryanCrowe/ApiPagination.ApiPagination', [
'key' => 'paging',
'aliases' => [
'page' => 'currentPage',
'current' => 'resultCount'
],
'visible' => [
'currentPage',
'resultCount',
'prevPage',
'nextPage',
'pageCount',
'page',
]
]);
}
public function beforeFilter(Event $event) {
parent::beforeFilter($event);
}
}
And my route file:
Router::prefix('ca/api', function ($routes) {
$routes->setExtensions(['json']);
$routes->connect('/login', ['controller' => 'Login', 'action' => 'login', "prefix" => "ca/api"]);
$routes->connect('/dashboard', ['controller' => 'Dashboard', 'action' => 'home', 'prefix' => "ca/api"]);
$routes->fallbacks('InflectedRoute');
});
My app controller class is not called and I don't understand where I am doing wrong.
you can create controller like this
<?php
namespace App\Controller\Api;
use Cake\Controller\Controller;
use Cake\Event\Event;
class AppController extends Controller
{
public function initialize()
{
parent::initialize();
$this->loadComponent('RequestHandler');
$this->loadComponent('Auth', [
'storage' => 'Memory',
'authenticate' => [
'Form' => [
'scope' => ['Users.group_id' => 1]
],
'ADmad/JwtAuth.Jwt' => [
'parameter' => 'token',
'userModel' => 'Users',
'fields' => [
'username' => 'id'
],
'queryDatasource' => true
]
],
'unauthorizedRedirect' => false,
'checkAuthIn' => 'Controller.initialize'
]);
}
}
And other controller like this
<?php
namespace App\Controller\Ca;
use Cake\Event\Event;
use Cake\Http\Exception\UnauthorizedException;
use Cake\Utility\Security;
use Firebase\JWT\JWT;
use Cake\Http\ServerRequest;
use Cake\I18n\Time;
use Aws\S3\S3Client;
use League\Flysystem\AwsS3v3\AwsS3Adapter;
use League\Flysystem\Filesystem;
use Cake\Http\Exception\NotFoundException;
class DashboardController extends AppController
{
public function initialize()
{
parent::initialize();
}
public function home()
{
pr("hiiih");
}
}
Remember do not use
**
use App\Controller\AppController;
**
When you are creating AppController in sub folder
for more information read this tutorial :- https://trinitytuts.com/secure-cakephp-web-services-using-jwt/
The AppController isn't called magically someplace internally in CakePHP. The Routes you define call a specific controller, which should simply extend your AppController.
Following convention you'd typically only ever use a single AppController for your entire application, in /src/Controller/AppController. It looks based on the authentication methods listed in your example, this is the approach your taking, but you don't need to move it into a subfolder to make prefix routing work.
Based on your routes:
Router::prefix('ca/api', function ($routes) {
... This will look for a classes that match connections inside /src/Controller/Ca/Api, and for matches like:
// Note, the "prefix" item you listed on this line is not required I'd remove it:
$routes->connect('/login', ['controller' => 'Login', 'action' => 'login', "prefix" => "ca/api"]);
.. This will look for a class called LoginController, at /src/Controller/Ca/Api/. This class should simply reference your existing default AppController in it's default location:
<?php
namespace App\Controller\Ca\Api;
use App\Controller\AppController; // The namespace declaration is how your subclass locates it's parent class
class LoginController extends AppController
{
If you have some particular need to have multiple AppControllers (which I'd not recommend) then just change what version you're referencing with use namespace.
See for more information:
PHP Namespaces
Prefix Routing
The AppController

CakePHP 3: tests for controller

I'm writing tests for a controller in a plugin (Assets plugin).
This is the controller:
namespace Assets\Controller;
use Cake\Controller\Controller;
class AssetsController extends Controller
{
public function asset($filename, $type)
{
$this->response->type($type);
$this->response->file(ASSETS . DS . $filename);
return $this->response;
}
}
As you can see, it only sends an asset files.
This is the route:
Router::plugin('Assets', ['path' => '/assets'], function ($routes) {
$routes->connect(
'/:type/:filename',
['controller' => 'Assets', 'action' => 'asset'],
[
'type' => '(css|js)',
'filename' => '[a-z0-9]+\.(css|js)',
'pass' => ['filename', 'type'],
]
);
});
And this is the test class:
namespace Assets\Test\TestCase\Controller;
use Assets\Utility\AssetsCreator;
use Cake\TestSuite\IntegrationTestCase;
class AssetsControllerTest extends IntegrationTestCase
{
public function testAsset()
{
//This is the filename
$filename = sprintf('%s.%s', AssetsCreator::css('test'), 'css');
$this->get(sprintf('/assets/css/%s', $filename));
$this->assertResponseOk();
}
}
Running the test, however, this exception is generated (full test here):
1) Assets\Test\TestCase\Controller\AssetsControllerTest::testAsset
Cake\Routing\Exception\MissingControllerException: Controller class could not be found. in /home/mirko/Libs/Plugins/Assets/vendor/cakephp/cakephp/src/Http/ControllerFactory.php:91
I do not think is a problem of broken, because the same exception is generated by doing so:
$url = \Cake\Routing\Router::url([
'controller' => 'Assets',
'action' => 'asset',
'plugin' => 'Assets',
'type' => 'css',
'filename' => $filename,
]);
$this->get($url);
Where am I doing wrong? Thanks.
Solved! On my tests' bootstrap, I missed:
DispatcherFactory::add('Routing');
DispatcherFactory::add('ControllerFactory');
Now it works.

Custom Template in Sonata Admin

I am using Sonata Admin to manage CRUD tasks in my application. In one Admin called Multimedia, which has one-to-many relations with Files and Weblinks, both of which are embedded in the Multimedia form. I have a custom template that renders the fields horizontally and with titles. My question is, do I have to specify two different templates for Files and Weblinks because using a single file has failed, Files renders the embed form how I want it but weblink ignores directive.
Here's the Admin Code
class MultimediaAdmin extends Admin
{
// Fields to be shown on create/edit forms
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->with('General')
->add('name')
->add('publish_date')
->add('keywords')
->add('copyright')
->end()
->with('Files')
->add('files','sonata_type_collection',
array('label' => 'Multimedia Files',
'btn_add' => 'Add File',
'by_reference' => 'false',
'type_options' => array('delete' => false)
), array(
'edit' => 'inline',
'template' => 'MyMultimediaBundle:Multimedia:horizontal.fields.html.twig'
)
)
->end()
->with('Tags')
->add('tags')
->end()
->with('Weblinks')
->add('weblinks','sonata_type_collection',
array('label' => 'External Videos',
'btn_add' => 'Add Video',
'by_reference' => 'false',
'type_options' => array('delete' => false)
), array(
'edit' => 'inline',
'template' => 'MyMultimediaBundle:Multimedia:horizontal.fields.html.twig'
)
)
->end()
;
}
// Fields to be shown on lists
protected function configureListFields(ListMapper $listMapper)
{
$listMapper
->addIdentifier('name')
->add('publish_date')
->add('keywords')
->add('copyright')
->add('_action','actions',array('actions'=>(array('edit'=>array(),'view'=>array(),'delete'=>array()))))
;
}
// Fields to be shown on filter forms
protected function configureDatagridFilters(DatagridMapper $datagridMapper)
{
$datagridMapper
->add('name')
->add('publish_date')
->add('keywords')
->add('copyright')
;
}
public function prePersist($multimedia)
{
$this->preUpdate($multimedia);
}
public function preUpdate($multimedia)
{
$multimedia->setFiles($multimedia->getFiles());
}
public function getFormTheme()
{
return array_merge(
parent::getFormTheme(),
array('MyMultimediaBundle:Multimedia:horizontal.fields.html.twig')
);
}

Check for existing controller

I've written static pages component for my application, where admins can dynamically add/edit/remove static content pages. these are saved in the database.
(e.g. you can create a page called "about" and can visit it at myapplication/about)
This is my routing for these pages:
$page = new StaticPage();
$slugs = $page->find('list', array(
'fields' => array('slug'),
'recursive' => -1,
'order' => 'StaticPage.slug DESC',
));
Router::connect('/:slug',
array('controller' => 'static_pages', 'action' => 'display'),
array(
'pass' => array('slug'),
'slug' => implode($slugs, '|')
)
);
Now i have the problem, that when you create a page which slug matches an existing controller (e.g. users), it overwrites the Route to the UsersController.
so i need something like a blacklist or similar: i began to write a validation rule, where i want to check if that controller exists. for cake 1.3 there was a function "loadController" which return false, if the controller did not exist, but for cake 2.x there is no such an function. am i missing this somehow? does it have a new name or is in a utility library now?
Or are there better ways to solve this?
you should try this : http://www.cleverweb.nl/cakephp/list-all-controllers-in-cakephp-2/
and by getting the list of all controllers you can easily exclude the name of controllers
This is my validation method for now:
$route = Router::parse($check['slug']);
$controllerName = Inflector::camelize($route['controller'] . 'Controller');
$aCtrlClasses = App::objects('controller');
foreach ($aCtrlClasses as $controller) {
if ($controller != 'AppController') {
// Load the controller
App::import('Controller', str_replace('Controller', '', $controller));
// Load the ApplicationController (if there is one)
App::import('Controller', 'AppController');
$controllers[] = $controller;
}
}
if (in_array($controllerName, $controllers)) {
return false;
} else {
return true;
}

Resources