Symfony2 SonataMediaBundle Audio Config - symfony

I need to use SonataMediaBundle to store audio files - mp3 and maybe also somehow conwert them also to ogg.
So basically I need config.
Currently I have this:
sonata_media:
default_context: default
db_driver: doctrine_orm # or doctrine_mongodb, doctrine_phpcr
contexts:
default: # the default context is mandatory
providers:
- sonata.media.provider.dailymotion
- sonata.media.provider.youtube
- sonata.media.provider.image
- sonata.media.provider.file
formats:
small: { width: 100 , quality: 70}
big: { width: 500 , quality: 70}
user:
providers:
- sonata.media.provider.file
formats:
mp3: { quality: 100}
ogg: { quality: 100}
cdn:
server:
path: /uploads/media # http://media.sonata-project.org/
filesystem:
local:
directory: %kernel.root_dir%/uploads/media
create: true
class:
media: Application\Sonata\MediaBundle\Entity\Media
But when I Im using command:
php.exe C:\xampp\htdocs\Radiooo\app\console sonata:media:add sonata.media.provider.file user C:/test.mp3
Im getting file stored like 45914671541816acb68412cc66ba1a71da3ac7a1.mpga
Can you help me what Im doing wrong?

First of all: I'm totally aware of the creation date of this question, but I recently stumbled upon the same problem, where I wanted to upload a mp3 file and just keep the file extension to mp3 instead of switching it to mpga. And as far as I can see, there was no easy solution added to the source code in the last three years ...
I'm pretty sure that my solution is not the best one, but it does the job with a small amount of code to add :)
In my project, I had to upload a file by setting the BinaryContent of a Media object to the Symfony Request class, which leads the Problem to exactly this line:
// FileProvider.php#L483
$guesser = ExtensionGuesser::getInstance();
$extension = $guesser->guess($media->getContentType());
The FileProvider class fetches the instance of the ExtensionGuesser from Symfony and lets him do the work of guessing what the extension for the given ContentType should be:
// MimeTypeExtensionGuesser.php#L623
'audio/mpeg' => 'mpga',
In my opinion it would be great, if we could either add own MimeType->Extension mappings or simply replace the class of the ExtensionGuesser with a small change in the configuration files. But no one knew that three people like us do not want to upload a mp3 file and switch the extension to mpga ... so there was no solution like these.
But we can actually change the className of the FileProvider and just overwrite the method that we want to do something different:
// app/config/services.yml
parameters:
sonata.media.provider.file.class: Application\Sonata\MediaBundle\Provider\FileProvider
// Application\Sonata\MediaBundle\Provider\FileProvider:
<?php
namespace Application\Sonata\MediaBundle\Provider;
use Sonata\MediaBundle\Extra\ApiMediaFile;
use Sonata\MediaBundle\Model\MediaInterface;
use Symfony\Component\HttpFoundation\File\MimeType\ExtensionGuesser;
use Symfony\Component\HttpFoundation\Request;
/**
* Class FileProvider
*
* #package Application\Sonata\MediaBundle\Provider
*/
class FileProvider extends \Sonata\MediaBundle\Provider\FileProvider
{
/**
* #var array
*/
protected $contentTypeMapping = [
'audio/mpeg' => 'mp3'
];
/**
* Set media binary content according to request content.
*
* #param MediaInterface $media
*/
protected function generateBinaryFromRequest(MediaInterface $media)
{
if (php_sapi_name() === 'cli') {
throw new \RuntimeException('The current process cannot be executed in cli environment');
}
if (!$media->getContentType()) {
throw new \RuntimeException(
'You must provide the content type value for your media before setting the binary content'
);
}
$request = $media->getBinaryContent();
if (!$request instanceof Request) {
throw new \RuntimeException('Expected Request in binary content');
}
$content = $request->getContent();
$extension = $this->getExtension($media->getContentType());
$handle = tmpfile();
fwrite($handle, $content);
$file = new ApiMediaFile($handle);
$file->setExtension($extension);
$file->setMimetype($media->getContentType());
$media->setBinaryContent($file);
}
/**
* Returns the fileExtension for a given contentType
*
* First of all, we have to look at our own mapping and if we have no mapping defined, just use the ExtensionGuesser
*
* #param string $contentType
*
* #return string
*/
private function getExtension($contentType)
{
if (array_key_exists($contentType, $this->contentTypeMapping)) {
return $this->contentTypeMapping[$contentType];
}
// create unique id for media reference
$guesser = ExtensionGuesser::getInstance();
$extension = $guesser->guess($contentType);
if (!$extension) {
throw new \RuntimeException(
sprintf('Unable to guess extension for content type %s', $contentType)
);
}
return $extension;
}
}
Until now it works pretty well and I had no problem with it :)

Related

Conditionally displaying specific routes on OpenAPI (aka Swagger) documentation generated by API-Platform

I wish to limit the routes displayed by the API-Platform generated OpenAPI documentation based on each route's security attribute and the logged on user's roles (i.e. only ROLE_ADMIN can see the OpenApi documentation).
A similar question was earlier asked and this answer partially answered it but not completely:
This isn't supported out of the box (but it would be a nice
contribution). What you can do is to decorate the
DocumentationNormalizer to unset() the paths you don't want to appear
in the OpenAPI documentation.
More information:
https://api-platform.com/docs/core/swagger/#overriding-the-openapi-specification
It appears that DocumentationNormalizer is depreciated and that one should decorate OpenApiFactoryInterface instead.
Attempting to implement, I configured config/services.yaml to decorate OpenApiFactory as shown below.
The first issue is I am unable to "unset() the paths you don't want to appear". The paths exist within the \ApiPlatform\Core\OpenApi\Model\Paths property of \ApiPlatform\Core\OpenApi\OpenApi, but there is only the ability to add additional paths to Paths and not remove them. I've come up with a solution which is shown in the below code which creates new objects and only adds back properties if they do not require admin access, but I suspect that doing so is not the "right way" to do this. Also, I just realized while it removed the documentation from SwaggerUI's routes, it did not remove it from the Schema displayed below the routes.
The second issue is how to determine which paths to display, and I temporarily hardcoded them in my getRemovedPaths() method. First, I will need to add a logon form to the SwaggerUi page so that we know the user's role which is fairly straightforward. Next, however, I will need to obtain the security attributes associated with each route so that I could determine whether a given route should be displayed, however, I have no idea how to do so. I expected the necessary data to be in each ApiPlatform\Core\OpenApi\Model\PathItem, however, there does not appear to be any methods to retrieve it and the properties are private. I also attempted to access the information by using \App\Kernel::getContainer()->get('router'), but was not successful locating the route security attributes.
In summary, how should one prevent routes from being displayed by the API-Platform generated OpenAPI documentation if the user does not have authority to access the route?
config/services.yaml
services:
App\OpenApi\OpenApiFactory:
decorates: 'api_platform.openapi.factory'
arguments: [ '#App\OpenApi\OpenApiFactory.inner' ]
autoconfigure: false
App/OpenApi/OpenApiFactory
<?php
namespace App\OpenApi;
use ApiPlatform\Core\OpenApi\Factory\OpenApiFactoryInterface;
use ApiPlatform\Core\OpenApi\OpenApi;
use ApiPlatform\Core\OpenApi\Model\Paths;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use App\Kernel;
class OpenApiFactory implements OpenApiFactoryInterface {
private $decorated, $tokenStorage, $kernel;
public function __construct(OpenApiFactoryInterface $decorated, TokenStorageInterface $tokenStorage, Kernel $kernel)
{
$this->decorated = $decorated;
$this->tokenStorage = $tokenStorage;
$this->kernel = $kernel;
}
public function __invoke(array $context = []): OpenApi
{
//$this->debug($context);
$openApi = $this->decorated->__invoke($context);
$removedPaths = $this->getRemovedPaths();
$paths = new Paths;
$pathArray = $openApi->getPaths()->getPaths();
foreach($openApi->getPaths()->getPaths() as $path=>$pathItem) {
if(!isset($removedPaths[$path])) {
// No restrictions
$paths->addPath($path, $pathItem);
}
elseif($removedPaths[$path]!=='*') {
// Remove one or more operation
foreach($removedPaths[$path] as $operation) {
$method = 'with'.ucFirst($operation);
$pathItem = $pathItem->$method(null);
}
$paths->addPath($path, $pathItem);
}
// else don't add this route to the documentation
}
$openApiTest = $openApi->withPaths($paths);
return $openApi->withPaths($paths);
}
private function getRemovedPaths():array
{
/*
Instead of hardcoding removed paths, remove all paths which $user does not have access to based on the route's security attributes and the user's credentials.
This hack returns an array with the path as the key, and either "*" to remove all operations or an array to remove specific operations.
*/
$user = $this->tokenStorage->getToken()->getUser();
return [
'/guids'=>'*', // Remove all operations
'/guids/{guid}'=>'*', // Remove all operations
'/accounts'=>['post'], // Remove only post operation
'/accounts/{uuid}'=>['delete'], // Remove only delete operation
];
}
private function debug(array $context = [])
{
$this->display($context, '$context');
$openApi = $this->decorated->__invoke($context);
$this->displayGetters($openApi);
$pathObject = $openApi->getPaths();
$this->displayGetters($pathObject, null, ['getPath', 'getPaths']);
$pathsArray = $pathObject->getPaths();
$this->display($pathsArray, '$openApi->getPaths()->getPaths()', true);
$pathItem = $pathsArray['/accounts'];
$this->displayGetters($pathItem);
$getGet = $pathItem->getGet();
$this->displayGetters($getGet, '$pathItem->getGet()', ['getResponses']);
$this->display($getGet->getTags(), '$getGet->getTags()');
$this->display($getGet->getParameters(), '$getGet->getParameters()');
$this->display($getGet->getSecurity(), '$getGet->getSecurity()');
$this->display($getGet->getExtensionProperties(), '$getGet->getExtensionProperties()');
$this->displayGetters($this->kernel, null, ['getBundles', 'getBundle']);
$container = $this->kernel->getContainer();
$this->displayGetters($container, null, ['getRemovedIds', 'getParameter', 'get', 'getServiceIds']);
$router = $container->get('router');
$this->displayGetters($router, null, ['getOption']);
$routeCollection = $router->getRouteCollection();
$this->displayGetters($routeCollection, null, ['get']);
$this->displayGetters($this, '$this');
$this->displayGetters($this->decorated, '$this->decorated');
$components = $openApi->getComponents ();
$this->displayGetters($components, null, []);
}
private function displayGetters($obj, ?string $notes=null, array $exclude=[])
{
echo('-----------------------------------------------------------'.PHP_EOL);
if($notes) {
echo($notes.PHP_EOL);
}
echo(get_class($obj).PHP_EOL);
echo('get_object_vars'.PHP_EOL);
print_r(array_keys(get_object_vars($obj)));
echo('get_class_methods'.PHP_EOL);
print_r(get_class_methods($obj));
foreach(get_class_methods($obj) as $method) {
if(substr($method, 0, 3)==='get') {
if(!in_array($method, $exclude)) {
$rs = $obj->$method();
$type = gettype($rs);
switch($type) {
case 'object':
printf('type: %s path: %s method: %s'.PHP_EOL, $type, $method, get_class($rs));
print_r(get_class_methods($rs));
break;
case 'array':
printf('type: %s method: %s'.PHP_EOL, $type, $method);
print_r($rs);
break;
default:
printf('type: %s method: %s, value: %s'.PHP_EOL, $type, $method, $rs);
}
}
else {
echo('Exclude method: '.$method.PHP_EOL);
}
}
}
}
private function display($rs, string $notes, bool $keysOnly = false)
{
echo('-----------------------------------------------------------'.PHP_EOL);
echo($notes.PHP_EOL);
print_r($keysOnly?array_keys($rs):$rs);
}
}

Symfony4 Twig Extension return image path

I want to create a TwigExtension Function in Symfony4 in order to display images based on a string property in the view.
I have installed the assets: "symfony/asset": "^4.4",
In lower versions of Symfony I could to this with AssetsHelper::getUrl() -> Returns the public url/path of an asset.
I am not sure how can I achieve this in Symfony4 with "Twig".
The solution is a bit different.
So This is what i did:
In webpackencore config i had to add this in order to have the files copied to the public folder:
.copyFiles({
'from': './assets/img',
'to': 'images/[path][name].[ext]'
})
Then in my extension class the function looks like this:
/**
* #param $type
*
* #return mixed
*/
public function showPaymentTypeIcon($type)
{
$sImagePath = '/build/images/payment-icons';
switch($type) {
case 'card':
default:
$assetImagePath = $sImagePath.'/credit.svg';
break;
case 'sofort':
$assetImagePath = $sImagePath.'/sofort.svg';
break;
}
$oPackage = new Package(new EmptyVersionStrategy());
return $oPackage->getUrl($assetImagePath);
}
So instead of AssetsHelper now we have to use Package as described here: https://symfony.com/doc/current/components/asset.html#asset-packages

config jsor library to make symfony 2/doctrine 2 work with postgis extension

Working with Symfony 2.8.24 and PostGIS
I need to THE TITLE. I found this in github but the problem is the configuration steps there didn't help me a lot. I don't know if it is because I can't use composer online (proxy) to install it as the first step states.
IRDK where to start, for example, I don't see the path 'Jsor\Doctrine\PostGIS\Types\GeographyType' anywhere in the files I am using so I don't know where to copy them to use them, or maybe it has nothing to do with it.
I know it's asking a lot but could anyone walk me through it or point me somewhere on how 2 include this library in symfony2.8 from the beginning, I have been using Symfony for a while now (not postgis) and still have a really hard time including this kind of third party libs, so we're talking baby steps here. thanx
Ensure you have the postgis extension installed on your postgresql server.
Install the package via composer.
composer require jsor/doctrine-postgis
Add the types to doctrine-bundle's configuration.
Example:
# app/config/config.yml
doctrine:
orm:
# [..]
entity_managers:
default:
dql:
string_functions:
Geometry: Jsor\Doctrine\PostGIS\Functions\Geometry
Geography: Jsor\Doctrine\PostGIS\Functions\Geography
ST_Distance: Jsor\Doctrine\PostGIS\Functions\ST_Distance
ST_Distance_Sphere: Jsor\Doctrine\PostGIS\Functions\ST_Distance_Sphere
ST_DWithin: Jsor\Doctrine\PostGIS\Functions\ST_DWithin
ST_GeomFromText: Jsor\Doctrine\PostGIS\Functions\ST_GeomFromText
ST_GeographyFromText: Jsor\Doctrine\PostGIS\Functions\ST_GeographyFromText
ST_Transform: Jsor\Doctrine\PostGIS\Functions\ST_Transform
ST_Point: Jsor\Doctrine\PostGIS\Functions\ST_Point
ST_SetSRID: Jsor\Doctrine\PostGIS\Functions\ST_SetSRID
ST_AsEWKT: Jsor\Doctrine\PostGIS\Functions\ST_AsEWKT
Create an entity:
namespace My\Entity;
class GeoLocation
{
protected $latitude;
protected $longitude;
protected $point;
public function __construct($latitude, $longitude)
{
$this->longitude = $longitude;
$this->latitude = $latitude;
$this->setPoint();
}
protected function setPoint()
{
$this->point = sprintf(
'POINT(%f %f)',
(string)$this->longitude,
(string)$this->latitude
);
}
public function getPoint()
{
return $this->point;
}
public function getLatitude()
{
return (float) $this->latitude;
}
public function getLongitude()
{
return (float) $this->longitude;
}
}
Add a mapping for your entity:
My\Entity\GeoLocation:
type: 'entity'
indexes:
idx_point:
columns:
- 'point'
flags:
- 'spatial'
id:
id:
type: integer
generator:
strategy: AUTO
fields:
longitude:
nullable: true
type: 'decimal'
precision: 9
scale: 6
latitude:
nullable: true
type: 'decimal'
precision: 8
scale: 6
point:
nullable: true
type: 'geography'
options:
geometry_type: 'POINT'
srid: 4326
Update your database schema:
app/console doctrine:schema:update [--force]
Now save some points ...
Finally use the postgis types in your doctrine repository:
public function findByDistanceFrom($latitude, $longitude, $distanceInMeters)
{
$qb = $this->createQueryBuilder('geolocation');
$pointFrom = 'Geography(ST_SetSRID(ST_Point(geolocation.longitude, geolocation.latitude), 4326))';
$pointTo = 'Geography(ST_SetSRID(ST_Point(:longitude, :latitude), 4326))';
$qb
->where( $qb->expr()->eq("ST_DWithin($pointFrom, $pointTo, :distance_in_meters)", $qb->expr()->literal(true) ) )
->setParameter('latitude', $latitude)
->setParameter('longitude', $longitude)
->setParameter('distance_in_meters', $distanceInMeters)
;
return $qb
->getQuery()
->getResult()
;
}

Symfony CMF - Uploading Images and PDF Files

I am using the Symfony CMF Media Bundle to achieve the following. I am having several nodes that can have an image and a downloadable PDF.
I have already figured out that the setImage method has to be implemented like that:
public function setPreviewImage($previewImage)
{
if ($previewImage === null) {
return $this;
}
if (!$previewImage instanceof ImageInterface && !$previewImage instanceof UploadedFile) {
$type = is_object($previewImage) ? get_class($previewImage) : gettype($previewImage);
throw new \InvalidArgumentException(sprintf(
'Image is not a valid type, "%s" given.',
$type
));
}
if ($this->previewImage) {
$this->previewImage->copyContentFromFile($previewImage);
} elseif ($previewImage instanceof ImageInterface) {
$previewImage->setName('previewImage');
$this->previewImage = $previewImage;
} else {
$this->previewImage = new Image();
$this->previewImage->copyContentFromFile($previewImage);
}
return $this;
}
Then in another forum someone was suggested to make this property cascade-persistent. with that hint: https://github.com/symfony-cmf/BlockBundle/blob/master/Resources/config/doctrine-phpcr/ImagineBlock.phpcr.xml#L22. Now i am wondering how and were i can set this option in my configuration.
The next part i am wondering about is the cmf_media_file type. Has anyone out here ever managed to store a PDF into a PHPCR node property?
For any help i would be really thankful.
I figured it out by myself.
For anyone who is using annotations you have to set it up like this:
use Symfony\Cmf\Bundle\MediaBundle\Doctrine\Phpcr\Image;
use Doctrine\ODM\PHPCR\Mapping\Annotations as PHPCR;
/**
* #var Image
* #PHPCR\Child(cascade="persist")
*/

Symfony PunkAveFileUploaderBundle > imagename before handleFileUpload

The Symfony2 PunkAve FileUpload Bundle works, but because of the returns inside the UploadHandler of BlueImp, it is not possible to get the filename.
<?php
/**
*
* #Route("/upload")
* #Template()
*/
public function uploadAction(Request $request)
{
$editId = $this->getRequest()->get('editId');
if (!preg_match('/^\d+$/', $editId))
{
throw new Exception("Bad edit id");
}
$em = $this->getDoctrine()->getManager();
$entity = $em->getRepository('Foobar:Foobar')->find($editId);
$destinationFolder = 'test';
$fileUploader = $this->get('punk_ave.file_uploader');
$imageName = $fileUploader->handleFileUpload(array('folder' => $destinationFolder ));
$imageEntity = new \Foobar\Entity\Image();
$imageEntity->setImage($imageName);
$imageEntity->setFolder($destinationFolder);
$em->persist($media);
$em->flush();
return true;
}
The example above uploads the image.
The variable $imageName triggers the fileUploadHandler. There is somewhere a return, why it doesn't go the the next lines where it should save the imagename.
How can I still get it working in Symfony? To save the filename in the Entity after he handled the upload?
As they said in documentation: handleFileUpload DOES NOT RETURN as the response is generated in native PHP by BlueImp's UploadHandler class. handleFileUpload has exit(0); at the end so when you call it then entire process stops there. If you want to save files to database you should do it in action which handles request (from documentation's example it will be editAction) and there, again as documentation said, use getFiles to get the list of filenames and mirror that in your database as you see fit.

Resources