Yii2 iterate dataprovider with relations - iterator

I need to know how to iterate through a Yii2 dataprovider with relations.
I have a model Asset that has a relationship to another model Make.
class Equipment extends \yii\db\ActiveRecord
{
// ...
public function getMake() {
return $this->hasOne(Make::className(), ['make_id' => 'make_id']);
}
}
In my controller, I have 2 functions, one to render a grid, and another to export the data to a CSV file.
public function actionEquipment()
{
$searchModel = new EquipmentSearch();
$dataProvider = $searchModel->search(Yii::$app->request->queryParams);
// Store the search model in session
Yii::$app->session->set('exportEquipmentModel', $searchModel);;
// Render grid
return $this->render('equipment', [
'searchModel' => $searchModel,
'dataProvider' => $dataProvider,
]);
}
public function actionExportequipment()
{
header('Content-type: text/csv');
header('Content-Disposition: attachment; filename="equipment_report-' . date('YmdHi') .'.csv"');
$searchModel = new EquipmentSearch();
$dataProvider = $searchModel->search(Yii::$app->request->queryParams);
// Use the search model from session, or get all
if(\Yii::$app->session->get('exportEquipmentModel')) {
$searchModel = Yii::$app->session->get('exportEquipmentModel');
$dataProvider = $searchModel->search(false);
$dataProvider->setPagination(false);
}
// csv header
$columns =[
'serial_number',
'Make',
'Model',
];
echo implode(",", $columns) . " \r\n";
// csv data
foreach ($dataProvider->getModels() as $data) {
$row =[
$data['serial_number'],
// TODO: I need to resolve the relation here
$data['make_id'] // Works
// $data->make_id // Works
// $data->make->description // Does not work
// $data['make_id']['description'], // Does not work
$data['model_id']
];
echo implode(",", $row) . " \r\n";
}
}
As can be see from the comments in the code, various forms of getting the make->description field is not yielding results.

Lesson learned : Always check for errors.
My code was correct. However, the data was not what I expected. The dba deleted some of the makes and models after setting off foreign key checks. Therefore, $data->make.
I therefore changed the line
$data->make->description
to
!empty($data->make)?$data->make->description:'Not Set',

Related

Yii1 - sanitize GET parameter in the before action

I am trying to find if it possible to use the beforeAction in the controller to access the injected parameter.
For example, every action in the controller accepts type parameter, which I need to sanitize:
public function actionGetCustomPaymentsChunk($type) {
$type = TextUtil::sanitizeString($type);
// Get filter data
$filter = FilterHelper::getFilterData();
// Initialize components
$totalCostPayableComponent = new TotalCostPayableComponent($filter);
// Get chunk data
$data = $totalCostPayableComponent->getCustomPaymentChunk($type);
// Return content to client side
$this->renderPartial('/partials/_payable_cost_chunk', array(
'data' => $data,
'route' => 'totalCostPayable/getCustomPaymentsGrid',
'type' => $type,
'paymentType' => 'Custom',
));
}
}
Is this possible to do (I am trying to avoid repetition)?
You should be able to, what did you try?
Assuming the $type is passed via GET, you can modify it in a beforeAction and the modified value will be applied to the target action with a request like
http://myhost.com/route/test?type=something
using the below, $type = "foo" in any action in this controller.
protected function beforeAction($action)
{
if (isset($_GET['type'])) {
$_GET['type'] = 'foo';
}
...
return parent::beforeAction($action);
}
public function actionTest($type)
{
# $type === 'foo'
...
}
Change the manipulation in beforeAction to satisfy whatever your requirements are.

Make an ajax request from a Prestashop module

I am making a module and I need to make an ajax request, with JSON response if possible, how can i do this ?
I don't understand really well the structure of Prestashop 1.7 on this.
Thanks !
This is pretty simple, you just have to make the controller with Prestashop's standards then link it to your frontend Javascript.
Name a php file like this : ./modules/modulename/controllers/front/ajax.php
Then put inside :
<?php
// Edit name and class according to your files, keep camelcase for class name.
require_once _PS_MODULE_DIR_.'modulename/modulename.php';
class ModuleNameAjaxModuleFrontController extends ModuleFrontController
{
public function initContent()
{
$module = new ModuleName;
// You may should do some security work here, like checking an hash from your module
if (Tools::isSubmit('action')) {
// Usefull vars derivated from getContext
$context = Context::getContext();
$cart = $context->cart;
$cookie = $context->cookie;
$customer = $context->customer;
$id_lang = $cookie->id_lang;
// Default response with translation from the module
$response = array('status' => false, "message" => $module->l('Nothing here.'));
switch (Tools::getValue('action')) {
case 'action_name':
// Edit default response and do some work here
$response = array('status' => true, "message" => $module->l('It works !'));
break;
default:
break;
}
}
// Classic json response
$json = Tools::jsonEncode($response);
echo $json;
die;
// For displaying like any other use this method to assign and display your template placed in modules/modulename/views/template/front/...
// Just put some vars in your template
// $this->context->smarty->assign(array('var1'=>'value1'));
// $this->setTemplate('template.tpl');
// For sending a template in ajax use this method
// $this->context->smarty->fetch('template.tpl');
}
}
?>
In your Module Hooks, you need to bring access to the route in JS, so we basicaly make a variable :
// In your module PHP
public function hookFooter($params)
{
// Create a link with the good path
$link = new Link;
$parameters = array("action" => "action_name");
$ajax_link = $link->getModuleLink('modulename','controller', $parameters);
Media::addJsDef(array(
"ajax_link" => $ajax_link
));
}
On the frontend side, you just call it like this in a JS file (with jQuery here) :
// ajax_link has been set in hookfooter, this is the best way to do it
$(document).ready(function(){
$.getJSON(ajax_link, {parameter1 : "value"}, function(data) {
if(typeof data.status !== "undefined") {
// Use your new datas here
console.log(data);
}
});
});
And voila, you have your ajax ready to use controller

Doctrine 2 issue inside Zf2 Navigation - strange issue

I can use doctrine from all my controller without any problem
$em = $this->getServiceLocator()->get("doctrine.entitymanager.orm_default");
$newsItems = $em->getRepository('Website\Entity\News')->findAll();
foreach($newsItems as $item)
// do stuff here
Preamble: if I use Zend\Db\Adapter instead of Doctrine the following work as aspected !
I'm inside a MyNavigationObject that was instanciated by MyNavigationFactory.
I have a mysql table called mainmenu with 4 records: home,news,company,contact
I would like to do my own navigation with Doctrine-Orm.
Also I have an entity called Mainmenu.
I can call the entityManager and the results is composed with 4 records but 4 times the records number 1. Incredible !
this is the code:
protected function getPages(ServiceLocatorInterface $serviceLocator)
{
if (null === $this->pages) {
$em = $serviceLocator->get("doctrine.entitymanager.orm_default");
$menuItems = $em->getRepository('Website\Entity\Mainmenu')->findAll();
//return default key
$configuration['navigation'][$this->getName()] = array();
foreach ($menuItems as $menuItem)
{
$configuration['navigation'][$this->getName()][$menuItem->getName()] = array(
'label' => $menuItem->getLabel(),
'route' => $menuItem->getRoute(),
'controller' => $menuItem->getController(),
);
}
if (!isset($configuration['navigation'])) {
throw new \Exception\InvalidArgumentException('Could not find navigation configuration key');
}
if (!isset($configuration['navigation'][$this->getName()])) {
throw new Exception\InvalidArgumentException(sprintf(
'Failed to find a navigation container by the name "%s"',
$this->getName()
));
}
// refer to Mvc::Application object not to modulename
$application = $serviceLocator->get('Application');
$routeMatch = $application->getMvcEvent()->getRouteMatch();
$router = $application->getMvcEvent()->getRouter();
$pages = $this->getPagesFromConfig($configuration['navigation'][$this->getName()]);
$this->pages = $this->injectComponents($pages, $routeMatch, $router);
}
return $this->pages;
Can anyone provide suggestions or reproduce the issue ?
bye

Authentication with 2 different tables

I need to create a new "auth" config with another table and users. I have one table for the "admin" users and another table for the normal users.
But how can I create another instance of Auth with a different configuration?
While trying to solve this problem myself, I found a much simpler way. I basically created a custom ServiceProvider to replace the default Auth one, which serves as a factory class for Auth, and allows you to have multiple instances for multiple login types. I also stuck it all in a package which can be found here: https://github.com/ollieread/multiauth
It's pretty easy to use really, just replace the AuthServiceProvider in app/config/app.php with Ollieread\Multiauth\MultiauthServiceProvider, then change app/config/auth.php to look something like this:
return array(
'multi' => array(
'account' => array(
'driver' => 'eloquent',
'model' => 'Account'
),
'user' => array(
'driver' => 'database',
'table' => 'users'
)
),
'reminder' => array(
'email' => 'emails.auth.reminder',
'table' => 'password_reminders',
'expire' => 60,
),
);
Now you can just use Auth the same way as before, but with one slight difference:
Auth::account()->attempt(array(
'email' => $attributes['email'],
'password' => $attributes['password'],
));
Auth::user()->attempt(array(
'email' => $attributes['email'],
'password' => $attributes['password'],
));
Auth::account()->check();
Auth::user()->check();
It also allows you to be logged in as multiple user types simultaneously which was a requirement for a project I was working on. Hope it helps someone other than me.
UPDATE - 27/02/2014
For those of you that are just coming across this answer, I've just recently added support for reminders, which can be accessed in the same factory style way.
You can "emulate" a new Auth class.
Laravel Auth component is basically the Illuminate\Auth\Guard class, and this class have some dependencies.
So, basically you have to create a new Guard class and some facades...
<?php
use Illuminate\Auth\Guard as AuthGuard;
class CilentGuard extends AuthGuard
{
public function getName()
{
return 'login_' . md5('ClientAuth');
}
public function getRecallerName()
{
return 'remember_' . md5('ClientAuth');
}
}
... add a ServiceProvider to initialize this class, passing it's dependencies.
<?php
use Illuminate\Support\ServiceProvider;
use Illuminate\Auth\EloquentUserProvider;
use Illuminate\Hashing\BcryptHasher;
use Illuminate\Auth\Reminders\PasswordBroker;
use Illuminate\Auth\Reminders\DatabaseReminderRepository;
use ClientGuard;
use ClientAuth;
class ClientServiceProvider extends ServiceProvider
{
public function register()
{
$this->registerAuth();
$this->registerReminders();
}
protected function registerAuth()
{
$this->registerClientCrypt();
$this->registerClientProvider();
$this->registerClientGuard();
}
protected function registerClientCrypt()
{
$this->app['client.auth.crypt'] = $this->app->share(function($app)
{
return new BcryptHasher;
});
}
protected function registerClientProvider()
{
$this->app['client.auth.provider'] = $this->app->share(function($app)
{
return new EloquentUserProvider(
$app['client.auth.crypt'],
'Client'
);
});
}
protected function registerClientGuard()
{
$this->app['client.auth'] = $this->app->share(function($app)
{
$guard = new Guard(
$app['client.auth.provider'],
$app['session.store']
);
$guard->setCookieJar($app['cookie']);
return $guard;
});
}
protected function registerReminders()
{
# DatabaseReminderRepository
$this->registerReminderDatabaseRepository();
# PasswordBroker
$this->app['client.reminder'] = $this->app->share(function($app)
{
return new PasswordBroker(
$app['client.reminder.repository'],
$app['client.auth.provider'],
$app['redirect'],
$app['mailer'],
'emails.client.reminder' // email template for the reminder
);
});
}
protected function registerReminderDatabaseRepository()
{
$this->app['client.reminder.repository'] = $this->app->share(function($app)
{
$connection = $app['db']->connection();
$table = 'client_reminders';
$key = $app['config']['app.key'];
return new DatabaseReminderRepository($connection, $table, $key);
});
}
public function provides()
{
return array(
'client.auth',
'client.auth.provider',
'client.auth.crypt',
'client.reminder.repository',
'client.reminder',
);
}
}
In this Service Provider, I put some example of how to create a 'new' password reminder component to.
Now you need to create two new facades, one for authentication and one for password reminders.
<?php
use Illuminate\Support\Facades\Facade;
class ClientAuth extends Facade
{
protected static function getFacadeAccessor()
{
return 'client.auth';
}
}
and...
<?php
use Illuminate\Support\Facades\Facade;
class ClientPassword extends Facade
{
protected static function getFacadeAccessor()
{
return 'client.reminder';
}
}
Of course, for password reminders, you need to create the table in database, in order to work. In this example, the table name should be client_reminders, as you can see in the registerReminderDatabaseRepository method in the Service Provider. The table structure is the same as the original reminders table.
After that, you can use your ClientAuth the same way you use the Auth class. And the same thing for ClientPassword with the Password class.
ClientAuth::gust();
ClientAuth::attempt(array('email' => $email, 'password' => $password));
ClientPassword::remind($credentials);
Don't forget to add your service provider to the service providers list in the app/config/app.php file.
UPDATE:
If you are using Laravel 4.1, the PasswordBroker doesn't need the Redirect class anymore.
return new PasswordBroker(
$app['client.reminder.repository'],
$app['client.auth.provider'],
$app['mailer'],
'emails.client.reminder' // email template for the reminder
);
UPDATE 2
Laravel 5.2 just introduced multi auth, so this is no longer needed in this version.
Ok, I had the same problem and here is how I solved it:
actually in laravel 4 you can simply change the auth configs at runtime so to do the trick you can simply do the following in your App::before filter:
if ($request->is('admin*'))
{
Config::set('auth.model', 'Admin');
}
this will make the Auth component to use th Admin model when in admin urls. but this will lead to a new problem, because the login session key is the same if you have two users in your admins and users table with the same id you will be able to login to the admin site if you have logged in before as a regular user! so to make the two different authetications completely independent I did this trick:
class AdminGuard extends Guard
{
public function getName()
{
return 'admin_login_'.md5(get_class($this));
}
public function getRecallerName()
{
return 'admin_remember_'.md5(get_class($this));
}
}
Auth::extend('eloquent.admin', function()
{
return new AdminGuard(new EloquentUserProvider(new BcryptHasher, 'Admin'), App::make('session.store'));
});
and change the App::before code to:
if ($request->is('admin*'))
{
Config::set('auth.driver', 'eloquent.admin');
Config::set('auth.model', 'Admin');
}
you can see that I made a new auth driver and rewrote some methods on the Guard class so it will generate different session keys for admin site. then I changed the driver for the admin site. good luck.
I had the same problem yesterday, and I ended up creating a much simpler solution.
My requirements where 2 different tables in two different databases. One table was for admins, the other was for normal users. Also, each table had its own way of hashing. I ended up with the following (Code also available as a gist on Github: https://gist.github.com/Xethron/6790029)
Create a new UserProvider. I called mine MultiUserProvider.php
<?php
// app/libraries/MultiUserProvider.php
use Illuminate\Auth\UserProviderInterface,
Illuminate\Auth\UserInterface,
Illuminate\Auth\GenericUser;
class MultiUserProvider implements UserProviderInterface {
protected $providers;
public function __construct() {
// This should be moved to the config later...
// This is a list of providers that can be used, including
// their user model, hasher class, and hasher options...
$this->providers = array(
'joomla' => array(
'model' => 'JoomlaUser',
'hasher' => 'JoomlaHasher',
)
'another' => array(
'model' => 'AnotherUser',
'hasher' => 'AnotherHasher',
'options' => array(
'username' => 'empolyee_number',
'salt' => 'salt',
)
),
);
}
/**
* Retrieve a user by their unique identifier.
*
* #param mixed $identifier
* #return \Illuminate\Auth\UserInterface|null
*/
public function retrieveById($identifier)
{
// Returns the current provider from the session.
// Should throw an error if there is none...
$provider = Session::get('user.provider');
$user = $this->createModel($this->providers[$provider]['model'])->newQuery()->find($identifier);
if ($user){
$user->provider = $provider;
}
return $user;
}
/**
* Retrieve a user by the given credentials.
*
* #param array $credentials
* #return \Illuminate\Auth\UserInterface|null
*/
public function retrieveByCredentials(array $credentials)
{
// First we will add each credential element to the query as a where clause.
// Then we can execute the query and, if we found a user, return it in a
// Eloquent User "model" that will be utilized by the Guard instances.
// Retrieve the provider from the $credentials array.
// Should throw an error if there is none...
$provider = $credentials['provider'];
$query = $this->createModel($this->providers[$provider]['model'])->newQuery();
foreach ($credentials as $key => $value)
{
if ( ! str_contains($key, 'password') && ! str_contains($key, 'provider'))
$query->where($key, $value);
}
$user = $query->first();
if ($user){
Session::put('user.provider', $provider);
$user->provider = $provider;
}
return $user;
}
/**
* Validate a user against the given credentials.
*
* #param \Illuminate\Auth\UserInterface $user
* #param array $credentials
* #return bool
*/
public function validateCredentials(UserInterface $user, array $credentials)
{
$plain = $credentials['password'];
// Retrieve the provider from the $credentials array.
// Should throw an error if there is none...
$provider = $credentials['provider'];
$options = array();
if (isset($this->providers[$provider]['options'])){
foreach ($this->providers[$provider]['options'] as $key => $value) {
$options[$key] = $user->$value;
}
}
return $this->createModel($this->providers[$provider]['hasher'])
->check($plain, $user->getAuthPassword(), $options);
}
/**
* Create a new instance of a class.
*
* #param string $name Name of the class
* #return Class
*/
public function createModel($name)
{
$class = '\\'.ltrim($name, '\\');
return new $class;
}
}
Then, I told Laravel about my UserProvider by adding the following lines to the top of my app/start/global.php file.
// app/start/global.php
// Add the following few lines to your global.php file
Auth::extend('multi', function($app) {
$provider = new \MultiUserProvider();
return new \Illuminate\Auth\Guard($provider, $app['session']);
});
And then, I told Laravel to use my user provider instead of EloquentUserProvider in app/config/auth.php
'driver' => 'multi',
Now, when I authenticate, I do it like so:
Auth::attempt(array(
'email' => $email,
'password' => $password,
'provider'=>'joomla'
)
)
The class would then use the joomlaUser model, with the joomlaHasher, and no options for the hasher... If using 'another' provider, it will include options for the hasher.
This class was built for what I required but can easily be changed to suite your needs.
PS: Make sure the autoloader can find MultiUserProvider, else it won't work.
I'm using Laravel 5 native auth to handle multiple user tables...
It's not difficult, please check this Gist:
https://gist.github.com/danielcoimbra/64b779b4d9e522bc3373
UPDATE: For Laravel 5, if you need a more robust solution, try this package:
https://github.com/sboo/multiauth
Daniel

Customizing Xmultiselect in Yii

I am not able to make the left list of multiselect widget in empty mode on load. It shows error when I set null value to the left list. This is my code:
$this->widget('ext.widgets.multiselects.XMultiSelects', array(
'leftTitle' => '',
'leftName' => 'Certificate[selected][]',
'leftList' => SpecificCertification::model()->findCertificate(),// here I need to make the list empty
'rightTitle' => '',
'rightName' => 'Certificate[all][]',
'rightList' => SpecificCertification::model()->findCertificates(),
'size' => 10,
));
How can I make the left list empty ?
You need to open file widget XMultiSelects.php and modify it to fit your need
public function init()
{
/* Comment out the below validation
if(!isset($this->leftList))
{
throw new CHttpException(500,'"leftList" have to be set!');
}
if(!isset($this->rightList))
{
throw new CHttpException(500,'"rightList" have to be set!');
}
*/
}
Add validation for leftList and rightList such as below
if($this->leftList){
foreach($this->leftList as $value=>$label)
{
echo "<option value=\"{$value}\">{$label}</option>\n";
}
}
and
if($this->rightList){
foreach($this->rightList as $value=>$label)
{
echo "<option value=\"{$value}\">{$label}</option>\n";
}
}
After then, you can set null for them like what you did