Return Image from Controller symfony2 - symfony

I would like to know how can i return an image from the controller without any template.
I would like to use it for pixel tracking in a newsletter.
I start with this code
$image = "1px.png";
$file = readfile("/path/to/my/image/1px.png");
$headers = array(
'Content-Type' => 'image/png',
'Content-Disposition' => 'inline; filename="'.$file.'"');
return new Response($image, 200, $headers);
But on the navigator i have a broken link (file not found...)

According to the Symfony Docs when serving files you could use a BinaryFileResponse:
use Symfony\Component\HttpFoundation\BinaryFileResponse;
$file = 'path/to/file.txt';
$response = new BinaryFileResponse($file);
// you can modify headers here, before returning
return $response;
This BinaryFileResponse automatically handles some HTTP request headers and spares you from using readfile() or other file functions.

Right now you return the filename as response body and write the file-content to the filename property of Content-Disposition.
return new Response($image, 200, $headers);
should be:
return new Response($file, 200, $headers);
... and ...
'Content-Disposition' => 'inline; filename="'.$file.'"');
should be ...
'Content-Disposition' => 'inline; filename="'.$image.'"');
right?
Further take a look at this question.

This works fine for me.
$filepath = "/path/to/my/image/chart.png";
$filename = "chart.png";
$response = new Response();
$disposition = $response->headers->makeDisposition(ResponseHeaderBag::DISPOSITION_INLINE, $filename);
$response->headers->set('Content-Disposition', $disposition);
$response->headers->set('Content-Type', 'image/png');
$response->setContent(file_get_contents($filepath));
return $response;

file_get_contents is a bad idea. Reading a large list of images via file_get_contents killed my little server. I had to find another solution and this works now perfect and very fast for me.
The key is to use readfile($sFileName) instead of file_get_contents. The Symfony Stream Response is able to take a callback function which will be executed while sending ($oResponse->send()). So this is a good place to use readfile().
As a little benefit I wrote down a way of caching.
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\StreamedResponse;
class ImageController
{
public function indexAction(Request $oRequest, Response $oResponse)
{
// Get the filename from the request
// e.g. $oRequest->get("imagename")
$sFileName = "/images_directory/demoimage.jpg";
if( ! is_file($sFileName)){
$oResponse->setStatusCode(404);
return $oResponse;
}
// Caching...
$sLastModified = filemtime($sFileName);
$sEtag = md5_file($sFileName);
$sFileSize = filesize($sFileName);
$aInfo = getimagesize($sFileName);
if(in_array($sEtag, $oRequest->getETags()) || $oRequest->headers->get('If-Modified-Since') === gmdate("D, d M Y H:i:s", $sLastModified)." GMT" ){
$oResponse->headers->set("Content-Type", $aInfo['mime']);
$oResponse->headers->set("Last-Modified", gmdate("D, d M Y H:i:s", $sLastModified)." GMT");
$oResponse->setETag($sEtag);
$oResponse->setPublic();
$oResponse->setStatusCode(304);
return $oResponse;
}
$oStreamResponse = new StreamedResponse();
$oStreamResponse->headers->set("Content-Type", $aInfo['mime']);
$oStreamResponse->headers->set("Content-Length", $sFileSize);
$oStreamResponse->headers->set("ETag", $sEtag);
$oStreamResponse->headers->set("Last-Modified", gmdate("D, d M Y H:i:s", $sLastModified)." GMT");
$oStreamResponse->setCallback(function() use ($sFileName) {
readfile($sFileName);
});
return $oStreamResponse;
}
}

Quick anwser
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
class AnotherController extends Controller {
public function imagesAction($img = 'my_pixel_tracking.png'){
$filepath = '/path/to/images/'.$img;
if(file_exists($filepath)){
$response = new Response();
$disposition = $response->headers->makeDisposition(ResponseHeaderBag::DISPOSITION_INLINE, $img);
$response->headers->set('Content-Disposition', $disposition);
$response->headers->set('Content-Type', 'image/png');
$response->setContent(file_get_contents($filepath));
return $response;
}
else{
return $this->redirect($this->generateUrl('my_url_to_site_index'));
}
}
}

Also, I had to change:
$file = readfile("/path/to/my/image/1px.png");
to this:
$file = file_get_contents("/path/to/my/image/1px.png");
Seemed like readfile was echoing contents as it read it forcing headers to output early and negating the forced Content-Type header.

Related

Support PHP 8's attributes for my routes in custom Symfony framework

I've followed the Symfony Tutorial Series to create my own Symfony framework. Currently I have the following code to define my routes and add them to the UrlMatcher:
$request = Request::createFromGlobals();
$routes = include __DIR__.'/../src/app.php';
$context = new Routing\RequestContext();
$matcher = new Routing\Matcher\UrlMatcher($routes, $context);
Here's my app.php file for reference:
<?php
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing;
$routes = new Routing\RouteCollection();
$routes->add('leap_year', new Routing\Route('/is_leap_year/{year}', [
'year' => null,
'_controller' => 'App\Controller\LeapYearController::index',
]));
return $routes;
What's the simplest way this can be modified to support PHP 8's attributes against the LeapYearController's actions for my routes instead of defining them in the app.php file?
It's been a while since I last looked at Symfony and a lot has changed in the framework aswell as in PHP itself and so far everything I have found is no longer supported.
I've managed to get the following working:
$loader = new AnnotationDirectoryLoader(
new FileLocator(),
new class() extends AnnotationClassLoader {
protected function configureRoute(Route $route, \ReflectionClass $class, \ReflectionMethod $method, object $annot) {
$route->setDefault('_controller', $class->getName() . '::' . $method->getName());
}
}
);
$routes = $loader->load(__DIR__ . '/../src/App/Controller');
Alternatively this works using the Psr4DirectoryLoader introduced in version 6.2:
$loader = new DelegatingLoader(
new LoaderResolver([
new Psr4DirectoryLoader(
new FileLocator()
),
new class() extends AnnotationClassLoader {
protected function configureRoute(Route $route, \ReflectionClass $class, \ReflectionMethod $method, object $annot) {
$route->setDefault('_controller', $class->getName() . '::' . $method->getName());
}
}
])
);
$routes = $loader->load(['path' => __DIR__ . '/../src/App/Controller', 'namespace' => 'App\Controller'], 'attribute');

Laravel change profile api gives null value

I am making change profile picture api in laravel .I want to update profile image in users table but not inserting or updating my images .Belew are my code please help me how to update user table .
fileUploadController.php
<?php
namespace App\Http\Controllers\API;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\User;
use App\Detail;
use App\Profile;
use Illuminate\Support\Facades\DB;
use Session;
use Illuminate\Support\Facades\Input;
use Illuminate\Support\Facades\Auth;
class FileUploadController extends Controller
{
public function changeProfile(Request $request,$id){
$this->validate($request, [
'image' => 'required|image|mimes:jpeg,png,jpg,gif,svg|max:2048',
]);
$updateuser = User::find($id);
if($file = $request->hasFile('image')) {
$file = $request->file('image');
$fileName = $file->getClientOriginalName() ;
$destinationPath = public_path().'/files/' ;
$file->move($destinationPath,$fileName);
$updateuser->image = '/files/'.$fileName;
}
$updateuser->save();
return $updateuser;
}
}
public function changeProfile(Request $request,$id){
$this->validate($request, [
'image' => 'required|image|mimes:jpeg,png,jpg,gif,svg|max:2048',
]);
$updateuser = User::find($id);
if($request->hasFile('image'))
{
$filewithext = $request->file('image')->getClientOriginalName();
$ext = $request->file('image')->getClientOriginalExtension();
$fileToStrore = $filewithext;
$path = $request->file('image')->storeAs('public/files',$fileToStrore);
$updateuser->image = $fileToStrore;
}
$updateuser->save();
return $updateuser;
}
This code works for me. I hope it will work for you too...
Good Luck..
This code is working for me.....
fileUploadController.php
public function changeProfile(Request $request,$id){
$updateuser = User::find($id);
if ($request->hasFile('image')) {
$images = $request->file('image');
$destinationPath = public_path('files');
$imageName = time().'.'.$images->getClientOriginalExtension();
$images->move($destinationPath, $imageName);
$updateuser->image= $imageName;
}else{
$updateuser->image= '';
}
$updateuser->update();
return ['message' => 'Image Uploaded Successfully'];
}

Return captcha in controller in Symfony 3.4

I have an action on a controller in symfony 3.4 that returns an image created with text, the problem is that when I see the image in the browser you see a small white square with nothing, I do not receive any type of error.
The code :
<?php
namespace Programas\Bundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;
class CaptchaController extends Controller
{
public function indexAction()
{
$response = new Response();
$response->headers->set('Content-Type', 'image/png');
#
$word = "";
$image = imagecreatetruecolor(500, 500);
$background_color = imagecolorallocate($image, 255, 255, 255);
imagefilledrectangle($image,0,0,200,50,$background_color);
$line_color = imagecolorallocate($image, 64,64,64);
for($i=0;$i<10;$i++) {
imageline($image,0,rand()%50,200,rand()%50,$line_color);
}
$pixel_color = imagecolorallocate($image, 0,0,255);
for($i=0;$i<1000;$i++) {
imagesetpixel($image,rand()%200,rand()%50,$pixel_color);
}
$letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
$len = strlen($letters);
$letter = $letters[rand(0, $len-1)];
$text_color = imagecolorallocate($image, 0,0,0);
for ($i = 0; $i< 6;$i++) {
$letter = $letters[rand(0, $len-1)];
imagestring($image, 5, 5+($i*30), 20, $letter, $text_color);
$word.=$letter;
}
ImagePng($image);
#
ob_start();
imagepng($image);
$imagevariable = ob_get_contents();
ob_end_clean();
ImageDestroy($image);
$response->setContent($imagevariable);
return $response;
}
}
How can I show the display of the image correctly?
It's much easier and better to use the captcha Bundle and use the "captcha" in your form type.
https://github.com/Gregwar/CaptchaBundle
Then you can use it like this $builder->add('captcha', CaptchaType::class);
I'm sure that there are some other good bundles but what you try is not the best way. Normally you have to extend your Form Builder and render a captcha field. You can take a look at the bundle how they solve it.

GuzzleHttp Client class not found in symfony2

I have loaded in GuzzleHttp from
http://docs.guzzlephp.org/en/5.3/quickstart.html
and have the
use GuzzleHttp\Client;
When i call this action...
public function googlevolumeAction(Request $request)
{
$data = $request->request->all();
$searchStr = $data['search'];
$client = new Client();
$req = $client->request('GET', 'https://www.googleapis.com/books/v1/volumes?q=intitle:' .$searchStr, []);
$decode = json_decode($req->getBody());
$total = $decode->totalItems;
$search = null;
if ($total != 0) {
$search = $decode->items;
}
return $this->render('BloggerBlogBundle:Page:googlevolume.html.twig',
['items' => $search]);
}
I get this error...
Attempted to load class "Client" from namespace "GuzzleHttp".
Did you forget a "use" statement for e.g. "Guzzle\Http\Client",
"Guzzle\Service\Client", "Symfony\Component\BrowserKit\Client",
"Symfony\Component\HttpKernel\Client" or
"Symfony\Bundle\FrameworkBundle\Client"?
Any ideas why?
thanks
Looks like you have a different version of guzzle installed than the docs you are looking at. From the error message you got it seems that if you change your use statement to:
use Guzzle\Http\Client;
It should work.

swiftmail symfony duplicate check for error log / email before sending

I would like to run a duplicate content check just before firing off an email whcih uses swiftmailer inside my symph2 app to send me dev ERROR log entries.
this functionality sits right next to my error log to database function, where it too has a duplicate check, although that one is much easier, it uses sql.
for this one, i want to maintain the last mail sent body for atleast the next 10 emails sent, so that if my error log goes out of control, it wont keep firing me duplicate emails of the same error.
should i just collect this body onto an object that holds last 10 email bodies, and attach this to the swift mailer class? or is there an easier way, like using something that is already embedded in swift mailer for this kind of post sending use? Or maybe a session..
Edit, i call swift mailer from a backend helper class, so think i can pretty much do anything there so long as its atleast semi-elegant.
EDIT this is a refined version of the method that calls both the persist and firing of email
<?php
class someWierdClass
{
public function addLogAction(Request $request, $persist = TRUE, $addEmail = TRUE)
{
$responseAdd = array();
if ($this->getRequest()->request->all() !== null) {
$data = $this->getRequest()->request->get('data') ? $this->getRequest()->request->get('data') : 'no_data';
$duplicate = $this->getRequest()->request->get('duplicate', null);
}
if ($addEmail) {
$responseAdd[] = 'firedIt';
$this->fireEmailString('You have an error log here. <br>' . $data);
}
if ($persist)
{
$responseAdd[] = 'persistedIt';
$this->persistLog($data, $duplicate);
}
if ($responseAdd)
{
$body = implode(', ', $responseAdd);
return new Response($body);
}
}
}
Log emails in a table and check that there it isn't a duplicate every time you send an email.
To do this, you should create a helper function that queries the emails table for entries who's body matches the body you would like to send. If the query returns nothing, then you know that isn't a duplicate. You would then send the email and log it the database. Otherwise, if it returned (a) record(s), you would send a dev ERROR log entry.
If you would like to only check against the last 10 emails, you would do this by querying for both $body == $new_body and $id >= ($total_rows-10)
You would then inject this into the container and call it using something like this
$this->container->get('helper')->sendEmail($body, $subject, $recipients);
Ok, thanks Dan for the idea as to using the database to do the dup check. If you notice, per your suggestion, i was already doing the dup check, but it made me think. It helped me connect the dots.
What i have done is return the answer if its a duplicate on the response when it does the updating the database, then using that response as a flag to determine if email fires or not. (in my case, i go further to check the updated stamp is at least +1 hour old, as opposed to the 'last 10 emails content' idea)
Heres the code.. Enjoy..
<?php
namespace Acme\AcmeBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller,
Acme\AcmeBundle\Entity\Log,
Symfony\Component\HttpFoundation\JsonResponse,
Symfony\Component\HttpFoundation\Response,
Symfony\Component\Config\Definition\Exception\Exception,
Symfony\Component\HttpFoundation\Request,
Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
class someWierdClass
{
/**
* #var array
*/
protected $senderArray = array('no-reply#yorudomain.com' => 'Your Website Name');
/**
* #param Request $request
* #param bool $persist
* #param bool $addEmail
* #return Response
*/
public function addLogAction(Request $request, $persist = TRUE, $addEmail = TRUE)
{
$responseAdd = array();
if ($this->getRequest()->request->all() !== null) {
$data = $this->getRequest()->request->get('data') ? $this->getRequest()->request->get('data') : 'no_data';
$type = $this->getRequest()->request->get('type') ? $this->getRequest()->request->get('type') : 'no_type';
$duplicate = $this->getRequest()->request->get('duplicate', null);
}
if ($addEmail) {
$responseAdd[] = 'firedIt';
$this->fireEmailString('You have an error log here. <br>' . $data);
}
if ($persist) {
$responseAdd[] = 'persistedIt';
$persistResponse = $this->persistLog( $type = $data, $duplicate);
if ($persistResponse) {
// a dup check is done here and results of this is on the response. (e.g. $content->passesCutoff)
$content = json_decode($persistResponse->getContent());
}
}
if ( $addEmail && ( isset($content->passesCutoff) && $content->passesCutoff ))
{
//fire off an email also, because its kind of hard to look in teh logs all the time, sometimes we just want an email.
$successEmail = $this->fireEmailString($data);
if( ! $successEmail )
{
$responseAdd[] = 'firedIt';
}
}
if ($responseAdd) {
$body = implode(', ', $responseAdd);
return new Response($body);
}
}
/**
* #param $emailStringData
* #param null $emailSubject
* #param null $emailTo
* #return mixed
*/
protected function fireEmailString($emailStringData, $emailSubject = null, $emailTo=null){
$templateName = 'AcmeBundle:Default:fireEmailString.html.twig';
if( ! $emailSubject )
{
$emailSubject = 'An email is being fired to you!' ;
}
if( ! $emailTo )
{
$emailTo = 'youremail#gmail.com';
}
$renderedView = $this->renderView(
$templateName, array(
'body' => $emailStringData,
));
$mailer = $this->get('mailer');
$message = $mailer->createMessage()
->setSubject( $emailSubject)
->setBody($emailStringData, 'text/plain')
->addPart($renderedView, 'text/html')
->setFrom($this->senderArray)
->setSender($this->senderArray)
->setTo($emailTo);
$results = $mailer->send($message);
return $results;
}
/**
* #param $type
* #param $data
* #param $duplicate
* #return JsonResponse
*/
protected function persistLog($type, $data, $duplicate) {
$em = $this->getDoctrine()->getManager();
$count = null;
$passesCutoff = null;
$mysqlNow = new \DateTime(date('Y-m-d G:i:s'));
//only two conditions can satisy here, strings '1' and 'true'.
if($duplicate !== '1' && $duplicate !== 'true' /*&& $duplicate != TRUE*/)
{
//in order to check if its unique we need to get the repo
//returns an object (findByData() would return an array)
$existingLog = $em->getRepository('AcmeBundle:Log')->findOneByData(
array('type' => $type, 'data' => $data)
);
if($existingLog)
{
$timeUpdatedString = strtotime($existingLog->getTimeupdated()->format('Y-m-d H:i:s'));
$cutoffStamp = strtotime('+1 hour', $timeUpdatedString); //advance 1 hour (customize this to the amount of time you want to go by before you consider this a duplicate. i think 1 hour is good)
$passesCutoff = time() >= $cutoffStamp ? TRUE : FALSE; //1 hour later
$count = $existingLog->getUpdatedcount();
$existingLog->setUpdatedcount($count + 1); // '2014-10-11 03:52:20' // date('Y-m-d G:i:s')
$em->persist($existingLog);
}
else
{
//this record isnt found, must be unique
$newLog = new Log(); //load our entity
//set in new values
$newLog->setType($type);
$newLog->setData($data);
$newLog->setUpdatedcount(0);
$newLog->setTimeupdated($mysqlNow);
$em->persist($newLog);
}
}
else
{
//we dont care if unique or not, we just want a new row
$newLog = new Log(); //load our entity
$newLog->setType($type);
$newLog->setData($data);
//time updated has been set to auto update to current timestamp in the schema, test first, then remove this
$newLog->setUpdatedcount(0);
$newLog->setTimeupdated($mysqlNow);
$em->persist($newLog);
}
$em->flush();
$response = new JsonResponse();
$response->setData(
array(
'data' => 'persistedIt',
'existingLog' => $count,
'passesCutoff' => $passesCutoff,
));
return $response;
}
}
In hindsight, i would have just passed the last update timestamp back on the response from the persist method, then do the cutoff calculation inside the fire email method obviously, but the above code does work as a starting point.. :-)

Resources