I need all models inside a custom CakePHP plugin to use a database prefix. I'm trying to use an event, as suggested by #lorenzo.
EventManager::instance()->on('Model.initialize', function ($event) {
$instance = $event->subject();
$instance->table('prefix_' . $instance->table());
});
I'm getting several callbacks from my plugin model as well as DebugKit models, and potentially it could be other models in the application.
Is there a way to tell if a given $event is coming from within a plugin?
I have checked $event->getSubject() and it contains the corresponding Table class. The only feasible way I could come up with is to check some properties for the plugin name.
$event->getSubject()->getRegistryAlias() is ExamplePlugin.Posts
$event->getSubject()->getEntityClass() is ExamplePlugin\Model\Entity\Post
I could check if either starts with ExamplePlugin. Is there a better way?
The fact that basically any PHP namespace can be a plugin means you could do something like that:
EventManager::instance()->on('Model.initialize', function (\Cake\Event\EventInterface $event) {
/** #var \Cake\ORM\Table $object */
$object = $event->getSubject();
$tableClassName = get_class($object);
$isApp = str_starts_with($tableClassName, 'App');
});
Because your main app's namespace will always begin with App
This of course wouldn't distinguish between your private plugins which are located in plugins and plugins which are installed via composer and therefore live in the vendor directory.
But you could introduce a name prefix to all your private plugins so you can easily distinguish them from any other plugins.
Related
I'm converting my app to CakePHP 3.6, and working now on using the new Authorization plugin. I'm not sure how to check authorization for things like indexes or other reports, where there is no "resource" to pass to the can() or authorize() functions.
For now, I've built a ControllerResolver, loosely copied from the ORMResolver, which accepts controller objects and finds policies based on the singularized controller name, so that they're named the same as the Entity policies I'm building. (That is, my UserPolicy can have canIndex and canEdit functions, the former found via the controller and the latter via the entity.)
This works fine in controller actions where I can call $this->Authorize->authorize($this);, but it doesn't work in views, where I'd like to be able to do things like:
if ($this->Identity->can('index', *something*)) {
echo $this->Html->link('List', ['action' => 'index']);
}
so as to only show links to people who are allowed to run those actions.
Anyone know if there's a reason why the system implicitly requires that the "resource" passed into authorization functions be an object? (For example, the plugin component calls get_class($resource) in the case of a failed authorization, without first checking that the provided resource is in fact an object.) Allowing a string (e.g. \App\Controller\UsersController::class) would make my life easy. Very happy to put together a PR for this if it's just an oversight.
But authorizing indexes seems like a pretty obvious function, so I wonder if I've missed something. Maybe I'm supposed to pass the table object, and split the authorization between an entity policy and a table policy? But using table objects in views just for this purpose seems like a violation of separation of concerns. Maybe uses of the plugin to date have been things where indexes are always public?
to do this you can use the authorizeModel, as stated in the documentation https://github.com/cakephp/authorization/blob/master/docs/Component.md#automatic-authorization-checks. Basically is adding the auhtorizeModel parameters when you load the component at AppController.php
$this->loadComponent('Authorization.Authorization', [
'skipAuthorization' => ['login','token'],
'authorizeModel' => ['index','add'],
]);
When you configure an action to be authorized by model the authorization service uses the TablePolicy, so if you want to authorize the index action for Books you need to create the BooksTablePolicy and implement the method
<?php
namespace App\Policy;
use App\Model\Table\BooksTable;
use Authorization\IdentityInterface;
/**
* Books policy
*/
class BooksTablePolicy
{
public function scopeIndex($user, $query)
{
return $query->where(['Books.user_id' => $user->id]);
}
public function canIndex(IdentityInterface $identity)
{
// here you can resolve true or false depending of the identity required characteristics
$identity['can_index']=true;
return $identity['can_index'];
}
}
This will be validated before the request reaches your controller so you do not need to authorize anything there. Nevertheless if you want to apply an scope policy as you can see in this example:
public function index()
{
$user = $this->request->getAttribute('identity');
$query = $user->applyScope('index', $this->Books->find()->contain('Users'));
$this->set('books', $this->paginate($query));
}
I'm on CakePHP 3.3.16
I've put this in the main app's Bootstrap.php:
Log::write('info','foo');
$contactTable = \Cake\ORM\TableRegistry::get('Contacts.contacts');
$contactTable->eventManager()
->on('Model.afterSave', ['priority' => 1],function($event, $entity)
{
pr( "afterSave - " );
Log::write('info','afterSave');
});
I do get "foo" in the lofs, and yet when I save the plugin model in question, there's nothing in the logs. I've tried with and without priority.
Do I need to update CakePHP? I think a method has been depracated? The CakePHP event docs said to place this code in Bootstrap.php, so I'm confused...
Trying to use the Search Plugin from friendsofcake in my cakedc users plugin.
I used everthing like before (in my normal users/index.ctp it worked) and just added my custom index.ctp to the cakedc users controller, like so:
public function initialize()
{
parent::initialize();
$this->loadComponent('Search.Prg', [
'actions' => ['index']
]);
}
public function index()
{
$this->viewBuilder()->layout('backend');
$query = $this->Users
// Use the plugins 'search' custom finder and pass in the
// processed query params
->find('search', $this->Users->filterParams($this->request->query))
// You can add extra things to the query if you need to
->contain(['Skills'])
->where(['firstname IS NOT' => null]);
$this->set('users', $this->paginate($query));
}
But I am getting the error
Unknown method "filterParams"
Any ideas?
filterParams() was a method of the CakeDC search plugin. It's not available in the FoC plugin nor it still is in the CDC plugin. The FoC Search is not a drop in replacement but a completely different implementation. I've worked on both and I prefer FoC search because the code was written for Cake3 and is IMHO better than the other implementation that was "just" an upgrade from the Cake2 implementation.
I don't know from where you got filterParams() any way. I couldn't find it in the latest CDC docs nor the code. You might want to report it as a bug.
In Fusion's Javascript Indexing Stage, we can import Java classes and run them in the javascript such as this :
var imports = new JavaImporter(java.lang.String);
with (imports) {
var name = new String("foo"); ...
}
If we have customized complex Java classes, how to include the compile jar with Fusion so that the class can be imported in Javascript Indexing Stages for use?
And where can we store configuration values for the Javascript Indexing Stage to look up and how to retrieve them?
I'm thinking of something like this:
var imports = new JavaImporter(mycompany.com.custompkg.SomeParser);
with (imports) {
var some_config = ResourceManager.GetString("key");
var sp = new SomeParser(some_config); ...
}
Regards,
Kelvin
Starting in Fusion 4.x The API and Connectors started using a common location for jars i.e. apps/libs . This is a reasonable place to put custom jars but the services must be told about the new jars as well. That's done in two places
/jetty/connectors-classic/webapps/connectors-extra-classpath.txt
./jetty/api/webapps/api-extra-classpath.txt
Also, index documents can get processed by the api service so even if the jar is only used for indexing, register with both classpaths. Finally, bounce the services.
Put the Java class file, as a jar file, in $FUSION_HOME/apps/jetty/api/webapps/api/WEB-INF/lib/.
I used this to access my custom class.
var SomeParser = Java.type('mycompany.com.custompkg.SomeParser');
I'm developing a plugin that has an action which decide the view to render according to data properties:
example:
class ProfilesController extends MyPluginAppController {
public function myaction($id){
//..omitting checks..
$profile = $this->Profile->read(null,$id);
//Stuff
if($this->hasDedicatedViewFor($profile)){
$this->render('profiles'.DS.$this->getDedicatedViewFor($profile));
}
//Else render default action view
}
}
While this controller was inside the APP everything was working right,
after moving into Plugin, cake says:
Error: Confirm you have created the file:
.../app/View/Plugin/MyPlugin/Profiles/myaction_secondary.ctp
While I'd expect to load it from:
.../plugins/MyPlugin/View/Profiles/myaction_secondary.ctp
While I'd expect to load it from:
.../plugins/MyPlugin/View/Profiles/myaction_secondary.ctp
It is attempting to load the default plugin path
If you trace through the code for finding template files it is checking multiple paths for plugin view files:
$paths = $this->_paths($plugin);
$exts = $this->_getExtensions();
foreach ($exts as $ext) {
foreach ($paths as $path) {
if (file_exists($path . $name . $ext)) {
return $path . $name . $ext;
}
}
}
The path in the error message is the last place CakePHP looks for a plugin view path, but it should also be checking the plugins directory. Debug the above code to see where CakePHP is looking for files if in doubt.
The path being wrong in the error message probably indicates using an out of date version of CakePHP; it's always, always a good idea to maintain your applications running the most recent maintenance release for the major version you are using. From the info provided that's 2.7.3 at the time of this answer.