I need to set up a Slim application for html and json contents.
I will have just one errorhandler and it is supposed to reply as json for json enpoints and html error page for html views.
In the old fashined Slim (v.2) I have defined the view at the begining of the route, so I could check the view type (twig or json) to understand how to reply.
With the new Slim3 implementation the view will be send at the end of the route and, as far as I know, there is no way to define it earlier.
How can I manage this mixed content errors?
I thought to use the request content type header, but there is not a real rule that the content type should be coherent with the response, for example I can send some request as application/json and get a text/html reply, I also cannot use the Accept header because it can be missing or general */*.
I guess it depends how you decide, between returning json format or html.
For example, you may return error in json format when it is requested from AJAX otherwise you return html.
If you have error handler that returns html like following code
<?php namespace Your\App\Name\Space\Errors;
class HtmlErrorHandler
{
public function __invoke($request, $response, $exception)
{
$err = '<html><head></head><body>' .
'<div>code : ' . $exception->getCode() .
' message' . $exception->getMessage() '</div>';
return $response->withStatus(500)
->withHeader('content-Type : text/html')
->write($err);
}
}
and error handler that returns json,
<?php namespace Your\App\Name\Space\Errors;
class JsonErrorHandler
{
public function __invoke($request, $response, $exception)
{
$err = (object) [
'code' => $exception->getCode(),
'message' => $exception->getMessage(),
];
return $response->withStatus(500)->withJson($err);
}
}
You can compose them in another error handler class which select which error handler to return error response based on whether request coming from AJAX for not.
<?php namespace Your\App\Name\Space\Errors;
class Http500Error
{
private $jsonErrorHandler;
private $htmlErrorHandler;
public function __construct($jsonErrHandler, $htmlErrHandler)
{
$this->jsonErrorHandler = $jsonErrHandler;
$this->htmlErrorHandler = $htmlErrHandler;
}
public function __invoke($request, $response, $exception)
{
if ($request->isXhr()) {
$errHandler = $this->jsonErrorHandler;
} else {
$errHandler = $this->htmlErrorHandler;
}
return $errHandler($request, $response, $exception);
}
}
Or you may put certain variable in query string to indicate which format client want, then you can use
if ($request->getParam('format', 'html') === 'json') {
$errHandler = $this->jsonErrorHandler;
} else {
$errHandler = $this->htmlErrorHandler;
}
Then you injects error handler to container
use Your\App\Name\Space\Errors;
...
$app = new \Slim\App();
$c = $app->getContainer();
$c['errorHandler'] = function ($c) {
return new Http500Error(new JsonErrorHandler(), new HtmlErrorHandler());
};
So I'm looking for a solution to save invoice automatically into my server folder, when I press view invoice as the generated URL occurs (http://www.example.com/admin11111/index.php?controller=AdminPdf&token="token"&submitAction=generateInvoicePDF&id_order="id").
I also did research on google, but this solution, somehow didnt work for me: https://www.prestash...es-in-a-folder/
From Prestashop Forum I got advice that I should use shell script, but using a shell download like wget or other only gets me html file, because when I download the invoice in Prestashop backoffice.. it takes some time to generate and the download save appears later.
With this 2 overrides you could accomplish this.
Override PDF.php:
class PDF extends PDFCore
{
public function render($display = true)
{
if($this->template == PDF::TEMPLATE_INVOICE)
parent::render('F', true);
return parent::render($display);
}
}
Override PDFGenerator.php:
class PDFGenerator extends PDFGeneratorCore
{
public function render($filename, $display = true)
{
if (empty($filename)) {
throw new PrestaShopException('Missing filename.');
}
$this->lastPage();
if ($display === true) {
$output = 'D';
} elseif ($display === false) {
$output = 'S';
} elseif ($display == 'D') {
$output = 'D';
} elseif ($display == 'S') {
$output = 'S';
} elseif ($display == 'F') {
$output = 'F';
$filename = _PS_ROOT_DIR_.'/'.$filename;
} else {
$output = 'I';
}
return $this->output($filename, $output);
}
}
Remember to choose another folder other than _PS_ROOT_DIR_. This was just for testing. Try $filename = _PS_ROOT_DIR_.'/../invoices/'.$filename; so it's not a public folder (and you must create the folder with the correct permissions.
I am stuck trying to get Swashbuckle 5 to generate complete help pages for an ApiController with a Post request using multipart/form-data parameters. The help page for the action comes up in the browser, but there is not included information on the parameters passed in the form. I have created an operation filter and enabled it in SwaggerConfig, the web page that includes the URI parameters, return type and other info derived from XML comments shows in the browser help pages; however, nothing specified in the operation filter about the parameters is there, and the help page contains no information about the parameters.
I must be missing something. Are there any suggestion on what I may have missed?
Operation filter code:
public class AddFormDataUploadParamTypes : IOperationFilter
{
public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription) {
if (operation.operationId == "Documents_Upload")
{
operation.consumes.Add("multipart/form-data");
operation.parameters = new[]
{
new Parameter
{
name = "anotherid",
#in = "formData",
description = "Optional identifier associated with the document.",
required = false,
type = "string",
format = "uuid"
},
new Parameter
{
name = "documentid",
#in = "formData",
description = "The document identifier of the slot reserved for the document.",
required = false,
type = "string",
format = "uuid"
},
new Parameter
{
name = "documenttype",
#in = "formData",
description = "Specifies the kind of document being uploaded. This is not a file name extension.",
required = true,
type = "string"
},
new Parameter
{
name = "emailfrom",
#in = "formData",
description = "A optional email origination address used in association with the document if it is emailed to a receiver.",
required = false,
type = "string"
},
new Parameter
{
name = "emailsubject",
#in = "formData",
description = "An optional email subject line used in association with the document if it is emailed to a receiver.",
required = false,
type = "string"
},
new Parameter
{
name = "file",
#in = "formData",
description = "File to upload.",
required = true,
type = "file"
}
};
}
}
}
With Swashbuckle v5.0.0-rc4 methods listed above do not work. But by reading OpenApi spec I have managed to implement a working solution for uploading a single file. Other parameters can be easily added:
public class FileUploadOperationFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
var isFileUploadOperation =
context.MethodInfo.CustomAttributes.Any(a => a.AttributeType == typeof(YourMarkerAttribute));
if (!isFileUploadOperation) return;
var uploadFileMediaType = new OpenApiMediaType()
{
Schema = new OpenApiSchema()
{
Type = "object",
Properties =
{
["uploadedFile"] = new OpenApiSchema()
{
Description = "Upload File",
Type = "file",
Format = "binary"
}
},
Required = new HashSet<string>()
{
"uploadedFile"
}
}
};
operation.RequestBody = new OpenApiRequestBody
{
Content =
{
["multipart/form-data"] = uploadFileMediaType
}
};
}
}
I presume you figured out what your problem was. I was able to use your posted code to make a perfect looking 'swagger ui' interface complete with the file [BROWSE...] input controls.
I only modified your code slightly so it is applied when it detects my preferred ValidateMimeMultipartContentFilter attribute stolen from Damien Bond. Thus, my slightly modified version of your class looks like this:
public class AddFormDataUploadParamTypes<T> : IOperationFilter
{
public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
{
var actFilters = apiDescription.ActionDescriptor.GetFilterPipeline();
var supportsDesiredFilter = actFilters.Select(f => f.Instance).OfType<T>().Any();
if (supportsDesiredFilter)
{
operation.consumes.Add("multipart/form-data");
operation.parameters = new[]
{
//other parameters omitted for brevity
new Parameter
{
name = "file",
#in = "formData",
description = "File to upload.",
required = true,
type = "file"
}
};
}
}
}
Here's my Swagger UI:
FWIW:
My NuGets
<package id="Swashbuckle" version="5.5.3" targetFramework="net461" />
<package id="Swashbuckle.Core" version="5.5.3" targetFramework="net461" />
Swagger Config Example
public class SwaggerConfig
{
public static void Register()
{
var thisAssembly = typeof(SwaggerConfig).Assembly;
GlobalConfiguration.Configuration
.EnableSwagger(c =>
{
c.Schemes(new[] { "https" });
// Use "SingleApiVersion" to describe a single version API. Swagger 2.0 includes an "Info" object to
// hold additional metadata for an API. Version and title are required but you can also provide
// additional fields by chaining methods off SingleApiVersion.
//
c.SingleApiVersion("v1", "MyCorp.WebApi.Tsl");
c.OperationFilter<MyCorp.Swashbuckle.AddFormDataUploadParamTypes<MyCorp.Attr.ValidateMimeMultipartContentFilter>>();
})
.EnableSwaggerUi(c =>
{
// If your API supports ApiKey, you can override the default values.
// "apiKeyIn" can either be "query" or "header"
//
//c.EnableApiKeySupport("apiKey", "header");
});
}
}
UPDATE March 2019
I don't have quick access to the original project above, but, here's an example API controller from a different project...
Controller signature:
[ValidateMimeMultipartContentFilter]
[SwaggerResponse(HttpStatusCode.OK, Description = "Returns JSON object filled with descriptive data about the image.")]
[SwaggerResponse(HttpStatusCode.NotFound, Description = "No appropriate equipment record found for this endpoint")]
[SwaggerResponse(HttpStatusCode.BadRequest, Description = "This request was fulfilled previously")]
public async Task<IHttpActionResult> PostSignatureImage(Guid key)
You'll note that there's no actual parameter representing my file in the signature, you can see below that I just spin up a MultipartFormDataStreamProvider to suck out the incoming POST'd form data.
Controller Body:
var signatureImage = await db.SignatureImages.Where(img => img.Id == key).FirstOrDefaultAsync();
if (signatureImage == null)
{
return NotFound();
}
if (!signatureImage.IsOpenForCapture)
{
ModelState.AddModelError("CaptureDateTime", $"This equipment has already been signed once on {signatureImage.CaptureDateTime}");
}
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
string fileName = String.Empty;
string ServerUploadFolder = System.Web.Hosting.HostingEnvironment.MapPath("~/App_Data/");
DirectoryInfo di = new DirectoryInfo(ServerUploadFolder + key.ToString());
if (di.Exists == true)
ModelState.AddModelError("id", "It appears an upload for this item is either in progress or has already occurred.");
else
di.Create();
var fullPathToFinalFile = String.Empty;
var streamProvider = new MultipartFormDataStreamProvider(di.FullName);
await Request.Content.ReadAsMultipartAsync(streamProvider);
foreach (MultipartFileData fileData in streamProvider.FileData)
{
if (string.IsNullOrEmpty(fileData.Headers.ContentDisposition.FileName))
{
return StatusCode(HttpStatusCode.NotAcceptable);
}
fileName = cleanFileName(fileData.Headers.ContentDisposition.FileName);
fullPathToFinalFile = Path.Combine(di.FullName, fileName);
File.Move(fileData.LocalFileName, fullPathToFinalFile);
signatureImage.Image = File.ReadAllBytes(fullPathToFinalFile);
break;
}
signatureImage.FileName = streamProvider.FileData.Select(entry => cleanFileName(entry.Headers.ContentDisposition.FileName)).First();
signatureImage.FileLength = signatureImage.Image.LongLength;
signatureImage.IsOpenForCapture = false;
signatureImage.CaptureDateTime = DateTimeOffset.Now;
signatureImage.MimeType = streamProvider.FileData.Select(entry => entry.Headers.ContentType.MediaType).First();
db.Entry(signatureImage).State = EntityState.Modified;
try
{
await db.SaveChangesAsync();
//cleanup...
File.Delete(fullPathToFinalFile);
di.Delete();
}
catch (DbUpdateConcurrencyException)
{
if (!SignatureImageExists(key))
{
return NotFound();
}
else
{
throw;
}
}
char[] placeHolderImg = paperClipIcon_svg.ToCharArray();
signatureImage.Image = Convert.FromBase64CharArray(placeHolderImg, 0, placeHolderImg.Length);
return Ok(signatureImage);
Extending #bkwdesign very useful answer...
His/her code includes:
//other parameters omitted for brevity
You can actually pull all the parameter information (for the non-multi-part form parameters) from the parameters to the filter. Inside the check for supportsDesiredFilter, do the following:
if (operation.parameters.Count != apiDescription.ParameterDescriptions.Count)
{
throw new ApplicationException("Inconsistencies in parameters count");
}
operation.consumes.Add("multipart/form-data");
var parametersList = new List<Parameter>(apiDescription.ParameterDescriptions.Count + 1);
for (var i = 0; i < apiDescription.ParameterDescriptions.Count; ++i)
{
var schema = schemaRegistry.GetOrRegister(apiDescription.ParameterDescriptions[i].ParameterDescriptor.ParameterType);
parametersList.Add(new Parameter
{
name = apiDescription.ParameterDescriptions[i].Name,
#in = operation.parameters[i].#in,
description = operation.parameters[i].description,
required = !apiDescription.ParameterDescriptions[i].ParameterDescriptor.IsOptional,
type = apiDescription.ParameterDescriptions[i].ParameterDescriptor.ParameterType.FullName,
schema = schema,
});
}
parametersList.Add(new Parameter
{
name = "fileToUpload",
#in = "formData",
description = "File to upload.",
required = true,
type = "file"
});
operation.parameters = parametersList;
first it checks to make sure that the two arrays being passed in are consistent. Then it walks through the arrays to pull out the required info to put into the collection of Swashbuckle Parameters.
The hardest thing was to figure out that the types needed to be registered in the "schema" in order to have them show up in the Swagger UI. But, this works for me.
Everything else I did was consistent with #bkwdesign's post.
I am not able to save the image uploaded from simple Form with this code
public function actionImage()
{
print_r($_FILES);
$dir = Yii::getPathOfAlias('application.uploads');
if(isset($_POST['img']))
{
$model = new FileUpload();
$model->attributes = $_POST['img'];
$model->image=CUploadedFile::getInstance($model,'image');
if($model->validate())
{
$model->image->saveAs($dir.'/'.$model->image->getName());
// redirect to success page
}
}
}
To Answer my own question instead of using above code I used this:
public function actionImage()
{
$dir = Yii::getPathOfAlias('application.uploads');
if (isset($_FILES['img']))
{
$image = CUploadedFile::getInstanceByName('img');
$image->saveAs($dir.'/'.$image->getName());
}
}
To Answer my own question instead of using above code I used this:
public function actionImage() {
$dir = Yii::getPathOfAlias('application.uploads');
if (isset($_FILES['img']))
{
$image = CUploadedFile::getInstanceByName('img');
$image->saveAs($dir.'/'.$image->getName());
} }
I was trying to pass SQL values from Model to Controller but the value couldn't be passed.
This the code in my model file:
class Has_alert extends CI_Model {
function __construct()
{
parent::__construct();
}
function __get_query() {
$sql = 'alerts_get_alerts';
$query = $this->db->query($sql);
$row = $query->first_row();
$header_data['hasAlert'] = $row->active;
}
}
And this is the code in my controller file:
class Chart extends CI_Controller {
// Default Constructor
public function __construct() {
parent::__construct();
$this->load->helper('html');
$this->load->model('Has_alert', '', TRUE);
$this->Has_alert->__get_query();
//$sql = 'alerts_get_alerts';
//$query = $this->db->query($sql);
//$row = $query->first_row();
//$header_data['hasAlert'] = $row->active;
}
public function index()
{
//Data Arrays
$this->load->helper('html');
$header_data['page_title'] = 'Title';
$header_data['tabid'] = "home";
//Load the headtop.php file and get values from data array
$this->load->view('includes/headertop.php', $header_data);
$this->load->view('homepage');
$this->load->view('includes/newfooter.php');
}
I got this error message on my view file:
A PHP Error was encountered
Severity: Notice
Message: Undefined variable: hasAlert
Filename: includes/headertop.php
Line Number: 184
Does anyone know what the problem is? Thank you.
Model
function __get_query() {
$sql = 'alerts_get_alerts';
$query = $this->db->query($sql);
$row = $query->first_row();
return $row->active;
}
Controller
public function index(){
$this->load->model("Has_alert");
//Data Arrays
$this->load->helper('html');
$header_data['page_title'] = 'Title';
$header_data['tabid'] = "home";
$header_data['hasAlert'] = $this->Has_alert->__get_query();
//Load the headtop.php file and get values from data array
$this->load->view('includes/headertop.php', $header_data);
$this->load->view('homepage');
$this->load->view('includes/newfooter.php');
}
I'm assuming that things like "alerts_get_alerts" is pseudocode.