Magento email template: Block template inheritance broken - magento

Problem
When I try to add a block into my transactional email template in the following manner:
{{block type='core/template' area='frontend' template='invent/baskettimer/email_items.phtml' record=$record}}
I get the following error, and nothing is rendered.
CRIT (2): Not valid template file:frontend/base/default/template/invent/baskettimer/email_items.phtml
Troubleshooting
Normally this warning points to a typo which is breaking the inheritance but I have quadruple checked and this should work.
I then copied the file into the base and did a test, it rendered correctly.
Create a custom block and set the template, same error is displayed.
Theory
To me it seems template inheritance is broken / not implemented for emails, so it is always looking in base, I cannot put my templates there so I am not sure how to call them.
Possible workarounds
Render the block to html then send it to as a variable to render, problem with this is I am sending the emails from Model level and am having a hard time pre rendering the block, even with a helper.
Render the data using a method, don't really want to do this as it is message / against MVC.
Any help is much appreciated.
Bounty update
So I have traced down the problem, it is probably an easy solution now.
The problem is that I am calling it from a cronjob does not have the correct store view, it is fairly easy to replicate similar situation by using a shell script, then changing the _appCode to null.
<?php
require_once 'abstract.php';
class Mage_Shell_Shell extends Mage_Shell_Abstract
{
protected $_appCode = ''; // works - remove to not work
/**
* Run script
*
*/
public function run()
{
Mage::getModel('invent_baskettimer/email')->sendJob();
}
}
$shell = new Mage_Shell_Shell();
$shell->run();
So basically the question has become:
How do I call a block->toHtml() regardless of store view?

There is not way of setting a cronjob to be like that. Lucky magento lets you emulate your store views, see the following to emulate the default store.
public function cronjob()
{
$iDefaultStoreId = Mage::app()
->getWebsite()
->getDefaultGroup()
->getDefaultStoreId();
$appEmulation = Mage::getSingleton('core/app_emulation');
$initialEnvironmentInfo = $appEmulation->startEnvironmentEmulation($iDefaultStoreId);
.. do your stuff here ..
$appEmulation->stopEnvironmentEmulation($initialEnvironmentInfo);
}
For more info see: http://inchoo.net/ecommerce/magento/emulate-store-in-magento/

Related

Getting the content from a MailMessage object

I am trying to create a system that will automatically log the content of any email notifications that my site sends to users, against their accounts.
I am doing this by listening for the NotificationSent event, which provides me with easy access to the Notifiable (the object that I want to store my log entry against) and the Notification (the object that defined the message that has been sent).
Using these I am able to get hold of the MailMessage object, but I can't work out how to render it. I was hoping it would have a render method but I can't find one. Presumably there is some other object that takes the MailMessage and does the rendering. Any clues?
Ideally I'd like the plain text version of the email (the markdown)
Thanks for any help
I've got a solution that is doing what I want. I cannibalised some code I wrote for something else, that got me a long way there. There are some steps that I don't completely understand so there may well be some unnecessary bloat in here:
class LogNotification
{
public function handle(NotificationSent $event)
{
//getting the MailMessage object
$mailMsg = $event->notification->toMail($event->notifiable);
//I think this is getting the additional variables from the
//MailMessage that are needed to render the view
$msgData = array_merge($mailMsg->toArray(),$mailMsg->viewData);
//I don't fully understand this. I get that the Markdown object is
//going to do the rendering, but I don't know why it needs to be
//instantiated with an empty View object
$markdown = new \Illuminate\Mail\Markdown(view(), config('mail.markdown'));
//Pass the renderText method the path to the markdown, and the message data
$msgContent = $markdown->renderText($mailMsg->markdown, $msgData);
}
}
Hopefully this is helpful to someone (and if anyone can offer a proper explanation of what is happening when the Markdown object is instantiated, I'd be grateful).

Isolate external php code in Laravel

I need to integrate Slider Revolution editor into my Laravel (5.5) app.
I've put the editor in public/revslider/ folder to be able to use the visual editor. I also created a helper class to "communicate" with it and be able to use it inside my Blade views:
namespace App\Helpers;
include( public_path('revslider/embed.php') );
class Slider{
/**
* This function is called where you want to put your slider
*/
public static function make($slider){
return \RevSliderEmbedder::putRevSlider( $slider );
}
/**
* This function is called inside <HEAD> tag to include all
* SR assets (js/css/font files)
*/
public static function head(){
return \RevSliderEmbedder::headIncludes(false);
}
}
The SR's PHP code does not use namespaces. In fact it is a strange mix of Code Igniter, Wordpress and vanilla php.
The problem is it is trying to declare a translation function __(...):
if( ! function_exists('__'))
{
function __($string = '')
{
....
}
}
and since there is already such Laravel's helper function, it does not redeclare it and tries to use Laravel's __() function. And that obviously causes errors.
I temporarily managed to fix this problem by changing the name of SR's __() function (and all references to it). But of course it is not a best way to solve this problem, since I will be unable to use SR's automatic updates or will be forced to do these changes after every update.
So my questions are:
Is there any good way of integrating such "bad" code into your project, invoking it safely without conflicts? Is there any way of isolating such code and avoid clashes? By "bad code" I mean code that does not follow strict OOP/PSR rules present in projects like Laravel.
What is the best way to include "external" PHP code? I've just used plain include() inside of my helper class' file, but is there a better/cleaner way? Like, I don't know, loading it through composer?

Accessing dynamic links in the format of domain.com/<dynamic_page_name> in CodeIgniter

I am using code Igniter for my PHP project. I want to give provision in my site such that users can create new pages of their own, and access them directly from domain.com/their_page_name.
But, my developers have raised a concern that, 1000's of dynamic links that are presented in the format of domain.com/ is "not good for site's performance". For some 10-15 pages, it is fine. But, beyond that, it would effect the site's performance.
So, they proposed that the URL format should be like www.domain.com/something/page_name (here, 'something' is the controller name, as they mentioned it)
But, I really can't sacrifice my framework nor my requirement.
Is there any way that I can achieve the format of "www.domain.com/page_name" without effecting the site's performance?
Thanks in advance.
No issues on
Www.domain.com\userpagename.
It's not a framework issues. Codeigniter support this type of URL.you can create n no of URL.
Performance will matter how you are handling that particular controller or that particular function.
If may be 10 may be 100 ,work around same way.
You just have to put route accordingly.
$route[default_controller]=userurl;
$route[userurl/(:any)]=userurl yourfunction/$1`;
What it seems you need is dynamic controller, which can be done using Codeigniter's build in function _remap().
A code example is:
public function _remap($method){
if($method != null){
$this->yourFunction($method);
} else {
// handle the error as you like
}
}
public function yourFunction($key){
// your code logic here
}
All this code block goes inside your controller.
Edit: the performance is exactlu the same as going with domain.com/controller/method. What it matters, as stated above, is how you handle the data.

prevent duplicate value using ajax in sugar crm

i have create module using module builder , now i am having a field called as book Name
now if i give same book name 2 time t is accepting .
i don't want to use and plug in for checking duplicate value because i want to learn the customization through code .
so i can call ajax and check in data base weather the same book name is exist in db or not but i don't know how controller works in sugar crm . and how to call ajax in sugar crm .
can any one guide me , your help is much appreciated .
If you really want to accomplish this using ajax then I'd recommend an entryPoint as the way to go. This customization will require a couple of simple things. First you'll write a little bit of javascript to perform the actual ajax call. That ajax call will post to the entryPoint you write. The entryPoint will run the query for you and return a response to you in the edit view. So lets get started by writing the entryPoint first.
First, open the file custom/include/MVC/Controller/entry_point_registry.php. If the folder structure and file do not exist yet, go ahead and create them.
Add the following code to the entry_point_registry.php file:
$entry_point_registry['test'] = array('file' => 'custom/test.php', 'auth' => true);
Some quick explanation about that line:
The index value of test can be changed to whatever you like. Perhaps 'unique_book_value' makes more sense in your case. You'll see how this value is used in a minute.
The file value in the array points to where you're gonna put your actual code. You should also give this a more meaningful name. It does NOT need to match the array key mentioned above.
The 'auth' => true part determines whether or not the browser needs to have an active logged in session with SugarCRM or not. In this case (and almost all) I'd suggest keeping this to true.
Now lets look at the code that will go in custom/test.php (or in your case unique_book_name.php):
/* disclaimer: we are not gonna get all crazy with using PDO and parameterized queries at this point,
but be aware that there is potential for sql injection here. The auth => true will help
mitigate that somewhat, but you're never supposed to trust any input, blah blah blah. */
global $db; // load the global sugarcrm database object for your query
$book_name = urldecode($_REQUEST['book_name']); // we are gonna start with $_REQUEST to make this easier to test, but consider changing to $_POST when confirmed working as expected
$book_id = urldecode($_REQUEST['book_id']); // need to make sure this still works as expected when editing an existing record
// the $db->quote is an alias for mysql_real_escape_string() It still does not protect you completely from sql injection, but is better than not using it...
$sql = "SELECT id FROM book_module_table_name WHERE deleted = 0 AND name = '".$db->quote($book_name)."' AND id <> '".$db->quote($book_id)."'";
$res = $db->query($sql);
if ($db->getRowCount($res) > 0) {
echo 'exists';
}
else {
echo 'unique';
}
A note about using direct database queries: There are api methods you can use to accomplish this. (hint: $bean->retrieve_by_string_fields() - check out this article if you wanna go that route: http://developer.sugarcrm.com/2012/03/23/howto-using-the-bean-instead-of-sql-all-the-time/) However, I find the api to be rather slow and ajax should be as fast as possible. If a client asked me to provide this functionality there's a 99% chance I'd use a direct db query. Might use PDO and parameterized query if I'm feeling fancy that day, but it's your call.
Using the above code you should be able to navigate to https://crm.yourdomain.com/index.php?entryPoint=test and run the code we just wrote.
However at this point all you're gonna get is a white screen. If you modify the url to include the entryPoint part and it loads your home page or does NOT go to a white screen there are 3 potential causes:
You put something different for $entry_point_registry['test']. If so change the url to read index.php?entryPoint=whatever_you_put_as_the_array_key
You have sugar in a folder or something on your domain so instead of crm.yourdomain.com it is located somewhere ugly and stupid like yourdomain.com/sugarcrm/ if this is the case just make sure that your are modifying the url such that the actual domain portion is preserved. Okay I'll spell it out for you... https://yourdomain.com/sugarcrm/index.php?entryPoint=test
This is more rare, but for some reason that I cannot figure out apache sometimes needs to be reloaded when adding a new entrypoint. If you have shell access a quick /etc/init.d/apache2 reload should do the trick. If you don't have shell access you may need to open a ticket with your hosting provider (or get a fricking vps where you have some control!!!, c'mon man!)
Still not working? Did you notice the "s" in https? Try http instead and buy a fricking $9 ssl cert, geez man!
Okay moving on. Let's test out the entryPoint a bit. Add a record to the book module. Let's add the book "War of Art" (no, not Art of War, although you should give that a read too).
Now in the url add this: index.php?entryPoint=test&book_name=Art%20of%20War
Oh gawd that url encoding is hideous right! Don't worry about it.
You should hopefully get an ugly white screen with the text "exists". If you do let's make sure it also works the other way. Add a 2 to the book name in the url and hopefully it will now say "unique".
Quick note: if you're using Sugar you're probably also using mysql which is case insensitive when searching on strings. If you really need case sensitivity check out this SO article:
How can I make SQL case sensitive string comparison on MySQL?
Okay so now we have our entryPoint working and we can move on to the fun part of making everything all ajaxical. There are a couple ways to go about this, but rather than going the most basic route I'm gonna show you what I've found to be the most reliable route.
You probably will need to create the following file: custom/modules/CUSTOM_BOOK_MODULE/views/view.edit.php (I hope by now I don't need to point out changing that path to use your module name...
Assuming this file did not exist and we are starting from scratch here is what it will need to look like:
if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
class CUSTOM_BOOK_MODULEViewEdit extends ViewEdit
{
public function display()
{
// make sure it works in the subpanel too
$this->useForSubpanel = true;
// make the name value available in the tpl file
$this->ss->assign('name_value', $this->bean->name);
// load the parsed contents of the tpl into this var
$name_input_code = $this->ss->fetch('custom/modules/CUSTOM_BOOK_MODULE/tpls/unique_book_checker.tpl.js');
// pass the parsed contents down into the editviewdefs
$this->ss->assign('custom_name_code', $name_input_code);
// definitely need to call the parent method
parent::display();
}
}
Things are looking good. Now we gotta write the code in this file: custom/modules/CUSTOM_BOOK_MODULE/tpls/unique_book_checker.tpl.js
First a couple of assumptions:
We're going to expect that this is Sugar 6.5+ and jquery is already available. If you're on an earlier version you'll need to manually include jquery.
We're going to put the event listener on the name field. If the book name value that you want to check is actually a different field name then simply adjust that in the javascript below.
Here is the code for custom/modules/CUSTOM_BOOK_MODULE/unique_book_checker.tpl.js:
<input type="text" name="name" id="name" maxlength="255" value="{$name_value}" />
<span id="book_unique_result"></span>
{literal}
<script type="text/javascript">
$(document).ready(function() {
$('#name').blur(function(){
$('#book_unique_result').html('<strong> checking name...</strong>');
$.post('index.php?entryPoint=test', {book_name: $('#name').val(), book_id: $('[name="record"]').val()}, function(data){
if (data == 'exists') {
removeFromValidate('EditView', 'name');
addToValidate('EditView', 'name', 'float', true, 'Book Name Must be Unique.');
$('#book_unique_result').html('<strong style="color:red;"> ✗</strong>');
}
else if (data == 'unique') {
removeFromValidate('EditView', 'name');
addToValidate('EditView', 'name', '', true, 'Name Required');
$('#book_unique_result').html('<strong style="color:green;"> ✓</strong>');
}
else {
// uh oh! maybe you have php display errors on?
}
});
});
});
</script>
{/literal}
Another Note: When the code detects that the name already exists we get a little hacky and use Sugar's built in validation stuff to prevent the record from saving. Basically, we are saying that if the name already exists then the name value MUST be a float. I figured this is pretty unlikely and will do the trick. However if you have a book named 3.14 or something like that and you try to create a duplicate this code will NOT prevent the save. It will tell you that a duplicate was found, but it will not prevent the save.
Phew! Okay last two steps and they are easy.
First, open the file: custom/modules/CUSTOM_BOOK_MODULE/metadata/editviewdefs.php.
Next, find the section that provides the metadata for the name field and add this customCode attribute so that it looks like this:
array (
'name' => 'name',
'customCode' => '{$custom_name_code}',
),
Finally, you'll need to do a quick repair and rebuild for the metadata changes to take effect. Go to Admin > Repair > Quick Repair & Rebuild.
Boom! You should be good to go!

Magento 1.7: Non-system product attribute not saving in PHP script

I have a script that has loaded a product using
$product = Mage::getModel("catalog/product")->loadByAttribute('sku', $data['id']);
If I modify or retrieve a system attribute, such as weight, everything works fine:
$product->getWeight(); // returns the correct weight
$product->setWeight(5.0); // correctly sets weight
I have two custom attributes: google_product_category and mpn. If I try to modify them using the same technique, like so:
$product->setMpn('123ABC');$product->setGoogleProductCategory('Electronics');the call does not throw an error but the data does not save (yes, I am calling $product->save() afterwards).
What am I doing wrong?
Can you post your scripts? Are you on developer mode?
It looks like the problem is because of store area
Take a look at Mage_Catalog_Model_Product -> setOrigData
public function setOrigData($key=null, $data=null)
{
if (Mage::app()->getStore()->isAdmin()) {
return parent::setOrigData($key, $data);
}
return $this;
}
When saving a product, it needs to compare the data with origdata.
It can retrieve origdata if it is run from 'admin' area. If your scripts run from 'default' area, it won't be able to get the origdata.
So that if you're on developer mode, in some point of code there gonna be a warning thrown that will broke your script (I may be wrong because you said no error was thrown)
You can do it by changing Mage::app('default') into Mage::app('admin')
or using Mage::app()->setCurrentStore('admin');

Resources