I'm facing a funny issue here, I will do my best to explain it:
I have an Order entity and an orderProducts with a one to many relation.
Now I'm trying to generate a PDF invoice.
So I built my invoice using twig, then I use Knp\Snappy\Pdf; then it's just the following
$snappy = new Pdf($myProjectDirectory . 'vendor/h4cc/wkhtmltopdf-i386/bin/wkhtmltopdf-i386');
$renderedView = $this->renderView(
'ERPBundle:Orders:invoicepdf.html.twig', array(
'order' => $order,
'invoice' => $invoice
)
);
$snappy->generateFromHtml($renderedView, $invoicePath);
In the generated PDF, the orderProducts are duplicated, meaning I get the same row displayed twice.
I've rendered the template separately to view it in the browser and the orderProdcuts displays correctly, I used the same code to retrieve the data.
So I'm guessing this has got to be an issue between Snappy rendering of the html output + doctrine's lazy load. But I don't have the skills to debug this.
The issue is irrelevant to this code or the relative bundles.
The invoice PDF was generated after a form update, and in assigning my sub entity values I did this:
$orderProducts = $order->getOrderProducts();
foreach ($orderProducts as $orderProduct) {
$order->addOrderProduct($orderProduct);
}
This generated the duplicate value for orderProducts, I didn't catch this before, because doctrine recognizes the duplication and ignores it.
The fix is to properly handle the update like this
$orderProducts = $order->getOrderProducts();
foreach ($orderProducts as $orderProduct) {
$order->addOrderProduct($orderProduct);
if (empty($orderProduct->getId())) {
$order->addOrderProduct($orderProduct);
}
}
link to fix
Related
I need to build a customizer for my customers. They will be able to choose between multiple templates for their subdomain.
In order to do that, I took the following path :
Store templates in the DB with twig tags for the user's data
When needing to display a preview of a template, I would get it from the DB
Then I would render it in the controller and send the resulting HTML as a variable to the main template
I'm not succeeding into rendering in the controller. I tried several things, but the closest I got is this :
$loader = new Twig_Loader_Array(array(
'code.html' => $site->getTheme()->getCode(),
));
$twig = new Twig_Environment($loader);
$code = $twig->render('code.html', array( "test" => "CUSTOM DATA" ));
But I miss a use statement :
Attempted to load class "Twig_Loader_Array" from namespace "AppBundle\Controller". Did you forget a "use" statement for another namespace?
I'm not sure if it's the right path though.
So, if it is, please help me find out what use statement to use. More generally, how do you find what use statement should be used ?
If it's not the right strategy according to you, please feel free to explain me how dumb my idea was :)
[EDIT] So thanks to #DarkBee I found this solution that works, but I'm not sure whether I should do this or not :
use Twig\Loader\ArrayLoader;
use Twig\Environment;
...
$loader = new ArrayLoader(array(
'code.html' => $site->getTheme()->getCode(),
));
$twig = new Environment($loader);
$code = $twig->render('code.html', array( "test" => "CUSTOM DATA" ));
So if it's ok, great. If it's wrong (or again, if I'm wrong in the strategy choices), please tell me why and what would be better.
I'm new to Drupal. I looked here and on google for a while before asking, but I'm sure I can't find the answer because I don't know how to ask the question.
Here is what's going on. I'm using a custom module to load certain entities and then output them in a specific format for an application to access. The problem is that the NODE BODY contains special information and media files that should be converted. My goal is to obtain the HTML output that would normally be used on this field.
// Execute an EntityFieldQuery
$result = $query->execute();
if (isset($result['node'])) {
$article_items_nids = array_keys($result['node']);
$article_items = entity_load('node', $news_items_nids);
}
// Loop through each article
foreach ($article_items as $article) {
return $article->body[LANGUAGE_NONE]['0']['value'];
}
All of this works great. The only problem is that I get things like this in the output:
[[{"type":"media","view_mode":"media_original","fid":"283","attributes":{"alt":"","class":"media-image","data-thmr":"thmr_32","height":"400","width":"580"}}]]
or
*protoss_icon*
My goal is to find a way that these items are converted just like they are when these articles are viewed normally.
I've tried doing things such as:
render(field_view_field('node', $article, 'body'));
or
render($article->body[LANGUAGE_NONE]['0']['value']);
without success. Thanks for any help, I'm learning so I don't have a complete grasp of the process drupal uses to build output.
You can try something like this (this works only with nodes not with other custom entity types):
$node = node_load($nid);
$field = field_get_items('node', $node, 'your_field_name');
$output = field_view_value('node', $node, 'your_field_name', $field[$delta]);
the field_view_value returns a renderable array for a single field value. (from drupal api documentation)
I want to return a value from entity to view file. Below is my entity function
public function getVisitorName($id)
{
$repository = $this->getDoctrine()->getRepository('SystemVmsBundle:VisitorsDetails');
$product = $repository->findOneBy(array('id' =>$id));
$name=$product->getFirstname();
return $name;
}
This is the line in my view file which calls that function
{{ entity.visitorName(entity.visitorId) }}
Its not giving me any error. But only a blank page. How can i fix this?
This is my controller code
public function indexAction()
{
$em = $this->getDoctrine()->getManager();
$entities = $em->getRepository('SystemVmsBundle:EntryDetails')->findAll();
return array(
'entities' => $entities,
);
}
I am trying to fetch the visitors name(from visitors table) corresponding to the visitor id(in entry table).How will i do it then?
you have two ways of doing it:
1) Map your SystemVmsBundle:EntryDetails entity, to SystemVmsBundle:VisitorsDetails as OntToOne by adding field details to your EntryDetails; , and then in twig template just call it via
{{ entity.details.name }}
2) instead of creating getVisitorName(), it is better to create twig function for this, with same functionality.
Your indexAction() is not returning a response object, it is just returning an array of entities. Controller actions should return a Response containing the html to be displayed (unless they are for e.g. ajax calls from javascript). If you are using twig templates you can use the controller render() method to create your response, something like this:
return $this->render('<YourBundle>:<YourViewsFolder>:<YourView>.html.twig', array(
'entities' => $entities,
));
When you've corrected that I suspect you'll get an error because $this->getDoctrine() won't work from an entity class. The code you have in the getVisitorName() method just shouldn't be in an entity class.
As #pomaxa has already suggested, I believe there should be a relationship between your EntryDetails and VisitorsDetails entities although I don't know enough about your data from the question to know what type of relationship it should be (OneToOne / ManyToOne). If your EntryDetails entity had a relationship to VisitorsDetails, the EntryDetails class would then contain a $visitorsDetails attribute and methods to get/set it. Then the line in your twig file would look like this:
{{ entity.visitorsDetails.firstName }}
There is a section on Entity Relationships / Associations in the Symfony Manual.
Also, I hope you don't mind me giving you a little advice:
Be careful when you copy and paste code as it appears you have done in getVisitorName(). You have kept the variable name '$product' although there are no products in your system. This sort of thing can cause bugs and make the code more difficult to maintain.
I recommend you avoid tacking 'Details' onto the end of entity names unless you genuinely have two separate and related entities such as Visitor + VisitorDetails and a good reason for doing so. I think the entities in your example are actually 'Visitor' and 'VistorEntry'.
Unless you are writing a re-usable component, I recommend you use specific variable names like '$visitorEntries' rather than '$entities' in your controller and twig.
In general, the more meaningful your variable names, the more readable, maintainable and bug-free your code is likely to be. Subsequently, it will also be much easier for people on SO to understand your code and give you help when you need it.
I already used versioning on DataObjects when they contain a lot of content, now I'm wondering if it's possible to apply versioning to a many_many relation?
Assuming I have the following:
class Page extends SiteTree
{
private static $many_many = array(
'Images' => 'Image'
);
}
Then the ORM will create a Page_Images table for me to store the relations. In order to have a versioned relation, more tables would be required (eg. Page_Images_Live).
Is there any way to tell the ORM to create versioned relations? When looking at the above example with a Page * – * Images relation, I don't want the Image class to be versioned, but rather the relation. Eg. something like this:
Version Stage:
---
PageA
Images ( ImageA, ImageB, ImageC )
Version Live:
---
PageA
Images ( ImageA, ImageC, ImageD, ImageE )
Is that even possible out of the box?
I've spent a lot of time looking into this and without fundamentally modifying ManyManyList (as it doesn't expose the necessary hooks through the extension system), there isn't many choices.
I am a dessert-first kind of person, how CAN we do it?
My only suggestion to accomplish this feat is essentially a many-to-many bridge object (ie. a separate entity joining Page and Image) via $has_many though it still requires quite a bit of modification.
This is partially discussed on the forum where a solution about subverting the actual relationship by storing the versioned items against the actual object rather than in a joining table. That would work but I think we can still do better than that.
I am personally leaning towards tying the version of the relationship to the Page itself and my partial solution below covers this. Read below the fold for more info trying this as an update to ManyManyList.
Something like this is a start:
class PageImageVersion extends DataObject
{
private static $db = array(
'Version' => 'Int'
);
private static $has_one = array(
'Page' => 'Page',
'Image' => 'Image'
);
}
This contains our 2-way relationship plus we have our version number stored. You will want to specify the getCMSFields function to add the right fields required allowing you to relate it to an existing image or upload a new one. I am avoiding covering this as it should be relatively straight forward compared to the actual version handling part.
Now, we have a has_many on Page like so:
private static $has_many = array(
'Images' => 'PageImageVersion'
);
In my tests, I also added an extension for Image adding the matching $has_many onto it as well like so:
class ImageExtension extends DataExtension
{
private static $has_many = array(
'Pages' => 'PageImageVersion'
);
}
Honestly, not sure if this is necessary beyond adding the Pages
function on the Image side of the relationship. As far as I can see, it won't really matter for this particular usecase.
Unfortunately, because of this way of versioning, we can't use the standard way of calling the Images, we will need to be a bit creative. Something like this:
public function getVersionedImages($Version = null)
{
if ($Version == null)
{
$Version = $this->Version;
}
else if ($Version < 0)
{
$Version = max($this->Version - $Version, 1);
}
return $this->Images()->filter(array('Version' => $Version));
}
When you call getVersionedImages(), it will return all images that have the Version set on it aligning with the version of the current page. Also supports getting previous versions via getVersionedImages(-1) for the last version or even gets images for a specific version of the page by passing any position number.
OK, so far so good. We now need to make sure that every page write we are getting a duplicate list of images for this new version of the page.
With an onAfterWrite function on Page, we can do this:
public function onAfterWrite()
{
$lastVersionImages = $this->getVersionedImages(-1);
foreach ($lastVersionImages as $image)
{
$duplicate = $image->duplicate(false);
$duplicate->Version = $this->Version;
$duplicate->write();
}
}
For those playing at home, this is where things get a bit iffy relating to how restoring previous versions of Page would affect this.
Because we would be editing this in GridField, we will need to do a few things. First is make sure our code can handle the Add New function.
My idea is an onAfterWrite on the PageImageVersion object:
public function onAfterWrite()
{
//Make sure the version is actually saved
if ($this->Version == 0)
{
$this->Version = $this->Page()->Version;
$this->write();
}
}
To get your versioned items displaying in GridField, you would have it set up similar to this:
$gridFieldConfig = GridFieldConfig_RecordEditor::create();
$gridField = new GridField("Images", "Images", $this->getVersionedImages(), $gridFieldConfig);
$fields->addFieldToTab("Root.Images", $gridField);
You might want to link to images directly from the GridField via GridFieldConfig_RelationEditor however this is when things get sour.
Time for the veggies...
One of the big difficulties is GridField, for both linking and unlinking these entities. Using the standard GridFieldDeleteAction will directly update the relationship without the right version.
You will need to extend GridFieldDeleteAction and override the handleAction to write your Page object (to trigger another version), duplicate every version of our versioned image object for the last version while making it skip the one you don't want in the new version.
I'll admit, this last bit is just guesswork by me. From my understanding and debugging, it should work but simply there is a lot of fiddling to get it right.
Your extension of GridFieldDeleteAction then needs to be added to your specific GridField.
This would essentially be your last step away from making this solution work. Once you have the adding, removing, duplicating, version updating part down, it really is a matter of just using getVersionedImages() to get the right images.
Conclusion
Avoid. I get why you want to do this but I really don't see a clean way of being able to handle this without a decent sized update to how many_many relationships are handled in Silverstripe.
But I really want it as a ManyManyList!
The changes I see required for ManyManyList are having a 3-way key (Foreign Key, Local Key, Version Key) and the various methods for adding/removing/fetching etc updated.
If there were hooks in the add and remove functions, you might be able to sneak in the functionality as an extension (via Silverstripe's extension system) and add the needed data to the extra fields that many_many relationships allow.
While I could get this happening by extending ManyManyList directly and then forcing ManyManyList to be replaced with my custom class via Object::useCustomClass, it would be even more of a messy solution.
It is simply too long/complex for me to give a full answer for a pure ManyManyList solution at this stage (though I may get back to this later and give it a shot).
Disclaimer: I am not a Silverstripe Core dev, there may be a neater solution to this entire thing but I simply can't see how.
You can define second relation with "_Live" suffix and update it when the page is published. Note: This solution stores only two versions (live and stage).
Bellow is my implementation which automatically detects whether many-many relation is versioned or not. It then handles publishing and data retrieval. All what is needed is to define one extra many-many relation with "_Live" suffix.
$page->Images() returns items according to the current stage (stage/live).
class Page extends SiteTree
{
private static $many_many = array(
'Images' => 'Image',
'Images_Live' => 'Image'
);
public function publish($fromStage, $toStage, $createNewVersion = false)
{
if ($toStage == 'Live')
{
$this->publishManyToManyComponents();
}
parent::publish($fromStage, $toStage, $createNewVersion);
}
protected function publishManyToManyComponents()
{
foreach (static::getVersionedManyManyComponentNames() as $component_name)
{
$this->publishManyToManyComponent($component_name);
}
}
protected function publishManyToManyComponent($component_name)
{
$stage = $this->getManyManyComponents($component_name);
$live = $this->getManyManyComponents("{$component_name}_Live");
$live_table = $live->getJoinTable();
$live_fk = $live->getForeignKey();
$live_lk = $live->getLocalKey();
$stage_table = $stage->getJoinTable();
$stage_fk = $live->getForeignKey();
$stage_lk = $live->getLocalKey();
// update or add items from stage to live
foreach ($stage as $item)
{
$live->add($item, $stage->getExtraData(null, $item->ID));
}
// delete remaining items from live table
DB::query("DELETE l FROM $live_table AS l LEFT JOIN $stage_table AS s ON l.$live_fk = s.$stage_fk AND l.$live_lk = s.$stage_lk WHERE s.ID IS NULL");
// update new items IDs in live table (IDs are incremental so the new records can only have higher IDs than items in ID => should not cause duplicate IDs)
DB::query("UPDATE $live_table AS l INNER JOIN $stage_table AS s ON l.$live_fk = s.$stage_fk AND l.$live_lk = s.$stage_lk SET l.ID = s.ID WHERE l.ID != s.ID;");
}
public function manyManyComponent($component_name)
{
if (Versioned::current_stage() == 'Live' && static::isVersionedManyManyComponent($component_name))
{
return parent::manyManyComponent("{$component_name}_Live");
}
else
{
return parent::manyManyComponent($component_name);
}
}
protected static function isVersionedManyManyComponent($component_name)
{
$many_many_components = (array) Config::inst()->get(static::class, 'many_many', Config::INHERITED);
return isset($many_many_components[$component_name]) && isset($many_many_components["{$component_name}_Live"]);
}
protected static function getVersionedManyManyComponentNames()
{
$many_many_components = (array) Config::inst()->get(static::class, 'many_many', Config::INHERITED);
foreach ($many_many_components as $component_name => $dummy)
{
$is_live = 0;
$stage_component_name = preg_replace('/_Live$/', '', $component_name, -1, $is_live);
if ($is_live > 0 && isset($many_many_components[$stage_component_name]))
{
yield $stage_component_name;
}
}
}
}
I am trying to use a UploadField on frontend for user to upload their company logo.
There isn't much documentation on UploadField yet. And I have tried it but no luck so far.
Can anyone guide me on how to use it?
This is a little old, but if anyone else stumbles upon this like I did.
UploadField does work frontend. I haven't been able to save into a many_many relationship using the saveInto function. But the biggest thing I missed was the DataObject/Page needs to exist first, as in it needs to be saved before you can attach a related object like an image.
static $has_one = array(
"Photo" => "Image"
);
$fields = new FieldList(
new UploadField( 'Photo', 'Upload' )
);
function saveForm( $data, $form ) {
$object = new DataObject();
// for a new object write before saveinto
$object->write();
$form->saveInto($object);
$object->write();
Director::redirectBack();
}
using ss 3.0.1
Alternatively rather than using the saveinto function you can manually loop over the parameters and attach them on the object yourself for many_many images.
The upload field checks for permissions via the can*() methods in the object.
In order to allow front end editing - you may have to overload File::canEdit (or Image::canEdit) in your custom object to handle this.