├── .gitignore ├── lib ├── skittle_IResourceLocator.php ├── skittle_ClasspathResourceLocator.php ├── skittle_IHelperMapping.php ├── skittle_PathResourceLocator.php └── skittle_Skittle.php ├── README └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | .buildpath 2 | .project 3 | .settings 4 | ._* 5 | -------------------------------------------------------------------------------- /lib/skittle_IResourceLocator.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Skittle 2 | http://skittle-php.org/ 3 | 4 | Skittle is a lightweight pure PHP template inclusion library. Its primary goal 5 | is to allow for the easy inclusion of template fragments into other templates. 6 | Originally designed to be used with Halo, Skittle has been extracted so that 7 | it can be used on its own. 8 | -------------------------------------------------------------------------------- /lib/skittle_ClasspathResourceLocator.php: -------------------------------------------------------------------------------- 1 | 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010, Dragonfly Development Inc 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * Neither the name of Dragonfly Development Inc nor the names of its 13 | contributors may be used to endorse or promote products derived from 14 | this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 20 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 | POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /lib/skittle_PathResourceLocator.php: -------------------------------------------------------------------------------- 1 | callingFile = $callingFile; 42 | } 43 | if ( $paths !== null ) { 44 | if ( ! is_array($paths) ) { 45 | $paths = array($paths); 46 | } 47 | foreach ( $paths as $path ) { 48 | $this->paths[] = $path; 49 | } 50 | } 51 | if ( $prependedPaths !== null ) { 52 | if ( ! is_array($prependedPaths) ) { 53 | $prependedPaths = array($prependedPaths); 54 | } 55 | foreach ( $prependedPaths as $path ) { 56 | $this->prependedPaths[] = $path; 57 | } 58 | } 59 | if ( $appendedPaths !== null ) { 60 | if ( ! is_array($appendedPaths) ) { 61 | $appendedPaths = array($appendedPaths); 62 | } 63 | foreach ( $appendedPaths as $path ) { 64 | $this->appendedPaths[] = $path; 65 | } 66 | } 67 | } 68 | 69 | /** 70 | * Find a target file 71 | * @param string $target Target 72 | * @return string 73 | */ 74 | public function find($target, $realPath = false) { 75 | 76 | if ( strpos($target, '/') === 0 ) { 77 | if ( file_exists($target) ) return $realPath ? realpath($target) : $target; 78 | return null; 79 | } 80 | 81 | foreach ( $this->allPaths() as $path ) { 82 | $testLocation = $path . '/' . $target; 83 | // TODO This could possibly be cached eventually. 84 | if ( file_exists($testLocation) ) return $realPath ? realpath($testLocation) : $testLocation; 85 | } 86 | 87 | // Could not be found. 88 | return null; 89 | 90 | } 91 | 92 | /** 93 | * Paths to search 94 | * @return array 95 | */ 96 | public function allPaths() { 97 | $callingFiles = array(); 98 | if ( $this->callingFile ) $callingFiles[] = dirname($this->callingFile); 99 | return array_merge( 100 | $callingFiles, 101 | $this->prependedPaths(), 102 | $this->paths(), 103 | $this->appendedPaths() 104 | ); 105 | 106 | } 107 | 108 | /** 109 | * Paths 110 | * @return array 111 | */ 112 | public function paths() { 113 | return $this->paths; 114 | } 115 | 116 | /** 117 | * Prepend a path to the classpath 118 | * @param string $path Path 119 | */ 120 | public function prependPath($path) { 121 | array_unshift($this->prependedPaths, $path); 122 | } 123 | 124 | /** 125 | * Append a path to the classpath 126 | * @param string $path Path 127 | */ 128 | public function appendPath($path) { 129 | push($this->appendedPaths, $path); 130 | } 131 | 132 | /** 133 | * Prepended paths 134 | * @return array 135 | */ 136 | public function prependedPaths() { 137 | return $this->prependedPaths; 138 | } 139 | 140 | /** 141 | * Appended paths 142 | * @return array 143 | */ 144 | public function appendedPaths() { 145 | return $this->appendedPaths; 146 | } 147 | 148 | } -------------------------------------------------------------------------------- /lib/skittle_Skittle.php: -------------------------------------------------------------------------------- 1 | 17 | * 18 | * 19 | * 20 | * 21 | * 22 | * 23 | * 24 | * 25 | * 26 | * 27 | * 28 | * 29 | * 30 | *
31 | * p($thisItem['title']); ?> 32 | * p($thisItem['author']); ?> 33 | * p($thisItem['body']); ?> 34 | *
35 | *
36 | * 37 | * This same item.php template would be usable from another template that has 38 | * access to another model structure that looks like $thisItem. 39 | * 40 | * 41 | * 'Hello World', 44 | * 'author' => 'Beau Simensen', 45 | * 'body' => 'Yes, World, Hello indeed.' 46 | * ); 47 | * ?> 48 | *
49 | * inc('item.php', array('thisItem' => $myItem)); ?> 50 | *
51 | *
52 | * 53 | * @package skittle 54 | */ 55 | class skittle_Skittle { 56 | 57 | /** 58 | * Data in use at the current include level. 59 | * @var array 60 | */ 61 | protected $currentData = null; 62 | 63 | /** 64 | * Data exported at the current include level. 65 | * @var array 66 | */ 67 | protected $currentExportedData = null; 68 | 69 | /** 70 | * Tracks the data at each include level. 71 | * @var array 72 | */ 73 | protected $storedData = array(); 74 | 75 | /** 76 | * Tracks the exported data at each include level. 77 | * @var array 78 | */ 79 | protected $storedExportedData = array(); 80 | 81 | /** 82 | * Array of {@link skittle_IHelperMapping} instances. 83 | * @var array 84 | */ 85 | protected $helperMappings = array(); 86 | 87 | /** 88 | * Cache of currently loaded helpers. 89 | * @var array 90 | */ 91 | protected $helpers = array(); 92 | 93 | /** 94 | * Resource Locator 95 | * @var skittle_IResourceLocator 96 | */ 97 | protected $resourceLocator; 98 | 99 | /** 100 | * Name of the shell template 101 | * @var string 102 | */ 103 | protected $shell = null; 104 | 105 | /** 106 | * Name of the view content key 107 | * @var string 108 | */ 109 | protected $shellContentKey = 'skittleViewContent'; 110 | 111 | /** 112 | * Global data 113 | * @var array 114 | */ 115 | protected $globalData = array(); 116 | 117 | /** 118 | * Constructor. 119 | * @param mixed $resourceLocator List of directories or a skittle Resource Locator. 120 | */ 121 | public function __construct($resourceLocator = null) { 122 | 123 | if ( $resourceLocator === null ) { 124 | require_once('skittle_ClasspathResourceLocator.php'); 125 | $resourceLocator = new skittle_ClasspathResourceLocator(); 126 | // 127 | } elseif ($resourceLocator instanceof skittle_IResourceLocator) { 128 | // noop 129 | } elseif ( ! is_object($resourceLocator) ) { 130 | require_once('skittle_PathResourceLocator.php'); 131 | $resourceLocator = new skittle_PathResourceLocator(null, $resourceLocator); 132 | } 133 | 134 | $this->resourceLocator = $resourceLocator; 135 | 136 | } 137 | 138 | /** 139 | * Sets the shell to be wrapped around the include 140 | * 141 | * Only wraps the outermost include, so be certain that this method 142 | * is called when it makes sense. 143 | * 144 | * @param $shell 145 | * @param $key 146 | */ 147 | public function shell($shell, $key = null) { 148 | $this->shell = $shell; 149 | if ( $key !== null ) { $this->shellContentKey = $key; } 150 | } 151 | 152 | /** 153 | * Prints a shell include. 154 | * 155 | * If it is desired to have a shell wrap content, us this method. The shell 156 | * template will be expected to print out (likely raw) the variable 157 | * $renderedBody. 158 | * 159 | * 160 | * 161 | * Sample 162 | * 163 | *
164 | * 165 | * 166 | *
167 | * 168 | * @deprecated 169 | * @see stringShellInc() 170 | * @param string $shell Shell template name 171 | * @param string $body Body template name 172 | */ 173 | public function shellInc() { 174 | $this->deprecated(); 175 | $args = func_get_args(); 176 | print call_user_func_array(array($this, 'stringShellInc'), $args); 177 | } 178 | 179 | /** 180 | * Wrap a shell around a body template. 181 | * 182 | * If it is desired to have a shell wrap content, us this method. The shell 183 | * template will be expected to print out (likely raw) the variable 184 | * $renderedBody. 185 | * 186 | * This method returns a string and is called by {@link shellInc()}. 187 | * 188 | * @deprecated 189 | * @param string $shell Shell template name 190 | * @param string $body Body template name 191 | * @return string Rendered shell and body 192 | */ 193 | public function stringShellInc() { 194 | 195 | $this->deprecated(); 196 | 197 | $args = func_get_args(); 198 | 199 | $shell = array_shift($args); 200 | $body = array_shift($args); 201 | 202 | $model = null; 203 | 204 | if ( count($args) > 0 and is_array($args[0]) ) { 205 | // Make sure that we do not do any out of bounds stuff 206 | // with our args array. 207 | $model = $args[0]; 208 | } 209 | 210 | // Render the body into a string. 211 | $renderedBody = $this->stringInc($body, $model); 212 | 213 | // Add our rendered body to the model. 214 | $model['renderedBody'] = $renderedBody; 215 | 216 | // Render the shell to a string and pass that back. 217 | return $this->stringInc($shell, $model); 218 | 219 | } 220 | 221 | /** 222 | * Render a template. 223 | * 224 | * Includes a template. The model data passed will be extracted into 225 | * variables that the template can access. For example, given an 226 | * include that looks like: 227 | * 228 | * 229 | * $s->inc('path/to/template.php', array('hello' => 'world')); 230 | * 231 | * 232 | * The template path/to/template.php will be able to reference 233 | * a $hello variable. 234 | * 235 | * 236 | * Hello there, p($hello); ?> 237 | * 238 | * 239 | * This method renders a template and prints it inline. 240 | * 241 | * @param string $path Path to template to render 242 | * @param array $input Model data 243 | * @see stringInc() 244 | */ 245 | public function inc() { 246 | $args = func_get_args(); 247 | print call_user_func_array(array($this, 'stringInc'), $args); 248 | } 249 | 250 | /** 251 | * Render a template into a string. 252 | * 253 | * This method returns the rendered template as a string. 254 | * 255 | * @param string $path Path to template to render 256 | * @param array $input Model data 257 | * @see inc() 258 | */ 259 | public function stringInc($path, $input = null) { 260 | 261 | // The data always starts out containing our helpers. 262 | $d = $this->helpers; 263 | 264 | // $s is always our Skittle instance ($this). 265 | $s = $this; 266 | 267 | $args = func_get_args(); 268 | 269 | // Why do we support this? I cannot remember. :) 270 | if ( count($args) == 1 and is_array($args[0]) ) 271 | $args = $args[0]; 272 | 273 | // Path is always the first argument. 274 | $path = array_shift($args); 275 | 276 | if ( $this->currentData !== null ) { 277 | // If we have current data, it should be merged into our data. 278 | $d = array_merge($d, $this->currentData); 279 | } 280 | 281 | foreach ( $args as $data ) { 282 | // Assume that all remaining arguments are arrays that contain 283 | // additional model data. 284 | $d = array_merge($d, $data); 285 | } 286 | 287 | // Get the real path to the template. 288 | $realPath = $this->resourceLocator->find($path); 289 | 290 | // Final exported data (in case this is the last pass) 291 | $finalExportedData = array(); 292 | 293 | if ( $realPath and file_exists($realPath) ) { 294 | 295 | if ( $this->currentExportedData === null ) $this->currentExportedData = array(); 296 | 297 | // Remember our current data and our current exported data locally 298 | // so that we can restore these after we are finished rendering the 299 | // requested template. 300 | $lastData = $this->currentData; 301 | $lastExportedData = $this->currentExportedData; 302 | 303 | // The current values should now reflect our current model 304 | // data. We should also add this to our stack of stored data. 305 | $this->storedData[] = $this->currentData = $d; 306 | $this->storedExportedData[] = $this->currentExportedData; 307 | 308 | if ( $lastExportedData !== null ) { 309 | // If anything was exported at the previous level, we should 310 | // merge it into our data at this point. We do this so that 311 | // this exported data does not taint our current/last data. 312 | $d = array_merge($d, $lastExportedData); 313 | } 314 | 315 | // Extract our data into the symbol table. 316 | extract($d, EXTR_PREFIX_SAME, 'skittle'); 317 | 318 | ob_start(); 319 | 320 | $_____skittle_buffer = ''; 321 | 322 | include($realPath); 323 | 324 | $_____skittle_buffer .= ob_get_contents(); 325 | 326 | ob_end_clean(); 327 | 328 | // Get rid of the stored data. 329 | array_pop($this->storedData); 330 | array_pop($this->storedExportedData); 331 | 332 | // In case this is the last pass we want to know what 333 | // was exported. 334 | $finalExportedData = $this->currentExportedData; 335 | 336 | // Restore our data. 337 | $this->currentData = $lastData; 338 | $this->currentExportedData = $lastExportedData; 339 | 340 | // Clean up the skittle buffer. PHP has a weird habit of adding extra trailing 341 | // newlines that are not always desired. 342 | $renderedContent = preg_replace('/[\r\n]$/s', '', $_____skittle_buffer); 343 | 344 | } else { 345 | // If the include is not found, return an HTML comment so that there is 346 | // some debugging possible. 347 | // TODO This is maybe not the best way to handle this. An exception or an 348 | // error_log call may be more appropriate. Not all output will be HTML and 349 | // this could do more harm than good. 350 | $renderedContent = "\n"; 351 | } 352 | 353 | if ( $this->shell and $this->currentData === null ) { 354 | $shell = $this->shell; 355 | $this->shell = null; 356 | $this->globalData[$this->shellContentKey] = $renderedContent; 357 | $renderedContent = $this->stringInc($shell, array_merge($d, $finalExportedData, $this->globalData)); 358 | $this->globalData = array(); 359 | } 360 | return $renderedContent; 361 | 362 | } 363 | 364 | /** 365 | * Add a helper. 366 | * 367 | * Helpers need to be added before they can be used in a template. 368 | * 369 | * 370 | * $s->addHelper('uri'); 371 | * 372 | * 373 | * This method allows a helper to be added by name, and optionally specify an alias 374 | * name for a specific helper. For example, if for some reason $uri is too long of a 375 | * name for a helper object, the $uri helper object can be exposed at the $u variable 376 | * by specifying 'u' for the variable name and 'uri' as the helper name. 377 | * 378 | * 379 | * $s->addHelper('u', 'uri'); 380 | * 381 | * 382 | * @param string $variableName Variable name to be used 383 | * @param string $helperName Actual name of the helper if different from the variable name 384 | * @see getHelper() 385 | */ 386 | public function addHelper($variableName, $helperName = null) { 387 | if ( $helperName === null ) $helperName = $variableName; 388 | $this->helpers[$variableName] = $this->getHelper($helperName); 389 | } 390 | 391 | /** 392 | * Add a {@link skittle_IHelperMapping} instance. 393 | * @param skittle_IHelperMapping $helperMappings A {@link skittle_IHelperMapping} instance 394 | */ 395 | public function addHelperMapping($helperMapping) { 396 | return $this->addHelperMappings(array($helperMapping)); 397 | } 398 | 399 | /** 400 | * Add multiple {@link skittle_IHelperMapping} instances. 401 | * @param array $helperMappings An array of {@link skittle_IHelperMapping} instances 402 | */ 403 | public function addHelperMappings($helperMappings) { 404 | if ( ! is_array($helperMappings) ) { 405 | $helperMappings = array($helperMappings); 406 | } 407 | foreach ( $helperMappings as $helperMapping ) { 408 | $this->helperMappings[] = $helperMapping; 409 | } 410 | } 411 | 412 | /** 413 | * Add a {@link skittle_IHelperMapping} instance. 414 | * 415 | * This method is used internally to enforce that the object passed is actually 416 | * an instance of {@link skittle_IHelperMapping}. 417 | * @param skittle_IHelperMapping $helperMappings A {@link skittle_IHelperMapping} instance 418 | */ 419 | private function addHelperMappingInternal(skittle_IHelperMapping $helperMapping) { 420 | $this->helperMappings[] = $helperMapping; 421 | } 422 | 423 | /** 424 | * Retrieve a helper. 425 | * 426 | * Retrieves a helper by name. If the helper object is passed, that is used. Otherwise, 427 | * it will load the helper object from the {@link $helpers} map. 428 | * 429 | * @param string $helperName Name of helper to retrieve 430 | * @param object $helperObject Helper object 431 | */ 432 | protected function retrieveHelper($helperName, $helperObject = null) { 433 | if ( $helperObject === null ) { 434 | // It is assumed that this method will never be called unless 435 | // $helperName is known to exist in the helpers array. 436 | $helperObject = $this->helpers[$helperName]; 437 | } 438 | return $helperObject; 439 | } 440 | 441 | /** 442 | * @deprecated 443 | * @param $helperName 444 | * @param $exceptionOnMissing 445 | */ 446 | public function getHelper($helperName, $exceptionOnMissing = true) { 447 | return $this->deprecated()->helper($helperName, $exceptionOnMissing); 448 | } 449 | 450 | /** 451 | * Get a helper by name. 452 | * 453 | * If a helper has already been retrieved, it returns the cached instance. 454 | * 455 | * Otherwise, each of the helper mappings are queried until one returns something 456 | * other than null for the specified helper name. 457 | * 458 | * If no helper is found anywhere, one of two things can happen. If $exceptionOnMissing 459 | * is true (this is the default case), an exception is thrown. Otherwise, null is 460 | * returned. 461 | * 462 | * @param string $helperName Name of helper to get 463 | * @param boolean $exceptionOnMissing Throw an exception of the helper cannot be found 464 | */ 465 | public function helper($helperName, $exceptionOnMissing = true) { 466 | if ( array_key_exists($helperName, $this->helpers) ) { 467 | // If this helper already exists, we can retrieve the helper. 468 | return $this->retrieveHelper($helperName); 469 | } 470 | foreach ( $this->helperMappings as $helperMapping ) { 471 | // Ask each helper mapping for this helper. 472 | $helper = $helperMapping->getHelper($helperName); 473 | if ( $helper !== null ) { 474 | // If the helper was found, store it for us to use 475 | // later and then retrieve the helper. 476 | $this->helpers[$helperName] = $helper; 477 | return $this->retrieveHelper($helperName); 478 | } 479 | } 480 | if ( $exceptionOnMissing ) { 481 | throw new Exception('Could not locate helper named "' . $helperName . '"'); 482 | } 483 | return null; 484 | } 485 | 486 | /** 487 | * Get all of the helpers. 488 | * 489 | * This method will query all of the helper mappings to get all of the available 490 | * helpers for each of them. This method is very heavy handed and should probably 491 | * be avoided, especially if there are some costly helpers to create. 492 | * @return array All helpers available from all of the helper mappings. 493 | */ 494 | public function getHelpers() { 495 | $allHelpers = array(); 496 | foreach ( $this->helperMappings as $helperMapping ) { 497 | foreach ( $helperMapping->getHelperNames() as $helperName ) { 498 | // TODO We might want to handle this differently so that naming 499 | // collisions are handled in a consistent way. 500 | $allHelpers[$helperName] = $this->retrieveHelper( 501 | $helperName, 502 | $helperMapping->getHelper($helperName) 503 | ); 504 | } 505 | } 506 | return $allHelpers; 507 | } 508 | 509 | /** 510 | * Safely print a string. 511 | * @param string $string String to print 512 | * @param boolean $htmlSafe Should the string be made HTML safe? 513 | * @see g 514 | */ 515 | public function p($string, $htmlSafe = null) { 516 | print $this->g($string, $htmlSafe); 517 | } 518 | 519 | /** 520 | * Get a string. 521 | * @param string $string String to get 522 | * @param boolean $htmlSafe Should the string be made HTML safe? 523 | * @see p 524 | */ 525 | public function g($string, $htmlSafe = null) { 526 | if ( $htmlSafe === null or $htmlSafe ) $string = htmlspecialchars($string); 527 | return $string; 528 | } 529 | 530 | /** 531 | * Export a named value to current namespace. 532 | * 533 | * Used in the case where a value needs to be exported into the 534 | * template system to potentially be used by sub templates. 535 | * 536 | * $foo = $s->export('foo', 'Hello World!'); 537 | * 538 | * @param string $name Name of value 539 | * @param mixed $value Actual value 540 | * @return mixed Value. 541 | */ 542 | public function export($name, $value = null) { 543 | return $this->currentExportedData[$name] = $value; 544 | } 545 | 546 | /** 547 | * Set a named value in the global scope 548 | * 549 | * Values set here will be extracted for the shell (if one is used) 550 | * and can also be retrieved using get(). 551 | * 552 | * @param $name 553 | * @param $value 554 | */ 555 | public function set($name, $value = null) { 556 | return $this->globalData[$name] = $value; 557 | } 558 | 559 | /** 560 | * Get a named value from the global scope 561 | * @param string $name 562 | */ 563 | public function get($name) { 564 | return isset($this->globalData[$name]) ? $this->globalData[$name] : null; 565 | } 566 | 567 | /** 568 | * Used when a method has been deprecated 569 | * @return skittle_Skittle 570 | */ 571 | private function deprecated() { 572 | return $this; 573 | } 574 | 575 | } 576 | --------------------------------------------------------------------------------
inc('item.php', array('thisItem' => $item)); ?>