Using record value as Twig function parameter - php

I'm working on writing a Bolt extension to parse an XML file and return the deserialised object tree for the XML. The parsing and deserialising is working like a charm but I cannot get the filename from the page record working as a parameter for the Twig function.
Extension code (simplified) is:
class CompetitionExtension extends SimpleExtension
{
protected function registerTwigFunctions()
{
return [
'competition' => 'competitionFunction',
];
}
/**
* Load and parse the competition XML.
*
* #param string $filename
* #return Competition
*/
public function competitionFunction(string $filename) : Competition
{
$competition = null;
$loader = new FileLoader($filename);
if ($loader->openFile()) {
$competition = $loader->parse();
}
return $competition;
}
}
The contenttype (extended the homepage) addition is:
competitionxml:
type: file
upload: competitions
group: content
Upload of the file is also no problem but the following Twig template code gives an error:
{% set xmlfile = homepage.competitionxml %}
{% set comp = competition(xmlfile) %}
Using dump I see that xmlfile has the correct value. But I get the following error:
An exception has been thrown during the rendering of a template ("Notice: Undefined variable: filename") in "index.twig" at line 27.
So how can I use the filename of the attachment as parameter for the Twig function?

Related

Getting twig partial markup in controller throws error?

I'm trying to get rendered twig content in a plugin controller. I'm using CmsCompoundObject to load a partial but it's throwing an error.
$theme = Theme::getActiveThemeCode();
$markup = CmsCompoundObject::load($theme, "partials/test.htm")->getTwigContent();
The from property is invalid, make sure that Cms\Classes\CmsCompoundObject has a string value for its $dirName property (use '' if not using directories)
../october/rain/src/Halcyon/Builder.php line 309
Looking at the code, this is where it's throwing the error:
/**
* #return array
*/
protected function runSelect()
{
if (!is_string($this->from)) {
throw new ApplicationException(sprintf("The from property is invalid, make sure that %s has a string value for its \$dirName property (use '' if not using directories)", get_class($this->model)));
}
if ($this->selectSingle) {
list($name, $extension) = $this->selectSingle;
return $this->datasource->selectOne($this->from, $name, $extension);
}
}
$theme is a string and not empty, it properly gets the current theme so I'm not sure why it's saying invalid.
OctoberCMS Slack channel helped me find the solution.
First was to use Partial instead of CmsCompoundObject and the 2nd was to remove "partials/" from the path name as the Partial class already knows to look in that directory.
$theme = Theme::getActiveTheme();
$markup = Partial::load($theme, "test.htm")->getTwigContent();

Mustache.php with JSON data throws Catchable fatal error

I've found out you can convert JSON file to PHP arrays and objects and use that output as a data for Mustache.php template engine like this example:
PHP Script:
require_once('Mustache/Autoloader.php');
Mustache_Autoloader::register();
$mustache = new Mustache_Engine();
$data_json = file_get_contents('data.json');
$data = json_decode( $data_json, true );
$template = $mustache -> loadTemplate('templates/template.html');
echo $mustache -> render( $template, $data );
JSON data:
{
"persons": [{
"person": {
"name": "Homer",
"number": 0
},
"person": {
"name": "Marge",
"number": 1
}
}]
}
Mustache template:
{{{# persons }}}
{{{# person }}}
<ul>
<li>Name: {{name}}</li>
<li>Number: {{number}}</li>
</ul>
{{{# person }}}
{{{/ persons }}}
But PHP throws this error:
Catchable fatal error:
Object of class __Mustache_12af6f5d841b135fc7bfd7d5fbb25c9e could not be converted to string in C:\path-to-mustache-folder\Engine.php on line 607
And this is where PHP points that error came from (Inside Engine.php file in above error):
/**
* Helper method to generate a Mustache template class.
*
* #param string $source
*
* #return string Mustache Template class name
*/
public function getTemplateClassName($source)
{
return $this->templateClassPrefix . md5(sprintf(
'version:%s,escape:%s,entity_flags:%i,charset:%s,strict_callables:%s,pragmas:%s,source:%s',
self::VERSION,
isset($this->escape) ? 'custom' : 'default',
$this->entityFlags,
$this->charset,
$this->strictCallables ? 'true' : 'false',
implode(' ', $this->getPragmas()),
$source
));
}
I just know somehing is wrong in data conversation but I'm not familiar to PHP debugging and this is for experimental use, I appreciate if you could tell me what is wrong.
The $template argument of Mustache_Engine::render must be a string; however Mustache_Engine::loadTemplate returns an instance of the Mustache_Template class (which Mustache subsequently tries to treat as a string, which fails).
You should be able to invoke the render(...) method on the template object instead (untested, though):
$template = $mustache->loadTemplate(...);
$renderedContent = $template->render($data);
I'm not that familiar with Mustache, but according to the documentation, by default loadTemplate needs to be invoked with the template string, and not a template file name. Consider also configuring a FileSystemLoader for loading your templates:
$mustache = new Mustache_Engine(array(
'loader' => new Mustache_Loader_FilesystemLoader($pathToTemplateDir),
));
I think mistake is here:
{{{# person }}}
{{{/ persons }}}
Try:
{{{#persons}}}
{{{#person}}}
<ul>
<li>Name: {{name}}</li>
<li>Number: {{number}}</li>
</ul>
{{{/person}}}
{{{/persons}}}

Creating Functions in Twig not working

I am attempting to convert some PHP code so that it uses Twig for its template engine.
The code I developed previously worked in such a manner that generating a HTML dropdown list involved calling a static method (formTools::generateSchoolList()) which queried a database and then echo the results into the HTML (spaghetti code).
I've been having some issues creating and then displaying the result of functions and I'm hoping someone can help me troubleshoot the errors I am receiving. In formTools.class.php, I am referring to a single static method for this example and although the Twig Docs suggested using an anonymous method (I called the static function above within the anonymous function), I wasn't able to get that to work, so I made the Twig_SimpleFunction callable parameter the static method directly.
If I do that I get (using return within the static method):
Parse error: syntax error, unexpected '<' in /Path_To_Vendor/vendor/twig/twig/lib/Twig/Environment.php(332) : eval()'d code on line 46
Fatal error: Class '__TwigTemplate_96fa0bd50202e1016defd78dc63d0ee7f8c3432728ffb0946f67a6a1e5c89437' not found in /Path_To_Vendor/vendor/twig/twig/lib/Twig/Environment.php on line 346
If I echo within static function, I get:
Fatal error: Call to undefined method Twig_SimpleFunction::compile() in /path_to_vendor/vendor/twig/twig/lib/Twig/Node/Expression/Call.php on line 27
formTools.class.php
/**
* Generates a list of all available schools in a dropdown box
*
* #throws Exception
*/
static function generateSchoolList() {
$db = new database(databaseHost, databaseUser, databasePass, databaseName);
$query = "SELECT * FROM `schoolCodes`";
$codes = $db->query($query);
if (isset($codes)) {
$result = "<select name='schoolCode'>";
$result .= "<option class='dropdown' value=''> </option>";
$result .= "<option class='dropdown' disabled>----</option>";
foreach ($codes as $code) {
$result .= "<option class='dropdown' value='{$code['codeID']}'>{$code['schoolName']} ({$code['code']})</option>";
}
$result .= "</select>";
**echo/return** $result;
} else {
throw new Exception("The schoolCodes table is empty.");
}
}
formviewer.php
require_once('../vendor/autoload.php');
$ft = new formTools();
$it = new ingestTasks();
Twig_Autoloader::register();
$loader = new Twig_Loader_Filesystem('../templates');
$twig = new Twig_Environment($loader);
$function = new Twig_SimpleFunction('generateSchoolList',
formTools::generateSchoolList()
);
$twig->addFunction($function);
$template = $twig->loadTemplate('viewPlayersByRoster.twig');
echo $twig->render($template);
viewPlayersByRoster.twig
{% extends "base.twig" %}
{% block title %}View Players by Roster{% endblock %}
{% block content %}
<p class="formDesc">This form is for viewing all players on a given roster.</p>
<form action="formviewer.php?action=viewPlayersByRoster" target="_self" method="post">
<input hidden="hidden" value="viewPlayersByRoster" name="action" title="action"/>
<table>
<tr>
<td class="rightAlignText"><label for="schoolCode">School:</label></td>
<td>{{ generateSchoolList() }}</td>
</tr>
</table>
</form>
{% endblock %}
EDIT:
Using PHP 5.5.24 (cannot update past this), Twig 1.18.2 and using Composer.
composer.json
"require": {
"twig/twig": "v1.18.2"
},
"autoload": {
"classmap": ["libs/"]
}
EDIT 2:
Changed to what tacone said and got this:
Catchable fatal error: Object of class __TwigTemplate_96fa0bd50202e1016defd78dc63d0ee7f8c3432728ffb0946f67a6a1e5c89437 could not be converted to string in /path_to_vendor/vendor/twig/twig/lib/Twig/Loader/Filesystem.php on line 216
/Filesystem.php
protected function normalizeName($name)
{
return preg_replace('#/{2,}#', '/', strtr((string) $name, '\\', '/'));
}
formTools.class.php
public function __toString() {
return $this->formTools;
}
You are executing the static method instead of passing its coordinates.
Try using PHP classical callback syntax:
$function = new Twig_SimpleFunction('generateSchoolList', ['formTools', 'generateSchoolList']);
$twig->addFunction($function);
See also https://stackoverflow.com/a/29629460/358813

Zend: Generating partial in command line

I have set of class that send mails to my coworkers, while specific event happens. I had no problem with this so far, until today. I have to run some code from CLI (target - cron) to send daily reports. As you might suspect - no controller nor view is involved.
My code:
/**
* Returns mail content
*
* #param Crm_Mail_Abstract_AlertMail $object
* #return string
* #throws Exception
*/
private function generateContent(Crm_Mail_Abstract_AlertMail $object)
{
$subContent = $object->getInnerContent();
if (!$subContent) {
throw new Exception('Cannot load subcontent');
}
$view = Zend_Layout::getMvcInstance()->getView();
$content = $view->partial($this->mainLayout, array('content' => $subContent));
return $content;
}
And the error I get:
PHP Fatal error: Call to a member function getView() on a non-object
in /home/..../library/Crm/Mail/AlertMail.php on line 195
So... how to render partials while in terminal?
According to This : http://framework.zend.com/manual/1.12/en/zend.layout.quickstart.html
// Returns null if startMvc() has not first been called
$layout = Zend_Layout::getMvcInstance();
So, if you didn't added this :
// In your bootstrap:
Zend_Layout::startMvc();
You will not access this -
Zend_Layout::getMvcInstance()->getView()
So try do this something like that:
Zend_Layout::startMvc();
Zend_Layout::getMvcInstance()->getView();

Is there any way to compile a blade template from a string?

How can I compile a blade template from a string rather than a view file, like the code below:
<?php
$string = '<h2>{{ $name }}</h2>';
echo Blade::compile($string, array('name' => 'John Doe'));
?>
http://paste.laravel.com/ujL
I found the solution by extending BladeCompiler.
<?php namespace Laravel\Enhanced;
use Illuminate\View\Compilers\BladeCompiler as LaravelBladeCompiler;
class BladeCompiler extends LaravelBladeCompiler {
/**
* Compile blade template with passing arguments.
*
* #param string $value HTML-code including blade
* #param array $args Array of values used in blade
* #return string
*/
public function compileWiths($value, array $args = array())
{
$generated = parent::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;
}
}
Small modification to the above script.
You can use this function inside any class without extending the BladeCompiler class.
public function bladeCompile($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;
}
For anyone still interested in this, they've added it to Laravel 9
use Illuminate\Support\Facades\Blade;
return Blade::render('Hello, {{ $name }}', ['name' => 'Julian Bashir']);
https://laravel.com/docs/9.x/blade#rendering-inline-blade-templates
I just stumbled upon the same requirement! For me, i had to fetch a blade template stored in DB & render it to send email notifications.
I did this in laravel 5.8 by kind-of Extending \Illuminate\View\View. So, basically i created the below class & named him StringBlade (I couldn't find a better name atm :/)
<?php
namespace App\Central\Libraries\Blade;
use Illuminate\Filesystem\Filesystem;
class StringBlade implements StringBladeContract
{
/**
* #var Filesystem
*/
protected $file;
/**
* #var \Illuminate\View\View|\Illuminate\Contracts\View\Factory
*/
protected $viewer;
/**
* StringBlade constructor.
*
* #param Filesystem $file
*/
public function __construct(Filesystem $file)
{
$this->file = $file;
$this->viewer = view();
}
/**
* Get Blade File path.
*
* #param $bladeString
* #return bool|string
*/
protected function getBlade($bladeString)
{
$bladePath = $this->generateBladePath();
$content = \Blade::compileString($bladeString);
return $this->file->put($bladePath, $content)
? $bladePath
: false;
}
/**
* Get the rendered HTML.
*
* #param $bladeString
* #param array $data
* #return bool|string
*/
public function render($bladeString, $data = [])
{
// Put the php version of blade String to *.php temp file & returns the temp file path
$bladePath = $this->getBlade($bladeString);
if (!$bladePath) {
return false;
}
// Render the php temp file & return the HTML content
$content = $this->viewer->file($bladePath, $data)->render();
// Delete the php temp file.
$this->file->delete($bladePath);
return $content;
}
/**
* Generate a blade file path.
*
* #return string
*/
protected function generateBladePath()
{
$cachePath = rtrim(config('cache.stores.file.path'), '/');
$tempFileName = sha1('string-blade' . microtime());
$directory = "{$cachePath}/string-blades";
if (!is_dir($directory)) {
mkdir($directory, 0777);
}
return "{$directory}/{$tempFileName}.php";
}
}
As you can already see from the above, below are the steps followed:
First converted the blade string to the php equivalent using \Blade::compileString($bladeString).
Now we have to store it to a physical file. For this storage, the frameworks cache directory is used - storage/framework/cache/data/string-blades/
Now we can ask \Illuminate\View\Factory native method 'file()' to compile & render this file.
Delete the temp file immediately (In my case i didn't need to keep the php equivalent file, Probably same for you too)
And Finally i created a facade in a composer auto-loaded file for easy usage like below:
<?php
if (! function_exists('string_blade')) {
/**
* Get StringBlade Instance or returns the HTML after rendering the blade string with the given data.
*
* #param string $html
* #param array $data
* #return StringBladeContract|bool|string
*/
function string_blade(string $html, $data = [])
{
return !empty($html)
? app(StringBladeContract::class)->render($html, $data)
: app(StringBladeContract::class);
}
}
Now i can call it from anywhere like below:
<?php
$html = string_blade('<span>My Name is {{ $name }}</span>', ['name' => 'Nikhil']);
// Outputs HTML
// <span>My Name is Nikhil</span>
Hope this helps someone or at-least maybe inspires someone to re-write in a better way.
Cheers!
I'm not using blade this way but I thought that the compile method accepts only a view as argument.
Maybe you're looking for:
Blade::compileString()
It's a old question. But I found a package which makes the job easier.
Laravel Blade String Compiler renders the blade templates from the string value. Check the documentation on how to install the package.
Here is an example:
$template = '<h1>{{ $name }}</h1>'; // string blade template
return view (['template' => $template], ['name' => 'John Doe']);
Note: The package is now updated to support till Laravel 6.
I know its pretty old thread, but today also requirement is same.
Following is the way I solved this on my Laravel 5.7 (but this will work with any laravel version greater than version 5), I used the knowledge gained from this thread and few other threads to get this working (will leave links to all threads at the end, if this help up-vote those too)
I added this to my helper.php (I used this technique to add helper to my project, but you can use this function directly as well)
if (! function_exists('inline_view')) {
/**
* Get the evaluated view contents for the given blade string.
*
* #param string $view
* #param array $data
* #param array $mergeData
* #return \Illuminate\View\View|\Illuminate\Contracts\View\Factory
*/
function inline_view($view = null, $data = [], $mergeData = [])
{
/* Create a file with name as hash of the passed string */
$filename = hash('sha1', $view);
/* Putting it in storage/framework/views so that these files get cleared on `php artisan view:clear*/
$file_location = storage_path('framework/views/');
$filepath = storage_path('framework/views/'.$filename.'.blade.php');
/* Create file only if it doesn't exist */
if (!file_exists($filepath)) {
file_put_contents($filepath, $view);
}
/* Add storage/framework/views as a location from where view files can be picked, used in make function below */
view()->addLocation($file_location);
/* call the usual view helper to render the blade file created above */
return view($filename, $data, $mergeData);
}
}
Usage is exactly same as laravel's view() helper, only that now first parameter is the blade string
$view_string = '#if(strlen($name_html)>6)
<strong>{{ $name_html }}</strong>
#else
{{$name_html}}
#endif';
return inline_view($view_string)->with('name_html', $user->name);
return inline_view($view_string, ['name_html' => $user->name]);
References:
https://stackoverflow.com/a/31435824/4249775
https://stackoverflow.com/a/33594452/4249775
Laravel 9 :
use Illuminate\Support\Facades\Blade;
return Blade::render('Your Blade Content {{ $parameter1}}', ['parameter1' => 'Name']);

Categories