Extend a DataObject in SilverStripe - silverstripe

I installed a photogallery module in SilverStripe. This module has a DataObject named PhotoItem.
The PhotoItem class contains some fields, but I want to add extra fields. The easiest way to do that is to edit the PhotoItem file, but then I lose my changes when updating the module.
How can I extend this DataObject with some more fields with a DataObject file under /mysite/code?

In Silverstripe 3.1 you can extend a class by creating a DataExtension and applying it to your class.
First you would create a CustomPhotoItem.php in mysite/code or mysite/code/extensions:
CustomPhotoItem.php
class CustomPhotoItem extends DataExtension {
private static $db = array(
'ExtraTextField' => 'Text'
);
public function updateCMSFields(FieldList $fields) {
$fields->push(TextField::create('ExtraTextField', 'Extra Text Field'));
}
}
In order to apply this extension to your class, you need to add the following to your config.yml:
config.yml
PhotoItem:
extensions:
- CustomPhotoItem
Your config.yml should be located in mysite/_config/config.yml.
Run dev/build?flush=1 and you should see your new variables added to your original object.

You are searching for DataExtension. Have a look to the documentation, there's evereything you need for adding some more fields to DataObjects.
In particular have a look to the section named Adding extra database fields

Related

How to override SonataBasketBundle vendor views

just a little question:
I have a vendor bundle e.g. SonataBasketBundle with some views.
I extended it by SonataEasyExtend in my src/Application/SonataBasketBundle folder.
PROBLEM: I would to override vendor views, and I use the classical two methods: copy all views files in src/Application/SonataBasketBundle/Resources/views or copy them in app/Resources/SonataBasketBundle/views.
But, unfortunately, both methods do not works. What's the possible problem?
I missed some configuration?
I made a little test:
my extended bundle is named "ApplicationSonataBasketBundle".
Now, if in the vendor basket index method, I change the view name this way
return $this->render('ApplicationSonataBasketBundle:Basket:index.html.twig',
array(
'basket' => $this->get('sonata.basket'),
'form' => $form->createView(),
));
the framework load the application bundle view, as I want.
But, if the application bundle extends the vendor one (SonataBasketBundle), doesn't should be loaded by default also with name SonataBasketBundle?
Thanks in advance.
Override view file by pasting correct view files in
app/Resources/bundle/views
then clearing cache afterward by console
php app/console ca:cl
or delete cache files manually from app/cache (Better)
symfony doc
when overriding child bundle, it is good to override the controllers as well as the templates
<?php
// src/Acme/UserBundle/AcmeUserBundle.php
namespace Acme\UserBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class AcmeUserBundle extends Bundle
{
public function getParent()
{
return 'FOSUserBundle';
}
}

How can I allow the user to set a custom global value in the silverstripe CMS?

I would like to be able to set custom values in the CMS, such as with the site name and tagline. I can't currently find any way of doing this other than on individual pages.
You can do so by extending SiteConfig. Your Extension might look like this:
class CustomSiteConfig extends DataExtension
{
private static $db = array(
'CustomContent' => 'Varchar(255)'
);
public function updateCMSFields(FieldList $fields)
{
$fields->addFieldToTab('Root.Main',
TextField::create('CustomContent', 'Custom content')
);
}
}
Then you need to apply the extension to SiteConfig. Add the following to mysite/_config/config.yml
SiteConfig:
extensions:
- CustomSiteConfig
And that's it. Run dev/build and your new field should be editable in the CMS as well as accessible in the Template using: $SiteConfig.CustomContent

decluttering UI, order Extension gets applied

A few years ago I made a SilverStripe website and added too many fields to Page.php. I'm reworking some of this at the moment but cannot afford do reinvent the Project - now on SilverStripe 3.1.10.
I thought to declutter the UI for Page Sub-Classes, that do not need all the inherited fields, with a few Extensions.
An example how this extension could look
class NoClutter extends Extension {
public function updateCMSFields(FieldList $fields) {
$fields->removeFieldFromTab("Root.Main", "MenuTitle");
$fields->removeFieldFromTab("Root.Main", "Workflow");
}
}
config.yml
RedirectorPage:
extensions:
- NoClutter
This works on all classes for fields added in SiteTree (such as the MenuTitle field), but not for fields added in Page (such as the Workflow field). If the Extension is on UserDefinedForm, Workflow is also removed. But it does not work if the extension is on RedirectorPage. MenuTitle on the other hand is removed in both classes. My guess it's about order. My project is After: 'framework/','cms/' and hope I can make an extension like NoClutter work within the project.
How can I achieve this or how else could I work around the problem?
You need to add $this->extend('updateCMSFields', $fields) at the end of your Page getCMSFields() function.
class Page extends SiteTree {
// ...
public function getCMSFields() {
// call updateCMSFields after adding your fields
SiteTree::disableCMSFieldsExtensions();
$fields = parent::getCMSFields();
SiteTree::enableCMSFieldsExtensions();
// ...
$this->extend('updateCMSFields', $fields);
return $fields;
}
}
$this->extend('updateCMSFields', $fields) declares where your code updateCMSFields() function will get called.
The problem you are having is updateCMSFields() is getting called before you add your custom fields in the Page getCMSFields() function. So you are trying to remove the Workflow field before it is added. This is because the updateCMSFields extension hook is declared in the parent SiteTree getCMSFields() function.
UserDefinedForm solves this by calling $this->extend('updateCMSFields', $fields) at the bottom of its getCMSFields(). SiteTree::disableCMSFieldsExtensions() is required before parent::getCMSFields() is called for the extension hook to work.

Global values: Define as service or define abstract Controller class?

In my Symfony2 app, I want to globally fetch a value from my database on each template and don't want to call on each Controller. I know I could define that as a service and inject that service into my twig templates (by defining it as a twig global).
Is that the common and recommended way? Or should I rather create an abstract Controller class where I fetch that value in my constructor and then inherit from all my other Controllers?
Note: It is actually not a static value that is the same for all users, but it is a user specific value which is different for each user.
If this variables are used to render the same spot on your page you can render an embedded controller. Like this:
<div id="sidebar">
{{ render(controller('YourBundle:User:stats')) }}
</div>
This will inject whole output of YourBundle/UserController/statsAction to the #sidebar div. Inside this action you can extract all inforamtion that you need.
If you need to use this variables in other way maybe you should look at response event.
Are you familiar with event listeners? http://symfony.com/doc/current/cookbook/service_container/event_listener.html
An event listener can be used to inject twig globals.
class ModelEventListener extends ContainerAware implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return array(
KernelEvents::CONTROLLER => array(
array('doProject', -1300),
),
KernelEvents::VIEW => array(
array('doView', -2100),
),
);
}
public function doProject(FilterControllerEvent $event)
{
$project = $whatever_is_needed_to_find_the_project();
if (!$project) throw new NotFoundHttpException('Project not found ' . $projectSearch);
// Add to request
$event->getRequest()->attributes->set('project',$project);
// Give all twig templates access to project
$twig = $this->container->get('twig');
$twig->addGlobal('project',$project);
}
# services.yml
cerad_core__model__event_listener:
class: '%cerad_core__model__event_listener__class%'
calls:
- [setContainer, ['#service_container']]
tags:
- { name: kernel.event_subscriber }
If it's a user value like you said you can get app.user.XXX on every twig template you need without processing nothing ;)

Overriding the FOSUserBundle controllers

So I read alot about the overriding of templates and such and overriding of bundles in Symfony.
I am using the new Symfony 2.3, I have not tried this in lower versions of Symfony.
I followed the tutorial about overriding bundles in Symfony:
http://symfony.com/doc/2.3/cookbook/bundles/inheritance.html
I followed the tutorial about overriding the controllers of FOSUserBundle, which is the same thing really:
https://github.com/FriendsOfSymfony/FOSUserBundle/blob/master/Resources/doc/overriding_controllers.md
I had a bundle named Acme/WebBundle.
Now I have done the following things:
Created a new bundle named Acme/UserBundle.
Created the file AcmeUserBundle.php in this bundle.
<?php
namespace Acme\UserBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class AcmeUserBundle extends Bundle
{
public function getParent()
{
return 'FOSUserBundle';
}
}
Created the following file structure:
-src
-Acme
-UserBundle
-Controller
RegistrationController.php
-Entity
User.php
-Resources
-translations
-views
AcmeUserBundle.php
In RegistrationController.php I set the namespace to:
namespace Acme\UserBundle\Controller;
Copied the contents of the registration controller of FOSUserBundle to mine.
Added to the beginning of registerAction()
die("message");
Now when I go to the registration form, the default /register route, I don't get a die, everything works fine. It does not see my bundle as a child, nothing is overridden and I've been trying to get it to work for ages hence my question here.
Did I do something wrong?
Remember that you need to add any new bundle to AppKernel::registerBundles() in app/AppKernel.php like this:
$bundles = array(
...
new Acme\UserBundle()
);

Resources