diff --git a/.gitignore b/.gitignore
new file mode 100755
index 0000000..2c1fc0c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+/vendor
+composer.phar
+composer.lock
+.DS_Store
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
new file mode 100755
index 0000000..0edb59c
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,11 @@
+language: php
+
+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
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..514cad6
--- /dev/null
+++ b/README.md
@@ -0,0 +1,144 @@
+# Purpose Media Menu Builder for Laravel 4
+
+
+## Usage
+
+
+Very simple method of building menus from database data (id, parent id), service provider register methods for application modules and much more.
+
+
+## Example 1
+
+
+```php
+ 'Home', 'URL' => '/', 'reference' => '0' ) );
+Menu::render();
+?>
+```
+
+##Example 2 - Nesting Children
+
+
+```php
+ 'Services', 'URL' => '/services/', 'reference' => '1', 'parent' => '0' ) );
+Menu::render();
+?>
+```
+
+## Example 3 - Multiple Menus
+
+
+```php
+ 'Services', 'URL' => '/services/', 'reference' => '1', 'parent' => '0' ) )->toMenu( 'main' );
+Menu::render( 'main' );
+?>
+```
+
+## Auto classes
+
+
+I have added in some of the most used and required classes for styling menus
+
+
+```css
+.first-item {}
+.last-item {}
+.current-root {}
+.current-parent {}
+.current-ancestor {}
+.has-children {}
+```
+
+## Output
+
+
+```php
+ 'Home', 'URL' => '/menu-test-2/public/', 'reference' => '1', 'class' => 'home-icon', 'weight' => 0 ) )->toMenu( 'main' );
+Menu::addItem( array( 'text' => 'Services', 'URL' => '/menu-test-2/public/services/', 'reference' => '2' ) )->toMenu( 'main' );
+Menu::addItem( array( 'text' => 'Development', 'URL' => '/menu-test-2/public/services/development/', 'reference' => '3', 'parent' => '2' ) )->toMenu( 'main' );
+Menu::addItem( array( 'text' => 'Design', 'URL' => '/menu-test-2/public/services/design/', 'reference' => '4', 'parent' => '2', 'weight' => 0 ) )->toMenu( 'main' );
+Menu::render( 'main' );
+?>
+```
+
+```html
+
+ -
+ Home
+
+ -
+ Services
+
+
+
+```
+
+## Use with third party menu UI through L4 Model
+(Please note this is just a general summary of how it would work if you had 2 tables (and models) for navigations and navigation items with a standard hasMany() relationship)
+
+
+```php
+where( 'navigation_slug', '=', 'main' )->get();
+foreach( $navigation->navigationItems as $item )
+{
+ Menu::addItem( array( 'text' => , $item->name 'URL' => $item->url, 'reference' => $item->id, 'parent' => $item->parent_id, 'weight' => $item->order ) )->toMenu( $navigation->navigation_slug );
+}
+Menu::render( $navigation->navigation_slug );
+?>
+```
+
+## Install
+
+Add the following to you applications composer.json file
+
+
+```json
+"require": {
+ ...
+ "purposemedia/menu" : "dev-master"
+},
+```
+
+Run the following from your terminal from your application route (make sure you have access to composer.phar)
+
+
+```shell
+php composer.phar update
+```
+
+add the following to your /app/config/app.php's provider array.
+
+
+```php
+'Purposemedia\Menu\MenuServiceProvider'
+```
+
+
+add the following to your /app/config/app.php's aliases array.
+
+
+```php
+'Menu' => 'Purposemedia\Menu\Facades\Menu'
+```
+
+
+and finally back to your terminal and run
+
+
+```shell
+php composer.phar dump-autoload
+```
+
+
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..1f69ef9
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,30 @@
+{
+ "name": "purposemedia/menu",
+ "description": "Effortless menu building for Laravel 4",
+ "homepage": "https://bitbucket.org/purposemedia/menu/",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Luke Snowden",
+ "email": "luke@purposemedia.co.uk"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "autoload": {
+ "psr-0": {
+ "Purposemedia\\Menu": "src/"
+ }
+ },
+ "scripts": {
+ "post-update-cmd": [
+ "php artisan package:install purposemedia/menu"
+ ],
+ "post-create-project-cmd": [
+ "php artisan key:generate",
+ "php artisan package:install purposemedia/menu"
+ ]
+ },
+ "minimum-stability": "dev"
+}
\ No newline at end of file
diff --git a/phpunit.xml b/phpunit.xml
new file mode 100644
index 0000000..e89ac6d
--- /dev/null
+++ b/phpunit.xml
@@ -0,0 +1,18 @@
+
+
+
+
+ ./tests/
+
+
+
\ No newline at end of file
diff --git a/provides.json b/provides.json
new file mode 100644
index 0000000..9089a2f
--- /dev/null
+++ b/provides.json
@@ -0,0 +1,11 @@
+{
+ "providers": [
+ "Purposemedia\Menu\MenuServiceProvider"
+ ],
+ "aliases": [
+ {
+ "alias": "Menu",
+ "facade": "Purposemedia\Menu\Facades\Menu"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/Purposemedia/Menu/Facades/Menu.php b/src/Purposemedia/Menu/Facades/Menu.php
new file mode 100644
index 0000000..bb78ab4
--- /dev/null
+++ b/src/Purposemedia/Menu/Facades/Menu.php
@@ -0,0 +1,19 @@
+ '!%3A!ui',
+ "/" => '!%2F!ui',
+ "?" => '!%3F!ui',
+ "#" => '!%23!ui',
+ "[" => '!%5B!ui',
+ "]" => '!%5D!ui',
+ "@" => '!%40!ui',
+ "!" => '!%21!ui',
+ "$" => '!%24!ui',
+ "&" => '!%26!ui',
+ "'" => '!%27!ui',
+ "(" => '!%28!ui',
+ ")" => '!%29!ui',
+ "*" => '!%2A!ui',
+ "+" => '!%2B!ui',
+ "," => '!%2C!ui',
+ ";" => '!%3B!ui',
+ "=" => '!%3D!ui',
+ "%" => '!%25!ui',
+ );
+
+ $url = rawurlencode($url);
+ $url = preg_replace(array_values($reserved), array_keys($reserved), $url);
+ return $url;
+ }
+
+}
+
+?>
\ No newline at end of file
diff --git a/src/Purposemedia/Menu/Menu.php b/src/Purposemedia/Menu/Menu.php
new file mode 100644
index 0000000..665983c
--- /dev/null
+++ b/src/Purposemedia/Menu/Menu.php
@@ -0,0 +1,43 @@
+ \camel_case( $match[1] ) ) );
+ }
+ return call_user_func_array( array( static::container(), $method ), $parameters );
+ }
+
+}
+
diff --git a/src/Purposemedia/Menu/MenuContainer.php b/src/Purposemedia/Menu/MenuContainer.php
new file mode 100644
index 0000000..77aa74f
--- /dev/null
+++ b/src/Purposemedia/Menu/MenuContainer.php
@@ -0,0 +1,133 @@
+ '',
+ 'URL' => '#',
+ 'reference' => 0,
+ 'parent' => false,
+ 'weight' => 1,
+ 'class' => '',
+ 'children' => array(),
+ 'icon' => '',
+ 'attributes' => array()
+ );
+ $this->items[] = array_merge( $defaults, $perams );
+ return $this;
+ }
+
+ /*
+ * @method To Menu
+ * @author Luke Snowden
+ * @param $name (string)
+ */
+
+ public function toMenu( $name )
+ {
+ $name = \camel_case( $name );
+ if( ! isset( $this->navigations[$name] ) )
+ {
+ $this->navigations[$name] = new MenuContainerNavigation( $name );
+ }
+ $this->navigations[$name]->addItem( array_pop( $this->items ) );
+ }
+
+ /*
+ * @method Render
+ * @author Luke Snowden
+ * @param $name (false/string)
+ */
+
+ public function render( $name = false )
+ {
+ if( isset( $this->renders[\camel_case($name)] ) )
+ {
+ return $this->renders[\camel_case($name)];
+ }
+ if( ! $name )
+ {
+ $this->navigations['pmDefaultMenu'] = new MenuContainerNavigation( 'pmDefaultMenu' );
+ while( count( $this->items ) !== 0 )
+ {
+ $item = array_shift( $this->items );
+ $this->navigations['pmDefaultMenu']->addItem( $item );
+ }
+ $this->renders[$name] = '';
+ foreach( $this->navigations as $navigation )
+ {
+ $this->renders[$name] .= $navigation->render();
+ }
+ return $this->renders[$name];
+ }
+ else
+ {
+ $name = \camel_case( $name );
+ if( ! isset( $this->navigations[$name] ) )
+ {
+ // This gets annoying!
+ // Throw new \Exception( "Navigation '{$name}' does not exist. Cannot process render." );
+ return false;
+ }
+ $this->renders[$name] = $this->navigations[$name]->render();
+ return $this->renders[$name];
+ }
+ }
+
+ /*
+ * @method Set Menu type
+ * @author Luke Snowden
+ * @param $type (false/string)
+ * @param $menu (false/string)
+ */
+
+ public function setMenuType( $type = false, $menu = false, $location = false )
+ {
+ if( ! isset( $this->navigations[$menu] ) )
+ {
+ Throw new \Exception( "Menu '{$menu}' does not exist or you have called this method before the menu has been created." );
+ }
+ if( $location === false )
+ {
+ $location = $this->stylesLocation;
+ }
+ $this->navigations[$menu]->setType( $type, $location );
+ }
+
+
+}
diff --git a/src/Purposemedia/Menu/MenuContainerNavigation.php b/src/Purposemedia/Menu/MenuContainerNavigation.php
new file mode 100644
index 0000000..7e182af
--- /dev/null
+++ b/src/Purposemedia/Menu/MenuContainerNavigation.php
@@ -0,0 +1,411 @@
+name = $name;
+ }
+
+ /*
+ * @method Add Item
+ * @author Luke Snowden
+ * @param $text (string), $url (string), $reference (int), $parent (false/int)
+ */
+
+ public function addItem( $item = array() )
+ {
+ $defaults = array(
+ 'reference' => 0,
+ 'text' => '',
+ 'URL' => '#',
+ 'parent' => false,
+ 'children' => array(),
+ 'class' => '',
+ 'weight' => 1,
+ 'icon' => '',
+ 'attributes' => array()
+ );
+ $this->items[] = array_merge( $defaults, $item );
+ }
+
+ /*
+ * @method Render Details
+ * @author Luke Snowden
+ * @param $structure (array), $depth (int)
+ */
+
+ private static function renderAttributes( array $attributes )
+ {
+ foreach( $attributes as $attribute => $value )
+ {
+ echo "{$attribute}=\"{$value}\" ";
+ }
+ }
+
+ private function renderDetail( $structure, $depth = 1 )
+ {
+ if( $depth === 1 )
+ {
+ ob_start();
+ }
+ ?>
+
+
+
+ >
+
+
+ renderDetail( $level['children'], ($depth+1) ); ?>
+
+
+
+
+ $elements )
+ {
+ if( isset( $elements[$column] ) && is_array( $elements[$column] ) && ! empty( $elements[$column] ) )
+ {
+ $array[$key][$column] = self::ausort( $elements[$column], $column );
+ }
+ }
+ return $array;
+ }
+
+ private function sortItems( $structure )
+ {
+ $structure = self::ausort( $structure, 'weight' );
+ foreach( $structure as $key => $item )
+ {
+ $structure[$key]['class'] .= $key === 0 ? ' first-item' : '';
+ $structure[$key]['class'] .= ! isset( $structure[$key+1] ) ? ' last-item' : '';
+ if( ! empty( $item['children'] ) )
+ {
+ $structure[$key]['children'] = $this->sortItems( $structure[$key]['children'] );
+ }
+ }
+ return $structure;
+ }
+
+ /*
+ * @method Render
+ * @author Luke Snowden
+ * @param (void)
+ */
+
+ public function render()
+ {
+ $structure = $this->generate();
+ $structure = $this->sortItems( $structure );
+ $return = '';
+
+ if( $this->type === 'default' )
+ {
+ $return .= "name} pm-menu\">";
+ $return .= $this->renderDetail( $structure );
+ $return .= "
";
+ return $return;
+ }
+ else
+ {
+ $class = $this->stylesLocation . '\\Styles';
+ if( ! class_exists( $class ) )
+ {
+ Throw new \Exception( "{$class} does not exist" );
+ }
+ $style = new $class();
+ $method = \camel_case( "render-{$this->type}" );
+ if( ! class_exists( $class, $method ) )
+ {
+ Throw new \Exception( "{$method} does not exist" );
+ }
+ $return .= "type} pm-menu\">";
+ $return .= $style->{$method}( $structure );
+ $return .= "
";
+ return $return;
+ }
+
+ }
+
+ /*
+ * @method Current URI
+ * @author Luke Snowden
+ * @param (void)
+ */
+
+ public static function currentURI()
+ {
+ $fullLocation = rtrim( \URL::current(), '/' ) . '/';
+ $domain = \Config::get( 'app.url' );
+ return str_replace( '//', '/', '/' . trim( str_replace( $domain, '', $fullLocation ), '/' ) . '/' );
+ }
+
+ /*
+ * @method Get Roots
+ * @author Luke Snowden
+ * @param (void)
+ */
+
+ private function getRoots()
+ {
+ $x = 0;
+ $return = array();
+ $count = count( $this->items );
+
+ while( $count >= $x )
+ {
+ $item = array_shift($this->items);
+ if( $item['parent'] === false )
+ {
+ $return[] = $item;
+ }
+ else
+ {
+ array_push( $this->items, $item );
+ }
+ $x++;
+ }
+ return $return;
+ }
+
+ /*
+ * @method Get Children
+ * @author Luke Snowden
+ * @param $ref (int)
+ */
+
+ private function getChildren( $ref )
+ {
+ $x = 0;
+ $return = array();
+ $count = count( $this->items );
+ while( $count > $x )
+ {
+ $item = array_shift( $this->items );
+ if( (string)$item['parent'] == (string)$ref )
+ {
+ $item['children'] = $this->getChildren( $item['reference'] );
+ $return[] = $item;
+ }
+ else
+ {
+ array_push( $this->items, $item );
+ }
+ $x++;
+ }
+ foreach( $return as $key => $item )
+ {
+ $return[$key]['class'] .= count( $item['children'] ) > 0 ? ' has-children' : '';
+ $return[$key]['class'] .= $this->isAnAncestor( $item['children'] );
+ $return[$key]['class'] .= $this->isParentClass( $item );
+ }
+ return $return;
+ }
+
+ /*
+ * @method Is An Ancestor
+ * @author Luke Snowden
+ * @param $children (array)
+ */
+
+ private function isAnAncestor( $children )
+ {
+ $currentURI = self::currentURI();
+ foreach( $children as $child )
+ {
+ if( $currentURI == self::cleanseToURI( $child['URL'] ) )
+ {
+ return ' current-ancestor';
+ }
+ if( ! empty( $child['children'] ) )
+ {
+ if( ! is_null( $this->isAnAncestor( $child['children'] ) ) )
+ {
+ return ' current-ancestor';
+ }
+ }
+ }
+ return NULL;
+ }
+
+ /*
+ * @method Is Parent Class
+ * @author Luke Snowden
+ * @param $item (array)
+ */
+
+ private function isParentClass( $item )
+ {
+ $currentURI = self::currentURI();
+ foreach( $item['children'] as $child )
+ {
+ if( $currentURI == self::cleanseToURI( $child['URL'] ) )
+ {
+ return ' current-parent';
+ }
+ }
+ return '';
+ }
+
+ /*
+ * @method Sort By Weight
+ * @author Luke Snowden
+ * @param $a (array)
+ * @param $b (array)
+ */
+
+ private static function sortByWeight( $a, $b )
+ {
+ return $a['weight'] - $b['weight'];
+ }
+
+ /*
+ * @method Root class
+ * @author Luke Snowden
+ * @param $children (array)
+ */
+
+ private function rootClass( $children )
+ {
+ $currentURI = self::currentURI();
+ foreach( $children as $child )
+ {
+ if( $currentURI == self::cleanseToURI( $child['URL'] ) )
+ {
+ return ' current-root';
+ }
+ if( ! empty( $child['children'] ) )
+ {
+ if( ! is_null( $class = $this->rootClass( $child['children'] ) ) )
+ {
+ return $class;
+ }
+ }
+ }
+ return NULL;
+ }
+
+ /*
+ * @method Cleanse To URI
+ * @author Luke Snowden
+ * @param $url (string)
+ */
+
+ public static function cleanseToURI( $url )
+ {
+ $domain = \Config::get( 'app.url' );
+ if( preg_match( "#^https?://.*#", $url ) )
+ {
+ return $url;
+ }
+ else
+ {
+ return str_replace( '//', '/', '/' . trim( str_replace( \Config::get( 'app.url' ), '', UTA::urlToAbsolute( \URL::current(), $url ) ), '/' ) . '/' );
+ }
+ }
+
+ /*
+ * @method Set Current Class
+ * @author Luke Snowden
+ * @param (void)
+ */
+
+ private function setCurrentClass()
+ {
+ $currentURI = self::currentURI();
+ foreach( $this->items as $key => $item )
+ {
+ if( $currentURI == self::cleanseToURI( $item['URL'] ) )
+ {
+ $this->items[$key]['class'] .= ' current';
+ }
+ }
+ }
+
+ /*
+ * @method Generate
+ * @author Luke Snowden
+ * @param (void)
+ */
+
+ private function generate()
+ {
+ $this->setCurrentClass();
+ $roots = $this->getRoots();
+ foreach( $roots as $key => $item )
+ {
+ $roots[$key]['children'] = $this->getChildren( $item['reference'] );
+ $roots[$key]['class'] .= count( $roots[$key]['children'] ) > 0 ? ' has-children' : '';
+ $roots[$key]['class'] .= $this->rootClass( $roots[$key]['children'] );
+ }
+ return $roots;
+ }
+
+ /*
+ * @method Set Type
+ * @author Luke Snowden
+ * @param $type (string)
+ * @param $stylesLocation (string)
+ */
+
+ public function setType( $type, $stylesLocation )
+ {
+ $this->type = $type;
+ $this->stylesLocation = $stylesLocation;
+ }
+
+}
\ No newline at end of file
diff --git a/src/Purposemedia/Menu/MenuServiceProvider.php b/src/Purposemedia/Menu/MenuServiceProvider.php
new file mode 100644
index 0000000..bb9a387
--- /dev/null
+++ b/src/Purposemedia/Menu/MenuServiceProvider.php
@@ -0,0 +1,42 @@
+app['menu'] = $this->app->share( function( $app )
+ {
+ return new Menu;
+ });
+ $this->app['config']->package( "purposemedia/menu", dirname( __FILE__ ) . "/../../../config" );
+ }
+
+ /**
+ * Get the services provided by the provider.
+ *
+ * @return array
+ */
+
+ public function provides()
+ {
+ return array();
+ }
+
+}
\ No newline at end of file
diff --git a/src/Purposemedia/Menu/Styles/Styles.php b/src/Purposemedia/Menu/Styles/Styles.php
new file mode 100644
index 0000000..4c5d4da
--- /dev/null
+++ b/src/Purposemedia/Menu/Styles/Styles.php
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/.gitkeep b/tests/.gitkeep
new file mode 100644
index 0000000..e69de29