I am wondering if the Yii framework uses the defined Labels atttributes in a multilanguage process.
So if I have
public function attributeLabels() {
return array(
'email' => 'Email address',
'rememberMe' => 'Remember me next time',
'password' => 'Password'
);
}
Will this be translated to some other language? Or do I have to do something manually to work?
Yii doesn't translate it automatically. You need to use the i18n built-in in Yii and manually add the translations and modify the labels as follow:
public function attributeLabels() {
return array(
'email' => Yii::t('account','Email address'),
'rememberMe' => Yii::t('account','Remember me next time'),
'password' => Yii::t('account','Password')
);
}
You can get more info about internationalize you app at Quick Start to Internationalize your application in Yii Framework
Well, you can use the built-in translation system to translate your attribute labels, for example:
public function attributeLabels() {
return array(
'email' => Yii::t('myapp','Email address'),
);
}
and then in messages folder create a directory for your language, for example:
messages\dk\myapp.php
myapp.php should return the translation, for example:
return array('Email address' => 'TRANSLATION...');
Next you need to set the language of your application in the config file for instance.
'language' => 'dk',
I had assumed that Yii AR would run getAttributeLabel through Yii::t. Not wanting to do all that copy and pasting on dozens of models, I added this function to my intermediate AR class:
public function getAttributeLabel($attribute)
{
$baseLabel = parent::getAttributeLabel($attribute);
return Yii::t(get_called_class(), $baseLabel);
}
Now to write a shell command that loops through the models and adds their labels to the message file.
Related
I don't know if this is a bug or a mistake on my end but basically I followed the Yii2 documentation to setup i18n translations for modules. The following snippet is directly copy pasted from the Yii2 guide.
public function init()
{
parent::init();
$this->registerTranslations();
}
public function registerTranslations()
{
Yii::$app->i18n->translations['modules/users/*'] = [
'class' => 'yii\i18n\PhpMessageSource',
'sourceLanguage' => 'en',
'basePath' => '#app/modules/users/messages',
];
}
public static function t($category, $message, $params = [], $language = null)
{
return Yii::t('modules/users/' . $category, $message, $params, $language);
}
According to the guide I should call it like this:
Module::t('validation', 'your custom validation message')
However, Yii2 tries to load the the 'validation.php' from the wrong location. This is the output of the debugger:
The message file for category 'modules/users/validation' does not exist: /Applications/MAMP/htdocs/.../domains/localhost/public_html/.../backend/modules/users/messages/en/modules/users/validation.php
From what I understand, it should be looking for modules/users/message/<lang>/validation.php instead, which makes a lot more sense than what it is looking for right now.
What am I doing wrong?
Thank you in advance.
You should simply add a filemap param, e.g. :
public function registerTranslations()
{
Yii::$app->i18n->translations['modules/users/*'] = [
'class' => 'yii\i18n\PhpMessageSource',
'sourceLanguage' => 'en',
'basePath' => '#app/modules/users/messages',
'fileMap' => [
'modules/users/validation' => 'validation.php',
],
];
}
Read more : http://www.yiiframework.com/doc-2.0/guide-tutorial-i18n.html#translating-module-messages
EDIT : As stated in Yii2 guide, if you want to remove filemap, your validation.php file should be in modules/users/messages/[lang]/modules/users/validation.php.
Instead of configuring fileMap you can rely on convention which is to use the category name as the file name (e.g. category app/error will result in the file name app/error.php under the basePath.
I am using Doctrine 2 in my Zend Framework 2 Project. I have now created a Form and create one of my Dropdowns with Values from the Database. My Problem now is that I want to change which values are used and not the one which I get back from my repository. Okay, here some Code for a better understanding:
$this->add(
array(
'type' => 'DoctrineModule\Form\Element\ObjectSelect',
'name' => 'county',
'options' => array(
'object_manager' => $this->getObjectManager(),
'label' => 'County',
'target_class' => 'Advert\Entity\Geolocation',
'property' => 'county',
'is_method' => true,
'empty_option' => '--- select county ---',
'value_options'=> function($targetEntity) {
$values = array($targetEntity->getCounty() => $targetEntity->getCounty());
return $values;
},
'find_method' => array(
'name' => 'getCounties',
),
),
'allow_empty' => true,
'required' => false,
'attributes' => array(
'id' => 'county',
'multiple' => false,
)
)
);
I want to set the value for my Select to be the County Name and not the ID. I thought that I would need the 'value_options' which needs an array. I tried it like above, but get the
Error Message: Argument 1 passed to Zend\Form\Element\Select::setValueOptions() must be of the type array, object given
Is this possible at all?
I was going to suggest modifying your code, although after checking the ObjectSelect code i'm surprised that (as far as I can tell) this isn't actually possible without extending the class. This is because the value is always generated from the id.
I create all form elements using factories (without the ObjectSelect), especially complex ones that require varied lists.
Alternative solution
First create a new method in the Repository that returns the correct array. This will allow you to reuse that same method should you need it anywhere else (not just for forms!).
class FooRepository extends Repository
{
public function getCounties()
{
// normal method unchanged, returns a collection
// of counties
}
public function getCountiesAsArrayKeyedByCountyName()
{
$counties = array();
foreach($this->getCounties() as $county) {
$counties[$county->getName()] = $county->getName();
}
return $counties;
}
}
Next create a custom select factory that will set the value options for you.
namespace MyModule\Form\Element;
use Zend\Form\Element\Select;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\ServiceManager\FactoryInterface;
class CountiesByNameSelectFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $formElementManager)
{
$element = new Select;
$element->setValueOptions($this->loadValueOptions($formElementManager));
// set other select options etc
$element->setName('foo')
->setOptions(array('foo' => 'bar'));
return $element;
}
protected function loadValueOptions(ServiceLocatorInterface $formElementManager)
{
$serviceManager = $formElementManager->getServiceLocator();
$repository = $serviceManager->get('DoctrineObjectManager')->getRepository('Foo/Entity/Bar');
return $repository->getCountiesAsArrayKeyedByCountyName();
}
}
Register the new element with the service manager by adding a new entry in Module.php or module.config.php.
// Module.php
public function getFormElementConfig()
{
return array(
'factories' => array(
'MyModule\Form\Element\CountiesByNameSelect'
=> 'MyModule\Form\Element\CountiesByNameSelectFactory',
),
);
}
Lastly change the form and remove your current select element and add the new one (use the name that you registered with the service manager as the type key)
$this->add(array(
'name' => 'counties',
'type' => 'MyModule\Form\Element\CountiesByNameSelect',
));
It might seem like a lot more code (because it is) however you will benefit from it being a much clearer separation of concerns and you can now reuse the element on multiple forms and only need to configure it in one place.
CakePHP v.2.4...
I'm following this documentation trying to set up the Auth component to use my custom password hashing class:
App::uses('PHPassPasswordHasher', 'Controller/Component/Auth');
class AppController extends Controller {
// auth needed stuff
public $components = array(
'Session',
'Cookie',
'Auth' => array(
'authenticate' => array(
'Form' => array(
'fields' => array('username'=>'email', 'password'=>'password'),
'passwordHasher' => 'PHPass'
)
),
Inside my UsersController::login() I debug the return from $this->Auth->login(); and it always returns false, even when I submit the correct email / password.
(NOTE: It looks strange to me that the login() takes no parameters, but the docs seem to imply that it looks into the the request data automatically. And this would make sense if my configurations aren't correctly causing it to check the User.email field instead username.)
The post data from the submitted login form looks like this:
array(
'User' => array(
'password' => '*****',
'email' => 'whatever#example.com'
)
)
What am I missing?
Update2
I'm starting to suspect that the default hashing algorithm is getting used instead of my custom class. I tried to match the examples in the docs but they're quite vague on how to do this.
Here's the contents of app/Controller/Component/Auth/PHPassPasswordHasher.php
<?php
App::import('Vendor', 'PHPass/class-phpass'); //<--this exists and defines PasswordHash class
class PHPassPasswordHasher extends AbstractPasswordHasher {
public function hash($password) {
$hasher = new new PasswordHash( 8, true );
return $hasher->HashPassword($password);
}
public function check($password, $hashedPassword) {
debug('PHPassHasher'); die('Using custom hasher'); //<--THIS NEVER HAPPENS!
$hasher = new new PasswordHash( 8, true );
return $hasher->CheckPassword($password, $hashedPassword);
}
}
AHA! The debug() never appears... so I'm pretty sure the problem is with my custom hasher configuration(s).
Update3
Additional clue: I experimented by setting various default hashing algorithms (Ex: "Simple", "Blowfish") and creating users. The hashes which show up in the DB are all the same which tells me that my config settings are getting ignored completely.
Update4
I debugged $this->settings inside the constructor of /lib/Cake/Controller/Component/Auth/BaseAuthenticate.php and my custom hasher settings are in there:
array(
'fields' => array(
'password' => 'password',
'username' => 'email'
),
'userModel' => 'User',
'scope' => array(),
'recursive' => (int) 0,
'contain' => null,
'passwordHasher' => 'PHPass'
)
You need to rename your password hasher class to have the suffix "PasswordHasher", and only provide the non-suffixed name in the 'className' argument.
eg:
<?php
App::import('Vendor', 'PHPass/class-phpass'); //<--this exists and defines PasswordHash class
class PHPassHasherPasswordHasher extends AbstractPasswordHasher {
// functions
}
The example from the docs sets the classname to 'Simple', which then loads 'SimplePasswordHasher'.
You might find that having a name of PHPassHasherPasswordHasher is a bit silly, it's up to you what you want to call it. Perhaps PHPassPasswordHasher might be a bit more appropriate (and then use the classname argument 'PHPass').
EDIT: It seems as if Cake has issues when multiple capital letters are used one after the other (eg. PHPass), so the right way to do it is to change the password hasher class to the following:
<?php
App::import('Vendor', 'PHPass/class-phpass'); //<--this exists and defines PasswordHash class
class PhpassPasswordHasher extends AbstractPasswordHasher {
// functions
}
... and make sure the filename matches the classname: PhpassPasswordHasher.php.
Thanks to SDP for the discussion, I learnt something today!
According to the docs:
To configure different fields for user in $components array:
// Pass settings in $components array
public $components = array(
'Auth' => array(
'authenticate' => array(
'Form' => array(
'fields' => array(
'username' => 'email',
'password' => 'password'
)
)
)
)
);
Source
I finally got this working. We were on the right track by renaming the file/class to comply with Cake conventions. I had to go one step further and change the capitalization as well:
PHPassPasswordHasher.php --> PhpassPasswordHasher.php
class PHPassPasswordHasher... --> class PhpassPasswordHasher...
Phew!
ps: Many many thanks to #Ben Hitchcock for support on this.
In ZF2, I've overridden the Text element with my own (call it My\Form\Element\Text). Now I want to make it so that when I add a text element to the form, it defaults to my overridden class and not Zend\Form\Element\Text:
$this->add([
'type' => 'text',
'name' => 'to',
]);
I know that I could use 'type' => 'My\Form\Element\Text' instead of just 'type' => 'text', but I'm trying to find out if I can avoid that and just use the custom element by default.
I've tried both of these techniques:
module.config.php
return [
'form_elements' => [
'invokables' => [
'text' => 'My\Form\Element\Text',
],
],
];
Module.php
class Module {
public function getFormElementConfig() {
return [
'invokables' => [
'text' => 'My\Form\Element\Text',
],
];
}
}
Neither of these worked (still getting an instance of Zend\Form\Element\Text). Is there some other way of registering the element so that the Zend\Form\Factory::create() method creates an instance of my custom element instead of the Zend version?
Although your config is correct, there are a couple of gotchas to be aware of when using custom elements, detailed in the docs here
Catch 1
If you are creating your form class by extending Zend\Form\Form, you must not add the custom element in the __construct-or, but rather in the init() method
Catch 2
You must not directly instantiate your form class, but rather get an instance of it through the Zend\Form\FormElementManager
Hello (sorry for my english...)
I got an aplication in Yii. I choose diffrent databases depending on $_GET['project']. My urls looks like index.php?r=controler/action&project=MyProject.
But i have to add &project=.. to every single link on my site, how can i make Yii do it automatically?
If you are using CUrlManager::createUrl() (or one of the other createUrl() variants) to create your links, you could override it in your own custom UrlManager:
class UrlManager extends CUrlManager {
public function createUrl($route, $params=array(), $ampersand='&') {
isset($params['project']) || $params['project'] = 'MyProject';
return parent::createUrl($route, $params, $ampersand);
}
}
Then in your config be sure to use your own custom UrlManager class:
return array(
...
'components' => array(
'urlManager' => array(
'class' => 'UrlManager',
),
),
...
);