Current File : /home/aventura/www/site/wp-content/plugins/victheme_core/wordpress/element/wploop.php |
<?php
/**
* Object for simulating Wordpress Loop
* this object can handle :
*
* WP_QUERY OBJECT
* ===============
* 1. Direct WP_Query object injection via context
* with key query
*
* 2. Fallback to global $wp_query on non single page
* if no Direct WP Query Object defined and no
* queryArgs defined
*
* 3. Build a new WP_Query Object if it cannot use
* Direct or Global WP_Query object, provided
* user specify the WP_Query object args via
* queryArgs context.
*
* Without valid object, the class will not produce any markups
*
* AJAX
* ====
* This class is compatible with wp-ajax.js to invoke the integration
* witht wp-ajax, it will need the :
*
* $context['ajax'] = true;
*
* GRIDS
* =====
*
* The class will provide columns object for processing
* bootstrap object grids defined in context into a valid
* css class for the bootstrap grids.
*
* The object can be accessed via $this->getContext('object.columns')->getClass()
* when in template or directly injected to object when template is specified
* as a valid VTCore_Html_Base object (or its children).
*
*
* ISOTOPE
* =======
*
* This class is capable to invoking isotope js directly if user
* specifies the correct isotope options via context['isotope']
*
* Common Isotope options
* $context['data']['isotope-options'] = array(
* // This must be the same as the item defined in the template
* 'itemSelector' => '.item',
*
* // the VTCoreIsotopeTermIdFilter can be used
* // if you link this with WpTermList and put the matching data-term-id
* // attributes in the template
* 'filter' => 'VTCoreIsotopeTermIdFilter',
*
* // This probably the most used options
* 'layoutMode' => 'fitRows',
*
* // See isotope.metafizzy.co/options.html for more options
* );
*
* TEMPLATING
* ==========
*
* This class supports :
* 1. Pure php file as template
* 2. VTCore_Html_Base child objects as template
* 3. class name for valid VTCore_Html_Base as template
*
* user should define the template in the context :
*
* $context['template']['items'] = the template for items inside the loop
* $context['template']['empty'] = the template when loop found no posts.
*
* @method WpLoop
* @author jason.xie@victheme.com
*
*/
class VTCore_Wordpress_Element_WpLoop
extends VTCore_Wordpress_Models_Element {
protected $context = array(
'type' => 'div',
'attributes' => array(
'class' => array(
'wp-loop',
'row',
),
),
'id' => false,
'query' => false,
'queryMain' => false,
'queryArgs' => array(),
'ajax' => false,
'ajaxData' => array(
'ajax-mode' => 'selfData',
'ajax-object' => 'loop',
'ajax-loading-text' => 'Loading...',
'ajax-target' => false,
'ajax-action' => 'vtcore_ajax_framework',
'ajax-value' => 'loop',
'ajax-queue' => array(
'replace',
),
),
'grids' => array(
'columns' => array(
'mobile' => 12,
'tablet' => 6,
'small' => 4,
'large' => 3,
),
),
'data' => array(
'isotope-options' => false,
),
'template' => array(
'items' => false,
'empty' => false,
),
'custom' => array(),
'loopQuery' => array(),
// Allow user to disable the automated build
// process items via context args.
'process' => array(
'query' => true,
'filter' => true,
'isotope' => true,
'ajax' => true,
'loop' => true,
)
);
public $get;
public $post;
public $request;
public $metaQuery = array('relation' => 'OR');
public $taxQuery = array('relation' => 'OR');
public $ajaxContext;
protected $actionKey = 'vtcore_wordpress_loop_';
/**
* Overriding parent method
*
* This is where the main logic invocation
* for building the loop object.
*
* It is advisable to extends other method
* when trying to customize this object and
* leave this main logic intact if possible.
*
* @see VTCore_Html_Base::buildElement()
*/
public function buildElement() {
// Build the container grids, use the container.grids context array
$this->buildGrid();
// Process query when configured to do so
if ($this->getContext('process.query')) {
// Attempt to autodetect which query to use
$this->detectQueryObject();
do_action($this->actionKey . 'detect_query', $this);
// Break if no useful query object
if (!$this->getContext('query')) {
return $this;
}
}
// Do the query filtering for taxonomy, metas and pagination
if ($this->getContext('process.filter')) {
// Preprocess the query object
$this->preprocessQueryObject();
do_action($this->actionKey . 'process_query', $this);
// Finalize query object
$this->finalizeQueryObject();
do_action($this->actionKey . 'finalize_query', $this);
}
// Isotope auto loading mode
if ($this->getContext('data.isotope-options') && $this->getContext('process.isotope')) {
$this->prepareIsotope();
do_action($this->actionKey . 'prepare_isotope', $this);
}
// Ajax mode
if ($this->getContext('ajax') && $this->getContext('process.ajax')) {
$this->prepareAjax();
do_action($this->actionKey . 'prepare_ajax', $this);
}
// Build the markup using loop
if ($this->getContext('process.loop')) {
// Build the WP Loop
$this->prepareBeforeLoop();
do_action($this->actionKey . 'before_loop', $this);
// Looping
$this->doLoop();
do_action($this->actionKey . 'after_loop', $this);
}
return $this;
}
/**
* Method for building the grid css classes
* @return VTCore_Bootstrap_Element_Base
*/
public function buildGrid() {
// Processing Grids
if ($this->getContext('container.grids')) {
$grids = new VTCore_Bootstrap_Grid_Column($this->getContext('container.grids'));
$this->addClass($grids->getClass(), 'grids');
}
return $this;
}
/**
* This method will attempt to detect the correct
* query object. The detection is in this order :
*
* 1. Use the context query object if found
* 2. Fallback to main query if defined in the context
* and not on single page
* 3. Try to build new query object if the queryArgs
* is populated with valid query args.
*
* @return VTCore_Wordpress_Element_WpLoop
*/
protected function detectQueryObject() {
if (!$this->getContext('query')
|| $this->getContext('query') instanceof WP_Query == false) {
// Fallback to main query
// Only if not on single page
if ($this->getContext('queryMain')
&& !is_singular()) {
global $wp_query;
$this->addContext('query', $wp_query);
}
// Try to build new query
elseif ($this->getContext('queryArgs')) {
// Detect if the array is taken directly from query form results
$this->maybeConvertQueryArgs();
$this->addContext('query', new WP_Query($this->getContext('queryArgs')));
}
}
// Set the id marker to the query object
// This will be available on pre_get_posts hook.
if ($this->getContext('id') && $this->getContext('query')) {
$this->getContext('query')->set('vtcore_queryid', $this->getContext('id'));
}
// Set the object marker for the query object
// This will be available on pre_get_posts hook
if ($this->getContext('query')) {
$this->getContext('query')->set('vtcore_object', 'wploop');
}
return $this;
}
/**
* Preprocess query object
* This is useful for adding dynamic query
* variables such as for sane pagination
* or dynamic filter.
*
* If need more filtering, please extend
* the method in an extended class.
*
* Always fill the $metaQuery and $taxQuery
* for metafield and taxonomy query.
*
*/
protected function preprocessQueryObject() {
$this->get = filter_input_array(INPUT_GET, FILTER_SANITIZE_STRING);
$this->post = filter_input_array(INPUT_POST, FILTER_SANITIZE_STRING);
$this->request = VTCore_Wordpress_Utility::arrayMergeRecursiveDistinct($this->post, $this->request);
if ($this->getContext('loopQuery')) {
$this->request = VTCore_Wordpress_Utility::arrayMergeRecursiveDistinct($this->request, (array) $this->getContext('loopQuery'));
}
// Processing pager
// this has to be linked with wppager to
// work properly and both object context id
// must be the same
if ($this->getContext('id')
&& isset($this->get['paged-' . $this->getContext('id')])
&& is_numeric($this->get['paged-' . $this->getContext('id')])) {
$this->getContext('query')->set('paged', $this->get['paged-' . $this->getContext('id')]);
}
// Processing WpTermList
// This must be linked to WpTermLIst object
// to get the term_id
if (isset($this->get['term-' . $this->getContext('id')])
&& is_numeric($this->get['term-' . $this->getContext('id')])
&& !empty($this->get['term-' . $this->getContext('id')])
&& isset($this->get['tax-' . $this->getContext('id')])) {
$this->taxQuery[] = array(
'taxonomy' => $this->get['tax-' . $this->getContext('id')],
'field' => 'term_id',
'terms' => (int) $this->get['term-' . $this->getContext('id')],
);
}
return $this;
}
/**
* Finalizing the query arguments
*/
protected function finalizeQueryObject() {
// Inject the meta and tax query
if (count($this->metaQuery) > 1) {
$this->getContext('query')->set('meta_query', $this->metaQuery);
}
// Inject the tax and tax query
if (count($this->taxQuery) > 1) {
$this->getContext('query')->set('tax_query', $this->taxQuery);
}
// Refresh the query object
$this->getContext('query')->query($this->getContext('query')->query_vars);
// Custom data attributes for helping
// with fancy pagination
$this->addData('max-pagination', $this->getContext('query')->max_num_pages);
$this->addData('current-page', max(1, $this->getContext('query')->query_vars['paged']));
// Mark as last page
if ($this->getData('max-pagination') == $this->getData('current-page')) {
$this->addContext('lastpage', true);
}
// User may have posted something for this loop
// mark in the query object so other VTCore element such as pagination
// can forward the request.
if (!empty($this->request)) {
$this->getContext('query')->loopRequest = $this->request;
$this->addContext('loopQuery', $this->request);
}
return $this;
}
/**
* Helper function for detecting template.
* Detection sequence :
* 1. Valid PHP file - include the file
* 2. Valid HTML Object - direct include
* 3. Valid HTML class - build new object and direct include
*/
protected function buildTemplate($template) {
global $post;
if (is_string($template)
&& strpos($template, '.php') !== false) {
// Template can access $this for retrieiving context
// and information.
ob_start();
include VTCore_Wordpress_Utility::locateTemplate($template);
$content = ob_get_clean();
$this->addChildren($content);
}
// Direct inject if template is VTCore object
elseif (is_object($template)
&& $template instanceof VTCore_Html_Base) {
$template->addContext('grids', $this->getContext('grids'));
$this->addChildren($template);
}
// Try to build a new object if the template
// is a class name.
elseif (class_exists($template, true)) {
$this->addChildren(new $template(array('grids' => $this->getContext('grids'))));
}
return $this;
}
/**
* Intersect context for retrieving only
* allowed value to be passed via ajax
*/
protected function preProcessAjaxContext() {
$this->ajaxContext = array_intersect_key($this->getContexts(), array_flip(array(
'type',
'attributes',
'id',
'queryArgs',
'ajax',
'ajaxData',
'grids',
'data',
'template',
'custom',
'loopQuery',
)));
return $this;
}
/**
* Method for adding extra markup to integrate
* with wp-ajax
*
* @see VTCore_Wordpress_Ajax_Processor_Loop
*/
protected function prepareAjax() {
VTCore_Wordpress_Utility::loadAsset('jquery-viewport');
VTCore_Wordpress_Utility::loadAsset('wp-ajax');
VTCore_Wordpress_Utility::loadAsset('wp-loop');
$args = $this->getContext('query')->query_vars;
$args = array_filter($args);
$this
->addContext('queryArgs', $args)
->preProcessAjaxContext()
->addData('context', base64_encode(serialize($this->ajaxContext)))
->addData('ajax-type', 'loop')
->addData('nonce', wp_create_nonce('vtcore-ajax-nonce-admin'))
->addClass('btn-ajax-content')
->addData('ajax-marker', array(
'id' => $this->getContext('id'),
'mode' => 'wp-loop',
));
foreach ($this->getContext('ajaxData') as $key => $data) {
$this->addData($key, $data);
}
return $this;
}
/**
* Prepare object for isotope integration
*/
protected function prepareIsotope() {
VTCore_Wordpress_Utility::loadAsset('jquery-isotope');
$this->addClass('js-isotope');
if ($this->getContext('ajax')) {
$this->addData('ajax-marker', array(
'isotope' => true,
'id' => $this->getContext('id'),
'mode' => 'wp-loop',
));
// @bugfix pagination breaks isotope!
$this->addContext('ajaxData.ajax-marker', array(
'isotope' => true,
'id' => $this->getContext('id'),
'mode' => 'wp-loop',
));
}
return $this;
}
/**
* Final preparation before the object
* build the loops, extend this method
* if you need additional logic to be
* performed before loops starts.
*/
protected function prepareBeforeLoop() {
parent::buildElement();
$this
->addContext('objects.columns', new VTCore_Bootstrap_Grid_Column($this->getContext('grids')))
->addData('arrival', $this->getContext('id'));
return $this;
}
/**
* Method for creating wordpress loop and
* performing the standard wordpress loop
*
* This method will also inject the template
* and / or vtcore objects
*/
protected function doLoop() {
if ($this->getContext('query')->have_posts()) {
global $post;
$delta = 1;
while ($this->getContext('query')->have_posts()) {
$this->getContext('query')->the_post();
$post->post_delta = $delta;
$this->buildTemplate($this->getContext('template.items'));
$delta++;
// @since 1.7.34, use this to clear any object that can cause memory leaks!
do_action($this->actionKey . 'before_next_post', $post);
}
// Reset back stupid global post
wp_reset_postdata();
}
// Nothing found, fallback to empty message
else {
$this->buildTemplate($this->getContext('template.empty'));
}
return $this;
}
/**
* Try and detect if the array is taken directly
* from wpquery form and convert them into valid
* WP_Query array arguments.
*/
protected function maybeConvertQueryArgs() {
// Process the grouped posts parameter
if ($this->getContext('queryArgs.posts')) {
foreach ((array) $this->getContext('queryArgs.posts') as $key => $value) {
$this->addContext('queryArgs.' . $key, $value);
}
$this->removeContext('queryArgs.posts');
}
// Process the grouped author parameters
if ($this->getContext('queryArgs.authors')) {
foreach ((array) $this->getContext('queryArgs.authors') as $key => $value) {
$this->addContext('queryArgs.' . $key, $value);
}
}
// Process the grouped orders parameters
if ($this->getContext('queryArgs.orders')) {
foreach ((array) $this->getContext('queryArgs.orders') as $key => $value) {
// Custom orderby
if ($key == 'orderby') {
if (strpos($value, 'metanum_') !== false) {
$meta = str_replace('metanum_', '', $value);
$value = 'meta_value_num';
$this->addContext('queryArgs.meta_key', $meta);
}
if (strpos($value, 'metaval_') !== false) {
$meta = str_replace('metaval_', '', $value);
$value = 'meta_value';
$this->addContext('queryArgs.meta_key', $meta);
}
}
$this->addContext('queryArgs.' . $key, $value);
}
}
// Process the grouped pagination parameters
if ($this->getContext('queryArgs.pagination')) {
foreach ((array) $this->getContext('queryArgs.pagination') as $key => $value) {
if (!$this->getContext('queryArgs.' . $key)) {
$this->addContext('queryArgs.' . $key, $value);
}
}
}
// Process the grouped taxonomy parameters
if ($this->getContext('queryArgs.taxonomy')) {
foreach ((array) $this->getContext('queryArgs.taxonomy') as $key => $data) {
if (is_numeric($key)) {
if (!empty($data['terms'])
&& !empty($data['taxonomy'])) {
$this->addContext('queryArgs.tax_query.' . $key, $data);
}
}
else {
$this->addContext('queryArgs.tax_query.' . $key, $data);
}
}
}
// Process the grouped metas parameter
if ($this->getContext('queryArgs.meta')) {
foreach ((array) $this->getContext('queryArgs.meta') as $key => $data) {
if (is_numeric($key)) {
if (!empty($data['key'])
&& !empty($data['value'])) {
$this->addContext('queryArgs.meta_query.' . $key, $data);
}
}
else {
$this->addContext('queryArgs.meta_query.' . $key, $data);
}
}
}
return $this;
}
}