| 
<?php
 /**
 * Generator.php - Jaxon code generator
 *
 * Generate HTML, CSS and Javascript code for Jaxon.
 *
 * @package jaxon-core
 * @author Thierry Feuzeu <[email protected]>
 * @copyright 2016 Thierry Feuzeu <[email protected]>
 * @license https://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
 * @link https://github.com/jaxon-php/jaxon-core
 */
 
 namespace Jaxon\Plugin\Code;
 
 use Jaxon\Plugin\Code\Contracts\Generator as GeneratorContract;
 use Jaxon\Utils\Template\Engine as TemplateEngine;
 use Jaxon\Utils\Http\URI;
 
 class Generator
 {
 use \Jaxon\Features\Config;
 use \Jaxon\Features\Minifier;
 
 /**
 * Default library URL
 *
 * @var string
 */
 const JS_LIB_URL = 'https://cdn.jsdelivr.net/gh/jaxon-php/[email protected]/dist';
 
 /**
 * The objects that generate code
 *
 * @var array<GeneratorContract>
 */
 protected $aGenerators = [];
 
 /**
 * The Jaxon template engine
 *
 * @var TemplateEngine
 */
 protected $xTemplateEngine;
 
 /**
 * The constructor
 *
 * @param TemplateEngine        $xTemplateEngine      The template engine
 */
 public function __construct(TemplateEngine $xTemplateEngine)
 {
 $this->xTemplateEngine = $xTemplateEngine;
 }
 
 /**
 * Get the correspondances between previous and current config options
 *
 * @return array
 */
 private function getOptionVars()
 {
 return [
 'sResponseType'             => 'JSON',
 'sVersion'                  => $this->getOption('core.version'),
 'sLanguage'                 => $this->getOption('core.language'),
 'bLanguage'                 => $this->hasOption('core.language') ? true : false,
 'sRequestURI'               => $this->getOption('core.request.uri'),
 'sDefaultMode'              => $this->getOption('core.request.mode'),
 'sDefaultMethod'            => $this->getOption('core.request.method'),
 'sCsrfMetaName'             => $this->getOption('core.request.csrf_meta'),
 'bDebug'                    => $this->getOption('core.debug.on'),
 'bVerboseDebug'             => $this->getOption('core.debug.verbose'),
 'sDebugOutputID'            => $this->getOption('core.debug.output_id'),
 'nResponseQueueSize'        => $this->getOption('js.lib.queue_size'),
 'sStatusMessages'           => $this->getOption('js.lib.show_status') ? 'true' : 'false',
 'sWaitCursor'               => $this->getOption('js.lib.show_cursor') ? 'true' : 'false',
 'sDefer'                    => $this->getOption('js.app.options', ''),
 ];
 }
 
 /**
 * Render a template in the 'plugins' subdir
 *
 * @param string    $sTemplate      The template filename
 * @param array     $aVars          The template variables
 *
 * @return string
 */
 private function _render($sTemplate, array $aVars = [])
 {
 $aVars['sJsOptions'] = $this->getOption('js.app.options', '');
 return $this->xTemplateEngine->render("jaxon::plugins/$sTemplate", $aVars);
 }
 
 /**
 * Add a new generator to the list
 *
 * @param GeneratorContract     $xGenerator     The code generator
 * @param integer               $nPriority      The desired priority, used to order the plugins
 *
 * @return void
 */
 public function addGenerator(GeneratorContract $xGenerator, $nPriority)
 {
 while(isset($this->aGenerators[$nPriority]))
 {
 $nPriority++;
 }
 $this->aGenerators[$nPriority] = $xGenerator;
 // Sort the array by ascending keys
 ksort($this->aGenerators);
 }
 
 /**
 * Generate a hash for all the javascript code generated by the library
 *
 * @return string
 */
 private function getHash()
 {
 $sHash = jaxon()->getVersion();
 foreach($this->aGenerators as $xGenerator)
 {
 $sHash .= $xGenerator->getHash();
 }
 return md5($sHash);
 }
 
 /**
 * Get the HTML tags to include Jaxon CSS code and files into the page
 *
 * @return string
 */
 public function getCss()
 {
 $sCssCode = '';
 foreach($this->aGenerators as $xGenerator)
 {
 $sCssCode = rtrim($sCssCode, " \n") . "\n" . $xGenerator->getCss();
 }
 return rtrim($sCssCode, " \n") . "\n";
 }
 
 /**
 * Get the HTML tags to include Jaxon javascript files into the page
 *
 * @return string
 */
 public function getJs()
 {
 $sJsExtension = $this->getOption('js.app.minify') ? '.min.js' : '.js';
 
 // The URI for the javascript library files
 $sJsLibUri = rtrim($this->getOption('js.lib.uri', self::JS_LIB_URL), '/') . '/';
 // Add component files to the javascript file array;
 $aJsFiles = [$sJsLibUri . 'jaxon.core' . $sJsExtension];
 if($this->getOption('core.debug.on'))
 {
 $sLanguage = $this->getOption('core.language');
 $aJsFiles[] = $sJsLibUri . 'jaxon.debug' . $sJsExtension;
 $aJsFiles[] = $sJsLibUri . 'lang/jaxon.' . $sLanguage . $sJsExtension;
 /*if($this->getOption('core.debug.verbose'))
 {
 $aJsFiles[] = $sJsLibUri . 'jaxon.verbose' . $sJsExtension;
 }*/
 }
 $sJsFiles = $this->_render('includes.js', ['aUrls' => $aJsFiles]);
 
 $sJsCode = '';
 foreach($this->aGenerators as $xGenerator)
 {
 $sJsCode = rtrim($sJsCode, " \n") . "\n" . $xGenerator->getJs();
 }
 return $sJsFiles . "\n" . rtrim($sJsCode, " \n") . "\n";
 }
 
 /**
 * Get the javascript code to be sent to the browser
 *
 * @return string
 */
 private function _getScript()
 {
 $sScript = '';
 $sReadyScript = '';
 foreach($this->aGenerators as $xGenerator)
 {
 $sScript .= rtrim($xGenerator->getScript(), " \n") . "\n";
 if($xGenerator->readyEnabled() && !$xGenerator->readyInlined())
 {
 // Ready code which can nbe exported to an external file.
 $sReadyScript .= rtrim($xGenerator->getReadyScript(), " \n") . "\n";
 }
 }
 
 // These three parts are always rendered together
 $aConfigVars = $this->getOptionVars();
 return $this->_render('config.js', $aConfigVars) . "\n" . $sScript . "\n" .
 $this->_render('ready.js', ['sScript' => $sReadyScript]);
 }
 
 /**
 * Get the javascript code to include directly in HTML
 *
 * @return string
 */
 private function _getInlineScript()
 {
 $sScript = '';
 foreach($this->aGenerators as $xGenerator)
 {
 if($xGenerator->readyEnabled() && $xGenerator->readyInlined())
 {
 // Ready code which must be inlined in HTML.
 $sScript .= rtrim($xGenerator->getReadyScript(), " \n") . "\n";
 }
 }
 return $this->_render('ready.js', ['sScript' => $sScript]);
 }
 
 /**
 * Get the javascript file name
 *
 * @return void
 */
 private function getJsFileName()
 {
 // Check config options
 // - The js.app.export option must be set to true
 // - The js.app.uri and js.app.dir options must be set to non null values
 if(!$this->getOption('js.app.export') ||
 !$this->getOption('js.app.uri') ||
 !$this->getOption('js.app.dir'))
 {
 return '';
 }
 
 // The file name
 return $this->hasOption('js.app.file') ? $this->getOption('js.app.file') : $this->getHash();
 }
 
 /**
 * Write javascript files and return the corresponding URI
 *
 * @return string
 */
 public function createFiles($sJsDirectory, $sJsFileName)
 {
 // Check dir access
 // - The js.app.dir must be writable
 if(!$sJsFileName || !is_dir($sJsDirectory) || !is_writable($sJsDirectory))
 {
 return '';
 }
 
 $sOutFile = $sJsFileName . '.js';
 $sMinFile = $sJsFileName . '.min.js';
 if(!is_file($sJsDirectory . $sOutFile))
 {
 if(!file_put_contents($sJsDirectory . $sOutFile, $this->_getScript()))
 {
 return '';
 }
 }
 if(($this->getOption('js.app.minify')) && !is_file($sJsDirectory . $sMinFile))
 {
 if(!$this->minify($sJsDirectory . $sOutFile, $sJsDirectory . $sMinFile))
 {
 return '';
 }
 }
 
 $sJsAppUri = rtrim($this->getOption('js.app.uri'), '/') . '/';
 $sJsExtension = $this->getOption('js.app.minify') ? '.min.js' : '.js';
 return $sJsAppUri . $sJsFileName . $sJsExtension;
 }
 
 /**
 * Get the javascript code to be sent to the browser
 *
 * @param boolean        $bIncludeJs         Also get the JS files
 * @param boolean        $bIncludeCss        Also get the CSS files
 *
 * @return string
 */
 public function getScript($bIncludeJs, $bIncludeCss)
 {
 if(!$this->getOption('core.request.uri'))
 {
 $this->setOption('core.request.uri', jaxon()->di()->get(URI::class)->detect());
 }
 
 $sScript = '';
 if(($bIncludeCss))
 {
 $sScript .= $this->getCss() . "\n";
 }
 if(($bIncludeJs))
 {
 $sScript .= $this->getJs() . "\n";
 }
 
 $sJsDirectory = rtrim($this->getOption('js.app.dir'), '/') . '/';
 $sUrl = $this->createFiles($sJsDirectory, $this->getJsFileName());
 if(($sUrl))
 {
 return $sScript . $this->_render('include.js', ['sUrl' => $sUrl]) . "\n" .
 $this->_render('wrapper.js', ['sScript' => $this->_getInlineScript()]);
 }
 
 return $sScript . $this->_render('wrapper.js', [
 'sScript' => $this->_getScript() . "\n" . $this->_getInlineScript()
 ]);
 }
 }
 
 |