I'm writing a subscribe module to plugin to the SilverStripe Blog module. So far I have my yml as:
---
Name: subscription
After: 'framework/*','cms/*'
---
Blog:
extensions:
- Subscription
Page_Controller:
extensions:
- SubscriptionWidget
And my SubscriptionWidget.php:
<?php
class SubscriptionWidget extends DataExtension {
public function SubscriptionWidget() {
$controller = SubscriptionWidget_Controller::create();
$form = $controller->SubscriptionWidget();
return $form;
}
}
class SubscriptionWidget_Controller extends Controller {
private static $allowed_actions = array('SubscriptionWidget');
public function SubscriptionWidget () {
$form = Form::create(
$this,
__FUNCTION__,
FieldList::create(
TextField::create('Email', 'Email'),
TextField::create('Name', 'Name')
),
FieldList::create(
FormAction::create('submit', 'Subscribe')
)
);//->setTemplate('SubscriptionWidget');
return $form;
}
public function submit($data, $form) {
return $this->redirect('/subscribed');
}
}
At the moment this works as intended however another plugin I use called BetterNavigator disappears from the screen. If I take out
Page_Controller:
extensions:
- SubscriptionWidget
from my yml it reappears. I've looked through both code bases which are quite simple and there are no conflicting functions. I've also tried using ContentController instead of Page_Controller and my template disappears until I disable BetterNavigator and then it reappears. I do have one or two pretty empty classes but all are called some variation of Subscriber while there is only one function in BetterNavigator called BetterNavigator.
Why would this be happening?
I see only one clash in your code that results in incorrect runtime behaviour. Your method SubscriptionWidget::SubscriptionWidget() is treated as legacy class constructor. So I suggest you thinking about better class and method names.
class SubscriptionWidget extends Extension
{
// explicitly defined constructor
public function __construct() {
parent::__construct();
}
// now this one is normal function
public function SubscriptionWidget() {
$controller = SubscriptionWidget_Controller::create();
$form = $controller->SubscriptionWidget();
return $form;
}
}
Related
My goal is it to add a custom FormAction to a DataObjects EditForm inside a ModelAdmin, next to the "Save" and "Delete" action.
The setup:
With updateCMSActions I can add that button.
class MyActionEventExtension extends DataExtension {
public function updateCMSActions(FieldList $actions) {
if($this->owner->canEdit(Member::currentUser())) {
$actions->push(FormAction::create('doMyAction', 'Action'));
}
return $actions;
}
}
This works perfectly fine.
Following this answer on stackoverflow I created a LeftAndMainExtension for the actions handler.
class MyActionLeftAndMainExtension extends LeftAndMainExtension {
private static $allowed_actions = array(
'doMyAction'
);
public function doMyAction($data, $form) {
// Do stuff.
// ... I never get here.
// Return stuff
$this->owner->response->addHeader(
'X-Status',
rawurlencode('Success message!')
);
return $this->owner->getResponseNegotiator()
->respond($this->owner->request);
}
}
The corresponding config.yml file looks like this:
LeftAndMain:
extensions:
- MyActionLeftAndMainExtension
TheDataObject:
extensions:
- MyActionEventExtension
The Problem:
When I click the button, the response gives me "404 Not Found".
The requested URL always is the same:
http://localhost/admin/model-admin-url-slug/TheDataObject/EditForm/field/TheDataObject/item/878/ItemEditForm
Some other solutions I found, suggested to extend the ModelAdmins GridField. This sadly is not an option, since the DataObject I need that action for has alot of relations, which means, it's EditForm also appears in other DataObjects EditForms (nested).
I'm really running out of ideas. Did I miss something within my ModelAdmin? The one I created only implements the basic static vars, so I didn't posted it here.
Any help would be great!
Update:
I ended up, providing a getEditForm method on my ModelAdmin.
public function getEditForm($id = null, $fields = null) {
$form = parent::getEditForm($id, $fields);
$listField = $form->Fields()->fieldByName($this->modelClass);
if ($gridField = $listField->getConfig()->getComponentByType('GridFieldDetailForm')) {
$gridField->setItemRequestClass('MyAdminForm_ItemRequest');
}
return $form;
}
and extending the GridFieldDetailForm_ItemRequest:
class MyAdminForm_ItemRequest extends GridFieldDetailForm_ItemRequest {
private static $allowed_actions = array (
'edit',
'view',
'ItemEditForm'
);
public function ItemEditForm() {
$form = parent::ItemEditForm();
$formActions = $form->Actions();
// Adds all FormActions provided by the model's `getCMSActions` callback
if ($actions = $this->record->getCMSActions()) {
foreach ($actions as $action) {
$formActions->push($action);
}
}
return $form;
}
public function doAction($data, $form) {
// do stuff here
}
}
Sadly this doesn't add an action on has_many or many_many relation gridfields.. and because of that, I'll leave the question opened and unanswered. Maybe sometime there will be a better solution.. :)
One very simple answer to this question (if it's an option for you) is to use the Better Buttons module: https://github.com/unclecheese/silverstripe-gridfield-betterbuttons#creating-a-custom-action
It lets you define the actions on the model, which is a bit questionable from an architecture standpoint, but also works pretty well in the context of Silverstripe and ModelAdmin.
Lets say I am building an OOP-based user authentication system, and I would like to incorporate the following principles: Direct Injection, Inheritance, Encapsulation, Polymorphism and the Single Responsibility Principle.
My background in programming is has always relied on procedural programming, and thus, am finding it difficult to really put these practices into correct use.
Assume I have these classes:
class Config
{
public function set($key, $value);
public function get($key, $default = null);
}
class User
{
public function __construct(PDO $dbh, $id = null);
public function setProfile(Profile $profile);
}
class Auth
{
public function __construct(Config $config);
public function login($username, $password, $keepLoggedIn = true);
public function isLoggedIn();
public function getLoggedInUser();
public function logout();
public function register(array $data);
}
class Session
{
public function start($sessionName = null);
public function write($key, $value);
public function read($key, $default = null);
}
class Profile
{
public function setAddress(Address $address);
public function setName($name);
public function setDOB(DateTime $date);
public function getAge();
}
class Validator
{
public function validate($input);
}
I have intentionally left off the function bodies to keep things simple.
To the best of my knowledge, I believe I'm using the principles correctly. However, I am still unclear as to how you would connect classes like: the Validator to the User model, the User model to the Auth and the Session to the Auth class. All of which depend on each other.
You are on the right track. The way these classes connect to each other is called extending. I tend to go towards an MVC setup, meaning Model, View, Controller.
Your logic goes into the controller, all your DB queries and concrete back end methods go in the model. The controller receives requests and returns responses. It's the middleman. It talks to the back end after a request has been made to it, and feeds the front in via response.
So you have a core controller (keep it bare minimal), then each class you make extends the core controller. So your controller is where you tie all this together.
<?php
//your main core controller, where you load all these things you need avilable, so long as this class is extended
class CoreController {
public $auth
public $session;
public $view;
function construct__ ()
{
$this->auth = instantiateAuthClassHere();
$this->session = instantiateSessionClassHere();
$this->view = instantiateViewClassHere();
}
public function anotherHelperForSomething(){
//helper stuff for this method
}
}
//index, page, or content controller, depending on how many you need, i.e. if you want a controller for each page, thats fine, e.g indexController, etc..
//this is the middle man, has logic, receives requst, returns response to view.
class Controller extends CoreController {
public function index (){
$userModel = new userModel();
//do something with this
$session = $this->session;
$content = 'some html';
$userInfo = $userModel->getUsers();
$view = $this->view->render( array(
'content' => $content,
'userInfo' => $userInfo,
));
return $view;
}
}
//Core LIbraries
class Validator {
//your validator stuff
}
//Core LIbraries
class Session {
//your validator stuff
}
//Core LIbraries
class Auth {
//your validator stuff
}
class CoreModel{
public $validator;
function __construct(){
$this->validator = instantiateValidatorClassHere();
}
}
//a user model class (back end). you want a model class for each db table pretty much.
class UserModel extends CoreModel {
// if you need the validator anywhere inside this class, its globally available here inside any class that extends the CoreModel, e.g. $this->validator->methodName()
public function getUsers (){
$sql = 'SELECT * from users';
$result = $db->get($sql);
return $result;
}
}
Notice, on the Controller, this is a generic name for something like indexController, or anything custom. Also, I have the word extends there. It inherits all the objects from the parent that it extends. Inside it, now they will be available via $this->. See my example where I get $this->session.
Try to avoid constructs - you probably don't need them anywhere except for the core, and under special circumstances, which you might then need to check for yourself before you do even that. I dont use constructs much anymore. It can be a bit clunky and unmanageable.
I installed and configured SilverStripe on my server. I installed the MultiForm module and followed the instructions in the module documentation.
After following the instructions I still don't see any new page type in my CMS Portal.
I also tried db/build?flush=1 & dev/build?flush=1 but it doesn't make a difference.
I Created the Following files in mysite/code/ directory
SponsorSignupForms.php
class SponsorSignupForms extends MultiForm{
protected static $start_step = 'CompanyDetailsStep';
}
CompanyDetailsStep.php
class CompanyDetailsStep extends MultiFormStep{
public static $next_steps = 'ContactDetailsStep';
function getFields()
{
$fields = singleton('Member')->getFrontendFields();
return $fields;
}
function getValidator()
{
return new Member_Validator('FirstName', 'Surname', 'Email', 'Password');
}
}
ContactDetailsStep.php
class ContactDetailsStep extends MultiFormStep{
public static $is_final_step = true;
function getFields()
{
$fields = singleton('Reference')->getFrontendFields();
return $fields;
}
}
How do I get these custom MultiForms working and appearing as creatable pages?
Of course you don't see any new page type in the list of available pages, you will only see subclasses of SiteTree there, MultiFormStep is "just" a subclass of DataObject.
You can plug your form to every page you want manually, but you also can create a new page type for your form and include the Form in your Controller and Template, see readme of MultiForm:
class MyFormPage extends Page
{
}
class MyFormPageController extends Page_Controller
{
//
private static $allowed_actions = array(
'SponsorSignupForms',
'finished'
);
public function SponsorSignupForms() {
return new SponsorSignupForms($this, 'Form');
}
public function finished() {
return array(
'Title' => 'Thank you for your submission',
'Content' => '<p>You have successfully submitted the form!</p>'
);
}
}
In the template just include the form:
<% if $SponsorSignupForms %>
$SponsorSignupForms
<% end_if %>
and you should see the form now.
I use Cakephp 2.1 and I need to call a component method which resides in a plugin, from a view helper:
The component is here:
/app/Plugin/Abc/Controller/Component/AbcComponent.php
The helper is here:
/app/View/Helper/SimpleHelper.php
I tried inside helper:
App::import('Component', 'Abc.Abc');
$this->Abc = new Abc(); or $this->Abc = new AbcComponent;
or
$this->Abc = $this->Components->load('Abc.Abc');
inside the controllers this component works with no problem.
I know this isn't recommended (MVC design etc.) but if I don't use it this way I need to duplicate a lot of code. I need to make something like:
MyHelper extends Helper{
$simpleVar = Component->get_data();
}
I use CakePHP 2.4
This is how I successfully call Component from a Helper:
App::uses('AclComponent', 'Controller/Component');
class MyHelper extends AppHelper {
public function myFunction() {
$collection = new ComponentCollection();
$acl = new AclComponent($collection);
// From here you can use AclComponent in $acl
if ($acl->check($aro, $aco) {
// ...
}
}
}
Passing data from CakePHP component to a helper
This seems to be a very nice way to handle this.
I tried working the way you are before, and, although it seems to be a nice immediate solution, in the long run, it is better to just work with the component and helper as 2 separate entities in your controller.
lee
You can put logic in trait and use this from component and helper, if your porpouse is to use the same business logic in different places, to avoid duplication code.
By example
the trait (file app/Lib/NameOfTrait.php or app/PluginName/Lib/NameOfTrait.php)
trait NameOfTrait {
public function theTraitFunc($a, $b) {
// Code here
}
}
The Component:
App::uses('Component', 'Controller');
App::uses('NameOfTrait', 'PluginName.Lib');
class NameOfComponent extends Component {
use NameOfTrait;
private $member;
private $controller;
public function __construct(ComponentCollection $collection, $settings = array()) {
parent::__construct($collection, $settings);
$this->member = $settings['memberName'];
}
function startup(Controller $controller) {
$this->controller = $controller;
}
/**
* Wrap function call of trait function,
* I think the function doesn't have the same name,
* I don't try this but I think is obvious,
* to avoid the function to call itself
*/
public function theTraitFuncWrap($a) {
return $this->theTraitFunc($a, $this->member);
}
}
Do The same for the Helper.
I hope this help someone, bye :)
I want to make some action (php script) before all actions in my frontend app and then pass a result from that script to actions in variable - so I can get variable value from all actions. Where should I declare sth like this?
If the filter solution dont feet your needs, you can also create a base action class with a preExecute function:
// app/frontend/lib/baseActions.class.php
class baseActions extends sfActions
{
public function preExecute()
{
$this->myVar = .... // define your vars...
}
}
Then your module actions class extends your baseActions class:
// app/frontend/modules/myModule/actions/actions.class.php
class myModuleActions extends baseActions
{
public function executeIndex(sfWebRequest $request)
{
// var $this->myVar is available in any action and in your template
...
}
}
if you have to use the preExecute function in your module class action, remember to call parent::preExecute() in it.
What kind of information ?
I would recommend you to use filters.
In your apps/frontend/config/filters.yml:
rendering: ~
myfilter:
class: myCustomFilter
Create the file lib/filter/myCustomFilter.php:
<?php
class myCustomFilter extends sfFilter
{
public function execute ($filterChain)
{
if ($this->isFirstCall())
{
// do what ever you want here.
$config = Doctrine_Core::getTable('Config')->findAll();
sfConfig::set('my_config', $config);
}
$filterChain->execute();
}
}
And then, every where, you can retrieve your data:
sfConfig::get('my_config');