Source of file ProductGroupController.php
Size: 26,682 Bytes - Last Modified: 2021-12-23T10:39:35+00:00
/var/www/docs.ssmods.com/process/src/src/Pages/ProductGroupController.php
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885 | <?php namespace Sunnysideup\Ecommerce\Pages; use PageController; use SilverStripe\Control\Director; use SilverStripe\Core\Config\Config; use SilverStripe\Core\Injector\Injector; use SilverStripe\ORM\ArrayList; use SilverStripe\ORM\DataList; use SilverStripe\ORM\PaginatedList; use SilverStripe\ORM\SS_List; use SilverStripe\Security\Permission; use SilverStripe\View\ArrayData; use SilverStripe\View\Requirements; use Sunnysideup\Ecommerce\Api\ArrayMethods; use Sunnysideup\Ecommerce\Api\ClassHelpers; use Sunnysideup\Ecommerce\Api\EcommerceCache; use Sunnysideup\Ecommerce\Api\ShoppingCart; use Sunnysideup\Ecommerce\Config\EcommerceConfig; use Sunnysideup\Ecommerce\Forms\ProductSearchForm; use Sunnysideup\Ecommerce\ProductsAndGroups\Applyers\BaseApplyer; use Sunnysideup\Ecommerce\ProductsAndGroups\Applyers\ProductSearchFilter; use Sunnysideup\Ecommerce\ProductsAndGroups\Builders\FinalProductList; use Sunnysideup\Vardump\Vardump; class ProductGroupController extends PageController { /** * the exact list of products that is going to be shown (excluding pagination). * * @var DataList */ protected $productList; /** * the final product list that we use to collect products. * * @var FinalProductList */ protected $finalProductList; /** * The original Title of this page before filters, etc... * * @var string */ protected $originalTitle = ''; protected $userPreferencesObject; /** * form for searching. * * @var ProductSearchForm */ protected $searchForm; /** * Is this a product search? * * @var bool */ protected $isSearchResults = false; protected $secondaryTitle = ''; protected $hasManyProductsCache; protected $totalRawCountCache; private static $minimum_number_of_pages_to_show_filters_and_sort = 3; private static $allowed_actions = [ 'debug' => 'ADMIN', 'ProductSearchForm' => true, ]; //####################################### // actions //####################################### public function index() { return $this->defaultReturn(); } //################################## // template methods //################################## /** * adds Javascript to the page to make it work when products are cached. */ public function CachingRelatedJavascript() { if ($this->ProductGroupListAreAjaxified()) { Requirements::customScript( " if(typeof EcomCartOptions === 'undefined') { var EcomCartOptions = {}; } EcomCartOptions.ajaxifyProductList = true; EcomCartOptions.ajaxifiedListHolderSelector = '#" . $this->AjaxDefinitions()->ProductListHolderID() . "'; EcomCartOptions.ajaxifiedListAdjusterSelectors = '." . $this->AjaxDefinitions()->ProductListAjaxifiedLinkClassName() . "'; EcomCartOptions.hiddenPageTitleID = '#" . $this->AjaxDefinitions()->HiddenPageTitleID() . "'; ", 'cachingRelatedJavascript_AJAXlist' ); } else { Requirements::customScript( " if(typeof EcomCartOptions === 'undefined') { var EcomCartOptions = {}; } EcomCartOptions.ajaxifyProductList = false; ", 'cachingRelatedJavascript_AJAXlist' ); } $currentOrder = ShoppingCart::current_order(); if ($currentOrder->TotalItems(true)) { $responseClass = EcommerceConfig::get(ShoppingCart::class, 'response_class'); $obj = new $responseClass(); $obj->setIncludeHeaders(false); $json = $obj->ReturnCartData(); Requirements::customScript( " if(typeof EcomCartOptions === 'undefined') { var EcomCartOptions = {}; } EcomCartOptions.initialData= " . $json . '; ', 'cachingRelatedJavascript_JSON' ); } } /** * get the unpaginated list. Only set once. * * @return SS_List */ public function getProductList() { if (! $this->productList) { $this->productList = $this->getCachedProductList(); if (! $this->productList) { // make sure to apply search filter first. $this->productList = $this->getFinalProductList() ->applySearchFilter($this->getCurrentUserPreferencesKey('SEARCHFILTER'), $this->getCurrentUserPreferencesParams('SEARCHFILTER')) ->applyGroupFilter($this->getCurrentUserPreferencesKey('GROUPFILTER'), $this->getCurrentUserPreferencesParams('GROUPFILTER')) ->applyFilter($this->getCurrentUserPreferencesKey('FILTER'), $this->getCurrentUserPreferencesParams('FILTER')) ->applySorter($this->getCurrentUserPreferencesKey('SORT'), $this->getCurrentUserPreferencesParams('SORT')) ->applyDisplayer($this->getCurrentUserPreferencesKey('DISPLAY'), $this->getCurrentUserPreferencesParams('DISPLAY')) ->getProducts() ; $this->setCachedProductList($this->productList); } } return $this->productList; } /** * Return the products for this group. * * This is the call that is made from the template and has the actual final * products being shown. * * @return \SilverStripe\ORM\PaginatedList */ public function Products(): ?PaginatedList { //get the list first, so that everything is calculated $list = $this->getProductList(); $this->addSecondaryTitle(); $this->cachingRelatedJavascript(); return $this->paginateList($list); } /** * Unique caching key for the product list... */ public function ProductGroupListCachingKey(?bool $withPageNumber = false): string { if ($this->ProductGroupListAreCacheable()) { return $this->getUserPreferencesClass()->ProductGroupListCachingKey($withPageNumber); } return ''; } /** * Important * Unique caching key for the product list... */ public function ProductGroupListCachingKeyForTemplate(?bool $withPageNumber = false): string { return EcommerceCache::inst()->cacheKeyRefiner($this->ProductGroupListCachingKey($withPageNumber)); } /** * Is the product list cache-able? */ public function ProductGroupListAreCacheable(): bool { if ($this->productListsHTMLCanBeCached()) { $currentOrder = ShoppingCart::current_order(); return ! $currentOrder->getHasAlternativeCurrency(); } return false; } /** * is the product list ajaxified. */ public function ProductGroupListAreAjaxified(): bool { return true; } /** * title without additions. */ public function OriginalTitle(): string { return $this->originalTitle; } /** * This method can be extended to show products in the side bar. */ public function SidebarProducts(): ?SS_List { return null; } /** * Returns child product groups for use in 'in this section'. For example * the vegetable Product Group may have listed here: Carrot, Cabbage, etc... */ public function MenuChildGroups(?int $levels = 2): ?DataList { if ($this->IsSearchResults()) { return null; } return $this->ChildCategories($levels); } public function ShowGroupFilterLinks(): bool { return $this->HasManyProducts() && $this->HasGroupFilters(); } public function ShowSearchFilterLinks(): bool { return $this->HasManyProducts() && $this->HasSearchFilters(); } public function ShowFilterLinks(): bool { return $this->HasManyProducts() && $this->HasFilters(); } public function ShowSortLinks(): bool { return $this->HasManyProducts() && $this->HasSorts(); } public function ShowDisplayLinks(): bool { return $this->HasManyProducts() && $this->HasDisplays(); } public function ShowGroupFilterSortDisplayLinks(): bool { return $this->ShowSearchFilterLinks() || $this->ShowGroupFilterLinks() || $this->ShowFilterLinks() || $this->ShowSortLinks() || $this->ShowDisplayLinks(); } public function HasManyProducts(): bool { if (null === $this->hasManyProductsCache) { $this->hasManyProductsCache = $this->getBaseProductList()->hasMoreThanOne($this->Config()->get('minimum_number_of_pages_to_show_filters_and_sort')); } return $this->hasManyProductsCache; } public function HasSearchFilter(): bool { return (bool) $this->getCurrentUserPreferencesParams('SEARCHFILTER'); } public function HasGroupFilter(): bool { return (bool) $this->getCurrentUserPreferencesParams('GROUPFILTER'); } public function HasFilter(): bool { return $this->getCurrentUserPreferencesKey('FILTER') !== $this->getListConfigCalculated('FILTER'); } public function HasSort(): bool { if ($this->IsSearchResults()) { return BaseApplyer::DEFAULT_NAME !== $this->getCurrentUserPreferencesKey('SORT'); } return $this->getCurrentUserPreferencesKey('SORT') !== $this->getListConfigCalculated('SORT'); } public function HasDisplay(): bool { return $this->getCurrentUserPreferencesKey('DISPLAY') !== $this->getListConfigCalculated('DISPLAY'); } public function HasGroupFilterSortDisplay(): bool { return $this->HasSearchFilter() || $this->HasGroupFilter() || $this->HasFilter() || $this->HasSort() || $this->HasDisplay(); } /** * we can use this for pre-set search filters. */ public function HasSearchFilters(): bool { return false; } /** * Are group filters available? we check one at the time so that we do the least * amount of DB queries. */ public function HasGroupFilters(): bool { return $this->GroupFilterLinks()->count() > 1; } /** * Are filters available? we check one at the time so that we do the least * amount of DB queries. */ public function HasFilters(): bool { return $this->FilterLinks()->count() > 1; } /** * Are filters available? we check one at the time so that we do the least * amount of DB queries. */ public function HasSorts(): bool { return $this->SortLinks()->count() > 1; } /** * Are filters available? we check one at the time so that we do the least * amount of DB queries. */ public function HasDisplays(): bool { return $this->DisplayLinks()->count() > 1; } /** * Number of entries per page limited by total number of pages available... */ public function MaxNumberOfProductsPerPage(): int { if ($this->IsShowFullList()) { return min($this->TotalCount(), $this->MaxNumberOfProductsPerPageAbsolute()); } $perPage = $this->getProductsPerPage(); $total = $this->TotalCount(); return $perPage > $total ? $total : $perPage; } public function TotalCount(): int { return $this->getFinalProductList()->getRawCountCached(); } public function getCurrentPageNumber(): int { $pageStart = (int) $this->request->getVar('start'); if ($pageStart) { return ($pageStart / $this->getProductsPerPage()) + 1; } return 1; } public function getUserPreferencesTitle(string $type, ?string $key): string { return $this->getProductGroupSchema()->getSortFilterDisplayValues($type, 'Title'); } /** * returns the current searcj filter applied to the list * in a human readable string. */ public function getCurrentSearchFilterTitle(): string { if ($this->hasSearchFilter()) { return $this->getUserPreferencesClass()->getSearchFilterTitle($this->getCurrentUserPreferencesKey('SEARCHFILTER')); } return ''; } /** * returns the current filter applied to the list * in a human readable string. */ public function getCurrentGroupFilterTitle(): string { if ($this->hasGroupFilter()) { return $this->getUserPreferencesClass()->getGroupFilterTitle($this->getCurrentUserPreferencesKey('GROUPFILTER')); } return ''; } /** * returns the current filter applied to the list * in a human readable string. */ public function getCurrentFilterTitle(): string { if ($this->hasFilter()) { return $this->getUserPreferencesClass()->getFilterTitle($this->getCurrentUserPreferencesKey('FILTER')); } return ''; } /** * returns the current sort applied to the list * in a human readable string. */ public function getCurrentSortTitle(): string { if ($this->HasSort()) { return $this->getUserPreferencesClass()->getSortTitle($this->getCurrentUserPreferencesKey('SORT')); } return ''; } public function getCurrentDisplayTitle(): string { if ($this->HasDisplay()) { return $this->getUserPreferencesClass()->getDisplayTitle($this->getCurrentUserPreferencesKey('DISPLAY')); } return ''; } public function getSearchFilterHeader(): string { return _t('Ecommerce.SEARCH_PRODUCTS', 'Search in ') . $this->originalTitle; } public function getGroupFilterHeader(): string { return _t('Ecommerce.FILTER_BY_CATEGORY', 'Filter by Category'); } public function getFilterHeader(): string { return _t('Ecommerce.FILTER', 'Filter'); } public function getSortHeader(): string { return _t('Ecommerce.SORT', 'SORT'); } public function getDisplayHeader(): string { return _t('Ecommerce.DISPLAY', 'Presentation'); } /** * short-cut for getListConfigCalculated("DISPLAY") * for use in templtes. * * @return string - key */ public function MyDefaultDisplayStyle(): string { return $this->getListConfigCalculated('DISPLAY'); } /** * Provides a ArrayList of links for filters products. * * @return \SilverStripe\ORM\ArrayList( ArrayData(Name, Link, SelectKey, Current (boolean), LinkingMode)) */ public function SearchFilterLinks(): ArrayList { return $this->getUserPreferencesClass()->getLinksPerType('SEARCHFILTER'); } /** * Provides a ArrayList of links for filters products. * * @return \SilverStripe\ORM\ArrayList( ArrayData(Name, Link, SelectKey, Current (boolean), LinkingMode)) */ public function GroupFilterLinks(): SS_List { return $this->getUserPreferencesClass()->getLinksPerType('GROUPFILTER'); } /** * Provides a ArrayList of links for filters products. * * @return \SilverStripe\ORM\ArrayList( ArrayData(Name, Link, SelectKey, Current (boolean), LinkingMode)) */ public function FilterLinks(): ArrayList { return $this->getUserPreferencesClass()->getLinksPerType('FILTER'); } /** * Provides a ArrayList of links for sorting products. */ public function SortLinks(): ArrayList { return $this->getUserPreferencesClass()->getLinksPerType('SORT'); } /** * Provides a ArrayList for displaying display links. */ public function DisplayLinks(): ArrayList { return $this->getUserPreferencesClass()->getLinksPerType('DISPLAY'); } public function getLink($action = null): string { return $this->Link($action); } public function Link($action = null): string { return $this->getLinkTemplate($action); } public function ResetPreferencesLink($action = null): string { return parent::link() . '?reload=1'; } public function ListAllLink(): string { return $this->getLinkTemplate('', 'DISPLAY', 'all'); } public function ListAFewLink(): string { return $this->getLinkTemplate('', 'DISPLAY', 'default'); } /** * After a search is conducted you may end up with a bunch * of recommended product groups. They will be returned here... * We sort the list in the order that it is provided. * * @return null|\SilverStripe\ORM\DataList (ProductGroups) */ public function SearchResultsChildGroups(): ?DataList { return $this->getSearchApplyer()->getProductGroupAsList(); } /** * returns a search form to search current products ready to search. * * @return ProductSearchForm object */ public function ProductSearchForm() { if (null === $this->searchForm) { // $onlySearchTitle = $this->originalTitle; // if (ClassHelpers::check_for_instance_of($this->dataRecord, ProductGroupSearchPage::class, false)) { // if ($this->HasSearchResults()) { // $onlySearchTitle = 'Last Search Results'; // } // } $this->searchForm = ProductSearchForm::create( $this, 'ProductSearchForm', ); //load previous data. $this->searchForm->setBaseListOwner($this->dataRecord); // $sortGetVariable = $this->getSortFilterDisplayValues('SORT', 'getVariable'); // $additionalGetParameters = $sortGetVariable . '=' . Config::inst()->get(ProductGroupSearchPage::class, 'best_match_key'); // $form->setAdditionalGetParameters($additionalGetParameters); // $form->setSearchHash($this->searchKeyword); } return $this->searchForm; } /** * Does this page have any search results? * If search was carried out without returns * then it returns zero (false). * * @todo: to cleanup */ public function HasSearchResults(): bool { return $this->getSearchApplyer()->getHasResults(); } /** * Should the product search form be shown immediately? */ public function ShowSearchFormImmediately(): bool { if ($this->ShowSearchFormAtAll()) { if ($this->IsSearchResults()) { return true; } return ! (bool) $this->getProductList()->exists(); } return false; } /** * Show a search form on this page? */ public function ShowSearchFormAtAll(): bool { return true; } /** * Is the current page a display of search results. */ public function IsSearchResults(): bool { return $this->HasSearchFilter(); } public function getCurrentUserPreferencesKey(?string $type = '') { return $this->getUserPreferencesClass()->getCurrentUserPreferencesKey($type); } public function getCurrentUserPreferencesParams(?string $type = '') { return $this->getUserPreferencesClass()->getCurrentUserPreferencesParams($type); } /** * Retrieve a list of products, based on the given parameters. * * This method is usually called by the various controller methods. * * The extraFilter helps you to select different products depending on the * method used in the controller. * * To paginate this * * @param array|string $extraFilter OPTIONAL Additional SQL filters to apply to the Product retrieval * @param array|string $alternativeSort OPTIONAL Additional SQL for sorting * * @return FinalProductList */ public function getFinalProductList($extraFilter = null, $alternativeSort = null) { if (! $this->finalProductList) { $className = $this->getProductGroupSchema()->getFinalProductListClassName(); $this->finalProductList = $className::inst($this, $this->dataRecord); ClassHelpers::check_for_instance_of($this->finalProductList, FinalProductList::class, true); } if ($extraFilter) { $this->finalProductList->setExtraFilter($extraFilter); } if ($alternativeSort) { $this->finalProductList->setAlternatveSort($alternativeSort); } return $this->finalProductList; } public function DebugSearchString(): string { return $this->getSearchApplyer()->getDebugOutputString(); } public function VardumpMe(string $method) { return Vardump::inst()->vardumpMe($this->{$method}(), $method, static::class); } public function setSecondaryTitle(string $v) { $this->secondaryTitle = $v; } public function getSecondaryTitle(): string { return $this->secondaryTitle; } protected function saveUserPreferences(?array $data = []) { return $this->getUserPreferencesClass()->saveUserPreferences($data); } protected function setSearchString() { //do nothing here, but on ProductGroupSearchPage, we set it as the baselist.... } protected function afterHandleRequest() { if ($this->request->getVar('showdebug') && (Permission::check('ADMIN') || Director::isDev())) { $this->getProductGroupSchema()->getDebugProviderAsObject($this, $this->dataRecord)->print(); die(); } parent::afterHandleRequest(); } protected function getCachedProductList(): ?DataList { $key = $this->ProductGroupListCachingKey(); if ($key && EcommerceCache::inst()->hasCache($key)) { $ids = EcommerceCache::inst()->retrieve($key); $ids = ArrayMethods::filter_array($ids); return Product::get() ->filter(['ID' => $ids]) ->sort(ArrayMethods::create_sort_statement_from_id_array($ids, Product::class)) ; } return null; } protected function setCachedProductList($productList) { $key = $this->ProductGroupListCachingKey(); $ids = ArrayMethods::filter_array($productList->columnUnique()); EcommerceCache::inst()->save($key, $ids); } /** * returns the current page with get variables. If a type is specified then * instead of the value for that type, we add: '[[INSERT_HERE]]'. * * @param string $action e.g. filterfor * @param string $type e.g. FILTER|SORT|DISPLAY * @param string $replacementForType e.g. 'all' */ protected function getLinkTemplate(?string $action = null, ?string $type = '', ?string $replacementForType = ''): string { return $this->getUserPreferencesClass()->getLinkTemplate($action, $type, $replacementForType); } protected function init() { parent::init(); if ($this->request->getVar('reload')) { return $this->redirect($this->Link()); } $this->originalTitle = $this->Title; Requirements::themedCSS('client/css/ProductGroup'); Requirements::themedCSS('client/css/ProductGroupPopUp'); Requirements::javascript('sunnysideup/ecommerce: client/javascript/EcomProducts.js'); //we save data from get variables... $this->saveUserPreferences(); $this->setSearchString(); //makes sure best match only applies to search -i.e. reset otherwise. } protected function setIdArrayDefaultSort($idArray, $alternativeSort = null) { return $this->getUserPreferencesClass()->setIdArrayDefaultSort($idArray, $alternativeSort); } /** * Overload this function of ProductGroup Extensions. */ protected function returnAjaxifiedProductList(): bool { return Director::is_ajax(); } /** * Overload this function of ProductGroup Extensions. */ protected function productListsHTMLCanBeCached(): bool { //todo: FIX! return ! (bool) EcommerceConfig::inst()->OnlyShowProductsThatCanBePurchased; } /** * turns full list into paginated list. * * @param SS_List $list */ protected function paginateList($list): ?PaginatedList { $obj = null; if ($list->exists()) { $obj = PaginatedList::create($list, $this->request); if ($this->IsShowFullList()) { $obj->setPageLength($this->MaxNumberOfProductsPerPageAbsolute()); } else { $obj->setPageLength($this->getProductsPerPage()); } } return $obj; } protected function MaxNumberOfProductsPerPageAbsolute(): int { return EcommerceConfig::get(ProductGroup::class, 'maximum_number_of_products_to_list') + 1; } protected function IsShowFullList(): bool { return $this->getUserPreferencesClass()->IsShowFullList(); } /** * @return ProductSearchFilter */ protected function getSearchApplyer() { return $this->getFinalProductList()->getApplyer('SEARCHFILTER'); } protected function defaultReturn() { if ($this->returnAjaxifiedProductList()) { return $this->renderWith('Sunnysideup\Ecommerce\Includes\AjaxProductList'); } // important - because we want to get all the details loaded before we start with // building template $this->Products(); return []; } protected function getUserPreferencesClass() { if (null === $this->userPreferencesObject) { $className = $this->getProductGroupSchema()->getUserPreferencesClassName(); $this->userPreferencesObject = Injector::inst()->get($className) ->setRootGroup($this->dataRecord) ->setRootGroupController($this) ->setRequest($this->getRequest()) ; } return $this->userPreferencesObject; } protected function addSecondaryTitle(?string $secondaryTitle = '') { $this->getUserPreferencesClass()->addSecondaryTitle($secondaryTitle); } } |