From 6e758f7212adf5cf14392a5123f893d2cd6a67b2 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Wed, 21 Nov 2012 13:16:40 -0600 Subject: [PATCH 01/27] update provider array. --- app/config/app.php | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/app/config/app.php b/app/config/app.php index b9ab5e2..d8909a2 100644 --- a/app/config/app.php +++ b/app/config/app.php @@ -93,27 +93,27 @@ 'Illuminate\Foundation\Providers\AliasServiceProvider', 'Illuminate\Foundation\Providers\ArtisanServiceProvider', - 'Illuminate\Foundation\Providers\AuthServiceProvider', - 'Illuminate\Foundation\Providers\CacheServiceProvider', + 'Illuminate\Auth\AuthServiceProvider', + 'Illuminate\Cache\CacheServiceProvider', 'Illuminate\Foundation\Providers\ComposerServiceProvider', - 'Illuminate\Foundation\Providers\ControllerServiceProvider', - 'Illuminate\Foundation\Providers\CookieServiceProvider', - 'Illuminate\Foundation\Providers\DatabaseServiceProvider', - 'Illuminate\Foundation\Providers\EncrypterServiceProvider', - 'Illuminate\Foundation\Providers\EventsServiceProvider', - 'Illuminate\Foundation\Providers\FilesystemServiceProvider', - 'Illuminate\Foundation\Providers\HashServiceProvider', - 'Illuminate\Foundation\Providers\LogServiceProvider', - 'Illuminate\Foundation\Providers\MailServiceProvider', - 'Illuminate\Foundation\Providers\MigrationServiceProvider', - 'Illuminate\Foundation\Providers\PaginationServiceProvider', + 'Illuminate\Routing\ControllerServiceProvider', + 'Illuminate\CookieServiceProvider', + 'Illuminate\Database\DatabaseServiceProvider', + 'Illuminate\EncrypterServiceProvider', + 'Illuminate\Events\EventsServiceProvider', + 'Illuminate\FilesystemServiceProvider', + 'Illuminate\Hashing\HashServiceProvider', + 'Illuminate\Log\LogServiceProvider', + 'Illuminate\Mail\MailServiceProvider', + 'Illuminate\Database\MigrationServiceProvider', + 'Illuminate\Pagination\PaginationServiceProvider', 'Illuminate\Foundation\Providers\PublisherServiceProvider', - 'Illuminate\Foundation\Providers\RedisServiceProvider', - 'Illuminate\Foundation\Providers\SeedServiceProvider', - 'Illuminate\Foundation\Providers\SessionServiceProvider', - 'Illuminate\Foundation\Providers\TranslationServiceProvider', - 'Illuminate\Foundation\Providers\ValidatorServiceProvider', - 'Illuminate\Foundation\Providers\ViewServiceProvider', + 'Illuminate\Redis\RedisServiceProvider', + 'Illuminate\Database\SeedServiceProvider', + 'Illuminate\Session\SessionServiceProvider', + 'Illuminate\Translation\TranslationServiceProvider', + 'Illuminate\Validation\ValidationServiceProvider', + 'Illuminate\View\ViewServiceProvider', ), From 3d0231e71f0c4b34153f935d35e4d15b8cdecf19 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Wed, 21 Nov 2012 13:18:01 -0600 Subject: [PATCH 02/27] get rid of unneeded import. --- start.php | 1 - 1 file changed, 1 deletion(-) diff --git a/start.php b/start.php index 6489453..869fe0a 100644 --- a/start.php +++ b/start.php @@ -2,7 +2,6 @@ use Illuminate\Filesystem; use Illuminate\Config\FileLoader; -use Illuminate\Config\ApcFileLoader; /* |-------------------------------------------------------------------------- From 85bc8d405f2065cda560b28f879e695960c509c0 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Wed, 21 Nov 2012 13:56:02 -0600 Subject: [PATCH 03/27] update providers config. --- app/config/app.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/config/app.php b/app/config/app.php index d8909a2..92bc5da 100644 --- a/app/config/app.php +++ b/app/config/app.php @@ -99,8 +99,8 @@ 'Illuminate\Routing\ControllerServiceProvider', 'Illuminate\CookieServiceProvider', 'Illuminate\Database\DatabaseServiceProvider', - 'Illuminate\EncrypterServiceProvider', - 'Illuminate\Events\EventsServiceProvider', + 'Illuminate\EncryptionServiceProvider', + 'Illuminate\Events\EventServiceProvider', 'Illuminate\FilesystemServiceProvider', 'Illuminate\Hashing\HashServiceProvider', 'Illuminate\Log\LogServiceProvider', From a5a39e62fceb370de008d331e161afa90b480b3e Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Wed, 21 Nov 2012 15:25:31 -0600 Subject: [PATCH 04/27] get rid of some unneeded config options. --- app/config/app.php | 15 +++++++++++---- app/config/view.php | 15 +-------------- app/views/{hello.blade.php => hello.php} | 0 3 files changed, 12 insertions(+), 18 deletions(-) rename app/views/{hello.blade.php => hello.php} (100%) diff --git a/app/config/app.php b/app/config/app.php index 92bc5da..d141f90 100644 --- a/app/config/app.php +++ b/app/config/app.php @@ -39,13 +39,20 @@ | */ - 'locales' => array('en'), - 'locale' => 'en', - 'fallback_locale' => 'en', + /* + |-------------------------------------------------------------------------- + | Application Fallback Locale + |-------------------------------------------------------------------------- + | + | The fallback locale determines the locale to use when the current one + | is not available. You may change the value to correspond to any of + | the language folders that are provided through your application. + | + */ - 'locale_path' => __DIR__.'/../lang', + 'fallback_locale' => 'en', /* |-------------------------------------------------------------------------- diff --git a/app/config/view.php b/app/config/view.php index 0b47626..498d8fd 100644 --- a/app/config/view.php +++ b/app/config/view.php @@ -15,7 +15,7 @@ | */ - 'driver' => 'blade', + 'driver' => 'php', /* |-------------------------------------------------------------------------- @@ -34,19 +34,6 @@ ), - /* - |-------------------------------------------------------------------------- - | Compiled View Cache Path - |-------------------------------------------------------------------------- - | - | Some view engines cache a compiled version of their templates, such as - | Twig and Blade. Here you may control where those caches are stored. - | A sensible default has already been setup for your applications. - | - */ - - 'cache' => __DIR__.'/../storage/views', - /* |-------------------------------------------------------------------------- | Pagination View diff --git a/app/views/hello.blade.php b/app/views/hello.php similarity index 100% rename from app/views/hello.blade.php rename to app/views/hello.php From 84b7ac56071a29532eb508bf486428009f7060b0 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Wed, 21 Nov 2012 15:32:15 -0600 Subject: [PATCH 05/27] Create a packages config place-holder. --- app/config/packages/.gitkeep | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 app/config/packages/.gitkeep diff --git a/app/config/packages/.gitkeep b/app/config/packages/.gitkeep new file mode 100644 index 0000000..e69de29 From b4d05bac3d528ba6d2881854b28f8b4b2cd8c9d1 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Fri, 23 Nov 2012 13:01:52 -0600 Subject: [PATCH 06/27] remove unnecessary options. --- app/config/view.php | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/app/config/view.php b/app/config/view.php index 498d8fd..eba10a4 100644 --- a/app/config/view.php +++ b/app/config/view.php @@ -2,21 +2,6 @@ return array( - /* - |-------------------------------------------------------------------------- - | Default View Driver - |-------------------------------------------------------------------------- - | - | This option controls the default view driver that will get used for the - | application. Laravel comes with a few great engines ranging from the - | simple PHP engine to the powerful Blade engine. Use what you like! - | - | Supported: "php", "blade" - | - */ - - 'driver' => 'php', - /* |-------------------------------------------------------------------------- | View Storage Paths @@ -28,11 +13,7 @@ | */ - 'paths' => array( - - __DIR__.'/../views', - - ), + 'paths' => array(__DIR__.'/../views'), /* |-------------------------------------------------------------------------- From e036c3900eb8634cc846ed0634f7c7c13d442c2c Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 25 Nov 2012 17:12:18 +1100 Subject: [PATCH 07/27] add jasonlewis/basset as a dependency Signed-off-by: Jason Lewis --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 97c376c..2cd0e9f 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,7 @@ { "require": { - "illuminate/foundation": ">=1.0.0" + "illuminate/foundation": ">=1.0.0", + "jasonlewis/basset": "*" }, "autoload": { "classmap": [ From 2fd860d5a76290d2ed2bfda73946a2160549e119 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 25 Nov 2012 17:33:46 +1100 Subject: [PATCH 08/27] Renamed the app directory to feather. Signed-off-by: Jason Lewis --- app/controllers/HomeController.php | 23 ---- app/start/global.php | 45 ------- app/start/local.php | 3 - app/tests/ExampleTest.php | 19 --- composer.json | 8 +- {app => feather}/config/app.php | 1 + {app => feather}/config/auth.php | 0 {app => feather}/config/cache.php | 0 {app => feather}/config/database.php | 0 {app => feather}/config/mail.php | 0 {app => feather}/config/packages/.gitkeep | 0 feather/config/packages/jasonlewis/basset.php | 125 ++++++++++++++++++ {app => feather}/config/session.php | 0 {app => feather}/config/view.php | 0 {app => feather}/controllers/.gitkeep | 0 feather/controllers/BaseController.php | 53 ++++++++ feather/controllers/HomeController.php | 10 ++ {app => feather}/database/migrations/.gitkeep | 0 {app => feather}/database/production.sqlite | 0 {app => feather}/database/seeds/.gitkeep | 0 {app => feather}/filters.php | 0 {app => feather}/lang/en/pagination.php | 0 {app => feather}/lang/en/validation.php | 0 {app => feather}/models/User.php | 0 {app => feather}/routes.php | 5 +- {app => feather}/start/artisan.php | 0 feather/start/global.php | 14 ++ {app => feather}/storage/.gitignore | 0 {app => feather}/storage/cache/.gitignore | 0 {app => feather}/storage/logs/.gitignore | 0 {app => feather}/storage/meta/.gitignore | 0 {app => feather}/storage/sessions/.gitignore | 0 {app => feather}/storage/views/.gitignore | 0 {app => feather}/tests/TestCase.php | 0 {app => feather}/views/hello.php | 0 phpunit.xml | 2 +- start.php | 2 +- 37 files changed, 210 insertions(+), 100 deletions(-) delete mode 100644 app/controllers/HomeController.php delete mode 100644 app/start/global.php delete mode 100644 app/start/local.php delete mode 100644 app/tests/ExampleTest.php rename {app => feather}/config/app.php (99%) rename {app => feather}/config/auth.php (100%) rename {app => feather}/config/cache.php (100%) rename {app => feather}/config/database.php (100%) rename {app => feather}/config/mail.php (100%) rename {app => feather}/config/packages/.gitkeep (100%) create mode 100644 feather/config/packages/jasonlewis/basset.php rename {app => feather}/config/session.php (100%) rename {app => feather}/config/view.php (100%) rename {app => feather}/controllers/.gitkeep (100%) create mode 100644 feather/controllers/BaseController.php create mode 100644 feather/controllers/HomeController.php rename {app => feather}/database/migrations/.gitkeep (100%) rename {app => feather}/database/production.sqlite (100%) rename {app => feather}/database/seeds/.gitkeep (100%) rename {app => feather}/filters.php (100%) rename {app => feather}/lang/en/pagination.php (100%) rename {app => feather}/lang/en/validation.php (100%) rename {app => feather}/models/User.php (100%) rename {app => feather}/routes.php (86%) rename {app => feather}/start/artisan.php (100%) create mode 100644 feather/start/global.php rename {app => feather}/storage/.gitignore (100%) rename {app => feather}/storage/cache/.gitignore (100%) rename {app => feather}/storage/logs/.gitignore (100%) rename {app => feather}/storage/meta/.gitignore (100%) rename {app => feather}/storage/sessions/.gitignore (100%) rename {app => feather}/storage/views/.gitignore (100%) rename {app => feather}/tests/TestCase.php (100%) rename {app => feather}/views/hello.php (100%) diff --git a/app/controllers/HomeController.php b/app/controllers/HomeController.php deleted file mode 100644 index 7eb9874..0000000 --- a/app/controllers/HomeController.php +++ /dev/null @@ -1,23 +0,0 @@ -client->request('GET', '/'); - - $this->assertTrue($this->client->getResponse()->isOk()); - - $this->assertCount(1, $crawler->filter('h1:contains("Hello World!")')); - } - -} \ No newline at end of file diff --git a/composer.json b/composer.json index 2cd0e9f..5f1b264 100644 --- a/composer.json +++ b/composer.json @@ -5,10 +5,10 @@ }, "autoload": { "classmap": [ - "app/controllers", - "app/models", - "app/database/migrations", - "app/tests/TestCase.php" + "feather/controllers", + "feather/models", + "feather/database/migrations", + "feather/tests/TestCase.php" ] }, "minimum-stability": "dev" diff --git a/app/config/app.php b/feather/config/app.php similarity index 99% rename from app/config/app.php rename to feather/config/app.php index d141f90..27a7fba 100644 --- a/app/config/app.php +++ b/feather/config/app.php @@ -121,6 +121,7 @@ 'Illuminate\Translation\TranslationServiceProvider', 'Illuminate\Validation\ValidationServiceProvider', 'Illuminate\View\ViewServiceProvider', + 'Basset\BassetServiceProvider' ), diff --git a/app/config/auth.php b/feather/config/auth.php similarity index 100% rename from app/config/auth.php rename to feather/config/auth.php diff --git a/app/config/cache.php b/feather/config/cache.php similarity index 100% rename from app/config/cache.php rename to feather/config/cache.php diff --git a/app/config/database.php b/feather/config/database.php similarity index 100% rename from app/config/database.php rename to feather/config/database.php diff --git a/app/config/mail.php b/feather/config/mail.php similarity index 100% rename from app/config/mail.php rename to feather/config/mail.php diff --git a/app/config/packages/.gitkeep b/feather/config/packages/.gitkeep similarity index 100% rename from app/config/packages/.gitkeep rename to feather/config/packages/.gitkeep diff --git a/feather/config/packages/jasonlewis/basset.php b/feather/config/packages/jasonlewis/basset.php new file mode 100644 index 0000000..57cba3d --- /dev/null +++ b/feather/config/packages/jasonlewis/basset.php @@ -0,0 +1,125 @@ + 'assets', + + /* + |-------------------------------------------------------------------------- + | Asset Directories + |-------------------------------------------------------------------------- + | + | These named directories are used for quick reference as well as when + | searching for an asset. Assets are located by cascading through the array + | of directories until an asset with the matching name is found. + | + | Directories are relative from the root of your application. + | + | You can specifiy an absolute path to a directory by prefixing it with + | 'path: '. + | + | array( + | 'css' => 'path: /path/to/your/directory' + | ) + | + */ + + 'directories' => array( + 'css' => 'app/assets/css', + 'js' => 'app/assets/js' + ), + + /* + |-------------------------------------------------------------------------- + | Asset Collections + |-------------------------------------------------------------------------- + | + | Define your collections in an array like so. + | + | array( + | 'website' => function($collection) + | { + | $collection->add('example.css'); + | } + | ) + | + | This collection is now available at Basset::show('website.css') + | + */ + 'collections' => array(), + + /* + |-------------------------------------------------------------------------- + | Compiling Path + |-------------------------------------------------------------------------- + | + | When assets are statically compiled via the command line the generated + | files will be stored in this directory. The path is relative to the root + | of your application, so remember to compile them to the public directory + | somewhere. + | + | If the directory does not exist, Basset will attempt to create it. + | + */ + + 'compiling_path' => 'public/assets', + + /* + |-------------------------------------------------------------------------- + | Production Environment + |-------------------------------------------------------------------------- + | + | Basset will attempt to detect your production environment and serve + | static assets. You can help Basset out in a number of ways to speed it up + | a bit. + | + | Set your actual production environment here and Basset will compare + | environments and serve the appropriate assets. + | + | Set to null or an empty string and Basset will try and detect your + | environment, this may deliver unexpected results. + | + | Set to false and Basset will always serve individual assets as it does in + | a development environment. Remember that filters will not be applied + | to the assets. + | + | Set to true to always serve static assets if available. + | + */ + + 'production_environment' => '', + + /* + |-------------------------------------------------------------------------- + | Named Filters + |-------------------------------------------------------------------------- + | + | A named filter can be used to quickly apply a filter to a collection of + | assets. + | + | 'YuiCss' => 'Yui\CssCompressorFilter' + | + | If you'd like to specify options for a named filter you can define the + | filter as an array. + | + | 'YuiCss' => array( + | 'Yui\CssCompressorFilter' => array('/path/to/yuicompressor.jar') + | ) + | + | The filter can then be referenced by its name when applying filters. + | + */ + + 'filters' => array() + +); \ No newline at end of file diff --git a/app/config/session.php b/feather/config/session.php similarity index 100% rename from app/config/session.php rename to feather/config/session.php diff --git a/app/config/view.php b/feather/config/view.php similarity index 100% rename from app/config/view.php rename to feather/config/view.php diff --git a/app/controllers/.gitkeep b/feather/controllers/.gitkeep similarity index 100% rename from app/controllers/.gitkeep rename to feather/controllers/.gitkeep diff --git a/feather/controllers/BaseController.php b/feather/controllers/BaseController.php new file mode 100644 index 0000000..3381dca --- /dev/null +++ b/feather/controllers/BaseController.php @@ -0,0 +1,53 @@ +app = Facade::getFacadeApplication(); + + if ($this->layout) + { + $this->layout = $this->app['view']->make($this->layout); + } + } + + /** + * Call the given action with the given parameters. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + protected function directCallAction($method, $parameters) + { + // If no response is returned by the controller method then we'll fall back to + // the layout that was bound in the constructor. + if ( ! $response = call_user_func_array(array($this, $method), $parameters)) + { + $response = $this->layout; + } + + return $response; + } + +} \ No newline at end of file diff --git a/feather/controllers/HomeController.php b/feather/controllers/HomeController.php new file mode 100644 index 0000000..aadbbfe --- /dev/null +++ b/feather/controllers/HomeController.php @@ -0,0 +1,10 @@ + - ./app/tests/ + ./feather/tests/ \ No newline at end of file diff --git a/start.php b/start.php index 869fe0a..d10160e 100644 --- a/start.php +++ b/start.php @@ -28,7 +28,7 @@ | */ -$app['path'] = __DIR__.'/app'; +$app['path'] = __DIR__.'/feather'; $app['path.base'] = __DIR__; From 3ed38d34e5430e865790a530656b5ce7f6474e09 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 25 Nov 2012 19:10:54 +1100 Subject: [PATCH 09/27] Initial commit of Feather as an application. Signed-off-by: Jason Lewis --- {feather => app}/config/app.php | 4 +- {feather => app}/config/auth.php | 0 {feather => app}/config/cache.php | 0 {feather => app}/config/database.php | 4 +- {feather => app}/config/mail.php | 0 {feather => app}/config/packages/.gitkeep | 0 .../config/packages/jasonlewis/basset.php | 5 +- {feather => app}/config/session.php | 0 {feather => app}/config/view.php | 0 {feather => app}/controllers/.gitkeep | 0 .../controllers/BaseController.php | 5 +- app/controllers/HomeController.php | 12 + {feather => app}/database/migrations/.gitkeep | 0 {feather => app}/database/production.sqlite | 0 {feather => app}/database/seeds/.gitkeep | 0 {feather => app}/filters.php | 0 {feather => app}/lang/en/pagination.php | 0 {feather => app}/lang/en/validation.php | 0 app/models/BaseModel.php | 65 +++ app/models/Config.php | 34 ++ app/models/Extension.php | 34 ++ app/models/Migrator.php | 35 ++ app/models/User.php | 162 +++++++ .../.gitkeep => app/models/User/Roles.php | 0 {feather => app}/routes.php | 2 +- {feather => app}/start/artisan.php | 9 +- app/start/console.php | 30 ++ app/start/facades.php | 25 ++ app/start/global.php | 128 ++++++ {feather => app}/storage/.gitignore | 0 {feather => app}/storage/cache/.gitignore | 0 {feather => app}/storage/logs/.gitignore | 0 {feather => app}/storage/meta/.gitignore | 0 {feather => app}/storage/sessions/.gitignore | 0 {feather => app}/storage/views/.gitignore | 0 {feather => app}/views/hello.php | 0 composer.json | 13 +- extensions/.gitignore | 1 + feather/auth/AuthServiceProvider.php | 27 ++ feather/auth/FeatherUserProvider.php | 97 ++++ feather/console/FeatherCommand.php | 30 ++ feather/console/PublishCommand.php | 138 ++++++ feather/controllers/HomeController.php | 10 - feather/database/EloquentBuilder.php | 34 ++ feather/extensions/Dispatcher.php | 147 ++++++ feather/extensions/Extension.php | 113 +++++ .../extensions/ExtensionServiceProvider.php | 23 + feather/migrator/DriverInterface.php | 33 ++ feather/migrator/Drivers/BaseDriver.php | 55 +++ feather/migrator/Drivers/FluxDriver.php | 35 ++ feather/migrator/MigratorManager.php | 77 ++++ feather/migrator/MigratorServiceProvider.php | 49 ++ feather/models/User.php | 41 -- .../presenter/Compilers/FeatherCompiler.php | 73 +++ feather/presenter/Presenter.php | 54 +++ .../presenter/PresenterServiceProvider.php | 43 ++ feather/start/global.php | 14 - phpunit.xml | 2 +- start.php | 2 +- {feather/tests => tests}/TestCase.php | 0 themes/basic/public/css/theme.css | 423 ++++++++++++++++++ themes/basic/public/img/background.png | Bin 0 -> 23173 bytes themes/basic/public/img/heading.png | Bin 0 -> 1729 bytes themes/basic/public/img/loading.gif | Bin 0 -> 847 bytes themes/basic/public/img/sprites.png | Bin 0 -> 15896 bytes themes/basic/start.php | 6 + themes/basic/views/theme.blade.php | 60 +++ 67 files changed, 2071 insertions(+), 83 deletions(-) rename {feather => app}/config/app.php (97%) rename {feather => app}/config/auth.php (100%) rename {feather => app}/config/cache.php (100%) rename {feather => app}/config/database.php (98%) rename {feather => app}/config/mail.php (100%) rename {feather => app}/config/packages/.gitkeep (100%) rename {feather => app}/config/packages/jasonlewis/basset.php (97%) rename {feather => app}/config/session.php (100%) rename {feather => app}/config/view.php (100%) rename {feather => app}/controllers/.gitkeep (100%) rename {feather => app}/controllers/BaseController.php (92%) create mode 100644 app/controllers/HomeController.php rename {feather => app}/database/migrations/.gitkeep (100%) rename {feather => app}/database/production.sqlite (100%) rename {feather => app}/database/seeds/.gitkeep (100%) rename {feather => app}/filters.php (100%) rename {feather => app}/lang/en/pagination.php (100%) rename {feather => app}/lang/en/validation.php (100%) create mode 100644 app/models/BaseModel.php create mode 100644 app/models/Config.php create mode 100644 app/models/Extension.php create mode 100644 app/models/Migrator.php create mode 100644 app/models/User.php rename public/packages/.gitkeep => app/models/User/Roles.php (100%) rename {feather => app}/routes.php (85%) rename {feather => app}/start/artisan.php (78%) create mode 100644 app/start/console.php create mode 100644 app/start/facades.php create mode 100644 app/start/global.php rename {feather => app}/storage/.gitignore (100%) rename {feather => app}/storage/cache/.gitignore (100%) rename {feather => app}/storage/logs/.gitignore (100%) rename {feather => app}/storage/meta/.gitignore (100%) rename {feather => app}/storage/sessions/.gitignore (100%) rename {feather => app}/storage/views/.gitignore (100%) rename {feather => app}/views/hello.php (100%) create mode 100644 extensions/.gitignore create mode 100644 feather/auth/AuthServiceProvider.php create mode 100644 feather/auth/FeatherUserProvider.php create mode 100644 feather/console/FeatherCommand.php create mode 100644 feather/console/PublishCommand.php delete mode 100644 feather/controllers/HomeController.php create mode 100644 feather/database/EloquentBuilder.php create mode 100644 feather/extensions/Dispatcher.php create mode 100644 feather/extensions/Extension.php create mode 100644 feather/extensions/ExtensionServiceProvider.php create mode 100644 feather/migrator/DriverInterface.php create mode 100644 feather/migrator/Drivers/BaseDriver.php create mode 100644 feather/migrator/Drivers/FluxDriver.php create mode 100644 feather/migrator/MigratorManager.php create mode 100644 feather/migrator/MigratorServiceProvider.php delete mode 100644 feather/models/User.php create mode 100644 feather/presenter/Compilers/FeatherCompiler.php create mode 100644 feather/presenter/Presenter.php create mode 100644 feather/presenter/PresenterServiceProvider.php delete mode 100644 feather/start/global.php rename {feather/tests => tests}/TestCase.php (100%) create mode 100644 themes/basic/public/css/theme.css create mode 100644 themes/basic/public/img/background.png create mode 100644 themes/basic/public/img/heading.png create mode 100644 themes/basic/public/img/loading.gif create mode 100644 themes/basic/public/img/sprites.png create mode 100644 themes/basic/start.php create mode 100644 themes/basic/views/theme.blade.php diff --git a/feather/config/app.php b/app/config/app.php similarity index 97% rename from feather/config/app.php rename to app/config/app.php index 27a7fba..ef67012 100644 --- a/feather/config/app.php +++ b/app/config/app.php @@ -81,7 +81,7 @@ 'aliases' => array( 'Controller' => 'Illuminate\Routing\Controllers\Controller', - 'Eloquent' => 'Illuminate\Database\Eloquent\Model', + 'Eloquent' => 'Illuminate\Database\Eloquent\Model' ), @@ -121,7 +121,7 @@ 'Illuminate\Translation\TranslationServiceProvider', 'Illuminate\Validation\ValidationServiceProvider', 'Illuminate\View\ViewServiceProvider', - 'Basset\BassetServiceProvider' + 'Basset\BassetServiceProvider', ), diff --git a/feather/config/auth.php b/app/config/auth.php similarity index 100% rename from feather/config/auth.php rename to app/config/auth.php diff --git a/feather/config/cache.php b/app/config/cache.php similarity index 100% rename from feather/config/cache.php rename to app/config/cache.php diff --git a/feather/config/database.php b/app/config/database.php similarity index 98% rename from feather/config/database.php rename to app/config/database.php index e8d6ce8..4b93333 100644 --- a/feather/config/database.php +++ b/app/config/database.php @@ -55,11 +55,11 @@ 'mysql' => array( 'driver' => 'mysql', 'host' => 'localhost', - 'database' => 'database', + 'database' => 'feather', 'username' => 'root', 'password' => '', 'charset' => 'utf8', - 'collation' => 'utf8_unicode_ci', + 'collation' => 'utf8_general_ci', 'prefix' => '', ), diff --git a/feather/config/mail.php b/app/config/mail.php similarity index 100% rename from feather/config/mail.php rename to app/config/mail.php diff --git a/feather/config/packages/.gitkeep b/app/config/packages/.gitkeep similarity index 100% rename from feather/config/packages/.gitkeep rename to app/config/packages/.gitkeep diff --git a/feather/config/packages/jasonlewis/basset.php b/app/config/packages/jasonlewis/basset.php similarity index 97% rename from feather/config/packages/jasonlewis/basset.php rename to app/config/packages/jasonlewis/basset.php index 57cba3d..ef90021 100644 --- a/feather/config/packages/jasonlewis/basset.php +++ b/app/config/packages/jasonlewis/basset.php @@ -34,10 +34,7 @@ | */ - 'directories' => array( - 'css' => 'app/assets/css', - 'js' => 'app/assets/js' - ), + 'directories' => array(), /* |-------------------------------------------------------------------------- diff --git a/feather/config/session.php b/app/config/session.php similarity index 100% rename from feather/config/session.php rename to app/config/session.php diff --git a/feather/config/view.php b/app/config/view.php similarity index 100% rename from feather/config/view.php rename to app/config/view.php diff --git a/feather/controllers/.gitkeep b/app/controllers/.gitkeep similarity index 100% rename from feather/controllers/.gitkeep rename to app/controllers/.gitkeep diff --git a/feather/controllers/BaseController.php b/app/controllers/BaseController.php similarity index 92% rename from feather/controllers/BaseController.php rename to app/controllers/BaseController.php index 3381dca..817e208 100644 --- a/feather/controllers/BaseController.php +++ b/app/controllers/BaseController.php @@ -1,4 +1,7 @@ - array('place', 'place_id'), + 'user' => array('user', 'user_id'), + 'author' => array('user', 'user_id') + ); + + /** + * Get a new query builder for the model's table. + * + * @return Feather\Models\Builder + */ + public function newQuery() + { + $builder = new EloquentBuilder($this->newBaseQueryBuilder()); + + // Once we have the query builders, we will set the model instances so the + // builder can easily access any information it may need from the model + // while it is constructing and executing various queries against it. + $builder->setModel($this); + + return $builder; + } + + /** + * Handle the dynamic retrieval of attributes and associations. + * + * @param string $key + * @return mixed + */ + public function __get($key) + { + if (isset($this->cachable[$key]) and ! isset($this->attributes[$key]) and ! isset($this->relations[$key])) + { + list($group, $foreign) = $this->cachable[$key]; + + if (Cache::has("{$group}_{$this->$foreign}")) + { + return Cache::get("{$group}_{$this->$foreign}"); + } + } + + return parent::__get($key); + } + +} \ No newline at end of file diff --git a/app/models/Config.php b/app/models/Config.php new file mode 100644 index 0000000..18b149e --- /dev/null +++ b/app/models/Config.php @@ -0,0 +1,34 @@ +get(); + }); + } + +} \ No newline at end of file diff --git a/app/models/Migrator.php b/app/models/Migrator.php new file mode 100644 index 0000000..2e9acb7 --- /dev/null +++ b/app/models/Migrator.php @@ -0,0 +1,35 @@ +where('driver', $driver)->first(); + } + + public function getOptions($options) + { + return (object) json_decode($options); + } + +} \ No newline at end of file diff --git a/app/models/User.php b/app/models/User.php new file mode 100644 index 0000000..3caa0ae --- /dev/null +++ b/app/models/User.php @@ -0,0 +1,162 @@ +getKey(); + } + + /** + * Get the password for user authentication. + * + * @return string + */ + public function getAuthPassword() + { + return $this->password; + } + + /** + * A user can have many and belong to many roles. + * + * @return object + */ + public function roles() + { + return $this->belongsToMany('Feather\\Core\\Role', 'user_roles', 'user_id', 'role_id'); + } + + /** + * When setting a password it must be hashed before stored in the database. + * + * @param string $password + * @return void + */ + public function setPassword($password) + { + $this->setAttribute('password', Hash::make($password)); + } + + /** + * Getter for a users slug. + * + * @return string + */ + public function getSlug() + { + return Str::slug($this->getAttribute('username')); + } + + /** + * Getter for a users name. + * + * @return string + */ + public function getName() + { + return $this->getAttribute('username'); + } + + /** + * Getter for a users avatar URL, provided by Gravatar. + * + * @return string + */ + public function getAvatar() + { + return 'http://www.gravatar.com/avatar/' . md5(strtolower(trim($this->getAttribute('email')))); + } + + /** + * Register a new user and return the new user object. + * + * @param array $input + * @param bool $activated + * @return Feather\Core\User + */ + public function register($input) + { + $this->fill(array( + 'username' => $input['username'], + 'password' => $input['password'], + 'email' => $input['email'] + )); + + if(Config::get('feather: db.registration.confirm_email')) + { + $this->fill(array( + 'activation_key' => Str::random(30), + 'activated' => 0 + )); + } + + if(!$this->save()) + { + throw new FeatherModelException; + } + + // Attach the users role. If the user needs to confirm their e-mail address they are given + // the confirming role status which has an ID of 4. If not then they receive the standard + // member role which has an ID of 2. + $this->roles()->attach(Config::get('feather: db.registration.confirm_email') ? 4 : 2); + + return $this; + } + + public static function edit($user, $input) + { + + } + + /** + * Register a new user and associate it with an e-mail from an authenticator. + * + * @param array $associate + * @param array $input + * @return Feather\Core\User + */ + public static function associate($associate, $input) + { + $user = new static(array( + 'username' => $input['username'], + 'authenticator' => Config::get('feather: db.auth.driver'), + 'authenticator_token' => $associate['token'], + 'authenticator_associated_email' => $associate['email'], + 'email' => $input['email'] + )); + + if(!$user->save()) + { + throw new FeatherModelException; + } + + $user->roles()->attach(2); + + return $user; + } + +} \ No newline at end of file diff --git a/public/packages/.gitkeep b/app/models/User/Roles.php similarity index 100% rename from public/packages/.gitkeep rename to app/models/User/Roles.php diff --git a/feather/routes.php b/app/routes.php similarity index 85% rename from feather/routes.php rename to app/routes.php index 612e329..1e1cf7e 100644 --- a/feather/routes.php +++ b/app/routes.php @@ -11,4 +11,4 @@ | */ -Route::get('/', 'HomeController@showHomepage'); \ No newline at end of file +Route::get('/', 'Feather\Controllers\HomeController@showHomepage'); \ No newline at end of file diff --git a/feather/start/artisan.php b/app/start/artisan.php similarity index 78% rename from feather/start/artisan.php rename to app/start/artisan.php index 14de2e9..612583e 100644 --- a/feather/start/artisan.php +++ b/app/start/artisan.php @@ -1,4 +1,4 @@ -resolveCommands(array( + 'command.feather', + 'command.feather.publish' +)); \ No newline at end of file diff --git a/app/start/console.php b/app/start/console.php new file mode 100644 index 0000000..a1c8005 --- /dev/null +++ b/app/start/console.php @@ -0,0 +1,30 @@ +share(function($app) +{ + return new Console\FeatherCommand; +}); + +/* +|-------------------------------------------------------------------------- +| Publish Command +|-------------------------------------------------------------------------- +| +| The publish command is responsible for publishing assets in both themes +| and extensions. +| +*/ + +$app['command.feather.publish'] = $app->share(function($app) +{ + return new Console\PublishCommand($app['asset.publisher'], $app['path.themes'], $app['path.extensions']); +}); \ No newline at end of file diff --git a/app/start/facades.php b/app/start/facades.php new file mode 100644 index 0000000..319f99d --- /dev/null +++ b/app/start/facades.php @@ -0,0 +1,25 @@ +has('config')) +{ + $app['cache']->forget('config'); +} + +$config = new Models\Config; + +foreach ($config->everything() as $item) +{ + $app['config']->set("feather.{$item->name}", $item->value); +} + +/* +|-------------------------------------------------------------------------- +| Feather Providers +|-------------------------------------------------------------------------- +| +| Now that most of Feather has been bootstrapped we can register the other +| providers that Feather uses. This keeps everything neat and tidy. +| +*/ + +$providers = array( + 'Feather\Presenter\PresenterServiceProvider', + 'Feather\Extensions\ExtensionServiceProvider' +); + +foreach ($providers as $provider) +{ + $app->register(new $provider); +} + +/* +|-------------------------------------------------------------------------- +| Prepare Feather Presenter +|-------------------------------------------------------------------------- +| +| Presenter is responsible for Feather's views. We'll prepare the presenter +| now, which will start the theme and set some view paths. +| +*/ + +$app['feather.presenter']->prepare(); + +/* +|-------------------------------------------------------------------------- +| Feather Extensions +|-------------------------------------------------------------------------- +| +| Register the activated extensions with Feather. +| +*/ + +$extensions = new Models\Extension; + +$app['feather.extensions']->registerExtensions($extensions->getActivated()); + +/* +|-------------------------------------------------------------------------- +| Require The Facades File +|-------------------------------------------------------------------------- +| +| We'll now register some facades to easily access some of Feather's +| components statically. +| +*/ + +require __DIR__.'/facades.php'; + +/* +|-------------------------------------------------------------------------- +| Require The Console File +|-------------------------------------------------------------------------- +| +| Commands are registered with the application container and when Artisan +| is run they are resolved from the Artisan start file. +| +*/ + +require __DIR__.'/console.php'; + +/* +|-------------------------------------------------------------------------- +| Require The Filters File +|-------------------------------------------------------------------------- +| +| Next we will load the filters file for the application. This gives us +| a nice separate location to store our route and application filter +| definitions instead of putting them all in the main routes file. +| +*/ + +require __DIR__.'/../filters.php'; \ No newline at end of file diff --git a/feather/storage/.gitignore b/app/storage/.gitignore similarity index 100% rename from feather/storage/.gitignore rename to app/storage/.gitignore diff --git a/feather/storage/cache/.gitignore b/app/storage/cache/.gitignore similarity index 100% rename from feather/storage/cache/.gitignore rename to app/storage/cache/.gitignore diff --git a/feather/storage/logs/.gitignore b/app/storage/logs/.gitignore similarity index 100% rename from feather/storage/logs/.gitignore rename to app/storage/logs/.gitignore diff --git a/feather/storage/meta/.gitignore b/app/storage/meta/.gitignore similarity index 100% rename from feather/storage/meta/.gitignore rename to app/storage/meta/.gitignore diff --git a/feather/storage/sessions/.gitignore b/app/storage/sessions/.gitignore similarity index 100% rename from feather/storage/sessions/.gitignore rename to app/storage/sessions/.gitignore diff --git a/feather/storage/views/.gitignore b/app/storage/views/.gitignore similarity index 100% rename from feather/storage/views/.gitignore rename to app/storage/views/.gitignore diff --git a/feather/views/hello.php b/app/views/hello.php similarity index 100% rename from feather/views/hello.php rename to app/views/hello.php diff --git a/composer.json b/composer.json index 5f1b264..669dbe3 100644 --- a/composer.json +++ b/composer.json @@ -5,10 +5,15 @@ }, "autoload": { "classmap": [ - "feather/controllers", - "feather/models", - "feather/database/migrations", - "feather/tests/TestCase.php" + "feather/extensions", + "feather/presenter", + "feather/database", + "feather/console", + "app/controllers", + "app/models", + "app/database/migrations", + "tests/TestCase.php", + "extensions" ] }, "minimum-stability": "dev" diff --git a/extensions/.gitignore b/extensions/.gitignore new file mode 100644 index 0000000..f59ec20 --- /dev/null +++ b/extensions/.gitignore @@ -0,0 +1 @@ +* \ No newline at end of file diff --git a/feather/auth/AuthServiceProvider.php b/feather/auth/AuthServiceProvider.php new file mode 100644 index 0000000..fc19f3f --- /dev/null +++ b/feather/auth/AuthServiceProvider.php @@ -0,0 +1,27 @@ +extend('feather', function($app) + { + $provider = new FeatherUserProvider($app['hash'], $app['feather']['migrator']); + + return new Guard($provider, $app['session']); + }); + + $app['config']->set('auth.driver', 'feather'); + } + +} \ No newline at end of file diff --git a/feather/auth/FeatherUserProvider.php b/feather/auth/FeatherUserProvider.php new file mode 100644 index 0000000..3a7d79a --- /dev/null +++ b/feather/auth/FeatherUserProvider.php @@ -0,0 +1,97 @@ +hasher = $hasher; + $this->migrator = $migrator; + } + + /** + * Retrieve a user by their unique idenetifier. + * + * @param mixed $identifier + * @return Illuminate\Auth\UserInterface|null + */ + public function retrieveByID($identifier) + { + $user = User::find($identifier); + + if ( ! is_null($user)) + { + return $user; + } + } + + /** + * Retrieve a user by the given credentials. + * + * @param array $credentials + * @return Illuminate\Auth\UserInterface|null + */ + public function retrieveByCredentials(array $credentials) + { + // First we'll attempt to locate a user by checking for the username they provided when logging in. + if ( ! $user = User::where('username', $credentials['username'])->first()) + { + return; + } + + // If we have a migrator interface then we need confirm that the user has been migrated previously. If + // the user has not been migrated then we'll leave it to the migrator to handle it. + if ($this->migrator->isActive()) + { + dd('here'); + } + + + if ( ! is_null($user)) + { + return $user; + } + } + + /** + * Validate a user against the given credentials. + * + * @param Illuminate\Auth\UserInterface $user + * @param array $credentials + * @return bool + */ + public function validateCredentials(UserInterface $user, array $credentials) + { + $plain = $credentials['password']; + + return $this->hasher->check($plain, $user->getAuthPassword()); + } + +} \ No newline at end of file diff --git a/feather/console/FeatherCommand.php b/feather/console/FeatherCommand.php new file mode 100644 index 0000000..dd85508 --- /dev/null +++ b/feather/console/FeatherCommand.php @@ -0,0 +1,30 @@ +line('Feather version '.FEATHER_VERSION.''); + } +} \ No newline at end of file diff --git a/feather/console/PublishCommand.php b/feather/console/PublishCommand.php new file mode 100644 index 0000000..7689702 --- /dev/null +++ b/feather/console/PublishCommand.php @@ -0,0 +1,138 @@ +publisher = $publisher; + $this->themePath = $themePath; + $this->extensionPath = $extensionPath; + } + + /** + * Execute the console command. + * + * @return void + */ + public function fire() + { + // The publish command encompasses both themes and extensions. Because of this a flag must be provided so that the + // publisher knows what's being published and where it is located. + if ( ! $this->input->getOption('theme') and ! $this->input->getOption('extension')) + { + $this->comment("Please use either the --theme or --extension flag."); + + return; + } + + $name = $this->input->getArgument('name'); + + // Using the AssetPublisher we can publish our themes or extensions to the correct destination within our public + // directory. If publishing fails we'll catch the exception and let the user no that they probably spelt the + // theme or extension incorrectly. + + if ($this->input->getOption('theme')) + { + try + { + $this->publisher->publish("{$this->themePath}/{$name}/public", "../feather/themes/{$name}"); + + $this->line("Successfully published theme: {$name}"); + } + catch (RuntimeException $error) + { + $this->comment('Failed to publish theme. There may be nothing to publish.'); + } + } + elseif ($this->input->getOption('extension')) + { + try + { + $this->publisher->publish("{$this->extensionPath}/{$name}/public", "../feather/extensions/{$name}"); + + $this->line("Successfully published extension: {$name}"); + } + catch (RuntimeException $error) + { + $this->comment('Failed to publish extension. There may be nothing to publish.'); + } + } + } + + /** + * Get the command arguments. + * + * @return array + */ + public function getArguments() + { + return array( + array('name', InputArgument::REQUIRED, 'Name of the theme or extension to publish') + ); + } + + /** + * Get the command options. + * + * @return array + */ + public function getOptions() + { + return array( + array('theme', null, InputOption::VALUE_NONE, 'Tells publisher to publish a theme'), + array('extension', null, InputOption::VALUE_NONE, 'Tells publisher to publish an extension'), + ); + } + +} \ No newline at end of file diff --git a/feather/controllers/HomeController.php b/feather/controllers/HomeController.php deleted file mode 100644 index aadbbfe..0000000 --- a/feather/controllers/HomeController.php +++ /dev/null @@ -1,10 +0,0 @@ -model->cachable[$this->model->table])) + { + list($group, $foreign) = $this->model->cachable[$this->model->table]; + + if (Cache::has("{$group}_{$id}")) + { + return Cache::get("{$group}_{$id}"); + } + } + + $this->query->where($this->model->getKeyName(), '=', $id); + + return $this->first($columns); + } + +} \ No newline at end of file diff --git a/feather/extensions/Dispatcher.php b/feather/extensions/Dispatcher.php new file mode 100644 index 0000000..bfc76e7 --- /dev/null +++ b/feather/extensions/Dispatcher.php @@ -0,0 +1,147 @@ +files = $files; + $this->path = $path; + } + + /** + * Set the Laravel application instance. + * + * @param Illuminate\Container $app + * @return void + */ + public function setApplication(Container $app) + { + $this->app = $app; + } + + /** + * Register an array of extensions with the dispatcher. + * + * @param array $extensions + * @return void + */ + public function registerExtensions($extensions) + { + foreach ($extensions as $extension) + { + $this->register($extension); + } + } + + /** + * Register an extension with the dispatcher. + * + * @param Feather\Models\Extension $extension + * @return void + */ + public function register(ExtensionModel $extension) + { + $path = $this->path.'/'.$extension->location; + + if ($this->files->exists($path)) + { + $extension->path = $path; + + $extension->loaded = array(); + + $this["extension.{$extension->identifier}"] = $extension; + + // If an extension is set to be automatically started then we'll hand it off to + // the starting method. + if ($extension->auto) + { + $this->start($extension->identifier); + } + } + } + + /** + * Start an extension. + * + * @param string $extension + * @return void + */ + public function start($extension) + { + if (in_array($extension, $this->started) or ! isset($this["extension.{$extension}"])) + { + return; + } + + $extension = $this["extension.{$extension}"]; + + foreach (new FilesystemIterator($extension->path) as $file) + { + $name = $file->getBasename(".{$file->getExtension()}"); + + if (ends_with($name, 'Extension')) + { + $location = str_replace('/', '\\', $extension->location); + + $class = "Feather\\Extensions\\{$location}\\{$name}"; + + // Instantiate the new extension class and assign it to the extensions loaded classes. The class + // receives an instance of the Laravel application. + $extension->loaded = array_merge($extension->loaded, array($class => new $class($this->app))); + + // If the extension has the start method we'll fire it, this is a great place for extensions + // to do any bootstrapping or listening to events without having to override the constructor. + if (method_exists($extension->loaded[$class], 'start')) + { + $extension->loaded[$class]->start($this->app); + } + } + } + } + + public function fire() + { + + } + +} \ No newline at end of file diff --git a/feather/extensions/Extension.php b/feather/extensions/Extension.php new file mode 100644 index 0000000..bccc9f6 --- /dev/null +++ b/feather/extensions/Extension.php @@ -0,0 +1,113 @@ +app = $app; + } + + /** + * Listen for an event and fire a method or closure handler. + * + * @param string $event + * @param string|Closure $handler + * @return void + */ + public function listen($event, $handler) + { + $this->bindEvent('listen', $event, $handler); + } + + /** + * Overrides existing events listening for an event and fire + * a method or closure handler. + * + * @param string $event + * @param string|Closure $handler + * @return void + */ + public function override($event, $handler) + { + $this->bindEvent('override', $event, $handler); + } + + /** + * Binds events for the extension. + * + * @param string $type + * @param string $event + * @param string|Closure $handler + * @return void + */ + private function bindEvent($type, $event, $handler) + { + // Set the current extension instance so we can use it within the event closure. + $extension = $this; + + $this->app['events']->$type($event, function($parameters = array()) use ($extension, $handler) + { + // If the handler is a callable closure then we'll execute the closure + // and return its result. + if (is_callable($handler)) + { + return $handler($parameters); + } + + // If the handler is a method that exists on the extension object then execute + // the method and return its result. + elseif (method_exists($extension, $handler)) + { + return call_user_func_array(array($extension, $handler), $parameters); + } + + // By default we'll return null, as the event dispatcher will expect a non + // null result. + return null; + }); + } + + /** + * Executed when an extension is installed. + * + * @return void + */ + public function installed(){} + + /** + * Executed when an extension is activated. + * + * @return void + */ + public function activated(){} + + /** + * Executed when an extension is deactivated. + * + * @return void + */ + public function deactivated(){} + + /** + * Executed when an extension is removed. + * + * @return void + */ + public function removed(){} + +} \ No newline at end of file diff --git a/feather/extensions/ExtensionServiceProvider.php b/feather/extensions/ExtensionServiceProvider.php new file mode 100644 index 0000000..4a0099c --- /dev/null +++ b/feather/extensions/ExtensionServiceProvider.php @@ -0,0 +1,23 @@ +share(function($app) + { + return new Dispatcher($app['files'], $app['path.extensions']); + }); + + $app['feather.extensions']->setApplication($app); + } + +} \ No newline at end of file diff --git a/feather/migrator/DriverInterface.php b/feather/migrator/DriverInterface.php new file mode 100644 index 0000000..9d014ff --- /dev/null +++ b/feather/migrator/DriverInterface.php @@ -0,0 +1,33 @@ +app = $app; + } + + /** + * Set the migrator database record. + * + * @param Feather\Models\Migrator $migrator + */ + public function setMigratorRecord(Migrator $migrator) + { + $this->migratorRecord = $migrator; + + return $this; + } + + /** + * Determine if the driver has a migrator database record. + * + * @return bool + */ + public function hasMigratorRecord() + { + return ! is_null($this->migratorRecord); + } + +} \ No newline at end of file diff --git a/feather/migrator/Drivers/FluxDriver.php b/feather/migrator/Drivers/FluxDriver.php new file mode 100644 index 0000000..abf1211 --- /dev/null +++ b/feather/migrator/Drivers/FluxDriver.php @@ -0,0 +1,35 @@ +app = $app; + $this->connection = $connection; + $this->active = $active; + } + + /** + * Get the default migrator driver name. + * + * @return string + */ + protected function getDefaultDriver() + { + return $this->app['config']->get('feather::migrator.driver'); + } + + /** + * Call a custom driver creator. + * + * @param string $driver + * @return mixed + */ + protected function callCustomCreator($driver) + { + return $this->customCreators[$driver]($this->app, $this->connection); + } + + /** + * Create a new flux driver instance. + * + * @return Feather\Migrator\Drivers\FluxDriver + */ + protected function createFluxDriver() + { + return new Drivers\FluxDriver($this->app); + } + + /** + * Determine if the migrator is active. + * + * @return bool + */ + public function isActive() + { + return $this->active; + } + +} \ No newline at end of file diff --git a/feather/migrator/MigratorServiceProvider.php b/feather/migrator/MigratorServiceProvider.php new file mode 100644 index 0000000..eb13d35 --- /dev/null +++ b/feather/migrator/MigratorServiceProvider.php @@ -0,0 +1,49 @@ +share(function() use ($app) + { + // Get the active migrator driver from the database. We'll then determine the connection to be used + // for the migrator. + $migrator = new Migrator; + + $migrator = $migrator->getMigratorDriver($app['config']->get('feather::migrator.driver')); + + // By default the database connection will be Feather's database connection. This is useful when you want + // the original tables from the old system to be on the same database as Feather. Migrators can overwrite + // this default connection. + $connection = $app['db']->connection(FEATHER_DATABASE); + + if ( ! is_null($migrator->options->database)) + { + $app['config']->set('database.connections.feather:migrator', array( + 'driver' => $migrator->options->driver, + 'host' => $migrator->options->host, + 'database' => $migrator->options->database, + 'username' => $migrator->options->username, + 'password' => $migrator->options->password, + 'charset' => $migrator->options->charset, + 'prefix' => $migrator->options->prefix, + 'collation' => $migrator->options->collation + )); + + $connection = $app['db']->connection('feather:migrator'); + } + + return new MigratorManager($app, $connection, ! is_null($migrator)); + }); + } + +} \ No newline at end of file diff --git a/feather/models/User.php b/feather/models/User.php deleted file mode 100644 index e2bcec5..0000000 --- a/feather/models/User.php +++ /dev/null @@ -1,41 +0,0 @@ -getKey(); - } - - /** - * Get the password for the user. - * - * @return string - */ - public function getAuthPassword() - { - return $this->password; - } - -} \ No newline at end of file diff --git a/feather/presenter/Compilers/FeatherCompiler.php b/feather/presenter/Compilers/FeatherCompiler.php new file mode 100644 index 0000000..f60f042 --- /dev/null +++ b/feather/presenter/Compilers/FeatherCompiler.php @@ -0,0 +1,73 @@ +compilers = array_merge($this->compilers, array( + 'Assignments', + 'ExtensionEvents', + 'InlineErrors', + 'Errors' + )); + + return parent::compileString($value); + } + + /** + * Compile assignments into valid PHP. + * + * @param string $value + * @return string + */ + public function compileAssignments($value) + { + return preg_replace('/(\s*)@assign\s*\(\$(.*), (.*)\)(\s*)/', '$1$4', $value); + } + + /** + * Compile extension events into valid PHP. + * + * @param string $value + * @return string + */ + public function compileExtensionEvents($value) + { + $pattern = $this->createMatcher('event'); + + return preg_replace($pattern, '$1', $value); + } + + /** + * Compile inline errors into valid PHP. + * + * @param string $value + * @return string + */ + public function compileInlineErrors($value) + { + $pattern = $this->createMatcher('error'); + + return preg_replace($pattern, '$1has$2 ? view("feather::errors.inline", array("error" => $errors->first$2)) : null; ?>', $value); + } + + /** + * Compile errors into valid PHP. + * + * @param string $value + * @return string + */ + public function compileErrors($value) + { + return str_replace('@errors', 'all() ? view("feather::errors.page", array("errors" => $errors->all())) : null; ?>', $value); + } + +} \ No newline at end of file diff --git a/feather/presenter/Presenter.php b/feather/presenter/Presenter.php new file mode 100644 index 0000000..4d13e14 --- /dev/null +++ b/feather/presenter/Presenter.php @@ -0,0 +1,54 @@ +app = $app; + } + + /** + * Prepare the theme by requiring a starter file if it exists. + * + * @return void + */ + public function prepare() + { + // If we are within theme development mode and not running within the console then we'll publish the current themes + // assets. This is especially handy when you're making a lot of changes to your themes assets. + if ($this->app['config']->get('feather.forum.theme_development_mode') and ! $this->app->runningInConsole()) + { + //$this->app['artisan']->call('feather:publish', array('name' => $this->app['config']->get('feather::forum.theme'), '--theme' => true)); + } + + // Assign a namespace and some cascading paths so that view files are first searched + // for within a theme then within the core view directory. + $this->app['view']->addLocation($this->app['path.themes'].'/'.$this->app['config']->get('feather.forum.theme').'/views'); + $this->app['view']->addLocation($this->app['path'].'/views'); + + // If the theme has a start file require the file to bootstrap the theme. + $start = $this->app['path.themes'].'/'.$this->app['config']->get('feather.forum.theme').'/start.php'; + + if ($this->app['files']->exists($start)) + { + // We'll make $app available to our start scripts so that they can access bound data and components. + $app = $this->app; + + require $start; + } + } + +} \ No newline at end of file diff --git a/feather/presenter/PresenterServiceProvider.php b/feather/presenter/PresenterServiceProvider.php new file mode 100644 index 0000000..5d63d50 --- /dev/null +++ b/feather/presenter/PresenterServiceProvider.php @@ -0,0 +1,43 @@ +share(function() use ($app) + { + return new Presenter($app); + }); + + $this->registerCompiler($app); + } + + /** + * Register the view compiler. + * + * @return void + */ + public function registerCompiler($app) + { + $app['view']->addExtension('blade.php', 'feather.compiler', function() use ($app) + { + // FeatherCompiler is used by Feather is an extension to the Blade compiler. Feather + // has a few special methods that are used throughout views that need to be compiled + // alongside the default Blade methods. + $compiler = new Compilers\FeatherCompiler($app['files'], $app['path'].'/storage/views'); + + return new CompilerEngine($compiler, $app['files']); + }); + } + +} \ No newline at end of file diff --git a/feather/start/global.php b/feather/start/global.php deleted file mode 100644 index 3a0bbd6..0000000 --- a/feather/start/global.php +++ /dev/null @@ -1,14 +0,0 @@ - - ./feather/tests/ + ./tests/ \ No newline at end of file diff --git a/start.php b/start.php index d10160e..869fe0a 100644 --- a/start.php +++ b/start.php @@ -28,7 +28,7 @@ | */ -$app['path'] = __DIR__.'/feather'; +$app['path'] = __DIR__.'/app'; $app['path.base'] = __DIR__; diff --git a/feather/tests/TestCase.php b/tests/TestCase.php similarity index 100% rename from feather/tests/TestCase.php rename to tests/TestCase.php diff --git a/themes/basic/public/css/theme.css b/themes/basic/public/css/theme.css new file mode 100644 index 0000000..f4daf7d --- /dev/null +++ b/themes/basic/public/css/theme.css @@ -0,0 +1,423 @@ +body { + background-image: url('../img/background.png'); + padding: 0; + margin: 0; + font-family: "Helvetica", "Helvetica Neue", Arial, sans-serif; + font-size: 13px; + color: #454545; + line-height: 1.45em; +} + +input, button, textarea { + color: #454545; + font-family: "Helvetica", "Helvetica Neue", Arial, sans-serif; + font-size: 1em; +} + +ul { + list-style-type: none; +} + +a { + color: #d7634c; +} + +a:hover { + text-decoration: none; +} + +.group:after, +fieldset dd:after, +.alert-floated:after { + content: ""; + display: table; + clear: both; +} + +/** + * The container element holds all other elements and is centered on our screen. + */ +.container { + position: relative; + width: 1140px; + margin: 0 auto; + padding: 0; +} + +/** + * The header contains the logo and some vital navigation for getting around the forum. + * Our user related data will also appear here when they are signed in. + */ +.header > .logo { + clear: right; + float: left; +} + +/** + * The user section of the header is above everything and is easily to see, it's fixed. + */ +.header > .user { + float: right; + margin: 10px 0 30px; + font-size: 14px; +} + +.header > .user a { + text-decoration: none; +} + +.header > .user > ul { + list-style-type: none; + margin: 0; + padding: 0; +} + +.header > .user > ul > li { + float: left; + padding: 0 18px; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; + background-color: #fff; + margin: 0 0 0 6px; + position: relative; + line-height: 34px; +} + +.header > .user > ul > li.light { background-color: #d1c9c1; } +.header > .user > ul > li.light:hover { background-color: #d9d2cb; } + +.header > .user > ul > li.dark { background: rgba(0, 0, 0, 0.6); } +.header > .user > ul > li.dark:hover { background: rgba(0, 0, 0, 0.7); } + +.header > .user > ul > li.attn { background: rgba(215, 99, 76, 0.8); } +.header > .user > ul > li.attn:hover { background: rgba(215, 99, 76, 0.95); } + +.header > .user > ul > li.link > a { + display: block; + margin: 0 -18px; + padding: 0 18px; +} + +.header > .user > ul > li.dark a, +.header > .user > ul > li.attn a { + color: #fff; + text-shadow: 0 1px 0 rgba(0, 0, 0, 0.15); +} + +/** + * When a menu with options is hovered we show another dropdown menu. + */ +.header > .user > ul > li.has-options:hover { + -moz-border-radius: 3px 3px 0 0; + -webkit-border-radius: 3px 3px 0 0; + border-radius: 3px 3px 0 0; +} + +.header > .user > ul > li.has-options > ul { + display: none; + list-style-type: none; + margin: 0; + padding: 0; + position: absolute; + top: 40px; + left: 0; + line-height: normal; + width: 350px; + -moz-border-radius: 0 2px 2px 2px; + -webkit-border-radius: 0 2px 2px 2px; + border-radius: 0 2px 2px 2px; + overflow: hidden; +} + +.header > .user > ul > li.dark.has-options > ul { background: rgba(0, 0, 0, 0.7); } +.header > .user > ul > li.attn.has-options > ul { background: rgba(215, 99, 76, 0.95); } + +.header > .user > ul > li.has-options:hover > ul { + display: block; +} + +/** + * Place a smaller bar after the hovered option so we can shift the actual dropdown below + * other menu items. + */ +.header > .user > ul > li.has-options:hover:before { + content: ""; + display: block; + width: 100%; + height: 6px; + position: absolute; + top: 34px; + left: 0; +} + +.header > .user > ul > li.dark.has-options:hover:before { background: rgba(0, 0, 0, 0.7); } +.header > .user > ul > li.attn.has-options:hover:before { background: rgba(215, 99, 76, 0.95); } + +/** + * Items for the dropdown menu. + */ +.header > .user > ul > li.has-options > ul > li { + float: left; + width: 50%; + padding: 4px; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +.header > .user > ul > li.has-options > ul > li > a { + display: block; + padding: 6px 14px; + color: #fff; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + border-radius: 2px; +} + +.header > .user > ul > li.attn.has-options > ul > li > a:hover { + background-color: #d15843; +} + +/** + * The header navigation is floated right and consists of nice squares, love the squares. + */ +.header > .navigation { + float: right; +} + +.header > .navigation > li { + float: left; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + border-radius: 2px; + margin: 0 0 0 3px; +} + +.header > .navigation > li > a { + display: block; + color: #999; + text-shadow: 0 1px 0 #fff; + text-decoration: none; + font-weight: bold; + font-size: 14px; + text-transform: lowercase; + padding: 8px 16px; +} + +.header > .navigation > li:hover { + background: rgba(0, 0, 0, 0.1); +} + +.header > .navigation > li.important { + background: rgba(215, 99, 76, 0.6); +} + +.header > .navigation > li.important:hover { + background: rgba(215, 99, 76, 0.8); +} + +.header > .navigation > li.selected { + background: rgba(0, 0, 0, 0.25); +} + +.header > .navigation > li.selected > a, +.header > .navigation > li.important > a { + color: #fff; + text-shadow: 0 1px 0 rgba(0, 0, 0, 0.2); +} + +/** + * The main body is a large container that stands out from the background. + */ +.body { + margin: 30px 0 0; + background-color: #fff; + box-shadow: 0 0 3px rgba(0, 0, 0, 0.1); + padding: 10px 10px 20px; + -moz-border-radius: 0 0 2px 2px; + -webkit-border-radius: 0 0 2px 2px; + border-radius: 0 0 2px 2px; +} + +.body .content { + clear: both; +} + +/** + * The breadcrumbs get nice little arrow separators. + */ +.breadcrumbs { + margin: -10px -10px 10px; + background-color: #f2f2f2; + border-bottom: 1px solid #e6e6e6; + text-shadow: 0 1px 0 #fff; + -moz-border-radius: 2px 2px 0 0; + -webkit-border-radius: 2px 2px 0 0; + border-radius: 2px 2px 0 0; + line-height: 40px; +} + +.breadcrumbs > li, +.breadcrumbs > li > a { + color: #666; +} + +.breadcrumbs > li { + display: inline-block; + margin: 0 0 0 -3px; + cursor: default; +} + +.breadcrumbs > li:first-child { margin-left: 0; } + +.breadcrumbs > li, +.breadcrumbs > li > a { + position: relative; + padding: 0 4px 0 26px; + color: #989898; +} + +.breadcrumbs > li > a { + display: block; + text-decoration: none; + margin: 0 -4px 0 -26px; +} + +.breadcrumbs > li:first-child, +.breadcrumbs > li:first-child > a { + position: relative; + padding: 0 4px 0 10px; + color: #989898; +} + +.breadcrumbs > li:first-child > a { + margin: 0 -4px 0 -10px; +} + +.breadcrumbs > li > a:hover, +.breadcrumbs > li > a:focus { + color: #555; +} + +.breadcrumbs > li:hover { + background-color: #f9f9f9; +} + +.breadcrumbs > li:active { + border-color: #f1f1f1; +} + +.breadcrumbs > li:before { + position: absolute; + display: inline-block; + content: ""; + top: -1px; + right: -20px; + border-top: 21px solid rgba(255, 255, 255, 0); + border-bottom: 21px solid rgba(255, 255, 255, 0); + border-left: 19px solid #e2e2e2; + z-index: 1; +} + +.breadcrumbs > li:hover:before, +.breadcrumbs > li:focus:before { + border-left-color: #d7d7d7; +} + +.breadcrumbs > li:after { + position: absolute; + display: inline-block; + content: ""; + top: -1px; + right: -18px; + border-top: 21px solid rgba(255, 255, 255, 0); + border-bottom: 21px solid rgba(255, 255, 255, 0); + border-left: 19px solid #f2f2f2; + z-index: 1; +} + +.breadcrumbs > li:hover:after { + border-left-color: #f9f9f9; +} + +.breadcrumbs > li:hover { text-decoration: none; } + +/** + * Headings appear throughout the body and are a different font and weight + * then other items. + */ +.body h1, +.body h2, +.body h3, +.body h4 { + margin: 0 0 16px; + font-family: "Trebuchet MS", "Ubuntu", serif; + font-size: 24px; + color: #999; + text-transform: uppercase; +} + +.body h2 { font-size: 20px; } +.body h3 { font-size: 18px; } +.body h4 { + font-size: 14px; + display: inline; +} + +.body h1 a, +.body h2 a { + text-decoration: none; +} + +.body p:first-child { margin-top: 0; } +.body p:last-child { margin-bottom: 0; } + +/** + * Body halves allow the page to be split in two equal halves. + */ +.halves > .half-left { + float: left; + width: 49%; +} + +.halves > .half-right { + margin: 0 0 0 51%; +} + +/** + * The footer simply appears under the body and nothing more. + */ +.footer { + font-size: 11px; + padding: 10px 0; + color: #999; +} + +.footer > .powered { + float: left; + margin: 0; +} + +.footer > .stats { + float: right; +} + +.footer > .stats > li { + float: left; + margin: 0 0 0 24px; +} + +.footer > .stats .unit { + display: inline-block; + background-color: #999; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; + color: #fff; + text-shadow: 0 1px 0 rgba(0, 0, 0, 0.1); + padding: 1px 4px; + box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.2); + font-weight: bold; + margin: 0 4px 0 0; +} \ No newline at end of file diff --git a/themes/basic/public/img/background.png b/themes/basic/public/img/background.png new file mode 100644 index 0000000000000000000000000000000000000000..3e3aca46b98cba878c55e5c8e451399093d70560 GIT binary patch literal 23173 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

1h2nDLqe>m=rswbkN*Aj# z&B7V|FEenp+4Q}g5l=HoF7nFV(nV-5igK-{ZSu$kHA}l&m8c}1Dbcnf9-qiElth8c zK&&TuBm~D|iN_=KXtnCAN~jU%eLUR1WMym#kHJ2v&kW``FIY6+l{(Rs2U3~)MM?F! z!RYY-#qt~%tmsoy^G##QG*>AMrS0Tr^kjwWN~;HHw#-P zn?65GSBpVZgh9!i6gOD{ey|(F3|`q2R!_65c=OBLC9cEri@M8Mhk1&k;`yySnksb4 zaTbgwc~IQ4gs9g(4ka9HAd~J$V^}sv)c-PHW~-{gfNe37e!cR$HNzTpOT<_@j6{hm z06}^?%QbmfEN+Xx>QGg?z_aDEhkK>EbDB9JlEamDc3zKQErezzGDznWB?)QeR_Ndg zp$Sgx-&w%w+L19reCwEcUW`yK4ZxuqC2K)!1gF9xbry=L#>MyZ)0($Uv&6J0f2Rb6 zlToNj_XU1}%lDO3P1kr)&D`roY+p66itRlTUU=A%W3i~butu+2;lqay9kZ@Q-%*;f zH?qfUie#a>&#jNG(3;T*I$24Y=-Y)7h$T) z98n=<9PU|7apKT#QL=U`h#0Av4pg~e5mW8w@->PpX(R#(Rvul7y=o`n7MHj(-ySDR zrs%d9iOjT}(_{rwDv@}BMt`x!&(3d=&m?Hen3yw|gj7YaBYi$10&QIQMBXJ`L<4hP zdUjP}h1CbAF1N-gE;Ux&YEsx*wHa1SH3I3{uPpPRaSuhS$!pCucL3t1u1!B-Rta8SC+G~$9;}ut9Hg+Qnzh|;!?kEEXfHA zGC8}dK`!lzK6;x7Sgzh!^-6bHW+@>=8un|fN>-1!JrV@u<}Qil)2B}w93(r+dnEGI z!yN#YgBsZYEVXlHqk`0__s1)d{$~%i(i#%jDT1ov3M*qInQ456!2RG`gk;6jd_hTV34&8uck(+Gxf z6|vm1_UKkbpOl=R3S}!@tQHcLYY2-l*x!kSa_*w#s9vH^6K$~nY%j)ERvwEio@+lb zyQ&i`%D!MxR+?iB$tz*XHaF21`;F|)CI4hcS{AybT$py1+9GIZxwjUYj|$!~_1vl5 zKt)P>!Cli#h@k9%bj2zvq@H?_!1_cf^h$al_jEr>`=D6NwrvrqR`4e#Cg+E#5vv8w z949d!pARt>iQUZpSU05Dgt z6%qO(;R)8%g4V=tt>k3qpXZoH>x<)MI=YCdd!bh%)Vy)!L(5-MbYCK#C^Qs);jCJ< z>N8K2I;|Q!dq*T06mW?Y+gSu+>~ESW`lm9C1;VOHiwK)yRmE|vvS~!Nq_CurA&J}7 zQ(WPAeuao{`h598HQHxc&yuBuMvCVRY}3G~z=e&h>eX&=trpf*j8zM0cUZBKJxFR> zy$%i%f-QagD;~zYO@!%wSD4oAA$#2W*;rlr&b+%a58s=ktc->MzI2P}G}$wxa9)F6 zd3?!@mfw_$pIR$dQ(rlkvc-Ht2%my6I~}Qbm6%d$$WiBoFhBKLvRjUXB~6iek1sDJ z0p*V>W3T+scDho=cgNc{6|E-a)7;b&q`KTymOeAPV!(S1hitr!?74KcI9pcfc{;X9GzrFa1afEQC0a2diozvs5bLsXR$pc%khX9A8mnqfsYcLpjMi2g6su}vlJd1)0Bb&3tNc?k$t-58_MmBrYQvY@ z>#_wI2bU4QC5Ih?Dq1WR^E9SVT55zpb_mk-#mSDJcI%sOzNylXraA7Xh{-9k#CsEd zTMtoK!x=sr;miO_rMVbvMslu@sR1r{U{Xd_4?AFW6OZvRy0T}~OseyZ9 zM{o(i$k6x?#zi#hY6Q>TdNt=~nG#p+oNe-S zIoXk(o^>=uxQtcB1XsqZJdsO$jaB7YB(E)bCqj!7%%qHm3d|BsNQjz7MNN`w`L<iI;FLh@PH&F1Z@)~>1duuE$i21E9b_(-t^rAD*-}(3|X#IZz3;_4qNOJ#D RU5o$#002ovPDHLkV1litTz&um literal 0 HcmV?d00001 diff --git a/themes/basic/public/img/heading.png b/themes/basic/public/img/heading.png new file mode 100644 index 0000000000000000000000000000000000000000..256be5a2f0782d2a9007e803957e149e7efe6e57 GIT binary patch literal 1729 zcmeAS@N?(olHy`uVBq!ia0vp^eL$?r!3HF!s9jVAQj#UE5hcO-X(i=}MX3yqDfvmM z3ZA)%>8U}fi7AzZCsS=07?>3@Ln2Bde0{8v^Ko2Tt~skz|cV7z)0WFNY~KZ%Gk)tz(4^Clz_GsrKDK}xwt{?0`hE?GD=Dctn~HE z%ggo3jrH=2()A53EiFN27#ZmTRp=I1=9MH?=;jqG!%T2VElw`VEGWs$&r<-In3$Ab zT4JjNbScCOxdm`z^NOLt1Pn0!io^naLp=kKmtYEgeeo;J&4sHjE(uCSxEHIz#UYgi zsro^w#rdU0$-sz9QwCX8VC7ttnpl!w6q28x0}I7~jQo=P;*9(P1?ON>h49S0l>G8y zuxejlE6=>*lEl2^R8JRMC7^!2%*+%kb2BqHV{-!+HzxyELqk_XM_=yk=Z7nBfk3xGDeq!wkCrKY$Q<>xAZJ#3YU+bvEw&4cPq z!R;1joO<j&mQA-`Zs}ge{hTB1;<{TikGN+{`>1E-baS?=rIRJo z)R>FAI`>Vua9rWf{hqWO#!Ww}_WVCmzGHXQR<1h}Fx zx7{x8f1-P-$J8ap_qf&(8L|1>r@l%!(RtazPs`>1AnFRJps!?{W*TAImHZ73MCna&OD;O7`k-Nq)!itV{TeXQ9C*$wra( zpsNjClg~6-Ok6sNr$#uf4@0sa81FhOF>=-O2TcRqDp0+kfRBnbgflj-9ni#d1N3>yd+@%WP{Z`gHVj*&*w!bLo16W{zRb~Zg?r!Y_(ywo?EIh4zN@;rP0m%lP)vTlD`8@uQNHE0*_E^>qbnE-;OgOf$lQ|gnw zWt!5vF7LZ*>iEuM&bBhvt81SqM;2mAN$4-E~SHEULX zfB);(uQzYreD&(p%F4>-=4J*`fZ~5{Ki808XU70nBRvCVMxb8Be^Smxsfi`2DGKG8 zB^e5dS&0=n`H3ldnR#jX42nNlIJtnTbU?O%9Lc~MprFu~k~wdwfk=*`R!hRFwKGJH zO_VZi_`r6vqanJN#fB-fHDIDZD`RKtqP{hnIt`vtKNhJZec0tOX@W(|3R5-NicZsP z1sh?JPnEJq-}gIVvm`@7q09YJPllG%(FZG5IH>Uf%AY4_+!FE7;yMx_!l}EY-SE9}BbSx)X+!TV z9xY7|**x@SsNCFG$-p77@8TKO8(G|Xjo~g1claV178!VOinZz(^f_JO>dO}JdiItl z)ZVn5nWaW0n>R+2pQjtEr9qHjZBUrPWGHgfVMVjE9NQZ4W7*wJa~Kt!o+Q{aG;U|w zH$mC8$)d_BFrp!OckZGxZXuXN$NjhA8stpCkV#!C8H>m&MeWS!~VrCworWK*zpwZK-g4NP^sHGl2k8vz> zFkv{f>6oLE=R}STvlb?B*yXjddK##7Nc75in{VRv&|yE=saw*l)zs`NB_goouN2N#u}s9-J_<+U=2#1U;qFUX$cPi literal 0 HcmV?d00001 diff --git a/themes/basic/public/img/sprites.png b/themes/basic/public/img/sprites.png new file mode 100644 index 0000000000000000000000000000000000000000..6768ab32b2b676825ce0f2348f263d293dee829b GIT binary patch literal 15896 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 VDKv{d|NK{5PD)9#Qrslye*q3d-1Yzf literal 0 HcmV?d00001 diff --git a/themes/basic/start.php b/themes/basic/start.php new file mode 100644 index 0000000..45dc4d9 --- /dev/null +++ b/themes/basic/start.php @@ -0,0 +1,6 @@ +add('feather/themes/basic/css/theme.css'); +})->apply('UriRewriteFilter', array($app['path.public'])); \ No newline at end of file diff --git a/themes/basic/views/theme.blade.php b/themes/basic/views/theme.blade.php new file mode 100644 index 0000000..5b84081 --- /dev/null +++ b/themes/basic/views/theme.blade.php @@ -0,0 +1,60 @@ + + + + + + + @event('view: before template.title') Title + + + {{ Basset::show('basic.css') }} + + + + +

+ +
+ +
+ +
+ + + + + +
+ +
+ + +
+ +
+
+ + + +
+ + + + + + \ No newline at end of file From 79f2f586d93cc5cc7014198832b27090871de7ce Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 25 Nov 2012 19:13:07 +1100 Subject: [PATCH 10/27] Updating .gitignore Signed-off-by: Jason Lewis --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index e27dc15..d6ab546 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ vendor composer.phar composer.lock -.DS_Store \ No newline at end of file +.DS_Store +*.sublime-* \ No newline at end of file From 65d576b6b5910c205d458027d6e797f23fabc01c Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Sun, 25 Nov 2012 19:54:54 -0600 Subject: [PATCH 11/27] added facades to alias config. --- app/config/app.php | 63 +++++++++++++++++++++++++++++++++------------- 1 file changed, 45 insertions(+), 18 deletions(-) diff --git a/app/config/app.php b/app/config/app.php index d141f90..4134b48 100644 --- a/app/config/app.php +++ b/app/config/app.php @@ -67,24 +67,6 @@ 'key' => 'YourSecretKey!!!', - /* - |-------------------------------------------------------------------------- - | Class Aliases - |-------------------------------------------------------------------------- - | - | This array of class aliases will be registered when this application - | is started. However, feel free to register as many as you wish as - | the aliases are "lazy" loaded so they don't hinder performance. - | - */ - - 'aliases' => array( - - 'Controller' => 'Illuminate\Routing\Controllers\Controller', - 'Eloquent' => 'Illuminate\Database\Eloquent\Model', - - ), - /* |-------------------------------------------------------------------------- | Autoloaded Service Providers @@ -124,4 +106,49 @@ ), + /* + |-------------------------------------------------------------------------- + | Class Aliases + |-------------------------------------------------------------------------- + | + | This array of class aliases will be registered when this application + | is started. However, feel free to register as many as you wish as + | the aliases are "lazy" loaded so they don't hinder performance. + | + */ + + 'aliases' => array( + + 'App' => 'Illuminate\Foundation\Facades\App', + 'Artisan' => 'Illuminate\Foundation\Facades\Artisan', + 'Auth' => 'Illuminate\Foundation\Facades\Auth', + 'Cache' => 'Illuminate\Foundation\Facades\Cache', + 'Config' => 'Illuminate\Foundation\Facades\Config', + 'Controller' => 'Illuminate\Routing\Controllers\Controller', + 'Cookie' => 'Illuminate\Foundation\Facades\Cookie', + 'Crypt' => 'Illuminate\Foundation\Facades\Crypt', + 'Crypt' => 'Illuminate\Foundation\Facades\Crypt', + 'DB' => 'Illuminate\Foundation\Facades\DB', + 'Eloquent' => 'Illuminate\Database\Eloquent\Model', + 'Event' => 'Illuminate\Foundation\Facades\Event', + 'File' => 'Illuminate\Foundation\Facades\File', + 'Hash' => 'Illuminate\Foundation\Facades\Hash', + 'Input' => 'Illuminate\Foundation\Facades\Input', + 'Lang' => 'Illuminate\Foundation\Facades\Lang', + 'Log' => 'Illuminate\Foundation\Facades\Log', + 'Mail' => 'Illuminate\Foundation\Facades\Mail', + 'Paginator' => 'Illuminate\Foundation\Facades\Paginator', + 'Redirect' => 'Illuminate\Foundation\Facades\Redirect', + 'Redis' => 'Illuminate\Foundation\Facades\Redis', + 'Request' => 'Illuminate\Foundation\Facades\Request', + 'Response' => 'Illuminate\Foundation\Facades\Response', + 'Route' => 'Illuminate\Foundation\Facades\Route', + 'Schema' => 'Illuminate\Foundation\Facades\Schema', + 'Session' => 'Illuminate\Foundation\Facades\Session', + 'URL' => 'Illuminate\Foundation\Facades\URL', + 'Validator' => 'Illuminate\Foundation\Facades\Validator', + 'View' => 'Illuminate\Foundation\Facades\View', + + ), + ); From eb5f447471120d2c7cdfb4324022307853bfeee6 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Sun, 25 Nov 2012 20:19:00 -0600 Subject: [PATCH 12/27] move basic filters into filter file. --- app/filters.php | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/app/filters.php b/app/filters.php index 3557f8b..2955f05 100644 --- a/app/filters.php +++ b/app/filters.php @@ -20,4 +20,45 @@ App::after(function($request, $response) { // +}); + +/* +|-------------------------------------------------------------------------- +| Authentication Filters +|-------------------------------------------------------------------------- +| +| The following filters are used to verify that the user of the current +| session is logged into this application. Also, a "guest" filter is +| responsible for performing the opposite. Both provide redirects. +| +*/ + +Route::filter('auth', function() +{ + if (Auth::guest()) return Redirect::route('login'); +}); + + +Route::filter('guest', function() +{ + if (Auth::check()) return Redirect::to('/'); +}); + +/* +|-------------------------------------------------------------------------- +| CSRF Protection Filter +|-------------------------------------------------------------------------- +| +| The CSRF filter is responsible for protecting your application against +| cross-site request forgery attacks. If this special token in a user +| session does not match the one given in this request, we'll bail. +| +*/ + +Route::filter('csrf', function() +{ + if (Session::getToken() != Input::get('csrf_token')) + { + throw new Illuminate\Session\TokenMismatchException; + } }); \ No newline at end of file From 5720c5fd9983f4b8a931a7b3f4cda26b5cd49d95 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Sun, 25 Nov 2012 23:01:58 -0600 Subject: [PATCH 13/27] added basic testing configuration for session and cache. --- app/config/testing/cache.php | 20 ++++++++++++++++++++ app/config/testing/session.php | 21 +++++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 app/config/testing/cache.php create mode 100644 app/config/testing/session.php diff --git a/app/config/testing/cache.php b/app/config/testing/cache.php new file mode 100644 index 0000000..16d3ae2 --- /dev/null +++ b/app/config/testing/cache.php @@ -0,0 +1,20 @@ + 'array', + +); \ No newline at end of file diff --git a/app/config/testing/session.php b/app/config/testing/session.php new file mode 100644 index 0000000..338aeba --- /dev/null +++ b/app/config/testing/session.php @@ -0,0 +1,21 @@ + 'array', + +); \ No newline at end of file From 5e09f1572077ea66e5a57c92bd57daae4d35b7fd Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Mon, 26 Nov 2012 18:31:10 +1100 Subject: [PATCH 14/27] Close mockery after test is done. Signed-off-by: Jason Lewis --- app/extensions/.gitignore | 1 + tests/TestCase.php | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 app/extensions/.gitignore diff --git a/app/extensions/.gitignore b/app/extensions/.gitignore new file mode 100644 index 0000000..f59ec20 --- /dev/null +++ b/app/extensions/.gitignore @@ -0,0 +1 @@ +* \ No newline at end of file diff --git a/tests/TestCase.php b/tests/TestCase.php index 1af7071..32d352b 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -1,7 +1,21 @@ Date: Mon, 26 Nov 2012 18:32:22 +1100 Subject: [PATCH 15/27] Moved the themes and extensions to within the app directory. Signed-off-by: Jason Lewis --- {themes => app/themes}/basic/public/css/theme.css | 0 .../themes}/basic/public/img/background.png | Bin {themes => app/themes}/basic/public/img/heading.png | Bin {themes => app/themes}/basic/public/img/loading.gif | Bin {themes => app/themes}/basic/public/img/sprites.png | Bin {themes => app/themes}/basic/start.php | 0 {themes => app/themes}/basic/views/theme.blade.php | 0 extensions/.gitignore | 1 - 8 files changed, 1 deletion(-) rename {themes => app/themes}/basic/public/css/theme.css (100%) rename {themes => app/themes}/basic/public/img/background.png (100%) rename {themes => app/themes}/basic/public/img/heading.png (100%) rename {themes => app/themes}/basic/public/img/loading.gif (100%) rename {themes => app/themes}/basic/public/img/sprites.png (100%) rename {themes => app/themes}/basic/start.php (100%) rename {themes => app/themes}/basic/views/theme.blade.php (100%) delete mode 100644 extensions/.gitignore diff --git a/themes/basic/public/css/theme.css b/app/themes/basic/public/css/theme.css similarity index 100% rename from themes/basic/public/css/theme.css rename to app/themes/basic/public/css/theme.css diff --git a/themes/basic/public/img/background.png b/app/themes/basic/public/img/background.png similarity index 100% rename from themes/basic/public/img/background.png rename to app/themes/basic/public/img/background.png diff --git a/themes/basic/public/img/heading.png b/app/themes/basic/public/img/heading.png similarity index 100% rename from themes/basic/public/img/heading.png rename to app/themes/basic/public/img/heading.png diff --git a/themes/basic/public/img/loading.gif b/app/themes/basic/public/img/loading.gif similarity index 100% rename from themes/basic/public/img/loading.gif rename to app/themes/basic/public/img/loading.gif diff --git a/themes/basic/public/img/sprites.png b/app/themes/basic/public/img/sprites.png similarity index 100% rename from themes/basic/public/img/sprites.png rename to app/themes/basic/public/img/sprites.png diff --git a/themes/basic/start.php b/app/themes/basic/start.php similarity index 100% rename from themes/basic/start.php rename to app/themes/basic/start.php diff --git a/themes/basic/views/theme.blade.php b/app/themes/basic/views/theme.blade.php similarity index 100% rename from themes/basic/views/theme.blade.php rename to app/themes/basic/views/theme.blade.php diff --git a/extensions/.gitignore b/extensions/.gitignore deleted file mode 100644 index f59ec20..0000000 --- a/extensions/.gitignore +++ /dev/null @@ -1 +0,0 @@ -* \ No newline at end of file From 9c3e9680a6d79fbd0b3f526bb312014c31943b18 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Mon, 26 Nov 2012 18:33:10 +1100 Subject: [PATCH 16/27] Updating of service providers to match latest L4 update. Signed-off-by: Jason Lewis --- app/config/app.php | 61 +++++++------- app/start/artisan.php | 3 +- app/start/console.php | 15 ++++ app/start/facades.php | 2 +- app/start/global.php | 45 +++++----- composer.json | 17 ++-- feather/auth/AuthServiceProvider.php | 7 +- feather/console/PublishCommand.php | 2 +- feather/extensions/Dispatcher.php | 82 +++++++++++++++---- feather/extensions/Extension.php | 40 +-------- feather/extensions/ExtensionInterface.php | 41 ++++++++++ .../extensions/ExtensionServiceProvider.php | 7 +- feather/migrator/MigratorServiceProvider.php | 5 +- feather/presenter/Presenter.php | 2 +- .../presenter/PresenterServiceProvider.php | 20 +++-- 15 files changed, 217 insertions(+), 132 deletions(-) create mode 100644 feather/extensions/ExtensionInterface.php diff --git a/app/config/app.php b/app/config/app.php index 3fc37fe..fdc52b6 100644 --- a/app/config/app.php +++ b/app/config/app.php @@ -120,35 +120,38 @@ 'aliases' => array( - 'App' => 'Illuminate\Foundation\Facades\App', - 'Artisan' => 'Illuminate\Foundation\Facades\Artisan', - 'Auth' => 'Illuminate\Foundation\Facades\Auth', - 'Cache' => 'Illuminate\Foundation\Facades\Cache', - 'Config' => 'Illuminate\Foundation\Facades\Config', - 'Controller' => 'Illuminate\Routing\Controllers\Controller', - 'Cookie' => 'Illuminate\Foundation\Facades\Cookie', - 'Crypt' => 'Illuminate\Foundation\Facades\Crypt', - 'Crypt' => 'Illuminate\Foundation\Facades\Crypt', - 'DB' => 'Illuminate\Foundation\Facades\DB', - 'Eloquent' => 'Illuminate\Database\Eloquent\Model', - 'Event' => 'Illuminate\Foundation\Facades\Event', - 'File' => 'Illuminate\Foundation\Facades\File', - 'Hash' => 'Illuminate\Foundation\Facades\Hash', - 'Input' => 'Illuminate\Foundation\Facades\Input', - 'Lang' => 'Illuminate\Foundation\Facades\Lang', - 'Log' => 'Illuminate\Foundation\Facades\Log', - 'Mail' => 'Illuminate\Foundation\Facades\Mail', - 'Paginator' => 'Illuminate\Foundation\Facades\Paginator', - 'Redirect' => 'Illuminate\Foundation\Facades\Redirect', - 'Redis' => 'Illuminate\Foundation\Facades\Redis', - 'Request' => 'Illuminate\Foundation\Facades\Request', - 'Response' => 'Illuminate\Foundation\Facades\Response', - 'Route' => 'Illuminate\Foundation\Facades\Route', - 'Schema' => 'Illuminate\Foundation\Facades\Schema', - 'Session' => 'Illuminate\Foundation\Facades\Session', - 'URL' => 'Illuminate\Foundation\Facades\URL', - 'Validator' => 'Illuminate\Foundation\Facades\Validator', - 'View' => 'Illuminate\Foundation\Facades\View', + 'App' => 'Illuminate\Foundation\Facades\App', + 'Artisan' => 'Illuminate\Foundation\Facades\Artisan', + 'Auth' => 'Illuminate\Foundation\Facades\Auth', + 'Basset' => 'Basset\Facades\Basset', + 'Cache' => 'Illuminate\Foundation\Facades\Cache', + 'Config' => 'Illuminate\Foundation\Facades\Config', + 'Controller' => 'Illuminate\Routing\Controllers\Controller', + 'Cookie' => 'Illuminate\Foundation\Facades\Cookie', + 'Crypt' => 'Illuminate\Foundation\Facades\Crypt', + 'Crypt' => 'Illuminate\Foundation\Facades\Crypt', + 'DB' => 'Illuminate\Foundation\Facades\DB', + 'Eloquent' => 'Illuminate\Database\Eloquent\Model', + 'Event' => 'Illuminate\Foundation\Facades\Event', + 'Feather\Extension' => 'Feather\Facades\Extension', + 'Feather\Presenter' => 'Feather\Facades\Presenter', + 'File' => 'Illuminate\Foundation\Facades\File', + 'Hash' => 'Illuminate\Foundation\Facades\Hash', + 'Input' => 'Illuminate\Foundation\Facades\Input', + 'Lang' => 'Illuminate\Foundation\Facades\Lang', + 'Log' => 'Illuminate\Foundation\Facades\Log', + 'Mail' => 'Illuminate\Foundation\Facades\Mail', + 'Paginator' => 'Illuminate\Foundation\Facades\Paginator', + 'Redirect' => 'Illuminate\Foundation\Facades\Redirect', + 'Redis' => 'Illuminate\Foundation\Facades\Redis', + 'Request' => 'Illuminate\Foundation\Facades\Request', + 'Response' => 'Illuminate\Foundation\Facades\Response', + 'Route' => 'Illuminate\Foundation\Facades\Route', + 'Schema' => 'Illuminate\Foundation\Facades\Schema', + 'Session' => 'Illuminate\Foundation\Facades\Session', + 'URL' => 'Illuminate\Foundation\Facades\URL', + 'Validator' => 'Illuminate\Foundation\Facades\Validator', + 'View' => 'Illuminate\Foundation\Facades\View', ), diff --git a/app/start/artisan.php b/app/start/artisan.php index 612583e..267f07f 100644 --- a/app/start/artisan.php +++ b/app/start/artisan.php @@ -13,5 +13,6 @@ $artisan->resolveCommands(array( 'command.feather', - 'command.feather.publish' + 'command.feather.publish', + 'command.feather.install' )); \ No newline at end of file diff --git a/app/start/console.php b/app/start/console.php index a1c8005..278d73d 100644 --- a/app/start/console.php +++ b/app/start/console.php @@ -27,4 +27,19 @@ $app['command.feather.publish'] = $app->share(function($app) { return new Console\PublishCommand($app['asset.publisher'], $app['path.themes'], $app['path.extensions']); +}); + +/* +|-------------------------------------------------------------------------- +| Install Command +|-------------------------------------------------------------------------- +| +| The install command is responsible for installing Feather in the current +| location. +| +*/ + +$app['command.feather.install'] = $app->share(function($app) +{ + return new Console\InstallCommand; }); \ No newline at end of file diff --git a/app/start/facades.php b/app/start/facades.php index 319f99d..09b5626 100644 --- a/app/start/facades.php +++ b/app/start/facades.php @@ -1,4 +1,4 @@ -register(new $provider); + $app->register(new $provider($app)); } /* |-------------------------------------------------------------------------- -| Prepare Feather Presenter +| Require The Facades File |-------------------------------------------------------------------------- | -| Presenter is responsible for Feather's views. We'll prepare the presenter -| now, which will start the theme and set some view paths. +| We'll now register some facades to easily access some of Feather's +| components statically. | */ -$app['feather.presenter']->prepare(); +require_once __DIR__.'/facades.php'; /* |-------------------------------------------------------------------------- -| Feather Extensions +| Require The Console File |-------------------------------------------------------------------------- | -| Register the activated extensions with Feather. +| Commands are registered with the application container and when Artisan +| is run they are resolved from the Artisan start file. | */ -$extensions = new Models\Extension; - -$app['feather.extensions']->registerExtensions($extensions->getActivated()); +require __DIR__.'/console.php'; /* |-------------------------------------------------------------------------- -| Require The Facades File +| Prepare Feather Presenter |-------------------------------------------------------------------------- | -| We'll now register some facades to easily access some of Feather's -| components statically. +| Presenter is responsible for Feather's views. We'll prepare the presenter +| now, which will start the theme and set some view paths. | */ -require __DIR__.'/facades.php'; +$app['feather.presenter']->prepare(); /* |-------------------------------------------------------------------------- -| Require The Console File +| Feather Extensions |-------------------------------------------------------------------------- | -| Commands are registered with the application container and when Artisan -| is run they are resolved from the Artisan start file. +| Register the activated extensions with Feather. | */ -require __DIR__.'/console.php'; +$extensions = new Models\Extension; + +$app['feather.extensions']->registerExtensions($extensions->getActivated()); /* |-------------------------------------------------------------------------- diff --git a/composer.json b/composer.json index 669dbe3..37cafb4 100644 --- a/composer.json +++ b/composer.json @@ -3,17 +3,22 @@ "illuminate/foundation": ">=1.0.0", "jasonlewis/basset": "*" }, + "require-dev": { + "mockery/mockery": "0.7.*" + }, "autoload": { "classmap": [ - "feather/extensions", - "feather/presenter", - "feather/database", - "feather/console", + "feather/Auth", + "feather/Console", + "feather/Database", + "feather/Extensions", + "feather/Migrator", + "feather/Presenter", "app/controllers", "app/models", + "app/extensions", "app/database/migrations", - "tests/TestCase.php", - "extensions" + "tests/TestCase.php" ] }, "minimum-stability": "dev" diff --git a/feather/auth/AuthServiceProvider.php b/feather/auth/AuthServiceProvider.php index fc19f3f..7c569d4 100644 --- a/feather/auth/AuthServiceProvider.php +++ b/feather/auth/AuthServiceProvider.php @@ -9,19 +9,18 @@ class AuthServiceProvider extends ServiceProvider { /** * Register the service provider. * - * @param Illuminate\Foundation\Application $app * @return void */ - public function register($app) + public function register() { - $app['auth']->extend('feather', function($app) + $this->app['auth']->extend('feather', function($app) { $provider = new FeatherUserProvider($app['hash'], $app['feather']['migrator']); return new Guard($provider, $app['session']); }); - $app['config']->set('auth.driver', 'feather'); + $this->app['config']->set('auth.driver', 'feather'); } } \ No newline at end of file diff --git a/feather/console/PublishCommand.php b/feather/console/PublishCommand.php index 7689702..9dfe8a5 100644 --- a/feather/console/PublishCommand.php +++ b/feather/console/PublishCommand.php @@ -20,7 +20,7 @@ class PublishCommand extends Command { * * @var string */ - protected $description = 'Publish extensions or themes'; + protected $description = 'Publish extension or theme assets'; /** * Asset publisher instance. diff --git a/feather/extensions/Dispatcher.php b/feather/extensions/Dispatcher.php index bfc76e7..92bb48a 100644 --- a/feather/extensions/Dispatcher.php +++ b/feather/extensions/Dispatcher.php @@ -73,6 +73,16 @@ public function registerExtensions($extensions) } } + /** + * Get the started extensions. + * + * @return array + */ + public function getStarted() + { + return $this->started; + } + /** * Register an extension with the dispatcher. * @@ -100,25 +110,47 @@ public function register(ExtensionModel $extension) } } + /** + * Determine if an extension is regisetered. + * + * @param string $identifier + * @return bool + */ + public function isRegistered($identifier) + { + return isset($this["extension.{$identifier}"]); + } + + /** + * Determine if an extension is started. + * + * @param string $identifier + * @return bool + */ + public function isStarted($identifier) + { + return in_array($identifier, $this->started); + } + /** * Start an extension. * - * @param string $extension + * @param string $identifier * @return void */ - public function start($extension) + public function start($identifier) { - if (in_array($extension, $this->started) or ! isset($this["extension.{$extension}"])) + if ($this->isStarted($identifier) or ! $this->isRegistered($identifier)) { return; } - - $extension = $this["extension.{$extension}"]; - foreach (new FilesystemIterator($extension->path) as $file) + $extension = $this["extension.{$identifier}"]; + + foreach ($this->findExtensions($extension->path) as $file) { $name = $file->getBasename(".{$file->getExtension()}"); - + if (ends_with($name, 'Extension')) { $location = str_replace('/', '\\', $extension->location); @@ -127,16 +159,36 @@ public function start($extension) // Instantiate the new extension class and assign it to the extensions loaded classes. The class // receives an instance of the Laravel application. - $extension->loaded = array_merge($extension->loaded, array($class => new $class($this->app))); - - // If the extension has the start method we'll fire it, this is a great place for extensions - // to do any bootstrapping or listening to events without having to override the constructor. - if (method_exists($extension->loaded[$class], 'start')) - { - $extension->loaded[$class]->start($this->app); - } + $extension->loaded = array_merge($extension->loaded, array($class => $this->loadExtension($class))); + + $extension->loaded[$class]->start($this->app); } } + + // Add the extension to the array of started extensions. + $this->started[] = $identifier; + } + + /** + * Get a FilesystemIterator to find the extensions within a path. + * + * @param string $path + * @return FilesystemIterator + */ + public function findExtensions($path) + { + return new FilesystemIterator($path); + } + + /** + * Create a new extension instance. + * + * @param string $class + * @return Feather\Extensions\Extension + */ + public function loadExtension($class) + { + return new $class($this->app); } public function fire() diff --git a/feather/extensions/Extension.php b/feather/extensions/Extension.php index bccc9f6..010eec8 100644 --- a/feather/extensions/Extension.php +++ b/feather/extensions/Extension.php @@ -69,45 +69,9 @@ private function bindEvent($type, $event, $handler) return $handler($parameters); } - // If the handler is a method that exists on the extension object then execute - // the method and return its result. - elseif (method_exists($extension, $handler)) - { - return call_user_func_array(array($extension, $handler), $parameters); - } - - // By default we'll return null, as the event dispatcher will expect a non - // null result. - return null; + // Finally assume that the handler is a method on the extension itself. + return call_user_func_array(array($extension, $handler), $parameters); }); } - /** - * Executed when an extension is installed. - * - * @return void - */ - public function installed(){} - - /** - * Executed when an extension is activated. - * - * @return void - */ - public function activated(){} - - /** - * Executed when an extension is deactivated. - * - * @return void - */ - public function deactivated(){} - - /** - * Executed when an extension is removed. - * - * @return void - */ - public function removed(){} - } \ No newline at end of file diff --git a/feather/extensions/ExtensionInterface.php b/feather/extensions/ExtensionInterface.php new file mode 100644 index 0000000..b5041d6 --- /dev/null +++ b/feather/extensions/ExtensionInterface.php @@ -0,0 +1,41 @@ +share(function($app) + $this->app['feather.extensions'] = $this->app->share(function($app) { return new Dispatcher($app['files'], $app['path.extensions']); }); - $app['feather.extensions']->setApplication($app); + $this->app['feather.extensions']->setApplication($this->app); } } \ No newline at end of file diff --git a/feather/migrator/MigratorServiceProvider.php b/feather/migrator/MigratorServiceProvider.php index eb13d35..c940ce8 100644 --- a/feather/migrator/MigratorServiceProvider.php +++ b/feather/migrator/MigratorServiceProvider.php @@ -8,12 +8,11 @@ class MigratorServiceProvider extends ServiceProvider { /** * Register the service provider. * - * @param Illuminate\Foundation\Application $app * @return void */ - public function register($app) + public function register() { - $app['feather']['migrator'] = $app->share(function() use ($app) + $this->app['feather.migrator'] = $this->app->share(function($app) { // Get the active migrator driver from the database. We'll then determine the connection to be used // for the migrator. diff --git a/feather/presenter/Presenter.php b/feather/presenter/Presenter.php index 4d13e14..4a60c37 100644 --- a/feather/presenter/Presenter.php +++ b/feather/presenter/Presenter.php @@ -31,7 +31,7 @@ public function prepare() // assets. This is especially handy when you're making a lot of changes to your themes assets. if ($this->app['config']->get('feather.forum.theme_development_mode') and ! $this->app->runningInConsole()) { - //$this->app['artisan']->call('feather:publish', array('name' => $this->app['config']->get('feather::forum.theme'), '--theme' => true)); + $this->app['artisan']->call('feather:publish', array('name' => $this->app['config']->get('feather::forum.theme'), '--theme' => true)); } // Assign a namespace and some cascading paths so that view files are first searched diff --git a/feather/presenter/PresenterServiceProvider.php b/feather/presenter/PresenterServiceProvider.php index 5d63d50..cb0e53a 100644 --- a/feather/presenter/PresenterServiceProvider.php +++ b/feather/presenter/PresenterServiceProvider.php @@ -9,17 +9,16 @@ class PresenterServiceProvider extends ServiceProvider { /** * Register the service provider. * - * @param Illuminate\Foundation\Application $app * @return void */ - public function register($app) + public function register() { - $app['feather.presenter'] = $app->share(function() use ($app) + $this->app['feather.presenter'] = $this->app->share(function($app) { return new Presenter($app); }); - $this->registerCompiler($app); + $this->registerCompiler(); } /** @@ -27,16 +26,21 @@ public function register($app) * * @return void */ - public function registerCompiler($app) + public function registerCompiler() { - $app['view']->addExtension('blade.php', 'feather.compiler', function() use ($app) + // Our compiler needs an instance of the file system and the storage path of the compiled views. + $files = $this->app['files']; + + $storagePath = $this->app['path'].'/storage/views'; + + $this->app['view']->addExtension('blade.php', 'feather.compiler', function() use ($files, $storagePath) { // FeatherCompiler is used by Feather is an extension to the Blade compiler. Feather // has a few special methods that are used throughout views that need to be compiled // alongside the default Blade methods. - $compiler = new Compilers\FeatherCompiler($app['files'], $app['path'].'/storage/views'); + $compiler = new Compilers\FeatherCompiler($files, $storagePath); - return new CompilerEngine($compiler, $app['files']); + return new CompilerEngine($compiler, $files); }); } From 728a2b4010e1b0d92e085308330a5c792da8d4be Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Mon, 26 Nov 2012 18:33:20 +1100 Subject: [PATCH 17/27] Initial commit of tests. Signed-off-by: Jason Lewis --- tests/ExtensionDispatcherTest.php | 80 ++++++++++++++++++++++ tests/ExtensionTest.php | 108 ++++++++++++++++++++++++++++++ tests/FeatherCompilerTest.php | 46 +++++++++++++ tests/PresenterTest.php | 26 +++++++ 4 files changed, 260 insertions(+) create mode 100644 tests/ExtensionDispatcherTest.php create mode 100644 tests/ExtensionTest.php create mode 100644 tests/FeatherCompilerTest.php create mode 100644 tests/PresenterTest.php diff --git a/tests/ExtensionDispatcherTest.php b/tests/ExtensionDispatcherTest.php new file mode 100644 index 0000000..94758ec --- /dev/null +++ b/tests/ExtensionDispatcherTest.php @@ -0,0 +1,80 @@ +getDispatcher(); + $extension = new Extension(array( + 'location' => 'extension/location', + 'identifier' => 'foo', + 'auto' => false + )); + $dispatcher->register($extension); + $this->assertInstanceOf('Feather\Models\Extension', $dispatcher['extension.foo']); + $this->assertEquals('foo', $dispatcher['extension.foo']->identifier); + $this->assertEquals('extension/location', $dispatcher['extension.foo']->location); + $this->assertTrue($dispatcher->isRegistered('foo')); + } + + + public function testDispatcherRegistersExtensionsFromArray() + { + $dispatcher = $this->getDispatcher(); + $extension = new Extension(array( + 'location' => 'extension/location', + 'identifier' => 'foo', + 'auto' => false + )); + $dispatcher->registerExtensions(array($extension)); + $this->assertInstanceOf('Feather\Models\Extension', $dispatcher['extension.foo']); + $this->assertEquals('foo', $dispatcher['extension.foo']->identifier); + $this->assertEquals('extension/location', $dispatcher['extension.foo']->location); + } + + + public function testDispatcherAutoStartExtension() + { + $app = new Illuminate\Container; + $app['events'] = new Illuminate\Events\Dispatcher; + $files = m::mock('Illuminate\Filesystem'); + $files->shouldReceive('exists')->once()->andReturn(true); + $dispatcher = m::mock('Feather\Extensions\Dispatcher[findExtensions,loadExtension]'); + $dispatcher->__construct($files, 'path/to'); + $dispatcher->setApplication($app); + $extension = new Extension(array( + 'location' => 'extension/location', + 'identifier' => 'foo', + 'auto' => true + )); + $file = m::mock('stdClass'); + $file->shouldReceive('getBasename')->once()->andReturn('FooExtension'); + $file->shouldReceive('getExtension')->once()->andReturn('php'); + $instantiatedExtension = m::mock('Feather\Extensions\Extension'); + $instantiatedExtension->shouldReceive('start')->once(); + $dispatcher->shouldReceive('findExtensions')->once()->with('path/to/extension/location')->andReturn(array($file)); + $dispatcher->shouldReceive('loadExtension')->with('Feather\Extensions\extension\location\FooExtension')->andReturn($instantiatedExtension); + $dispatcher->register($extension); + $this->assertContains('foo', $dispatcher->getStarted()); + $this->assertTrue($dispatcher->isStarted('foo')); + } + + + protected function getDispatcher() + { + $app = new Illuminate\Container; + $app['events'] = new Illuminate\Events\Dispatcher; + $files = m::mock('Illuminate\Filesystem'); + $files->shouldReceive('exists')->once()->andReturn(true); + $dispatcher = m::mock(new Feather\Extensions\Dispatcher($files, 'path/to')); + $dispatcher->setApplication($app); + return $dispatcher; + } + + +} + diff --git a/tests/ExtensionTest.php b/tests/ExtensionTest.php new file mode 100644 index 0000000..9a97eb8 --- /dev/null +++ b/tests/ExtensionTest.php @@ -0,0 +1,108 @@ +getApplicationAndDispatcher(); + $extension = new Extension(array( + 'location' => 'extension/location', + 'identifier' => 'foo', + 'auto' => true + )); + $dispatcher->register($extension); + + $this->assertEquals('success', $app['events']->first('start_test')); + } + + + public function testExtensionsCanListenForEvents() + { + list($app, $dispatcher) = $this->getApplicationAndDispatcher(); + $extension = new Extension(array( + 'location' => 'extension/location', + 'identifier' => 'foo', + 'auto' => true + )); + $dispatcher->register($extension); + $extension->loaded['Feather\Extensions\extension\location\FooExtension']->listen('foobar', function() + { + return 'barfoo'; + }); + $extension->loaded['Feather\Extensions\extension\location\FooExtension']->listen('barfoo', function() + { + return 'foobar'; + }); + $this->assertEquals('barfoo', $app['events']->first('foobar')); + $this->assertEquals('foobar', $app['events']->first('barfoo')); + } + + + public function testExtensionsCanOverrideEvents() + { + list($app, $dispatcher) = $this->getApplicationAndDispatcher(); + $extension = new Extension(array( + 'location' => 'extension/location', + 'identifier' => 'foo', + 'auto' => true + )); + $dispatcher->register($extension); + $extension->loaded['Feather\Extensions\extension\location\FooExtension']->listen('foobar', function() + { + return 'barfoo'; + }); + $extension->loaded['Feather\Extensions\extension\location\FooExtension']->override('foobar', function() + { + return 'barbar'; + }); + $this->assertEquals('barbar', $app['events']->first('foobar')); + } + + + public function testExtensionsCanUseMethods() + { + list($app, $dispatcher) = $this->getApplicationAndDispatcher(); + $extension = new Extension(array( + 'location' => 'extension/location', + 'identifier' => 'foo', + 'auto' => true + )); + $dispatcher->register($extension); + $extension->loaded['Feather\Extensions\extension\location\FooExtension']->listen('foobar', 'foo'); + $this->assertEquals('foomethod', $app['events']->first('foobar')); + } + + + protected function getApplicationAndDispatcher() + { + $app = new Illuminate\Container; + $app['events'] = new Illuminate\Events\Dispatcher; + $app['files'] = m::mock('Illuminate\Filesystem'); + $app['files']->shouldReceive('exists')->once()->andReturn(true); + $dispatcher = m::mock('Feather\Extensions\Dispatcher[findExtensions,loadExtension]'); + $dispatcher->__construct($app['files'], 'path/to'); + $dispatcher->setApplication($app); + $file = m::mock('stdClass'); + $file->shouldReceive('getBasename')->once()->andReturn('FooExtension'); + $file->shouldReceive('getExtension')->once()->andReturn('php'); + $instantiatedExtension = m::mock('Feather\Extensions\Extension[start]'); + $instantiatedExtension->__construct($app); + $instantiatedExtension->shouldReceive('foo')->andReturn('foomethod'); + $instantiatedExtension->shouldReceive('start')->once()->andReturnUsing(function() use ($instantiatedExtension) + { + $instantiatedExtension->listen('start_test', function() + { + return 'success'; + }); + }); + $dispatcher->shouldReceive('findExtensions')->once()->with('path/to/extension/location')->andReturn(array($file)); + $dispatcher->shouldReceive('loadExtension')->with('Feather\Extensions\extension\location\FooExtension')->andReturn($instantiatedExtension); + return array($app, $dispatcher); + } + + +} \ No newline at end of file diff --git a/tests/FeatherCompilerTest.php b/tests/FeatherCompilerTest.php new file mode 100644 index 0000000..a8c0e8d --- /dev/null +++ b/tests/FeatherCompilerTest.php @@ -0,0 +1,46 @@ +getFiles(), __DIR__); + $this->assertEquals('', $compiler->compileString('@assign($foo, \'bar\')')); + $this->assertEquals('', $compiler->compileString('@assign($foo, $bar)')); + } + + + public function testExtensionEventsAreCompiled() + { + $compiler = new FeatherCompiler($this->getFiles(), __DIR__); + $expected = ''; + $this->assertEquals($expected, $compiler->compileString('@event(\'foo\')')); + } + + + public function testInlineErrorsAreCompiled() + { + $compiler = new FeatherCompiler($this->getFiles(), __DIR__); + $expected = 'has(\'foo\') ? view("feather::errors.inline", array("error" => $errors->first(\'foo\'))) : null; ?>'; + $this->assertEquals($expected, $compiler->compileString('@error(\'foo\')')); + } + + + public function testErrorsAreCompiled() + { + $compiler = new FeatherCompiler($this->getFiles(), __DIR__); + $expected = 'all() ? view("feather::errors.page", array("errors" => $errors->all())) : null; ?>'; + $this->assertEquals($expected, $compiler->compileString('@errors')); + } + + + public function getFiles() + { + return m::mock('Illuminate\Filesystem'); + } + +} \ No newline at end of file diff --git a/tests/PresenterTest.php b/tests/PresenterTest.php new file mode 100644 index 0000000..791065c --- /dev/null +++ b/tests/PresenterTest.php @@ -0,0 +1,26 @@ +getLoader()->shouldReceive('load')->once()->with('production', 'feather', null)->once()->andReturn(array('forum' => array('theme' => 'foo'))); + $app['files'] = m::mock('Illuminate\Filesystem'); + $app['files']->shouldReceive('exists')->once()->andReturn(false); + $app['view'] = m::mock('Illuminate\View\ViewManager'); + $app['view']->shouldReceive('addLocation')->once()->with('themes/foo/views'); + $app['view']->shouldReceive('addLocation')->once()->with('app/views'); + $app['path'] = 'app'; + $app['path.themes'] = 'themes'; + $presenter = new Presenter($app); + $presenter->prepare(); + } + + +} \ No newline at end of file From c8847c021df32c70d1eafad8c3f9bcd7503a69a5 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Mon, 26 Nov 2012 18:33:35 +1100 Subject: [PATCH 18/27] Initial commit of InstallCommand Signed-off-by: Jason Lewis --- feather/console/InstallCommand.php | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 feather/console/InstallCommand.php diff --git a/feather/console/InstallCommand.php b/feather/console/InstallCommand.php new file mode 100644 index 0000000..7cf0dab --- /dev/null +++ b/feather/console/InstallCommand.php @@ -0,0 +1,30 @@ +line('Feather version '.FEATHER_VERSION.''); + } +} \ No newline at end of file From d68b39709c60e226abbe0d2a2ac375e8bc88053b Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Mon, 26 Nov 2012 18:38:35 +1100 Subject: [PATCH 19/27] Initial commit of travis ci Signed-off-by: Jason Lewis --- .travis.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..12ed64a --- /dev/null +++ b/.travis.yml @@ -0,0 +1,15 @@ +language: php + +branches: + only: + - master + +php: + - 5.3 + - 5.4 + +before_script: + - curl -s http://getcomposer.org/installer | php + - php composer.phar install --dev + +script: phpunit \ No newline at end of file From 5b53d0896d471f2314608f79458f78e13c2dc84d Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Mon, 26 Nov 2012 18:48:34 +1100 Subject: [PATCH 20/27] Fixing up a bunch of case sensitive issues. Signed-off-by: Jason Lewis --- feather/{auth => Auth}/AuthServiceProvider.php | 0 feather/{auth => Auth}/FeatherUserProvider.php | 0 feather/{console => Console}/FeatherCommand.php | 0 feather/{console => Console}/InstallCommand.php | 0 feather/{console => Console}/PublishCommand.php | 0 feather/{database => Database}/EloquentBuilder.php | 0 feather/{extensions => Extensions}/Dispatcher.php | 0 feather/{extensions => Extensions}/Extension.php | 0 feather/{extensions => Extensions}/ExtensionInterface.php | 0 feather/{extensions => Extensions}/ExtensionServiceProvider.php | 0 feather/{migrator => Migrator}/DriverInterface.php | 0 feather/{migrator => Migrator}/Drivers/BaseDriver.php | 0 feather/{migrator => Migrator}/Drivers/FluxDriver.php | 0 feather/{migrator => Migrator}/MigratorManager.php | 0 feather/{migrator => Migrator}/MigratorServiceProvider.php | 0 feather/{presenter => Presenter}/Compilers/FeatherCompiler.php | 0 feather/{presenter => Presenter}/Presenter.php | 0 feather/{presenter => Presenter}/PresenterServiceProvider.php | 0 18 files changed, 0 insertions(+), 0 deletions(-) rename feather/{auth => Auth}/AuthServiceProvider.php (100%) rename feather/{auth => Auth}/FeatherUserProvider.php (100%) rename feather/{console => Console}/FeatherCommand.php (100%) rename feather/{console => Console}/InstallCommand.php (100%) rename feather/{console => Console}/PublishCommand.php (100%) rename feather/{database => Database}/EloquentBuilder.php (100%) rename feather/{extensions => Extensions}/Dispatcher.php (100%) rename feather/{extensions => Extensions}/Extension.php (100%) rename feather/{extensions => Extensions}/ExtensionInterface.php (100%) rename feather/{extensions => Extensions}/ExtensionServiceProvider.php (100%) rename feather/{migrator => Migrator}/DriverInterface.php (100%) rename feather/{migrator => Migrator}/Drivers/BaseDriver.php (100%) rename feather/{migrator => Migrator}/Drivers/FluxDriver.php (100%) rename feather/{migrator => Migrator}/MigratorManager.php (100%) rename feather/{migrator => Migrator}/MigratorServiceProvider.php (100%) rename feather/{presenter => Presenter}/Compilers/FeatherCompiler.php (100%) rename feather/{presenter => Presenter}/Presenter.php (100%) rename feather/{presenter => Presenter}/PresenterServiceProvider.php (100%) diff --git a/feather/auth/AuthServiceProvider.php b/feather/Auth/AuthServiceProvider.php similarity index 100% rename from feather/auth/AuthServiceProvider.php rename to feather/Auth/AuthServiceProvider.php diff --git a/feather/auth/FeatherUserProvider.php b/feather/Auth/FeatherUserProvider.php similarity index 100% rename from feather/auth/FeatherUserProvider.php rename to feather/Auth/FeatherUserProvider.php diff --git a/feather/console/FeatherCommand.php b/feather/Console/FeatherCommand.php similarity index 100% rename from feather/console/FeatherCommand.php rename to feather/Console/FeatherCommand.php diff --git a/feather/console/InstallCommand.php b/feather/Console/InstallCommand.php similarity index 100% rename from feather/console/InstallCommand.php rename to feather/Console/InstallCommand.php diff --git a/feather/console/PublishCommand.php b/feather/Console/PublishCommand.php similarity index 100% rename from feather/console/PublishCommand.php rename to feather/Console/PublishCommand.php diff --git a/feather/database/EloquentBuilder.php b/feather/Database/EloquentBuilder.php similarity index 100% rename from feather/database/EloquentBuilder.php rename to feather/Database/EloquentBuilder.php diff --git a/feather/extensions/Dispatcher.php b/feather/Extensions/Dispatcher.php similarity index 100% rename from feather/extensions/Dispatcher.php rename to feather/Extensions/Dispatcher.php diff --git a/feather/extensions/Extension.php b/feather/Extensions/Extension.php similarity index 100% rename from feather/extensions/Extension.php rename to feather/Extensions/Extension.php diff --git a/feather/extensions/ExtensionInterface.php b/feather/Extensions/ExtensionInterface.php similarity index 100% rename from feather/extensions/ExtensionInterface.php rename to feather/Extensions/ExtensionInterface.php diff --git a/feather/extensions/ExtensionServiceProvider.php b/feather/Extensions/ExtensionServiceProvider.php similarity index 100% rename from feather/extensions/ExtensionServiceProvider.php rename to feather/Extensions/ExtensionServiceProvider.php diff --git a/feather/migrator/DriverInterface.php b/feather/Migrator/DriverInterface.php similarity index 100% rename from feather/migrator/DriverInterface.php rename to feather/Migrator/DriverInterface.php diff --git a/feather/migrator/Drivers/BaseDriver.php b/feather/Migrator/Drivers/BaseDriver.php similarity index 100% rename from feather/migrator/Drivers/BaseDriver.php rename to feather/Migrator/Drivers/BaseDriver.php diff --git a/feather/migrator/Drivers/FluxDriver.php b/feather/Migrator/Drivers/FluxDriver.php similarity index 100% rename from feather/migrator/Drivers/FluxDriver.php rename to feather/Migrator/Drivers/FluxDriver.php diff --git a/feather/migrator/MigratorManager.php b/feather/Migrator/MigratorManager.php similarity index 100% rename from feather/migrator/MigratorManager.php rename to feather/Migrator/MigratorManager.php diff --git a/feather/migrator/MigratorServiceProvider.php b/feather/Migrator/MigratorServiceProvider.php similarity index 100% rename from feather/migrator/MigratorServiceProvider.php rename to feather/Migrator/MigratorServiceProvider.php diff --git a/feather/presenter/Compilers/FeatherCompiler.php b/feather/Presenter/Compilers/FeatherCompiler.php similarity index 100% rename from feather/presenter/Compilers/FeatherCompiler.php rename to feather/Presenter/Compilers/FeatherCompiler.php diff --git a/feather/presenter/Presenter.php b/feather/Presenter/Presenter.php similarity index 100% rename from feather/presenter/Presenter.php rename to feather/Presenter/Presenter.php diff --git a/feather/presenter/PresenterServiceProvider.php b/feather/Presenter/PresenterServiceProvider.php similarity index 100% rename from feather/presenter/PresenterServiceProvider.php rename to feather/Presenter/PresenterServiceProvider.php From 798b7b26fcf9e39d6e6723953ddfb7845a4a24ee Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Mon, 26 Nov 2012 18:58:54 +1100 Subject: [PATCH 21/27] Initial commit of README Signed-off-by: Jason Lewis --- README.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..b962ff0 --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# Feather + +Feather is a lightweight, fully awesome forum built on the Laravel framework. + +[![Build Status](https://secure.travis-ci.org/featherforums/feather.png?branch=master)](https://travis-ci.org/featherforums/feather) \ No newline at end of file From 8f1cfe71c2c2adb44c903c806106c7e5e213a5da Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Mon, 26 Nov 2012 18:59:15 +1100 Subject: [PATCH 22/27] Prevent any database related methods from firing to allow better Travis testing. Signed-off-by: Jason Lewis --- app/start/global.php | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/app/start/global.php b/app/start/global.php index ec59f59..ec71306 100644 --- a/app/start/global.php +++ b/app/start/global.php @@ -9,7 +9,7 @@ | */ -if (!defined('FEATHER_VERSION')) +if ( ! defined('FEATHER_VERSION')) { define('FEATHER_VERSION', '1.0.0'); } @@ -36,16 +36,19 @@ | */ -if ($app['cache']->has('config')) +if ( ! $app->runningInConsole()) { - $app['cache']->forget('config'); -} + if ($app['cache']->has('config')) + { + $app['cache']->forget('config'); + } -$config = new Models\Config; + $config = new Models\Config; -foreach ($config->everything() as $item) -{ - $app['config']->set("feather.{$item->name}", $item->value); + foreach ($config->everything() as $item) + { + $app['config']->set("feather.{$item->name}", $item->value); + } } /* @@ -113,9 +116,12 @@ | */ -$extensions = new Models\Extension; +if ( ! $app->runningInConsole()) +{ + $extensions = new Models\Extension; -$app['feather.extensions']->registerExtensions($extensions->getActivated()); + $app['feather.extensions']->registerExtensions($extensions->getActivated()); +} /* |-------------------------------------------------------------------------- From 3249fa067998d2d230b795477590720a52516e04 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Mon, 26 Nov 2012 19:32:31 +1100 Subject: [PATCH 23/27] Working on the dispatcher. Signed-off-by: Jason Lewis --- feather/Extensions/Dispatcher.php | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/feather/Extensions/Dispatcher.php b/feather/Extensions/Dispatcher.php index 92bb48a..c8fa726 100644 --- a/feather/Extensions/Dispatcher.php +++ b/feather/Extensions/Dispatcher.php @@ -86,28 +86,30 @@ public function getStarted() /** * Register an extension with the dispatcher. * - * @param Feather\Models\Extension $extension - * @return void + * @param array $extension + * @return array */ - public function register(ExtensionModel $extension) + public function register(array $extension) { - $path = $this->path.'/'.$extension->location; + $path = $this->path.'/'.$extension['location']; if ($this->files->exists($path)) { - $extension->path = $path; + $extension['path'] = $path; - $extension->loaded = array(); + $extension['loaded'] = array(); - $this["extension.{$extension->identifier}"] = $extension; + $this["extension.{$extension['identifier']}"] = $extension; // If an extension is set to be automatically started then we'll hand it off to // the starting method. - if ($extension->auto) + if ($extension['auto']) { - $this->start($extension->identifier); + $this->start($extension['identifier']); } } + + return $this["extension.{$extension['identifier']}"]; } /** @@ -147,24 +149,26 @@ public function start($identifier) $extension = $this["extension.{$identifier}"]; - foreach ($this->findExtensions($extension->path) as $file) + foreach ($this->findExtensions($extension['path']) as $file) { $name = $file->getBasename(".{$file->getExtension()}"); if (ends_with($name, 'Extension')) { - $location = str_replace('/', '\\', $extension->location); + $location = str_replace('/', '\\', $extension['location']); $class = "Feather\\Extensions\\{$location}\\{$name}"; // Instantiate the new extension class and assign it to the extensions loaded classes. The class // receives an instance of the Laravel application. - $extension->loaded = array_merge($extension->loaded, array($class => $this->loadExtension($class))); + $extension['loaded'][$class] = $this->loadExtension($class); - $extension->loaded[$class]->start($this->app); + $extension['loaded'][$class]->start($this->app); } } + $this["extension.{$identifier}"] = $extension; + // Add the extension to the array of started extensions. $this->started[] = $identifier; } From c8d4c055114a8d7eace9a26a7c1c399969d1067d Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Mon, 26 Nov 2012 19:32:43 +1100 Subject: [PATCH 24/27] Fixing some extension tests. Signed-off-by: Jason Lewis --- tests/ExtensionDispatcherTest.php | 28 ++++++--------------- tests/ExtensionTest.php | 42 +++++++++---------------------- 2 files changed, 19 insertions(+), 51 deletions(-) diff --git a/tests/ExtensionDispatcherTest.php b/tests/ExtensionDispatcherTest.php index 94758ec..9ca18c3 100644 --- a/tests/ExtensionDispatcherTest.php +++ b/tests/ExtensionDispatcherTest.php @@ -9,15 +9,10 @@ class ExtensionDispatcherTest extends TestCase { public function testDispatcherRegisterExtension() { $dispatcher = $this->getDispatcher(); - $extension = new Extension(array( - 'location' => 'extension/location', - 'identifier' => 'foo', - 'auto' => false - )); + $extension = array('location' => 'extension/location', 'identifier' => 'foo', 'auto' => false); $dispatcher->register($extension); - $this->assertInstanceOf('Feather\Models\Extension', $dispatcher['extension.foo']); - $this->assertEquals('foo', $dispatcher['extension.foo']->identifier); - $this->assertEquals('extension/location', $dispatcher['extension.foo']->location); + $this->assertEquals('foo', $dispatcher['extension.foo']['identifier']); + $this->assertEquals('extension/location', $dispatcher['extension.foo']['location']); $this->assertTrue($dispatcher->isRegistered('foo')); } @@ -25,15 +20,10 @@ public function testDispatcherRegisterExtension() public function testDispatcherRegistersExtensionsFromArray() { $dispatcher = $this->getDispatcher(); - $extension = new Extension(array( - 'location' => 'extension/location', - 'identifier' => 'foo', - 'auto' => false - )); + $extension = array('location' => 'extension/location', 'identifier' => 'foo', 'auto' => false); $dispatcher->registerExtensions(array($extension)); - $this->assertInstanceOf('Feather\Models\Extension', $dispatcher['extension.foo']); - $this->assertEquals('foo', $dispatcher['extension.foo']->identifier); - $this->assertEquals('extension/location', $dispatcher['extension.foo']->location); + $this->assertEquals('foo', $dispatcher['extension.foo']['identifier']); + $this->assertEquals('extension/location', $dispatcher['extension.foo']['location']); } @@ -46,11 +36,7 @@ public function testDispatcherAutoStartExtension() $dispatcher = m::mock('Feather\Extensions\Dispatcher[findExtensions,loadExtension]'); $dispatcher->__construct($files, 'path/to'); $dispatcher->setApplication($app); - $extension = new Extension(array( - 'location' => 'extension/location', - 'identifier' => 'foo', - 'auto' => true - )); + $extension = array('location' => 'extension/location', 'identifier' => 'foo', 'auto' => true); $file = m::mock('stdClass'); $file->shouldReceive('getBasename')->once()->andReturn('FooExtension'); $file->shouldReceive('getExtension')->once()->andReturn('php'); diff --git a/tests/ExtensionTest.php b/tests/ExtensionTest.php index 9a97eb8..fa0f45d 100644 --- a/tests/ExtensionTest.php +++ b/tests/ExtensionTest.php @@ -1,7 +1,6 @@ getApplicationAndDispatcher(); - $extension = new Extension(array( - 'location' => 'extension/location', - 'identifier' => 'foo', - 'auto' => true - )); + $extension = array('location' => 'extension/location', 'identifier' => 'foo', 'auto' => true); $dispatcher->register($extension); - $this->assertEquals('success', $app['events']->first('start_test')); } @@ -23,17 +17,13 @@ public function testExtensionsAreBootstrapped() public function testExtensionsCanListenForEvents() { list($app, $dispatcher) = $this->getApplicationAndDispatcher(); - $extension = new Extension(array( - 'location' => 'extension/location', - 'identifier' => 'foo', - 'auto' => true - )); - $dispatcher->register($extension); - $extension->loaded['Feather\Extensions\extension\location\FooExtension']->listen('foobar', function() + $extension = array('location' => 'extension/location', 'identifier' => 'foo', 'auto' => true); + $extension = $dispatcher->register($extension); + $extension['loaded']['Feather\Extensions\extension\location\FooExtension']->listen('foobar', function() { return 'barfoo'; }); - $extension->loaded['Feather\Extensions\extension\location\FooExtension']->listen('barfoo', function() + $extension['loaded']['Feather\Extensions\extension\location\FooExtension']->listen('barfoo', function() { return 'foobar'; }); @@ -45,17 +35,13 @@ public function testExtensionsCanListenForEvents() public function testExtensionsCanOverrideEvents() { list($app, $dispatcher) = $this->getApplicationAndDispatcher(); - $extension = new Extension(array( - 'location' => 'extension/location', - 'identifier' => 'foo', - 'auto' => true - )); - $dispatcher->register($extension); - $extension->loaded['Feather\Extensions\extension\location\FooExtension']->listen('foobar', function() + $extension = array('location' => 'extension/location', 'identifier' => 'foo', 'auto' => true); + $extension = $dispatcher->register($extension); + $extension['loaded']['Feather\Extensions\extension\location\FooExtension']->listen('foobar', function() { return 'barfoo'; }); - $extension->loaded['Feather\Extensions\extension\location\FooExtension']->override('foobar', function() + $extension['loaded']['Feather\Extensions\extension\location\FooExtension']->override('foobar', function() { return 'barbar'; }); @@ -66,13 +52,9 @@ public function testExtensionsCanOverrideEvents() public function testExtensionsCanUseMethods() { list($app, $dispatcher) = $this->getApplicationAndDispatcher(); - $extension = new Extension(array( - 'location' => 'extension/location', - 'identifier' => 'foo', - 'auto' => true - )); - $dispatcher->register($extension); - $extension->loaded['Feather\Extensions\extension\location\FooExtension']->listen('foobar', 'foo'); + $extension = array('location' => 'extension/location', 'identifier' => 'foo', 'auto' => true); + $extension = $dispatcher->register($extension); + $extension['loaded']['Feather\Extensions\extension\location\FooExtension']->listen('foobar', 'foo'); $this->assertEquals('foomethod', $app['events']->first('foobar')); } From 06bc91eb5bd359524f3bd2713583563bb4feffd8 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Mon, 26 Nov 2012 21:07:09 +1100 Subject: [PATCH 25/27] Implement ArrayAccess instead of extending Illuminate\Container Signed-off-by: Jason Lewis --- feather/Extensions/Dispatcher.php | 68 +++++++++++++++++++++++++++++-- 1 file changed, 64 insertions(+), 4 deletions(-) diff --git a/feather/Extensions/Dispatcher.php b/feather/Extensions/Dispatcher.php index c8fa726..6209328 100644 --- a/feather/Extensions/Dispatcher.php +++ b/feather/Extensions/Dispatcher.php @@ -1,11 +1,12 @@ app = $app; } @@ -91,6 +99,8 @@ public function getStarted() */ public function register(array $extension) { + $identifier = $extension['identifier']; + $path = $this->path.'/'.$extension['location']; if ($this->files->exists($path)) @@ -200,4 +210,54 @@ public function fire() } + /** + * Determine if a given offset exists. + * + * @param string $key + * @return bool + */ + public function offsetExists($key) + { + return isset($this->extensions[$key]); + } + + /** + * Get the value at a given offset. + * + * @param string $key + * @return mixed + */ + public function offsetGet($key) + { + if ( ! isset($this->extensions[$key])) + { + throw new InvalidArgumentException("Type {$key} is not bound."); + } + + return $this->extensions[$key]; + } + + /** + * Set the value at a given offset. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function offsetSet($key, $value) + { + $this->extensions[$key] = $value; + } + + /** + * Unset the value at a given offset. + * + * @param string $key + * @return void + */ + public function offsetUnset($key) + { + unset($this->extensions[$key]); + } + } \ No newline at end of file From 5e618c5d431f1afe3dfab85c3d4f8440d21b4d11 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Mon, 26 Nov 2012 21:38:32 +1100 Subject: [PATCH 26/27] Register extensions as an array not as objects. Signed-off-by: Jason Lewis --- app/start/global.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/start/global.php b/app/start/global.php index ec71306..9de826b 100644 --- a/app/start/global.php +++ b/app/start/global.php @@ -120,7 +120,7 @@ { $extensions = new Models\Extension; - $app['feather.extensions']->registerExtensions($extensions->getActivated()); + $app['feather.extensions']->registerExtensions($extensions->getActivated()->toArray()); } /* From 514819da196e026971f0cb94a9382960066afbb8 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Mon, 26 Nov 2012 21:38:59 +1100 Subject: [PATCH 27/27] Only return the extension array if the extension is valid. Signed-off-by: Jason Lewis --- feather/Extensions/Dispatcher.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/feather/Extensions/Dispatcher.php b/feather/Extensions/Dispatcher.php index 6209328..5d9af30 100644 --- a/feather/Extensions/Dispatcher.php +++ b/feather/Extensions/Dispatcher.php @@ -117,9 +117,9 @@ public function register(array $extension) { $this->start($extension['identifier']); } - } - return $this["extension.{$extension['identifier']}"]; + return $this["extension.{$extension['identifier']}"]; + } } /**