SilverStripe convertDataObjectSet is stripping additional properties - silverstripe

I am attempting to add the 'AbsoluteLink' property to each DataObject in a DataList and then convert the list to JSON with JSONDataFormatter::convertDataObjectSet().
I have the following function:
public function json() {
$data = ResourceCentreArticlePage::get()->filter('ShowInMenus', '1')->filter('ShowInSearch', '1')->sort('Created', 'DESC');
$pageArray = new ArrayList();
foreach ($data as $page) {
$page->AbsoluteLink = $page->AbsoluteLink();
$pageArray->push($page);
}
// If I dump out the content of $pageArray here the object has the AbsoluteLink property
$jsonFormatter = new JSONDataFormatter();
$jsonData = $jsonFormatter->convertDataObjectSet($pageArray);
// If I dump out the content of $jsonData here there is no AbsoluteLink property
$this->response->addHeader("Content-type", "application/json");
return $jsonData;
}
The problem:
The AbsoluteLink property is removed after running the $pageArray through the convertDataObjectSet method.
What am I missing?

Using $jsonFormatter->setCustomAddFields(); will help here.
Add the following to the Page class:
public function getMyAbsoluteLink() {
return $this->AbsoluteLink();
}
For example to the Page.php:
class Page extends SiteTree {
public function getMyAbsoluteLink() {
return $this->AbsoluteLink();
}
}
And use that "magic field" like this:
public function json() {
$pages = Page::get()
->filter('ShowInMenus', '1')
->filter('ShowInSearch', '1')
->sort('Created', 'DESC');
$jsonFormatter = new JSONDataFormatter();
// add your custom field
$jsonFormatter->setCustomAddFields(["MyAbsoluteLink"]);
$jsonData = $jsonFormatter->convertDataObjectSet(
$pages
);
return $jsonData;
}
Note the $jsonFormatter->setCustomAddFields(["MyAbsoluteLink"]); and I removed the array manipulation.
Also I removed your array manipulation. How the convertDataobjectSet function works it seems you can't amend the objects before it runs.

Related

Sorting list of VirtualPages on a field from its Page

I have an AreaPage with $many_many VirtualPages:
class AreaPage extends Page {
/**
* #var array
*/
private static $many_many = [
'RelatedVirtualPages' => 'VirtualPage'
];
// ...
}
The RelatedVirtualPages are copying content from ContentPages:
class ContentPage extends Page {
/**
* #var array
*/
private static $db = [
'Highlighted' => 'Boolean'
];
// ...
}
What's the best way to sort RelatedVirtualPages on the Highlighted db field of the ContentPage that it's copying?
Virtual Pages could be pointed at pages of different types and there is no enforcement that all of those pages are ContentPages, or at least pages that have a Hightlighted db field. You can ensure this manually when you create your SiteTree, but users could come along and screw it up so keep this in mind.
Here is some psuedo-code that might help you get started. It assumes that all virtual pages are ContentPages. If you will have multiple types of VirtualPages referenced by an AreaPage then this is probably not sufficient.
$virtualPages = $myAreaPage->RelatedVirtualPages();
$contentSourcePages = ContentPage::get()->byIDs($virtualPage->column('CopyContentFromID'));
$sortedSourcePages = $contentSourcePages->sort('Highlighted','ASC');
You possibly could also use an innerJoin, but then you have to deal with _Live tables and possibly multiple page tables (again if not just using ContentPage as VirtualPage) which could lead to some complicated scenarios.
Update
So, to summarize in my own words, you need a list of the VirtualContentPages linked to a specific AreaPage sorted on the Highlighted field from the ContentPage that each VirtualContentPage links to. If this summary is accurate, would this work:
$sortedVirtualPages = $myAreaPage->RelatedVirtualPages()
->innerJoin('ContentPage', '"ContentPage"."ID" = "VirtualContentPage"."CopyContentFromID"')
->sort('Highlighted DESC');
I could not find a very clean method, but did find two ways to achieve this. The function goes in the class AreaPage
First
public function getRelatedVirtualPages()
{
$items = $this->getManyManyComponents('RelatedVirtualPages');
$highlighted = $items->filterByCallback(function($record, $list) {
if($record->CopyContentFrom() instanceOf ContentPage) {
//return ! $record->CopyContentFrom()->Highlighted; // ASC
return $record->CopyContentFrom()->Highlighted; // DESC
}
});
$highlighted->merge($items);
$highlighted->removeDuplicates();
return $highlighted;
}
Second (the method you described in the comments)
public function getRelatedVirtualPages()
{
$items = $this->getManyManyComponents('RelatedVirtualPages');
$arrayList = new ArrayList();
foreach($items as $virtualPage)
{
if($virtualPage->CopyContentFrom() instanceOf ContentPage) {
$virtualPage->Highlighted = $virtualPage->CopyContentFrom()->Highlighted;
$arrayList->push($virtualPage);
}
}
$arrayList = $arrayList->sort('Highlighted DESC');
return $arrayList;
}
I'm not very proud of any of these solutions, but I believe they do fit your criteria.
Here's what I ended up doing, which I think works:
/**
* #return ArrayList
*/
public function VirtualPages()
{
$result = [];
$virtualPages = $this->RelatedVirtualPages();
$contentPages = ContentPage::get()
->byIDs($virtualPages->column('CopyContentFromID'))
->map('ID', 'Highlighted')
->toArray();
foreach($virtualPages as $virtualPage) {
$highlighted = $contentPages[$virtualPage->CopyContentFromID];
$virtualPage->Highlighted = $highlighted;
$result[] = $virtualPage;
}
return ArrayList::create(
$result
);
}
And then it's sortable like so:
$areaPage->VirtualPages()->sort('Highlighted DESC');
Thank you for all the answers and pointers. I'll wait a bit before marking any answer.
Couldn't you just do
//just get one areapage
$AreaPageItem = AreaPage::get()->First();
//now get the RelatedVirtualPages sorted
$related_pages = $AreaPageItem->RelatedVirtualPages()->sort("Highlighted","ASC");

Silverstripe 3.2: How to make a custom action button in the CMS to create a new Dataobject and populate it from another one

I'm searching for a way to create a custom action button which allows me to make a new DataObject with pre-filled content from another DataObject. As a simple example: When I have an email and click the "answer"-button in my email-client, I get a new window with pre-filled content from the email before. I need exactly this functionality for my button. This button should appear next to each DataObject in the GridField.
So I know how to make a button and add it to my GridField (--> https://docs.silverstripe.org/en/3.2/developer_guides/forms/how_tos/create_a_gridfield_actionprovider/) and I know how to go to a new DataObject:
Controller::curr()->redirect($gridField->Link('item/new'));
I also found out that there is a duplicate function for DataObjects:
public function duplicate($doWrite = true) {
$className = $this->class;
$clone = new $className( $this->toMap(), false, $this->model );
$clone->ID = 0;
$clone->invokeWithExtensions('onBeforeDuplicate', $this, $doWrite);
if($doWrite) {
$clone->write();
$this->duplicateManyManyRelations($this, $clone);
}
$clone->invokeWithExtensions('onAfterDuplicate', $this, $doWrite);
return $clone;
}
Perhaps it's easier than I think but at the moment I just don't get how to rewrite this to get what I need. Can somebody give me a hint?
That's for sure not the cleanest solution but I think it should do the trick.
At first let's create the custom gridfield action. Here we will save all accessible records in a session and add a query string to the url so that we'll know which object we want to "clone"
public function getColumnContent($gridField, $record, $columnName) {
if(!$record->canEdit()) return;
$field = GridField_FormAction::create(
$gridField,
'clone'.$record->ID,
'Clone',
'clone',
array('RecordID' => $record->ID)
);
$values = Session::get('ClonedData');
$data = $record->data()->toMap();
if($arr = $values) {
$arr[$record->ID] = $data;
} else {
$arr = array(
$record->ID => $data
);
}
Session::set('ClonedData', $arr);
return $field->Field();
}
public function getActions($gridField) {
return array('clone');
}
public function handleAction(GridField $gridField, $actionName, $arguments, $data) {
if($actionName == 'clone') {
$id = $arguments['RecordID'];
Controller::curr()->redirect($gridField->Link("item/new/?cloneID=$id"));
}
}
after adding this new component to our gridfield,
$gridField->getConfig()->addComponent(new GridFieldCustomAction());
we'll need to bring the data into the new form. To do so, add this code directly above "return $fields" on your getCMSFields function so it will be executed every time we'll open this kind of object.
$values = Session::get('ClonedData');
if($values) {
Session::clear('ClonedData');
$json = json_encode($values);
$fields->push(LiteralField::create('ClonedData', "<div id='cloned-data' style='display:none;'>$json</div>"));
}
At the end we need to bring the content back into the fields. We'll do that with a little bit of javascript so at first you need to create a new script.js file and include it in the ss backend (or just use an existing one).
(function($) {
$('#cloned-data').entwine({
onmatch: function() {
var data = JSON.parse($(this).text()),
id = getParameterByName('cloneID');
if(id && data) {
var obj = data[id];
if(obj) {
$.each(obj, function(i, val) {
$('[name=' + i + ']').val(val);
});
}
}
}
});
// http://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript#answer-901144
function getParameterByName(name) {
name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
results = regex.exec(location.search);
return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
}
})(jQuery);
And that's it ... quite tricky. Hope it will solve your problem.

Symfony call get by Name from variable

I would like to call a getter with the stored fieldname from the database.
For example, there are some fieldnames store like ['id','email','name'].
$array=Array('id','email','name');
Normally, I will call ->getId() or ->getEmail()....
In this case, I have no chance to handle things like this. Is there any possibility to get the variable as part of the get Command like...
foreach ($array as $item){
$value[]=$repository->get$item();
}
Can I use the magic Method in someway? this is a bit confusing....
Symfony offers a special PropertyAccessor you could use:
use Symfony\Component\PropertyAccess\PropertyAccess;
$accessor = PropertyAccess::createPropertyAccessor();
class Person
{
private $firstName = 'Wouter';
public function getFirstName()
{
return $this->firstName;
}
}
$person = new Person();
var_dump($accessor->getValue($person, 'first_name')); // 'Wouter'
http://symfony.com/doc/current/components/property_access/introduction.html#using-getters
You can do it like this :
// For example, to get getId()
$reflectionMethod = new ReflectionMethod('AppBundle\Entity\YourEntity','get'.$soft[0]);
$i[] = $reflectionMethod->invoke($yourObject);
With $yourObject being the object of which you want to get the id from.
EDIT : Don't forget the use to add :
use ReflectionMethod;
Hope this helps.
<?php
// You can get Getter method like this
use Doctrine\Common\Inflector\Inflector;
$array = ['id', 'email', 'name'];
$value = [];
foreach ($array as $item){
$method = Inflector::classify('get_'.$item);
// Call it
if (method_exists($repository, $method))
$value[] = $repository->$method();
}

Validation on extended fields UserDefinedForm

I have made an extension on the UserDefinedForm (module userforms). This works well, but I cannot figure out how to set validation on this extra fields. This is (a part of) my code:
class UserDefinedPaymentForm_Controller extends UserDefinedForm_Controller {
private static $allowed_actions = array(
"finished",
"complete",
"error"
);
public function getFormFields() {
//Payment fields
$supported_methods = PaymentProcessor::get_supported_methods();
$gateways = array();
foreach ($supported_methods as $methodName) {
$methodConfig = PaymentFactory::get_factory_config($methodName);
$gateways[$methodName] = $methodConfig['title'];
}
$fields = parent::getFormFields();
$fields->add(new NumericField("PaymentAmount", _t('UserDefinedPaymentForm.PAYMENT_AMOUNT', 'Payment Amount')));
$fields->add(new Literalfield("literalfield", _t('UserDefinedPaymentForm.PAY', '<h2>Pay</h2>')));
$fields->add(new Literalfield("literalfield", _t('UserDefinedPaymentForm.PAY_INSTRUCTIONS', '<p>Choose your prefered payment method and click Pay:</p>')));
$fields->add(new DropdownField("PaymentMethod", _t('UserDefinedPaymentForm.PAYMENT_METHOD', 'Payment Method'), $gateways));
return $fields;
}
}
Now I want to validate the field PaymentAmount, the value of this field has to be 2 or more. How can I do this?
I would guess (I haven't tested this) your best bet is to create a subclass of UserFormValidator and override the php($data) method.
Then, in your UserDefinedPaymentForm_Controller, you will also need to override the Form method.
class PaymentAmountUserFormValidator extends UserFormValidator {
public function php($data) {
$result = parent::php($data);
if ($result === true) {
// verify your PaymentAmount here and return true or false, accordingly
}
return $result;
}
class UserDefinedPaymentForm_Controller {
...
public function Form()
{
$form = UserForm::create($this);
// Generate required field validator
$requiredNames = $this
->getController()
->Fields()
->filter('Required', true)
->column('Name');
$validator = new PaymentAmountUserFormValidator($requiredNames);
$form->setValidator($validator);
$this->generateConditionalJavascript();
return $form;
}
...
}

Prestashop menu tab, that opens an iframe(Backoffice)

I'm working on a project for creating a prestashop module that creates a custom tab at the back office and by pressing it, it opens an iframe. I have created the tab but at the menu bar at the backoffice. But i dont know how open an iframe by pressing it.
Can you help me out please?
This is my module's code:
<?php
if (!defined('_PS_VERSION_'))
exit;
class Mytab extends Module
{
// PLIROFORIES TOY AYTHOR
public function __construct()
{
$this->name = 'Mytab';
$this->tab = 'Administration';
$this->version = 1.5;
$this->author = 'Sergio Kagiema';
$this->need_instance = 0;
//$this->tabParentName = 'AdminTools';
parent::__construct();
$this->displayName = $this->l('My Tab');
$this->description = $this->l('Module makes a tab at BackEnd');
$this->confirmUninstall = $this->l('Are you sure you want to uninstall?');
if (!Configuration::get('My Tab'))
$this->warning = $this->l('No name provided');
} //END OF PLIROFORIES TOY AYTHOR
//INSTALL TOY MODULE
public function install()
{
$parent_tab = new Tab();
foreach (Language::getLanguages(true) as $lang)
$parent_tab->name [$lang['id_lang']] = 'Tab';
$parent_tab->class_name = 'Tab';
$parent_tab->id_parent = 0;
$parent_tab->module = $this->name;
$parent_tab->add();
if (!parent::install()
|| !$this->installModuleTab('MyTabsController', array((int)(Configuration::get('PS_LANG_DEFAULT'))=>'My Tab'), $parent_tab->id)
)
return false;
return true;
}
//UNISTALL TOY MODULE
public function uninstall()
{
if (!parent::uninstall()
|| !$this->uninstallModuleTab('MyTab')
|| !$this->uninstallModuleTab('MyTabsController'))
return false;
return true;
}
private function installModuleTab($tabClass, $tabName, $idTabParent)
{
$idTab = Tab::getIdFromClassName($idTabParent);
$idTab = $idTabParent;
$pass = true ;
#copy(_PS_MODULE_DIR_.$this->name.'/logo.gif', _PS_IMG_DIR_.'t/'.$tabClass.'.gif');
$tab = new Tab();
$tab->name = $tabName;
$tab->class_name = $tabClass;
$tab->module = $this->name;
$tab->id_parent = $idTab;
$pass = $tab->save();
return($pass);
}
private function uninstallModuleTab($tabClass)
{
$pass = true ;
#unlink(_PS_IMG_DIR_.'t/'.$tabClass.'.gif');
$idTab = Tab::getIdFromClassName($tabClass);
if($idTab != 0)
{
$tab = new Tab($idTab);
$pass = $tab->delete();
}
return($pass);
}
}
?>
This is my controller's code at my contoroller/admin file:
<?php
class AffiliatesTabsController extends AdminController
{
public function init()
{
parent::init();
}
/**
* Assign template vars related to page content
* #see FrontController::initContent()
*/
public function initContent()
{ parent::initContent();
$this->setTemplate(_PS_THEME_DIR_.'/MyTab.tpl');
//$smarty = $this->context->smarty;
//$smarty->assign('test', 'test1');
// include(dirname(__FILE__).'/jQ.tpl');
}
}
?>
Please help me! Thanks!
I'm searching for similar things today and find out.
Controller:
class AffiliatesTabsController extends ModuleAdminController
{
public function initContent()
{
parent::initContent();
$this->setTemplate('MyTab.tpl');
}
}
Then move MyTab.tpl into
prestashop\modules\mytab\views\templates\admin\affiliatestabs\
MyTab.tpl:
<iframe src="webpage url">
You are trying to set an admin template for frontoffice use, that won't work.
This solution here works for me:
How to create a new page in prestashop admin panel?
Edit: additions after comments below:
After you created the the AdminAffiliatesController.php file in controllers\admin folder you create the template file here:
admin\themes\default\template\controllers\adminaffiliates\content.tpl
In that content.tpl file you can create your iframe or whatever.
Every output that you create in the controller or default values that you wan't to use or display in your template do you need to assign in the controller like this:
$title_of_page = "Welcome at this page!";
$smarty->assign('title_of_page', 'title_of_page');
In your template:
<h1>{$title_of_page}</h1>

Resources