I'm using Cakephp 3.2 and proffer plugin to upload images.
By default the path of the image is as follows
/media/files/<tablename>/<primary_key>/<filename>
Each time a new row is inserted into same table a new folder is created by its primary key.
I want to upload all images of a table to the same directory. means path like
/media/files/<tablename>/<filename>
I'm using event listener as per given in proffer documentation.
This is my SellersTable.php
<?php
namespace App\Model\Table;
use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;
use Cake\Event\Event;
class SellersTable extends Table
{
/**
* Initialize method
*
* #param array $config The configuration for the Table.
* #return void
*/
public function initialize(array $config)
{
parent::initialize($config);
$listener = new App\Event\UploadFileNameListener(); // line 23
$this->eventManager()->on($listener);
$this->table('sellers');
$this->displayField('id');
$this->primaryKey('id');
$this->addBehavior('Timestamp');
$this->addBehavior('Proffer.Proffer', [
'profile_picture' => [
'root' => Configure::read('ArgoSystems.media.upload') . DS . 'files',
'dir' => 'dir'
]
]);
}
/**
* Default validation rules.
*
* #param \Cake\Validation\Validator $validator Validator instance.
* #return \Cake\Validation\Validator
*/
public function validationDefault(Validator $validator)
{
$validator
->integer('id')
->allowEmpty('id', 'create');
$validator
->requirePresence('first_name', 'create')
->notEmpty('first_name');
$validator
->requirePresence('last_name', 'create')
->notEmpty('last_name');
$validator
->email('email')
->requirePresence('email', 'create')
->notEmpty('email')
->add('email', 'unique', ['rule' => 'validateUnique', 'provider' => 'table']);
$validator->provider('proffer', 'Proffer\Model\Validation\ProfferRules');
$validator
->add('profile_picture', 'proffer', [
'rule' => ['dimensions', [
'min' => ['w' => 100, 'h' => 500],
'max' => ['w' => 100, 'h' => 500],
]],
'message' => 'Image must be of 100 x 500 resolution',
'provider' => 'proffer'
])
->requirePresence('profile_picture', 'create')
->allowEmpty('profile_picture','update');
$validator
->requirePresence('password', 'create')
->notEmpty('password');
return $validator;
}
public function buildRules(RulesChecker $rules)
{
$rules->add($rules->isUnique(['email']));
return $rules;
}
}
and created UploadFileNameListener.php in src/Event/
<?php
namespace App\Event;
use Cake\Event\Event;
use Cake\Event\EventListenerInterface;
use Cake\Utility\Inflector;
use Proffer\Lib\ProfferPath;
class UploadFileNameListener implements EventListenerInterface
{
public function implementedEvents()
{
return [
'Proffer.afterPath' => 'change',
];
}
/**
* Rename a file and change it's upload folder before it's processed
*
* #param Event $event The event class with a subject of the entity
* #param ProfferPath $path
* #return ProfferPath $path
*/
public function change(Event $event, ProfferPath $path)
{
// Detect and select the right file extension
switch ($event->subject()->get('image')['type']) {
default:
case "image/jpeg":
$ext = '.jpg';
break;
case "image/png":
$ext = '.png';
break;
case "image/gif":
$ext = '.gif';
break;
}
// Create a new filename using the id and the name of the entity
$newFilename = $event->subject()->get('id') . '_' . Inflector::slug($event->subject()->get('name')) . $ext;
// set seed
$path->setSeed('profile_picture');
// Change the filename in both the path to be saved, and in the entity data for saving to the db
$path->setFilename($newFilename);
$event->subject('image')['name'] = $newFilename;
// Must return the modified path instance, so that things are saved in the right place
return $path;
}
}
But this is giving Fatal error as
Error: Uncaught Error: Class 'App\Model\Table\App\Event\UploadFileNameListener' not found in
/var/www/html/projects/admin/src/Model/Table/SellersTable.php:23
From the error message, it's clear that it's trying to load the class with a namespace relative to the namespace of your current class. Try
$listener = new \App\Event\UploadFileNameListener();
Related
I have created a route with Api prefix
$routes->scope('/api', function (RouteBuilder $routes) {
$routes->setExtensions(['json']);
$routes->post(
'/add-categories',
['controller' => 'Miles', 'action' => 'addCategories','prefix'=>'Api']
);
}
I have created a controller file in directory Controller/Api/MilesController.php
I have created a addCategory method like below
public function addCategories()
{
$categoriesTable = $this->fetchTable('Categories');
$categoryEnt = $categoriesTable->newEmptyEntity();
if ($this->request->is('post')) {
$category = $categoriesTable->patchEntity($categoryEnt, $this->request->getData());
if ($categoriesTable->save($category)) {
$responseBody = [
'status' => 201,
'data' => $category
];
}else{
$responseBody = [
'status' => 400,
'data' => $category->getErrors()
];
}
}
$this->set(compact('responseBody'));
$this->viewBuilder()->setOption('serialize', ['responseBody']);
}
In postman if I try to send a post request without any data , then I getting the response like below
{
"responseBody": {
"status": 201,
"data": {
"created": "2022-05-10T20:04:13+09:00",
"modified": "2022-05-10T20:04:13+09:00",
"id": 8
}
}
}
My Model/Table/CategoriesTable.php looks like
<?php
declare(strict_types=1);
namespace App\Model\Table;
use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;
class CategoriesTable extends Table
{
public function initialize(array $config): void
{
parent::initialize($config);
$this->setTable('categories');
$this->setDisplayField('name');
$this->setPrimaryKey('id');
$this->addBehavior('Timestamp');
$this->hasMany('Products', [
'foreignKey' => 'category_id',
]);
}
public function validationDefault(Validator $validator): Validator
{
$validator
->allowEmptyString('id', null, 'create');
$validator
->scalar('title')
->notEmptyString('title');
$validator
->boolean('status')
->notEmptyString('status');
$validator
->scalar('imageUrl')
->allowEmptyFile('imageUrl');
return $validator;
}
}
Why my notEmptyString validation not working which I have written in CategoriesTable.php file ?
Note : This Api/MilesController.php has no self model. How can I apply CategoriesTable validation here ?
None of your fields are set as being required, ie they are all optional, and when an optional field is missing, no validation rules will be applied.
So for all fields that must be present, use requirePresence(), for example:
$validator
->requirePresence('title', 'create') // required on create, optional on update
->scalar('title')
->notEmptyString('title');
See also
Cookbook > Validation > Creating Validators > Requiring Field Presence
I am trying to seed the user table but I am facing some issues can someone guide me where I am missing.
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
class DatabaseSeeder extends Seeder
{
/**
* Run the database seeds.
*
* #return void
*/
public function run()
{
DB::table('users')->insert([
'name' => Str::random(10),
'email' => Str::random(10).'#example.com',
'password' => Hash::make('password'),
]);
}
}
I think you are missing the below line
use Illuminate\Support\Str;
The complete code look like
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
class DatabaseSeeder extends Seeder
{
/**
* Run the database seeders.
*
* #return void
*/
public function run()
{
DB::table('users')->insert([
'name' => Str::random(10),
'email' => Str::random(10).'#gmail.com',
'password' => Hash::make('password'),
]);
}
}
Try something like this using eloquent instead of the DB class with an existence check too:
<?php
namespace Database\Seeders;
use App\Models\User;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Hash;
class UsersTableSeeder extends Seeder
{
/**
* Run the database seeds.
*
* #return void
*/
public function run()
{
// Seed test user 1
$seededAdminEmail = 'admin#admin.com';
$user = User::where('email', '=', $seededAdminEmail)->first();
if ($user === null) {
$user = User::create([
'name' => 'Admin',
'email' => $seededAdminEmail,
'password' => Hash::make('password'),
]);
}
// Seed test user 2
$user = User::where('email', '=', 'user#user.com')->first();
if ($user === null) {
$user = User::create([
'name' => 'User',
'email' => 'user#user.com',
'password' => Hash::make('password'),
]);
}
}
}
I have just created a very minimal project in CakePHP 4.1, mostly mimicking the CMS tutorial, and want to implement a fairly straightforward piece of logic.
Using the Authorization module I want to allow a user A to be able to view a user B if 1) they are actually the same user (A = B) OR 2) if A is an admin.
There are two DB tables - users and user_types. users has a foreign key user_type_id to user_types.
This relationship is reflected in code as:
##### in UsersTable.php #####
class UsersTable extends Table {
public function initialize(array $config): void
{
parent::initialize($config);
$this->setTable('users');
$this->setDisplayField('name');
$this->setPrimaryKey('id');
$this->belongsTo('UserTypes');
$this->addBehavior('Timestamp');
}
//...
}
##### in UserTypesTable.php #####
class UserTypesTable extends Table {
public function initialize(array $config): void
{
parent::initialize($config);
$this->setTable('user_types');
$this->setDisplayField('name');
$this->setPrimaryKey('id');
$this->hasMany('Users');
}
//...
}
In UsersController.php I have:
public function view($id = null)
{
$user = $this->Users->get($id, [
'contain' => ['UserTypes'],
]);
$this->Authorization->authorize($user);
$this->set(compact('user'));
}
And in UserPolicy.php:
use App\Model\Entity\User;
class UserPolicy
{
public function canView(User $user, User $resource)
{
// TODO: allow view if $user and $resource are the same User or if $user is an admin
//
// My problem is that here $user->user_type is NULL
// while $resource->user_type is populated correctly
}
}
The code comment in the above excerpt shows where my problem is.
I do not know how to get $user to have its user_type field populated in order to check whether they're an admin.
As a part of my efforts, I have set the User class to be the authorization identity, following this article: https://book.cakephp.org/authorization/2/en/middleware.html#using-your-user-class-as-the-identity.
Code-wise this looks like:
##### relevant part of Application.php #####
$middlewareQueue
->add(new AuthenticationMiddleware($this))
->add(new AuthorizationMiddleware($this, [
'identityDecorator' => function(\Authorization\AuthorizationServiceInterface $auth, \Authentication\IdentityInterface $user) {
return $user->getOriginalData()->setAuthorization($auth);
}
]));
##### User.php #####
namespace App\Model\Entity;
use Authentication\PasswordHasher\DefaultPasswordHasher;
use Authorization\AuthorizationServiceInterface;
use Authorization\Policy\ResultInterface;
use Cake\ORM\Entity;
/**
* User Entity
*
* #property int $id
* #property string $email
* #property string $password
* #property string|null $name
* #property \App\Model\Entity\UserType $user_type
* #property \Cake\I18n\FrozenTime|null $created
* #property \Cake\I18n\FrozenTime|null $modified
* #property \Authorization\AuthorizationServiceInterface $authorization
*/
class User extends Entity implements \Authorization\IdentityInterface, \Authentication\IdentityInterface
{
protected $_accessible = [
'email' => true,
'password' => true,
'name' => true,
'created' => true,
'modified' => true,
];
/**
protected $_hidden = [
'password',
];
protected function _setPassword(string $password) : ?string
{
if (strlen($password) > 0) {
return (new DefaultPasswordHasher())->hash($password);
}
}
/**
* #inheritDoc
*/
public function can(string $action, $resource): bool
{
return $this->authorization->can($this, $action, $resource);
}
/**
* #inheritDoc
*/
public function canResult(string $action, $resource): ResultInterface
{
return $this->authorization->canResult($this, $action, $resource);
}
/**
* #inheritDoc
*/
public function applyScope(string $action, $resource)
{
return $this->authorization->applyScope($this, $action, $resource);
}
/**
* #inheritDoc
*/
public function getOriginalData()
{
return $this;
}
/**
* Setter to be used by the middleware.
* #param AuthorizationServiceInterface $service
* #return User
*/
public function setAuthorization(AuthorizationServiceInterface $service)
{
$this->authorization = $service;
return $this;
}
/**
* #inheritDoc
*/
public function getIdentifier()
{
return $this->id;
}
}
However, I have not been able to get the identity User in the UserPolicy.php file to have the user_type field populated.
Some under-the-hood magic seems to happen when I call $this->Authorization->authorize() from the controller where I explicitly pass the resource together with its user type (since I have constructed it with 'contain' => ['UserTypes'] BUT the identity user is populated automatically by the Authorization module.
Could someone please help me to find a way to bring associated tables data into the identity user of an authorization policy?
NOTE:
I have fudged the code to make it work like this:
##### in UserPolicy.php #####
use App\Model\Entity\User;
class UserPolicy
{
public function canView(User $user, User $resource)
{
$user = \Cake\Datasource\FactoryLocator::get('Table')->get('Users')->get($user->id, ['contain' => ['UserTypes']]);
// Now both $user->user_type and $resource->user_type are correctly populated
}
}
HOWEVER, this feels awfully "hacky" and not the way it's supposed to be, so my original question still stands.
The identity is being obtained by the resolver of the involved identifier. In case of the CMS tutorial that's the Password identifier which by default uses the ORM resolver.
The ORM resolver can be configured to use a custom finder in case you need to control the query for obtaining the user, that's where you should add the containment for your UserTypes association.
In your UsersTable add a finder like this:
public function findForAuthentication(\Cake\ORM\Query $query, array $options): \Cake\ORM\Query
{
return $query->contain('UserTypes');
}
and configure the identifier's resolver to use that finder like this:
$service->loadIdentifier('Authentication.Password', [
'resolver' => [
'className' => 'Authentication.Orm',
'finder' => 'forAuthentication',
],
'fields' => [
'username' => 'email',
'password' => 'password',
]
]);
You need to specify the resolver class name too when overriding the resolver option, as by default it is just a string, not an array that would merge with the new config!
See also
Cookbook > Database Access & ORM > Retrieving Data & Results Sets > Custom Finder Methods
Authentication Cookbook > Identifiers
I am using CakePHP 3.2
I am bit new to CakePHP. I am trying to get data from Icases table where the created date is within 10 days, but for some reason it's just returning the first row. Can anyone please let me know what I am doing wrong.
My Controller IcasesController
namespace App\Controller;
use App\Controller\AppController;
/**
* Icases Controller
*
* #property \App\Model\Table\IcasesTable $Icases
*/
class IcasesController extends AppController
{
public function index()
{
$case_count_data = $this->Icases->getCaseCountByAge();
print_r($case_count_data);
}
}
My Table class IcasesTable
namespace App\Model\Table;
use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;
use Cake\ORM\TableRegistry;
use DateTime;
class IcasesTable extends Table
{
/**
* Initialize method
*
* #param array $config The configuration for the Table.
* #return void
*/
public function initialize(array $config)
{
parent::initialize($config);
$this->table('icases');
$this->displayField('name');
$this->primaryKey('id');
$this->belongsTo('Clients', [
'foreignKey' => 'client_id'
]);
$this->hasMany('Documents', [
'foreignKey' => 'icase_id'
]);
$this->belongsToMany('Users', [
'foreignKey' => 'icase_id',
'targetForeignKey' => 'user_id',
'joinTable' => 'icases_users'
]);
}
/**
* Default validation rules.
*
* #param \Cake\Validation\Validator $validator Validator instance.
* #return \Cake\Validation\Validator
*/
public function validationDefault(Validator $validator)
{
$validator
->integer('id')
->allowEmpty('id', 'create');
$validator
->dateTime('date_instruction_received')
->allowEmpty('date_instruction_received');
$validator
->dateTime('date_online_invitation_last_sent')
->allowEmpty('date_online_invitation_last_sent');
$validator
->dateTime('date_approved_for_allocation')
->allowEmpty('date_approved_for_allocation');
$validator
->dateTime('date_consent_received')
->allowEmpty('date_consent_received');
$validator
->dateTime('go_date')
->allowEmpty('go_date');
$validator
->dateTime('date_last_referred_to_client')
->allowEmpty('date_last_referred_to_client');
$validator
->dateTime('date_last_referred_to_qc_report')
->allowEmpty('date_last_referred_to_qc_report');
$validator
->dateTime('date_last_referred_qc')
->allowEmpty('date_last_referred_qc');
$validator
->dateTime('date_last_referred_sub')
->allowEmpty('date_last_referred_sub');
$validator
->dateTime('date_last_state_change')
->allowEmpty('date_last_state_change');
$validator
->dateTime('date_marked_for_archival')
->allowEmpty('date_marked_for_archival');
$validator
->allowEmpty('state');
$validator
->integer('weight')
->allowEmpty('weight');
$validator
->requirePresence('case_reference_i_d', 'create')
->notEmpty('case_reference_i_d')
->add('case_reference_i_d', 'unique', ['rule' => 'validateUnique', 'provider' => 'table']);
$validator
->allowEmpty('special_intructions');
$validator
->allowEmpty('international');
$validator
->allowEmpty('name');
$validator
->requirePresence('sms_enabled', 'create')
->notEmpty('sms_enabled');
$validator
->allowEmpty('client_reference');
$validator
->allowEmpty('position_applied_for');
$validator
->integer('age')
->allowEmpty('age');
$validator
->dateTime('date_deleted')
->allowEmpty('date_deleted');
$validator
->dateTime('date_modified')
->allowEmpty('date_modified');
$validator
->dateTime('date_created')
->requirePresence('date_created', 'create')
->notEmpty('date_created');
return $validator;
}
/**
* Returns a rules checker object that will be used for validating
* application integrity.
*
* #param \Cake\ORM\RulesChecker $rules The rules object to be modified.
* #return \Cake\ORM\RulesChecker
*/
public function buildRules(RulesChecker $rules)
{
$rules->add($rules->isUnique(['case_reference_i_d']));
$rules->add($rules->existsIn(['client_id'], 'Clients'));
return $rules;
}
/**
* Gets Case count by age 0-10,10-15,15> days
* #return Array ['count0to10'=>00,'count10to15'=>00,'count15'=>00]
*/
public function getCaseCountByAge()
{
$casesTable = TableRegistry::get("Icases");
$query = $casesTable->find("all",['condition'=>['Icases.date_created >' => new DateTime('-10 days')]]);
$data = $query->execute();
return $data->fetch('assoc');
}
}
You should read and practice more according to CakePHP official Doc
public function getCaseCountByAge()
{
$casesTable = TableRegistry::get("Icases");
$query = $casesTable->find("all",['condition'=>['Icases.date_created >' => new DateTime('-10 days')]]);
$data = $query->execute();
return $data->fetch('assoc');
}
It could be very simple
public function getCaseCountByAge()
{
return $this->find("all",['condition'=>['Icases.date_created >' => new DateTime('-10 days')]])->all();
}
I have a problem with my cakephp3 app. I try to import some subscribers from a CSV file.
I use a Behavior to import my file (https://github.com/ProLoser/CakePHP-CSV). I haven't manage to load the plugin, so I have put the Behavior file on my project under src/Model/Behavior.
I have added the behavior to my SubscribersTable.
I have created a new view (import/export) and I have added this method to my SubscribersController :
public function importExport()
{
if ($this->request->is('post'))
{
$data = $this->request->data;
if(!empty($data['import_file']['name']))
{
$file = $data['import_file'];
if ((isset($file['tmp_name']) &&($file['error'] == UPLOAD_ERR_OK)))
{
$subscribersData = $this->Subscribers->importCsv($file['tmp_name']);
$entities = $this->Subscribers->newEntities($subscribersData);
$table = $this->Subscribers;
$table->connection()->transactional(function () use ($table, $entities) {
foreach ($entities as $entity) {
$table->save($entity, ['atomic' => false]);
}
});
}
}
}
}
The data I get seems to be good, as the entities created, but, when I call $table-save(), I have this error :
Table "App\Model\Table\SubscribersTable" is not associated with "request"
I have read some other questions on stackoverflow, but I don't understand why I have this error. I am new to cakephp so I don't understand everything...
If can someone help me...
Thanks !!
EDIT : It seems during the debug, the behavior is strange. Maybe this error has nothing to see with my real problem.
Here is the debug timeline :
$table->save() call:
if ($options['atomic']) {
$success = $connection->transactional(function () use ($entity, $options) {
return $this->_processSave($entity, $options);
});
} else {
===> $success = $this->_processSave($entity, $options);
}
...
$this->_processSave call :
$data = $entity->extract($this->schema()->columns(), true);
and during the extract, no column is retrieved because
public function extract(array $properties, $onlyDirty = false)
{
$result = [];
foreach ($properties as $property) {
if (!$onlyDirty || $this->dirty($property)) {
$result[$property] = $this->get($property);
}
}
return $result;
}
$onlyDirty = true and $this->dirty($property)) return false.
So, when this function is called
$success = $this->_insert($entity, $data);
as the data is null, nothing is saved.
I don't really understant the concept of dirty. In the doc, it seems it is usefull when working with BelongToMany, but this element has no link with other tables, so if someone can clarify this concept ?
SubscribersTable :
<?php
namespace App\Model\Table;
use App\Model\Entity\Subscriber;
use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;
/**
* Subscribers Model
*
*/
class SubscribersTable extends Table
{
/**
* Initialize method
*
* #param array $config The configuration for the Table.
* #return void
*/
public function initialize(array $config)
{
parent::initialize($config);
$this->table('subscribers');
$this->displayField('email');
$this->primaryKey('email');
$options = array(
// Refer to php.net fgetcsv for more information
'length' => 0,
'delimiter' => ',',
'enclosure' => '"',
'escape' => '\\',
// Generates a Model.field headings row from the csv file
'headers' => true,
// If true, String $content is the data, not a path to the file
'text' => false,
);
$this->addBehavior('Csv', $options);
}
/**
* Default validation rules.
*
* #param \Cake\Validation\Validator $validator Validator instance.
* #return \Cake\Validation\Validator
*/
public function validationDefault(Validator $validator)
{
$validator
->add('email', 'valid', ['rule' => 'email'])
->allowEmpty('email', 'create');
$validator
->allowEmpty('contact');
$validator
->allowEmpty('company');
$validator
->allowEmpty('postal_code');
$validator
->add('vip', 'valid', ['rule' => 'boolean'])
->allowEmpty('vip');
$validator
->add('indoor', 'valid', ['rule' => 'boolean'])
->allowEmpty('indoor');
$validator
->add('live', 'valid', ['rule' => 'boolean'])
->allowEmpty('live');
$validator
->add('prod', 'valid', ['rule' => 'boolean'])
->allowEmpty('prod');
$validator
->allowEmpty('localisation');
$validator
->allowEmpty('family');
return $validator;
}
/**
* Returns a rules checker object that will be used for validating
* application integrity.
*
* #param \Cake\ORM\RulesChecker $rules The rules object to be modified.
* #return \Cake\ORM\RulesChecker
*/
public function buildRules(RulesChecker $rules)
{
$rules->add($rules->isUnique(['email']));
return $rules;
}
}
This error likely comes from inside your SubscribersTable.php.
If you have $this->request ... in that file, it will throw that error because it's attempting to find request as an associated model, since it's not a valid method to call from a model.
Or, if it's in your controller, you likely have $this->Subscribers->request...
It could also be from inside another Model if you refer to it like this: $this->Subscribers->request ...
Ok.
As I thought, the problem wasn't this error (probably due to the use of the debugger), but it was the structure of my input file.
The input file must be formatted as indicated in this document :
http://book.cakephp.org/3.0/en/orm/saving-data.html#converting-multiple-records
The problem has been resolved !
Thanks again for you help !