Bundle products not added into cart in magento 2 - magento2

I create a bundle products with many group products inside bundle product, where user can also change the qty of group product inside bundle, all group products have qty more than 10 ,but the problem is that its not adding to cart.
Error shows screenshot

It was a caching/SID issue!
Symptoms to reproduce: Magento Blocks HTML caching is turned on (only when turned on)
A SID parameter was being added to the URL that I couldn't see and was being added in between query parameters in a way that messed everything up.
How to fix, add '_nosid' => true to URL params:
$params = array('_query'=> $query,'qty' => 1, '_nosid' => true);
Then the add to cart URL can be retrieved by
Mage::helper('checkout/cart')->getAddUrl($_product, $params)

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 Plugin - Load and display ProductStream via id

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();

Get list of projects user has access to

I have a site for automation, and want to display to users a list of projects they have access to in a dropdown.
If I have a a PAT for an admin account in the org, how can I get the list of projects given a user's email?
Presumably the REST api is the best way to do this?
https://learn.microsoft.com/en-us/rest/api/azure/devops/?view=azure-devops-rest-7.1
I've found two ways to do this.
First method: list all projects, filter based on user list
projects = GET https://dev.azure.com/{orgname}/_apis/projects?api-version=6.0
results = []
foreach project in projects:
descriptor = GET https://vssps.dev.azure.com/{orgname}/_apis/graph/descriptors/{project.id}
members = GET https://vssps.dev.azure.com/{orgname}/_apis/graph/users?api-version=6.0-preview.1&scopeDescriptor={descriptor}
if userId in members:
results.push(project)
Second method: get user entitlements
This is better because this will show all projects that people have Reader (or higher) on, where the first method doesn't show projects where the user doesn't have explicit membership
users = GET https://vsaex.dev.azure.com/{orgname}/_apis/UserEntitlements?$filter=name eq '{userId}'&$orderBy=name Ascending&select=Projects
user = [x for x in users where x.userId == userId][0]
results = user.projectEntitlements
Note the select query parameter included in the second example, this is necessary for projectEntitlements to be included in the result.

How can I create and update pages dynamically in Sulu CMS?

I have the following situation:
A database stores information about houses (address, number of rooms, date built, last selling price, etc.)
This database is being manipulated through an app (let's call that app the "backend house app") that cannot be directly integrated in a Sulu-driven app. I can access the stored data through an API that gives me JSON-representations of House-objects. I can also have the app launch some sort of call to a Sulu-driven app when a house is created, updated or deleted.
The Sulu-driven app (let's call that the "frontend house app") with templates for "house", "room", etc., is connected to a different database on a different server. This Sulu-driven app's website-environment shows house-pages with room-pages where some content is pre-filled through a connection to the "backend house app". Other content only exists on the database of the "frontend house app", like user comments, appraisals of interior design, etc., according to configured aspects of the Sulu-templates.
What I want to achieve, is a way to automate the creation, updating and deletion of "frontend house app"-pages based on activity in the "backend house app".
For instance, when a new house is added in the "backend house app", I want it to notify the "frontend house app" so that the "frontend house app" will automatically create the entire node-tree for the newly added house. Meaning: a "house"-page with the required data filled in, "room"-pages for each room, etc., so that the content manager of the "frontend house app" can see the entire tree of the newly added house in the workspace and can start manipulating content in the already available templates. In addition to automatically creating these pages, I also want to pre-set the rights to update and create, since the content manager of the "frontend house app" must not be able to create new rooms or change the name of the house, for instance.
I did not manage to get it working, I'll just add what I already done to show where I got stuck.
I started out with the following code, in a controller that extends Sulu's own WebsiteController:
$documentManager = $this->get('sulu_document_manager.document_manager');
$nodeManager = $this->get('sulu_document_manager.node_manager');
$parentHousesDocument = $documentManager->find('/cmf/immo/routes/nl/huizen', 'nl');
$newHouseDocument = $documentManager->create('page');
// The backendApi just gives a House object with data from the backend
// In this case we get an existing House with id 1
$house = $backendApi->getHouseWithId(1);
$newHouseDocument->setTitle($house->getName()); // For instance 'Smurfhouse'
$newHouseDocument->setLocale('nl'); // Nl is the only locale we have
$newHouseDocument->setParent($parentHouseDocument); // A default page where all the houses are listed
$newHouseDocument->setStructureType('house'); // Since we have a house.xml template
// I need to grab the structure to fill it with values from the House object
$structure = $newHouseDocument->getStructure();
$structure->bind([
'title' => $house->getName(),
'houseId' => $house->getId(),
]);
$newHouseDocument->setWorkflowStage(WorkflowStage::PUBLISHED); // You would expect this to automatically publish the document, but apparently it doesn't... I took it from a test I reverse-engineered in trying to create a page, I have no clue what it is supposed to change.
$nodeManager->createPath('/cmf/immo/routes/nl/huizen/' . $house->getId());
$documentManager->persist(
$newHouseDocument,
'nl',
[
'path' => '/cmf/immo/contents/huizen/' . Slugifier::slugify($house->getName()), // Assume for argument's sake that the Slugifier just slugifies the name...
'auto_create' => true, // Took this value from a test that creates pages, don't know whether it is necessary
'load_ghost_content' => false, // Idem
]
);
$documentManager->flush();
Now, when I fire the controller action, I first get the exception
Property "url" in structure "house" is required but no value was given.
I tried to fix this by just manually binding the property 'url' with value '/huizen/' . $house->getId() to $structure, at the point where I bind the other values. But this doesn't fix it, as apparently the url value is overwritten somewhere in the persist event chain, and I haven't yet found where.
However, I can, just for testing purposes, manually override the url in the StructureSubscriber that handles the mapping for this particular persist event. If I do this, something gets created in the Sulu-app-database - hurray!
My phpcr_nodes table lists two extra records, one for the RouteDocument referring to /cmf/immo/routes/nl/huizen/1, and one for the PageDocument referring to /cmf/immo/contents/huizen/smurfhouse. Both have the workspace_name column filled with the value default_live. However, as long as there are not also records that are complete duplicates of these two records except with the value default in the workspace_name column, the pages will not appear in the Sulu admin CMS environment. Needless to say, they will also not appear on the public website proper.
Furthermore, when I let the DocumentManager in my controller action try to ->find my newly created document, I get a document of the class UnknownDocument. Hence, I cannot have the DocumentManager go ->publish on it; an Exception ensues. If I visit the pages in the Sulu admin environment, they are hence unpublished; once I publish them there, they can be found by the DocumentManager in the controller action - even if I later unpublish them. They are no longer UnknownDocument, for some reason. However, even if they can be found, I cannot have the DocumentManager go ->unpublish nor ->publish - that just has NO effect on the actual documents.
I was hoping there would be a Sulu cookbook-recipe or another piece of documentation that extensively describes how to create fully published pages dynamically, thus without going through the 'manual labor' of the actual CMS environment, but so far I haven't found one... All help is much appreciated :)
PS: For the purposes of being complete: we're running Sulu on a Windows server environment on PHP 7.1; dbase is PostgreSQL, Sulu being a local forked version of release tag 1.4.7 because I had to make some changes to the way Sulu handles uploaded files to get it to work on a Windows environment.
EDIT: a partial solution for making a new house page if none exists already (not explicitly using the AdminKernel, but should of course be run in a context where the AdminKernel is active):
public function getOrCreateHuisPagina(Huis $huis)
{
$parent = $this->documentManager->find('/cmf/immo/routes/nl/huizen', 'nl'); // This is indeed the route document for the "collector page" of all the houses, but this doesn't seem to give any problems (see below)
try {
$document = $this->documentManager->find('/cmf/immo/routes/nl/huizen/' . $huis->id(), 'nl'); // Here I'm checking whether the page already exists
} catch(DocumentNotFoundException $e) {
$document = $this->setupPublishedPage();
$document->setTitle($huis->naam());
$document->setStructureType('huis_detail');
$document->setResourceSegment('/huizen');
$document->setParent($parent);
$document->getStructure()->bind([
'title' => $huis->naam(), // Not sure if this is required seeing as I already set the title
'huis_id' => $huis->id(),
]);
$this->documentManager->persist(
$document,
'nl',
[
'parent_path' => '/cmf/immo/contents/huizen', // Explicit path to the content document of the parnt
]
);
}
$this->documentManager->publish($document, 'nl');
return $document;
}
First of all I think the following line does not load what you want it to load:
$parentHousesDocument = $documentManager->find('/cmf/immo/routes/nl/huizen', 'nl');
It loads the route instead of the page document, so it should look like the following:
$parentHousesDocument = $documentManager->find('/cmf/immo/contents/nl/huizen', 'nl');
Regarding your error with the URL, instead of overriding the StructureSubscriber you should simple use the setResourceSegment method of the document, which does exactly what you need :-)
And the default_live workspace is wrong, is it possible that you are running these commands on the website kernel? The thing is that the WebsiteKernel has the default_live workspace as default, and therefore writes the content in this workspace. If you run the command with the AdminKernel it should land in the default workspace, and you should be able to copy it into the default_live workspace with the publish method of the DocumentManager.
I hope that helps :-)

Calling 'RemoveItemFromOrder' of CartModifierFormHandler after already proceeded to checkout by calling 'moveToPurchaseInfo' in ATG

Context:
In ATG Commerce - for going into checkout one need to call moveToPurchaseInfo method of the CartModifierFormHandler which executes the moveToPurchaseInfo pipeline chain and check the order/commerce items and validates them. Then, checkout login page will be displayed if the
user has not logged yet. Otherwise user will be directed to the shipping page.
Requirement:
Even after going to shipping page user should be able to remove/updateQuantity of items in Cart.
Question:
if I want to remove/updateQuanity at this stage, do I just need to call 'RemoveItemFromOrder' Or if I'll have to call "moveToPurchaseInfo" again after any modification in the cart ? Any other alternative to fulfill above requirement ?
Resolving the CartModifierFormhandler you can do something like this
String[] skuIds = { "sku10011"};
CartModifierFormHandler cmfh = (CartModifierFormHandler) ServletUtil .getCurrentRequest().resolveName("/atg/commerce/order/purchase/CartModifierFormHandler");
cmfh.setCatalogRefIds(skuIds);
cmfh.setProductId("prod10010");
cmfh.setQuantity(12);
cmfh.handleAddItemToOrder(ServletUtil.getCurrentRequest(),
ServletUtil.getCurrentResponse());
order = cmfh.getOrder();
DynamoHttpServletRequest request = ServletUtil.getCurrentRequest();
request.setParameter("sku2", "13");
// Set the new quantity for the commerce item being updated.
cmfh.setCheckForChangedQuantity(true);
DynamoHttpServletResponse response = request.getResponse();
cmfh.handleSetOrder(request, response);
List<CommerceItem> commerceItem = order.getCommerceItems();
double quantity = commerceItem.get(0).getQuantity();
assertEquals(13, quantity, 0);
#Vihung for making the correction
There is a Update Order Pipeline Chain in ATG Commerce Checkout.
So, whenever there is a change in an Order, during Checkout or before Checkout, the update order chain is always called.
Now if you see your operations:
Update Order / Remove Item from Order - Both are update operations. So everytime you do this kind of update, calling the Update Order Pipeline Chain should suffice. But, make sure that you re-price the order (repriceOrderChain) before calling the Update Order Chain. If you keep digging in the addItemToOrder method, you will know how the call the two pipeline chains to update the order.
Hence, you don't need to use the moveToPurchaseInfo because your order was already checked on the Checkout specific parameters when you Moved to Checkout for the first time. And now the only change you are doing is update quantity / remove items.
PS: Do reply if you find anything different.
Regards,
Gaurav E