Skip to content
This repository
Browse code

ENHANCEMENT: Allow arguments to be passed to templates via an array p…

…assed to SSViewer#process and via keyword=value pairs in the <% include %> tag
  • Loading branch information...
commit e4a043ac0b642bc624a54550a9e484154bdb7d29 1 parent 40ca21e
Hamish Friedlander authored April 11, 2012
1  tests/templates/SSViewerTestIncludeWIthArguments.ss
... ...
@@ -0,0 +1 @@
  1
+<p>$Arg1</p><p>$Arg2</p>
27  tests/view/SSViewerTest.php
@@ -443,6 +443,33 @@ function testBaseTagGeneration() {
443 443
 		$this->assertRegExp('/<head><base href=".*" \/><\/head>/', $response->getBody());
444 444
 	}
445 445
 
  446
+	function testIncludeWithArguments() {
  447
+		$this->assertEquals(
  448
+			$this->render('<% include SSViewerTestIncludeWithArguments %>'),
  449
+			'<p>[out:Arg1]</p><p>[out:Arg2]</p>'
  450
+		);
  451
+
  452
+		$this->assertEquals(
  453
+			$this->render('<% include SSViewerTestIncludeWithArguments Arg1=A %>'),
  454
+			'<p>A</p><p>[out:Arg2]</p>'
  455
+		);
  456
+
  457
+		$this->assertEquals(
  458
+			$this->render('<% include SSViewerTestIncludeWithArguments Arg1=A, Arg2=B %>'),
  459
+			'<p>A</p><p>B</p>'
  460
+		);
  461
+
  462
+		$this->assertEquals(
  463
+			$this->render('<% include SSViewerTestIncludeWithArguments Arg1=A Bare String, Arg2=B Bare String %>'),
  464
+			'<p>A Bare String</p><p>B Bare String</p>'
  465
+		);
  466
+
  467
+		$this->assertEquals(
  468
+			$this->render('<% include SSViewerTestIncludeWithArguments Arg1="A", Arg2=$B %>', new ArrayData(array('B' => 'Bar'))),
  469
+			'<p>A</p><p>Bar</p>'
  470
+		);
  471
+	}
  472
+
446 473
 	
447 474
 	function testRecursiveInclude() {
448 475
 		$view = new SSViewer(array('SSViewerTestRecursiveInclude'));
3,058  view/SSTemplateParser.php
1637 additions, 1421 deletions not shown
79  view/SSTemplateParser.php.inc
@@ -99,7 +99,7 @@ class SSTemplateParser extends Parser {
99 99
 	# Template is any structurally-complete portion of template (a full nested level in other words). It's the primary matcher, 
100 100
 	# and is used by all enclosing blocks, as well as a base for the top level
101 101
 	 
102  
-	Template: (Comment | If | Require | CacheBlock | UncachedBlock | OldI18NTag | ClosedBlock | OpenBlock | MalformedBlock | Injection | Text)+
  102
+	Template: (Comment | If | Require | CacheBlock | UncachedBlock | OldI18NTag | Include | ClosedBlock | OpenBlock | MalformedBlock | Injection | Text)+
103 103
 	*/
104 104
 	function Template_STR(&$res, $sub) {
105 105
 		$res['php'] .= $sub['php'] . PHP_EOL ;
@@ -264,7 +264,7 @@ class SSTemplateParser extends Parser {
264 264
 	
265 265
 	function Argument_FreeString(&$res, $sub) {
266 266
 		$res['ArgumentMode'] = 'string';
267  
-		$res['php'] = "'" . str_replace("'", "\\'", $sub['text']) . "'";
  267
+		$res['php'] = "'" . str_replace("'", "\\'", rtrim($sub['text'])) . "'";
268 268
 	}
269 269
 	
270 270
 	/*!*
@@ -573,7 +573,55 @@ class SSTemplateParser extends Parser {
573 573
 	function OldI18NTag_STR(&$res, $sub) {
574 574
 		$res['php'] = '$val .= ' . $sub['php'] . ';';
575 575
 	}
576  
-	
  576
+
  577
+	/*!*
  578
+
  579
+	# An argument that can be passed through to an included template
  580
+
  581
+	NamedArgument: Name:Word "=" Value:Argument
  582
+
  583
+	*/
  584
+	function NamedArgument_Name(&$res, $sub) {
  585
+		$res['php'] = "'" . $sub['text'] . "' => ";
  586
+	}
  587
+
  588
+	function NamedArgument_Value(&$res, $sub) {
  589
+		$res['php'] .= ($sub['ArgumentMode'] == 'default') ? $sub['string_php'] : str_replace('$$FINAL', 'XML_val', $sub['php']);
  590
+	}
  591
+
  592
+	/*!*
  593
+
  594
+	# The include tag
  595
+
  596
+	Include: "<%" < "include" < Template:Word < (NamedArgument ( < "," < NamedArgument )*)? > "%>"
  597
+
  598
+	*/
  599
+	function Include__construct(&$res){
  600
+		$res['arguments'] = array();
  601
+	}
  602
+
  603
+	function Include_Template(&$res, $sub){
  604
+		$res['template'] = "'" . $sub['text'] . "'";
  605
+	}
  606
+
  607
+	function Include_NamedArgument(&$res, $sub){
  608
+		$res['arguments'][] = $sub['php'];
  609
+	}
  610
+
  611
+	function Include__finalise(&$res){
  612
+		$template = $res['template'];
  613
+		$arguments = $res['arguments'];
  614
+
  615
+		$res['php'] = '$val .= SSViewer::execute_template('.$template.', $scope->getItem(), array('.implode(',', $arguments)."));\n";
  616
+
  617
+		if($this->includeDebuggingComments) { // Add include filename comments on dev sites
  618
+			$res['php'] =
  619
+				'$val .= \'<!-- include '.addslashes($template).' -->\';'. "\n".
  620
+				$res['php'].
  621
+				'$val .= \'<!-- end include '.addslashes($template).' -->\';'. "\n";
  622
+		}
  623
+	}
  624
+
577 625
 	/*!*
578 626
 	
579 627
 	# To make the block support reasonably extendable, we don't explicitly define each closed block and it's structure,
@@ -586,7 +634,7 @@ class SSTemplateParser extends Parser {
586 634
 	# NotBlockTag matches against any word that might come after a "<%" that the generic open and closed block handlers
587 635
 	# shouldn't attempt to match against, because they're handled by more explicit matchers 
588 636
 	 
589  
-	NotBlockTag: "end_" | (("if" | "else_if" | "else" | "require" | "cached" | "uncached" | "cacheblock") ] )
  637
+	NotBlockTag: "end_" | (("if" | "else_if" | "else" | "require" | "cached" | "uncached" | "cacheblock" | "include") ] )
590 638
 	
591 639
 	# Match against closed blocks - blocks with an opening and a closing tag that surround some internal portion of
592 640
 	# template
@@ -718,28 +766,7 @@ class SSTemplateParser extends Parser {
718 766
 			throw new SSTemplateParseException('Unknown open block "'.$blockname.'" encountered. Perhaps you missed the closing tag or have mis-spelled it?', $this);
719 767
 		}
720 768
 	}
721  
-	
722  
-	/**
723  
-	 * This is an open block handler, for the <% include %> tag
724  
-	 */
725  
-	function OpenBlock_Handle_Include(&$res) {
726  
-		if ($res['ArgumentCount'] != 1) throw new SSTemplateParseException('Include takes exactly one argument', $this);
727  
-		
728  
-		$arg = $res['Arguments'][0];
729  
-		$php = ($arg['ArgumentMode'] == 'default') ? $arg['string_php'] : $arg['php'];
730  
-		
731  
-		if($this->includeDebuggingComments) { // Add include filename comments on dev sites
732  
-			return 
733  
-				'$val .= \'<!-- include '.addslashes($php).' -->\';'. "\n".
734  
-				'$val .= SSViewer::execute_template('.$php.', $scope->getItem());'. "\n".
735  
-				'$val .= \'<!-- end include '.addslashes($php).' -->\';'. "\n";
736  
-		}
737  
-		else {
738  
-			return 
739  
-				'$val .= SSViewer::execute_template('.$php.', $scope->getItem());'. "\n";
740  
-		}
741  
-	}
742  
-	
  769
+
743 770
 	/**
744 771
 	 * This is an open block handler, for the <% debug %> utility tag
745 772
 	 */
130  view/SSViewer.php
@@ -300,7 +300,7 @@ class SSViewer_DataPresenter extends SSViewer_Scope {
300 300
 
301 301
 	protected $extras;
302 302
 
303  
-	function __construct($item, $extras = array()){
  303
+	function __construct($item, $extras = null){
304 304
 		parent::__construct($item);
305 305
 
306 306
 		// Build up global property providers array only once per request
@@ -317,7 +317,7 @@ function __construct($item, $extras = array()){
317 317
 			$this->createCallableArray(self::$iteratorProperties, "TemplateIteratorProvider", "get_template_iterator_variables", true);   //call non-statically
318 318
 		}
319 319
 
320  
-		$this->extras = $extras;
  320
+		$this->extras = $extras ? $extras : array();
321 321
 	}
322 322
 
323 323
 	protected function createCallableArray(&$extraArray, $interfaceToQuery, $variableMethod, $createObject = false) {
@@ -504,7 +504,7 @@ static function get_source_file_comments() {
504 504
 	 * @var string
505 505
 	 */
506 506
 	protected static $current_custom_theme = null;
507  
-	
  507
+
508 508
 	/**
509 509
 	 * Create a template from a string instead of a .ss file
510 510
 	 * 
@@ -697,7 +697,59 @@ static function flush_template_cache() {
697 697
 			self::$flushed = true;
698 698
 		}
699 699
 	}
700  
-	
  700
+
  701
+	/**
  702
+	 * @var Zend_Cache_Core
  703
+	 */
  704
+	protected $partialCacheStore = null;
  705
+
  706
+	/**
  707
+	 * Set the cache object to use when storing / retrieving partial cache blocks.
  708
+	 * @param Zend_Cache_Core $cache
  709
+	 */
  710
+	public function setPartialCacheStore($cache) {
  711
+		$this->partialCacheStore = $cache;
  712
+	}
  713
+
  714
+	/**
  715
+	 * Get the cache object to use when storing / retrieving partial cache blocks
  716
+	 * @return Zend_Cache_Core
  717
+	 */
  718
+	public function getPartialCacheStore() {
  719
+		return $this->partialCacheStore ? $this->partialCacheStore : SS_Cache::factory('cacheblock');
  720
+	}
  721
+
  722
+	/**
  723
+	 * An internal utility function to set up variables in preparation for including a compiled
  724
+	 * template, then do the include
  725
+	 *
  726
+	 * Effectively this is the common code that both SSViewer#process and SSViewer_FromString#process call
  727
+	 *
  728
+	 * @param string $cacheFile - The path to the file that contains the template compiled to PHP
  729
+	 * @param Object $item - The item to use as the root scope for the template
  730
+	 * @param array|null $arguments - Any variables to layer into the root scope
  731
+	 * @return string - The result of executing the template
  732
+	 */
  733
+	protected function includeGeneratedTemplate($cacheFile, $item, $arguments) {
  734
+		if(isset($_GET['showtemplate']) && $_GET['showtemplate']) {
  735
+			$lines = file($cacheFile);
  736
+			echo "<h2>Template: $cacheFile</h2>";
  737
+			echo "<pre>";
  738
+			foreach($lines as $num => $line) {
  739
+				echo str_pad($num+1,5) . htmlentities($line, ENT_COMPAT, 'UTF-8');
  740
+			}
  741
+			echo "</pre>";
  742
+		}
  743
+
  744
+		$cache = $this->getPartialCacheStore();
  745
+		$scope = new SSViewer_DataPresenter($item, $arguments ? $arguments : array());
  746
+		$val = '';
  747
+
  748
+		include($cacheFile);
  749
+
  750
+		return $val;
  751
+	}
  752
+
701 753
 	/**
702 754
 	 * The process() method handles the "meat" of the template processing.
703 755
 	 * It takes care of caching the output (via {@link SS_Cache}),
@@ -711,11 +763,15 @@ static function flush_template_cache() {
711 763
 	 * @param SS_Cache $cache Optional cache backend
712 764
 	 * @return String Parsed template output.
713 765
 	 */
714  
-	public function process($item, $cache = null) {
  766
+	public function process($item, $arguments = null) {
715 767
 		SSViewer::$topLevel[] = $item;
716  
-		
717  
-		if (!$cache) $cache = SS_Cache::factory('cacheblock');
718  
-		
  768
+
  769
+		if ($arguments && $arguments instanceof Zend_Cache_Core) {
  770
+			Deprecation::notice('3.0', 'Use setPartialCacheStore to override the partial cache storage backend, the second argument to process is now an array of variables.');
  771
+			$this->setPartialCacheStore($arguments);
  772
+			$arguments = null;
  773
+		}
  774
+
719 775
 		if(isset($this->chosenTemplates['main'])) {
720 776
 			$template = $this->chosenTemplates['main'];
721 777
 		} else {
@@ -739,34 +795,21 @@ public function process($item, $cache = null) {
739 795
 
740 796
 			if(isset($_GET['debug_profile'])) Profiler::unmark("SSViewer::process - compile", " for $template");
741 797
 		}
742  
-	
743  
-		
744  
-		if(isset($_GET['showtemplate']) && !Director::isLive()) {
745  
-			$lines = file($cacheFile);
746  
-			echo "<h2>Template: $cacheFile</h2>";
747  
-			echo "<pre>";
748  
-			foreach($lines as $num => $line) {
749  
-				echo str_pad($num+1,5) . htmlentities($line, ENT_COMPAT, 'UTF-8');
750  
-			}
751  
-			echo "</pre>";
752  
-		}
753  
-		
  798
+
  799
+		$internalArguments = array('I18NNamespace' => basename($template));
  800
+
754 801
 		// Makes the rendered sub-templates available on the parent item,
755 802
 		// through $Content and $Layout placeholders.
756 803
 		foreach(array('Content', 'Layout') as $subtemplate) {
757 804
 			if(isset($this->chosenTemplates[$subtemplate])) {
758 805
 				$subtemplateViewer = new SSViewer($this->chosenTemplates[$subtemplate]);
759  
-				$item = $item->customise(array(
760  
-					$subtemplate => $subtemplateViewer->process($item, $cache)
761  
-				));
  806
+				$subtemplateViewer->setPartialCacheStore($this->getPartialCacheStore());
  807
+
  808
+				$internalArguments[$subtemplate] = $subtemplateViewer->process($item);
762 809
 			}
763 810
 		}
764 811
 
765  
-		$scope = new SSViewer_DataPresenter($item, array('I18NNamespace' => basename($template)));
766  
-		$val = "";
767  
-		
768  
-		include($cacheFile);
769  
-
  812
+		$val = $this->includeGeneratedTemplate($cacheFile, $item, $arguments ? array_merge($internalArguments, $arguments) : $internalArguments);
770 813
 		$output = Requirements::includeInHTML($template, $val);
771 814
 		
772 815
 		array_pop(SSViewer::$topLevel);
@@ -792,9 +835,9 @@ public function process($item, $cache = null) {
792 835
 	 * Execute the given template, passing it the given data.
793 836
 	 * Used by the <% include %> template tag to process templates.
794 837
 	 */
795  
-	static function execute_template($template, $data) {
  838
+	static function execute_template($template, $data, $arguments = null) {
796 839
 		$v = new SSViewer($template);
797  
-		return $v->process($data);
  840
+		return $v->process($data, $arguments);
798 841
 	}
799 842
 
800 843
 	static function parseTemplateContent($content, $template="") {			
@@ -848,7 +891,13 @@ public function __construct($content) {
848 891
 		$this->content = $content;
849 892
 	}
850 893
 	
851  
-	public function process($item, $cache = null) {
  894
+	public function process($item, $arguments = null) {
  895
+		if ($arguments && $arguments instanceof Zend_Cache_Core) {
  896
+			Deprecation::notice('3.0', 'Use setPartialCacheStore to override the partial cache storage backend, the second argument to process is now an array of variables.');
  897
+			$this->setPartialCacheStore($arguments);
  898
+			$arguments = null;
  899
+		}
  900
+
852 901
 		$template = SSViewer::parseTemplateContent($this->content, "string sha1=".sha1($this->content));
853 902
 
854 903
 		$tmpFile = tempnam(TEMP_FOLDER,"");
@@ -856,26 +905,9 @@ public function process($item, $cache = null) {
856 905
 		fwrite($fh, $template);
857 906
 		fclose($fh);
858 907
 
859  
-		if(isset($_GET['showtemplate']) && $_GET['showtemplate']) {
860  
-			$lines = file($tmpFile);
861  
-			echo "<h2>Template: $tmpFile</h2>";
862  
-			echo "<pre>";
863  
-			foreach($lines as $num => $line) {
864  
-				echo str_pad($num+1,5) . htmlentities($line, ENT_COMPAT, 'UTF-8');
865  
-			}
866  
-			echo "</pre>";
867  
-		}
  908
+		$val = $this->includeGeneratedTemplate($tmpFile, $item, $arguments);
868 909
 
869  
-		$scope = new SSViewer_DataPresenter($item);
870  
-		$val = "";
871  
-		$valStack = array();
872  
-		
873  
-		$cache = SS_Cache::factory('cacheblock');
874  
-		
875  
-		include($tmpFile);
876 910
 		unlink($tmpFile);
877  
-		
878  
-
879 911
 		return $val;
880 912
 	}
881 913
 }
4  view/ViewableData.php
@@ -328,12 +328,12 @@ public function renderWith($template, $customFields = null) {
328 328
 		
329 329
 		$data = ($this->customisedObject) ? $this->customisedObject : $this;
330 330
 		
331  
-		if(is_array($customFields) || $customFields instanceof ViewableData) {
  331
+		if($customFields instanceof ViewableData) {
332 332
 			$data = $data->customise($customFields);
333 333
 		}
334 334
 		
335 335
 		if($template instanceof SSViewer) {
336  
-			return $template->process($data);
  336
+			return $template->process($data, is_array($customFields) ? $customFields : null);
337 337
 		}
338 338
 		
339 339
 		throw new UnexpectedValueException (

0 notes on commit e4a043a

Please sign in to comment.
Something went wrong with that request. Please try again.