<?php
/**
* Copyright (C) 2021 Rhyme Digital, LLC.
*
* @author Blair Winans <blair@rhyme.digital>
* @author Adam Fisher <adam@rhyme.digital>
* @link https://rhyme.digital
* @license http://www.gnu.org/licenses/lgpl-3.0.html LGPL
*/
namespace Rhyme\WMassArtsHub\Module\Profile;
use Contao\Input;
use Contao\System;
use Contao\Database;
use Contao\Pagination;
use Contao\StringUtil;
use Contao\BackendTemplate;
use Contao\Model\QueryBuilder;
use Haste\Util\InsertTag as HasteInsertTag;
use Haste\Generator\RowClass;
use Rhyme\WMassArtsHub\Helper\FilterHelper;
use Rhyme\WMassArtsHub\Model\Profile\Item as WMassArtsHub_ProfileItemModel;
use Rhyme\WMassArtsHub\Model\Profile\Type as WMassArtsHub_ProfileTypeModel;
/**
* Class Lister
* @package Rhyme\WMassArtsHub\Module\Collection
*/
class Lister extends Module
{
/**
* Template
* @var string
*/
protected $strTemplate = 'mod_artshub_profile_lister';
/**
* Display a wildcard in the back end
* @return string
*/
public function generate()
{
if (TL_MODE === 'BE')
{
$objTemplate = new BackendTemplate('be_wildcard');
$objTemplate->wildcard = '### ARTSHUB PROFILE ITEM LISTER ###';
$objTemplate->title = $this->headline;
$objTemplate->id = $this->id;
$objTemplate->link = $this->name;
$objTemplate->href = 'contao/main.php?do=themes&table=tl_module&act=edit&id=' . $this->id;
return $objTemplate->parse();
}
if (\is_numeric(Input::get('perpage')) && \intval(Input::get('perpage')))
{
$this->perPage = \intval(Input::get('perpage'));
}
return parent::generate();
}
/**
* Compile
*/
protected function compile()
{
// Haste AJAX reload
\Haste\Ajax\ReloadHelper::subscribe(
\Haste\Ajax\ReloadHelper::getUniqid(\Haste\Ajax\ReloadHelper::TYPE_MODULE, $this->id),
['reloadArtsHubProfileListItems']
);
//Get items
$arrProfileItems = $this->findItems();
// No items found
if (!is_array($arrProfileItems) || empty($arrProfileItems)) {
$this->compileEmptyMessage();
return;
}
// Make sure this module has an ID
$arrCssID = (array)$this->cssID;
$arrCssID[0] = $arrCssID[0] ?: 'profilelister'.$this->id;
$this->cssID = $arrCssID;
// Add scripts
$GLOBALS['TL_JAVASCRIPT']['jquerymosaic'] = 'web/bundles/rhymewmassartshub/assets/js/vendor/jquerymosaic/jquery.mosaic.js|static';
$GLOBALS['TL_CSS']['jquerymosaic'] = 'web/bundles/rhymewmassartshub/assets/js/vendor/jquerymosaic/jquery.mosaic.css|static';
$GLOBALS['TL_JAVASCRIPT']['profileListerModal'] = 'web/bundles/rhymewmassartshub/assets/js/frontend/profileLister/modal.js|static';
$GLOBALS['TL_JAVASCRIPT']['profileListerPagination'] = 'web/bundles/rhymewmassartshub/assets/js/frontend/profileLister/pagination.js|static';
$GLOBALS['TL_BODY']['historyjs'] = '<script src="https://cdnjs.cloudflare.com/ajax/libs/history.js/1.8/native.history.min.js" integrity="sha512-LW9lY12yhdxezhOnfueR/bF7GdraV3ZCZpdTTUqrq6ifCsp4wmnlBbw8/qTjs+9jqLXZn+cBVYN9rlQz52UVkA==" crossorigin="anonymous"></script>';
$GLOBALS['TL_BODY']['profileListerPagination'.$this->id] = "
<script>
jQuery(document).ready(function(){
ArtsHub.ProfileLister.Pagination.create(jQuery('#".$arrCssID[0]."'), {
});
});
</script>";
$arrBuffer = array();
foreach ($arrProfileItems as $objItem) {
$arrConfig = array(
'module' => $this,
'template' => $this->artshub_profile_list_layout ?: 'artshub_profile_list_default',
'jumpTo' => $this->findJumpToPage($objItem),
);
$arrCSS = StringUtil::deserialize($objItem->cssID, true);
$arrBuffer[] = array(
'cssID' => ($arrCSS[0] != '') ? ' id="' . $arrCSS[0] . '"' : '',
'class' => trim('profileItem ' . $arrCSS[1]),
'html' => $objItem->generate($arrConfig),
'item' => $objItem,
);
}
if (isset($GLOBALS['TL_HOOKS']['generateProfileList']) && is_array($GLOBALS['TL_HOOKS']['generateProfileList'])) {
foreach ($GLOBALS['TL_HOOKS']['generateProfileList'] as $callback) {
$objCallback = System::importStatic($callback[0]);
$arrBuffer = $objCallback->{$callback[1]}($arrBuffer, $arrProfileItems, $this->Template, $this);
}
}
RowClass::withKey('class')->addCount('profileItem_')->addEvenOdd('profileItem_')->addFirstLast('profileItem_')->addGridRows($this->artshub_profile_cols)->addGridCols($this->artshub_profile_cols)->applyTo($arrBuffer);
$this->Template->items = $arrBuffer;
//Add jumpto Page
global $objPage;
$this->Template->jumpTo = $this->jumpTo ?: $objPage->id;
}
/**
* Compile template to show a message if there are no items
*
* @param bool $disableSearchIndex
*/
protected function compileEmptyMessage($disableSearchIndex = true)
{
global $objPage;
// Do not index or cache the page
if ($disableSearchIndex) {
$objPage->noSearch = 1;
$objPage->cache = 0;
}
$message = $this->artshub_profile_emptyMessage ? $this->artshub_profile_noListings : $GLOBALS['TL_LANG']['MSC']['artshub_profile_noListings'];
$this->Template->empty = true;
$this->Template->type = 'empty';
$this->Template->message = $message;
$this->Template->items = array();
}
/**
* Find all items we need to list.
* @return array
*/
protected function findItems()
{
$arrColumns = array();
//Get filters and sorting values
list($arrValues, $strWhere, $strSorting) = $this->getFiltersAndSorting();
//Handle no values
if (!is_array($arrValues)) {
$arrValues = array();
}
//Add categories to query
$t = WMassArtsHub_ProfileItemModel::getTable();
if ($this->artshub_profile_list_where != '') {
$arrColumns[] = HasteInsertTag::replaceRecursively($this->artshub_profile_list_where);
}
//Add where query from filters/sorting
if ($strWhere != '') {
$arrColumns[] = $strWhere;
}
//Calculate the total on the query
$intTotal = static::countPublishedBy($arrColumns, $arrValues);
// Use number of items too
if ($this->numberOfItems)
{
$intTotal = min($this->numberOfItems, $intTotal);
}
//Generate pagination and get offset
$offset = $this->generatePagination($intTotal);
//Build options
$arrOptions = array
(
'offset' => $offset,
'limit' => ($this->numberOfItems && $this->perPage) ? min($this->numberOfItems, $this->perPage) : ($this->perPage ?: $this->numberOfItems),
'order' => $strSorting,
);
// Fix for category sorting values
//$arrColumns[] = "c.page_id IN (" . implode(',', $arrCategories) . ")";
//Run query
$objItems = WMassArtsHub_ProfileItemModel::findPublishedBy(
$arrColumns,
$arrValues,
$arrOptions
);
return (null === $objItems) ? array() : $objItems->getModels();
}
/**
* Generate the pagination
* @param integer
* @return integer
*/
protected function generatePagination($total)
{
// Add pagination
if ($this->perPage > 0 && $total > 0)
{
if (Input::post('page'))
{
Input::setGet('page', Input::post('page'));
}
$page = Input::get('page') ? Input::get('page') : 1;
// Check the maximum page number
if ($page > ($total/$this->perPage))
{
$page = ceil($total/$this->perPage);
}
$offset = ($page - 1) * $this->perPage;
$objPagination = new Pagination($total, $this->perPage);
$this->Template->pagination = $objPagination->generate("\n ");
return $offset;
}
return 0;
}
/**
* Get filter & sorting configuration
* @return array
*/
protected function getFiltersAndSorting($blnNativeSQL = true)
{
$strWhere = '';
$strSorting = '';
$arrWhere = array();
$arrValues = array();
$blnDefaultModuleSort = false;
$c = WMassArtsHub_ProfileItemModel::getTable();
// Sorting
if (Input::get('sorting'))
{
$arrSortField = explode('-', Input::get('sorting'));
// Needs to be a field value in the table and either be asc or desc
if (Database::getInstance()->fieldExists($arrSortField[0], $c) && (strtolower($arrSortField[1])=='asc' || strtolower($arrSortField[1])=='desc'))
{
$strSorting = $arrSortField[0] . ' ' . strtoupper($arrSortField[1]);
}
}
// Default sorting
if (!$strSorting && $this->artshub_profile_listingSortField && $this->artshub_profile_listingSortDirection)
{
$blnDefaultModuleSort = true;
$strSorting = $this->artshub_profile_listingSortField . ' ' . $this->artshub_profile_listingSortDirection;
}
$arrSortFields = array();
$arrSortValues = array();
// Keywords
if ($strKeywords = \trim(Input::get('keywords')))
{
$arrFields = StringUtil::deserialize($this->artshub_profile_searchFields, true);
if (\count($arrFields))
{
$where = array();
include_once(TL_ROOT . '/src/Rhyme/WMassArtsHub/Resources/contao/config/stopwords.php');
$arrKeywords = \array_map(array(FilterHelper::class, 'uncleanChars'), \explode(',', $strKeywords));
foreach ($arrKeywords as $keyword)
{
// Look for all words in the fields
$arrFinalKeywords = \explode(' ', $keyword);
foreach ($arrFinalKeywords as $finalKeyword)
{
$strTerm = \trim($finalKeyword);
if (empty($strTerm) || \in_array(\strtolower($strTerm), \array_map('strtolower', $GLOBALS['KEYWORD_STOP_WORDS'])) || in_array(strtolower($strTerm), array_map('strtolower', $GLOBALS['KEYWORD_STOP_WORDS'])))
{
continue;
}
foreach ($arrFields as $field)
{
$where[] = "$c.$field REGEXP ?";
$arrValues[] = $strTerm;
}
}
// Do relevance sorting
$intPriority = 1;
// See if the field matches the term exactly
foreach ($arrFields as $field)
{
$strWholeTerm = FilterHelper::uncleanChars($strKeywords);
if ($strWholeTerm)
{
$arrSortFields[] = "CASE WHEN TRIM($c.$field) LIKE ? THEN $intPriority ELSE 9999999999 END";
$arrSortValues[] = $strWholeTerm;
$intPriority++;
}
}
// Try to match as many terms as possible starting with the first
foreach ($arrFields as $field)
{
foreach ($arrFinalKeywords as $finalKeyword)
{
$strTerm = trim($finalKeyword);
if (empty($strTerm) || in_array(strtolower($strTerm), array_map('strtolower', $GLOBALS['KEYWORD_STOP_WORDS'])) || in_array(strtolower($strTerm), array_map('strtolower', $GLOBALS['KEYWORD_STOP_WORDS'])))
{
continue;
}
$arrSortFields[] = "CASE WHEN $c.$field REGEXP ? THEN $intPriority ELSE 9999999999 END";
$arrSortValues[] = $strTerm;
$intPriority++;
}
}
}
if (count($where))
{
$arrWhere['keywords'] = '('.implode(' OR ', $where).')';
}
}
}
// !HOOK: custom filter/sorting types
if (isset($GLOBALS['TL_HOOKS']['processProfileFiltersAndSorting']) && is_array($GLOBALS['TL_HOOKS']['processProfileFiltersAndSorting']))
{
foreach ($GLOBALS['TL_HOOKS']['processProfileFiltersAndSorting'] as $callback)
{
$objCallback = System::importStatic($callback[0]);
list($arrValues, $arrWhere, $strSorting) = $objCallback->{$callback[1]}($arrValues, $arrWhere, $strSorting, $this->findCategories(), $this);
}
}
// Do relevance sorting - todo: need a better way to do this so it can be passed to the hook
if (!$blnDefaultModuleSort && !$strSorting && !empty($arrSortFields) && !empty($arrSortValues))
{
$strSorting = implode(',', $arrSortFields);
foreach ($arrSortValues as $val)
{
$arrValues[] = $val;
}
}
// Sort by category if nothing else
$strSorting = $strSorting ?: "$c.sorting ".$this->artshub_profile_listingSortDirection;
// Now put together the entire WHERE
if(count($arrWhere) > 0)
{
$strWhere = implode(' AND ', $arrWhere);
}
return array($arrValues, $strWhere, $strSorting);
}
/**
* Find published products by condition
* @param mixed
* @param mixed
* @param array
* @return int
*/
public static function countPublishedBy($arrColumns, $arrValues, $arrOptions=array())
{
$c = WMassArtsHub_ProfileItemModel::getTable();
$arrValues = (array) $arrValues;
if (!is_array($arrColumns)) {
$arrColumns = array();
}
if (BE_USER_LOGGED_IN !== true) {
$arrColumns[] = "$c.published='1'";
}
$arrFind = array_merge(array
(
'table' => $c,
'column' => $arrColumns,
'value' => $arrValues,
), (array)$arrOptions);
$strQuery = QueryBuilder::find($arrFind);
$strQuery = static::replaceSectionsOfString($strQuery, "SELECT ", "FROM ", "SELECT $c.id FROM ", true, false);
$arrIDs = Database::getInstance()->prepare($strQuery)->execute($arrValues)->fetchEach('id');
// !HOOK: custom actions
if (isset($GLOBALS['TL_HOOKS']['passFoundProfileItems']) && is_array($GLOBALS['TL_HOOKS']['passFoundProfileItems']))
{
foreach ($GLOBALS['TL_HOOKS']['passFoundProfileItems'] as $callback)
{
$objCallback = System::importStatic($callback[0]);
$objCallback->{$callback[1]}($arrIDs);
}
}
return (int) count($arrIDs);
}
}