Symfony Batch Action - symfony1

I'm trying to create a batch action (symfony admin) that enables the creation/download on the fly of zip file containing users photos which are avaialable on the uploads/images directory.
Here is the code that I already implemented:
public function executeBatchDownloadFotos(sfWebRequest $request)
{
$zip = new ZipArchive();
// atheletes identifiers
$ids = $request->getParameter('ids');
// get all the atheletes objects
$q = Doctrine_Query::create()
->from('Atleta a')
->whereIn('a.id', $ids);
foreach ($q->execute() as $atleta)
{
$zip->addFile($atleta->id . '_' . $atleta->Clube . '.jpg', 'uploads/atletas/' . $atleta->id . '_' . $atleta->Clube . '.jpg');
}
}
By the other hand, here is the view configuration:
BatchDownloadFotos:
http_metas:
content-type: application/zip
has_layout: false
For some reason, each time execute the batch action, the browser do not prompts me with the window to download the zip file.

After you create ZIP archive in your controller file you should send the content to the browser.
You can do this using methods described here: http://www.symfony-project.org/gentle-introduction/1_4/en/06-Inside-the-Controller-Layer#chapter_06_sub_action_termination
Now you are trying to create ZIP file, but you are not sending it to the browser. You should use setContent() and setHttpHeader() methods.
Your action could look like this (you should add error handling):
public function executeIndex(sfWebRequest $request)
{
$fileName = '/tmp/test.zip';
$zip = new ZipArchive();
$zip->open($fileName, ZipArchive::CREATE);
// add some files to archive
$zip->addFile('/tmp/test', 'test.txt');
$zip->close();
$this->getResponse()->setContent(file_get_contents($fileName));
$this->getResponse()->setHttpHeader('Content-Type', 'application/zip');
$this->getResponse()->setHttpHeader('Content-Disposition',
'attachment; filename=download.zip');
return sfView::NONE;
}

Related

RestBuilder Plugin. How can I upload a file without creating a file?

Currently, I can upload files(exist) with Grails's RestBuilder.
However, I want to upload a file without creating a file .
I want to create binary data (= Text File) in a program and send it directly
Is it possible?
RestBuilder rest = new RestBuilder()
RestResponse resp = rest.post(url){
contentType("multipart/form-data")
setProperty("dataFile",[filePath])// <- it can
setProperty("dataFile",[ byte[] or inputStream() or String ? ])// <- Is it possible?
}
'''
I'm sure you figured this out already, but you can just use a String reference or a byte[] just as you can use File instances for the multipart request using RestBuilder. It should 'just work' e.g.
RestBuilder rest = new RestBuilder()
RestResponse response = rest.post(url) {
contentType 'multipart/form-data'
stringPart = 'hello' // String
bytePart = '68656c6c6f'.decode64() // byte[]
filePart = new File('/path/to/file.jpg') // File
}

ASP.NET MVC download prompt not appearing

I'm trying to generate a Excel .xlsx file in a controller action. I would like to have the website show a download prompt to download the resulting file. The controller actions executes fine, but no download prompt is shown. Nothing happens.
I've tried:
MemoryStream mstream = ... //generated file;
return File(mstream.ToArray(), "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", model.DisplayName + ".xlsx");
I've tried:
return new FileStreamResult(mstream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") { FileDownloadName = model.DisplayName + ".xlsx" };
I've tried:
Response.Clear();
Response.AddHeader("Content-Disposition", "attachment; filename=" + model.DisplayName + ".xlsx");
Response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
Response.Write(mstream.ToArray());
Response.End();
return Content("");
I even tried saving the file to disk, then returning via the filepath
return File(filepath, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
What am I doing wrong?
Thanks!
I am using the following code in an MVC project.
public ActionResult GetCSV()
{
string filename = "example";
string csv = MyHelper.GetCSVString();
return File(Encoding.UTF8.GetBytes(csv.ToString()), "text/csv", string.Format("{0}.csv", filename));
}
My csv string could look something like this
"Col1,Col2,Col3\nRow1Val1,Row1Val2,Row1Val3\n"
To trigger this download in a new window I call the following JavaScript
window.open('/MyUrl/GetCSV', 'DownloadWindowName');
Add the header as follows.
var cd = new System.Net.Mime.ContentDisposition
{
FileName = model.DisplayName + ".xlsx",
Inline = false
};
Response.AppendHeader("Content-Disposition", cd.ToString());
then return the file as follows
return File(mstream, ".xlsx");
Regarding download prompt. If you mean a prompt where it asks where to save the file, then it depends on how the user has set it up in their browser settings. For example in chrome, users can choose not to get a prompt when downloading files and have it downloaded to a pre specified location like the download folder.
http://malektips.com/google-chrome-prompt-download-file.html#.VM-DbFWsUm8

Phalcon PHP post link with JavaScript confirmation dialog

I am developing a CRUD system in Phalcon PHP (version 1.3.4).
My goal is to create a link (delete row), that asks for confirmation on click (JavaScript confirmation box) and then goes (request type POST) to the link.
So lets say a user clicks on the "delete row" button.
JavaScript confirmation "Are you sure you want to delete this row?"
User clicks "yes"
Webpage does a POST to "/users/delete/1"
I know CakePHP has a function (FormHelper::postLink()) that does exactly that.
I was wondering if Phalcon PHP also had a function like this.
I see three possibilities to achieve what you want. One is to create a macro in Volt template, second is to add a function to your View. Third and closest to - what I understand is your wish - is to extend Phalcons tag helper and this is part I will describe here.
Phalcon has its own Tag helper to allow you to easily create some elements. postLink is not a part that is implemented there, but you can easily achieve it. In my example I have namespace of Application with class of Tag that extends from \Phalcon\Tag. This is my base for this tutorial.
// Tag.php
namespace Application;
class Tag extends \Phalcon\Tag
{
static public function postLink() {
return '<strong>TEST TAG</strong>';
}
}
To force Phalcon DI to use this class, it is necessary to override it's standard declaration from engine by declaring it by hand as a new DI service:
// services.php
$di['tag'] = function() {
return new \Application\Tag();
};
You can test if it is working properly by typing {{ tag.postLink() }} in Volt template or with $this->tag->postLink() if using phtml template.
Now you can fill your Tag::postLink() method with HTML and parameters you wish it will produce:
namespace Application;
class Tag extends \Phalcon\Tag
{
static $forms = [];
static public function postLink($title, $url, $options = array()) {
// random & unique form ID
while ($randId = 'f_' . mt_rand(9000, 999999)) {
if (!isset(self::$forms[$randId])) {
self::$forms[$randId] = true;
break;
}
}
// dialog message
$dialogMessage = isset($options['message']) && $options['message'] ? $options['message'] : 'Are you sure you want to go on?';
$html = <<<HTML
<form action="{$url}" method="post" id="{$randId}">
<!-- maybe not necessary part -->
<input type="hidden" name="confirmed" value="1" />
</form>
{$title}
HTML;
return $html;
}
}
Now you can run it like this:
{{ tag.postLink('delete', '/users/delete/1') }}
{% set formOptions = ['message' : 'Are you sure you want to delete user Kialia Kuliambro?'] %}
{{ tag.postLink('delete', '/users/delete/1', formOptions) }}
{{ tag.postLink('delete', '/users/delete/1', ['message' : 'Are you sure you want to delete user Kialia Kuliambro?']) }}
Have fun extending :)
There's a few ways to implement such behavior in phalcon. Before anything, we need to understand how views and view helpers work in phalcon. And if you pay close attention, you'll notice, both .volt and .phtml have direct access to the DI.
In volt, for example, you can access the flash service, and output its messages by calling:
{{ flash.output() }}
which gets converted to the phtml: <?php echo $this->flash->output(); ?>
Thus my solution focuses on defining a new service in the DI which volt can access. In CakePHP, the syntax for postLink(), looks something like: echo $this->Form->postLink() while the function is actually defined in a class named FormHelper. So my solution will do the same thing, define a class FormHelper, then inject it into the view under the name Form.
Create an app/helpers/ directory.
Update your app/config/config.php file adding a reference to our new directory: 'helpersDir'=> APP_PATH . '/app/helpers/'
Update your app/config/loader.php file adding $config->application->helpersDir to the registered directories.
Create a new file app/helpers/FormHelper.php
Copy-paste the following code into the file:
<?php
use Phalcon\Tag;
class FormHelper extends Tag
{
protected $_lastAction = '';
public function dottedNameToBracketNotation($name)
{
$parts=explode('.',$name);
$first = array_shift($parts);
$name=$first . ($parts ? '[' . implode('][', $parts) . ']' : '');
return $name;
}
protected function flatten(array $data, $separator = '.')
{
$result = [];
$stack = [];
$path = null;
reset($data);
while (!empty($data)) {
$key = key($data);
$element = $data[$key];
unset($data[$key]);
if (is_array($element) && !empty($element)) {
if (!empty($data)) {
$stack[] = [$data, $path];
}
$data = $element;
reset($data);
$path .= $key . $separator;
} else {
$result[$path . $key] = $element;
}
if (empty($data) && !empty($stack)) {
list($data, $path) = array_pop($stack);
reset($data);
}
}
return $result;
}
protected function _confirm($message, $okCode, $cancelCode = '', $options = [])
{
$message = json_encode($message);
$confirm = "if (confirm({$message})) { {$okCode} } {$cancelCode}";
if (isset($options['escape']) && $options['escape'] === false) {
$confirm = $this->h($confirm);
}
return $confirm;
}
public function h($text, $double = true, $charset = 'UTF-8')
{
return htmlspecialchars($text, ENT_QUOTES | ENT_SUBSTITUTE, $charset, $double);
}
protected function _lastAction($url)
{
$action = $url;//Router::url($url, true);
$query = parse_url($action, PHP_URL_QUERY);
$query = $query ? '?' . $query : '';
$this->_lastAction = parse_url($action, PHP_URL_PATH) . $query;
}
public function postLink($title, $url = null, array $options = [])
{
$out='';
$options += ['block' => null, 'confirm' => null];
$requestMethod = 'POST';
if (!empty($options['method'])) {
$requestMethod = strtoupper($options['method']);
unset($options['method']);
}
$confirmMessage = $options['confirm'];
unset($options['confirm']);
$formName = str_replace('.', '', uniqid('post_', true));
$formOptions = [
'name' => $formName,
'style' => 'display:none;',
'method' => 'post',
];
if (isset($options['target'])) {
$formOptions['target'] = $options['target'];
unset($options['target']);
}
$formOptions[0]=$url;
$out.=$this->form($formOptions);
$out .= $this->hiddenField(['_method','value' => $requestMethod]);
$fields = [];
if (isset($options['data']) && is_array($options['data'])) {
foreach ($this->flatten($options['data']) as $key => $value) {
$out .= $this->hiddenField([$this->dottedNameToBracketNotation($key),'value' => $value]);
}
unset($options['data']);
}
$out .= $this->endForm();
//This is currently unsupported
if ($options['block']) {
if ($options['block'] === true) {
$options['block'] = __FUNCTION__;
}
//$this->_View->append($options['block'], $out);
$out = '';
}
unset($options['block']);
$url = '#';
$onClick = 'document.' . $formName . '.submit();';
if ($confirmMessage) {
$options['onclick'] = $this->_confirm($confirmMessage, $onClick, '', $options);
} else {
$options['onclick'] = $onClick . ' ';
}
$options['onclick'] .= 'event.returnValue = false; return false;';
$options[0]=$url;
$options[1]=$title;
$options[2]=false;
$out .= $this->linkTo($options);
return $out;
}
}
Edit your app/config/services.php file and add in:
$di->set('Form',function () {
return new FormHelper();
});
(you could make "Form" lowercase if you want, both work. I made it capital to closer resemble CakePHP's syntax. Do note that Volt is case sensitive when trying to access services but phtml will lowercase it.)
Edit the template you want to test the code on, such as app/views/index/test.volt
Copy-paste the following code into there:
{{ Form.postLink(' Delete','',['confirm':'Are you sure you want to delete #4?','data':['a':['b','c']]]) }}
Alternatively for phtml, use: <?php echo $this->form->postLink(' Delete', '', array('confirm' => 'Are you sure you want to delete #4?', 'data' => array('a' => array('b', 'c')))); ?>
Run it, and watch it work its magic, just render your index/test.volt template by visiting /index/test in your address bar. (Make sure you defined such an action in your index controller)
In terms, of other solutions, you could also use $compiler->addFunction() to make functions available to volt, one at time. The page in the manual gives the example of $compiler->addFunction('shuffle', 'str_shuffle');. You can attempt to override the factoryDefault for "tag" in the DI, and use the helper we already defined which extends tag. So you'd just change it from "form" to "tag" like so: $di->set('tag',function () {return new FormHelper();}); but, as you can see, it won't make the function postLink() available to volt as a function, you'll notice you still need to access it as tag.postLink(). Rather, all the \Phalcon\Tag functions are actually hard-coded into the volt engine. You can see this clearly by viewing the zephir source code of the \Phalcon\Mvc\View\Engine\Volt\Compiler class available over here. For your convenience, and in case the link ever gets broken, I have posted a snippet here which shows the "tag" functions in volt are actually hard-coded into it:
if method_exists(className, method) {
let arrayHelpers = this->_arrayHelpers;
if typeof arrayHelpers != "array" {
let arrayHelpers = [
"link_to": true,
"image": true,
"form": true,
"select": true,
"select_static": true,
"submit_button": true,
"radio_field": true,
"check_field": true,
"file_field": true,
"hidden_field": true,
"password_field": true,
"text_area": true,
"text_field": true,
"email_field": true,
"date_field": true,
"tel_field": true,
"numeric_field": true,
"image_input": true
];
let this->_arrayHelpers = arrayHelpers;
}
if isset arrayHelpers[name] {
return "$this->tag->" . method . "(array(" . arguments . "))";
}
return "$this->tag->" . method . "(" . arguments . ")";
}
So, if you'd like to "hack" in a few more methods by extending the \Phalcon\Tags class, you're out of luck. However, as demonstrated on the volt documentation page, there exists the concept of registering custom extensions to work with volt. The documentation gives the example of: $compiler->addExtension(new PhpFunctionExtension());
Where the source of the class is:
<?php
class PhpFunctionExtension
{
/**
* This method is called on any attempt to compile a function call
*/
public function compileFunction($name, $arguments)
{
if (function_exists($name)) {
return $name . '('. $arguments . ')';
}
}
}
This would allow volt access to any function you'd like, without having to manually register every possible function you could possibly ever need. You can test this by trying to access str_shuffle in volt, like we did before with $compiler->addFunction('shuffle', 'str_shuffle'); but this time without having to register it.
In terms of other solutions, you could also try to integrate CakePHP and PhalconPHP together, and attempt to call CakePHP's view helpers from PhalconPHP, but then you'd run into a problem of CakePHP not understanding your router setup you have configured in Phalcon. But, if you're determined, you could code all the routes and config for CakePHP and run it alongside PhalconPHP, but I'd highly discourage such a desperate workaround. And, finally, if you understand how the function works, and you barely use it, you could get away with just hard-coding the HTML in the first place. Honestly, CakePHP's logic doesn't look so sound to me in the first place because it has to corrupt your HTML document with a form inserted which can bother your layout. I think it would make more sense to generate a form dynamically with JavaScript, if we're using JavaScript already, and append it to the <body> when the button is clicked, then submit the form we just created dynamically. But, you wanted a CakePHP implementation, so I coded it as close to the logic they used as possible. It's not perfect, in terms of supporting all their features, such as block, but it should suit most of your needs.
I can always revise my implementation, but I think it demonstrates how to work with Phalcon pretty well for those migrating from CakePHP.

How to test file download in Behat

There is this new Export functionality developed on this application and I'm trying to test it using Behat/Mink.
The issue here is when I click on the export link, the data on the page gets exported in to a CSV and gets saved under /Downloads but I don't see any response code or anything on the page.
Is there a way I can export the CSV and navigate to the /Downloads folder to verify the file?
Assuming you are using the Selenium driver you could "click" on the link and $this->getSession()->wait(30) until the download is finished and then check the Downloads folder for the file.
That would be the simplest solution. Alternatively you can use a proxy, like BrowserMob, to watch all requests and then verify the response code. But that would be a really painful path for that alone.
The simplest way to check that the file is downloaded would be to define another step with a basic assertion.
/**
* #Then /^the file ".+" should be downloaded$/
*/
public function assertFileDownloaded($filename)
{
if (!file_exists('/download/dir/' . $filename)) {
throw new Exception();
}
}
This might be problematic in situations when you download a file with the same name and the browser saves it under a different name. As a solution you can add a #BeforeScenario hook to clear the list of the know files.
Another issue would be the download dir itself – it might be different for other users / machines. To fix that you could pass the download dir in your behat.yml as a argument to the context constructor, read the docs for that.
But the best approach would be to pass the configuration to the Selenium specifying the download dir to ensure it's always clear and you know exactly where to search. I am not certain how to do that, but from the quick googling it seems to be possible.
Checkout this blog: https://www.jverdeyen.be/php/behat-file-downloads/
The basic idea is to copy the current session and do the request with Guzzle. After that you can check the response any way you like.
class FeatureContext extends \Behat\Behat\Context\BehatContext {
/**
* #When /^I try to download "([^"]*)"$/
*/
public function iTryToDownload($url)
{
$cookies = $this->getSession()->getDriver()->getWebDriverSession()->getCookie('PHPSESSID');
$cookie = new \Guzzle\Plugin\Cookie\Cookie();
$cookie->setName($cookies[0]['name']);
$cookie->setValue($cookies[0]['value']);
$cookie->setDomain($cookies[0]['domain']);
$jar = new \Guzzle\Plugin\Cookie\CookieJar\ArrayCookieJar();
$jar->add($cookie);
$client = new \Guzzle\Http\Client($this->getSession()->getCurrentUrl());
$client->addSubscriber(new \Guzzle\Plugin\Cookie\CookiePlugin($jar));
$request = $client->get($url);
$this->response = $request->send();
}
/**
* #Then /^I should see response status code "([^"]*)"$/
*/
public function iShouldSeeResponseStatusCode($statusCode)
{
$responseStatusCode = $this->response->getStatusCode();
if (!$responseStatusCode == intval($statusCode)) {
throw new \Exception(sprintf("Did not see response status code %s, but %s.", $statusCode, $responseStatusCode));
}
}
/**
* #Then /^I should see in the header "([^"]*)":"([^"]*)"$/
*/
public function iShouldSeeInTheHeader($header, $value)
{
$headers = $this->response->getHeaders();
if ($headers->get($header) != $value) {
throw new \Exception(sprintf("Did not see %s with value %s.", $header, $value));
}
}
}
Little modified iTryToDownload() function with using all cookies:
public function iTryToDownload($link) {
$elt = $this->getSession()->getPage()->findLink($link);
if($elt) {
$value = $elt->getAttribute('href');
$driver = $this->getSession()->getDriver();
if ($driver instanceof \Behat\Mink\Driver\Selenium2Driver) {
$ds = $driver->getWebDriverSession();
$cookies = $ds->getAllCookies();
} else {
throw new \InvalidArgumentException('Not Selenium2Driver');
}
$jar = new \Guzzle\Plugin\Cookie\CookieJar\ArrayCookieJar();
for ($i = 0; $i < count($cookies); $i++) {
$cookie = new \Guzzle\Plugin\Cookie\Cookie();
$cookie->setName($cookies[$i]['name']);
$cookie->setValue($cookies[$i]['value']);
$cookie->setDomain($cookies[$i]['domain']);
$jar->add($cookie);
}
$client = new \Guzzle\Http\Client($this->getSession()->getCurrentUrl());
$client->addSubscriber(new \Guzzle\Plugin\Cookie\CookiePlugin($jar));
$request = $client->get($value);
$this->response = $request->send();
} else {
throw new \InvalidArgumentException(sprintf('Could not evaluate: "%s"', $link));
}
}
In project we have problem that we have two servers: one with web drivers and browsers and second with selenium hub. As result we decide to use curl request for fetching headers. So I wrote function which would called in step definition. Below you can find a function which use a standard php functions: curl_init()
/**
* #param $request_url
* #param $userToken
* #return bool
* #throws Exception
*/
private function makeCurlRequestForDownloadCSV($request_url, $userToken)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $request_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$headers = [
'Content-Type: application/json',
"Authorization: Bearer {$userToken}"
];
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
$output = curl_exec($ch);
$info = curl_getinfo($ch);
$output .= "\n" . curl_error($ch);
curl_close($ch);
if ($output === false || $info['http_code'] != 200 || $info['content_type'] != "text/csv; charset=UTF-8") {
$output = "No cURL data returned for $request_url [" . $info['http_code'] . "]";
throw new Exception($output);
} else {
return true;
}
}
How you can see I have authorization by token. If you want to understand what headers you should use you should download file manual and look request and response in browser's tab network

eajaxupload in yii failes

I try ti user eajaxupload extention in yii(using this article: http://www.yiiframework.com/extension/eajaxupload
I want Upload and attache image to one of controller,
I try this code:
in controller: *(my controller name is : article)*
public function actionUpload()
{
Yii::import("ext.EAjaxUpload.qqFileUploader");
$folder= Yii::app()->baseUrl .'/uploads';// folder for uploaded files
$allowedExtensions = array("jpg");//array("jpg","jpeg","gif","exe","mov" and etc...
$sizeLimit = 10 * 1024 * 1024;// maximum file size in bytes
$uploader = new qqFileUploader($allowedExtensions, $sizeLimit);
$result = $uploader->handleUpload($folder);
$result=htmlspecialchars(json_encode($result), ENT_NOQUOTES);
$fileSize=filesize($folder.$result['filename']);//GETTING FILE SIZE
$fileName=$result['filename'];//GETTING FILE NAME
//echo $result;// it's array
}
and in _form.php (for controller) i have:
$this->widget('ext.EAjaxUpload.EAjaxUpload',
array(
'id'=>'uploadFile',
'config'=>array(
'action'=>'/article/upload',
'allowedExtensions'=>array("jpg"),//array("jpg","jpeg","gif","exe","mov" and etc...
'sizeLimit'=>10*1024*1024,// maximum file size in bytes
//'minSizeLimit'=>10*1024*1024,// minimum file size in bytes
//'onComplete'=>"js:function(id, fileName, responseJSON){ alert(fileName); }",
'showMessage'=>"js:function(message){ alert(message); }"
)
)); ?>
upload folder have full access for all!,
but when i push upload file and select a file always get error: filename, filesize and Faild!
What is wrong in my code?
What do you see on the console when you you add this
echo "<pre>";
print_r($result);
echo "</pre>";exit(0);
after $result=htmlspecialchars(json_encode($result), ENT_NOQUOTES);
Make sure your upload folder exists. Yii::app()->baseUrl returns '...yourproject/protected'.
I use:$folder=Yii::app() -> getBasePath() . "/../images/";
Also check your console in browser(press F12). If you got 403 error, then add rules to your controller for upload action
public function accessRules()
{
return array(
array('allow', // allow all users to perform 'index' and 'view' actions
'actions'=>array('index','view','upload'),
'users'=>array('*'),
),
.....

Resources