On Startpage.php:
class StartPage extends Page {
public static $db = array(
'Sperrtag' => 'Varchar'
);
function example(){
.........
$test = $this->Sperrtag;
.........
}
how can i access $this->Sperrtag from another page?
Tryed StartPage::$db->Sperrtag
and a lot of other variants
no luck.
you need to access an instance of StartPage instead of the class itself.
most probably you have only 1 'StartPage', so you could write in silverstripe 3:
$sperrtag = StartPage::get()->First()->Sperrtag;
for silverstripe 2.4 it's:
$startpage = DataObject::get_one('Startpage');
$sperrtag = $startpage->Sperrtag;
you should have a look at http://doc.silverstripe.org/framework/en/topics/datamodel to get an understanding of silverstripe's data handling.
Related
We are working on a project using Silverstripe with the Fluent module to enable multiple translations.
Here's an example Data Object with Fluent enabled. First we create the Data Object & explicitly set the CMS fields:
namespace Yard;
use SilverStripe\ORM\DataObject;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\TextField;
class Milkshake extends DataObject {
private static $table_name = 'Milkshake';
private static $db = [
'Title' => 'Varchar(255)'
]
public function getCMSFields() {
$fields = new FieldList(
new TextField('Title', 'Milkshake Title', null, 255)
);
$this->extend('updateCMSFields', $fields);
return $fields;
}
}
Then we set Title as translatable in a YML file:
Yard\Milkshake:
extensions:
- 'TractorCow\Fluent\Extension\FluentExtension'
translate:
- 'Title'
This gives us an object with a translatable Title field that can have different values in different locales. It creates the following database table:
Milkshake_Localised
ID | RecordID | Locale | Title
So far so good, except using:
$milkshake = Milkshake::get()->first() doesn't return the localised data & pulls from the Milkshake table.
I think it could be possible to use:
$locale= FluentState::singleton()->getLocale();
$milkshake = Milkshake_Localised::get()->filter(['Locale' => $locale])->first();
But this feels clumsy & has no fallback if the locale data doesn't exist for that field (at which point it should fall back to the default locale, or failing that the original Milkshake field).
What is the correct way to access Locale data in Fluent so there is a fallback if required?
I got the desired behaviour by wrapping the get command in "withState"
use TractorCow\Fluent\State\FluentState;
$milkshake = FluentState::singleton()->withState(function (FluentState $state) {
return Milkshake::get()->first();
});
I wanted to white label silverstripe CMS, i.e. one codebase serves different domains and each domains have their own members, etc. I have asked this question Here. And I was suggested to use Subsite modules.
However this partially solved my problem (I m very new to SilverStripe and official community isn't that active).
I was able to make my custom modules Subsite specific by using following code in my ModelAdmin
<?php
class CompaniesAdmin extends ModelAdmin {
private static $url_segment = 'Companies';
private static $managed_models = "Company";
private static $menu_title = 'Companies';
private static $menu_icon = 'mysite/images/icons/company-icon.png';
public function getEditForm($id = null, $fields = null){
$form = parent::getEditForm($id, $fields);
$gridField = $form->Fields()->fieldByName($this->sanitiseClassName($this->modelClass));
if(class_exists('Subsite')){
$list = $gridField->getList()->filter(array('SubsiteID'=>Subsite::currentSubsiteID()));
$gridField->setList($list);
}
return $form;
}
}
?>
Everything works as expected, except the "Security" Module. My requirement is even membership should be subsite specific. That means members from one subsite is not visible in another subsite, they can't login to another subsite, etc.
I also visited this post by another user. However the suggested solution is not implementable in my scenario.
My Questions
Is it possible to extend subsite module so that we can have subsite
specific Members??
Can we do without modifying core files?
I was able to add hidden field for SubsiteID while adding/editing Members and was able to save member with specific SubsiteID.
How to override Security module, so that I can list/filter subsite specific Members?
How to prevent editing of other Subsite's member if someone provides member id in URL querystring?
How to prevent member of one subsite login to another subsite?
Any help is highly appreciated.
UPDATE
I tried following injector, but didn't work, got an error Fatal error: Call to a member function getList() on a non-object in...
<?php
class CustomSecurityAdmin extends SecurityAdmin {
public function getEditForm($id = null, $fields = null){
$form = parent::getEditForm($id, $fields);
$gridField = $form->Fields()->fieldByName('Member');
if(class_exists('Subsite')){
$list = $gridField->getList()->filter(array('SubsiteID'=>Subsite::currentSubsiteID()));
$gridField->setList($list);
}
return $form;
}
}
?>
And in _config.yml
Injector:
SecurityAdmin:
class: CustomSecurityAdmin
UPDATE 2
My injector for MemberAuthenticator class
<?php
class CustomMemberAuthenticator extends MemberAuthenticator {
public static function authenticate($RAW_data, Form $form = null) {
//add logic before
//get Subsite ID
$Subsite = SubsiteDomain::get()->filter('Domain', $_SERVER["HTTP_HOST"])->First();
if($Subsite){
$SubsiteID = $Subsite->SubsiteID;
}else{
$SubsiteID = 0;
}
$email = Convert::raw2sql($RAW_data['Email']);
$member = Member::get()->filter(array(
"Email" => $email,
"SubsiteID" => $SubsiteID
))->First();
if(!$member){
if($form) $form->sessionMessage("Invalid User", 'bad');
}else{
parent::authenticate($RAW_data,$form);
}
}
}
?>
And _config.yml
Injector:
MemberAuthenticator:
class: CustomMemberAuthenticator
But this didn't work the injector is not working at all
Is it possible to extend subsite module so that we can have subsite specific Members??
Yes
Can we do without modifying core files?
How to override Security module, so that I can list/filter subsite specific Members?
How to prevent member of one subsite login to another subsite?
The two classes you are probably most interested in are SecurityAdmin and MemberAuthenticator.
All silverstripe core files can be "modified" in some way... of the methods discussed on this video presentation by StripeCon - Loz Calver - Why you shouldn’t edit SilverStripe core files (and how to do it anyway) - I recommend method 3, fork the code and add this custom security to your version of silverstripe.
For SecurityAdmin the best option is to simply removing the current SecurityAdmin from the menu and adding your own custom class:
in _config.php
CMSMenu::remove_menu_item('SecurityAdmin ');
How to prevent editing of other Subsite's member if someone provides member id in URL querystring?
You could determine if the user is allowed to edit the form based on which site they belong to... either in permissions or simply use updateCMSFields to remove all fields and a validator to ensure nothing can be submitted that doesn't match your rules.
public function updateCMSFields(FieldList $fields) {
if (<not valid user to edit>) $fields = FieldList::create();
...
}
Here is another question about how to add a validator and here are the docs for that.
There is another hack for this, which flawlessly worked for me.
/mysite/extensions/CustomLeftAndMain.php
<?php
class CustomLeftAndMain extends Extension {
public function onAfterInit() {
self::handleUser();
}
public static function handleUser(){
$currentSubsiteID = Subsite::currentSubsiteID();
$member = Member::currentUser();
$memberBelongsToSubsite = $member->SubsiteID;
if($memberBelongsToSubsite>0 && $currentSubsiteID!=$memberBelongsToSubsite){
Security::logout(false);
Controller::curr()->redirect("/Security/login/?_c=1001");
}
}
}
and in /mysite/_config.php add an extension
LeftAndMain::add_extension('CustomLeftAndMain');
What above code basically does is, the system lets user login no matter which subsite they belong to. And as long as application is initiated, we'll check whether the logged in user belongs to current website or not (method handleUser does it.).
If user doesnot belong to current site, then they are logged out and then redirected to the login page.
I'm a bit of noob when it comes to OOP PHP, so please forgive me if I make this sound more complicated then it is.
Basically I am trying to clean up my controller as it's starting to get too cluttered.
I have my entities set up and I have also created a repository to add methods for some db queries to a sqlite database.
But now I also have to manipulate this data before outputting it, I've created a separate connector class that fetches additional info (from an XML web source) for each item being queried and then this gets added to the doctrine query data before being outputted.
I could manipulate this data in the repository but the data I am adding obviously doesn't originate from my entity. So I have therefore created a separate model class to add this data.
Please tell me if I'm on the right track.
In my entity repository I will have a custom method like this:
public function queryTop10All()
{
$query = $this->getEntityManager($this->em)
->createQueryBuilder('u')
->select('u.ratingkey, u.origTitle, u.origTitleEp, u.episode, u.season, u.year, u.xml, count(u.title) as playCount')
->from($this->class, 'u')
->groupBy('u.title')
->orderBy('playCount', 'desc')
->addOrderBy('u.ratingkey', 'desc')
->setMaxResults(10)
->getQuery();
return $query->getResult();
}
Now I created a new class in \Model\ChartsDataModel.php and I am injecting doctrine into it using a service and calling the custom method, getting the results and then adding the additional data from the web connector to it, like so:
namespace PWW\DataFactoryBundle\Model;
use Doctrine\ORM\EntityManager;
use PWW\DataFactoryBundle\Connector\XMLExtractor;
use PWW\DataFactoryBundle\Connector\WebConnector;
use PWW\ContentBundle\Entity\Settings;
class ChartsDataModel {
private $settings;
private $repository;
private $em;
public function __construct(EntityManager $em)
{
$this->settings = new Settings();
$this->repository = $this->settings->getGroupingCharts() ? 'PWWDataFactoryBundle:Grouped' : 'PWWDataFactoryBundle:Processed';
$this->em = $em;
}
public function getChartsTop10All()
{
$xmlExtractor = new XMLExtractor();
$webConnector = new WebConnector();
$results = $this->em->getRepository($this->repository)->queryTop10All();
$xml = $xmlExtractor->unXmlArray($results);
$outputArray = array();
foreach($xml as $item) {
$outputArray[] = array(
"ratingKey" => $item['ratingkey'],
"origTitle" => $item['origTitle'],
"origTitleEp" => $item['origTitleEp'],
"playCount" => $item['playCount'],
"episode" => $item['episode'],
"season" => $item['season'],
"year" => $item['year'],
"type" => $item['media']['type'],
"parent" => $webConnector->getMetaData($webConnector->getMetaDataParentKey($item['ratingkey'])),
"metadata" => $webConnector->getMetaData($item['ratingkey'])
);
}
return $outputArray;
}
}
The xmlExtractor class is used to pull out certain xml fields stored in a database field as a raw xml dump.
My config.yml:
services:
pww.datafactorybundle.model.charts_data_model:
class: PWW\DataFactoryBundle\Model\ChartsDataModel
arguments: [ #doctrine.orm.entity_manager ]
Then in my controller, I just instantiate a new ChartsDataModel and call the method like so:
namespace PWW\ContentBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
...
use PWW\DataFactoryBundle\Model\ChartsDataModel;
public function chartsAction()
{
$charts = new ChartsDataModel($this->getDoctrine()->getManager());
$top10Array = $charts->getChartsTop10All();
return $this->render('PWWContentBundle:Default:charts.html.twig', array('page' => 'charts', 'top10' => $top10Array));
}
I just want to know if I am doing this correctly and is there a better way of doing this (or right way)?
I'm also very new to Symfony and still getting my head around it. I just don't want to get into bad habits so I'm trying to do things right from the start.
I hope I explained this well enough :)
TIA
Just detected two things that are in the top of my head.
1 If you define the service like:
services:
pww.datafactorybundle.model.charts_data_model:
class: PWW\DataFactoryBundle\Model\ChartsDataModel
arguments: [ #doctrine.orm.entity_manager ]
Then you can inject it in the controller like described here, so you keep the service arguments out of the Controller:
public function chartsAction()
{
$myservice = $this->get('pww.datafactorybundle.model.charts_data_model');
$top10Array = $myservice->getChartsTop10All();
}
Secondly, I would not put this standard queries in the Model, I think is better to keep the models clean with their setters, getters and put this custom queries elsewhere like in a service that will handle all related Chart queries and you can instance from anywhere else.
TL;DR When creating/saving a versioned DataObject with relation to some page, two entries are created in the corresponding versions table (instead of one).
I'm trying to version some DataObject and have the Versioned extension applied as follows:
class Testobject extends DataObject {
static $has_one = array(
'Page' => 'Page'
);
static $extensions = array(
"Versioned('Stage', 'Live')",
);
Testobjects are being managed in a GridField on some page like so:
class PageContent extends Page {
public static $has_many = array(
"Testobjects" => "TestObject"
);
public function getCMSFields() {
$fields = parent::getCMSFields();
$config = GridFieldConfig_RelationEditor::create();
$gridField = new GridField(
'Testobjects',
'Testobject',
$this->Testobjects(),
$config);
$fields->addFieldToTab('Root.Main', $gridField);
}
Now, whenever i add or save some Testobject in the GridField's EditForm, two new entries show up in the Testobject_versions table. For comparsion, when i save a page in the SiteTree, only one entry in the corresponding versions table is created.
As there will we thousands of these DataObjects on a page, i'm worried about this duplication filling up my database. Is there a way to get around this?
Further recognitions:
On creation of a new Testobject, the first entry in the versions table has it's PageID field set to 0, the second entry has set the actual PageID of the corresponding page.
If I replace $this->Testobjects() in the GridField construction by Testobject::get(), only one entry shows up in the versions table.
onBeforeWrite is called twice when using $this->Testobjects()
So it seems setting the relation to the page happens after a first 'write()', then another 'write()' is called. But where in the code does this happen?
If you're editing your page/testobject in the main section of the CMS (# '/admin/pages'), you can try this somewhat hackish trick in your TestObject class
public function getCMSFields(){
$fields = parent::getCMSFields();
$fields->push( new HiddenField('PageID','PageID', Controller::curr()->CurrentPageID());
return $fields;
}
This is not ideal for the following reasons:
hard to test with unit test controller
awareness of the controller in the model is bad
IMHO
But it can be a reasonable fix if it works for you
In symfony 1.4, how to call an action of another application from the current action?
There's a blog post about that here:
http://symfony.com/blog/cross-application-links
There's a plugin for it:
https://github.com/rande/swCrossLinkApplicationPlugin
And there are some blogposts explaining it:
http://rabaix.net/en/articles/2009/05/30/cross-link-application-with-symfony
http://rabaix.net/en/articles/2009/07/13/cross-link-application-with-symfony-part-2
(Note: both are for symfony 1.2, but they should also work in 1.4)
To route to your frontend to your backend, there are three easy steps:
1. Add to your backend configuration the following two methods
These methods read the backend routing, and use it to generate routes. You'll need to supply the link to it, since php does not know how you configured your webserver for the other application.
.
// apps/backend/config/backendConfiguration.class.php
class backendConfiguration extends sfApplicationConfiguration
{
protected $frontendRouting = null;
public function generateFrontendUrl($name, $parameters = array())
{
return 'http://frontend.example.com'.$this->getFrontendRouting()->generate($name, $parameters);
}
public function getFrontendRouting()
{
if (!$this->frontendRouting)
{
$this->frontendRouting = new sfPatternRouting(new sfEventDispatcher());
$config = new sfRoutingConfigHandler();
$routes = $config->evaluate(array(sfConfig::get('sf_apps_dir').'/frontend/config/routing.yml'));
$this->frontendRouting->setRoutes($routes);
}
return $this->frontendRouting;
}
// ...
}
2. You can link to your application in such a fashion now:
$this->redirect($this->getContext()->getConfiguration()->generateFrontendUrl('hello', array('name' => 'Bar')));
3. Since it's a bit tedious to write, you can create a helper
function link_to_frontend($name, $parameters)
{
return sfProjectConfiguration::getActive()->generateFrontendUrl($name, $parameters);
}
The sfCrossLinkApplicationPlugin does this , this, but in a bit simpler fashion, you would be able to use a syntax similar to this:
<?php if($sf_user->isSuperAdmin()):?>
<?php link_to('Edit Blog Post', '#backend.edit_post?id='.$blog->getId()) ?>
<?php endif ?>
It would be something like this:
public function executeActionA(sfWebRequest $request)
{
$this->redirect("http:://host/app/url_to_action");
}
In Symfony each application is independent from the others, so if you need to call an action of another app, you need to request it directly.
Each app is represented by one main controller (frontend, backend, webapp), this controller takes care of the delivery of each request to the corresponding action (and lots of other things like filters, etc.).
I really recommend you to read this, it would be quite more explanatory: Symfony - Inside the Controller Layer