CakePHP3 filtering based on custom rules - orm

I have following schema:
User hasMany RegistrationState
RegistrationState belongsTo User
to its classic 1->M relation where user_id is in RegistrationState table.
RegistrationState has following attributes
id, user_id, state, created_at ... ( others are not relevant )
User can have more registration states for example:
Registration state -> 1, created -> 01-02-2017
Registration state -> 2, created -> 01-03-2017
Registration state -> 3, created -> 01-04-2017
Registration state -> 4, created -> 01-05-2017
Registration state -> 2, created -> 01-06-2017
Registration state -> 3, created -> 01-07-2017
I need to filter based on this 'state property', where the valid state is last one created.
Valid state for this example is 3, because its latest state.
Now, i have controler, and index method where i have
// push custom query for filtering to finder method
$this->paginate['finder'] = [
'clients' => ['q' => $this->request->query]
];
try {
$clients = $this->paginate( $this->Users );
} catch (\Exception $e) {
// redirect on main
return $this->redirect($this->here);
}
My finder method looks like this
public function findClients($query, array $options)
{
$opts = $query->getOptions()['q'];
if (!empty($opts['email'])) {
$query->andWhere(['email LIKE' => '%' . trim($opts['email']) . '%']);
}
if (!empty($opts['identity_num'])) {
$cn = $opts['identity_num'];
$query->matching('Identities', function ($query) use ($cn) {
$query->andWhere(['Identities.number' => $cn]);
});
}
// registration state
if (!empty($opts['reg_state'])) {
// WHAT SHOULD BE HERE???
}
return $query;
}
There is also another 1->M relation User -> Identity, but ( matching method ) but that works fine because number is always unique.
I can't resolve problem with registration states, how can I implement this kind of search in CakePHP3? ( i dont want to break pagination so i would like to solve this in finder method )

One of possible (dirty?) solutions to this is subquery ( INNER JOIN on RegistrationState )
// registration state
if (!empty($opts['reg_state'])) {
$conn = ConnectionManager::get('default');
$subquery = $conn->execute(
'SELECT uir.user_id ' .
'FROM registration_state uir ' .
'INNER JOIN ( ' .
'SELECT user_id, max(created) as m_created ' .
'FROM registration_state ' .
'GROUP BY user_id ' .
') muir ON uir.user_id = muir.user_id AND uir.created = muir.m_created AND uir.status = '
. intval( $opts['reg_state'])
);
$usersInStateIds = [];
// transform result of subquery to usable array
foreach( $subquery->fetchAll('assoc') as $r ) {
$usersInStateIds[] = intval( $r['user_id'] );
}
$query->matching('RegistrationState', function ($query) use ($usersInStateIds) {
return $query->andWhere(['RegistrationState.user_id IN' => $usersInStateIds]);
});
}

Related

Unable to insert id of user to another table laravel 8 livewire

I am unable to store user id of another user on my db. I have a messages table, where from_id,t_id and user_id are the foreign key of table users. I have models defined properly as well. The issue I am having is:
can't insert the user_id.
I can save everything else. when i do dd $this->id it shows the corret user id but when it comes to save them i can't.
I tried doing explode and it stopped showing those random values. it now gets the id however in below format: [{"id":3}]
: Invalid text representation: 7 ERROR: invalid input syntax for type bigint: "[{"id":3}]"
As you can see it's getting the user id when i get the values from authenticated user which is obvious.
My code :
public $messages, $sender;
public $users;
public $avatar;
protected $destination = '';
public $messageText;
public function mount($id)
{
$this->id = $id;
$this->avatar = DB::table('users')
->join('messages', 'users.id', '=', 'messages.user_id')
->select('users.id')
->where('users.id', '=', $this->id)->distinct()->get();
$this->destination = DB::table('users')
->join('messages', 'users.id', '=', 'messages.user_id')
->select('users.id')
->where('users.id', '=', $this->id)->first();
$this->users =
Message::latest()
->take(5)
->get()
->sortBy('id');
$this->messages = DB::table('users')
->join('messages', 'users.id', '=', 'messages.user_id')
->select('users.name', 'users.id', 'messages.message', 'messages.created_at')
->where('users.id', '=', $this->id)
->get();
// SELECT * FROM users AS u INNER JOIN messages AS m ON u.ID IN (m.senderID, m.recipientID) WHERE u.ID <> $userID GROUP BY u.ID ORDER BY m.sentAt DESC
}
public function render()
{
// $name = explode(' ', $this->avatar);
// dd($name);
return view(
'livewire.show',
[
'avatar' => $this->avatar,
'messages' => $this->messages,
'users' => $this->users,
]
)->layout('layouts.app');
}
public function sendMessage()
{
$name = explode(' ', $this->avatar);
// $this->validate(['messageText' => "required"]);
$message = new Message();
$message->from_id = Auth::user()->id;
$message->user_id = Auth::user()->id;
$message->to_id = $name[0]; // [{"id":3}]
$message->message = $this->messageText;
// dd($message);
$message->save();
$this->reset('messageText');
}
Error Message:

Implement Phalcon 4 Database Existence validator (similar to Uniqueness)

Often I need to validate if given value is existing in certain column (attribute) of a table (model).
This can be useful in foreign keys of a model, to check if the given values exists.
Most probably the validation logic can be mostly the same as for Uniqueness, except the comparison here can be something like > 0.
A possible usage scenario could be like below:
$validator->add(
'organization_id',
new ExistenceOnDbValidator(
[
'model' => Organization::class,
'expr'=> ' id = %s ',
'excludeNullValue'=> true,
'message' => 'Organization does not exist.',
]
)
);
Finally I implemented myself a validator called ExistenceOnDbValidator and it works fine.
Usage
$validator = new Validation();
$validator->add(
'organization_id',
new ExistenceOnDbValidator(
[
'model' => Organization::class,
'expr' => ' id = %s ',
'ignoreNullValue' => false,
'message' => 'Selected organization does not exist.',
]
)
);
Implenentation
use Phalcon\Messages\Message;
use Phalcon\Validation;
use Phalcon\Validation\AbstractValidator;
use Phalcon\Validation\ValidatorInterface;
class ExistenceOnDb extends AbstractValidator implements ValidatorInterface
{
public function validate(Validation $validator, $attribute): bool
{
$expr = $this->getOption('expr');
$model = $this->getOption('model');
$value = $validator->getValue($attribute);
$ignoreNullValue = true;
if ($this->hasOption('ignoreNullValue')) {
$ignoreNullValue = $this->getOption('ignoreNullValue');
}
if ((is_null($value) || empty($value)) && $ignoreNullValue == true) {
return true;
}
$expr = sprintf(
$expr,
$value,
);
$result = $model::findFirst($expr);
if ((is_null($result) || empty($result))) {
$message = $this->getOption('message');
$validator->appendMessage(new Message($message));
return false;
}
return true;
}
}

Fetch related 'far' records by eloquent or fluent

I have the following tables:
users:
------
id
first_name
current_property_id
property_users:
---------------
id
user_id
property_id
chat_user_id
nickname
chat_users:
-----------
id
identity
chat_groups:
------------
id
name
type ['Single' means the group a user is having permutation without repetition for all property users that will serve as their 1:1 conversation (in short everyone is paired with everyone once), 'Group' means more than 2 people can be in the group]
last_messaged_at
chat_group_members:
-------------------
id
chat_group_id
user_id
chat_user_id
I need to make a query to fetch all users (except me) in the property I am in and get the chat_groups I have with them (Type == Single).
I tried doing it PHP way but it takes too long (for 125 users it takes +30s of response)
This is how I am doing it in PHP
$this->currentUser = auth()->user();
//this is Fluent using DB::table and joining property_users, chat_users, and users
$propertyUsers = $this->run(GetPropertyUsersJob::class, [
'property' => $this->property
]);
//this is using simple eloquent getting groups
$chatGroups = $this->run(GetGroupsJob::class, [
'property' => $this->property,
'type' => ChatGroup::TYPE_SINGLE
]);
$chatGroups->load('members.user');
//first level of loop
foreach($propertyUsers as $propertyUser) {
if($this->currentUser->id == $propertyUser->user_id) {
return [];
}
$chatGroupId = '';
$chatGroupName = '';
//second level of loop
foreach($chatGroups as $chatGroup) {
$found = 0;
//third level of loop
foreach($chatGroup->members as $member) {
if($member->user_id == $this->currentUser->id) {
$found++;
}
if($member->user_id == $propertyUser->user_id) {
$found++;
}
}
//just to get this, I need to do nested loop 3x
if($found == 2) {
$chatGroupId = $chatGroup->id;
$chatGroupName = $chatGroup->name;
}
}
$user[] = [
'id' => $propertyUser->user_id,
'first_name' => $propertyUser->user->first_name,
'nickname' => $propertyUser->nickname,
'chat_group_id' => $chatGroupId,
'chat_group_name' => $chatGroupName
];
}
How can I convert this to a query (eloquent/fluent) instead?
#Update:
User model: I am only adding related eager loading
-----------
public function property_users()
{
return $this->hasMany(PropertyUser::class, 'user_id', 'id');
}
PropertyUser model:
-------------------
public function user()
{
return $this->belongsTo(User::class);
}
public function chat_user()
{
return $this->hasOne(ChatUser::class, 'id', 'chat_user_id');
}
ChaUser model:
--------------
public function property_user()
{
return $this->belongsTo(PropertyUser::class, 'id', 'chat_user_id');
}

How to create Category / Subcategory Friendly URL in PrestaShop?

I have a question about formatting the friendly URL for category and subcategory and getting the matching products. I am using PrestaShop 1.5.2.0
Let's say we have a structure like this:
Category 1
Spare Parts
Accessories
Category 2
Chips
Accessories
I want to display the link like this: /category-1/accessories and to display the products from category 1->accessories. How can I achieve this?
The current behavior is when I click on accessories, being in category 1, the link is /accessories and the products that are displayed belong from both /category-1/accessories and /category-2/accessories
Thanks!
This question was answered on the PrestaShop forum.
You can find it here http://www.prestashop.com/forums/topic/220017-category-subcategory-url/
The solution - add this changes to the fallowing classes
CLASSES/Dispatcher.php
'rule' => '{categories:/}{id}-{rewrite}/',
'categories' => array('regexp' => '[/_a-zA-Z0-9-\pL]*'),
CLASSES/Link.php
$cats = array();
foreach ($category->getParentsCategories() as $cat)
if (!in_array($cat['id_category'], array(1, 2, $category->id)))//remove root, home and current category from the URL
$cats[] = $cat['link_rewrite'];
$params['categories'] = implode('/', array_reverse($cats));
The above code is valid for the older versions, but it would not work for the newer/latest version. I updated the same solution for the newer version (1.7.7.4.), it might be used for others.
change CLASSES/Dispatcher.php
at about line 55 in the above file copy paste
public $default_routes = [
'category_rule' => [
'controller' => 'category',
**/** added 1 line below*/
'rule' => '{category:/}{id}-{rewrite}/',
/** commented 1line below*/
/**'rule' => '{id}-{rewrite}',*/
'keywords' => [
'id' => ['regexp' => '[0-9]+', 'param' => 'id_category'],
/*** added 1 line below*/
'category' => ['regexp' => '[_a-zA-Z0-9-\pL]*'],
'rewrite' => ['regexp' => self::REWRITE_PATTERN],
'meta_keywords' => ['regexp' => '[_a-zA-Z0-9-\pL]*'],
'meta_title' => ['regexp' => '[_a-zA-Z0-9-\pL]*'],**
],
],
In the file classes/link.php find the function getCategoryLink and replace it with the function code below
/**
* Create a link to a category.
*
* #param mixed $category Category object (can be an ID category, but deprecated)
* #param string $alias
* #param int $idLang
* #param string $selectedFilters Url parameter to autocheck filters of the module blocklayered
*
* #return string
*/
public function getCategoryLink(
$category,
$alias = null,
$idLang = null,
$selectedFilters = null,
$idShop = null,
$relativeProtocol = false
) {
$dispatcher = Dispatcher::getInstance();
if (!$idLang) {
$idLang = Context::getContext()->language->id;
}
$url = $this->getBaseLink($idShop, null, $relativeProtocol) . $this->getLangLink($idLang, null, $idShop);
// Set available keywords
$params = [];
if (!is_object($category)) {
$params['id'] = $category;
} else {
$params['id'] = $category->id;
}
// Selected filters is used by the module ps_facetedsearch
$selectedFilters = null === $selectedFilters ? '' : $selectedFilters;
if (empty($selectedFilters)) {
$rule = 'category_rule';
} else {
$rule = 'layered_rule';
$params['selected_filters'] = $selectedFilters;
}
if (!$alias) {
$category = $this->getCategoryObject($category, $idLang);
}
$params['rewrite'] = (!$alias) ? $category->link_rewrite : $alias;
if ($dispatcher->hasKeyword($rule, $idLang, 'meta_keywords', $idShop)) {
$category = $this->getCategoryObject($category, $idLang);
$params['meta_keywords'] = Tools::str2url($category->getFieldByLang('meta_keywords'));
}
if ($dispatcher->hasKeyword($rule, $idLang, 'meta_title', $idShop)) {
$category = $this->getCategoryObject($category, $idLang);
$params['meta_title'] = Tools::str2url($category->getFieldByLang('meta_title'));
}
if ($category !='var'){
$category = $this->getCategoryObject($category, $idLang);
$pcategory= new Category($category->id_parent, $idLang);
if($category->id_parent!=1 && $category->id_parent!=2){
$params['category'] = $pcategory->link_rewrite;
//append the categoryID with its name
$params['category'] = $category->id_parent . '-'. $params['category'];
}
}
return $url . Dispatcher::getInstance()->createUrl($rule, $idLang, $params, $this->allow, '', $idShop);
}
in the same file classes/link.php update if condition as follows at line 218 in the code for (function getProductLink)
if ($dispatcher->hasKeyword('product_rule', $idLang, 'categories', $idShop)) {
$product = $this->getProductObject($product, $idLang, $idShop);
$params['category'] = (!$category) ? $product->category : $category;
$cats = [];
foreach ($product->getParentCategories($idLang) as $cat) {
if (!in_array($cat['id_category'], Link::$category_disable_rewrite)) {
//remove root and home category from the URL
//commented the line below
//$cats[] = $cat['link_rewrite'];
//replaced the above line with the line below to append the category ID in the products link
$cats[] = $cat['id_category'].'-'.$cat['link_rewrite'];
}
}
$params['categories'] = implode('/', $cats);
}
I happened to be using the prestashop version 1.7.7.4. You can see this solution working on my site https://jinbaba.pk
Also after making the above changes in the code files, do not forget to update shopparameters-->SEO&URL Setting, change the category and product routes as follows (if they are not already like this)
"Route to category" = {category:/}{id}-{rewrite}
"Route to product" = {categories:/}{id}{-:id_product_attribute}-{rewrite}{-:ean13}.html
Just a suggestion for SEO : You do not need to remove category ID and product ID from the URL. They have minimal or no impact on SEO.
also the about solution will work for 2 level nesting e.g.
yourdomain.com/category-1/category-2/1-product.html
do not create more nesting of categories in your catalog. If you plan to do create deeper nesting your site you will need to update this solution. However, for SEO deep nesting is not recommended.

CakePHP Auth Component Using 2 Tables

CakePHP Version 1.2.5
I would like a single user to have multiple email addresses.
I would like a single user to have a single password.
I would like users to log in using any of their multiple email addresses and their single password.
I have created a users table with an id and a password field.
I have created a user_email_addresses table with an id field a user_id field and an email_address field.
Question:
How do I modify the auth component minimally to look for the "username" in this case, "email_address", in the user_email_addresses table and the "password" in the users table?
Seems as though modifying the identify method in the auth component might do it. But I think modifying the auth component directly is a bad idea - any ideas on how to extend and still possibly modify the identify method? http://cakebaker.42dh.com/2009/09/08/extending-cakephps-core-components/ or possibly nominate a different authenticate object?
Starting line 774:
function identify($user = null, $conditions = null) {
if ($conditions === false) {
$conditions = null;
} elseif (is_array($conditions)) {
$conditions = array_merge((array)$this->userScope, $conditions);
} else {
$conditions = $this->userScope;
}
if (empty($user)) {
$user = $this->user();
if (empty($user)) {
return null;
}
} elseif (is_object($user) && is_a($user, 'Model')) {
if (!$user->exists()) {
return null;
}
$user = $user->read();
$user = $user[$this->userModel];
} elseif (is_array($user) && isset($user[$this->userModel])) {
$user = $user[$this->userModel];
}
if (is_array($user) && (isset($user[$this->fields['username']]) || isset($user[$this->userModel . '.' . $this->fields['username']]))) {
if (isset($user[$this->fields['username']]) && !empty($user[$this->fields['username']]) && !empty($user[$this->fields['password']])) {
if (trim($user[$this->fields['username']]) == '=' || trim($user[$this->fields['password']]) == '=') {
return false;
}
$find = array(
$this->userModel.'.'.$this->fields['username'] => $user[$this->fields['username']],
$this->userModel.'.'.$this->fields['password'] => $user[$this->fields['password']]
);
} elseif (isset($user[$this->userModel . '.' . $this->fields['username']]) && !empty($user[$this->userModel . '.' . $this->fields['username']])) {
if (trim($user[$this->userModel . '.' . $this->fields['username']]) == '=' || trim($user[$this->userModel . '.' . $this->fields['password']]) == '=') {
return false;
}
$find = array(
$this->userModel.'.'.$this->fields['username'] => $user[$this->userModel . '.' . $this->fields['username']],
$this->userModel.'.'.$this->fields['password'] => $user[$this->userModel . '.' . $this->fields['password']]
);
} else {
return false;
}
$model =& $this->getModel();
$data = $model->find(array_merge($find, $conditions), null, null, 0);
if (empty($data) || empty($data[$this->userModel])) {
return null;
}
} elseif (!empty($user) && is_string($user)) {
$model =& $this->getModel();
$data = $model->find(array_merge(array($model->escapeField() => $user), $conditions));
if (empty($data) || empty($data[$this->userModel])) {
return null;
}
}
if (!empty($data)) {
if (!empty($data[$this->userModel][$this->fields['password']])) {
unset($data[$this->userModel][$this->fields['password']]);
}
return $data[$this->userModel];
}
return null;
}
AuthComponent::identify() takes two parameters, $user and $conditions
if ($conditions === false) {
$conditions = null;
} elseif (is_array($conditions)) {
$conditions = array_merge((array)$this->userScope, $conditions);
} else {
$conditions = $this->userScope;
}
Looking at the above snippet, if you pass false as the $conditions, the method will execute with no model conditions.
Also, looking at the rest of the code, if you pass a $user value of type string, it won't execute most of the user-related code until it gets here:
} elseif (!empty($user) && is_string($user)) {
$model =& $this->getModel();
$data = $model->find(array_merge(array($model->escapeField() => $user), $conditions));
if (empty($data) || empty($data[$this->userModel])) {
return null;
}
}
Here it runs Model::escapeField(), with no parameters, which returns an escaped version of User.id (by default) and maps this field to the string that was passed in. It then merges this with the $conditions array and performs a Model::find().
It should be safe to say that if the string is the user's ID and there are no conditions it will find the person with that ID every time.
As such, you should be able to extend AuthComponent to do what you want like so:
// app/controllers/components/app_auth.php
<?php
App::import('Component', 'Auth');
class AppAuthComponent extends AuthComponent {
/**
* Custom user identification
*/
function identify($user=null, $conditions=null) {
// get the model AuthComponent is configured to use
$model =& $this->getModel(); // default is User
// do a query that will find a User record when given successful login data
$user = $model->find('first', array('conditions' => array(
'EmailAddress.' . $this->fields['username'] => $user[$this->userModel][$this->fields['username']],
'User.' . $this->fields['password'] => $user[$this->userModel][$this->fields['password']],
));
// return null if user invalid
if (!$user) {
return null; // this is what AuthComponent::identify would return on failure
}
// call original AuthComponent::identify with string for $user and false for $conditions
return parent::identify($user[$this->userModel][$model->primaryKey], false);
}
}
?>
You will have to replace all references to Auth with AppAuth in your application unless you follow this handy tip (the approach in the comments is nice).