I have a module that needs a canonical link injected into <head> on literally every page on frontend. Is there a way to do it? Currently, given my module doesn't need its own page on frontend, nor any controllers whatsoever, I have only set the helper in my config.xml. Now, I do have an xml in layout, but the problem is that I need to change canonical link attributes based on user input(in admin), so XML doesn't fit. Yes, I could indeed fopen said fronted layout xml file, then replace what I need, then write new content back to it, but I wanted to check first whether there's other way for achieving that.
You can hook in on the core_block_abstract_prepare_layout_before event and use the Head block's addLinkRel method to add the link tag.
In your config.xml you need to define an observer like so:
<events>
<core_block_abstract_prepare_layout_before>
<observers>
<your_module>
<class>Your_Module_Model_Observer</class>
<method>addCanonicalLink</method>
</your_module>
</observers>
</core_block_abstract_prepare_layout_before>
</events>
Create an observer class in the Model directory
<?php
class Your_Module_Model_Observer {
public function addCanonicalLink(Varien_Event_Observer $observer) {
$block = $observer->getData('block');
if ($block->getNameInLayout() === 'head') {
// this will add <link rel="canonical" href="http://your-url.com">
$block->addLinkRel('canonical', 'http://your-url.com');
// If you need more attributes on the link tag use addItem instead
// This will add <link rel="canonical" href="http://your-url" attr="Your Attribute">
// $block->addItem('link_rel', 'http://your-url', 'rel="canonical" attr="Your Attribute"')
}
}
}
Update:
Since the core head.phtml template files run echo $this->getChildHtml() (render all children) it is possible to insert tags by adding a core/text block as a child and add a text string to it (just like you already tried with xml). If addItem doesn't fit your needs, this is more flexible as you can insert any string this way. Replace thie $block->addLinkRel line with
$canonical = $block->getLayout()->createBlock('core/text')
->setText('<link rel="canonical" href="http://your-url.com">')
$block->append($canonical);
Related
I have some issues saving categories in Magento 2.3.5, when I click save after changing the SEO information (Meta Title, Meta description and Meta Keywords) gives me this error.
Argument 1 passed to Magento\Catalog\Model\Category\FileInfo::removeStorePath() must be of the type string, array given, called in /home/adminpsol2016/public_html/vendor/magento/module-catalog/Model/Category/FileInfo.php on line 167
here you can see a screenshot of the problem.
This gave me quite a headache but finally managed to get to the bottom of it; my case is as follow:
Repro:
add a custom category attribute with backend_model := Magento\Catalog\Model\Category\Attribute\Backend\Image
Have the category form save operation fail for whatever reason (e.g. have a plugin on the category model save function which throws an Exception)
Reason:
If you look at https://github.com/magento/magento2/blob/2.4-develop/app/code/Magento/Catalog/Controller/Adminhtml/Category/Save.php#L240 you'll see that this has the effect of storing the entire POST data of the current form request to session (also the LocalizedException block does the same).
Later on, this data is restored in https://github.com/magento/magento2/blob/2.4-develop/app/code/Magento/Catalog/Controller/Adminhtml/Category/Edit.php#L95 and immediately after the form information for the image attribute is stripped/cleared.
This of course does not handle any custom attribute of Image type we might have defined for our category entity.
Solution:
I added an after* plugin (in adminhtml area only) on \Magento\Framework\Session\SessionManager::__call, where I explicitly check that the invoked method is getCategoryData: if this is the case, I fetch all the custom category image attributes, and strip them from the returned array like Category/Edit does.
This way any further exception message is correctly displayed in the backoffice (granted it extends LocalizedException)
Just to expand on the answer from Francesco Salvi which really helped me with the same problem, this is how we implemented that solution:
etc/adminhtml/di.xml
<?xml version="1.0" ?>
<config>
<type name="Magento\Framework\Session\SessionManager">
<plugin name="pluginNameGoesHere" type="Vendor\Namespace\Plugin\StripCustomImage" />
</type>
</config>
plugin/StripCustomImage.php
<?php
namespace Vendor\Namespace\Plugin;
class StripCustomImage
{
public function after__call($method, $response, ...$args)
{
if ($args[0] === 'getCategoryData') {
if (isset($response['widget_image']['delete'])) {
$response['widget_image'] = null;
} else {
unset($response['widget_image']);
}
}
return $response;
}
}
Where 'widget_image' is the attribute name for the custom category image we created in another module that was causing us the pain.
I am starting to learn how to work with Magento. I like the concept of static block and that it is so easy to add them to a cms page using the static block widget.
The widget gives me a dropdown where I can choose what template file it should use. By default there is only the default template file located in cms/widget/static_block/default.phtml
Here is the dialog:
How can I add my own template files to the dropdown?
I know that the widget only creates the following line:
{{widget type="cms/widget_block" template="cms/widget/static_block/default.phtml" block_id="8"}}
And I know that I could easly change the "template" attribute. But I think that is not a clean way as in the future, users don't know the right file name and it would be much easier for them just to use the dropdown.
I hope I could explain the problem and thanks for any help!
For the purposes of this answer I will assume you understand how Magento extensions work, but if not there are many good answers on this topic.
Widgets are defined by widget.xml files, so you will need to add a widget.xml file to the etc subdirectory of a new or existing extension (i.e. app/code/local/My/MyExtension/etc). Since the CMS Static Block widget is already defined as <cms_static_block> in app/code/core/Mage/Cms/etc/widget.xml, your new widget.xml file will just be injecting a new template node into the existing XML config of this widget. Here is what your new widget.xml might look like:
<?xml version="1.0"?>
<widgets>
<cms_static_block>
<parameters>
<template>
<values>
<custom translate="label">
<value>cms/widget/static_block/custom.phtml</value>
<label>My Extension - Custom CMS Block Template</label>
</custom>
</values>
</template>
</parameters>
</cms_static_block>
</widgets>
As you probably already know, the <value> node refers to the relative location of your custom template. So in my example above, it refers to a template file here (whatever your custom package/theme dir is): app/design/frontend/my_package/my_theme/template/cms/widget/static_block/custom.phtml
I have a custom block within Magento Community which rewrites the Orders Tab in Adminhtml and replaces it with a Fullcalendar(arshaw.com's one) which displays events for when products are to be shipped.
I have implemented a JSON feed which relies on my custom modules controller to populate and save the data. This works beautifully and I can see the products once I click on Month / Today / Prev / Next. However, this is not how it should work. It should render when the page loads, except all I see are the buttons.
I have tried referencing the tab in javascript which it is in eg.
jQuery('#diagram_tab_orders_content').tabs({
show: function(event, ui) {
jQuery('#dashCalendar').fullCalendar('render');
}
Where #dashCalendar is the id / class of my tag.
Except this does not render the calendar. It does nothing but change the CSS of diagram_tab_orders_content.
I've seen a similar topic on Stackoverflow where you must explicitly reference the tab where the calendar is in order for it to render properly. This is what I'm trying to achieve in order for the calendar to render when the document is ready.
Any suggestions on how to make this calendar render when the document is ready would be much appreciated.
So I've figured out the issue with the calendar and it pertains to the tabs within Magento. Referencing the tabs explicitly within Javascript causes issues with the rendering, thus I had to rewrite the Dashboard block.
Config.xml in my ModuleName/etc folder:
<global>
<!-- Blocks -->
<blocks>
<adminhtml>
<rewrite>
<dashboard>ModuleName_Block_Adminhtml_Calendar</dashboard>
</rewrite>
</adminhtml>
</blocks>
</global>
Then within my ModuleName/Block/Calendar.php file I set the template:
public function __construct()
{ //this creates the template block for the calendar
parent::__construct();
$this->setId('Calendar');
$this->setTemplate('moduleName/calendar/calendar.phtml');
}
Copy/Paste the fullcalendar's JSON Example page provided, set your events: tag to your .php file which generates / requests data from your database using JSON. Customise your Theme if you want using the provided CSS and you will see the calendar render with the events when you log into your Adminhtml. Below you will see 'Populate'. Its an action within my Controller which retrieves the data and echo's out a JSON feed to the Calendar.
events: {
url: "<? echo $this->getUrl('moduleName/adminhtml_calendar/populate') ?>"
},
Now the Calendar renders with events properly.
What I want to achieve:
Clicking on a product link/image (at least in certain areas) to open a pop-up with the full product information (basically all the contents of the product view page).
What I did/tried so far:
created all the stuff outside the ajax php code (the module, links, templates, rewrites)
created the ajax controller (which can be accessed with a link similar to: http://test.com/index.php/ajaxproductview/ajax/index/id/2 ).
to follow various tutorials ( like this or this ) - that helped me get this far. But I don't want to load my custom block, I want the default product view block(s).
tried to add some code in the indexAction(). It gets there, but the code fails. I don't get any errors/notices/reports, just what it seems like an infinite loop that kills my processor.
$body = $this
->getLayout()
->createBlock('product.info') // taken from catalog.xml
->toHtml();
$this->getResponse()->setBody($body);
All the other pages work fine, and it's a fresh magento with only magneto and my module installed and activated.
My AJAX function simply gets this HTML response, puts it into a div, and opens a pop-up.
My question(s) is(are) - how can I set the product id, so the block knows what product to load, and how can I load this block correctly. I also tried something similar to this:
Thank you.
PS: I also tried this:
$layout = $this->getLayout();
$update = $layout->getUpdate();
$update->load('catalog_product_view');
$layout->generateXml();
$layout->generateBlocks();
$output = $layout->getOutput(); // $output is an empty string
The Product controller uses a helper to set the active product. You should be able to do the same in your controller!
Try this before you do your layouting:
$productId = (int) $this->getRequest()->getParam('id');
Mage::helper('catalog/product')->initProduct($productId, $this);
Another thing to be aware of:
If you add a block like the product.info block. It needs additional child blocks if it calls them in its template file.
It would be easiest to use a custom layout xml file. You can then add a specific layout for your action handle (your action handle consists of your routers node in your module's etc/config.xml file under <frontend><routers>, e.g. <Yourmodule> node, make sure to lowercase it! And then with underscores add the controller name and action name, in your case index_index) like this:
<yourmodule_index_index>
<remove name="right"/>
<remove name="left"/>
<block type="catalog/product_view" name="root" output="toHtml" template="catalog/product/view.phtml">
<!-- Add all the child blocks you need -->
</block>
</yourmodule_index_index>
This makes the view.phtml the root block which renders itself using its toHtml method.
Therefore, in your controller action, all you need is my two lines above and then:
$this->loadLayout();
$this->renderLayout();
I have custom modules (that I bought or developed) that have frontend pages in my store.
How can I include these pages in the Google sitemap Magento generates?
The function that generates the sitemap is: Mage_Sitemap_Model_Sitemap::generateXml()
which does the following:
open file and write xml header
get the category collection and write it to sitemap file
get the products collection and write it to sitemap file
get the CMS collection and write it to sitemap file
close the sitemap xml file
No event is dispatched before closing the xml file... in this case you can extend this Mage_Sitemap_Model_Sitemap and override the generateXml() function adding your own custom URLs (if these urls are not in any of the above collections: category, product, cms page)
Another approach to your issue is using multiple sitemap files and a sitemap index file (Google Supports this: http://googlewebmastercentral.blogspot.co.uk/2006/10/multiple-sitemaps-in-same-directory.html). In this case, you can have a separate module to generate the 2nd xml sitemap file.
Magento did add the events sitemap_products_generating_before and sitemap_categories_generating_before in later versions (I don't know when, my version is 1.9.2). So in case you stumple upon this thread like me, looking for the best way to add custom modules' pages to the Google Sitemap, this is how simple it is with these events:
In my case I have a custom module that creates dynamic "News" pages, so I add an event listener to the <global> tag in my module's config.xml. I chose to go with the product sitemap event*:
<events>
<sitemap_products_generating_before>
<observers>
<{your_unique_event_observer_name}>
<class>{Brand}_{Module}_Model_Observer</class>
<method>addPagesToSitemap</method>
</{your_unique_event_observer_name}>
</observers>
</sitemap_products_generating_before>
</events>
And then, in my module's Observer, I let the method addPagesToSitemap add extra items to the product collection (or category collecton, if you went with the category sitemap event):
class {Brand}_{Module}_Model_Observer
{
function addPagesToSitemap(Varien_Event_Observer $observer){
$sitemapItems = $observer->getEvent()->getCollection()->getItems();
// Get your module's page collection including their urls
// Adjust the following lines to your needs
$collection = Mage::getModel('{brand}_{module}/pages')->getCollection()
->addFieldToSelect(array('page_id','url'))
->addFieldToFilter('display', 1);
// My module stores the page path separately,
// you might don't need this:
$modulePagePath = Mage::helper('{my-modules-helper}')->getNewsPath();
foreach($collection as $_item){
$varienObject = new Varien_Object();
// We don't want to override
// any existing product/category items
$uniqueId = '{module}'.$_item->getPageId();
$varienObject->setId($uniqueId);
// You might want to adjust this if your item
// stores the complete url. Don't add the base url tho,
// Mage_Sitemap_Model_Sitemap::generateXml adds it
$varienObject->setUrl($modulePagePath . DS . $_item->getUrl());
$sitemapItems[$uniqueId] = $varienObject;
}
$observer->getEvent()->getCollection()->setItems($sitemapItems);
return $this;
}
}
That's it. You can test it by manually generating your sitemap under Catalog > Google Sitemap.
*For some reasons Magento did not add an event "sitemap_pages_generating_before", so you have to either choose the products' or category's event to go with. For both events Magento takes the respective changefreq- and priority-value set for category & product pages in your Magento configuration. So just choose the event which values fit better.