src/HTMLGenerator.php
<?php
namespace samson\html;
use samson\core\ExternalModule;
use samson\core\File;
/**
* HTML markup generator module
* @package SamsonPHP
* @author Vitaly Iegorov <egorov@samsonos.com>
*/
class HTMLGenerator extends ExternalModule
{
/** Идентификатор модуля */
protected $id = 'html';
/** Source path */
public $input = __SAMSON_CWD__;
/** Output path */
public $output;
/** Restricted file types for compression */
public $restricted = array(
'php',
'vphp',
'buildpath',
'setting',
'project',
'htaccess',
'json',
'js',
'less',
'css',
'coffee',
'gitignore',
'md'
);
/**
* Core render handler for including CSS and JS resources to html
*
* @param string $view View content
* @param array $data View data
* @param \samson\core\Module $m Pointer to current module
*
* @return string Processed view content
*/
public function renderer($view, array $data = array(), $m = null)
{
// TODO: This must be done with new router module
// Cache only local modules
if (url()->module != $this->id) {
// Build cache path with current
$path = $this->cache_path.locale_path();
// Add module name to path
$path .= isset(url()->module{0}) ? url()->module:'index';
// Add module controller action name
$path .= isset(url()->method{0}) ? '_'.url()->method : '';
// Add file extension
$path .= '.html';
// Get directory path
$dir = dirname($path);
// Create folder
if (!file_exists($dir)) {
\samson\core\File::mkdir($dir);
}
// Save html data
file_put_contents($path, $view);
}
return $view;
}
/**
* Copy file from source location to destination location with
* analyzing last file modification time, and copying only changed files
*
* @param string $src source file
* @param string $dst destination file
* @param null $handler
*
* @return bool
*/
public function copy_resource( $src, $dst, $handler = null )
{
if( !file_exists( $src ) ) return e('Cannot copy file - Source file(##) does not exists', E_SAMSON_SNAPSHOT_ERROR, $src );
// Action to do
$action = null;
// Get source file timestamp
$source_ts = filemtime( $src );
// If destination file does not exists
if( !file_exists( $dst ) ) $action = 'Creating';
// If source file has been changed
else if( abs($source_ts - filemtime( $dst )) > 125 ) $action = 'Updating';
// If we know what to do
if( isset( $action ))
{
// Create folder structure if necessary
$dir_path = pathname( $dst );
if( !file_exists( $dir_path ))
{
elapsed( ' -- Creating folder structure '.$dir_path.' from '.$src );
\samson\core\File::mkdir($dir_path);
}
// If file handler specified
if( is_callable($handler) ) call_user_func( $handler, $src, $dst, $action );
// Copy file
else copy( $src, $dst );
elapsed( ' -- '.$action.' file '.$dst.' from '.$src.'(Difference '.date('H:i:s',abs($source_ts - filemtime( $dst ))).')' );
// Touch source file with copied file
touch( $src );
}
}
/**
* Create static HTML site version
*/
public function compress()
{
// If no output path specified
if (!isset($this->output{0})) {
$this->output = __SAMSON_PUBLIC_PATH.'/out/';
}
// Create output directory and clear old HTML data
if (\samson\core\File::mkdir($this->output)) {
\samson\core\File::clear($this->output);
}
// Save original output path
$o_output = $this->output;
elapsed('Creating static HTML web-application from: '.$this->input.' to '.$this->output);
// Collection of existing generated views
$views = '<h1>#<i>'.$_SERVER['HTTP_HOST'].'</i> .html pages:</h1>';
$views .= 'Generated on '.date('d M Y h:i');
// Path to new resources
$cssPath = '';
$jsPath = '';
// Copy generated css & js resources to root folder
if (class_exists('\samson\resourcer\ResourceRouter')) {
$rr = m('resourcer');
// Get resourcer CSS generated files
$cssPath = $this->input.$rr->cached['css'];
if (isset($cssPath)) {
elapsed('Creating CSS resource file from:'.$cssPath);
// Read CSS file
$css = file_get_contents($cssPath);
// Perform URL rewriting
$css = preg_replace_callback( '/url\s*\(\s*(\'|\")?([^\)\s\'\"]+)(\'|\")?\s*\)/i', array( $this,
'srcReplaceCallback'
), $css );
//$css = preg_replace('url((.*?)=si', '\\www', $css);
$css = str_replace('url("fonts/', 'url("www/fonts/', $css);
$css = str_replace('url("img/', 'url("www/img/', $css);
// Write new CSS file
file_put_contents($this->output.'style.css', $css);
}
// Get resourcer JS generated files
$jsPath = $rr->cached['js'];
if (isset($jsPath)) {
elapsed('Creating JavaScript resource file from:'.$jsPath);
$this->copy_resource($this->input.$jsPath, $this->output.'index.js');
}
}
// Iterate all site supported locales
foreach (\samson\core\SamsonLocale::$locales as $locale) {
// Generate localized path to cached html pages
$pages_path = $this->cache_path.locale_path($locale);
// Set views locale description
$views .= '<h2>Locale <i>'.($locale == \samson\core\SamsonLocale::DEF ? 'default' : $locale).'</i>:<h2>';
// Get original output path
$this->output = $o_output;
//создаем набор дескрипторов cURL
$mh = curl_multi_init();
//$__modules = array('local', 'account', 'clients', 'dashboard', 'login', 'main', 'notification', 'project', 'sidebar', 'translators');
$system_methods = array('__construct', '__sleep', '__destruct', '__get', '__set', '__call', '__wakeup');
//handler
// TODO: this is not should be rewritten to support new router
// Perform generation of every controller
foreach (s()->module_stack as $id => $ctrl) {
$rmController = sizeof($ctrl->resourceMap->controllers) ? $ctrl->resourceMap->controllers : $ctrl->resourceMap->module;
//trace($rmController, 1);
$controller = array();
//trace($controller, true);
if (isset($rmController[0]) && class_exists($rmController[0])) {
if (!substr_count($rmController[1], 'vendor')) {
$methods = get_class_methods($rmController[0]);
foreach ($methods as $method) {
if (!in_array($method, $system_methods)) {
if ($method == '__handler') {
$controller[] = '/'.$id;
} elseif (substr_count($method, '__') && !substr_count($method, '__async')) {
$new_method = str_replace('__', '',$method);
$controller[] = '/'.$id.'/'.$new_method;
}
}
}
}
} else {
$controller = $rmController;
}
foreach ($controller as $cntrl) {
if (strpos($cntrl, '.php')) {
// generate controller URL
$cntrl = '/'.locale_path('ru').strtolower(basename($cntrl,'.php'));
}
elapsed('Generating HTML snapshot for: '.$cntrl);
// Create curl instance
$ch = \curl_init('127.0.0.1'.$cntrl);
// Set base request options
\curl_setopt_array($ch, array(
CURLOPT_VERBOSE => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER =>array('Host: '.$_SERVER['HTTP_HOST'] ),
));
// Add curl too multi request
curl_multi_add_handle( $mh, $ch );
}
}
// TODO: Create function\module for this
// Curl multi-request
$active = null;
do{
$mrc = curl_multi_exec($mh, $active);
} while ($mrc == CURLM_CALL_MULTI_PERFORM);
while ($active && $mrc == CURLM_OK) {
if (curl_multi_select($mh) != -1) {
do {
$mrc = curl_multi_exec($mh, $active);
} while ($mrc == CURLM_CALL_MULTI_PERFORM);
}
}
curl_multi_close($mh);
// Files array
$files = array();
// Iterate generated pages
foreach (\samson\core\File::dir($pages_path,'html',null,$files,0) as $f) {
// Ignore default controller index page as it will be included in this controller
if (strpos($f, '/index.html') != false) {
continue;
}
// Read HTML file
$html = file_get_contents($f);
// If we have resourcer CSS resource
if (isset($cssPath{0})) {
//$html = str_replace(basename($cssPath, '.css'), 'style', $html);
//$html = str_replace('/cache/resourcer/style.css', 'style.css', $html);
$html = preg_replace("'<link type=\"text/css\"[^>]*?>'si", '<link type="text/css" rel="stylesheet" href="style.css">', $html);
}
// If we have resourcer JS resource
if (isset($jsPath{0})) {
$html = preg_replace("'<script[^>]*?></script>'si", '<script type="text/javascript" src="index.js"></script>', $html);
}
// Change path in all img SRC attributes
if (preg_match_all('/< *img[^>]*src *= *["\']?([^"\']*)/i', $html, $matches)) {
if(isset($matches['url'])) {
foreach ($matches['url'] as $match) {
trace($match.'-'.(__SAMSON_PUBLIC_PATH.ltrim($match, '/')));
$html = str_ireplace($match, __SAMSON_PUBLIC_PATH.ltrim($match, '/'), $html);
}
}
}
// Fix images inside modules
$html = str_replace('<img src="img', '<img src="www/img', $html);
// Remove relative links
$html = str_ireplace('<base href="/">', '', $html);
// Get just file name
$view_path = ($locale != \samson\core\SamsonLocale::DEF ? $locale.'_' : '').basename($f);
// Save HTML file
file_put_contents($this->output.$view_path, $html);
// Create index.html record
$views .= '<a href="'.$view_path.'">'.basename($f).'</a><br>';
}
}
// Write index file
file_put_contents( $this->output.'index.html', $views );
// TODO: Add support to external-internal modules
// Iterate other resources
foreach ( \samson\core\ResourceMap::get($this->input, false, array(__SAMSON_PUBLIC_PATH.'out/'))->resources as $type => $files ) {
if (!in_array( $type, $this->restricted)) {
foreach ($files as $f) {
$this->copy_resource($f, str_replace($this->input, $this->output, $f));
}
}
}
$imagesArray = array('png', 'jpg', 'jpeg', 'gif');
foreach (s()->module_stack as $module) {
if (isset($module->resourceMap->module[1]) && !substr_count($module->resourceMap->module[1], 'vendor')) {
foreach ($imagesArray as $format) {
if (isset($module->resourceMap->resources[$format]) && sizeof($module->resourceMap->resources[$format])) {
foreach ($module->resourceMap->resources[$format] as $file) {
$this->copy_resource($file, $this->output.'www/img/'.basename($file));
}
}
}
}
}
// Generate zip file name
$zip_file = $o_output.'www.zip';
elapsed('Creating ZIP file: '.$zip_file);
// Create zip archieve
$zip = new \ZipArchive;
if ($zip->open($zip_file, \ZipArchive::CREATE) === true) {
foreach (\samson\core\File::dir($this->output) as $file) {
$zip->addFile( $file, str_replace($this->output, '', $file));
}
$zip->close();
} else {
elapsed('Cannot create zip file');
}
}
/** Callback for CSS url rewriting */
public function srcReplaceCallback( $matches )
{
// If pattern element is found
if (isset($matches[2])) {
$matches[2] = substr($matches[2], strpos($matches[2], '=')+1);
// Change path
return 'url("'.ltrim (str_replace('../','', $matches[2]), '/').'")';
}
}
/** @see ModuleConnector::init() */
public function init( array $params = array() )
{
// Subscribe to core rendered event
s()->subscribe('core.rendered', array($this, 'renderer'));
// Вызовем родительский метод
parent::init($params);
}
/** Default controller */
public function __BASE()
{
// Perform compression
$this->compress();
$this->view('index');
}
}