diff --git a/src/Core/Renderer.php b/src/Core/Renderer.php index 5ce47ad93..bf4cd3907 100644 --- a/src/Core/Renderer.php +++ b/src/Core/Renderer.php @@ -23,8 +23,8 @@ namespace Friendica\Core; use Exception; use Friendica\DI; -use Friendica\Render\FriendicaSmarty; -use Friendica\Render\ITemplateEngine; +use Friendica\Network\HTTPException\InternalServerErrorException; +use Friendica\Render\TemplateEngine; /** * This class handles Renderer related functions. @@ -66,28 +66,30 @@ class Renderer ]; /** - * This is our template processor + * Returns the rendered template output from the template string and variables * - * @param string|FriendicaSmarty $s The string requiring macro substitution or an instance of FriendicaSmarty - * @param array $vars Key value pairs (search => replace) - * - * @return string substituted string - * @throws Exception + * @param string $template + * @param array $vars + * @return string + * @throws InternalServerErrorException */ - public static function replaceMacros($s, array $vars = []) + public static function replaceMacros(string $template, array $vars = []) { $stamp1 = microtime(true); // pass $baseurl to all templates if it isn't set - $vars = array_merge(['$baseurl' => DI::baseUrl()->get()], $vars); + $vars = array_merge(['$baseurl' => DI::baseUrl()->get(), '$APP' => DI::app()], $vars); $t = self::getTemplateEngine(); try { - $output = $t->replaceMacros($s, $vars); + $output = $t->replaceMacros($template, $vars); } catch (Exception $e) { - echo "
" . __FUNCTION__ . ": " . $e->getMessage() . "
"; - exit(); + DI::logger()->critical($e->getMessage(), ['template' => $template, 'vars' => $vars]); + $message = is_site_admin() ? + $e->getMessage() : + DI::l10n()->t('Friendica can\'t display this page at the moment, please contact the administrator.'); + throw new InternalServerErrorException($message); } DI::profiler()->saveTimestamp($stamp1, "rendering", System::callstack()); @@ -98,22 +100,25 @@ class Renderer /** * Load a given template $s * - * @param string $s Template to load. + * @param string $file Template to load. * @param string $subDir Subdirectory (Optional) * * @return string template. - * @throws Exception + * @throws InternalServerErrorException */ - public static function getMarkupTemplate($s, $subDir = '') + public static function getMarkupTemplate($file, $subDir = '') { $stamp1 = microtime(true); $t = self::getTemplateEngine(); try { - $template = $t->getTemplateFile($s, $subDir); + $template = $t->getTemplateFile($file, $subDir); } catch (Exception $e) { - echo "
" . __FUNCTION__ . ": " . $e->getMessage() . "
"; - exit(); + DI::logger()->critical($e->getMessage(), ['file' => $file, 'subDir' => $subDir]); + $message = is_site_admin() ? + $e->getMessage() : + DI::l10n()->t('Friendica can\'t display this page at the moment, please contact the administrator.'); + throw new InternalServerErrorException($message); } DI::profiler()->saveTimestamp($stamp1, "file", System::callstack()); @@ -125,18 +130,22 @@ class Renderer * Register template engine class * * @param string $class + * @throws InternalServerErrorException */ public static function registerTemplateEngine($class) { $v = get_class_vars($class); - if (!empty($v['name'])) - { + if (!empty($v['name'])) { $name = $v['name']; self::$template_engines[$name] = $class; } else { - echo "template engine $class cannot be registered without a name.\n"; - die(); + $admin_message = DI::l10n()->t('template engine cannot be registered without a name.'); + DI::logger()->critical($admin_message, ['class' => $class]); + $message = is_site_admin() ? + $admin_message : + DI::l10n()->t('Friendica can\'t display this page at the moment, please contact the administrator.'); + throw new InternalServerErrorException($message); } } @@ -146,7 +155,8 @@ class Renderer * If $name is not defined, return engine defined by theme, * or default * - * @return ITemplateEngine Template Engine instance + * @return TemplateEngine Template Engine instance + * @throws InternalServerErrorException */ public static function getTemplateEngine() { @@ -156,15 +166,20 @@ class Renderer if (isset(self::$template_engine_instance[$template_engine])) { return self::$template_engine_instance[$template_engine]; } else { + $a = DI::app(); $class = self::$template_engines[$template_engine]; - $obj = new $class; + $obj = new $class($a->getCurrentTheme(), $a->theme_info); self::$template_engine_instance[$template_engine] = $obj; return $obj; } } - echo "template engine $template_engine is not registered!\n"; - exit(); + $admin_message = DI::l10n()->t('template engine is not registered!'); + DI::logger()->critical($admin_message, ['template_engine' => $template_engine]); + $message = is_site_admin() ? + $admin_message : + DI::l10n()->t('Friendica can\'t display this page at the moment, please contact the administrator.'); + throw new InternalServerErrorException($message); } /** diff --git a/src/Model/Tag.php b/src/Model/Tag.php index 87383fbbc..d8c252ca2 100644 --- a/src/Model/Tag.php +++ b/src/Model/Tag.php @@ -535,6 +535,6 @@ class Tag } } - return Strings::startsWith($tag, $tag_chars); + return Strings::startsWithChars($tag, $tag_chars); } } diff --git a/src/Module/Admin/Summary.php b/src/Module/Admin/Summary.php index 4aaeaaec0..4d30f8ebf 100644 --- a/src/Module/Admin/Summary.php +++ b/src/Module/Admin/Summary.php @@ -31,7 +31,9 @@ use Friendica\Database\DBStructure; use Friendica\DI; use Friendica\Model\Register; use Friendica\Module\BaseAdmin; +use Friendica\Module\Update\Profile; use Friendica\Network\HTTPException\InternalServerErrorException; +use Friendica\Render\FriendicaSmarty; use Friendica\Util\ConfigFileLoader; use Friendica\Util\DateTimeFormat; use Friendica\Util\Network; @@ -46,6 +48,14 @@ class Summary extends BaseAdmin // are there MyISAM tables in the DB? If so, trigger a warning message $warningtext = []; + + $templateEngine = Renderer::getTemplateEngine(); + $errors = []; + $templateEngine->testInstall($errors); + foreach ($errors as $error) { + $warningtext[] = DI::l10n()->t('Template engine (%s) error: %s', $templateEngine::$name, $error); + } + if (DBA::count(['information_schema' => 'tables'], ['engine' => 'myisam', 'table_schema' => DBA::databaseName()])) { $warningtext[] = DI::l10n()->t('Your DB still runs with MyISAM tables. You should change the engine type to InnoDB. As Friendica will use InnoDB only features in the future, you should change this! See here for a guide that may be helpful converting the table engines. You may also use the command php bin/console.php dbstructure toinnodb of your Friendica installation for an automatic conversion.
', 'https://dev.mysql.com/doc/refman/5.7/en/converting-tables-to-innodb.html'); } @@ -136,7 +146,6 @@ class Summary extends BaseAdmin throw new InternalServerErrorException('Stream is null.'); } } - } catch (\Throwable $exception) { $warningtext[] = DI::l10n()->t('The debug logfile \'%s\' is not usable. No logging possible (error: \'%s\')', $file, $exception->getMessage()); } diff --git a/src/Render/FriendicaSmarty.php b/src/Render/FriendicaSmarty.php index 2b06c88c9..5a1e7ed10 100644 --- a/src/Render/FriendicaSmarty.php +++ b/src/Render/FriendicaSmarty.php @@ -21,7 +21,6 @@ namespace Friendica\Render; -use Friendica\DI; use Smarty; use Friendica\Core\Renderer; @@ -34,26 +33,23 @@ class FriendicaSmarty extends Smarty public $filename; - function __construct() + function __construct(string $theme, array $theme_info) { parent::__construct(); - $a = DI::app(); - $theme = $a->getCurrentTheme(); - // setTemplateDir can be set to an array, which Smarty will parse in order. // The order is thus very important here $template_dirs = ['theme' => "view/theme/$theme/" . self::SMARTY3_TEMPLATE_FOLDER . "/"]; - if (!empty($a->theme_info['extends'])) { - $template_dirs = $template_dirs + ['extends' => "view/theme/" . $a->theme_info["extends"] . "/" . self::SMARTY3_TEMPLATE_FOLDER . "/"]; + if (!empty($theme_info['extends'])) { + $template_dirs = $template_dirs + ['extends' => "view/theme/" . $theme_info["extends"] . "/" . self::SMARTY3_TEMPLATE_FOLDER . "/"]; } $template_dirs = $template_dirs + ['base' => "view/" . self::SMARTY3_TEMPLATE_FOLDER . "/"]; $this->setTemplateDir($template_dirs); $this->setCompileDir('view/smarty3/compiled/'); - $this->setConfigDir('view/smarty3/config/'); - $this->setCacheDir('view/smarty3/cache/'); + $this->setConfigDir('view/smarty3/'); + $this->setCacheDir('view/smarty3/'); $this->left_delimiter = Renderer::getTemplateLeftDelimiter('smarty3'); $this->right_delimiter = Renderer::getTemplateRightDelimiter('smarty3'); @@ -63,13 +59,4 @@ class FriendicaSmarty extends Smarty // Don't report errors so verbosely $this->error_reporting = E_ALL & ~E_NOTICE; } - - function parsed($template = '') - { - if ($template) { - return $this->fetch('string:' . $template); - } - return $this->fetch('file:' . $this->filename); - } - -} \ No newline at end of file +} diff --git a/src/Render/FriendicaSmartyEngine.php b/src/Render/FriendicaSmartyEngine.php index 6984daa15..2c76ff025 100644 --- a/src/Render/FriendicaSmartyEngine.php +++ b/src/Render/FriendicaSmartyEngine.php @@ -23,56 +23,82 @@ namespace Friendica\Render; use Friendica\Core\Hook; use Friendica\DI; +use Friendica\Network\HTTPException\InternalServerErrorException; +use Friendica\Util\Strings; /** - * Smarty implementation of the Friendica template engine interface + * Smarty implementation of the Friendica template abstraction */ -class FriendicaSmartyEngine implements ITemplateEngine +final class FriendicaSmartyEngine extends TemplateEngine { static $name = "smarty3"; - public function __construct() + const FILE_PREFIX = 'file:'; + const STRING_PREFIX = 'string:'; + + /** @var FriendicaSmarty */ + private $smarty; + + /** + * @inheritDoc + */ + public function __construct(string $theme, array $theme_info) { - if (!is_writable(__DIR__ . '/../../view/smarty3/')) { - echo "ERROR: folder view/smarty3/ must be writable by webserver."; - exit(); + $this->theme = $theme; + $this->theme_info = $theme_info; + $this->smarty = new FriendicaSmarty($this->theme, $this->theme_info); + + if (!is_writable(DI::basePath() . '/view/smarty3')) { + $admin_message = DI::l10n()->t('The folder view/smarty3/ must be writable by webserver.'); + DI::logger()->critical($admin_message); + $message = is_site_admin() ? + $admin_message : + DI::l10n()->t('Friendica can\'t display this page at the moment, please contact the administrator.'); + throw new InternalServerErrorException($message); } } - // ITemplateEngine interface - public function replaceMacros($s, $r) + /** + * @inheritDoc + */ + public function testInstall(array &$errors = null) { - $template = ''; - if (gettype($s) === 'string') { - $template = $s; - $s = new FriendicaSmarty(); - } + $this->smarty->testInstall($errors); + } - $r['$APP'] = DI::app(); + /** + * @inheritDoc + */ + public function replaceMacros(string $template, array $vars) + { + if (!Strings::startsWith($template, self::FILE_PREFIX)) { + $template = self::STRING_PREFIX . $template; + } // "middleware": inject variables into templates $arr = [ - "template" => basename($s->filename), - "vars" => $r + 'template' => basename($this->smarty->filename), + 'vars' => $vars ]; - Hook::callAll("template_vars", $arr); - $r = $arr['vars']; + Hook::callAll('template_vars', $arr); + $vars = $arr['vars']; - foreach ($r as $key => $value) { + foreach ($vars as $key => $value) { if ($key[0] === '$') { $key = substr($key, 1); } - $s->assign($key, $value); + $this->smarty->assign($key, $value); } - return $s->parsed($template); + + return $this->smarty->fetch($template); } - public function getTemplateFile($file, $subDir = '') + /** + * @inheritDoc + */ + public function getTemplateFile(string $file, string $subDir = '') { - $a = DI::app(); - $template = new FriendicaSmarty(); - // Make sure $root ends with a slash / if ($subDir !== '' && substr($subDir, -1, 1) !== '/') { $subDir = $subDir . '/'; @@ -80,21 +106,20 @@ class FriendicaSmartyEngine implements ITemplateEngine $root = DI::basePath() . '/' . $subDir; - $theme = $a->getCurrentTheme(); - $filename = $template::SMARTY3_TEMPLATE_FOLDER . '/' . $file; + $filename = $this->smarty::SMARTY3_TEMPLATE_FOLDER . '/' . $file; - if (file_exists("{$root}view/theme/$theme/$filename")) { - $template_file = "{$root}view/theme/$theme/$filename"; - } elseif (!empty($a->theme_info['extends']) && file_exists(sprintf('%sview/theme/%s}/%s', $root, $a->theme_info['extends'], $filename))) { - $template_file = sprintf('%sview/theme/%s}/%s', $root, $a->theme_info['extends'], $filename); + if (file_exists("{$root}view/theme/$this->theme/$filename")) { + $template_file = "{$root}view/theme/$this->theme/$filename"; + } elseif (!empty($this->theme_info['extends']) && file_exists(sprintf('%sview/theme/%s}/%s', $root, $this->theme_info['extends'], $filename))) { + $template_file = sprintf('%sview/theme/%s}/%s', $root, $this->theme_info['extends'], $filename); } elseif (file_exists("{$root}/$filename")) { $template_file = "{$root}/$filename"; } else { $template_file = "{$root}view/$filename"; } - $template->filename = $template_file; + $this->smarty->filename = $template_file; - return $template; + return self::FILE_PREFIX . $template_file; } } diff --git a/src/Render/ITemplateEngine.php b/src/Render/ITemplateEngine.php deleted file mode 100644 index b18af69f2..000000000 --- a/src/Render/ITemplateEngine.php +++ /dev/null @@ -1,31 +0,0 @@ -. - * - */ - -namespace Friendica\Render; - -/** - * Interface for template engines - */ -interface ITemplateEngine -{ - public function replaceMacros($s, $v); - public function getTemplateFile($file, $subDir = ''); -} diff --git a/src/Render/TemplateEngine.php b/src/Render/TemplateEngine.php new file mode 100644 index 000000000..34ce03c5d --- /dev/null +++ b/src/Render/TemplateEngine.php @@ -0,0 +1,68 @@ +. + * + */ + +namespace Friendica\Render; + +/** + * Interface for template engines + */ +abstract class TemplateEngine +{ + /** @var string */ + static $name; + + /** @var string */ + protected $theme; + /** @var array */ + protected $theme_info; + + /** + * @param string $theme The current theme name + * @param array $theme_info The current theme info array + */ + abstract public function __construct(string $theme, array $theme_info); + + /** + * Checks the template engine is correctly installed and configured and reports error messages in the provided + * parameter or displays them directly if it's null. + * + * @param array|null $errors + */ + abstract public function testInstall(array &$errors = null); + + /** + * Returns the rendered template output from the template string and variables + * + * @param string $template + * @param array $vars + * @return string + */ + abstract public function replaceMacros(string $template, array $vars); + + /** + * Returns the template string from a file path and an optional sub-directory from the project root + * + * @param string $file + * @param string $subDir + * @return mixed + */ + abstract public function getTemplateFile(string $file, string $subDir = ''); +} diff --git a/src/Util/Strings.php b/src/Util/Strings.php index 3dd91193d..04d676ef5 100644 --- a/src/Util/Strings.php +++ b/src/Util/Strings.php @@ -369,13 +369,27 @@ class Strings * @param array $chars * @return bool */ - public static function startsWith($string, array $chars) + public static function startsWithChars($string, array $chars) { $return = in_array(substr(trim($string), 0, 1), $chars); return $return; } + /** + * Check if the first string starts with the second + * + * @param string $string + * @param string $start + * @return bool + */ + public static function startsWith(string $string, string $start) + { + $return = substr_compare($string, $start, 0, strlen($start)) === 0; + + return $return; + } + /** * Returns the regular expression string to match URLs in a given text * diff --git a/tests/Util/AppMockTrait.php b/tests/Util/AppMockTrait.php index 1f6605390..59e1b3f55 100644 --- a/tests/Util/AppMockTrait.php +++ b/tests/Util/AppMockTrait.php @@ -108,7 +108,7 @@ trait AppMockTrait ->andReturn($this->configMock); $this->app ->shouldReceive('getTemplateEngine') - ->andReturn(new FriendicaSmartyEngine()); + ->andReturn(new FriendicaSmartyEngine('frio', [])); $this->app ->shouldReceive('getCurrentTheme') ->andReturn('Smarty3');