Phuedx has uploaded a new change for review. https://gerrit.wikimedia.org/r/219188
Change subject: Extend the core TemplateParser ...................................................................... Extend the core TemplateParser TemplateParser#getTemplate eval's a compiled template so that the renderer function can be returned to the caller. Due to a bug in HHVM's implementation of eval [0], we can't rely on the render function returned by #getTemplate on being the correct one. Extend the Gather\views\TemplateParser class so that #getTemplate will write the compiled template to a file that is then required, thus avoiding eval. The compiled templates are written to the templates/compiled directory and should be committed whenever they change. Since the new TemplateParser class can't change the public API of the parent TemplateParser (LSP), the rest of the codebase should be agnostic to the change. However, because we're expecting the compiled templates to be committed, modify Gather\views\helpers\Template::render to force template recompilation whenever the GatherRecompileTemplates config variable is true. The GatherRecompileTemplates config variable defaults to false. Developers are expected to override it during development. Also reverts "Work around Mustache problems" (1c1f658) so we're in a clean state immediately. [0] https://github.com/facebook/hhvm/pull/5502 Bug: T102941 Change-Id: I729dd34d102879e596428cb07d8cf381071068bb --- M extension.json M includes/views/CollectionsListItemCard.php A includes/views/TemplateParser.php M includes/views/helpers/Template.php M templates/CollectionsListItemCard.mustache A templates/compiled/CardImage.mustache.php A templates/compiled/CollectionsList.mustache.php A templates/compiled/CollectionsListItemCard.mustache.php 8 files changed, 204 insertions(+), 39 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/Gather refs/changes/88/219188/1 diff --git a/extension.json b/extension.json index 21759b6..c80570e 100644 --- a/extension.json +++ b/extension.json @@ -79,6 +79,7 @@ "Gather\\views\\Pagination": "includes/views/Pagination.php", "Gather\\views\\ReportTableRow": "includes/views/ReportTableRow.php", "Gather\\views\\ReportTable": "includes/views/ReportTable.php", + "Gather\\views\\TemplateParser": "includes/views/TemplateParser.php", "Gather\\views\\helpers\\CSS": "includes/views/helpers/CSS.php", "Gather\\views\\helpers\\Template": "includes/views/helpers/Template.php", "Gather\\SpecialGather": "includes/specials/SpecialGather.php", @@ -701,7 +702,8 @@ "config": { "GatherShouldShowTutorial": true, "GatherAllowPublicWatchlist": false, - "GatherEnableBetaFeature": false + "GatherEnableBetaFeature": false, + "GatherRecompileTemplates": false }, "manifest_version": 1 } diff --git a/includes/views/CollectionsListItemCard.php b/includes/views/CollectionsListItemCard.php index 845d3db..f1bdf6b 100644 --- a/includes/views/CollectionsListItemCard.php +++ b/includes/views/CollectionsListItemCard.php @@ -78,40 +78,7 @@ 'class' => helpers\CSS::iconClass( 'profile', 'before' ), 'label' => $owner->getName(), ); - // FIXME: Not needed when https://phabricator.wikimedia.org/T101918 is fixed. - $ownerElement = Html::element( 'a', array( - 'class' => 'collection-owner ' . $defaults['owner']['class'], - 'href' => $defaults['owner']['link'], - ), $defaults['owner']['label'] ); - } else { - // FIXME: Not needed when https://phabricator.wikimedia.org/T101918 is fixed. - $ownerElement = Html::element( 'span', array( 'class' => 'collection-card-following' ), - $this->getPrivacyMsg() ); } - // FIXME: Use Template::render( 'CollectionsListItemCard', array_merge( $defaults, $data ) ); - // as soon as https://phabricator.wikimedia.org/T101918 is fixed. - return Html::openElement( 'div', - array( - 'class' => $defaults['hasImage'] ? 'collection-card' : 'collection-card without-image', - ) ) . - Html::rawElement( 'a', array( - 'href' => $defaults['collectionUrl'], - 'class' => 'collection-card-image', - ), $defaults['image'] ) . - Html::openElement( 'div', array( - 'class' => 'collection-card-overlay', - 'dir' => $defaults['langdir'], - ) ) . - Html::openElement( 'div', array( 'class' => 'collection-card-title' ) ) . - Html::element( 'a', array( - 'href' => $defaults['collectionUrl'], - ), $defaults['title'] ) . - Html::closeElement( 'div' ) . - $ownerElement . - Html::element( 'span', array(), '•' ) . - Html::element( 'span', array( 'class' => 'collection-card-article-count' ), - $defaults['articleCountMsg'] ) . - Html::closeElement( 'div' ) . - Html::closeElement( 'div' ); + return Template::render( 'CollectionsListItemCard', array_merge( $defaults, $data ) ); } } diff --git a/includes/views/TemplateParser.php b/includes/views/TemplateParser.php new file mode 100644 index 0000000..fabf855 --- /dev/null +++ b/includes/views/TemplateParser.php @@ -0,0 +1,84 @@ +<?php + +/** + * views\TemplateParser.php + */ + +namespace Gather\views; + +use TemplateParser as BaseTemplateParser; +use LightnCandy; +use Exception; + +/** + * See https://phabricator.wikimedia.org/T102941. + */ +class TemplateParser extends BaseTemplateParser { + + /** + * Constructs the location of the the source Mustache template and + * where to store the compiled PHP that will be used to render it. + * + * @param string $templateName + * + * @return string[] + * @throws \UnexpectedValueException Disallows upwards directory traversal via + * $templateName (see TemplateParser#getTemplateFilename). + */ + public function getTemplateFilenames( $templateName ) { + $templateFilename = $this->getTemplateFilename( $templateName ); + + return array( + 'template' => $templateFilename, + 'compiled' => "{$this->templateDir}/compiled/{$templateName}.mustache.php", + ); + } + + /** + * Returns a given template function if found, otherwise throws an exception. + * + * @param string $templateName + * + * @return \Closure + * @throws Exception + */ + public function getTemplate( $templateName ) { + if ( isset( $this->renderers[$templateName] ) ) { + return $this->renderers[$templateName]; + } + + $filenames = $this->getTemplateFilenames( $templateName ); + + if ( $this->forceRecompile ) { + $code = $this->compile( file_get_contents( $filenames['template'] ), $this->templateDir ); + + if ( !$code ) { + throw new Exception( "Failed to compile template '$templateName'." ); + } + + if ( !file_put_contents( $filenames['compiled'], $code ) ) { + throw new Exception( "Failed to save updated compiled template '$templateName'" ); + } + } + + $this->renderers[$templateName] = $renderer = require $filenames['compiled']; + + return $renderer; + } + + /** + * @param string $code Handlebars code + * @param string $templateDir Directory templates are stored in + * + * @return string PHP code + */ + protected function compile( $code ) { + return LightnCandy::compile( + $code, + array( + 'flags' => LightnCandy::FLAG_ERROR_EXCEPTION + | LightnCandy::FLAG_MUSTACHE, + ) + ); + } +} diff --git a/includes/views/helpers/Template.php b/includes/views/helpers/Template.php index 206f2b3..51808e1 100644 --- a/includes/views/helpers/Template.php +++ b/includes/views/helpers/Template.php @@ -6,14 +6,16 @@ namespace Gather\views\helpers; -use TemplateParser; +use Gather\views\TemplateParser; class Template { /** * Easy helper for rendering a template from the Gather extension */ public static function render( $template, $data=array() ) { - $templateParser = new TemplateParser( __DIR__ . '/../../../templates' ); + global $wgGatherRecompileTemplates; + + $templateParser = new TemplateParser( __DIR__ . '/../../../templates', $wgGatherRecompileTemplates ); return $templateParser->processTemplate( $template, $data ); } } diff --git a/templates/CollectionsListItemCard.mustache b/templates/CollectionsListItemCard.mustache index feef5e5..8e04761 100644 --- a/templates/CollectionsListItemCard.mustache +++ b/templates/CollectionsListItemCard.mustache @@ -7,12 +7,15 @@ <a href='{{collectionUrl}}'>{{title}}</a> </div> {{#owner}} - <a class="{{class}} collection-owner" href="{{link}}">{{label}}</a> + <a + class="{{class}} collection-owner" + href="{{link}}">{{label}}</a> + <span>•</span> {{/owner}} {{^owner}} <span class='collection-card-following'>{{privacyMsg}}</span> - {{/owner}} <span>•</span> + {{/owner}} <span class='collection-card-article-count'>{{articleCountMsg}}</span> </div> </div> diff --git a/templates/compiled/CardImage.mustache.php b/templates/compiled/CardImage.mustache.php new file mode 100644 index 0000000..c576b60 --- /dev/null +++ b/templates/compiled/CardImage.mustache.php @@ -0,0 +1,31 @@ +<?php return function ($in, $debugopt = 1) { + $cx = array( + 'flags' => array( + 'jstrue' => false, + 'jsobj' => false, + 'spvar' => false, + 'prop' => false, + 'method' => false, + 'mustlok' => true, + 'echo' => false, + 'debug' => $debugopt, + ), + 'constants' => array(), + 'helpers' => array(), + 'blockhelpers' => array(), + 'hbhelpers' => array(), + 'partials' => array(), + 'scopes' => array(), + 'sp_vars' => array('root' => $in), + 'lcrun' => 'LCRun3', + + ); + + return '<div + class=\'list-thumb + '.LCRun3::sec($cx, LCRun3::v($cx, $in, array('wide')), $in, false, function($cx, $in) {return 'list-thumb-y';}).' + '.((LCRun3::isec($cx, LCRun3::v($cx, $in, array('wide')))) ? 'list-thumb-x' : '').'\' + style=\'background-image: url("'.htmlentities((string)LCRun3::v($cx, $in, array('url')), ENT_QUOTES, 'UTF-8').'")\'></div> +'; +} +?> \ No newline at end of file diff --git a/templates/compiled/CollectionsList.mustache.php b/templates/compiled/CollectionsList.mustache.php new file mode 100644 index 0000000..7020e88 --- /dev/null +++ b/templates/compiled/CollectionsList.mustache.php @@ -0,0 +1,33 @@ +<?php return function ($in, $debugopt = 1) { + $cx = array( + 'flags' => array( + 'jstrue' => false, + 'jsobj' => false, + 'spvar' => false, + 'prop' => false, + 'method' => false, + 'mustlok' => true, + 'echo' => false, + 'debug' => $debugopt, + ), + 'constants' => array(), + 'helpers' => array(), + 'blockhelpers' => array(), + 'hbhelpers' => array(), + 'partials' => array(), + 'scopes' => array(), + 'sp_vars' => array('root' => $in), + 'lcrun' => 'LCRun3', + + ); + + return ' +<div class=\'collections-list content view-border-box\' data-owner="'.htmlentities((string)LCRun3::v($cx, $in, array('owner')), ENT_QUOTES, 'UTF-8').'" data-is-owner=\''.LCRun3::v($cx, $in, array('isOwner')).'\'> + <div class=\'collection-cards\'> + '.LCRun3::v($cx, $in, array('items')).' + </div> + <div class=\'collection-actions\'></div> +</div> +'; +} +?> \ No newline at end of file diff --git a/templates/compiled/CollectionsListItemCard.mustache.php b/templates/compiled/CollectionsListItemCard.mustache.php new file mode 100644 index 0000000..3aaacd6 --- /dev/null +++ b/templates/compiled/CollectionsListItemCard.mustache.php @@ -0,0 +1,43 @@ +<?php return function ($in, $debugopt = 1) { + $cx = array( + 'flags' => array( + 'jstrue' => false, + 'jsobj' => false, + 'spvar' => false, + 'prop' => false, + 'method' => false, + 'mustlok' => true, + 'echo' => false, + 'debug' => $debugopt, + ), + 'constants' => array(), + 'helpers' => array(), + 'blockhelpers' => array(), + 'hbhelpers' => array(), + 'partials' => array(), + 'scopes' => array(), + 'sp_vars' => array('root' => $in), + 'lcrun' => 'LCRun3', + + ); + + return '<div class=\'collection-card '.((LCRun3::isec($cx, LCRun3::v($cx, $in, array('hasImage')))) ? 'without-image' : '').'\'> + <a href=\''.htmlentities((string)LCRun3::v($cx, $in, array('collectionUrl')), ENT_QUOTES, 'UTF-8').'\' class=\'collection-card-image\'> + '.LCRun3::v($cx, $in, array('image')).' + </a> + <div class=\'collection-card-overlay\' dir=\''.htmlentities((string)LCRun3::v($cx, $in, array('langdir')), ENT_QUOTES, 'UTF-8').'\'> + <div class=\'collection-card-title\'> + <a href=\''.htmlentities((string)LCRun3::v($cx, $in, array('collectionUrl')), ENT_QUOTES, 'UTF-8').'\'>'.htmlentities((string)LCRun3::v($cx, $in, array('title')), ENT_QUOTES, 'UTF-8').'</a> + </div> +'.LCRun3::sec($cx, LCRun3::v($cx, $in, array('owner')), $in, false, function($cx, $in) {return ' <a + class="'.htmlentities((string)LCRun3::v($cx, $in, array('class')), ENT_QUOTES, 'UTF-8').' collection-owner" + href="'.htmlentities((string)LCRun3::v($cx, $in, array('link')), ENT_QUOTES, 'UTF-8').'">'.htmlentities((string)LCRun3::v($cx, $in, array('label')), ENT_QUOTES, 'UTF-8').'</a> + <span>•</span> +';}).''.((LCRun3::isec($cx, LCRun3::v($cx, $in, array('owner')))) ? ' <span class=\'collection-card-following\'>'.htmlentities((string)LCRun3::v($cx, $in, array('privacyMsg')), ENT_QUOTES, 'UTF-8').'</span> + <span>•</span> +' : '').' <span class=\'collection-card-article-count\'>'.htmlentities((string)LCRun3::v($cx, $in, array('articleCountMsg')), ENT_QUOTES, 'UTF-8').'</span> + </div> +</div> +'; +} +?> \ No newline at end of file -- To view, visit https://gerrit.wikimedia.org/r/219188 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I729dd34d102879e596428cb07d8cf381071068bb Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/extensions/Gather Gerrit-Branch: master Gerrit-Owner: Phuedx <g...@samsmith.io> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits