Change dynamically validated_file_class in symfony 1.4 - forms

I have this model:
Banner:
columns:
filename: string(255)
url: string(255)
position:
type: enum
values: [top, right]
default: right
and this form:
class BannerForm extends BaseBannerForm
{
public function configure()
{
$this->widgetSchema['filename'] = new sfWidgetFormInputFileEditable(array(
'file_src' => $this->getObject()->getThumbURL(),
'is_image' => true,
'edit_mode' => $this->getObject()->exists()
));
$validated_file_class = $this->getObject()->position === 'right' ? 'bannerRightValidatedFile' : 'bannerTopValidatedFile';
$this->validatorSchema['filename'] = new sfValidatorFile(array(
'path' => sfConfig::get('sf_upload_dir'),
'mime_types' => 'web_images',
'validated_file_class' => $validated_file_class',
'required' => $this->getObject()->isNew()
));
}
}
I use different validate classes because inside it i incapsulate thumbnail operations, and the sizes of banners depends on it position field.
The problem is that $validated_file_class is always bannerRightValidatedFile class.
How i can achieve this thing ?

I can suggest 4 solutions which you can choose from:
Option 1:
You should add a update$fieldNameColumn method to the form class. In your case it should look like this:
// change validated file instance before calling save
protected function updateFilenameColumn($value)
{
if ($value instanceof sfValidatedFile)
{
$class = 'right' == $this->getValue('position') ? 'bannerRightValidatedFile' : 'bannerTopValidatedFile';
// this will not work as I thought at first time
// $this->getValidator('filename')->setOption('validated_file_class', $class);
$this->values['filename'] = new $class(
$value->getOriginalName(),
$value->getType(),
$value->getTempName(),
$value->getSize(),
$value->getPath()
);
return $this->processUploadedFile('filename');
}
return $value;
}
I think it's kind of hacky.
Option 2:
You should add a doctrine hook method to the model:
/**
* #param Doctrine_Event $event
*/
public function postSave($event)
{
$record = $event->getInvoker();
if (array_key_exists('filename', $record->getLastModified()))
{
// get the full path to the file
$file = sfConfig::get('sf_upload_dir') . '/' . $record->getFilename();
if (file_exists($file))
{
// resize the file e.g. with sfImageTransformPlugin
$img = new sfImage($file);
$img
->resize(100, 100)
->save();
}
}
}
This will work when creating records whitout a form e.g. when using fixtures.
Option 3:
Use the admin.save_object event.
public static function listenToAdminSaveObject(sfEvent $event)
{
$record = $event['object'];
if ($event['object'] instanceof Banner)
{
// use the same code as in the `postSave` example
}
}
Option 4:
Use the sfImageTransformExtraPlugin
It's kind of hard to setup and configure (and it's code is a mess :), but it makes possible to modify the size of the image whithout regenerating all the already resized ones.

You could add a sfCallbackValidator as a post-validator, and set the property accordingly.
Pseudo code (I don't have the exact function signatures at hand).
public function configure() {
// ...
$this->mergePostValidator(new sfCallbackValidator(array('callback' => array($this, 'validateFile'))));
}
public function validateFile($values) {
$realValidator = new sfValidatorFile(...);
return $realValidator->clean($values['field']);
}

If you can modify the call to the form class, you can do that:
$form = new BannerForm(array(), array('validated_file_class' => 'bannerRightValidatedFile');
$form2 = new BannerForm(array(), array('validated_file_class' => 'bannerTopValidatedFile');
And then in your form:
class BannerForm extends BaseBannerForm
{
public function configure()
{
$this->widgetSchema['filename'] = new sfWidgetFormInputFileEditable(array(
'file_src' => $this->getObject()->getThumbURL(),
'is_image' => true,
'edit_mode' => $this->getObject()->exists()
));
$this->validatorSchema['filename'] = new sfValidatorFile(array(
'path' => sfConfig::get('sf_upload_dir'),
'mime_types' => 'web_images',
'validated_file_class' => $this->options['validated_file_class'],
'required' => $this->getObject()->isNew()
));
}
}
Edit:
Since you are playing inside the admin gen, I think the best way is to use a postValidator like #Grad van Horck says.
Your validate class depend on an extra field. With a postvalidator, you can access any field inside the form. Then, you just need to create a little switch to handle the case for each position / validated class.
public function configure()
{
// ...
$this->mergePostValidator(new sfValidatorCallback(array('callback' => array($this, 'validateFile'))));
}
public function validateFile($validator, $values, $arguments)
{
$default = array(
'path' => sfConfig::get('sf_upload_dir'),
'mime_types' => 'web_images',
'required' => $this->getObject()->isNew()
);
switch ($values['position'] ) {
case 'right':
$validator = new sfValidatorFile($default + array(
'validated_file_class' => 'bannerRightValidatedFile',
));
break;
case 'top':
$validator = new sfValidatorFile($default + array(
'validated_file_class' => 'bannerTopValidatedFile',
));
default:
# code...
break;
}
$values['filename'] = $validator->clean($values['filename']);
return $values;
}

Related

Yii2: rest api model get data

I am using REST API in my project and everything works great. I describe a model using a model
<?php
namespace api\modules\v1\models;
use Carbon\Carbon;
use Yii;
class Comment extends \common\models\Comment
{
public function fields()
{
return [
'id',
'user' => function(Comment $model) {
return User::findOne($model->user_id);
},
'text',
'image' => function(Comment $model) {
return Yii::$app->params['link'].$model->image;
},
'created_at' => function(Comment $model) {
Carbon::setLocale(Yii::$app->language);
return Carbon::createFromTimeStamp(strtotime($model->created_at))->diffForHumans();
},
'children' => function(Comment $model) {
$comments = Comment::find()
->where(['comment_id' => $model->id]);
if (!$comments->exists()) {
return false;
}
return $comments->all();
},
'like',
'news_id',
'comment_id'
];
}
}
The data is returned in the specified format and that's great. But I need to send data to the controller using websockets. For example, when a new comment arrives, send it to all users.
$post = Yii::$app->request->post();
$image = UploadedFile::getInstanceByName('image');
$model = new \api\modules\v1\models\Comment([
'news_id' => $post['feed_id'],
'comment_id' => $post['comment_id'] ?? null,
'user_id' => Yii::$app->user->identity->id,
]);
$model->text = $model->findLinks($post['text']);
if ($image && !$image->error) {
if (!file_exists(Yii::$app->params['comment.pathAbsolute'])) {
if (!FileHelper::createDirectory(Yii::$app->params['comment.pathAbsolute'], 0777)) {
throw new \Exception('Помилка створення папки');
}
}
$serverName = Yii::$app->security->generateRandomString(16).'.'.$image->extension;
if ($image->saveAs(Yii::$app->params['comment.pathAbsolute'].$serverName)) {
$model->image = $serverName;
} else {
throw new \Exception($image->error);
}
}
if (!$model->save()) {
throw new \Exception($model->error());
}
Helper::ws(false, 'updateComment', ['feed_id' => $post['feed_id'], 'comment' => $model]);
And when I pass the $model, the data is passed as it is stored in the database. Is it possible to call a method or something so that the data is passed as I described in the model api?

Octobercms, redirect to home from component not working

i'm trying to redirect from component if id from slug is wrong.
Running from layout
function onBeforePageStart(){ $this->Contentloader->getMeta(); }
In component i have:
public function getMeta(){
//id checking logic goes here
if ($id == null) return Redirect::to('/'); }
Inspecting the dd(Redirect::to('/')) object I see
But it's not redirecting.
Please advice
Thanks
try this
in your component :
public function getMeta()
{
if ($id == null) return false;
}
in your layout :
function onBeforePageStart()
{
$meta = $this->Contentloader->getMeta();
if(!$meta)
return Redirect::to('/');
}
I hope help you :)
Components should be able to handle redirects without having onBeforePageStart(). This is just a quick example. Here I am checking to see if a component field is null. If it is null then return to '/'.
You can do this in a component: Make sure to utilize the Redirect class use Redirect;
public function defineProperties()
{
return [
'useSomething' => [
'title' => 'Something',
'description' => 'Testing Testing',
'default' => '',
'type' => 'text',
]
];
}
public function onRun()
{
if ($this->property('useSomething') == null) {
return Redirect::to('/');
} else {
$this->page['something'] = $this->property('useSomething');
}
}

How to order custom attribute in Datatable?

Say my User model's balance attribute from getBalanceAttribute() returns the sum of amount from user's Transaction model, how can this be orderable in the Datatable?
User.php
public function transactions()
{
return $this->hasMany(\App\Transaction::class);
}
public function getBalanceAttribute()
{
return $this->transactions()->sum('amount');
}
Transaction.php
public function user()
{
return $this->belongsTo(\App\User::class);
}
UserCrudController.php
...
public function setup()
{
...
$this->crud->addColumn(
[
'name' => "balance",
'label' => "Balance",
'type' => 'number',
// Here the column is clickable but is not actually sorted.
'orderable' => true,
],
...
}
Thank you in advance!
Unfortunately, Backpack cannot make a model_function column orderable, since model functions are called after the SQL has already gotten back.
Sorry.

Zend Framework 2 - Controller action return json

I submit my form through ajax and if there are errors return json with them. But, when action return json model are appear this error:
Inconsistent state; child view model is marked as terminal
That is my action:
$viewModel = new ViewModel();
$form = $sm->get('FormElementManager')->get('MyForm');
if ($request->isPost() && $request->isXmlHttpRequest()) {
$viewModel->setTerminal(true);
$jsonModel = new JsonModel();
try {
if ($sm->get('MyService')->myFunction($form, $request->getPost())) {
return $jsonModel->setVariables(array('var' => 'var1'));
}
} catch (\Exception $e) {
return $jsonModel->setVariables(
array('errorMessage' => $e->getMessage())
);
}
}
return $viewModel->setVariables(array('form' => $form));
That is my module.config.php
'view_manager' => array(
.....
'strategies' => array(
'ViewJsonStrategy',
),
)
You should not return your $viewModel but your $jsonModel.
By the way a JsonModel is set as terminal by default.
You should add return $jsonModel to your if clause.

Symfony2 Is it possible to redirect from other places(functions)?

Forgive my English.
I want to redirect not from action side but from a other function.
controller
public function editAction(Request $request, $id)
{
$em = $this->getDoctrine()->getEntityManager();
$article = $em->getRepository('MySampleBundle:Article')->find($id);
// TODO:
// Since other actions have the same processing,
// I would like to do check work in other function.
// And when inaccurate,
// I want to make it move from that function to other page.
$this->is_edit_granted($article);
$form = $this->createForm(new ArticleType(), $article);
if ($request->getMethod() == 'POST') {
$form->bindRequest($request);
if ($form->isValid()) {
// ...
}
}
return $this->render('MySampleBundle:Article:edit.html.twig', array(
'form' => $form->createView(),
'article' => $article,
));
}
public function is_edit_granted($article)
{
// TODO:
// I check not at the action side but at this place,
// and want to make it move from this function to other page.
if (!$article) {
throw $this->createNotFoundException('No article found.');
} else if ( $article->getAuthor() != $this->getUser()->getId() ) {
return $this->redirect( // doesn't work here
$this->generateUrl('home', array(
"id" => $article->getId()
))
);
}
}
I also tried similar code:
use Symfony\Component\HttpFoundation\RedirectResponse;
class SampleController extends Controller
{
// ...
public function editAction(Request $request, $id)
{
// ...
$this->is_edit_granted($article);
// ...
}
public function is_edit_granted($article)
{
if (!$article) {
throw $this->createNotFoundException('No article found.');
} else if ( $article->getAuthor() != $this->getUser()->getId() ) {
return new RedirectResponse(
$this->generateUrl('home', array(
"id" => $article->getId()
))
);
}
}
}
but it doesn't work.
It is performing in the environment of Symfony 2.1.2.
How can I manage to achieve that?
Or, is there any better method?
Do something like:
public function editAction(Request $request, $id)
{
// ...
$response = $this->is_edit_granted($article);
if ($response) return $response;
// ...
}
public function is_review_granted($article)
{
if (!$article) {
throw $this->createNotFoundException('No article found.');
} else if ( $article->getAuthor() != $this->getUser()->getId() ) {
return new RedirectResponse(
$this->generateUrl('home', array(
"id" => $article->getId()
))
);
}
return null;
}
It is not possible to redirect from the is_review_granted without returning the RedirectResponse form the editAction. So the answer of Carlos Granados is correct.
Another option would be to throw an AccessDeniedException in the is_review_granted method:
public function is_review_granted($article)
{
if (!$article) {
throw $this->createNotFoundException('No article found.');
} else if ( $article->getAuthor() != $this->getUser()->getId() ) {
throw new Symfony\Component\Security\Core\Exception\AccessDeniedException('no acces');
}
}
You could also look to some more in-depth solutions like ACL and SecurityVoters.