PageRenderTime 38ms CodeModel.GetById 10ms app.highlight 19ms RepoModel.GetById 1ms app.codeStats 0ms

/phocoa/framework/WFSkin.php

https://github.com/apinstein/phocoa
PHP | 789 lines | 302 code | 64 blank | 423 comment | 33 complexity | af4615ddb2cb6507d669654e8a68db72 MD5 | raw file
  1<?php
  2/* vim: set expandtab tabstop=4 shiftwidth=4: */
  3/**
  4 * Skin System.
  5 *
  6 * A collection of classes and infrastructure for supporting skins of any layout, with multiple "themes" per skin.
  7 *
  8 * The system ships with a simple skin, "default", which is a simple wrapper. You can create your own skins.
  9 *
 10 * @package framework-base
 11 * @subpackage Skin
 12 * @copyright Alan Pinstein 2005
 13 * @author Alan Pinstein <apinstein@mac.com>
 14 * @version $Id: skin.php,v 1.37 2005/03/23 20:31:01 alanpinstein Exp $
 15 */
 16
 17/**
 18 * Skin Manifest abstract interface. Each skin will need to have a concrete subclass to provide the system with needed information about itself.
 19 *
 20 * For a full explanation on the Skin infrastructure, including how to set up Skin Types, Skins, and Themes, see {@link WFSkin}.
 21 *
 22 * @see WFSkin
 23 *
 24 */
 25abstract class WFSkinManifestDelegate extends WFObject
 26{
 27    /**
 28     * Get the DEFAULT theme of for skin.
 29     *
 30     * This method is REQUIRED.
 31     *
 32     * @return string - The DEFAULT theme for this skin.
 33     */
 34    abstract function defaultTheme();
 35
 36    /**
 37     * Get a list of themes for the skin.
 38     *
 39     * This is an optional method.
 40     *
 41     * @return array A list of all themes supported for this skin.
 42     */
 43    function themes() { return array(); }
 44
 45    /**
 46     * Load the theme information for this skin.
 47     *
 48     * The theme information is a simple associative array of variables used by the skin. Good uses for this include
 49     * colorscheme information.
 50     *
 51     * This is an optional method.
 52     *
 53     * @param string $theme Theme information to retrieve.
 54     * @return assoc_array Various name/value pairs to be used in the templates for the skin.
 55     */
 56    function loadTheme($theme) { return array(); }
 57}
 58
 59/**
 60 * Delegate interface for the skin object.
 61 *
 62 * The skin delegate provides the skin system with a way of extending the skin's capabilities. Essentially, the WFSkin object is a framework for building "themed" pages.
 63 * The parts of each skin that are provided by the skin infrastructure can be easily customized using the skin delegate system.
 64 *
 65 * The main web application mechanism always uses the skin delegate provided by {@link WFApplicationDelegate}. However, an application may have multiple skin delegates
 66 * for multiple skinned usages. For instance, maybe you have a need to send skinned email, but the skins for email have a different setup than the normal web site.
 67 * In this case, you could create a skin object and provide a specialized skin delegate to handle the skinnable email function.
 68 * 
 69 * You may also find that you need more template types besides "normal" and "raw". WFSkinDelegate allows you to manifest additional templateTypes(). Each skin
 70 * you create for that WFSkinDelegate will of course need to implement all of the templateTypes() that the skinDelegate supports.
 71 */
 72class WFSkinDelegate
 73{
 74    /**
 75     * Retreive the "named" content from the skin delegate.
 76     *
 77     * The "named content" mechanism is the way by which individual applications using this framework can add additional content sections
 78     * for use in their skins. By default, only a HEAD and BODY section exist within the skin.
 79     * Individual applications can use the named content mechanism to supply skin-specific information such as NAVIGATION LINKS, COPYRIGHT DISCLAIMERS, etc.
 80     *
 81     * @param string Name of the content to retrieve.
 82     * @param assoc_array Optional parameter list. Name/value pairs to pass on to content generator.
 83     * @return mixed The named content as provided byt he skin delegate.
 84     */
 85    function namedContent($name, $params = NULL) {}
 86
 87    /**
 88     * Get a list of all named content for the skin delegate.
 89     *
 90     * @return array A list of all named content items in the catalog for this skin delegate.
 91     */
 92    function namedContentList() {}
 93
 94    /**
 95     * A delegate method to allow the delegate object to load default values for certain skin properties such as:
 96     * skin, skinTheme, metaDescription, metaKeywords, title etc.
 97     * Example:
 98     *
 99     * $skin->setSkin('exampleskin2');
100     * $skin->setTheme('red');
101     * $skin->setMetaDescription('This is the default description.');
102     * $skin->addMetaKeywords(array('more', 'keywords'));
103     * $skin->setTitle('Page Title');
104     *
105     * @param object WFSkin The skin object to load defaults for.
106     */
107    function loadDefaults($skin) {}
108
109    /**
110     *  Get a list of the additional template types (besides "normal" and "raw") that are supported by this skin.
111     *
112     *  @return array An array of strings; each is a template type that is supported by this skin.
113     *                For each skin, there must be a template_<templateType>.tpl file for each manifested template type.
114     */
115    function templateTypes() { return array(); }
116
117    /**
118     * Callback method which is called just before the skin is rendered.
119     *
120     * This allows your skin delegate to calculate anything it might need to pass on to the skin for use in rendering.
121     *
122     * @param object WFSkin The skin object that will be rendered.
123     */
124    function willRender($skin) {}
125}
126
127/**
128 * Main skin class for managing skin wrappers around the content.
129 *
130 * The Web Application's WFRequestController object always uses exactly one skin instance to render the page.
131 * However, your application may choose to create other skin instances to use the infrastructure for things like HTML email, etc.
132 *
133 * The Skin mechanism is broken down into three layers. Each layer provides the ability to swap behaviors/looks at runtime.
134 * For each request, one of each layer must be specified.
135 *
136 * 1. Skin Type -- i.e., which SkinDelegate is used. The Skin Delegate provides the skin with its catalog of behaviors, i.e., menus, footers, etc.
137 *                 Each skin type is unique, and skins must be written specifically for each Skin Type.
138 *                 Most web sites have just one skin type, that handles the elements appropriate for the skins of that application.
139 *                 However, sometimes there is a need for a single site to have multiple skins. For instance, the public site may have different
140 *                 navigational needs than the back-end admin interface.
141 *                 A {@link WFSkinDelegate} is implemented for each skin type an tells the system what data is available for the skin type. 
142 *                 Skin Types, however, do NOT provide any layout or style.
143 *                 By default, each Skin Type implements only the "normal" template type. Your Skin Type may need additional layouts. For instance, printer-friendly
144 *                 or minimal layouts used for popups. Your skin can support additiona template types via {@link WFSkinDelegate::templateTypes() templateTypes()}.
145 *                 Every Skin for a Skin Type must have a template file for all template types to ensure proper operation.
146 * 2. Skin -- A skin provides basic layout for a given Skin Type. Skins are specific for Skin Types, since they necessarily know about the 
147 *            data types offered by a particular skin type, via its Skin Delegate.
148 *            Each Skin resides in its own directory inside the Skin Type directory that it belongs to.
149 *            Each Skin thus provides a template file that implements a layout. Skins may also have multiple Skin Themes.
150 *            Each Skin has a {@link WFSkinManifestDelegate SkinManifestDelegate} which tells the system which themes are available, and which theme to use by default.
151 * 3. Skin Themes -- It may be desirable for a skin to have multiple colorschemes or other minor "thematic" differences. Each skin must have at least one theme.
152 *                   Infrastructure is provided so that the SkinManifestDelegate can easily supply different data to the skin based on the theme. This allows easy creation
153 *                   of colorschemes or other thematic differences.
154 *
155 * <pre>
156 * Skin Directory Structure:
157 *
158 * The skins have a specified, hierarchical directory structure, based in the "skins" directory.
159 * skins/ - Contains only directories; each directory represents a Skin Type.
160 * skins/&lt;skinType&gt;/ - For each skin type, pick a unique name and create a directory.
161 * skins/&lt;skinType&gt;/www/ - For each skin type, css that is shared across all skins.
162 * skins/&lt;skinType&gt;/&lt;skinType&gt;_SkinDelegate.php - A php file containing exactly one class, named &lt;skinType&gt;_SkinDelegate, that is the {@link WFSkinDelegate} for the Skin Type.
163 * skins/&lt;skinType&gt;/&lt;skinName&gt;/ - Also in the skinType directory are other directories, one for each skin that can be used for the Skin Type.
164 * skins/&lt;skinType&gt;/&lt;skinName&gt;/&lt;skinName&gt;_SkinManifestDelegate.php - The {@link WFSkinManifestDelegate} for the skin &lt;skinName&gt;.
165 * skins/&lt;skinType&gt;/&lt;skinName&gt;/* - Other files in here are the various tpl and css files used for this skin.
166 * skins/&lt;skinType&gt;/&lt;skinName&gt;/www/ - Web root of the skin. Nothing actually goes in this folder but other folders.
167 * skins/&lt;skinType&gt;/&lt;skinName&gt;/www/shared/* - Files that need to be accesible to the WWW and are shared by multiple themes of this skin go here.
168 * skins/&lt;skinType&gt;/&lt;skinName&gt;/www/&lt;themeName&gt;/* - Files that need to be accessible to the WWW and are specific to a theme go here.  Each theme has its own folder to contain "themed" versions of resources. Typically every theme has the same set of resources, but of course customized for that theme.
169 *
170 * To use WWW visible items in your pages, simply use {$skinDir}/myImage.jpg and {$skinDirShared}/mySharedImage.jpg in your templates. The skin system automatically assigns these vars.
171 * skinDir maps to skins/&lt;skinType&gt;/&lt;skinName&gt;/www/&lt;themeName&gt;/
172 * skinDirShared maps to skins/&lt;skinType&gt;/&lt;skinName&gt;/www/shared/
173 * </pre>
174 *
175 * @see WFSkinDelegate, WFSkinManifestDelegate
176 */
177class WFSkin extends WFObject
178{
179    /**
180     * The "normal" templateType: template_normal.tpl
181     */
182    const SKIN_WRAPPER_TYPE_NORMAL  = 'normal';
183    /**
184     * The "raw" templateType: the exact page contents will be displayed; equivalent to using no skin.
185     */
186    const SKIN_WRAPPER_TYPE_RAW     = 'raw';
187
188    /**
189     * @var string The skin delegate name to use for this instance.
190     */
191    protected $delegateName;
192    /**
193     * @var string The skin to use for this instance.
194     */
195    protected $skinName;
196    /**
197     * @var string The theme of the skin to use.
198     */
199    protected $skinThemeName;
200    /**
201     * @var object The {@link WFSkinDelegate delegate object} for this skin.
202     */
203    protected $delegate;
204    /**
205     * @var object The SkinManifestDelegate for the current skin.
206     */
207    protected $skinManifestDelegate;
208    /**
209     * @var string The body content for the skin. This is the only "predefined" content element of the skin infrastructure.
210     */
211    protected $body;
212    /**
213     * @var string The TITLE of the skin. This will be used automatically as the HTML title.
214     */
215    protected $title;
216    /**
217     * @var array A list of META KEYWORDS for HTML skins.
218     */
219    protected $metaKeywords;
220    /**
221     * @var string The META DESCRIPTION for HTML skins.
222     */
223    protected $metaDescription;
224    /**
225     * @var integer Skin wrapper type. One of {@link WFSkin::SKIN_WRAPPER_TYPE_NORMAL}, {@link WFSkin::SKIN_WRAPPER_TYPE_RAW}, or a custom type.
226     */
227    protected $templateType;
228    /**
229     * @var string The absolute filesystem path to a tpl file that is automatically added to the "head" element of all skins of this skin type.
230     */
231    protected $headTemplate;
232    /**
233     * @var array An array of strings of things that needed to be added to the <head> section.
234     */
235    protected $headStrings;
236    /**
237     * @var arrary An associative array of "named" content chunks.
238     */
239    protected $namedContent;
240
241    /**
242     * @var string The character set used for encoding strings.
243     */
244    protected $charset;
245
246    function __construct()
247    {
248        // determine which skin to use
249        $wa = WFWebApplication::sharedWebApplication();
250        $this->skinName = 'default';
251
252        $this->delegate = NULL;
253        $this->skinManifestDelegate = NULL;
254        $this->body = NULL;
255        $this->templateType = WFSkin::SKIN_WRAPPER_TYPE_NORMAL;
256        $this->namedContent = array();
257
258        $this->title = NULL;
259        $this->metaKeywords = array();
260        $this->metaDescription = NULL;
261        $this->headStrings = array();
262        $this->headTemplate = WFWebApplication::appDirPath(WFWebApplication::DIR_SMARTY) . '/head.tpl';
263        $this->charset = "ISO-8859-1";
264    }
265
266    /**
267     * Set the which template file of the current skin will be used to render the skin.
268     *
269     * - (default) {@link WFSkin::SKIN_WRAPPER_TYPE_NORMAL}, which maps to "template.tpl".
270     * - {@link WFSkin::SKIN_WRAPPER_TYPE_RAW}, which will output the body contents only. This is logically equivalent to using no skin.
271     * - Any other string you pass will use the file "template_<template_type>.tpl in the skin directory.
272     *
273     * Potential uses for this include:
274     *
275     * - Using {@link WFSkin::SKIN_WRAPPER_TYPE_RAW} to return HTML snippets that will be used in AJAX callback
276     * - Using a custom "minimal" file that is used for popup windows where there is not enough real estate for the full skin.
277     * - Using a custom "mobile" file that would be used for mobile devices.
278     *
279     * Any custom templates must be manifested by the skin delegate {@link WFSkinDelegate::tempateTypes() templateTypes()} method.
280     *
281     * @param string The name of the template to use. One of {@link WFSkin::SKIN_WRAPPER_TYPE_NORMAL}, {@link WFSkin::SKIN_WRAPPER_TYPE_RAW}, or a custom string.
282     * @throws object WFException if the template of the given name does not exist for this skin
283     */
284    function setTemplateType($templateType)
285    {
286        $allowedTemplates = $this->templateTypes();
287        if (!in_array($templateType, $allowedTemplates)) throw( new WFException("Template type: '{$templateType}' does not exist for skin '" . $this->skinName() . "'.") );
288        $this->templateType = $templateType;
289    }
290
291    /**
292     *  Get a list of all template types available for this skin.
293     *
294     *  This list will include WFSkin::SKIN_WRAPPER_TYPE_NORMAL, WFSkin::SKIN_WRAPPER_TYPE_RAW, and any custom template types manifested by the skin type delegate (skin manifest delegate).
295     *
296     *  @return array An array of strings with the names of all valid template types.
297     */
298    function templateTypes()
299    {
300        $allowedTemplates = array(WFSkin::SKIN_WRAPPER_TYPE_NORMAL, WFSkin::SKIN_WRAPPER_TYPE_RAW);
301        // call skin type delegate to get list of template types -- delegate implements application-specific logic.
302        if (is_object($this->delegate) && method_exists($this->delegate, 'templateTypes')) {
303            $allowedTemplates = array_merge($allowedTemplates, $this->delegate->templateTypes());
304        }
305        return $allowedTemplates;
306    }
307
308    /**
309     *  Set the skin's delegate by passing the NAME of the skin delegate.
310     *
311     *  This function will look for the skin delegate in the appropriate place, instantiate it, and set it up for this skin instance.
312     *
313     *  NOTE: Calling this function may overwrite any existing skin settings, since the loadDefaults() function may overwrite title, meta tags, etc.
314     *  For best results, always call setDelegateName() BEFORE making adjustments to the WFSkin object.
315     *
316     *  @param string The NAME of the Skin Type.
317     *  @throws object Exception if the skin delegate does not exist, or it does not contain the skin delegate class.
318     */
319    function setDelegateName($skinDelegateName)
320    {
321        $this->delegateName = $skinDelegateName;
322        // change name to our convention
323        $skinDelegateFileClassName = $skinDelegateName . '_SkinDelegate';
324        // load skin class -- in lieu of require_once, do this
325        if (!class_exists($skinDelegateFileClassName))
326        {
327            $skinsDir = WFWebApplication::appDirPath(WFWebApplication::DIR_SKINS);
328            $skinDelegatePath = $skinsDir . '/' . $skinDelegateName . '/' . $skinDelegateFileClassName . '.php';
329            if (!file_exists($skinDelegatePath)) throw( new Exception("Skin Delegate {$skinDelegateName} file {$skinDelegatePath} does not exist.") );
330            require($skinDelegatePath);
331        }
332        if (!class_exists($skinDelegateFileClassName)) throw( new Exception("Skin Delegate class {$skinDelegateFileClassName} does not exist.") );
333        $this->setDelegate(new $skinDelegateFileClassName());
334    }
335
336    /**
337     *  Get the name of the Skin Type for the current instance.
338     *
339     *  @return string The name of the current skin type.
340     */
341    function delegateName()
342    {
343        return $this->delegateName;
344    }
345
346    /**
347     *  Get the delegate instance.
348     *
349     *  @return object WFSkinDelegate A WFSkinDelegate instance.
350     */
351    function delegate()
352    {
353        return $this->delegate;
354    }
355
356    /**
357     * Assign a skin delegate for this instance.
358     * @param object The skin delegate.
359     */
360    function setDelegate($skinDelegate)
361    {
362        $this->delegate = $skinDelegate;
363        $this->loadDefaults();
364    }
365
366    /**
367     * Set the skin to the given name. Will automatically load the skin and its default theme.
368     * @param string The name of the skin to use.
369     */
370    function setSkin($skinName)
371    {
372        $this->skinName = $skinName;
373        $this->loadSkin();
374    }
375
376    /**
377     *  Set the theme to use.
378     *
379     *  @param string The name of the theme of the skin to use.
380     */
381    function setTheme($skinThemeName)
382    {
383        $this->skinThemeName = $skinThemeName;
384    }
385
386    function getskinThemeName()
387    {
388        return $this->skinThemeName;
389    }
390
391    /**
392     *  Get the current skin name
393     *
394     *  @return string The name of the current skin.
395     */
396    function skinName()
397    {
398        return $this->skinName;
399    }
400
401    /**
402     * Load the current skin.
403     * @internal
404     */
405    function loadSkin()
406    {
407        // load the current skin
408        $skinsDir = WFWebApplication::appDirPath(WFWebApplication::DIR_SKINS);
409        $skinManifestDelegateFileClassName = $this->skinName . '_SkinManifestDelegate';
410        
411        // in lieu of require_once
412        if (!class_exists($skinManifestDelegateFileClassName))
413        {
414            $skinManifestDelegatePath = $skinsDir . '/' . $this->delegateName . '/' . $this->skinName . '/' . $skinManifestDelegateFileClassName . '.php';
415            if (!file_exists($skinManifestDelegatePath)) throw( new Exception("Skin manifest delegate file does not exist: $skinManifestDelegatePath.") );
416            require($skinManifestDelegatePath);
417        }
418
419        // instantiate the skin manifest delegate
420        if (!class_exists($skinManifestDelegateFileClassName)) throw( new Exception("Skin manifest delegate class does not exist: {$skinManifestDelegateFileClassName}."));
421        $this->skinManifestDelegate = new $skinManifestDelegateFileClassName();
422
423        // make sure a theme is selected
424        if (empty($this->skinThemeName)) $this->skinThemeName = $this->skinManifestDelegate->defaultTheme();
425    }
426
427    /**
428     *  Add a string that needs to go in the page's head section.
429     *
430     *  @param string The string to go in the head section.
431     */
432    function addHeadString($string)
433    {
434        // de-duplicate
435        $this->headStrings[$string] = $string;
436    }
437
438    /**
439     * Set the content for the skin to wrap. Typically this is HTML but could be anything.
440     * @param string The content of the skin.
441     */
442    function setBody($html)
443    {
444        $this->body = $html;
445    }
446
447    /**
448     * Set the title of the page. This is the HTML title if you are building an HTML skin.
449     * @param string The title of the page.
450     */
451    function setTitle($title)
452    {
453        $this->title = htmlentities($title, ENT_COMPAT, $this->charset);
454    }
455
456    /**
457     * Set the template file to be added to the "head" element of every page. Defaults to the built-in template file that sets up various PHOCOA things.
458     *
459     * If you want to include the default head content, use {$skinPhocoaHeadTpl} in your custom head template file.
460     *
461     * Typically this function would be called from your SkinDelegate's loadDefaults() function.
462     *
463     * @param string Absolute path to new head template file.
464     */
465    function setHeadTemplate($path)
466    {
467        $this->headTemplate = $path;
468    }
469
470    /**
471     * Add meta keywords to the skin.
472     * @param array A list of keywords to add.
473     */
474    function addMetaKeywords($keywords)
475    {
476        $this->metaKeywords = array_merge($keywords, $this->metaKeywords);
477    }
478
479    /**
480     * Set the META DESCRIPTION of the page.
481     * @param string The description of the page.
482     */
483    function setMetaDescription($description)
484    {
485        $this->metaDescription = $description;
486    }
487
488	/**
489	 * @deprecated
490     * @see getSkinThemeAssetsDir()
491	 */
492	function getSkinDir()
493	{
494		return $this->getSkinThemeAssetsDir();
495	}
496
497	/**
498     * @deprecated
499     * @see getSkinSharedAssetsDir
500	 */
501	function getSkinDirShared()
502	{
503        return $this->getSkinSharedAssetsDir();
504	}
505
506	/**
507	 * @return string www-accessible path to the skin type assets dir (<skintype>/www)
508	 */
509	function getSkinTypeAssetsDir()
510	{
511		return WWW_ROOT . '/skins/' . $this->delegateName . '/www';
512	}
513
514	/**
515	 * @return string www-accessible path to the skin shared assets dir (<skintype>/<skin>/www/<shared>)
516	 */
517    function getSkinSharedAssetsDir()
518    {
519		return WWW_ROOT . '/skins/' . $this->delegateName . '/' . $this->skinName . '/shared';
520    }
521
522	/**
523	 * @return string www-accessible path to the skin theme assets dir (<skintype>/<skin>/www/<theme>)
524	 */
525    function getSkinThemeAssetsDir()
526    {
527		return WWW_ROOT . '/skins/' . $this->delegateName . '/' . $this->skinName . '/' . $this->skinThemeName;
528    }
529
530    /**
531     * @return string filesystem-accessible path to the skin directory. This is useful as a base dir for factoring out tpl code that's shared among different templateTypes.
532     */
533    function getSkinTemplatesDir()
534    {
535        return "{$this->getSkinTypeDir()}/{$this->skinName}";
536    }
537
538    /**
539     * @return string filesystem-accessible path to the skin type directory. This is useful for shared components *across* skins.
540     */
541    function getSkinTypeDir()
542    {
543        return WFWebApplication::appDirPath(WFWebApplication::DIR_SKINS) . '/' . $this->delegateName;
544    }
545
546    /**
547     * Set the charset string;
548     *
549     *
550     * @param charaset The character set string.
551     * @return object WFSkin (fluent interface).
552     */
553    function setCharset($charset)
554    {
555        $this->charset = $charset;
556        return $this;
557    }
558
559    /**
560     * Render the skin.
561     * @param boolean TRUE to display the results to the output buffer, FALSE to return them in a variable. DEFAULT: TRUE.
562     * @return string The rendered view. NULL if displaying.
563     * @todo convert the DIR_SMARTY calls to use a new WFWebApplication::getResource($path) infrastructure that will allow for userland overloads of these templates
564     */
565    function render($display = true)
566    {
567        $this->loadSkin();
568
569        $skinTemplatesDir = $this->getSkinTemplatesDir();
570
571        $smarty = new WFSmarty();
572        $smarty->assign('skin', $this);
573
574        // add variables to smarty
575        $themeVars = $this->skinManifestDelegate->loadTheme($this->skinThemeName);
576        $smarty->assign('skinThemeVars', $themeVars);
577        $smarty->assign('skinTitle', $this->title);
578        $smarty->assign('skinMetaKeywords', join(',', $this->metaKeywords));
579        $smarty->assign('skinMetaDescription', $this->metaDescription);
580        $smarty->assign('skinCharset', $this->charset);
581        $smarty->assign('skinBody', $this->body);
582        $smarty->assign('skinHeadStrings', join("\n", array_values($this->headStrings)));
583        $smarty->assign('phocoaDebug', WFWebApplication::sharedWebApplication()->debug());
584
585        // set up shared directory URLs
586        // deprecated
587        $smarty->assign('skinDir', $this->getSkinDir() );
588        $smarty->assign('skinDirShared', $this->getSkinDirShared() );
589        // new names
590        $smarty->assign('skinTypeAssetsDir', $this->getSkinTypeAssetsDir() );
591        $smarty->assign('skinSharedAssetsDir', $this->getSkinSharedAssetsDir() );
592        $smarty->assign('skinThemeAssetsDir', $this->getSkinThemeAssetsDir() );
593        // FS paths of things
594        $smarty->assign('skinTemplatesDir', $skinTemplatesDir);
595        $smarty->assign('skinTypeDir', $this->getSkinTypeDir());
596
597        // build the <head> section
598        $smarty->assign('skinPhocoaHeadTpl', WFWebApplication::appDirPath(WFWebApplication::DIR_SMARTY) . '/head.tpl');
599        $smarty->assign('skinHead', $smarty->fetch($this->headTemplate));
600
601        // set the template
602        if ($this->templateType == WFSkin::SKIN_WRAPPER_TYPE_RAW)
603        {
604            $smarty->setTemplate(WFWebApplication::appDirPath(WFWebApplication::DIR_SMARTY) . '/template_raw.tpl');
605        }
606        else
607        {
608            $smarty->setTemplate($skinTemplatesDir . '/template_' . $this->templateType . '.tpl');
609        }
610
611        // pre-render callback
612        $this->willRender();
613
614        // render smarty
615        if ($display) {
616            $smarty->render();
617        } else {
618            return $smarty->render(false);
619        }
620    }
621
622    /**
623     * Get a list of all installed skin types.
624     * @static
625     * @return array Skin Types are installed.
626     */
627    function installedSkinTypes()
628    {
629        $skinTypes = array();
630
631        $skinsDir = WFWebApplication::appDirPath(WFWebApplication::DIR_SKINS);
632        $dh = opendir($skinsDir);
633        if ($dh) {
634            while ( ($file = readdir($dh)) !== false ) {
635                if (is_dir($skinsDir . '/' . $file) and !in_array($file, array('.','..'))) {
636                    array_push($skinTypes, $file);
637                }
638            }
639            closedir($dh);
640        }
641
642        return $skinTypes;
643    }
644
645    /**
646     * Get a list of all installed skins for the current Skin Type.
647     *
648     * @return array Skins that are installed.
649     */
650    function installedSkins()
651    {
652        $skins = array();
653
654        $skinsDir = WFWebApplication::appDirPath(WFWebApplication::DIR_SKINS);
655        $skinDirPath = $skinsDir . '/' . $this->delegateName;
656        $dh = opendir($skinDirPath);
657        if ($dh) {
658            while ( ($file = readdir($dh)) !== false ) {
659                if (is_dir($skinDirPath . '/' . $file) and !in_array($file, array('.','..'))) {
660                    array_push($skins, $file);
661                }
662            }
663            closedir($dh);
664        }
665
666        return $skins;
667    }
668
669    /**
670     * Allow the skin delegate to load the default values for this skin.
671     * @see WFSkinDelegate::loadDefaults
672     */
673    function loadDefaults()
674    {
675        // call skin delegate to get skin to use -- delegate implements application-specific logic.
676        if (is_object($this->delegate) && method_exists($this->delegate, 'loadDefaults')) {
677            $this->delegate->loadDefaults($this);
678        }
679    }
680
681    /**
682     * Get the catalog (ie list) of named content for this skin.
683     *
684     * This is a merge of all names from the delegate and any existing content that's already been added programmatically to WFSkin.
685     *
686     * If the skin delegate supports additional content for the skin, the catalog of content is provided here. Mostly this is for documentation purposes.
687     * @see WFSkinDelegate::namedContentList
688     * @return array Array of strings; each entry is a name of a content driver for this skin delegate.
689     */
690    function namedContentList()
691    {
692        $keys = array_keys($this->namedContent);
693        if (is_object($this->delegate) && method_exists($this->delegate, 'namedContentList')) {
694            $keys = array_merge($keys, $this->delegate->namedContentList());
695        }
696        return $keys;
697    }
698
699    /**
700     * Get the named content.
701     *
702     * This will use the value from the delegate *first*, and if one is not found (ie NULL is returned) then it will also 
703     * look for a value in the local namedContent list.
704     *
705     * @see WFSkinDelegate::namedContent
706     * @param string The name of the content to retrieve.
707     * @param assoc_array A list of additional parameters.
708     * @return mixed The content for the named content for this skin instance. Provided by the delegate.
709     */
710    function namedContent($name, $options = NULL)
711    {
712        $val = NULL;
713        if (is_object($this->delegate) && method_exists($this->delegate, 'namedContent')) {
714            $val = $this->delegate->namedContent($name, $options);
715        }
716        if ($val === NULL) {
717            if (isset($this->namedContent[$name])) {
718                $val = $this->namedContent[$name];
719            }
720        }
721        return $val;
722    }
723
724    /**
725     * Set a value for a "namedContent" entry.
726     *
727     * If the namedContent is an array, the entry will be appended.
728     *
729     * To "set up" a namedContent as an array, the skin delegate should call {@link WFSkin::initializeNamedContentAsArray()}.
730     *
731     * @param mixed The "content" of the namedContent you are setting.
732     * @param string The "name" of the namedContent you are setting.
733     * @return object WFSkin (fluent interface).
734     */
735    function setContentForName($content, $name)
736    {
737        $curVal = $this->namedContent($name);
738        if (is_array($curVal)) {
739            $this->namedContent[$name][] = $content;
740        } else {
741            $this->namedContent[$name] = $content;
742        }
743        return $this;
744    }
745
746    /**
747     * Test whether there is a value set for the namedContent passed.
748     *
749     * NOTE: at present this only looks *locally* in the WFSkin. It does not call through
750     * to the delegate.
751     *
752     * The idea behind this is that it's much more efficient that testing namedContent('name').
753     *
754     * @param string The "name" of the namedContent you are inquiring about.
755     * @return boolean
756     * @todo add support for hasNamedContent to WFSkinDelegate.
757     */
758    function hasNamedContent($name)
759    {
760        return isset($this->namedContent[$name]);
761    }
762
763    /**
764     * Mark a "namedContent" chunk as an array so that when used by pages the values will be concatentated rather than last-one-wins.
765     *
766     * @param string The "name" of the "namedContent".
767     * @return object WFSkin (fluent interface).
768     */
769    function initializeNamedContentAsArray($name)
770    {
771        $this->namedContent[$name] = array();
772        return $this;
773    }
774
775    /**
776     *  Pre-render callback.
777     *
778     *  Calls the skin delegate's willRender() method if it exists.
779     *  This method is called just before the template for the skin is rendered.
780     *  @todo Do I need to pass in the WFSmarty instance here so that you can actually change the TPL file from this callback? If so, also update the willRender() delegate docs/prototype
781     */
782    function willRender()
783    {
784        if (is_object($this->delegate) && method_exists($this->delegate, 'willRender')) {
785            $this->delegate->willRender($this);
786        }
787    }
788}
789?>