Yii2 multilingual slug and language switcher that will "respect" translated slug's - yii2

The problem is - I can't figure out how to switch to translated slug:
I've implemented multilingual slugs using lav45/yii2-translated-behavior.
Database is quite simple:
Portfolio model:
id | created_at | is_active
PortfolioLang model:
portfolio_id | lang_id | title | content
So far - so good. I have a actionView that calls out translated content as follows:
protected function findModelBySlug($slug)
{
if (($model = Portfolio::findOne(['slug' => $slug])) !== null) {
return $model;
} else {
throw new NotFoundHttpException();
}
}
public function actionView($_lang, $slug)
{
$portfolioLang = PortfolioLang::findOne(['lang_id' => $_lang,'slug' => $slug]);
$model = $portfolioLang->portfolio;
$pictures = PortfolioPicture::find()->where(['portfolio_id' => $model->id])->orderBy(['sorting'=>SORT_ASC])->all();
return $this->render('view', [
'model' => $model,
'picture' => $pictures,
'langList' => Lang::getList(),
]);
}
Must note, that this is working, "until" it try to switch between languages while I'm opened the entry.
I was using example code from here: https://github.com/lav45/yii2-translated-behavior-demo/blob/master/frontend/components/LangHelper.php
I guess that I need something else than a sample code (it looks like it works only for current ID's for existing entries). Could you, someone, give me some hints for the next step?
Thanks in advance!

Related

Having problems in the store method Laravel-Eloquent

Been at this for a very long time and I can't get it right. Would be really thankful for some help. Thanks in advance!
Im trying to build a little clone of reddit some learning purpose.
I think these models is correct for what I'm trying to do. Im able to save a Subreddit to DB with user_id. But my problem is that I can't post to the post table since it's telling me it cannot find the subreddits_id column. Im seems like the method im trying to call should be working but it doesnt.
protected $fillable = ['title', 'link','content', 'post_picture', 'user_id', 'subreddit_id'];
//Functions
public function user () {
return $this->belongsTo(User::class);
}
public function subreddit() {
return $this->belongsTo(Subreddit::class);
}
public function comments () {
return $this->hasMany(Comment::class);
}
Above is my Post model
protected $guarded = [];
//Functions
public function user () {
return $this->belongsTo(User::class);
}
public function posts () {
return $this->hasMany(Post::class);
}
public function comments () {
return $this->hasMany(Comment::class);
}
Above is my Subreddit Model
public function posts () {
return $this->hasMany(Post::class);
}
public function subreddit() {
return $this->hasMany(Subreddit::class);
}
public function commments () {
return $this->hasMany(Comment::class);
}
Above is my User model
I think these models is correct for what I'm trying to do. Im able to save a Subreddit to DB with user_id. But my problem is that I can't post to the post table since it's telling me it cannot find the subreddits_id column. Im seems like the method im trying to call should be working but it doesnt.
The Store method looks like this:
public function store(Request $request)
{ $data = request()->validate([
'title' => 'required',
'link' => 'required',
'content' => 'required',
]);
$post = auth()->user()->posts()->create($data);
return redirect('/home');
}
I'm getting this error General error: 1364 Field 'subreddit_id' doesn't have a default value (SQL: insert into posts (title, link, content, user_id, updated_at, created_at) values (awe, ew, we, 2, 2020-04-01 17:41:29, 2020-04-01 17:41:29))
your $data only takes the parameters you specify for validation, But you did not include subreddit_id, It should be like this:
public function store(Request $request)
{ $data = request()->validate([
'title' => 'required',
'link' => 'required',
'content' => 'required',
'subreddit_id' => 'required|exist:subreddits,id'
]);
$post = auth()->user()->posts()->create($data);
return redirect('/home');
}

OctoberCMS: How to maintain a two-way friendship relation?

I'm extending the rainlab.user plugin to allow each user to have friends via a simple intermediate table with the following fields:
user_id
friend_id
status
I've extended the User model:
use RainLab\User\Models\User as FrontUser;
FrontUser::extend(function($model) {
$model->belongsToMany['friends']=[
'RainLab\User\Models\User',
'table' => 'meysam_social_friends',
'pivot' => ['status'],
'pivotModel' => 'Meysam\Social\Models\FriendsPivot',
'timestamps' => true,
'key' => 'user_id',
'otherKey' => 'friend_id'
];
$model->addDynamicMethod('isFriendWith', function (FrontUser $user) use ($model) {
$model->friends->contains($user->id);
});
$model->addDynamicMethod('addFriend', function (FrontUser $user) use ($model) {
$model->friends()->attach($user->id);
});
$model->addDynamicMethod('removeFriend', function (FrontUser $user) use ($model) {
$model->friends()->detach($user->id);
});
});
And also extended the Rainlab.User Controller to have Friends tab where all friends of a user are listed and can be added and removed:
use RainLab\User\Controllers\Users as UsersController;
UsersController::extend(function($controller) {
if(!isset($controller->implement['Backend.Behaviors.RelationController'])) {
$controller->implement[] = 'Backend.Behaviors.RelationController';
}
$controller->relationConfig = '$/meysam/social/controllers/user/config_relations.yaml';
});
UsersController::extendFormFields(function($form, $model, $context) {
if(!$model instanceof FrontUser or $context != 'preview'){
// friends tab should not be displayed in update and create contexts
return;
}
$form->addTabFields([
'friends' => [
'label' => '',
'tab' => 'Friends',
'type' => 'partial',
'path' => '$/meysam/social/controllers/user/_friends.htm',
]
]);
});
Now I need to maintain a two-way friendship relationship. i.e. whenever user_id and friend_id is added to the friends table, I want to automatically add friend_id and user_id to the table as well. To achieve this, I implemented afterSave and beforeSave in the FriendsPivot model:
class FriendsPivot extends Pivot
{
/*
* Validation
*/
public $rules = [
'status' => 'required'
];
public $belongsTo = [
'user' => ['RainLab\User\Models\User', 'key' => 'user_id'],
'friend' => ['RainLab\User\Models\User', 'key' => 'friend_id']
];
public function getStatusOptions()
{
return [
1 => 'Pending',
2 => 'Approved',
3 => 'Blocked',
];
}
public function afterSave()
{
Log::info('Saving pivot...');
if(!$this->friend->isFriendWith($this->user)) {
$this->friend->addFriend($this->user);
}
}
public function beforeDelete()
{
Log::info('Deleting pivot...');
if($this->friend->isFriendWith($this->user)) {
$this->friend->removeFriend($this->user);
}
}
}
The problem is that beforeDelete is never called. afterSave gets called but beforeDelete never gets called and therefor the inverse of the relationship is not deleted (user_id-friend_id gets removed from database but friend_id-user_id does not get deleted). Why is beforeDelete not called? Is there anything I'm doing wrong? Is there any better way to maintain a two-way friendship relation?
I found this post because I'm trying to do exactly the same thing as you. If you have solved this then I wonder if you would be willing to share your solution?
This sounds very odd at first, but maybe this is because of the special delete behavior of the Pivot model. It appears that it builds a raw query using the QueryBuilder and thus bypasses any regular Eloquent (October) events.
In my eyes, the best solution would be to trigger the delete event manually in the delete method, but I'm unsure if this has any side effects.
Maybe you could test that and prepare a PR on Github if it works.

Many to many relationships in Yii2

I am trying to create a many to many relationship between a profile and several fields within that profile including languages and specialities. I have looked at several implementations and understand that there are several extensions, but I am required to minimise usage of extensions.
I have created the proper migration...this is a skeleton and the user table is purely for logins and OAuth...so keys can be ignored. As you can see in my controller I don't really know the way forward at this point. My language model is for all intensive purposes static from this controller(It is controlled by an admin backend). What is working? If I make a couple modifications to the below code then the checkboxlist will display with the proper checked items that I manually added to the lookup table. Trying to modify the lookup table from code, I have been unable to do unless I populate the $languageModel with a findOne(knownPK), however that is not usable, because multiple checkboxes can be selected resulting in an array and the link command requires ActiveRecordInterface which is singular. Ideally I would like to simply use
$languageModel->load(Yii::$app->request->post(),$trainerModel->formName());
but that isn't working.
Additionally, is there a mechanism within the framework to remove lookups or is that done manually. Any help or insight would be helpful. Thank you in advance.
public function safeUp()
{
$this->createTable('student', [
'id' => $this->primaryKey(),
'user_id' => $this->integer()->notNull(),
]);
$this->createTable('language', [
'id' => $this->primaryKey(),
'language' => $this->string(63),
]);
$this->createTable('student_language',[
'id' => $this->primaryKey(),
'language_id' => $this->integer(),
'student_id' => $this->integer()
]);
$this->addForeignKey('fk-student-language-language', 'student_language', 'language_id', 'language', 'id', 'CASCADE', 'CASCADE');
$this->addForeignKey('fk-student-language-student', 'student_language', 'student_id', 'student', 'id', 'CASCADE', 'CASCADE');
$this->addForeignKey('fk-student-user-user_id', 'student', 'user_id', 'user', 'id', 'CASCADE', 'CASCADE');
}
My student Active Record
/**
* #return \yii\db\ActiveQuery
*/
public function getstudentLanguages()
{
return $this->hasMany(studentLanguage::className(), ['student_id' => 'id']);
}
public function getLanguages(){
return $this->hasMany(Language::className(),['id'=>'language_id'])->viaTable('student_language',['student_id'=>'id']);
}
My Language Model
/**
* #return \yii\db\ActiveQuery
*/
public function getstudentLanguages()
{
return $this->hasMany(studentLanguage::className(), ['language_id' => 'id']);
}
public function getLanguages(){
return $this->hasMany(student::className(),['id'=>'student_id'])->viaTable('student_language',['language_id'=>'id']);
}
My Controller
public function actionProfile()
{
if (Yii::$app->user->isGuest) {
throw new UnauthorizedHttpException('This page requires you to be logged in');
} else {
$user_id = Yii::$app->user->identity->getId();
$studentModel = $this->findstudentModelByUserId($user_id);
if (is_null($studentModel)) {
$studentModel = new student();
$studentModel->setAttribute('user_id', $user_id);
$studentModel->save();
}
$languageModel = ??????????????????
$studentModel->load(Yii::$app->request->post()) && $studentModel->save() && $studentModel->link('languages',$languageModel);
}
return $this->render('profile', ['model' => $studentModel]);
}
My View
<?= $form->field($model, 'languages')->checkboxList(\common\models\Language::find()->select(
['language', 'id'])->indexBy('id')->column(), ['prompt' => 'select Language']); ?>
This is my solution at this point, however I am interested to hear improvements.
Note: There is no error checking etc...just functional code.
I added this to the student activerecord class
public function linkMultiple( $name,Array $models){
studentLanguage::deleteAll(['student_id' =>$this->id]);
foreach($models as $model){
$this->link($name, $model);
}
return true;
}
Modified my Controller action to the following
public function actionProfile()
{
if (Yii::$app->user->isGuest) {
throw new UnauthorizedHttpException('This page requires you to be logged in');
} else {
$user_id = Yii::$app->user->identity->getId();
$studentModel = $this->findstudentModelByUserId($user_id);
if (is_null($studentModel)) {
$studentModel = new student();
$studentModel->setAttribute('user_id', $user_id);
$studentModel->save();
}
$languages = Yii::$app->request->post('student')['languages'];
$languageModels = Language::findAll($languages);
$studentModel->load(Yii::$app->request->post()) && $studentModel->save() && $studentModel->linkMultiple('languages',$languageModels);
}
return $this->render('profile', ['model' => $studentModel]);
}

How to update customer in braintree payment method

I have integrated braintree method in yii2 rest api Using this reference.. I want to update the customer but I am getting following error:
Missing argument 2 for Braintree\Customer::update()
Below is my code :
$braintree = Yii::$app->braintree;
$response = $braintree->call('Customer', 'update','15552090',[
'firstName' => 'test-1545',
'lastName' => 'asdf',
'company' => 'New Company',
'email' => 'new.email#example.com',
'phone' => 'new phone',
'fax' => 'new fax',
'website' => 'http://new.example.com'
]);
print_r($response); die;
I am stack here how to pass the arguments?
It's a problem of this particular extension. See this issue on Github.
Issue OP recommends this fix:
public function call($command, $method, $values, $values2 = null)
{
$class = strtr("{class}_{command}", [
'{class}' => $this->_prefix,
'{command}' => $command,
));
if ($values2) {
return call_user_func(array($class, $method), $values, $values2);
else {
return call_user_func(array($class, $method), $values);
}
}
while extension author recommends this:
if (is_array($values)) {
call_user_func_array(...);
} else {
call_user_func(...);
}
Either way you need to override this component with your own and apply a patch.
Note that the amount of code in application is small (64 lines in one file) so you can create your own wrapper or find better one because this issue is still not fixed.
And maybe is better to directly use braintree_php methods which will be more clear than magical call.
Update: To override component, create own class extending from bryglen's, place it for example in common/components folder in case of using advanced app.
namespace common\components;
class Braintree extends \bryglen\braintree\Braintree
{
public function call($command, $method, $values)
{
// Override logic here
}
}
Then replace extension class name with your custom one in config:
'components' => [
'braintree' => [
'class' => 'common\components\Braintree',
'environment' => 'sandbox',
'merchantId' => 'your_merchant_id',
'publicKey' => 'your_public_key',
'privateKey' => 'your_private_key',
],
],

QueryException - Integrity constraint violation: 1062 Duplicate entry when I hit the route 'logout'

I get the above error when I try and logout by hitting the route /logout. The table that is referenced in the screenshot is mdbids. It stores all of my IDs (strings, 16 characters in length).
When a user is created their MDBID (id) is stored in the mdbids table.
routes.php
<?php
Route::get('login', ['as' => 'login', 'uses' => 'SessionsController#create']);
Route::get('logout', ['as' => 'logout', 'uses' => 'SessionsController#destroy']);
SessionsController.php
<?php
use MDB\Forms\LoginForm;
class SessionsController extends \BaseController {
protected $loginForm;
function __construct(LoginForm $loginForm)
{
$this->loginForm = $loginForm;
}
public function create()
{
if(Auth::check()) return Redirect::to("/users");
return View::make('sessions.create');
}
public function store()
{
$this->loginForm->validate($input = Input::only('email','password'));
if (Auth::attempt($input)) {
Notification::success('You signed in successfully!');
return Redirect::intended('/');
}
Notification::error('The form contains some errors');
return Redirect::to('login')->withInput()->withFlashMessage("The form contains some errors");
}
public function destroy()
{
Auth::logout();
return Redirect::home();
}
}
The following is taken from my User.php (model) file. It isn't the whole file as it is fairly big, but this is the only part where IDs are mentioned.
User.php (model)
<?php
public function save(array $options = array())
{
$this->mdbid = $this->mdbid ?: str_random(16);
$this->key = $this->key ?: str_random(11);
Mdbid::create([
'mdbid' => $this->mdbid,
'table_number' => 7,
'table_name' => 'users',
'created_at' => Carbon::now(),
'updated_at' => Carbon::now()
]);
parent::save($options);
}
I don't know where to start to look. Any help is greatly appreciated.
Your issue is that the logout is actually causing save() to run, and therefor you are causing Mdbid::create to run with an already added key (presumably when you logged in, or somewhere else in your User model?).
Solution #1:
You could add a logout() function to the User model that you have. Something similar to
function logout()
{
$this->mdbid = null;
return Auth::logout()
}
This will stop two of the same keys being added to the logout function.
Solution #2
If what you are trying to accomplish is adding a row upon a successful login, then you should not be using the User::save() function, rather, you should be listening for the auth.login event.
Inside app/start/global.php, add the following code:
Event::listen('auth.login', function($user)
{
$user->mdbid = $user->mdbid ?: str_random(16);
$user->key = $user->key ?: str_random(11);
Mdbid::create([
'mdbid' => $user->mdbid,
'table_number' => 7,
'table_name' => 'users',
'created_at' => Carbon::now(),
'updated_at' => Carbon::now()
]);
});
This will ensure only one row gets added to Mdbid per successful login, instead of adding a new row (with the same id) each time the User model is updated.
Solution #3 (a.k.a. what was really wanted)
Each table has mdbid as a primary key. Each primary key needs to be added to the Mdbid table each time a new row is inserted.
The way that this should be done is with an Observer. The first part is adding a new Observer class that will be used for all of the models we want to add the mdbid into:
class MdbidObserver
{
/**
* Observe new rows being added into the database
*/
public function creating($model)
{
// note that $model could be any model
$model->mdbid = $model->mdbid ?: str_random(16);
$model->key = $model->key ?: str_random(11);
Mdbid::create([
'mdbid' => $model->mdbid,
'table_number' => 7,
'table_name' => 'users',
'created_at' => Carbon::now(),
'updated_at' => Carbon::now()
]);
}
}
The second part is adding the Observer to all the models that we want the mdbid added to (inside app/start/global.php):
User::observe(new MdbidObserver);
Artist::observe(new MdbidObserver);
Album::observe(new MdbidObserver);
To stop any issues with mdbid not actually being random already being used, you might want to add a loop just before $model->mdbid. Something similar to:
$isUnique = false;
while (!$isUnique)
{
$unqiueId = str_random(16);
$row = Mdbid::where('mdbid', $uniqueId);
if (is_object($row))
$isUnique = true;
}
$model->mdbid = $uniqueId;