diff --git a/Builder.php b/Builder.php
new file mode 100644
index 0000000..c57ad9d
--- /dev/null
+++ b/Builder.php
@@ -0,0 +1,294 @@
+
+ * @license https://opensource.org/licenses/MIT MIT
+ * @version GIT: 1.0.0
+ * @link http://sleepymustache.com
+ */
+
+namespace Module\Navigation;
+
+use Sleepy\Core\Hook;
+use Sleepy\Core\Module;
+
+/**
+ * Creates a Navigation UL based on a JSON file
+ *
+ * Uses JSON to structure navigation pages and attributes. It can detect what page
+ * is active and assign classes to them for special treatment.
+ *
+ * ### Usage
+ *
+ *
+ * $topNavData = '{
+ * "pages": [
+ * {
+ * "title": "Nav 1",
+ * "link": "/nav1/"
+ * }, {
+ * "title": "Nav 2",
+ * "link": "/nav2/",
+ * "pages": [
+ * {
+ * "title": "Subnav 1",
+ * "link": "/downloads/fpo.pdf",
+ * "target": "_blank"
+ * }
+ * ]
+ * }
+ * ]
+ * }';
+ *
+ * $topNav = new \Module\Navigation\Builder($topNavData);
+ *
+ * // In body somewhere...
+ *
+ *
+ *
+ * ### Changelog
+ * # Version 2.0
+ * * Converted to 2.x API
+ *
+ * # Version 1.4
+ * * Now automatically sets $_SERVER["SCRIPT_NAME"] as current page
+ * * Added multiple hook points for manipulating navigations
+ *
+ * ## Version 1.2
+ * * Added a track parameter
+ *
+ * @category Navigation
+ * @package Module\Navigation
+ * @author Jaime Rodriguez
+ * @license https://opensource.org/licenses/MIT MIT
+ * @link http://sleepymustache.com
+ */
+class Builder extends Module
+{
+ /**
+ * The array of hooks this module attaches to
+ *
+ * @var mixed[]
+ */
+ public $hooks = [];
+
+ /**
+ * Use this string to determine currently active page
+ *
+ * @var string
+ * @private
+ */
+ private $_current;
+
+ /**
+ * Navigation data
+ *
+ * @var mixed[]
+ * @private
+ */
+ private $_data;
+
+ /**
+ * Level
+ *
+ * @var int
+ * @private
+ */
+ private $_level = 0;
+
+ /**
+ * Constructor
+ *
+ * @param string $json json data containing the Navigation data
+ */
+ public function __construct($json="")
+ {
+ if (!is_object($json)) {
+ if (class_exists("\Sleepy\Hook")) {
+ $json = \Sleepy\Hook::addFilter("navigation_raw_json", $json);
+ }
+
+ $json = json_decode($json);
+ }
+
+ if (class_exists("\Sleepy\Hook")) {
+ $json = \Sleepy\Hook::addFilter("navigation_rendered_json", $json);
+ }
+
+ $this->data = $json;
+ $this->setCurrent($_SERVER["SCRIPT_NAME"]);
+ }
+
+ /**
+ * Is this page or its children an active page?
+ *
+ * @param object $page An object containing page data
+ *
+ * @return int 0=none;1=active;2=activeChild
+ * @private
+ */
+ private function hasActive($page)
+ {
+ // are there subpages? check those too...
+ if (isset($page->pages)) {
+ foreach ($page->pages as $subPage) {
+ if ($this->hasActive($subPage)) {
+ return 2;
+ }
+ }
+ }
+
+ // can we find a match?
+ if (substr($page->link, strlen($page->link) * -1) === $this->current) {
+ if (class_exists("\Sleepy\Hook")) {
+ \Sleepy\Hook::addAction("navigation_has_active");
+ }
+
+ return 1;
+ }
+
+ // no match...
+ if (class_exists("\Sleepy\Hook")) {
+ \Sleepy\Hook::addAction("navigation_no_active");
+ }
+
+ return 0;
+ }
+
+ /**
+ * Renders the $pages as an unordered list
+ *
+ * @param object $pages the page data
+ *
+ * @return string The string containing the unordered list
+ */
+ private function _renderNav($pages, $class="")
+ {
+ $this->_level = $this->_level + 1;
+ $buffer = array();
+
+ if ($this->_level > 1) {
+ $class = "submenu " . $class;
+ } else {
+ $class = "menu " . $class;
+ }
+
+ $class = trim($class);
+ $buffer[] = "";
+
+ foreach ($pages as $page) {
+ if (class_exists("\Sleepy\Hook")) {
+ $page = \Sleepy\Hook::addFilter("navigation_page", $page);
+
+ if (!empty($page->id)) {
+ $page = \Sleepy\Hook::addFilter("navigation_page_" . $page->id, $page);
+ }
+ }
+
+ if (!isset($page->class)) {
+ $page->class = "";
+ }
+
+ if (isset($page->pages)) {
+ $page->class = $page->class . " has-children";
+ }
+
+ $active = $this->hasActive($page);
+ $classy
+ = (!empty($page->class))
+ ? true
+ : false;
+ $track
+ = (!empty($page->track))
+ ? "data-track=\"{$page->track}\" "
+ : "";
+ $id
+ = (!empty($page->id))
+ ? "id=\"{$page->id}\" "
+ : "";
+ $rel
+ = (!empty($page->rel))
+ ? "rel=\"{$page->rel}\" "
+ : "";
+ $target
+ = (!empty($page->target))
+ ? "target=\"{$page->target}\" "
+ : "";
+ $href
+ = (!empty($page->link))
+ ? "href=\"{$page->link}\" "
+ : "";
+ $attributes = trim($id . $track . $rel . $target . $href);
+ $buffer[] = "- class = $page->class . " active";
+ break;
+ case 2:
+ $page->class = $page->class . " active-child";
+ }
+
+ $buffer[] = trim($page->class);
+ $buffer[] = "\"";
+ }
+
+ $buffer[] = ">";
+ $buffer[] = "{$page->title}";
+
+ if (isset($page->pages)) {
+ $buffer[] = $this->_renderNav($page->pages);
+ }
+
+ $buffer[] = "
";
+ }
+
+ $buffer[] = "
";
+
+ return implode("", $buffer);
+ }
+
+ /**
+ * Renders the Navigation
+ * @return string The rendered navigation
+ */
+ public function show($class="")
+ {
+ $toggle = "";
+ $rendered = $toggle . $this->_renderNav($this->data->pages, $class);
+
+ if (class_exists("\Sleepy\Hook")) {
+ $rendered = \Sleepy\Hook::addFilter("navigation_rendered", $rendered);
+ }
+
+ return $rendered;
+ }
+
+ /**
+ * Sets the current page search string
+ * @param string $string A string used to determine if a page is current
+ */
+ public function setCurrent($string)
+ {
+ $this->current = str_replace(
+ @URLBASE,
+ "/",
+ str_replace("index.php", "", $string)
+ );
+
+ if (class_exists("\Sleepy\Hook")) {
+ $this->current = \Sleepy\Hook::addFilter("navigation_current_page", $this->current);
+ }
+ }
+}
diff --git a/README.md b/README.md
index e790f30..11fbc2d 100644
--- a/README.md
+++ b/README.md
@@ -47,7 +47,7 @@ Creates a Navigation UL based on a JSON file. Uses JSON to structure navigation
### Version 1.4
-* Now automatically sets $_SERVER['SCRIPT_NAME'] as current page
+* Now automatically sets $_SERVER["SCRIPT_NAME"] as current page
* Added multiple hook points for manipulating navigations
### Version 1.2
diff --git a/class.navigation.php b/class.navigation.php
deleted file mode 100644
index 624ba56..0000000
--- a/class.navigation.php
+++ /dev/null
@@ -1,226 +0,0 @@
-
- * $topNavData = '{
- * "pages": [
- * {
- * "title": "Nav 1",
- * "link": "/nav1/"
- * }, {
- * "title": "Nav 2",
- * "link": "/nav2/",
- * "pages": [
- * {
- * "title": "Subnav 1",
- * "link": "/downloads/fpo.pdf",
- * "target": "_blank"
- * }
- * ]
- * }
- * ]
- * }';
- *
- * $topNav = new \Module\Navigation\Builder($topNavData);
- *
- * // In body somewhere...
- *
- *
- *
- * ### Changelog
- * # Version 1.4
- * * Now automatically sets $_SERVER['SCRIPT_NAME'] as current page
- * * Added multiple hook points for manipulating navigations
- *
- * ## Version 1.2
- * * Added a track parameter
- *
- * @date June 16, 2014
- * @author Jaime A. Rodriguez
- * @version 1.1
- * @license http://opensource.org/licenses/MIT
- */
-class Builder {
- /**
- * string Use this string to determine currently active page
- * @private
- */
- private $current;
-
- /**
- * mixed Navigation data
- */
- private $data;
-
- /**
- * mixed Level
- */
- private $_level = 0;
-
- /**
- * Constructor
- * @param string $json json data containing the Navigation data
- */
- public function __construct($json='') {
- if (!is_object($json)) {
- if (class_exists('\Sleepy\Hook')) {
- $json = \Sleepy\Hook::addFilter('navigation_raw_json', $json);
- }
-
- $json = json_decode($json);
- }
-
- if (class_exists('\Sleepy\Hook')) {
- $json = \Sleepy\Hook::addFilter('navigation_rendered_json', $json);
- }
-
- $this->data = $json;
- $this->setCurrent($_SERVER['SCRIPT_NAME']);
- }
-
- /**
- * Is this page or its children an active page?
- * @param object $page An object containing page data
- * @return int 0=none;1=active;2=activeChild
- * @private
- */
- private function hasActive($page) {
- // are there subpages? check those too...
- if (isset($page->pages)) {
- foreach ($page->pages as $subPage) {
- if ($this->hasActive($subPage)) {
- return 2;
- }
- }
- }
-
- // can we find a match?
- if (substr($page->link, strlen($page->link) * -1) === $this->current) {
- if (class_exists('\Sleepy\Hook')) {
- \Sleepy\Hook::addAction('navigation_has_active');
- }
-
- return 1;
- }
-
- // no match...
- if (class_exists('\Sleepy\Hook')) {
- \Sleepy\Hook::addAction('navigation_no_active');
- }
-
- return 0;
- }
-
- /**
- * Renders the $pages as an unordered list
- * @param object $pages the page data
- * @return string The string containing the unordered list
- */
- private function renderNav($pages, $class="") {
- $this->_level = $this->_level + 1;
- $buffer = array();
-
- if ($this->_level > 1) {
- $class = "submenu " . $class;
- } else {
- $class = "menu " . $class;
- }
-
- $class = trim($class);
-
- $buffer[] = "";
-
- foreach ($pages as $page) {
- if (class_exists('\Sleepy\Hook')) {
- $page = \Sleepy\Hook::addFilter('navigation_page', $page);
-
- if (!empty($page->id)) {
- $page = \Sleepy\Hook::addFilter('navigation_page_' . $page->id, $page);
- }
- }
-
- if (!isset($page->class)) {
- $page->class = "";
- }
-
- if (isset($page->pages)) {
- $page->class = $page->class . " has-children";
- }
-
- $active = $this->hasActive($page);
- $classy = (!empty($page->class)) ? true : false;
- $track = (!empty($page->track)) ? "data-track=\"{$page->track}\" " : "";
- $id = (!empty($page->id)) ? "id=\"{$page->id}\" " : "";
- $target = (!empty($page->target)) ? "target=\"{$page->target}\" " : "";
- $href = (!empty($page->link)) ? "href=\"{$page->link}\" " : "";
- $attributes = trim($id . $track . $target . $href);
-
- $buffer[] = "- class = $page->class . " active";
- break;
- case 2:
- $page->class = $page->class . " active-child";
- }
-
- $buffer[] = trim($page->class);
-
- $buffer[] = "\"";
- }
-
- $buffer[] = ">";
-
- $buffer[] = "{$page->title}";
-
- if (isset($page->pages)) {
- $buffer[] = $this->renderNav($page->pages);
- }
-
- $buffer[] = "
";
- }
- $buffer[] = "
";
-
- return implode("", $buffer);
- }
-
- /**
- * Renders the Navigation
- * @return string The rendered navigation
- */
- public function show($class="") {
- $rendered = $this->renderNav($this->data->pages, $class);
-
- if (class_exists('\Sleepy\Hook')) {
- $rendered = \Sleepy\Hook::addFilter('navigation_rendered', $rendered);
- }
-
- return $rendered;
- }
-
- /**
- * Sets the current page search string
- * @param string $string A string used to determine if a page is current
- */
- public function setCurrent($string) {
- $this->current = str_replace(@URLBASE, "/", str_replace("index.php", "", $string));
-
- if (class_exists('\Sleepy\Hook')) {
- $this->current = \Sleepy\Hook::addFilter('navigation_current_page', $this->current);
- }
- }
-}
\ No newline at end of file
diff --git a/navigation_test.php b/navigation_test.php
deleted file mode 100644
index 2f02abe..0000000
--- a/navigation_test.php
+++ /dev/null
@@ -1,61 +0,0 @@
-
- * @version 1.8
- * @license http://opensource.org/licenses/MIT
- */
-class TestOfNavigation extends UnitTestCase {
- function setUp() {
- $this->nav = new \Module\Navigation\Builder('{
- "pages": [
- {
- "title": "1",
- "target": "_blank",
- "link": "1.html",
- "pages": [
- {
- "title": "1.1",
- "link": "1.1.html"
- }, {
- "title": "1.2",
- "link": "1.2.html"
- }
- ]
- }, {
- "id": "second",
- "title": "2",
- "link": "2.html",
- "class": "second"
- }
- ]
- }');
- }
-
- function testNav() {
- $nav = $this->nav->show();
- $this->assertEqual($nav,'');
- }
-
- function testTarget() {
- $nav = $this->nav->show();
- $this->assertEqual($nav,'');
- }
-
- function testActive() {
- $this->nav->setCurrent('1.html');
- $nav = $this->nav->show();
- $this->assertEqual($nav,'');
- }
-
- function testSubActive() {
- $this->nav->setCurrent('1.1.html');
- $nav = $this->nav->show();
- $this->assertEqual($nav,'');
- }
-}
\ No newline at end of file
diff --git a/tests/BuilderTest.php b/tests/BuilderTest.php
new file mode 100644
index 0000000..2b7aa52
--- /dev/null
+++ b/tests/BuilderTest.php
@@ -0,0 +1,166 @@
+
+ * @license http://opensource.org/licenses/MIT; MIT
+ * @link https://sleepymustache.com
+ */
+
+require_once dirname(__FILE__) . "/../../../sleepy/core/Loader.php";
+
+use PHPUnit\Framework\TestCase;
+use Sleepy\Core\SM;
+use Sleepy\Core\Hook;
+use Sleepy\Core\Loader;
+use Module\Navigation\Builder;
+
+Loader::register();
+Loader::addNamespace("Sleepy", dirname(__FILE__) . "/../../../sleepy");
+Loader::addNamespace("Sleepy\Core", dirname(__FILE__) . "/../../../sleepy/core");
+Loader::addNamespace("Module", dirname(__FILE__) . "/../../../modules");
+
+require_once dirname(__FILE__) . "/../../../../settings.php";
+
+/**
+ * Builder Unit Test
+ *
+ * @category Test
+ * @package Module\Navigation
+ * @author Jaime A. Rodriguez
+ * @license http://opensource.org/licenses/MIT; MIT
+ * @link https://sleepymustache.com
+ */
+class BuilderTest extends TestCase
+{
+ /**
+ * Setup method
+ *
+ * @return void
+ */
+ public function setUp() : void
+ {
+ $this->nav = new \Module\Navigation\Builder(
+ '{
+ "pages": [
+ {
+ "title": "1",
+ "target": "_blank",
+ "link": "1.html",
+ "pages": [
+ {
+ "title": "1.1",
+ "link": "1.1.html"
+ }, {
+ "title": "1.2",
+ "link": "1.2.html"
+ }
+ ]
+ }, {
+ "id": "second",
+ "title": "2",
+ "link": "2.html",
+ "class": "second"
+ }
+ ]
+ }'
+ );
+ }
+
+ /**
+ * Test if Actions work
+ *
+ * @return void
+ */
+ public function testNav() : void
+ {
+ $nav = $this->nav->show();
+ $this->assertEquals(
+ $nav,
+ implode(
+ [
+ ''
+ ]
+ )
+ );
+ }
+
+ /**
+ * Test if Targets works
+ *
+ * @return void
+ */
+ public function testTarget() : void
+ {
+ $nav = $this->nav->show();
+ $this->assertEquals(
+ $nav,
+ implode(
+ [
+ ''
+ ]
+ )
+ );
+ }
+
+ /**
+ * Test if Active works
+ *
+ * @return void
+ */
+ public function testActive() : void
+ {
+ $this->nav->setCurrent('1.html');
+ $nav = $this->nav->show();
+ $this->assertEquals(
+ $nav,
+ implode(
+ [
+ ''
+ ]
+ )
+ );
+ }
+
+ /**
+ * Test if SubActive works
+ *
+ * @return void
+ */
+ public function testSubActive() : void
+ {
+ $this->nav->setCurrent('1.1.html');
+ $nav = $this->nav->show();
+ $this->assertEquals(
+ $nav,
+ implode(
+ [
+ ''
+ ]
+ )
+ );
+ }
+}
\ No newline at end of file