How to override a model class in PyroCMS (Laravel, PHP)? - php

I installed PyroCMS and am extending it to make it into a Learning Management System (LMS) where only logged-in users can view the pages, and the pages also only begin to be viewable a variable number of days after a user enrolls in the course.
(I.e., Module 1's Lesson 1 may unlock and be visible immediately, but Lesson 2 could be configured to be hidden until 1 day later, and Lesson 3 might become visible X days later, etc.)
How I achieved this was by writing a Laravel package with this migration:
Schema::table('pages_pages', function (Blueprint $table) {
$table->string('drip_delay')->nullable()->after('str_id');
});
I then created a DrippablePagesServiceProvider class with this in the boot() function:
$this->app->bind('Anomaly\PagesModule\Http\Controller\PagesController', 'me\DrippablePages\PagesController'); //https://laravel.com/docs/5.6/container#binding
I designed my custom PagesController to show a special view whenever the logged-in user is trying to access a page too early. This functionality is all working totally fine.
But instead of editing the drip_delay field directly in the database like I've been doing, I'd prefer to be able to edit right alongside the other fields at the /admin/pages/edit/4 URL.
I'm pretty sure I need to override various parts of PagesModule, such as PageEntryFormSections (doc). And I think I have that working.
But when stepping through with Xdebug, I see that the PageModel that gets looked up at this line (via dependency injection?†) in edit() within Http\Controller\Admin\PagesController still doesn't show my new drip_delay field.
How can I override PageModel or do whatever I need to do so that it shows the drip_delay field in this Admin panel view?
† Laravel docs about container and controllers imply this.

To override a model first you need a new one which extends a model you want to override:
<?php namespace Ryan\ExtenderModule\Post;
class PostModel extends \Anomaly\PostsModule\Post\PostModel
{
}
Then inside the ServiceProvider you need to bind it reversed:
<?php namespace Ryan\ExtenderModule;
use Anomaly\PostsModule\Post\PostModel;
use Anomaly\Streams\Platform\Addon\AddonServiceProvider;
class ExtenderModuleServiceProvider extends AddonServiceProvider
{
protected $bindings = [
PostModel::class => \Ryan\ExtenderModule\Post\PostModel::class,
];
}
That's all. Good luck ))

Related

Silverstripe Elemental Module redirects to frontend 404 page on element save

I use dnadesign/silverstripe-elemental 2.x-dev, and Silverstripe 4.0.1.
I created a module for page to hold all pages. This is how i added the extension to HomePage.
XYPage\Model\HomePage:
extensions:
- DNADesign\Elemental\Extensions\ElementalPageExtension
This is my HomePageController:
namespace XYpage\Controller;
use PageController;
class HomePageController extends PageController
{
}
This is my HomePageModel:
namespace XYpage\Model;
use Page;
use XYpage\Controller\HomePageController;
class HomePage extends Page
{
private static $table_name = 'HomePage';
/**
* As our controller resides in a different namespace we have to
overwrite this method
*
* #return string
*/
public function getControllerName()
{
return HomePageController::class;
}
}
I changed the template variable to $ElementalArea. I see the expected UI in the BackEnd.
Now if i save an single element in the backend i always get redirected into the front-end to the 404 Page.
When i switch back to the Backend the element is linked to the page.
If i fill in content to the WYSIWYG editor on the Content Element and save it the content gets displayed on the page in the frontend.
If i try to edit that element misses the WYSIWYG editor for the content.
I tried hard to fix this, red the docs but i don´t see what i did wrong.
There is an issue with BetterButtons & DNADesign Elemental.
Just add this to disable BetterButtons for ElementContent in the meantime.
DNADesign\Elemental\Models\ElementContent:
better_buttons_enabled: false
You have two problems that I can see immediately:
1: Your YAML configuration is referencing XYPage\HomePage, where the class's namespace is actually XYPage\Model\HomePage. I suspect this is actually an error in your example rather than your actual project, since you say that the elemental editor is working in the CMS.
2: Your getControllerName() method is returning HomePageController::class which isn't imported in the class, so it will be resolving to the same namespace as the model (XYPage\Model\HomePageController). While this is the default/expected location for SiteTree controllers, overloading this code means it's all on you! Add use XYPage\Controller\HomePageController; to your class definition.
After a lot of debugging i found out what caused this some of that behavior. In my case one problem was that i used unclecheese/silverstripe-gridfield-betterbuttons with elemental.
The next Problem is a react error:

MVC: what code belongs to the model

I've started development on a CakePHP project since a few weeks now. Since the beginning I was struggling with the amount of code inside the controllers. The controllers have, in most cases more lines of code than the models. By knowing the expression "Skinny controller, fat model" I'm searching for some days now for a way to put more code in the models.
The question arises at this point is, "where to draw the line". What should the controller do and what should the model do. There are already some questions/answers on this only I'm searching for a more practical explanation. For example I've put a function below which is now inside the controller. I think a part of this code must and can be moved to the model. So my question is: what part can I move to the model and what can remain in the controller.
/**
* Save the newly added contacts and family members.
*/
public function complete_contacts()
{
if ($this->request->is('post')) {
if (isset($this->data['FamilyMembers'])) {
$selected_user = $this->Session->read('selected_user');
$family_members = $this->data['FamilyMembers'];
$this->ContactsConnection->create();
foreach ($family_members as $family_member) {
// connection from current user to new user
$family_member['ContactsConnection']['contact_family_member_id'] = $selected_user['id'];
$family_member['ContactsConnection']['nickname'] = $selected_user['first_name'];
$this->ContactsConnection->saveAll($family_member);
// inverted connection from new user to current user
$inverted_connection['ContactsConnection']['family_member_id'] = $selected_user['id'];
$inverted_connection['ContactsConnection']['contact_family_member_id'] = $this->FamilyMember->inserted_id;
$inverted_connection['ContactsConnection']['nickname'] = $family_member['FamilyMember']['nickname'];
$this->ContactsConnection->saveAll($inverted_connection);
}
}
}
}
Should I create a function in the FamilyMember model called: "save_new_family_member($family_member, $selected_user)"?
As far as the purposes of the M and the C
The model manages the behavior and data of the application domain,
responds to requests for information about its state (usually from the
view), and responds to instructions to change state (usually from the
controller).
The controller receives user input and initiates a response by making
calls on model objects. A controller accepts input from the user and
instructs the model and a view port to perform actions based on that
input.
I would suggest you can pass
$selected_user = $this->Session->read('selected_user');
To your Model and perform your for each inside of your Model. You may want to change rules as to how the data is stored or perform some transformations on it and the Controller should be blind to this. Basically use the Controller to get your information [from the View often] to the Model. Don't directly manipulate the Model from the Controller. In short YES create the function that you suggested :)
That being said sometimes I find myself in a position where my Controller has to do more than I'd like, in which case at least break the task down into helper methods that way your controller is more manageable and you can reuse code where needed.
You are doing it right.
You can of course create some methods in model and make it fat with:
function updateContactFamilyMemberId($id){}
function updateNickname($nickname){}
...
In my opinion it still will be correct, but unnecessary.

symfony sfDoctrineRoute model question

I couldn't understand completely how does the sfDoctrineRoute class works
for example, i have the following route:
Comment:
class: sfDoctrineRouteCollection
options:
prefix_path: :username/comment
module: comment
model: Comment
now, in executeNew() method of commentActions class, this code:
$this->getRoute()->getObject()
will return the first Comment object in my database. of course i can manually create a new Comment() object, but then what's the benefit of using the sfDoctrineRoute class instead of sfRoute?
In the case of executeNew, there is little/no benefit to using a doctrine route.
Consider instead the executeEdit method (update, delete and show are the same too).
A url could be like:
/comment/5/edit
(or in your case, /myusername/comment/5/edit)
$this->getRoute()->getObject() will then return comment 5 from the database - saving you the trouble of loading it (only a line or 2 of code, but still). And, a neat feature, if there is no comment 5 in the database, it automatically handles this and causes a 404 error - so you don't need to worry about that either.

Admin panel - what is the best way to display "static" data in the layout?

I'm about to write a admin panel for my CMS written in CodeIgniter. There will be some user information visible at all time - both in the layout's header section and the sidebar. I'm used to do it in a way that I personally hope and think could be done a lot easier, since I'm tired of sending the same parametres to the view over and over again, when it's dynamic data that needs to be displayed on every page anyways (such as unread messages, username, name, status, etc).
I'll need controllers and models, I know that, but do I have to pass, just for an example, the user's username, unread messages etc. every time I need to load a view? Should I do some kind of library for this?
Now my question is: How would I do it when it comes to best practice and for making it easy to maintain in the future?
I hope my question is understandable :)
Personally, I would extend the Controller library (create a MY_Controller by following the guidance at the bottom of Creating Libraries at codeigniter.com).
You would use your model etc as normal. Then you would create a private function in your MY_Controller class to get the relevant "global" data and call
$this->load->vars('everywhere_data', $data_from_relevant_models);
which would make the data available to all views called from that point on as $everywhere_data. Then add a reference to that function in the constructor of MY_Controller, perhaps with a conditional checking for the user to be actually logged in.
If it's complex to collect and get all that data, you might write a library to handle it for you, but the 'controller' part would still be done by MY_Controller: i.e. to get the data and then use load->vars() to publish it to the view.
As a quick and untested example, MY_Controller would start something like as follows:
<?php
class MY_Controller extends Controller
{
private $logged_in_user;
function MY_Controller()
{
parent::Controller();
if( $this->_logged_in_userid() > 0 )
{
$this->logged_in_user = $this->_get_user( $this->logged_in_userid() );
$this->load->vars('logged_in_username', $this->logged_in_user->username );
} else {
$this->logged_in_user = false;
}
}
...
}
Note that things like _logged_in_userid() would access the session for you (e.g. return $this->session->userdata('logged_in_userid');), and _get_user() would access the relevant models.
Finally, you would have a view that accesses $logged_in_username (or everywhere_data in my first example) which you would call into your headers etc. This leaves your normal controllers uncluttered so that they can focus on delivering their specific functionality, stops you rewriting your code several times AND maintains the MVC ideals.
You could create a View just to hold the information and get it from a $_SESSION variable in the View itself if you want to keep it all in one place.

widget within module in Yii

I'm trying to create a widget within the module and then load that widget from 'outside' of the module. More particularly I'm using user module written by someone else. I don't want to have a separate page for displaying a login form, therefore I tried to make a CPortlet/widget (confusion) displaying the login form. Basically, I've moved the code from LoginController into that widget. Then I try to display the widget on some random page by
<?php $this->widget('user.components.LoginForm'); ?>
However, I get an error
CWebApplication does not have a method named "encrypting".
in UserIdentity class in this line:
else if(Yii::app()->controller->module->encrypting($this->password)!==$user->password)
This happens, because I'm basically trying to execute this code within context of the app and not the module. Thus the "Yii::app()->controller->module" trick doesn't really work as expected.
What am I doing wrong:-\
Is there a better way to achieve this. I.e. display that login form in some other page, which is normally displayed by accessing login controller within user module (user/login) or is a widget the right way of doing it?
Thanks.
The quick solution
Ok, so I simply ended up doing
Yii::app()->getModule('user')->encrypting($this->password)
instead of
Yii::app()->controller->module->encrypting($this->password)
Notice that now the module must be called 'user' in the main config, but I think this allows for more flexibility. I.e. we're not bound to only use module functionality within the module.
Additional insight on displaying widget outside of the module scope
After playing more with it that's what I did. In the UserModule.php I've created a method
public static function id() {
return 'user';
}
Then everywhere where I need the module I use
Yii::app()->getModule(UserModule::id())->encrypting($this->password)
I don't like having many imports related to the module like:
'application.modules.user.models.*',
'application.modules.user.components.*',
Because we already have those imports in the UserModule.php:
public function init()
{
// this method is called when the module is being created
// you may place code here to customize the module or the application
// import the module-level models and components
$this->setImport(array(
'user.models.*',
'user.components.*',
));
}
Therefore whenever you know that some piece of functionality will be used outside of the module it's important to make sure the module is loaded. For example, in the LoginForm widget that I am trying to display NOT in one of the module controllers, I have this line of code:
$model = new UserLogin;
However, UserLogin is a model inside of the User module, and in order to be able to autoload this model we first have to make sure the module was initialised:
$module = Yii::app()->getModule(UserModule::id());
$model = new UserLogin;
I hope this will be helpful if you were stuck with the whole modules concept the way I was.
http://www.yiiframework.com/forum/index.php?/topic/6449-access-another-modules-model/ was useful but hard to find =)
You better move that encrypting() into a MyUserIdentiy class which extends CUserIdentity. Whatever the code you take to use, they putting the method in controller is a bad idea and as a result you cannot reuse that code.
The login form should still post to User/Login controller but I guess they use Yii's standard login code and you might want to modify it to use the MyUserIdentity.

Categories