Shopware 6 Plugin - Load and display ProductStream via id - plugins

I want to load ProductStream data via a given id and display it in the frontend, for example below the product cart.
The ProductStream is not linked to a ProductCrossSelling and therefore not linked to a product.
I loaded a ProductStream Collection via the repository.
$criteria = new Criteria($producStreamIds);
$productStreamResult = $this->productStreamRepository->search($criteria, $context);
$productStreams = $productStreamResult->getEntities();
Now I need the associated list of products, to include them via cms-element-product-slider.html.twig.
I found a post with a similar question:
How to get products from the product stream ID in Shopware 6?
The answer was, to use the function loadByStream of Core\Content\Product\SalesChannel\CrossSelling\ProductCrossSellingRoute.php, but this function is private.
The only public function is load and it needs a $productId as a parameter, which I do not have, because my ProductStream is not linked to a product.
Is there a clean way to load products of a ProductStream that is not linked to a ProductCrossSelling?
I have currently copied the code of the loadByStream function to load the products for the stream.
Or is there an other function in the shopware core that I can use for this case. I haven't found anything else.
It feels as if the current assumption is that ProductStreams are not used without connection to a ProductCrossSelling and thus to a product.

A product stream is really just a collection of criterium filters. You inject ProductStreamBuilder and call buildFilters with the id of your product stream to retrieve the filters. Then you use the filters as normal with your criteria, add a limit, sorting etc and fetch your product entities.
// <argument type="service" id="Shopware\Core\Content\ProductStream\Service\ProductStreamBuilder"/>
private ProductStreamBuilderInterface $productStreamBuilder;
// <argument type="service" id="sales_channel.product.repository"/>
private SalesChannelRepositoryInterface $productRepository;
// ..
$filters = $this->productStreamBuilder->buildFilters(
$productStreamId,
$salesChannelContext->getContext()
);
$criteria = new Criteria();
$criteria->addFilter(...$filters);
$products = $this->productRepository
->search($criteria, $salesChannelContext)
->getEntities();

Related

Magento 2 with Full Page Cache: How to get product ID from a product page?

I am trying to find a solution to what seems to be a FPC-linked issue.
In my code I am using the following method in order to get the current product ID from a Product page.
$this->catalogSession->getData('last_viewed_product_id')
This worked just fine until I tried it on a website with Full Page Cache: in this case, it returns an empty value (maybe because the session data cannot be accessed from cache).
Does anyone know an alternative method to get the product ID from the current context?
I have tried this alternative synthax:
$_SESSION['catalog']['last_viewed_product_id'];
While not the best solution and definitely not best practice, you can use objectmanager:
$objectManager = \Magento\Framework\App\ObjectManager::getInstance();
$product = $objectManager->get('Magento\Framework\Registry')->registry('current_product');
$id = $product->getId();
This will get you the id but, as stated above, it's not suggested and you should create a block and inject the registry there and call it in your code.
You can see this post https://meetanshi.com/blog/get-current-product-id-in-magento-2/

Shopware 6 Forms

Newbie question. How to handle Storefront Form Submit in Shopware 6? How to save the data from form to database? I have an entity, form shown in storefront and a controller but i have no idea how to save the data to entity. Thanks in advance.
you would have to be more specific with the description, of what exactly you are trying to achieve.
But in general, if you already have a controller, that receives the data, then you can get them from the request like this:
$data = $request->request->all();
By this, you have all the values from your form saved in an array $data. You have written, that you already have an entity, so from that I assume, that your entity is already mapped to your database table. So the only thing you have to do, is to use the repository to save the data. For thta, you just need to inject it into your class and get a context. The context depends on where you currently are, so for the purpose of the example, I have just created the default context.
It should look like this:
class MyClass
{
protected $myEntityRepository;
public function __construct(
MyEntityRepository $myEntityRepository
)
{
$this->myEntityRepository = $myEntityRepository;
$this->context = \Shopware\Core\Framework\Context::createDefaultContext();
}
public function myMethod ($data)
{
$this->myEntityRepository->upsert($data, $this->context);
}
}
Hope this helps. I have actually written an article on repositories in Shopware 6, so if you want to get some more information and examples, you can check it here: https://shopwarian.com/repositories-in-shopware-6/.

Creating a Bill with .NET IPP Data Services SDK

I have a SaaS application that creates expenses within QuickBooks Online. In the the IPP documentation (https://ipp.developer.intuit.com/0010_Intuit_Partner_Platform/0050_Data_Services/0400_QuickBooks_Online/Bill) it shows that on a line object you can specify the AccountID (its the last field in the documentation) and that is required to create an account-based expense.
However, in the .NET object for BillLine, there appears to be no way to specify that data. Does anyone know how to create an "Account-based Bill Expense" using the .NET SDK?
Intuit.Ipp.Data.Qbo.BillLine billLine = new Intuit.Ipp.Data.Qbo.BillLine();
billLine.ItemsElementName = new ItemsChoiceType1[] {ItemsChoiceType1.AccountId, ItemsChoiceType1.AccountName};
billLine.Items = new object[] {new IdType() {idDomain = idDomainEnum.QB, Value = "123"}, "MyAccountName"};
Any properties that are not exposed directly are defined in the ItemsElementName property, and the value is passed in the respective index in the Items object array. You will see this across most entities in the .NET DevKit.

Navigation Property Filter

My question is this: How can you implement a default server-side "filter" for a navigation property?
In our application we seldom actually delete anything from the database. Instead, we implement "soft deletes" where each table has a Deleted bit column. If this column is true the record has been "deleted". If it is false, it has not.
This allows us to easily "undelete" records accidentally deleted by the client.
Our current ASP.NET Web API returns only "undeleted" records by default, unless a deleted argument is sent as true from the client. The idea is that the consumer of the service doesn't have to worry about specifying that they only want undeleted items.
Implementing this same functionality in Breeze is quite simple, at least for base entities. For example, here would be the implementation of the classic Todo's example, adding a "Deleted" bit field:
// Note: Will show only undeleted items by default unless you explicitly pass deleted = true.
[HttpGet]
public IQueryable<BreezeSampleTodoItem> Todos(bool deleted = false) {
return _contextProvider.Context.Todos.Where(td => td.Deleted == deleted);
}
On the client, all we need to do is...
var query = breeze.EntityQuery.from("Todos");
...to get all undeleted Todos, or...
var query = breeze.EntityQuery.from("Todos").withParameters({deleted: true})
...to get all deleted Todos.
But let's say that a BreezeSampleTodoItem has a child collection for the tools that are needed to complete that Todo. We'll call this "Tools". Tools also implements soft deletes. When we perform a query that uses expand to get a Todo with its Tools, it will return all Tools - "deleted" or not.
But how can I filter out these records by default when Todo.Tools is expanded?
It has occurred to me to have separate Web API methods for each item that may need expanded, for example:
[HttpGet]
public IQueryable<Todo> TodoAndTools(bool deletedTodos = false, bool deletedTools = false)
{
return // ...Code to get filtered Todos with filtered Tools
}
I found some example code of how to do this in another SO post, but it requires hand-coding each property of Todo. The code from the above-mentioned post also returns a List, not an IQueryable. Furthermore this requires methods to be added for every possible expansion which isn't cool.
Essentially what I'm looking for is some way to define a piece of code that gets called whenever Todos is queried, and another for whenever Tools is queried - preferably being able to pass an argument that defines if it should return Deleted items. This could be anywhere on the server-side stack - be it in the Web API method, itself, or maybe part of Entity Framework (note that filtering Include extensions is not supported in EF.)
Breeze cannot do exactly what you are asking for right now, although we have discussed the idea of allowing the filtering of "expands", but we really need more feedback as to whether the community would find this useful. Please add this to the breeze User Voice and vote for it. We take these suggestions very seriously.
Moreover, as you point out, EF does not support this.
But... what you can do is use a projection instead of an expand to do something very similar:
public IQueryable<Object> TodoAndTools(bool deleted = false
,bool deletedTools = false) {
var baseQuery = _contextProvider.Context.Todos.Where(td => td.Deleted == deleted);
return baseQuery.Select(t => new {
Todo: t,
Tools: t.Tools.Where( tool => tool.Deleted = deletedTools);
});
}
Several things to note here:
1) We are returning an IQueryable of Object instead of IQueryable of ToDo
2) Breeze will inspect the returned payload and automatically create breeze entities for any 'entityTypes' returned (even within a projection). So the result of this query will be an array of javascript objects each with two properties; 'ToDo' and 'Tools' where Tools is an array of 'Tool' entities. The nice thing is that both ToDo and Tool entities returned within the projection will be 'full' breeze entities.
3) You can still pass client side filters based on the projected property names. i.e.
var query = EntityQuery.from("TodoAndTools")
.where("Todo.Description", "startsWith", "A")
.using(em);
4) EF does support this.

Adding a custom/relate field in Email template in Sugarcrm

I have created a custom module named surveys which will be in many to many relationship with targets. I need to insert survey name while sending campaign mails to targets.
Currently I managed to populate survey module entities in insert variable by following the guide from adding a custom module in insert variable dropdownlist in email template but the issue is it will never parse the survey name and displays $survey_name in emails that are delivered.
Any help/guidance to sort this out.
I just added the answer to that forum. The sugarCrm EmailTemplate modules basically is a lot of code to get just few beans working (Accounts, Contacts, Leads, Users, Prospects), I get some templates working with other beans with this steps, this code is based on SugarCrm 6.0.2, class modules/Email.php details on the forum:
1) Let the Email.php create the own bean. Example: LOC510
if (... $_REQUEST['parent_type'] == 'Prospects' || TRUE)
2) Create an array of replace fields. Example: LOC521
foreach($bean->field_defs as $key => $field_def) {
$replace_fields ['$'.strtolower(get_class($bean).'_'.$field_def['name'])]
= $bean->$field_def['name'];
//example of fieldnames: $bug_name, $bug_type, $case_date_created, $case_name, etc...
}
3) Replace the fields on html template. Example: LOC545
$this->description_html = str_replace(array_keys($replace_fields), $replace_fields, $this->description_html);
3) Replace the fields on txt template. Example: LOC549
$this->description = str_replace(array_keys($replace_fields), $replace_fields, $this->description);