diff --git a/blocktopmenu.php b/blocktopmenu.php index 5994b13..9594185 100644 --- a/blocktopmenu.php +++ b/blocktopmenu.php @@ -25,12 +25,16 @@ */ require(dirname(__FILE__).'/menutoplinks.class.php'); +require(dirname(__FILE__).'/src/BlocktopmenuNode.php'); +require(dirname(__FILE__).'/src/BlocktopmenuNodeVisitor.php'); +require(dirname(__FILE__).'/src/SuperfishNodeVisitor.php'); class Blocktopmenu extends Module { protected $_menu = ''; protected $_html = ''; protected $user_groups; + protected $items; /* * Pattern for matching config values @@ -467,6 +471,8 @@ protected function makeMenu() $id_lang = (int)$this->context->language->id; $id_shop = (int)Shop::getContextShopID(); + $items = array(); + foreach ($menu_items as $item) { if (!$item) { continue; @@ -477,47 +483,86 @@ protected function makeMenu() switch (substr($item, 0, strlen($value[1]))) { case 'CAT': - $this->_menu .= $this->generateCategoriesMenu(Category::getNestedCategories($id, $id_lang, false, $this->user_groups)); + $categories = Category::getNestedCategories($id, $id_lang, false, $this->user_groups); + if ($category = current($categories)) { + $categoryNode = $this->createCategoryNode($category, true); + $items[] = $categoryNode; + // Backward compatibility + $legacyOutput = $this->generateCategoriesMenu($categories); + if (!empty($legacyOutput)) { + $this->_menu .= $legacyOutput; + } else { + $this->_menu .= self::renderNode($categoryNode); + } + } break; case 'PRD': $selected = ($this->page_name == 'product' && (Tools::getValue('id_product') == $id)) ? ' class="sfHover"' : ''; $product = new Product((int)$id, true, (int)$id_lang); if (!is_null($product->id)) { - $this->_menu .= ''.$product->name.''.PHP_EOL; + $productNode = self::createNode(BlocktopmenuNode::TYPE_PRODUCT, array( + 'name' => $product->name, + 'link' => Tools::HtmlEntitiesUTF8($product->getLink()), + )); + $productNode->setSelected($selected); + $items[] = $productNode; + $this->_menu .= self::renderNode($productNode); } break; case 'CMS': - $selected = ($this->page_name == 'cms' && (Tools::getValue('id_cms') == $id)) ? ' class="sfHover"' : ''; + $selected = $this->page_name == 'cms' && Tools::getValue('id_cms') == $id; $cms = CMS::getLinks((int)$id_lang, array($id)); - if (count($cms)) { - $this->_menu .= ''.Tools::safeOutput($cms[0]['meta_title']).''.PHP_EOL; + if (count($cms) == 1) { + $cmsNode = self::createNode(BlocktopmenuNode::TYPE_CMS, array( + 'name' => Tools::safeOutput($cms[0]['meta_title']), + 'link' => Tools::HtmlEntitiesUTF8($cms[0]['link']), + )); + $cmsNode->setSelected($selected); + $items[] = $manufacturersNode; + $this->_menu .= self::renderNode($productNode); } break; case 'CMS_CAT': $category = new CMSCategory((int)$id, (int)$id_lang); - if (count($category)) { - $this->_menu .= '
  • '.$category->name.''; + if (!is_null($category->id)) { + $cmsCategoryNode = $this->createCmsCategoryNode($category); + $items[] = $cmsCategoryNode; + // Backward compatibility + $tmp = $this->_menu; + $this->_menu = ''; $this->getCMSMenuItems($category->id); - $this->_menu .= '
  • '.PHP_EOL; + if (!empty($this->_menu)) { + $this->_menu = $tmp.'
  • '.$category->name.''.$this->_menu.'
  • '; + } else { + $this->_menu = $tmp.self::renderNode($cmsCategoryNode); + } } break; // Case to handle the option to show all Manufacturers case 'ALLMAN': $link = new Link; - $this->_menu .= '
  • '.$this->l('All manufacturers').'
      '.PHP_EOL; + $manufacturersNode = self::createNode(BlocktopmenuNode::TYPE_MANUFACTURERS, array( + 'link' => $link->getPageLink('manufacturer'), + 'name' => $this->l('All manufacturers'), + )); $manufacturers = Manufacturer::getManufacturers(); foreach ($manufacturers as $key => $manufacturer) { - $this->_menu .= '
    • '.Tools::safeOutput($manufacturer['name']).'
    • '.PHP_EOL; + $manufacturerNode = self::createNode(BlocktopmenuNode::TYPE_MANUFACTURER, array( + 'link' => $link->getManufacturerLink((int)$manufacturer['id_manufacturer'], $manufacturer['link_rewrite']), + 'name' => Tools::safeOutput($manufacturer['name']), + )); + $manufacturersNode->addChild($manufacturerNode); } - $this->_menu .= '
    '; + $items[] = $manufacturersNode; + $this->_menu .= self::renderNode($manufacturersNode); break; case 'MAN': - $selected = ($this->page_name == 'manufacturer' && (Tools::getValue('id_manufacturer') == $id)) ? ' class="sfHover"' : ''; + $selected = ($this->page_name == 'manufacturer' && Tools::getValue('id_manufacturer') == $id); $manufacturer = new Manufacturer((int)$id, (int)$id_lang); if (!is_null($manufacturer->id)) { if (intval(Configuration::get('PS_REWRITING_SETTINGS'))) { @@ -526,50 +571,82 @@ protected function makeMenu() $manufacturer->link_rewrite = 0; } $link = new Link; - $this->_menu .= ''.Tools::safeOutput($manufacturer->name).'
  • '.PHP_EOL; + $manufacturerNode = self::createNode(BlocktopmenuNode::TYPE_MANUFACTURER, array( + 'link' => $link->getManufacturerLink((int)$id, $manufacturer->link_rewrite), + 'name' => Tools::safeOutput($manufacturer['name']), + )); + $manufacturerNode->setSelected($selected); + $items[] = $manufacturerNode; + $this->_menu .= self::renderNode($manufacturersNode); } break; // Case to handle the option to show all Suppliers case 'ALLSUP': $link = new Link; - $this->_menu .= '
  • '.$this->l('All suppliers').'
      '.PHP_EOL; + $suppliersNode = self::createNode(BlocktopmenuNode::TYPE_MANUFACTURER, array( + 'link' => $link->getPageLink('supplier'), + 'name' => $this->l('All suppliers'), + )); $suppliers = Supplier::getSuppliers(); foreach ($suppliers as $key => $supplier) { - $this->_menu .= '
    • '.Tools::safeOutput($supplier['name']).'
    • '.PHP_EOL; + $supplierNode = self::createNode(BlocktopmenuNode::TYPE_MANUFACTURER, array( + 'link' => $link->getSupplierLink((int)$supplier['id_supplier'], $supplier['link_rewrite']), + 'name' => Tools::safeOutput($supplier['name']), + )); + $suppliersNode->addChild($supplierNode); } - $this->_menu .= '
    '; + $items[] = $suppliersNode; + $this->_menu .= self::renderNode($suppliersNode); break; case 'SUP': - $selected = ($this->page_name == 'supplier' && (Tools::getValue('id_supplier') == $id)) ? ' class="sfHover"' : ''; + $selected = $this->page_name == 'supplier' && Tools::getValue('id_supplier') == $id; $supplier = new Supplier((int)$id, (int)$id_lang); if (!is_null($supplier->id)) { $link = new Link; - $this->_menu .= ''.$supplier->name.'
  • '.PHP_EOL; + $supplierNode = self::createNode(BlocktopmenuNode::TYPE_MANUFACTURER, array( + 'link' => Tools::HtmlEntitiesUTF8($link->getSupplierLink((int)$id, $supplier->link_rewrite)), + 'name' => Tools::safeOutput($supplier->name), + )); + $items[] = $supplierNode; + $this->_menu .= self::renderNode($supplierNode); } break; case 'SHOP': - $selected = ($this->page_name == 'index' && ($this->context->shop->id == $id)) ? ' class="sfHover"' : ''; + $selected = $this->page_name == 'index' && $this->context->shop->id == $id; $shop = new Shop((int)$id); if (Validate::isLoadedObject($shop)) { $link = new Link; - $this->_menu .= ''.$shop->name.''.PHP_EOL; + $shopNode = self::createNode(BlocktopmenuNode::TYPE_SHOP, array( + 'link' => Tools::HtmlEntitiesUTF8($shop->getBaseURL()), + 'name' => Tools::safeOutput($shop->name), + )); + $shopNode->setSelected($selected); + $items[] = $shopNode; + $this->_menu .= self::renderNode($shopNode); } break; case 'LNK': $link = MenuTopLinks::get((int)$id, (int)$id_lang, (int)$id_shop); - if (count($link)) { + if (count($link) == 1) { if (!isset($link[0]['label']) || ($link[0]['label'] == '')) { $default_language = Configuration::get('PS_LANG_DEFAULT'); $link = MenuTopLinks::get($link[0]['id_linksmenutop'], $default_language, (int)Shop::getContextShopID()); } - $this->_menu .= '
  • '.Tools::safeOutput($link[0]['label']).'
  • '.PHP_EOL; + $linkNode = self::createNode(BlocktopmenuNode::TYPE_LINK, array( + 'link' => Tools::HtmlEntitiesUTF8($link[0]['link']), + 'name' => Tools::safeOutput($link[0]['label']), + 'new_window' => $link[0]['new_window'], + )); + $this->_menu .= self::renderNode($linkNode); } break; } } + + $this->items = $items; } protected function generateCategoriesOption($categories, $items_to_skip = null) @@ -590,93 +667,20 @@ protected function generateCategoriesOption($categories, $items_to_skip = null) return $html; } + /** + * @deprecated + */ protected function generateCategoriesMenu($categories, $is_children = 0) { - $html = ''; - - foreach ($categories as $key => $category) { - if ($category['level_depth'] > 1) { - $cat = new Category($category['id_category']); - $link = Tools::HtmlEntitiesUTF8($cat->getLink()); - } else { - $link = $this->context->link->getPageLink('index'); - } - - /* Whenever a category is not active we shouldnt display it to customer */ - if ((bool)$category['active'] === false) { - continue; - } - - $html .= 'page_name == 'category' - && (int)Tools::getValue('id_category') == (int)$category['id_category']) ? ' class="sfHoverForce"' : '').'>'; - $html .= ''.$category['name'].''; - - if (isset($category['children']) && !empty($category['children'])) { - $html .= '
      '; - $html .= $this->generateCategoriesMenu($category['children'], 1); - - if ((int)$category['level_depth'] > 1 && !$is_children) { - $files = scandir(_PS_CAT_IMG_DIR_); - - if (count(preg_grep('/^'.$category['id_category'].'-([0-9])?_thumb.jpg/i', $files)) > 0) { - $html .= '
    • '; - - foreach ($files as $file) { - if (preg_match('/^'.$category['id_category'].'-([0-9])?_thumb.jpg/i', $file) === 1) { - $html .= '
      '.Tools::SafeOutput($category['name']).'
      '; - } - } - - $html .= '
    • '; - } - } - - $html .= '
    '; - } - - $html .= ''; - } - - return $html; + return ''; } + /** + * @deprecated + */ protected function getCMSMenuItems($parent, $depth = 1, $id_lang = false) { - $id_lang = $id_lang ? (int)$id_lang : (int)Context::getContext()->language->id; - - if ($depth > 3) { - return; - } - - $categories = $this->getCMSCategories(false, (int)$parent, (int)$id_lang); - $pages = $this->getCMSPages((int)$parent); - - if (count($categories) || count($pages)) { - $this->_menu .= '
      '; - - foreach ($categories as $category) { - $cat = new CMSCategory((int)$category['id_cms_category'], (int)$id_lang); - - $this->_menu .= '
    • '; - $this->_menu .= ''.$category['name'].''; - $this->getCMSMenuItems($category['id_cms_category'], (int)$depth + 1); - $this->_menu .= '
    • '; - } - - foreach ($pages as $page) { - $cms = new CMS($page['id_cms'], (int)$id_lang); - $links = $cms->getLinks((int)$id_lang, array((int)$cms->id)); - - $selected = ($this->page_name == 'cms' && ((int)Tools::getValue('id_cms') == $page['id_cms'])) ? ' class="sfHoverForce"' : ''; - $this->_menu .= '
    • '; - $this->_menu .= ''.$cms->meta_title.''; - $this->_menu .= '
    • '; - } - - $this->_menu .= '
    '; - } + return ''; } protected function getCMSOptions($parent = 0, $depth = 1, $id_lang = false, $items_to_skip = null, $id_shop = false) @@ -735,6 +739,7 @@ public function hookDisplayTop($param) $this->smarty->assign('MENU_SEARCH', Configuration::get('MOD_BLOCKTOPMENU_SEARCH', null, $shop_group_id, $shop_id)); $this->smarty->assign('MENU', $this->_menu); + $this->smarty->assign('items', $this->items); $this->smarty->assign('this_path', $this->_path); } @@ -1358,4 +1363,101 @@ public function renderList() return $helper->generateList($links, $fields_list); } + + private function createCategoryNode(array $category, $addThumbnails = false) + { + if (!$category['active']) { + return; + } + + if ($category['level_depth'] > 1) { + $cat = new Category($category['id_category']); + $link = Tools::HtmlEntitiesUTF8($cat->getLink()); + } else { + $link = $this->context->link->getPageLink('index'); + } + + $categoryNode = self::createNode(BlocktopmenuNode::TYPE_CATEGORY, array( + 'link' => $link, + 'name' => $category['name'], + )); + + $selected = $this->page_name == 'category' && Tools::getValue('id_category') == $category['id_category']; + $categoryNode->setSelected($selected); + + if (isset($category['children']) && !empty($category['children'])) { + foreach ($category['children'] as $child) { + if ($childNode = $this->createCategoryNode($child)) { + $categoryNode->addChild($childNode); + } + } + if ($addThumbnails && $category['level_depth'] > 1) { + $images = array(); + $files = scandir(_PS_CAT_IMG_DIR_); + foreach ($files as $file) { + if (preg_match('/^'.$category['id_category'].'-([0-9])?_thumb.jpg/i', $file) === 1) { + $images[] = array( + 'src' => $this->context->link->getMediaLink(_THEME_CAT_DIR_.$file), + 'alt' => Tools::SafeOutput($category['name']), + 'title' => Tools::SafeOutput($category['name']), + ); + } + } + if (!empty($images)) { + $thumbnailsNode = self::createNode(BlocktopmenuNode::TYPE_CATEGORY_THUMBNAILS, array( + 'images' => $images, + )); + $categoryNode->addChild($childNode); + } + } + } + + return $categoryNode; + } + + private function createCmsCategoryNode(CMSCategory $cmsCategory) + { + $cmsCategoryNode = self::createNode(BlocktopmenuNode::TYPE_CMS_CATEGORY, array( + 'link' => Tools::HtmlEntitiesUTF8($cmsCategory->getLink()), + 'name' => $cmsCategory->name, + )); + + $id_lang = Context::getContext()->language->id; + + $categories = $this->getCMSCategories(false, $cmsCategory->id, $id_lang); + $pages = $this->getCMSPages($cmsCategory->id); + + if (count($categories) || count($pages)) { + foreach ($categories as $category) { + $cmsSubCategory = new CMSCategory($category['id_cms_category'], $id_lang); + $cmsCategoryNode->addChild($this->createCmsCategoryNode($cmsSubCategory)); + } + foreach ($pages as $page) { + $cms = new CMS($page['id_cms'], $id_lang); + $links = $cms->getLinks($id_lang, array($cms->id)); + $selected = $this->page_name == 'cms' && Tools::getValue('id_cms') == $page['id_cms']; + $cmsNode = self::createNode(BlocktopmenuNode::TYPE_CMS, array( + 'link' => Tools::HtmlEntitiesUTF8($links[0]['link']), + 'name' => $cms->meta_title, + )); + $cmsNode->setSelected($selected); + $cmsCategoryNode->addChild($cmsNode); + } + } + + return $cmsCategoryNode; + } + + private static function renderNode(BlocktopmenuNode $node) + { + $visitor = new SuperfishNodeVisitor(); + $visitor->visit($node); + + return (string) $visitor; + } + + private static function createNode($type, array $data = array()) + { + return new BlocktopmenuNode($type, $data); + } } diff --git a/blocktopmenu.tpl b/blocktopmenu.tpl index cf8d86f..17f9c73 100644 --- a/blocktopmenu.tpl +++ b/blocktopmenu.tpl @@ -1,24 +1,43 @@ -{if $MENU != ''} - - -
    -
      - {$MENU} - {if $MENU_SEARCH} - - {/if} +{function name="blocktopmenu_menu" items=array()} + {foreach $items as $item} +
    • + {$item.data.name} + {if $item|count > 0} +
        + {blocktopmenu_menu items=$item.children} + {if $item.type == 'category-thumbnails'} +
      • + {foreach $item.images as $image} +
        + {$image.alt} +
        + {/foreach} +
      • + {/if}
      -
    -
     
    + {/if} + + {/foreach} +{/function} - +{if $items|count > 0} +
    +
      + {$MENU} + {* {blocktopmenu_menu items=$items} *} + {if $MENU_SEARCH} + + {/if} +
    +
    +
     
    {/if} \ No newline at end of file diff --git a/composer.json b/composer.json index 86bd4b3..56cae0c 100644 --- a/composer.json +++ b/composer.json @@ -12,6 +12,9 @@ "require": { "php": ">=5.3.2" }, + "require-dev": { + "phpunit/phpunit": "~4.5" + }, "config": { "preferred-install": "dist" }, diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..4605603 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,7 @@ + + + + tests + + + \ No newline at end of file diff --git a/src/BlocktopMenuNodeVisitor.php b/src/BlocktopMenuNodeVisitor.php new file mode 100644 index 0000000..93dc6f0 --- /dev/null +++ b/src/BlocktopMenuNodeVisitor.php @@ -0,0 +1,6 @@ +type = $type; + $this->data = $data; + } + + public function getType() + { + return $this->type; + } + + public function getData($name) + { + return isset($this->data[$name]) ? $this->data[$name] : null; + } + + public function addChild(BlocktopmenuNode $node) + { + $this->children[] = $node; + + return $this; + } + + public function setSelected($selected = true) + { + $this->selected = $selected; + + return $this; + } + + public function isSelected() + { + return $this->selected; + } + + public function getChildren() + { + return $this->children; + } + + public function offsetExists($offset) + { + return null !== $this->offsetGet($offset); + } + + public function offsetGet($offset) + { + switch ($offset) { + case 'selected' : + return $this->selected; + case 'children' : + return $this->children; + case 'data' : + return $this->data; + break; + case 'type' : + return $this->type; + break; + } + } + + public function offsetSet($offset, $value) + { + // Read only + } + + public function offsetUnset($offset) + { + // Read only + } + + public function getIterator() + { + return new ArrayIterator($this->children); + } + + public function count() + { + return count($this->children); + } +} diff --git a/src/SuperfishNodeVisitor.php b/src/SuperfishNodeVisitor.php new file mode 100644 index 0000000..20492f2 --- /dev/null +++ b/src/SuperfishNodeVisitor.php @@ -0,0 +1,51 @@ +output = ''; + } + + public function visit(BlocktopMenuNode $node) + { + $cssClasses = array(); + if ($node->isSelected()) { + $cssClasses[] = 'sfHover'; + } + if ($node->getType() === BlocktopMenuNode::TYPE_CATEGORY_THUMBNAILS) { + $cssClasses[] = 'category-thumbnail'; + } + + $this->output .= ''; + switch ($node->getType()) { + case BlocktopMenuNode::TYPE_CATEGORY_THUMBNAILS: + foreach ($node->getData('images') as $image) { + $this->output .= '
    '.$image['alt'].'
    '; + } + break; + case BlocktopMenuNode::TYPE_LINK: + $newWindow = true === $node->getData('new_window'); + $this->output .= ''.$node->getData('name').''; + break; + default : + $this->output .= ''.$node->getData('name').''; + } + + if (count($node) > 0) { + $this->output .= '
      '; + foreach ($node as $childNode) { + $this->visit($childNode); + } + $this->output .= '
    '; + } + $this->output .= ''; + } + + public function __toString() + { + return $this->output; + } +} diff --git a/tests/BlocktopmenuNodeTest.php b/tests/BlocktopmenuNodeTest.php new file mode 100644 index 0000000..afe99fe --- /dev/null +++ b/tests/BlocktopmenuNodeTest.php @@ -0,0 +1,30 @@ +addChild($subCategory1); + + $subCategory2 = new BlocktopmenuNode(BlocktopmenuNode::TYPE_CATEGORY); + $category->addChild($subCategory2); + + $this->assertCount(2, $category); + $i = 0; + foreach ($category as $key => $subCategory) { + if ($key === 0) { + $this->assertSame($subCategory1, $subCategory); + } + if ($key === 1) { + $this->assertSame($subCategory2, $subCategory); + } + ++$i; + } + $this->assertEquals(2, $i); + } +} \ No newline at end of file diff --git a/tests/SuperfishNodeVisitorTest.php b/tests/SuperfishNodeVisitorTest.php new file mode 100644 index 0000000..a5f7a35 --- /dev/null +++ b/tests/SuperfishNodeVisitorTest.php @@ -0,0 +1,97 @@ + 'Foo', + 'link' => 'http://prestashop.com', + )); + + $node->setSelected(false); + $visitor->visit($node); + $this->assertEquals((string) $visitor, '
  • Foo
  • '); + + $visitor->reset(); + $node->setSelected(true); + $visitor->visit($node); + $this->assertEquals((string) $visitor, '
  • Foo
  • '); + } + + public function testVisitLinkNode() + { + $visitor = new SuperfishNodeVisitor(); + + $node = new BlocktopmenuNode(BlocktopmenuNode::TYPE_LINK, array( + 'name' => 'Foo', + 'link' => 'http://prestashop.com', + 'new_window' => true + )); + + $node->setSelected(false); + $visitor->visit($node); + $this->assertEquals((string) $visitor, '
  • Foo
  • '); + } + + public function testVisitCategoryNode() + { + $category = new BlocktopmenuNode(BlocktopmenuNode::TYPE_CATEGORY, array( + 'name' => 'Foo', + 'link' => 'http://foo.prestashop.com', + )); + + $subCategory1 = new BlocktopmenuNode(BlocktopmenuNode::TYPE_CATEGORY, array( + 'name' => 'Bar', + 'link' => 'http://bar.prestashop.com', + )); + $category->addChild($subCategory1); + + $subCategory2 = new BlocktopmenuNode(BlocktopmenuNode::TYPE_CATEGORY, array( + 'name' => 'Baz', + 'link' => 'http://baz.prestashop.com', + )); + $category->addChild($subCategory2); + + $thumbnails = new BlocktopmenuNode(BlocktopmenuNode::TYPE_CATEGORY_THUMBNAILS, array( + 'images' => array( + array( + 'src' => 'http://prestashop.com/logo.jpg', + 'alt' => 'PrestaShop', + 'title' => 'PrestaShop', + ), + array( + 'src' => 'http://prestashop.com/logo.jpg', + 'alt' => 'PrestaShop', + 'title' => 'PrestaShop', + ), + ), + )); + $category->addChild($thumbnails); + + $visitor = new SuperfishNodeVisitor(); + $visitor->visit($category); + + $expected = << + Foo + + +EXPECTED; + + $this->assertEquals((string) $visitor, str_replace(array(PHP_EOL, ' '), array('', ''), $expected)); + } +}