├── .gitignore ├── .travis.yml ├── phpunit.xml.dist ├── tests └── Dflydev │ └── Silex │ └── Provider │ └── Theme │ └── ThemeServiceProviderTest.php ├── README.md ├── LICENSE ├── composer.json └── src └── Dflydev └── Silex └── Provider └── Theme └── ThemeServiceProvider.php /.gitignore: -------------------------------------------------------------------------------- 1 | composer.lock 2 | vendor 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.3.3 5 | - 5.3 6 | - 5.4 7 | 8 | before_script: 9 | - wget -nc http://getcomposer.org/composer.phar 10 | - php composer.phar install --dev 11 | 12 | script: phpunit --coverage-text --verbose 13 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ./tests/Dflydev/Silex/Provider/Theme 6 | 7 | 8 | 9 | 10 | 11 | ./src/Dflydev/Silex/Provider/Theme/ 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /tests/Dflydev/Silex/Provider/Theme/ThemeServiceProviderTest.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class ThemeServiceProviderTest extends \PHPUnit_Framework_TestCase 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Theme Service Provider 2 | ====================== 3 | 4 | Provides [Theme][00] services to [Silex][01] applications. 5 | 6 | 7 | Installation 8 | ------------ 9 | 10 | Through [Composer][02] as [dflydev/theme-service-provider][03]. 11 | 12 | 13 | License 14 | ------- 15 | 16 | MIT, see LICENSE. 17 | 18 | 19 | Community 20 | --------- 21 | 22 | If you have questions or want to help out, join us in the 23 | [#dflydev][#dflydev] or [#silex-php][#silex-php] channels 24 | on irc.freenode.net. 25 | 26 | [00]: http://github.com/dflydev/dflydev-theme 27 | [01]: http://silex.sensiolabs.org 28 | [02]: http://getcomposer.org 29 | [03]: https://packagist.org/packages/dflydev/theme-service-provider 30 | 31 | [#dflydev]: irc://irc.freenode.net/#dflydev 32 | [#silex-php]: irc://irc.freenode.net/#silex-php -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Dragonfly Development Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dflydev/theme-service-provider", 3 | "description": "Theme Service Provider", 4 | "keywords": ["theme"], 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Dragonfly Development Inc.", 9 | "email": "info@dflydev.com", 10 | "homepage": "http://dflydev.com" 11 | }, 12 | { 13 | "name": "Beau Simensen", 14 | "email": "beau@dflydev.com", 15 | "homepage": "http://beausimensen.com" 16 | } 17 | ], 18 | "require": { 19 | "php": ">=5.3.3", 20 | "dflydev/theme": "1.*@dev", 21 | "silex/silex": "1.*@dev" 22 | }, 23 | "require-dev": { 24 | "dflydev/theme-twig-extension": "1.*@dev" 25 | }, 26 | "suggest": { 27 | "dflydev/psr0-resource-locator-composer-service-provider": "1.*", 28 | "dflydev/theme-twig-extension": "1.*" 29 | }, 30 | "autoload": { 31 | "psr-0": { 32 | "Dflydev\\Silex\\Provider\\Theme": "src" 33 | } 34 | }, 35 | "extra": { 36 | "branch-alias": { 37 | "dev-master": "1.0-dev" 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Dflydev/Silex/Provider/Theme/ThemeServiceProvider.php: -------------------------------------------------------------------------------- 1 | 32 | */ 33 | class ThemeServiceProvider implements ServiceProviderInterface 34 | { 35 | /** 36 | * {@inheritdoc} 37 | */ 38 | public function boot(Application $app) 39 | { 40 | if (isset($app['theme.types'])) { 41 | foreach ($app['theme.types'] as $type) { 42 | $descriptor = $app[$app['theme.dynamic_typed_descriptor_param.prefix'].$type.$app['theme.dynamic_typed_descriptor_param.suffix']]; 43 | $app['theme.manager']->registerTheme($descriptor, $type); 44 | } 45 | } 46 | 47 | if (isset($app[$app['theme.descriptor_param']])) { 48 | $app['theme.manager']->registerTheme($app[$app['theme.descriptor_param']]); 49 | } 50 | } 51 | 52 | /** 53 | * {@inheritdoc} 54 | */ 55 | public function register(Application $app) 56 | { 57 | foreach ($this->getDefaults($app) as $key => $value) { 58 | if (!isset($app[$key])) { 59 | $app[$key] = $value; 60 | } 61 | } 62 | 63 | $app['theme.path_mapper'] = $app->share(function($app) { 64 | $pathMapper = new PatternPathMapper($app['theme.docroot']); 65 | 66 | $pathMapper->setThemeUrlTemplate($app['theme.url_template']); 67 | $pathMapper->setTypedThemeUrlTemplate($app['theme.typed_url_template']); 68 | 69 | return $pathMapper; 70 | }); 71 | 72 | $app['theme.registry'] = $app->share(function($app) { 73 | return new ArrayRegistry; 74 | }); 75 | 76 | $app['theme.path_locator'] = $app->share(function($app) { 77 | $pathLocator = new CompositePathLocator(array( 78 | new PathMapperPathLocator($app['theme.path_mapper']), 79 | new FilesystemPathLocator, 80 | )); 81 | 82 | if (isset($app['psr0_resource_locator'])) { 83 | $pathLocator->addPathLocator(new Psr0ResourceLocatorPathLocator( 84 | $app['psr0_resource_locator'] 85 | )); 86 | } 87 | 88 | return $pathLocator; 89 | }); 90 | 91 | $app['theme.theme_factory'] = $app->share(function($app) { 92 | switch ($app['theme.format']) { 93 | case 'version0': 94 | return new Version0ThemeFactory; 95 | break; 96 | 97 | default: 98 | throw new \InvalidArgumentException('Invalid theme format specified.'); 99 | break; 100 | } 101 | }); 102 | 103 | $app['theme.manager'] = $app->share(function($app) { 104 | return new ThemeManager( 105 | $app['theme.registry'], 106 | $app['theme.path_locator'], 107 | $app['theme.theme_factory'] 108 | ); 109 | }); 110 | 111 | $app['theme.provider'] = $app->share(function($app) { 112 | if (!empty($app[$app['theme.typed_descriptor_param']])) { 113 | $type = $app[$app['theme.typed_descriptor_param']]; 114 | $descriptor = $app[$app['theme.dynamic_typed_descriptor_param.prefix'].$type.$app['theme.dynamic_typed_descriptor_param.suffix']]; 115 | } elseif (!empty($app[$app['theme.descriptor_param']])) { 116 | $type = null; 117 | $descriptor = $app[$app['theme.descriptor_param']]; 118 | } else { 119 | throw new \InvalidArgumentException("Could not determine theme; Looked for ''."); 120 | } 121 | 122 | return new ThemeProvider($app['theme.manager']->registerTheme($descriptor, $type)); 123 | }); 124 | 125 | $app['twig.loader.filesystem'] = $app->share($app->extend('twig.loader.filesystem', function ($loader, $app) { 126 | if (null === $theme = $app['theme.provider']->provideTheme()) { 127 | return $loader; 128 | } 129 | 130 | $templatePath = $theme->rootPath().'/templates'; 131 | 132 | if (!file_exists($templatePath)) { 133 | throw new \InvalidArgumentException("Theme is missing its template directory; Expected to find it at '$templatePath'."); 134 | } 135 | 136 | $paths = $loader->getPaths(); 137 | $paths[] = $templatePath; 138 | $loader->setPaths($paths); 139 | 140 | return $loader; 141 | })); 142 | 143 | $app['theme.resource_url_generator'] = $app->share(function($app) { 144 | return new SymfonyRoutingResourceUrlGenerator( 145 | $app['theme.provider'], 146 | $app['theme.path_mapper'], 147 | $app['url_generator'] 148 | ); 149 | }); 150 | 151 | $app['theme.determine_content_type'] = $app->protect(function($file) use ($app) { 152 | if (function_exists('finfo_file')) { 153 | $finfo = finfo_open(FILEINFO_MIME_TYPE); 154 | $type = finfo_file($finfo, $file); 155 | finfo_close($finfo); 156 | } else { 157 | $type = 'text/plain'; 158 | } 159 | 160 | if (!$type || in_array($type, array('application/octet-stream', 'text/plain'))) { 161 | $secondOpinion = exec('file -b --mime-type ' . escapeshellarg($file), $foo, $returnCode); 162 | if ($returnCode === 0 && $secondOpinion) { 163 | $type = $secondOpinion; 164 | } 165 | } 166 | 167 | if (in_array($type, array('text/plain', 'text/x-c')) && preg_match('/\.css$/', $file)) { 168 | $type = 'text/css'; 169 | } 170 | 171 | return $type; 172 | }); 173 | 174 | if (class_exists('Dflydev\Twig\Extension\Theme\ThemeTwigExtension')) { 175 | $this->configureThemeTwigExtension($app); 176 | } 177 | } 178 | 179 | protected function configureThemeTwigExtension(Application $app) 180 | { 181 | $app['twig'] = $app->share($app->extend('twig', function($twig, $app) { 182 | $twig->addExtension(new ThemeTwigExtension($app['theme.resource_url_generator'])); 183 | 184 | return $twig; 185 | })); 186 | 187 | $app->get('/_theme_typed/{type}/{name}/resources/{resource}', function($type, $name, $resource) use ($app) { 188 | $theme = $app['theme.manager']->findThemeByName($name, $type); 189 | $filesystemPath = $theme->rootPath().'/public/'.$resource; 190 | 191 | if (!file_exists($filesystemPath)) { 192 | return $app->abort(404, 'The resource was not found.'); 193 | } 194 | 195 | $type = $app['theme.determine_content_type']($filesystemPath); 196 | 197 | $stream = function () use ($filesystemPath) { 198 | readfile($filesystemPath); 199 | }; 200 | 201 | return $app->stream($stream, 200, array( 202 | 'Content-Type' => $type, 203 | )); 204 | }) 205 | ->assert('name', '.+') 206 | ->assert('resource', '.+') 207 | ->bind('_dflydev_typed_theme_handler'); 208 | 209 | $app->get('/_theme/{name}/resources/{resource}', function($name, $resource) use ($app) { 210 | $theme = $app['theme.manager']->findThemeByName($name); 211 | $filesystemPath = $app['theme.path_mapper']->generatePublicResourceFilesystemPathForTheme($theme, $resource); 212 | $type = $app['theme.determine_content_type']($filesystemPath); 213 | 214 | print $type; 215 | }) 216 | ->assert('name', '.+') 217 | ->assert('resource', '.+') 218 | ->bind('_dflydev_theme_handler'); 219 | } 220 | 221 | protected function getDefaults() 222 | { 223 | return array( 224 | 'theme.format' => 'version0', 225 | 'theme.descriptor_param' => 'theme.descriptor', 226 | 'theme.typed_descriptor_param' => 'theme.type', 227 | 'theme.dynamic_typed_descriptor_param.prefix' => 'theme.descriptor.', 228 | 'theme.dynamic_typed_descriptor_param.suffix' => '', 229 | 'theme.url_template' => '/themes/%name%', 230 | 'theme.typed_url_template' => '/themes/%type%/%name%', 231 | ); 232 | } 233 | } 234 | --------------------------------------------------------------------------------