Laravel Blade - User Allowed to Input Variable - php

I'm making an admin setting section of my laravel 5.2 app using the storage package from thetispro/laravel5-setting.
I'd like my admin users to be able to update email copy that get sent out to the user, but some of the emails include variables such as the users name. "Thanks for shopping with us, CUSTOMER NAME".
I can easily store the following in a setting, but when blade outputs it it just prints it out as a string instead of a variable. I've tried escaped and nonescaped the characters with {{}} and {{!! !!}. Here's what I have:
Email message an admin user can edit:
<h2>Hi, {{ $user->name }}</h2>
<p>Welcome to my web app</p>
In my view I have:
{!! Setting::get('emailuserinvite') !!}
<br /><br />
<!-- Testing both escaped and nonescaped versions -->
{{ Setting::get('emailuserinvite') }}
What blade renders is just:
echo "<h2>Hi, {{ $user->name }}</h2>
<p>Welcome to my web app</p>";
I was trying to make a custom blade directive that could close the echo, display the variable and open the echo back up, but that doesn't seem to be working correctly either.
// AppServiceProvider
Blade::directive('echobreak', function ($expression) {
// echo "my string " . $var . " close string";
$var = $expression;
return "' . $var . '";
});
// Admin user settings
Hi #echobreak($user->name)
Welcome to my web app
Any advice would be appreciated! Thanks.
Update
I mocked up a simple test case using #abdou-tahiri's example but I'm still getting errors with the eval()'d code.
ErrorException in SettingController.php(26) : eval()'d code line 1: Undefined variable: user
And here is my simple controller:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Requests;
use Blade;
class SettingController extends Controller
{
public function index() {
$user = [
"fname" => "Sam",
"lname" => "yerkes"];
$str = '{{ $user }}';
return $this->bladeCompile($str, $user);
}
private function bladeCompile($value, array $args = [])
{
$generated = \Blade::compileString($value);
ob_start() and extract($args, EXTR_SKIP);
try {
eval('?>'.$generated);
}
catch (\Exception $e) {
ob_get_clean(); throw $e;
}
$content = ob_get_clean();
return $content;
}
}

You may need to compile the string using Blade , check this helper function :
function blade_compile($value, array $args = array())
{
$generated = \Blade::compileString($value);
ob_start() and extract($args, EXTR_SKIP);
// We'll include the view contents for parsing within a catcher
// so we can avoid any WSOD errors. If an exception occurs we
// will throw it out to the exception handler.
try
{
eval('?>'.$generated);
}
// If we caught an exception, we'll silently flush the output
// buffer so that no partially rendered views get thrown out
// to the client and confuse the user with junk.
catch (\Exception $e)
{
ob_get_clean(); throw $e;
}
$content = ob_get_clean();
return $content;
}
so in your view file :
{!! blade_compile(Setting::get('emailuserinvite'),compact('user')) !!}
Check this Is there any way to compile a blade template from a string?

<h2>Hi, $user->name</h2>
<p>Welcome to my web app</p>
Is this what you are trying to do?
<h2>Hi, {{$user->name}}</h2>
<p>Welcome to my web app</p>

Related

Issue with updating twig global variables

Hello I am fairly new to developing with PHP and Twig and have encountered a problem when updating global variables in twig. I though it would be nice to have a flash / error message global variable that I could display to a user if they have entered incorrect input (ex. a login screen).
At the moment I am using the session in php for this flash message. When the flash message is updated it should also be updated in Twig. But it does not update until I reload the page. At this point the flash message may be changed. I thought the problem may have been in PHP so I echoed the flash variable in my code before the Twig template was rendered and the flash variable was updated in that respective echo statement. I want to stress that Twig does update the flash message, but it does not until the page is loaded again. So it never has the current flash message.
I have written a short program to demonstrate this. If the user presses button "one" then the flash message should be "message one" and if they press button "two" the flash message should be "message two". I have included both an echo of the flash message in php and the flash message in the Twig template.
index.php
<?php
session_start();
require_once '../PhotoBlog/utilities/twig/vendor/autoload.php';
$loader = new Twig_Loader_Filesystem('view');
$twig = new Twig_Environment($loader, array('cache' => false));
$twig->addGlobal('session', $_SESSION);
if (isset($_GET["test_one"])) {
$_SESSION["flash"] = "message one";
}else if(isset($_GET["test_two"])) {
$_SESSION["flash"] = "message two";
}
echo "PHP says: ".$_SESSION["flash"]."<br><br>";
echo $twig->render('index.html');
?>
index.html
<form action="index.php" method="GET">
<input type="submit" name="test_one" value="one">
<input type="submit" name="test_two" value="two">
</form>
<p>Twig says: {{ session.flash }}</p>
Ideally the messages should match each other but the Twig message always prints the previous message.
But Twig will always output the second to last submission. I can't seem to get my head around this. I have looked through the Twig docs and through stackoverflow and haven't found any solutions. I turned caching off so I think I have ruled that out but maybe I am missing something here.
Array's are passed by value by default in PHP, unless otherwise specified in the signature of the method (function addGlobal($key, $value) vs function addGlobal($key, &$value) {}).
If you really wanted to update the flash messages you'd need to switch up to an object to solve this.
<?php
class Foo {
protected $array = [];
public function addGlobal($key, $value) {
$this->array[$key] = $value;
return $this;
}
public function getGlobal($key) {
return $this->array[$key] ?? null;
}
}
class Bar {
protected $value;
public function setValue($value) {
$this->value = $value;
return $this;
}
public function getValue() {
return $this->value;
}
}
$foo = new Foo();
$bar = new Bar();
$bar->setValue('foobar');
$array = [ 'bar' => 'foobar', ];
$foo->addGlobal('array', $array);
$foo->addGlobal('object', $bar);
$array['bar'] = 'bar';
$bar->setValue('bar');
var_dump($foo->getGlobal('object')->getValue()); /** Output: bar **/
var_dump($foo->getGlobal('array')['bar']); /** Output: foobar **/
demo

Is it possible to have Twig force render broken bracket variables {{ example } instead of throwing an exception?

I am using Twig to render customer generated templates. If a customer misses a closing bracket:
Currently, twig throws a \Twig_Error and returns a message:
Unexpected "}" in "custom-template-render5b91be77b99485.95837275" at line 2.
Would it be possible to have Twig render as many variables as it can get (presumably the beginning ones in the template) and then just abandon after getting rogue brackets?
$parameters = [
'mine' => 'sentence',
'variable' => 'broken',
'another' => 'broken',
];
$templateString = "Here is my {{ mine }} with a broken {{ variable } and {{ another }}";
echo $twigEngine->render($templateString, $parameters);
The above shows a missing closing second bracket. It would be great if the output was:
"Here is my sentence with a broken {{ variable } and {{ another }}"
Any ideas would be appreciated.
Validate your user input before saving it
$error = null;
try {
$twigEngine->render($templateString, $parameters);
}catch(\Exception $e) {
$error = $e->getMessage();
}
if ($error) {
//display error
}else{
//save customer template
}

Laravel use a controller in blade

I just start to learning Laravel, I stuck in a part, I want to get data from database and pass it to a blade (view) file then use this view in another view file, and want to pass one variable to a controller but I got this error:
"Class 'adsController' not found"
web.php
Route::get('seller/panel', 'sellerController#panel');
panel.blade.php
#include('seller\hook\ads')
sellerController.php
public function panel(){
$ads = DB::table('ads')->get();
return view('hook\ads', ['ads' => $ads]);
}
adsController.php
class adsController extends Controller {
public function getStatus($p) {
if ($p == 'something') {
$status = 'yeah';
} else {
$status = 'blah blahe';
}
return view('hook\ads', $status);
}
}
ads.blade.php
<div class="ads">
#foreach ($ads as $ad)
{{ $ad->name }}
{{ adsController::getStatus($ad->approved) }}
#endforeach
</div>
So, as you see I am tring to get data from database in sellerController.php then pass it to ads.blade.php then I want to use adsController.php 's function in ads.blade.php, but it can't find adsController
Sorry I am newbie to laravel
As everyone said, it's not recommended to call the controller from the view.
Here's what I would do :
In your model :
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Ad extends Model{
public function getStatus()
{
return $this->approved == 'something' ? 'yeah' : 'blah blaehe';
}
}
In your view :
<div class="ads">
#foreach ($ads as $ad)
{{ $ad->name }}
#include('hook.ads', ['status' => $ad->getStatus()])
#endforeach
</div>
In your controller :
public function panel(){
$ads = \App\Ad::all();
return view('hook\ads', ['ads' => $ads]);
}
At the beginning of the blade file you could include the following
#php
use App\Http\Controllers\adsController as adsController;
#endphp
then on your blade template you could use the controller as you have used here.
But it is a really bad habit to use one directly.
Since you are a newbie to Laravel change that coding practice.
You could use a service provider of a sort if you want that data which needs to be shown on a particular blade view all the time.
You should try this despite I don't recommend the approach you are trying to achieve.
{!! app('App\Http\Controllers\adsController')->getStatus($ad->approved) !!}
It should work but that's very wrong.
Most often when you get this error when your namespace declaration in the controller is wrong. Typical scenario is where you generate controller stub with Artisan and then move it somewhere else from the default location.
In some cases you need to run:
composer dumpautoload
To generate new optimized class loader map.

Laravel Extending Blade Templates - error in $errors collection

I am using Laravel 5.3. My first Laravel project/learning experience
In my blade file I use the following snippet to show the errors below a field after a PUT or POST request.
In this case the database field is called firstName
#if ($errors->has('firstName'))
<span class="help-block">
<strong>{{ $errors->first('firstName') }}</strong>
</span>
#endif
Now since I have lot of fields, I have keep repeatings this block for every field. I looked up the Laravel docs on Blade templates (Extending Blade section) and thought I could do the following in the AppServiceProvider class (AppServiceProvider .php)
public function boot()
{
//
Blade::directive('showIfError', function($fieldName) {
if ($errors->has('$fieldName')) {
echo "<span class='help-block'>
<strong> $errors->first('$fieldName') </strong>
</span>";
}
});
}
and then use
#showIfError('firstName')
But no luck...I get the error 'Undefined variable: errors'
Looks like the Laravel error collection is not accessible in this view file.
Appreciate any help. Thanks.
This is late reply, but hopefully it will help another person who comes along. A custom blade directive is supposed to return a string php code that will be evaluated when the template is rendered. Because the $errors variable is only available when the response is made, it won't work trying to evaluate it in the directive. The solution is this :
// custom blade directive to render the error block if input has error
// put this inside your service provider's boot method
\Blade::directive('errorBlock', function ($input) {
return
'<?php if($errors->has('.$input.')):?>
<div class=\'form-control-feedback\'>
<i class=\'icon-cancel-circle2\'></i>
</div>
<span class=\'help-block\'>
<strong><?php echo $errors->first('.$input.') ?></strong>
</span>
<?php endif;?>';
});
The thing is $errors in not accessible in closure. Also, you can't pass whole object as directive closure accepts string only. With simple data you can implode() and then explode() it, but not with an object or a collection.
What you can do is to create $errors manually inside the closure.
I've tested it and it works as expected:
Blade::directive('showIfError', function($fieldName) {
$errors = session('errors');
if ($errors->has($fieldName)) {
return "<span class='help-block'>".$errors->first($fieldName)."</span>";
}
});
The issue is that the $errors variable is only available within the views. If you take a look at the Middleware that shares the variable (https://github.com/laravel/framework/blob/5.0/src/Illuminate/View/Middleware/ShareErrorsFromSession.php) you will see that it is stored in the session.
So you can access it as follows:
$errors = session()->get('errors');
Note in your example you do have a couple of other issues; the $fieldName variable should not be in quotes. Eg:
public function boot() {
Blade::directive('showIfError', function($fieldName) {
$errors = session()->get('errors');
if ($errors->has($fieldName)) {
echo "<span class='help-block'> <strong>". $errors->first($fieldName). "</strong> </span>";
}
});
}
I finally wrote a PHP function inside my view and call it with various field names.
I hope this is a good approach. Not sure what is the best way to implement this.
function showIfError($fieldName)
{
$errors=session('errors');
if ( count( $errors)>0) {
if (session('errors')->has($fieldName)) {
$msg=$errors->first($fieldName);
echo '<span class="help-block">
<strong>'. $msg.' </strong>
</span>';
}
}
}

Pass a custom message (or any other data) to Laravel 404.blade.php

I am using Laravel 5, and I have created a file 404.blade.php in
views/errors/404.blade.php
This file gets rendered each time I call:
abort(404); // alias of App::abort(404);
How can I pass a custom message? Something like this in 404.blade.php
Sorry, {{ $message }}
Filled by (example):
abort(404, 'My custom message');
or
abort(404, array(
'message' => 'My custom message'
));
In Laravel 4 one could use App::missing:
App::missing(function($exception)
{
$message = $exception->getMessage();
$data = array('message', $message);
return Response::view('errors.404', $data, 404);
});
(Note: copied from my answer here.)
In Laravel 5, you can provide Blade views for each response code in the /resources/views/errors directory. For example a 404 error will use /resources/views/errors/404.blade.php.
What's not mentioned in the manual is that inside the view you have access to the $exception object. So you can use {{ $exception->getMessage() }} to get the message you passed into abort().
Extend Laravel's Exception Handler, Illuminate\Foundation\Exceptions\Handler, and override renderHttpException(Symfony\Component\HttpKernel\Exception\HttpException $e) method with your own.
If you haven't run php artisan fresh, it will be easy for you. Just edit app/Exceptions/Handler.php, or create a new file.
Handler.php
<?php namespace App\Exceptions;
use Exception;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Symfony\Component\HttpKernel\Exception\HttpException;
class Handler extends ExceptionHandler {
// ...
protected function renderHttpException(HttpException $e) {
$status = $e->getStatusCode();
if (view()->exists("errors.{$status}")) {
return response()->view("errors.{$status}", compact('e'), $status);
}
else {
return (new SymfonyDisplayer(config('app.debug')))->createResponse($e);
}
}
}
And then, use $e variable in your 404.blade.php.
i.e.
abort(404, 'Something not found');
and in your 404.blade.php
{{ $e->getMessage() }}
For other useful methods like getStatusCode(), refer Symfony\Component\HttpKernel\Exception
How about sharing a variable globally?
view()->share('message', 'llnk has gone away');
// or using the facade
View::share('message', 'llnk has gone away badly');
Just make sure in the template to fallback to a default in case you forget to set it.
See sharing data with views: http://laravel.com/docs/5.0/views

Categories