DRY Layouts For Grails Emails? - email

I'm trying to clean up some of the the email views in my grails project. My team uses the same introduction, logo, and sign off for every email. I tried to place these into the traditional grails layout file and call it with the meta tag <meta name="layout" content="email"/>, but it doesn't appear to work. Is there another way I can create one singular layout for these email templates?

I was trying to figure out what you mean by DRY, I think it must be some ruby term and I guess you mean templates.
The problem with HTML emails is actually a standard problem acrosss all languages as in when it comes to including logos (headers/footers). The standards vary and whilst it may work on some mail clients may not work on for example web mail i.e. gmail and so forth.
The trick is to use inline images I will give you some sample stuff to work from:
So here is a sample Controller - this is off my own code but from different parts. It is to give you an idea or should I say not very well explained specifically around multi inline images:
class Mycontroller() {
pivate final static String TEMPLATE='/emails/emailTemplate'
def emailService
def doEmail() {
def images=[]
//Where this list contains a map of photo Ids(could be macde up, the photo content type and actual photo file Names (to go and grab from uploaded folder)
images <<[id: "uImage${photo.id}", contentType: "${photo.contentType}", file: photo.file]
images <<[id: "uImage${photo1.id}", contentType: "${photo1.contentType}", file: photo1.file]
emailService.sendEmail(user.email, subject, TEMPLATE, [instance: bean, domainName:domainName, fqdn:fqdn ], images)
}
}
I have a list if images above it containts the photo id the content type and the actual file name (in text) that is sent through to emailService.sendEmail
private void sendEmail(email,mysubject,template,templateModel,List images) throws Exception {
List<String> recipients = []
try {
mailService.sendMail {
//this must be set true and at the top for inline images to work
multipart true
if (recipients) {
to recipients
}
else {
to email
}
if (config.admin.emailFrom) {
if (Environment.current == Environment.DEVELOPMENT && config.admin.emailFromDev ) {
from "${config.admin.emailFromDev}"
} else {
from "${config.admin.emailFrom}"
}
}
subject mysubject
//actual content must be html sent to fill in a grails template
html Holders.grailsApplication.mainContext.groovyPageRenderer.render(template: template, model: templateModel)
//Main Site logo
inline 'inlineImage', 'image/png', new File("/opt/site-stuff/myLogo.png")
//Additional images
if (images) {
images?.each { a ->
inline "${a.id}", "${a.contentType}", new File("${a.file}")
}
}
}
}
catch (e) {
//throw new Exception(e.message)
log.error "Problem sending email ${e.message}"
}
}
Now the bit that you thought would just work as in using grails templates in the way you are as in layouts is not what you want instead, as above you can see it is rendering a template but the template is a typical gsp template which instead is a full on html page:
/emails/emailTemplate
<%# page contentType="text/html;charset=UTF-8" %>
<!doctype html>
<html>
<head>
<style>
.site-icon img {
width: 300px;
margin-top:-50px;
}
.site-logo img {
min-width: 25em;
max-width: 45em;
}
.menu {
background: #CCC;
}
a {
text-decoration: none;
}
.menu a {
color : #FF0000;
text-decoration: none;
font-weight:bold;
}
.menu a:hover {
color : #00FFFF;
background: #ccc;
}
</style>
</head>
<body style=" background-color: blue; font-size: 1.0em;">
<table width="100%" border="0" cellpadding="0" cellspacing="0" bgcolor="#FFFFFF">
<tr style="background: red;"><td colspan="3" class="site-logo">
<img src="cid:inlineImage"/>
<h1 style="display:inline; margin-top:-2em;"><g:message code="domain.label"/></h1>
</td>
</tr>
<g:each in="${instance.results}" var="user" status="ii">
<tr><td>
<div class="image">
<g:if test="${user.profilePhoto}">
<img src="cid:uImage${user.id}"/>
</g:if>
</div>
</td></tr>
</g:each>
</table>
You see the main problem with html emails is that CSS styles don't work very well, it works in some cases but in a lot of cases you are better off sticking to traditional tables and using style tags to properly declare your layout.
Forget about using your actual site CSS files since so far as this process goes, this is a direct email being generated and sent. It has no awareness of your site CSS files.
In regards to multiple images we are talking about
here is a list of users
usera {usera Photo} / userA Description
userb {userb Photo} / userB Description
The above solution will have all you need to add all the inline images you need to do this. This means you are attaching the images within the email so if the images are huge then you are also attaching them so the trick is to reformat the images / resize them.
For your own site logos you can do that directly and have a separate file/folder that contains actual size to be emailed but for on the fly re-sizing you could try something like this:
static Map getPhoto(Photos photo, int width=100,int height=100) {
File f
def contentType
if (photo.status==Photos.ACTIVE) {
def id = photo.id
def imageSHa = photo.imageSHa
contentType = photo.contentType
def fileExtension = photo.fileExtension
//remove . from fileExtension
def noDotExtension = fileExtension.substring(1)
def user = photo.user
f = new File(ROOT_PATH + '/' + user.username + '/' + imageSHa);
if (f.exists() && !f.isDirectory()) {
f = new File(ROOT_PATH + '/' + user.username + '/' + imageSHa+'_email');
if (!f.exists()) {
def imageStream = new FileInputStream(ROOT_PATH + '/' + user.username + '/' + imageSHa)
def image = FileCopyUtils.copyToByteArray(imageStream).encodeBase64().toString()
def caption = photo.caption
def position = photo.position
// String caption=photo.caption
//Lets present the image as a thumbNail
imageStream = new FileInputStream(ROOT_PATH + '/' + user.username + '/' + imageSHa)
def imageBuffer = ImageIO.read(imageStream)
def scaledImg = Scalr.resize(imageBuffer, Scalr.Method.QUALITY, width, height, Scalr.OP_ANTIALIAS)
ByteArrayOutputStream os = new ByteArrayOutputStream();
ImageIO.write(scaledImg, noDotExtension, os)
InputStream is = new ByteArrayInputStream(os.toByteArray())
def scaledImage = FileCopyUtils.copyToByteArray(is).encodeBase64().toString()
//imageSHa = DigestUtils.shaHex(scaledImg)
byte[] data = Base64.decodeBase64(scaledImage)
OutputStream stream = new FileOutputStream(ROOT_PATH + '/' + user.username + '/' + imageSHa+'_email')
stream.write(data)
f = new File(ROOT_PATH + '/' + user.username + '/' + imageSHa+'_email');
}
return [file:f, contentType:'img/'+fileExtension.substring(1)]
}
}
return [:]
}
This now maps up to when I was doing the images mapping above:
def res = PhotosBean.getPhoto(ui.attributes.profilePhoto)
if (res) {
images << [id: "uImage${ui.id}", contentType: "${res.contentType}", file: res.file]
}
Hope this clears up a lot of headache I had to go through to achieve html emails with as many images as required and all resized to what I want

Related

How to Form Tables Correctly When HTML Template Components are Separated?

When building tables in Lit, sometimes I need to generate different parts of a table in different parts of the code. But when I put everything together, the table does not look the same as if it were declared all in one place.
See this playground for an example of the same table that's assembled two different ways. Is there any way I can make the "two halves" table look the same as the first, while still creating the elements in separate html`` blocks?
I tried creating a table in two different ways (see playground), I expected it to be the same resulting table in both instances. What actually happened is that they looked different, and I want to know why/how to correct this.
Lit does not do string concatenation and each html tag function results in a cached Template element which can be efficiently rendered. This means that each of your html tags get implicitly closed by the browser's HTML parser.
E.g.: html`<table>...` is parsed by the browser into: html`<table>...</table>`. From lit.dev documentation on "Well-formed HTML": https://lit.dev/docs/templates/expressions/#well-formed-html
Lit templates must be well-formed HTML. The templates are parsed by the browser's built-in HTML parser before any values are interpolated. Follow these rules for well-formed templates:
Templates should not contain unclosed elements—they will be closed by the HTML parser.
Therefore instead of the following structure:
// Does not work correctly - do not copy this. For demonstration purposes.
const start = html`<table>`;
const content = html`content`;
const end = html`</table>`;
const result = [start, content, end];
return html`${result}`;
// This would render as: <table></table>content<table></table>
Consider the following structure instead where each html tag function is well formed HTML:
const tableFrame = (content) => html`<table>${content}</table>`;
const content = html`content`;
// Pass the content template result into the table frame.
return tableFrame(content);
The following is your code from the playground example restructured in this way:
<script type="module">
import {LitElement, html, css} from "https://cdn.jsdelivr.net/gh/lit/dist#2/core/lit-core.min.js";
class TableExample extends LitElement {
static styles = css`table, th, td { border: 1px solid black; }`;
generateTables() {
const tables = [];
const tableRow1 = html`
<tr>
<td rowspan=2>0</td>
<td>1</td>
</tr>`;
const tableRow2 = html`
</tr>
<td>2</td>
</tr>`;
const table1 = html`
<table>
<tr>
<td rowspan=2>0</td>
<td>1</td>
</tr>
</tr>
<td>2</td>
</tr>
</table>
`;
const tableFrame = (content) => html`<table>${content}</table>`;
// Full table
tables.push(table1);
tables.push(html`<br />`);
// Use tableFrame with custom content.
tables.push(tableFrame(html`<tr><td>Custom Content</td></tr>`));
tables.push(html`<br />`);
// Use tableFrame with the two rows defined earlier.
tables.push(tableFrame([tableRow1, tableRow2]));
return tables;
}
render() {
return this.generateTables();
}
}
customElements.define('table-example', TableExample)
</script>
<table-example></table-example>
As an additional reference the documentation on Templates: https://lit.dev/docs/templates/expressions/#templates

How do I accept file uploads from pasting a file into the browser?

Accepting image uploads from a paste into the browser window is much easier than traditional file upload form inputs and even the newer style drag 'n' drop file uploads.
How do I implement it?
Here's an example PHP/JavaScript page that accepts drag 'n' drop image uploads. It's not dependent on PHP though - you could adapt it quite easily to work with another server-based language. This code was based on a snippet I found on jsFiddle by someone called Nick.
This is a full page - so you should be able to copy the code below and put it in a file on your web-server as-is (if you're not running PHP then you'll need to update the PHP code at the top or point the form to your own form handler script).
<?php
if (!empty($_POST)) {
// Handle the POSTed data here - the image is actually base64 encoded data in
// the $_POST['myTextarea'] variable which you can run through the base64_decode()
// function and then write to a file
$pos = strpos($_POST['myTextarea'], 'base64,');
$encoded = substr($_POST['myTextarea'], $pos + 7);
$raw = base64_decode($encoded);
// Show the base64 encoded $data - use the $raw variable when writing to a file
var_dump($_POST);
exit;
}
?>
<!DOCTYPE html >
<html>
<body>
<h1>File upload using paste</h1>
<p>
You can paste an image, which is on your clipboard, into this window and it will appear below.
If you use Windows you can press <b>PrtScr</b> to get a screenshot on your clipboard. Then
press <b>CTRL+V</b> to paste it into this document.
</p>
<!-- PUT THE ADDRESS OF YOUR FORM HANDLER SCRIPT IN THE ACTION ATTRIBUTE -->
<form action="" method="post">
<div id="form-elements-container">
<input type="text" value="An example text input..." name="myTextInput"><br />
<input type="submit" value="Submit form"><br />
</div>
</form>
<!-- THIS IS WHERE THE IMAGE THUMBNAILS WILL APPEAR -->
<div id="images-container"></div>
<script>
counter = 0;
document.body.onpaste = function (e)
{
// use event.originalEvent.clipboard for newer chrome versions
var items = (e.clipboardData || e.originalEvent.clipboardData).items;
// Find pasted image among pasted items
var blob = null;
for (var i=0; i<items.length; i++) {
if (items[i].type.indexOf("image") === 0) {
blob = items[i].getAsFile();
}
}
// Load image if there is a pasted image
if (blob !== null) {
var reader = new FileReader();
reader.onload = function(e)
{
// Create a new image object from the pasted data
var img = new Image();
img.src = e.target.result;
img.width = 128;
img.height = 128;
img.style.margin = '5px';
// Append the file to the document
document.getElementById('images-container').appendChild(img);
// Add a new textarea to the form
var textarea = document.createElement('textarea');
textarea.name = 'myTextarea_' + counter++;
textarea.value = img.src;
textarea.style.width = '200px';
textarea.style.height = '200px';
document.getElementById('form-elements-container').appendChild(textarea);
};
reader.readAsDataURL(blob);
}
}
</script>
</body>
</html>

Convert Excel form window to Google Sheets window

As you've probably found, there appears to be no equivalent way to add the following Excel form and associated VBA code to Google Sheets or Scripts or Forms:
Is there some add-in that can be used to pop up this image and its controls? This has to be used many times in an accounting sheet to categorize expenditures at tax time.
It may not look exactly the same but I was able to construct a custom dialog in a short period of time to show how HTML service can be used to produce similar results.
First I construct an HTML template that contains the 2 combo boxes with multiple lines.
HTML_Test.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<?!= include('CSS_Test'); ?>
</head>
<body>
<div id="left">
<label for="expenseCategory">Expense Category</label><br>
<select id="expenseCategory" size="10">
</select>
</div>
<div id="middle">
<label for="expenseSubCategory">Expense Sub Category</label><br>
<select id="expenseSubCategory" size="10">
</select>
</div>
<?!= include('JS_Test'); ?>
</body>
</html>
Then a CSS file to contain all my element formatting.
CSS_Test.html
<style>
#expenseCategory {
width: 90%;
}
#expenseSubCategory {
width: 90%;
}
#left {
width: 25%;
float: left;
}
#middle {
width: 50%;
float: left;
}
</style>
And a javascript file for client side javascript. I've simply hard coded some data to show how the select elements are filled in but this could just as easily be done using template scripting, or google.script.run
<script>
var expenses = [["A","1","2","3"],
["B","4","5"],
["C","6","7","8","9","10"]
];
function expenseCategoryOnClick() {
try {
let expenseCategory = document.getElementById('expenseSubCategory');
expenseCategory.options.length = 0;
expenses[this.selectedIndex].forEach( (expense,index) => {
if( index > 0 ) {
let option = document.createElement("option");
let text = document.createTextNode(expense);
option.appendChild(text);
expenseCategory.appendChild(option);
}
}
);
}
catch(err) {
alert("Error in expenseCategoryOnClick: "+err)
}
}
(function () {
// load first expense
let expenseCategory = document.getElementById('expenseCategory');
expenseCategory.addEventListener("click",expenseCategoryOnClick);
expenses.forEach( expense => {
let option = document.createElement("option");
let text = document.createTextNode(expense[0]);
option.appendChild(text);
expenseCategory.appendChild(option);
}
);
expenseCategory = document.getElementById('expenseSubCategory');
expenses[0].forEach( (expense,index) => {
if( index > 0 ) {
let option = document.createElement("option");
let text = document.createTextNode(expense);
option.appendChild(text);
expenseCategory.appendChild(option);
}
}
);
}
)();
</script>
Then there is the server side code bound to a spreadsheet.
Code.gs
function onOpen(e) {
var menu = SpreadsheetApp.getUi().createMenu("Test");
menu.addItem("Show Test","showTest");
menu.addToUi();
}
// include(filename) required to include html files in the template
function include(filename) {
return HtmlService.createHtmlOutputFromFile(filename)
.getContent();
}
function showTest() {
var html = HtmlService.createTemplateFromFile("HTML_Test");
html = html.evaluate();
html.setWidth(800);
SpreadsheetApp.getUi().showModalDialog(html,"Test");
}
The dialog looks like this. Many more html elements can be added as needed. This just shows the basics. This may be more difficult than an wysiwig html editor but I find I have better control of the appearance and function of my pages this way. Notice I clicked "C" and the sub category is filled in automatically.

how to resize image and reorient it if it rotates when uploaded through IOS devices using image intervention package?

I have developed a Laravel web application with the help of some tutorial videos and codes given by other developers on stackoverflow .The app is working pretty good except for the image upload feature. I am facing an issue related to the uploaded image being cut either on the sides or on the bottom as well as the image when uploaded through any IOS device then the image under goes rotation. I have installed image intervention but i don't know where to put the code inside my files i am sharing my controller code as well as the image displaying code here
controller code
namespace App\Http\Controllers;
use Auth;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\MessageBag;
use App\comment;
use App\User;
use App\post;
use View;
use Lang;
use image;
class usersController extends Controller
{
private $u;
public function __construct(){
$this->u = new User();
}
public function search(Request $request){
$search_input = $request["input"];
$path = $request["path"];
$users = User::where("name","like","%$search_input%")->orWhere("username","like","%$search_input%")->get();
if ($users->isEmpty()) {
return "<p style='text-align: center;width: 100%;color: gray;font-size: 12px;margin: 3px'>".Lang::get('trans.nothingToShow')."</p>";
}else{
foreach ($users as $user) {
if ($user->verify == 1) {
$ifVerify = '<span class="verify-badge" style="width: 420px; height: 700px; background: url(\''.$path.'/imgs/verify.png\') no-repeat; background-size: cover; margin: 0px 2px;"></span>';
}else{
$ifVerify = '';
}
if($user->avatar == "avatar.png"){
$avatar_path = '/imgs/avatar.png';
}else{
$avatar_path = '/storage/avatar/'.$user->avatar;
}
echo '<div class="navbar-search-item">
<a href="'.$path.'/'.$user->username.'">
<div>
<div style="background-image:url(\''.$path.$avatar_path.'\');">
</div>
<p>
'.$user->name.$ifVerify.'<br>
<span>#'.$user->username.'</span>
</p>
</div>
</a>
</div>';
}
}
}
public function profile($username){
$user_info = User::where("username",$username)->get();
foreach ($user_info as $uinfo) {
$user_id = $uinfo->uid;
}
if (isset($user_id)) {
$feedbacks = post::where("to_id",$user_id)->where("privacy",1)->orderBy('time', 'desc')->get();
$feedbacks_count = post::where("to_id",$user_id)->get()->count();
$new_count = post::where("to_id",$user_id)->where('read',0)->get()->count();
// check comments on post (count)
$commentsCount = array();
foreach ($feedbacks as $fb) {
$pid = $fb->pid;
$countComments = comment::where("c_pid",$pid)->get()->count();
array_push($commentsCount,$countComments);
}
return view("pages.profile")->with(["user_info" => $user_info,"feedbacks" => $feedbacks,'feedbacks_count' => $feedbacks_count,'new_count' => $new_count,'commentsCount' => $commentsCount]);
}else{
return view("pages.profile")->with(["user_info" => $user_info]);
}
}
public function settings($username){
$user_info = User::where("username",$username)->get();
if (Auth::user()->username == $username) {
return view("pages.settings")->with("user_info",$user_info);
}else{
return redirect()->back();
}
}
public function s_general(Request $request){
$this->validate($request,[
'avatar' => 'nullable|image|mimes:jpeg,png,jpg|max:3072',
'fullname' => 'required',
'email' => 'required|email'
]);
if ($request['fullname'] == Auth::user()->name && $request['email'] == Auth::user()->email && !$request->hasFile('avatar')) {
return redirect()->back()->with('general_msg', Lang::get('trans.noChanges_MSG'));
}else{
$avatar = $request->file('avatar');
if ($request->hasFile('avatar')) {
$avatar_ext = $avatar->getClientOriginalExtension();
$avatar_name = rand(9,999999999)+time().".".$avatar_ext;
$avatar_new = $avatar->storeAs("avatar",$avatar_name);
}else{
$avatar_name = Auth::user()->avatar;
}
$update_general = User::where('uid',Auth::user()->uid)->update(['name' => $request['fullname'],'email' => $request['email'],'avatar' => $avatar_name]);
return redirect()->back()->with('general_msg', Lang::get('trans.changes_saved'));
}
}
and this is how i display the image
<div class="profile-avatar" style="width: 300px;height:400px; border-radius: 0%;background-image: url('#if(Auth::user()->avatar == "avatar.png") {{ url("/imgs/".Auth::user()->avatar) }} #else {{ url("/storage/avatar/".Auth::user()->avatar) }} #endif');">
please help me with code should i put and where i should put that in order to resolve this issue
this code is for preview of the image
<div style="display: inline-flex;">
<div class="profile-avatar" id="settings_img_elm" style="margin: 10px; width:350px;margin-top: 0; margin-bottom: 0;border-color: #fff; text-align: center;background-image: url('#if(Auth::user()->avatar == "avatar.png") {{ url("/imgs/".Auth::user()->avatar) }} #else {{ url("/storage/avatar/".Auth::user()->avatar) }} #endif');">
</div>
<p style="color: #a7aab5; font-size: 9px;padding: 25px 10px 25px 10px; margin: 0;">#lang("trans.preview")<br>#lang("trans.maxSize")<br>upload vertical <br>images.<br>Save the<br>image first<br> and then<br> check the<br> preview</p>
</div>
<p style="border-bottom: 1px solid #dfe2e6;margin: 0; margin-top: 12px; margin-bottom: 12px;">
<input type="file" name="avatar" style="display: none;" id="settings_img">
<label for="settings_img" class="btn btn-success">#lang("trans.selectImage")</label>
the javascript for the preview image is
function imagePreview(input,elm) {
if (input.files && input.files[0]) {
var reader = new FileReader();
reader.onload = function (e) {
$(elm).css("background-image","url('"+e.target.result+"')");
}
reader.readAsDataURL(input.files[0]);
}
}
$("#settings_img").on("change",function(){
imagePreview(this,"#settings_img_elm");
});
$("#feedback_hFile").on("change",function(){
$(".send_feedback_image").show();
imagePreview(this,"#sfb_image_preview");
});
The code below should get you started. The two key things you are looking for here are Interventions orientate and resize methods which should take care of the two issues you mention. Orientation will rotate the image based on EXIF data, and resize can resize your image to whatever specifications you need.
Import the facade
use Intervention\Image\Facades\Image;
Suggestions
Remove use image; from your imports as it is probably causing or going to cause you issues. It is invalid.
s_general method adjustments
public function s_general(Request $request){
$this->validate($request,[
'avatar' => 'nullable|image|mimes:jpeg,png,jpg|max:3072',
'fullname' => 'required',
'email' => 'required|email'
]);
if ($request['fullname'] === Auth::user()->name && $request['email'] === Auth::user()->email && !$request->hasFile('avatar')) {
return redirect()->back()->with('general_msg', Lang::get('trans.noChanges_MSG'));
}
if ($request->hasFile('avatar')) {
// Grab the original image from the request
$originalImage = $request->file('avatar');
// Grab the original image extension
$originalExtension = $originalImage->getClientOriginalExtension();
// Instantiate a new Intervention Image using our original image,
// read the EXIF 'Orientation' data of the image and rotate the image if needed
$newImage = Image::make($originalImage)->orientate();
// Resize the new image to a width of 300 and a height of null (auto)
// we also use a callback to constrain the aspect ratio so we don't distort the image on resize
$newImage->resize(300, null, function ($constraint) {
$constraint->aspectRatio();
});
// Generate a randomized filename and append the original image extension
$filename = random_int(9, 999999999) + time() . '.' . $originalExtension;
// Save the new image to disk
$newImage->save(storage_path('app/public/avatar/' . $filename));
} else {
$filename = Auth::user()->avatar;
}
User::where('uid', Auth::user()->uid)->update(
[
'name' => $request['fullname'],
'email' => $request['email'],
'avatar' => $filename
]);
return redirect()->back()->with('general_msg', Lang::get('trans.changes_saved'));
}
More suggestions
I know this isn't a code review, but
Use PascalCase for your class names. I see you have a few imports such as App\comment and App\post
Your constructor doesn't seem to be needed. I'd ditch it. If you are keeping it, i would get use to more descriptive variable names. Short names like $u are generally considered bad practice.
You have a few unused imports, Validator, Hash and MessageBag could be removed to clean this up.
Your controller is doing a lot of stuff that most would consider bad practice. Fumbling around with html for example. 9.9 times out of 10 you should probably be leveraging blade for these things as it's one of its main purposes.
Stick to one naming convention or another. You are using a mixture of camelCase and snake_case for your variables. I prefer camelCase but whichever you choose it's best to stick with it and not mix them.
Sorry, i know this isn't suppose to be a code review, i just thought that a few little suggestions might help you in the future.
You can check Intervation docs on how to resize image, I've also created an example for you for s_general function:
$avatar = $request->file('avatar');
if ($request->hasFile('avatar')) {
//pass uploaded avatar to Intervention Image instance
$image = \Image::make($avatar);
//resize image to 300 width, keep height automated adjusted, or any other width you need
$image->resize(300, null, function ($constraint) {
$constraint->aspectRatio();
});
//Or you can set specific width and height
//$image->resize(300, 200);
$avatar_ext = $avatar->getClientOriginalExtension();
$avatar_name = rand(9,999999999) + time().".".$avatar_ext;
//I used public path to store the image you can change it based on your needs
$image->save(public_path('/avatar/'.$avatar_name));
}else{
$avatar_name = Auth::user()->avatar;
}
use image intervention for resizing and for correcting the orientation of the image uploaded from a mobile phone. IF you are showing the preview of this image using simple javascript something like
reader.onload = function (e) {
$(elm).css("background-image","url('"+e.target.result+"')");
then there is a chance that the preview of the image won't be oriented as we want but once you save the image the orientation will be proper.
this problem of orientation is encountered mostly on iphone.
The code for orientation correction and resizing is
$newImage = Image::make($originalImage)->orientate();
$newImage->resize(300, null, function ($constraint) {
$constraint->aspectRatio();
});
///or for resizing use
$newImage->resize(width,height);

Tinymce Editor Plugin adding p tags

I have custom editor toolbar plugin which inserts html tags. The tag opens with div. Every time when page is saved or published the editor adds a new p tag above it.
here is the image
IMG tag drifts after publish I see a new p tag is inserted.
The Img being replaced by an html before saving
<p>
<div class="ssimage_code">
<img src="src_path0" alt=""/ >
<img src="src_path1" alt="" / >
</div>
</p>
I want to see if it gets solved by replacing a p tag instead of a img tag.
How do I access the parent tag from Node?
getParent does not work
Code
ed.serializer.addNodeFilter('img', function(nodes, name, args) {
var i = nodes.length, node;
while (i--) {
node = nodes[i];
if ((node.attr('class') || '').indexOf('slider_toimg') !== -1) {
self.imgToslidercode(node, args);
}
}
});
imgToslidercode:function(node,args){
insertcode = '';
node.replace(insertcode);
}
What I am looking here is?
node.getParent().replace(insertcode);