OctoberCMS Related Model Dynamic Default Value - laravel-5

I have a backend controller implements Backend\Behaviors\RelationController, I just want to set default values for the related model depending on the parent model values.
I have tried the following: model.beforeCreate, formExtendFields but no luck.
Thank you all.

After whole day of searching, I found the solution, it is documented no where on OctoberCMS website, I inspected the source file Backend\Behaviors\RelationController, after that I came with the below solution.
You should implement relationExtendViewWidget on your controller, then you can access the model from: $widget->model, something like below:
class Members extends Controller
{
public $implement = [
'Backend\Behaviors\RelationController',
];
public function relationExtendViewWidget($widget, $field)
{
$member = Member::findOrFail($this->params[0]);
$widget->model->course_id = $member->course_id;
$widget->model->member_id = $member->id;
}
public function relationExtendManageWidget($widget, $field)
{
$member = Member::findOrFail($this->params[0]);
$widget->model->course_id = $member->course_id;
$widget->model->member_id = $member->id;
}
}
I believe this should be documented somewhere on OctoberCMS documentation

Related

Define fields based on resource's model attributes in Laravel Nova

I have a (relatively) basic need in Nova that I can't seem to figure out and I slowly start to feel that I'm approaching things the wrong way.
So, I've got a User, Company, Device and Transfer models and respectively resources, everything pretty default regarding the resource setup.
The schema is the following:
users: id, company_id
companies: id, type_id, name where type_id is pointing to one of three pre-populated types (manufacturer, dealer, client)
devices: id, imei
transfers: id, from_company_id, to_company_id, accepted_at
and Transfer is in a Many-to-Many with Device.
The idea behind the transfers being that Manufacturers transfer to Dealers, Dealers transfer to Clients, so it's really only a one-way thing.
Now the problem occurs at the following crucial point in the logic:
In my Transfer resource pages, I want to show different fields depending on the type of the company the currently authenticated user belongs to. Basically, if the company is:
Manufacturer, then display a DEALER column populated with the transfers' toCompany relation;
Dealer, then display a CONTRAGENT column populated with the transfers' fromCompany or toCompany relations (depending on which mathces the current auth() company)
Client, then display a DEALER column populated with the transfers' fromCompany
All of the described logic works fine with the following code (App\Nova\Transfer.php as is) UNTIL I wanted to finally display the transfer's devices on the details page:
<?php
namespace App\Nova;
use Illuminate\Http\Request;
use Laravel\Nova\Fields\ID;
use Laravel\Nova\Fields\BelongsTo;
use Laravel\Nova\Fields\BelongsToMany;
use Laravel\Nova\Http\Requests\NovaRequest;
class Transfer extends Resource
{
public static $model = \App\Models\Transfer::class;
public static $title = 'id';
public static $search = [
'id',
];
public static $with = [
'fromCompany',
'toCompany'
];
public function fields(Request $request)
{
$company = auth()->company();
if($company->hasType('manufacturer'))
{
$contragentTitle = 'Dealer';
$contragent = 'toCompany';
}
else if($company->hasType('dealer'))
{
//\Debugbar::info($this); //showing empty resource when populating the devices
$contragentTitle = 'Contragent';
$contragent = $this->fromCompany->is($company) ? 'toCompany' : 'fromCompany'; //exception here, since the resource is empty and fromCompany is null
}
else
{
$contragentTitle = 'Dealer';
$contragent = 'fromCompany';
}
$contragentCompanyField = BelongsTo::make("$contragentTitle company", $contragent, Company::class);
if($company->hasType('dealer'))
{
$contragentCompanyField->displayUsing(function ($contragentCompany) use ($contragent){
return $contragentCompany->title() . " (".($contragent == 'toCompany' ? 'Outgoing' : "Incoming").')';
});
}
return [
ID::make(__('ID'), 'id')->sortable(),
$contragentCompanyField,
BelongsToMany::make('Devices') //problematic field, when removed, everything is fine...
];
}
public static function indexQuery(NovaRequest $request, $query)
{
if(auth()->check())
{
return $query->where(function($subQuery){
return $subQuery->where('from_company_id', auth()->company()->id)->orWhere('to_company_id', auth()->company()->id);
});
}
}
public function cards(Request $request)
{
return [];
}
public function filters(Request $request)
{
return [];
}
public function lenses(Request $request)
{
return [];
}
//action is working fine (additional canRun added to avoid policy conflicts)
public function actions(Request $request)
{
return [
(new Actions\AcceptTransfer())->showOnTableRow()->canSee(function ($request) {
if ($request instanceof \Laravel\Nova\Http\Requests\ActionRequest) {
return true;
}
return $this->resource->exists
&& $this->resource->toCompany->is(auth()->company())
&& $this->resource->accepted_at === null;
})->canRun(function ($request) {
return true;
})
];
}
}
Now the strange thing that is happening is that the fields() method gets called multiple times on multiple ajax requests behind the scenes with Nova and when populating the devices relationship table, it gets called without a resource, although a call is never actually needed (as far as I can grasp the mechanics behind Nova) or at least when fetching relationships, you must still have the model information (at least the ID) somewhere to fetch by... So basically, if I'm a user of a dealer company, I can't see the devices that are being transferred (currently throwing a calling is() on null exception).
Now, this happens to be a big problem, since it hinders most of the stuff I need for my transfers, but also generally I don't like my approach so far, so... What would be the right way to achieve this multi-layer resource? Ideally I'd like to define three different transfer resource classes and somehow tell nova which one to use based on the user's company's type (since branching will most probably just grow more complex and therefore uglier as of the current aproach), but I can't figure out the way to do so.
I've also considered moving this entire logic to a separate Nova tool, but I really don't know much about them yet and whether that would be the right option... The only thing stopping me is that I still won't be able to elegantly solve the multi-layer problem and will have to write much of the otherwise useful Nova CRUD logic and views myself...
Any explanations (regarding the multiple calls of fields() and why resource is empty) or general structural recommendations to solve this case would be greatly appreciated! Many thanks in advance!
EDIT:
I was able to circumvent the error by taking advantage of viaResourceId, so instaed of $this I ended up using:
$transfer = $this->id ? $this->resource : \App\Models\Transfer::find($request->viaResourceId);
but the messy code and the unneeded calls still remain an open question. Thanks again in advance!
Here is an example of how I handled this:
public function fields(NovaRequest $request)
{
/** #var \App\Models\User $user */
$user = $this->id ? $this->resource : \App\Models\User::find($request->viaResourceId);
if ($user && $user->whatEver()) {
// display special fields in preview/detail view
return [...];
}
// display for index and if no model is found
return [...];
}

Argument 1 passed to Spatie\Activitylog\ActivityLogger::performedOn() must be an instance of Illuminate\Database\Eloquent\Model, string given

I am using Spatie activity log package.
Within my controller, I am trying to pass the model name but I get the on my question's title.
See my Model below:
class Project extends Model
{
//
use SoftDeletes;
protected $softDelete = true;
protected static $logAttibutes = ['projectname','duedate','team_id','assignee_id',
,'projecttype_id'];
public static $logName = 'project';
public function getDescriptionForEvent(string $eventName): string
{
return "You have {$eventName} project" ;
}
The event logging for viewing a list happens in the controller. As shown below:
public function index()
{
//
$projects = Project::all();
activity()
->useLog('Projects')
->withProperties(['type'=>'view project list'])
->performedOn(Project::class)
->log('viewed the project list');
return view('projects.index',['projects'=>$projects]);
}
On performedOn, I also tried:
->performedOn('App/Project')
The documentation says
->performedOn($someContentModel)
This is just a variable and I know a variable needs to be populated with some data, but I think I'm struggling to understand the format of that data
Can you try this :
$project = new Project();
--
$activity()
->performedOn($project)
That means use the object of project as argument which we can also call, the instance of model.

Laravel policies - passing the class as a variable to $user->can() method doesn't work

I have a route with dynamic model recognition. In other words, I take the desired model as an argument and use it in the controller. I have complex authorization in my app and I need to pass the model class name as a variable to the $user->can() method for using policies, but for some reason it doesn't work. Here's my code:
Policy:
public function view($user, Model $model) {
return $user->model_id == $model_id;
}
public function create($user) {
return $user->isAdmin();
}
Controller:
public function createModel($model) {
$model_class = $model . '::class';
if (Auth::user()->can('create', $model_class)) {
return $model_class::create();
}
return 'invalid_permissions';
}
If I hardcode the model class name it works. For example, if my model is 'Car' and in the controller I put:
if (Auth::user()->can('create', Car::class)) {
Anybody got any ideas why this is so and how to fix it? I hope that it's possible because I would have to change my whole concept if it isn't.
*Note: this is example code, not my actuall classes

Do not use "flat" data in magento

Having read Fixing Magento Flat Collections with Chaos by Alan Storm and finding a similar SO question here I am trying to return products that are in a category but without using Magento's "flat" data.
Here is the code that I originally used:
$category_model = Mage::getModel('catalog/category')->load($cid);
$collection = Mage::getModel('catalog/product_collection');
$collection->addCategoryFilter($category_model);
$collection->addAttributeToSort('entity_id','DESC');
$collection->addAttributeToSelect('*');
$collection->printLogQuery(true);
When this code gets fired through AJAX I get different results than when I run it from an observer and the reason is because of flat data. So I have written my own classes that are meant to use the EAV model:
app/code/local/Mynamespace/Mymodule/Model/Category.php:
class Mynamespace_Mymodule_Model_Category extends Mage_Catalog_Model_Category
{
protected function _construct()
{
$this->_init('catalog/category');
}
}
And:
app/code/local/Mynamespace/Mymodule/Model/Productcollection.php:
class Mynamespace_Mymodule_Model_Productcollection
extends Mage_Catalog_Model_Resource_Product_Collection
{
protected function _construct()
{
$this->_init('catalog/product');
$this->_initTables();
}
}
And then change my query code:
$category_model = Mage::getModel('mymodule/category')->load($cid);
$collection = Mage::getModel('mymodule/productcollection');
$collection->addCategoryFilter($category_model);
$collection->addAttributeToSort('entity_id','DESC');
$collection->addAttributeToSelect('*');
$collection->printLogQuery(true);
However the above code is still querying the flat data table. What is it I may be doing wrong?
since you already rewrote Mage_Catalog_Model_Resource_Product_Collection simple rewrite
public function isEnabledFlat()
{
if (Mage::app()->getStore()->isAdmin()) {
return false;
}
if (!isset($this->_flatEnabled[$this->getStoreId()])) {
$this->_flatEnabled[$this->getStoreId()] = $this->getFlatHelper()
->isEnabled($this->getStoreId());
}
return $this->_flatEnabled[$this->getStoreId()];
}
to
public function isEnabledFlat()
{
return false;
}
that should fix it :)
To disable flat index for the current request, you can also set the configuration temporarily without saving it as described here:
Mage::app()->getStore()->setConfig(
Mage_Catalog_Helper_Product_Flat::XML_PATH_USE_PRODUCT_FLAT, '0');
It is important that this is done before the product collection has been initialized because that's where the configuration is checked (via isEnabledFlat().

How To Start Using Kostache?

I just asked a question ( Templates In Kohana 3.1 ) about templates and now I know that I should use Kostache. It's a module for the Mustache template language.
Anyway, I just enabled Kostache module for my Kohana 3.1 and all works. It's installed correctly! What to do next? How to use it?
Where should I put my views now?
What my controller should extend?
How to assign variable?
How to make header, footer etc. for views?
Maybe there are step to step guide for it? This and this won't help me a lot...
Where should I put my views now?
View classes contain logic for your templates and by convention should be stored in classes/view/{template name}.php
Templates contain your HTML and should be stored in the templates directory in the root of your module, e.g. templates/login.mustache
By default kostache will try and work out the location of the template based on your view class' name.
If your view class is called View_Admin_Login then kostache will look for templates/admin/login.mustache
What my controller should extend?
You do not need to extend any special controllers, the normal Controller will work fine as a base.
How to assign variable
Controller:
$view = new View_Admin_Login;
$view->message = 'Hello';
$this->response->body($view->render());
Template:
{{message}}
Of course, any methods or variables you declare in your view class will also be available in
the template. If there is a class variable and method with the same name then the method will always take precedence over the variable.
How to make header, footer etc. for views
It will help if you read the kostache guide. The idea is that your views extend Kostache_Layout, see also the layout template
There's lots of demos and examples in both of the repositories that you said won't help you.
Try this...
//application/classes/controller:
class Controller_Test extends Controller {
public function action_index()
{
$view = new View_Home;
$this->response->body($view->render());
}
}
//application/classes/view/Home.php:
class View_Home {
public $name = "Chris";
public $value = 10000;
public function taxed_value() {
return $this->value - ($this->value * 0.4);
}
public $in_ca = true;
protected $_layout = 'home';
}
//application/templates/home.mustache:
Hello {{name}}
You have just won ${{value}}!
{{#in_ca}}
Well, ${{ taxed_value }}, after taxes.
{{/in_ca}}
In your APPPATH/classes/controller/Test.php:
class Controller_Test extends Controller{
public function action_index()
{
$renderer = Kostache::factory();
$this->response->body($renderer->render(new View_Test));
}
}
In your MODPATH/KOstache/classes/view/Test.php:
class View_Test
{
public $name = "Chris";
public $value = 10000;
public function taxed_value() {
return $this->value - ($this->value * 0.4);
}
public $in_ca = true;
}
In your MODPATH/KOstache/classes/templates/test.mustache:
Hello {{name}}
You have just won ${{value}}!
{{#in_ca}}
Well, ${{ taxed_value }}, after taxes.
{{/in_ca}}
In the following example, do not pay attention to naming classes and inheritance:
More examples on GitHub

Resources