├── .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 | * | inc('item.php', array('thisItem' => $item)); ?> |
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 |
--------------------------------------------------------------------------------