Invision Power Services, Inc. * @copyright (c) Invision Power Services, Inc. * @license https://www.invisioncommunity.com/legal/standards/ * @package Invision Community * @subpackage Nexus * @since 29 Apr 2014 */ namespace IPS\nexus\modules\front\store; /* To prevent PHP errors (extending class does not exist) revealing path */ if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) ) { header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' ); exit; } /** * Browse Store */ class _store extends \IPS\Dispatcher\Controller { /** * @brief Products per page */ protected static $productsPerPage = 50; /** * Execute * * @return void */ public function execute() { \IPS\Output::i()->cssFiles = array_merge( \IPS\Output::i()->cssFiles, \IPS\Theme::i()->css( 'store.css', 'nexus' ) ); if ( \IPS\Theme::i()->settings['responsive'] ) { \IPS\Output::i()->cssFiles = array_merge( \IPS\Output::i()->cssFiles, \IPS\Theme::i()->css( 'store_responsive.css', 'nexus', 'front' ) ); } parent::execute(); } /** * Browse Store * * @return void */ protected function manage() { /* Work out currency */ if ( isset( \IPS\Request::i()->currency ) and in_array( \IPS\Request::i()->currency, \IPS\nexus\Money::currencies() ) ) { if ( isset( \IPS\Request::i()->csrfKey ) and \IPS\Request::i()->csrfKey === \IPS\Session\Front::i()->csrfKey ) { $_SESSION['cart'] = array(); $_SESSION['currency'] = \IPS\Request::i()->currency; } $currency = \IPS\Request::i()->currency; } else { $currency = \IPS\nexus\Customer::loggedIn()->defaultCurrency(); } /* If we have a category, display it */ if ( isset( \IPS\Request::i()->cat ) ) { try { $category = \IPS\nexus\Package\Group::loadAndCheckPerms( \IPS\Request::i()->cat ); if ( \IPS\Request::i()->view ) { \IPS\Request::i()->setCookie( 'storeView', ( \IPS\Request::i()->view == 'list' ) ? 'list' : 'grid', \IPS\DateTime::ts( time() )->add( new \DateInterval( 'P1Y' ) ) ); \IPS\Request::i()->cookie['storeView'] = ( \IPS\Request::i()->view == 'list' ) ? 'list' : 'grid'; } $subcategories = new \IPS\Patterns\ActiveRecordIterator( \IPS\Db::i()->select( '*', 'nexus_package_groups', array( 'pg_parent=?', $category->id ), 'pg_position ASC' ), 'IPS\nexus\Package\Group' ); $joins = array(); switch ( \IPS\Request::i()->sortby ) { case 'name': $joins['core_sys_lang_words'] = array( "word_app='nexus' AND word_key=CONCAT( 'nexus_package_', p_id ) AND lang_id=?", \IPS\Member::loggedIn()->language()->id ); $sortBy = 'word_custom'; break; case 'price_low': case 'price_high': $joins['nexus_package_base_prices'] = array( 'id=p_id' ); $sortBy = \IPS\Request::i()->sortby == 'price_low' ? $currency : ( $currency . ' DESC' ); break; case 'rating': $sortBy = 'p_rating DESC'; break; default: $sortBy = 'p_position'; break; } $currentPage = isset( \IPS\Request::i()->page ) ? intval( \IPS\Request::i()->page ) : 1; if( $currentPage < 1 ) { $currentPage = 1; } $select = \IPS\Db::i()->select( '*', 'nexus_packages', array( array( 'p_group=?', $category->id ), array( 'p_store=1' ), array( "( p_member_groups='*' OR " . \IPS\Db::i()->findInSet( 'p_member_groups', \IPS\Member::loggedIn()->groups ) . ' )' ) ), $sortBy, array( ( $currentPage - 1 ) * static::$productsPerPage, static::$productsPerPage ), NULL, NULL, \IPS\Db::SELECT_SQL_CALC_FOUND_ROWS ); foreach ( $joins as $table => $clause ) { $select->join( $table, $clause ); } $packages = new \IPS\Patterns\ActiveRecordIterator( $select, 'IPS\nexus\Package' ); $totalCount = \IPS\Db::i()->select( 'count(*)', 'nexus_packages', array( array( 'p_group=?', $category->id ), array( 'p_store=1' ), array( "( p_member_groups='*' OR " . \IPS\Db::i()->findInSet( 'p_member_groups', \IPS\Member::loggedIn()->groups ) . ' )' ) ) )->first(); $totalPages = ceil( $packages->count( TRUE ) / static::$productsPerPage ); if ( $totalPages and $currentPage > $totalPages ) { \IPS\Output::i()->redirect( $category->url()->setQueryString( 'page', $totalPages ), NULL, 303 ); } $pagination = \IPS\Theme::i()->getTemplate( 'global', 'core', 'global' )->pagination( $category->url(), $totalPages, $currentPage, static::$productsPerPage ); $packagesWithCustomFields = array(); foreach ( iterator_to_array( \IPS\Db::i()->select( 'cf_packages', 'nexus_package_fields', 'cf_purchase=1' ) ) as $ids ) { $packagesWithCustomFields = array_merge( $packagesWithCustomFields, array_filter( explode( ',', $ids ) ) ); } \IPS\Output::i()->sidebar['contextual'] = \IPS\Theme::i()->getTemplate('store')->categorySidebar( $category, $subcategories ); \IPS\Output::i()->output = \IPS\Theme::i()->getTemplate('store')->category( $category, $subcategories, $packages, $pagination, $packagesWithCustomFields, $totalCount ); foreach ( $category->parents() as $parent ) { \IPS\Output::i()->breadcrumb[] = array( $parent->url(), $parent->_title ); } \IPS\Output::i()->breadcrumb[] = array( NULL, $category->_title ); \IPS\Output::i()->title = $category->_title; /* Add JSON-LD @note Google does not like categories marked up and instead recommends marking up each product separately. @link https://developers.google.com/search/docs/guides/intro-structured-data#multiple-entities-on-the-same-page */ foreach( $packages as $package ) { $item = \IPS\nexus\Package\Item::load( $package->id ); $price = $package->price(); \IPS\Output::i()->jsonLd['package' . $package->id ] = array( '@context' => "http://schema.org", '@type' => "Product", 'name' => $package->_title, 'description' => strip_tags( $item->truncated() ), 'category' => $category->_title, 'url' => (string) $package->url(), 'sku' => $package->id, 'offers' => array( '@type' => 'Offer', 'availability' => ( $package->stock > 0 OR $package->stock == -1 ) ? 'http://schema.org/InStock' : 'http://schema.org/OutOfStock', 'price' => $price->amountAsString(), 'priceCurrency' => $price->currency, 'seller' => array( '@type' => 'Organization', 'name' => \IPS\Settings::i()->board_name ), ), ); if( $package->image ) { \IPS\Output::i()->jsonLd['package' . $package->id ]['image'] = (string) $package->image; } if( $package->reviewable ) { \IPS\Output::i()->jsonLd['package' . $package->id ]['aggregateRating'] = array( '@type' => 'AggregateRating', 'ratingValue' => $item->averageReviewRating(), 'ratingCount' => $item->reviews ); } } return; } catch ( \OutofRangeException $e ) { \IPS\Output::i()->error( 'node_error', '1X241/1', 404, '' ); } } /* Otherwise, display the index */ else { /* New Products */ $newProducts = array(); $nexus_store_new = explode( ',', \IPS\Settings::i()->nexus_store_new ); if ( $nexus_store_new[0] ) { $newProducts = \IPS\nexus\Package\Item::getItemsWithPermission( array( array( 'p_date_added>?', \IPS\DateTime::create()->sub( new \DateInterval( 'P' . $nexus_store_new[1] . 'D' ) )->getTimestamp() ) ), 'p_date_added DESC', $nexus_store_new[0] ); } /* Popular Products */ $popularProducts = array(); $nexus_store_popular = explode( ',', \IPS\Settings::i()->nexus_store_popular ); if ( $nexus_store_popular[0] ) { $where = array(); $where[] = array( 'ps_app=? AND ps_type=? AND ps_start>?', 'nexus', 'package', \IPS\DateTime::create()->sub( new \DateInterval( 'P' . $nexus_store_popular[1] . 'D' ) )->getTimestamp() ); $where[] = "( p_member_groups='*' OR " . \IPS\Db::i()->findInSet( 'p_member_groups', \IPS\Member::loggedIn()->groups ) . ' )'; $where[] = array( 'p_store=?', 1 ); $popularIds = \IPS\Db::i()->select( 'nexus_purchases.ps_item_id', 'nexus_purchases', $where, 'COUNT(ps_item_id) DESC', $nexus_store_popular[0], 'ps_item_id' )->join( 'nexus_packages', 'ps_item_id=p_id' ); if( count( $popularIds ) ) { $popularProducts = new \IPS\Patterns\ActiveRecordIterator( \IPS\Db::i()->select( 'nexus_packages.*', 'nexus_packages', array( \IPS\Db::i()->in( 'p_id', iterator_to_array( $popularIds ) ) ), 'FIELD(p_id, ' . implode( ',', iterator_to_array($popularIds) ) . ')' ), 'IPS\nexus\Package' ); } } /* Display */ \IPS\Output::i()->sidebar['contextual'] = \IPS\Theme::i()->getTemplate('store')->categorySidebar( ); \IPS\Output::i()->output = \IPS\Theme::i()->getTemplate('store')->index( \IPS\nexus\Customer::loggedIn()->cm_credits[ $currency ], $newProducts, $popularProducts ); \IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('module__nexus_store'); } } /** * Registration Packages * * @return void */ public function register() { \IPS\Output::i()->bodyClasses[] = 'ipsLayout_minimal'; \IPS\Output::i()->sidebar['enabled'] = FALSE; \IPS\Output::i()->output = \IPS\Theme::i()->getTemplate('store')->register( \IPS\nexus\Package\Item::getItemsWithPermission( array( array( 'p_reg=1' ) ), 'pg_position, p_position', 10, 'read', \IPS\Content\Hideable::FILTER_AUTOMATIC, 0, NULL, TRUE ) ); \IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('sign_up'); } /** * View Cart * * @return void */ protected function cart() { \IPS\Output::i()->output = \IPS\Theme::i()->getTemplate('store')->cart(); } }