DataObject + TranslatableDataObject + ModelAdmin - internationalization

Setup: SS 3.1.13, Translatable 2.0.8 and TranslatableDataObject dev-master.
I have two DataObject classes: "Facility" ($has_one) and "FacilityType" ($has_many) which I chose to be managed in ModelAdmin (as they are global properties of the site owner). I cannot however get TranslatableDataObject extension to actually enable translation for them, despite reading carefully the setup instructions here.
Here's my _config.php:
// Set the site locale
i18n::set_locale('en_US');
SiteConfig::add_extension('Translatable');
// Set allowed locales
Translatable::set_allowed_locales(array('en_US', 'pl_PL'));
TranslatableDataObject::set_locales(array('en_US', 'pl_PL'));
Facility::add_extension("Translatable");
Facility::add_extension("TranslatableDataObject"); // tried "TranslatableDataObject('Title')" as well
FacilityCategory::add_extension("Translatable");
FacilityCategory::add_extension("TranslatableDataObject");
Could anyone please confirm that this setup should or won't work for me?
FacilityCategory.php
class FacilityCategory extends DataObject {
private static $db = array(
'Title' => 'Varchar'
);
private static $has_many = array(
'Facilities' => 'Facility.FacilityCategory'
);
public function getCMSFields(){
$fields = FieldList::create(
TextField::create('Title')
);
return $fields;
}
}
class FacilityCategoryAdmin extends ModelAdmin {
private static $menu_title = 'Facility Categories';
private static $url_segment = 'facility-categories';
private static $managed_models = array(
'FacilityCategory'
);
}
Facility.php
class Facility extends DataObject {
private static $db = array(
'Title' => 'Varchar',
);
private static $has_one = array(
'Category' => 'FacilityCategory'
);
private static $summary_fields = array(
'Title' => 'Name',
'Category.Title' => 'Type'
);
public function getCMSFields(){
$fields = FieldList::create(
TextField::create('Title'),
DropdownField::create('CategoryID', 'Category')
->setSource(FacilityCategory::get()->map()->toArray())
->setEmptyString('-- select a category --')
);
return $fields;
}
}
class FacilityAdmin extends ModelAdmin {
private static $menu_title = 'Facilities';
private static $url_segment = 'facilities';
private static $managed_models = array(
'Facility'
);
}

I've looked into this for you.
I've installed a new SS site using:
composer create-project silverstripe/installer
I've then added translatable:
composer require silverstripe/translatable ~2.0.8
My _config.php looks like this:
<?php
global $project;
$project = 'mysite';
global $database;
$database = '';
require_once('conf/ConfigureFromEnv.php');
// Set the site locale
i18n::set_locale('en_GB');
Translatable::set_default_locale('en_GB');
MyObj::add_extension('Translatable');
I've created a ModelAdmin.
I navigate to /dev/build?flush=all in my browser
I then navigated to the ModelAdmin in the CMS and I see this:
and
I think part of the problem is you're using two translatable modules at once, you only need one.
Other than that, I don't know why you wouldn't see the translatable tab. Are you getting any PHP errors, have you ?flushed or done a /dev/build?

Related

SwaggerDecorator not working after update API Platform to v2.3.5

After the API platform upgrade, the decorator from the documentation has stopped working:
https://api-platform.com/docs/core/swagger/#overriding-the-swagger-documentation
Does anyone know if this is a change, is it a bug?
I use Symfony 4.2.2 (probably the problem is due to the Symfony update).
My code adding to swagger input form to change context:
<?php
namespace App\Swagger;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
final class SwaggerDecorator implements NormalizerInterface
{
private $decorated;
public function __construct(NormalizerInterface $decorated)
{
$this->decorated = $decorated;
}
public function normalize($object, $format = null, array $context = [])
{
$docs = $this->decorated->normalize($object, $format, $context);
$customDefinition = [
'name' => 'context',
'definition' => 'Context field',
'default' => '',
'in' => 'query',
];
// Add context parameter
foreach ($docs['paths'] as $key => $value) {
// e.g. add a custom parameter
$customDefinition['default'] = lcfirst($docs['paths'][$key]['get']['tags'][0] ?? '');
$docs['paths'][$key]['get']['parameters'][] = $customDefinition;
if(isset($docs['paths'][$key]['post'])){
$docs['paths'][$key]['post']['parameters'][] = $customDefinition;
}
if(isset($docs['paths'][$key]['put'])){
$docs['paths'][$key]['put']['parameters'][] = $customDefinition;
}
}
return $docs;
}
public function supportsNormalization($data, $format = null)
{
return $this->decorated->supportsNormalization($data, $format);
}
}
Try to use parameter "decoration_priority" in services configuration (https://symfony.com/doc/current/service_container/service_decoration.html#decoration-priority)
For example:
App\Swagger\SwaggerDecorator:
decorates: 'api_platform.swagger.normalizer.documentation'
arguments: [ '#App\Swagger\SwaggerDecorator.inner' ]
decoration_priority: 1000
Or fix version "symfony/dependency-injection": "4.2.1" in composer.json )
See https://github.com/symfony/symfony/issues/29836 for details

ZF2 transalation is not working in form class

I am using zendframework 2 and My translations are not working here in form class where the form is formed and there is validation, elsewhere in whole applications they are working properly.
I have pasted all the code in my file with namespaces.
<?php
namespace Services\Form;
use Zend\Form\Form;
use Zend\Form\Element;
use Zend\InputFilter\Input;
use Zend\InputFilter\InputFilter;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class ProfilePicForm extends Form implements ServiceLocatorAwareInterface
{
protected $serviceLocator;
public function setServiceLocator(ServiceLocatorInterface $sl)
{
$this->serviceLocator = $sl;
}
public function getServiceLocator()
{
return $this->serviceLocator;
}
public function init()
{
$routeMatch = $this->getServiceLocator()->getServiceLocator()->get('Application')->getMvcEvent()->getRouteMatch();
$translator = $this->getServiceLocator()->getServiceLocator()->get('viewHelperManager')->get('translate')->getTranslator();
$action = $routeMatch->getParam('action');
// Form
parent::__construct('profile_pic_form');
$this->setAttribute('method', 'post');
$this->setAttribute('enctype','multipart/form-data');
$profile_pic_form_csrf = new Element\Csrf('profile_pic_form_csrf');
$profile_pic_form_csrf->setCsrfValidatorOptions(array('timeout'=>'3600'));
$this->add($profile_pic_form_csrf);
$profile_pic = new Element\File('profile_pic');
$this->add($profile_pic);
// Validation
$inputFilter = new InputFilter();
$profile_pic = new Input('profile_pic');
$profile_pic->getFilterChain()
->attach(new \Lib\MyLib\Filter\RenameUpload(array(
'target' => SERVICE_PROFILE_PIC_UPLOAD_PATH.'/profile-pic.*',
'use_upload_extension' => true,
'randomize' => true
)));
$required = true;
$profile_pic->setRequired($required);
$validator = new \Zend\Validator\File\UploadFile();
$validator->setOptions(array(
'messageTemplates' => array(
\Zend\Validator\File\UploadFile::FILE_NOT_FOUND => 'Please select picture.'
)));
$profile_pic->getValidatorChain()->attach($validator,true);
$validator = new \Zend\Validator\File\Size(array('max' => 250*1024));
$validator->setMessage(**$translator->translate('MyAccountPictureErrorMessage1')**);
$profile_pic->getValidatorChain()->attach($validator,true);
$validator = new \Zend\Validator\File\Extension('png,jpg');
$validator->setMessage(**$translator->translate('MyAccountPictureErrorMessage2')**);
$profile_pic->getValidatorChain()->attach($validator,true);
$inputFilter->add($profile_pic);
$this->setInputFilter($inputFilter);
}
this is my controller function.
public function profileAction() {
$this->layout('ajax-layout');
$var = new \stdClass();
$viewmodel = new ViewModel();
$this->authPlugin()->checkLogin();
$this->servicePlugin()->checkSid();
$this->layout()->setVariable('allowedFeatures', $this->featurePlugin()->getAllowedFeatures());
$this->languagePlugin()->translate();
$var->userInfo = $this->authPlugin()->getUserInfo();
if($this->params()->fromRoute('sid') !== null){
$var->sid = $this->params()->fromRoute('sid');
}
elseif ($this->params()->fromRoute('id') !== null) {
$var->sid = $this->params()->fromRoute('id');
}
// ----------------------- i m here --------------------------
// $var->sid = $this->params()->fromRoute('sid');
$var->profilePicForm = $this->getServiceLocator()->get('FormElementManager')->get('\Services\Form\ProfilePicForm');
$var->serviceNameForm = $this->getServiceLocator()->get('FormElementManager')->get('\Services\Form\ServiceNameForm');
$var->service = $this->getServices()->fetchServiceById($var->sid);
// Fetch payment history
$var->paymentHistory = $this->getServiceLocator()->get('Services\Model\PaymentTransactionService')->fetchPaymentTransactionsByServiceId($var->sid);
$var->timezones = $this->getTimeZoneTable()->listAll();
$viewmodel->setVariables(array('var' => $var));
return $viewmodel;
}
This is happening because of your validator.
I already talked about this problem, when you call new validators without the service locator :
https://stackoverflow.com/a/36500438/3333246
To fix that you need to set the translator in your options because:
class Size extends AbstractValidator
abstract class AbstractValidator implements
Translator\TranslatorAwareInterface,
ValidatorInterface
And TranslatorAwareInterface is not initialized if you instanciate a new Validator without ServiceLocator.
So your validators need options declared like this in your code:
$validator = new \Zend\Validator\File\Size(array('translator' => $translator, 'max' => 250*1024));
$validator->setMessage('MyAccountPictureErrorMessage1');
No need to translate the message now, the validator will translate it for you.
For my comment, about your code, nevermind it's not related to your problem. It's just personal in fact.
EDIT:
You don't need this translator :
$translator = $this->getServiceLocator()->getServiceLocator()->get('viewHelperManager')->get('translate')->getTranslator();
But this one
$translator = $this->getServiceLocator()->get('translator');
I have found another way to do this job, i have made an ajax call and on its response i show the divs having the translations.

Laravel get relationship model within object after save()

I am using laravel 4.2.
I have two models as below :
class User extends Eloquent{
protected $table = 'users';
public function user_card_details(){
return $this->hasMany('User_card_details');
}
}
And
class User_card_details extends Eloquent {
protected $table = 'user_card_details';
public $timestamps = true;
public $softdeletes = true;
public function user(){
return $this->belongsTo('User')->first();
}
}
And I can save the relationship record using :
$user_card_details = new User_card_details();
$user_card_details->card_number = Input::get('card_number');
$user_card_details->card_exp_month = Input::get('card_expires_m');
$user_card_details->card_exp_year = Input::get('card_expires_y');
$user_card_details->card_cvv = Input::get('card_cvv');
$user->user_card_details()->save($user_card_details);
Up to this it works fine for me.
After save() , I want the user object should be populated with user_details.
So if I want to use the properties, I can use it like :
echo $user->user_card_details->card_number;
But it is not working now.
Any suggestions?
Thanks
You have to remove the () to get the actual model or collection:
echo $user->user_card_details->card_number;
When you're calling the actual function, you'll receive an instance of the Query builder.
Also, it seems that you're not persisting your $user_card_details-object before you try to bind it to your user:
$user_card_details = new User_card_details();
$user_card_details->card_number = Input::get('card_number');
$user_card_details->card_exp_month = Input::get('card_expires_m');
$user_card_details->card_exp_year = Input::get('card_expires_y');
$user_card_details->card_cvv = Input::get('card_cvv');
$user_card_details->save(); //Added this line.
$user->user_card_details()->save($user_card_details);
The more correct way would be:
$user_card_details = [
'card_number' => Input::get( 'card_number' ),
'card_exp_month' => Input::get( 'card_expires_m' ),
'card_exp_year' => Input::get( 'card_expires_y' ),
'card_cvv' => Input::get( 'card_cvv' ),
];
$userCardDetailObj = $user->user_card_details()->create( $user_card_details );
Now, your User_card_detail-instance will be available as the returned object.

Zend Framework 2: get matched route in view

I'm currently learning ZF2 by developing a small MVC application roughly based on the skeleton app. Right now I'm trying to hide some fixed HTML elements based on the route matched: just as an example, I don't want the main menu to show during the login phase.
I can do that easily by passing toggle parameters as return values from the controller actions, but it doesn't feel right, so I'd like to just check the matched route from the layout and compose the layout accordingly.
Problem is, I don't know how to get the matched route in the template. Is this possible? Are there other solutions to avoid adding layout logic into controllers?
Edit after some good Frankenstein work, I was able to find a solution for this. I like the idea of using a helper, so I just tried to pass it the Application object, from the boostrap function in the main module:
$app = $e->getApplication();
$serviceManager = $app->getServiceManager();
....
$serviceManager->get('viewhelpermanager')->setFactory('getRoute', function($sm) use ($app) {
return new Helper\GetRoute($app);
});
and the helper function:
use Zend\View\Helper\AbstractHelper;
class GetRoute extends AbstractHelper {
private $sm;
public function __construct($app) {
$this->sm = $app->getServiceManager();
}
public function echoRoute() {
$router = $this->sm->get('router');
$request = $this->sm->get('request');
$routeMatch = $router->match($request);
if (!is_null($routeMatch))
echo $routeMatch->getMatchedRouteName();
}
}
perhaps there's a cleaner, more ZF2ish way to do this...
Another solution without a new match
$routeMatch = $serviceLocator->get('Application')->getMvcEvent()->getRouteMatch();
echo $routeMatch->getMatchedRouteName();
There is a way to get service manager in layout:
$sm = $this->getHelperPluginManager()->getServiceLocator();
and then you can access $sm->get('router') etc.
You could create a View helper that implements ServiceManagerAwareInterface. Then inside the View helper using the ServiceManager instance to retrieve both the router and request objects then reconstruct the route match.
$services = $this->getServiceManager();
$router = $services->get('router');
$request = $services->get('request');
$routeMatch = $router->match($request);
echo $routeMatch->getMatchedRouteName();
I'd also recommend writing the View helper so that code only triggers once per request.
When moving to ZF3, you should consider use this method... since getLocator isn't available anymore (and it's not correct inject it).
Create the Helper
namespace Application\View\Helper;
use Zend\Http\Request;
use Zend\Router\RouteStackInterface;
use Zend\View\Helper\AbstractHelper;
/**
* Helper to get the RouteMatch
*/
class RouteMatch extends AbstractHelper
{
/**
* RouteStackInterface instance.
*
* #var RouteStackInterface
*/
protected $router;
/**
* #var Request
*/
protected $request;
/**
* RouteMatch constructor.
* #param RouteStackInterface $router
* #param Request $request
*/
public function __construct(RouteStackInterface $router, Request $request)
{
$this->router = $router;
$this->request = $request;
}
/**
* #return \Zend\Router\RouteMatch
*/
public function __invoke()
{
return $this->router->match($this->request);
}
}
Create a Factory for this helper
namespace Application\View\Helper;
use Interop\Container\ContainerInterface;
use Zend\ServiceManager\Factory\FactoryInterface;
class RouteMatchFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$router = $container->get('router');
$request = $container->get('request');
return new RouteMatch($router, $request);
}
}
Call your Factory on your Module.php and create an Alias for it.
public function getViewHelperConfig()
{
return array(
'factories' => array(
RouteMatch::class => RouteMatchFactory::class,
),
'aliases' => array(
'routeMatch' => RouteMatch::class,
)
);
}
That's it... you have a RouteMatch Helper using the new ZF3 standards.
Bye!
In view you can use:
$this->getHelperPluginManager()->getServiceLocator()->get('request')->getUri()->getPath();
or
$this->getHelperPluginManager()->getServiceLocator()->get('request')->getUri()->toString();
I believe you can solve it by finding the action/controller names:
$controller = $this->getRequest()->getControllerName();
$action = $this->getRequest()->getActionName();
Once you know the action, you can have specific conditions to enable sections of the layout.
I view you can use
$this->getHelperPluginManager()->getServiceLocator()->get('Application')->getMvcEvent()->getRouteMatch()->getMatchedRouteName();
Additional information about "Rodrigo Boratto" post for integrating getRouteMatch in ZF3 (I can't comment because I have under 50 repo...)
In view helper file these line:
use Zend\Mvc\Router\RouteMatch as MvcRouteMatch;
use Zend\Mvc\Router\RouteStackInterface;
should be:
use Zend\Router\RouteMatch as MvcRouteMatch;
use Zend\Router\RouteStackInterface;
I don't know when they made that change but the files are in Zend\Router namespace.
P.S. I use composer if that matters.
My controller:
<?PHP
namespace SomeName\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
class SomeController extends AbstractActionController
{
public function getIdAction()
{
$id = $this->params()->fromRoute('id', 0);
return new ViewModel(array(
'id' => $id,
));
}
}
My Router:
<?php
return array(
'controllers' => array(
'invokables' => array(
'SomeName\Controller\Some' => 'SomeName\Controller\SomeController',
),
),
'router' => array(
'routes' => array(
'testId' => array(
'type' => 'segment',
'options' => array(
'route' => '/[:id]',
'constraints' => array(
'id' => '\d*',
),
'defaults' => array(
'controller' => 'SomeName\Controller\Some',
'action' => 'getId',
),
),
),
),
),
'view_manager' => array(
'template_path_stack' => array(
'album' => __DIR__ . '/../view',
),
),
);
At any view or layout, you are able to test route with this function:
<?php function itsRoute($routeName){
$flag = false;
if($this->serverUrl(true) == $this->url($route,[],['force_canonical'=>true]))){
$flag = true;
}
return $flag;
}

How to preserve session through all tests on phpunit?

I'm working on testing a shopping cart, checkout, payment process on Zend Framework with phpunit. I'm testing ShoppingCartController by adding products to cart, a ShoppingCart Model handles product additions by storing product id's in a Zend Session Namespace, and then in another test I want to test that the products were added. The same ShoppingCart Model retrieves a list of added products from the same Zend Session namespace variable.
The add product test looks like this and works well, and the var_dump($_SESSION) was added to debug and shows the products correctly:
public function testCanAddProductsToShoppingCart() {
$testProducts = array(
array(
"product_id" => "1",
"product_quantity" => "5"
),
array(
"product_id" => "1",
"product_quantity" => "3"
),
array(
"product_id" => "2",
"product_quantity" => "1"
)
);
Ecommerce_Model_Shoppingcart::clean();
foreach ($testProducts as $product) {
$this->request->setMethod('POST')
->setPost(array(
'product_id' => $product["product_id"],
'quantity' => $product["product_quantity"]
));
$this->dispatch($this->getRouteUrl("add_to_shopping_cart"));
$this->assertResponseCode('200');
}
$products = Ecommerce_Model_Shoppingcart::getData();
$this->assertTrue($products[2][0]["product"] instanceof Ecommerce_Model_Product);
$this->assertEquals($products[2][0]["quantity"],
"8");
$this->assertTrue($products[2][1]["product"] instanceof Ecommerce_Model_Product);
$this->assertEquals($products[2][1]["quantity"],
"1");
var_dump($_SESSION);
}
The second test attempts to retrieve the products by asking the model to do so, the var_dump($_SESSION) is null already at the beginning of the test. The session variables were reset, I want to find a way to preserve them, can anyone help?
public function testCanDisplayShoppingCartWidget() {
var_dump($_SESSION);
$this->dispatch($this->getRouteUrl("view_shopping_mini_cart"));
$this->assertResponseCode('200');
}
Sorry for pointing you in the wrong direction. Here is a way better way of achieving this, suggested by ashawley from #phpunit channel of irc.freenode.net:
<?php
# running from the cli doesn't set $_SESSION here on phpunit trunk
if ( !isset( $_SESSION ) ) $_SESSION = array( );
class FooTest extends PHPUnit_Framework_TestCase {
protected $backupGlobalsBlacklist = array( '_SESSION' );
public function testOne( ) {
$_SESSION['foo'] = 'bar';
}
public function testTwo( ) {
$this->assertEquals( 'bar', $_SESSION['foo'] );
}
}
?>
== END UPDATE
In function tearDown(): copy $_SESSION to a class attribute and
In function setUp(): copy the class attribute to $_SESSION
For example, this test fails when you remove the functions setUp() and tearDown() methods:
<?php
# Usage: save this to test.php and run phpunit test.php
# running from the cli doesn't set $_SESSION here on phpunit trunk
if ( !isset( $_SESSION ) ) $_SESSION = array( );
class FooTest extends PHPUnit_Framework_TestCase {
public static $shared_session = array( );
public function setUp() {
$_SESSION = FooTest::$shared_session;
}
public function tearDown() {
FooTest::$shared_session = $_SESSION;
}
public function testOne( ) {
$_SESSION['foo'] = 'bar';
}
public function testTwo( ) {
$this->assertEquals( 'bar', $_SESSION['foo'] );
}
}
Also there is a backupGlobals feature but it doesn't work for me. You should try it thought, maybe it works on stable PHPUnit.
that's a very ugly of doing that. The right way should be using dependency injection.
That implies changing your source code to use this class instead of sessions directly:
class Session
{
private $adapter;
public static function init(SessionAdapter $adapter)
{
self::$adapter = $adapter;
}
public static function get($var)
{
return self::$adapter->get($var);
}
public static function set($var, $value)
{
return self::$adapter->set($var, $value);
}
}
interface SessionAdapter
{
public function get($var);
public function set($var, $value);
}
Additional information:
http://community.sitepoint.com/t/phpunit-testing-cookies-and-sessions/36557/2
Using PHPUnit to test cookies and sessions, how?
You can also just create a random session id for your PHPUnit test, and then make sure you pass this session id in a cookie in every further call you make. With Curl, you would use the CURLOPT_COOKIE option and set it to 'PHPSESSID=thesessionidofyourunittest' as such:
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_COOKIE, 'PHPSESSID=thesessionidofyourunittest');
I explained more in detail and with an example in this stackoverflow answer.

Resources