class-fl-builder-loop.php 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047
  1. <?php
  2. /**
  3. * Helper class for building custom WordPress loops.
  4. *
  5. * @since 1.2.3
  6. */
  7. final class FLBuilderLoop {
  8. /**
  9. * Loop query counter
  10. *
  11. * @since 1.9.5
  12. * @var int $loop_counter
  13. */
  14. static public $loop_counter = 0;
  15. /**
  16. * Custom pagination regex base.
  17. *
  18. * @since 1.10.7
  19. * @var string
  20. */
  21. static public $paged_regex_base = 'paged-[0-9]{1,}';
  22. /**
  23. * Cache the custom pagination data.
  24. * Format:
  25. * array(
  26. * 'current_page' => '',
  27. * 'current_loop' => '',
  28. * 'paged' => ''
  29. * )
  30. *
  31. * @since 1.10.7
  32. * @var array
  33. */
  34. static public $custom_paged_data = array();
  35. /**
  36. * Flag for flushing post type rewrite rules.
  37. *
  38. * @since 1.10.7
  39. * @var bool
  40. */
  41. static private $_rewrote_post_type = false;
  42. /**
  43. * Flag for flushing taxonomy rewrite rules.
  44. *
  45. * @since 1.10.7
  46. * @var bool
  47. */
  48. static private $_rewrote_taxonomy = false;
  49. /**
  50. * Initializes hooks.
  51. *
  52. * @since 1.8
  53. * @return void
  54. */
  55. static public function init() {
  56. // Actions
  57. add_action( 'fl_builder_before_control_suggest', __CLASS__ . '::render_match_select', 10, 4 );
  58. add_action( 'init', __CLASS__ . '::init_rewrite_rules', 20 );
  59. add_action( 'fl_builder_activated', __CLASS__ . '::init_rewrite_rules', 10 );
  60. add_action( 'registered_post_type', __CLASS__ . '::post_type_rewrite_rules', 10, 2 );
  61. add_action( 'registered_taxonomy', __CLASS__ . '::taxonomy_rewrite_rules', 10, 3 );
  62. add_action( 'wp_loaded', __CLASS__ . '::flush_rewrite_rules', 1 );
  63. // Filters
  64. add_filter( 'found_posts', __CLASS__ . '::found_posts', 1, 2 );
  65. add_filter( 'redirect_canonical', __CLASS__ . '::override_canonical', 1, 2 );
  66. add_filter( 'pre_handle_404', __CLASS__ . '::pre_404_pagination', 1, 2 );
  67. add_filter( 'paginate_links', __CLASS__ . '::filter_paginate_links', 1 );
  68. }
  69. /**
  70. * Returns either a clone of the main query or a new instance of
  71. * WP_Query based on the provided module settings.
  72. *
  73. * @since 1.2.3
  74. * @param object $settings Module settings to use for the query.
  75. * @return object A WP_Query instance.
  76. */
  77. static public function query( $settings ) {
  78. $settings = apply_filters( 'fl_builder_loop_before_query_settings', $settings );
  79. do_action( 'fl_builder_loop_before_query', $settings );
  80. // Count how many times this method has been called
  81. self::$loop_counter++;
  82. if ( isset( $settings->data_source ) && 'main_query' == $settings->data_source ) {
  83. $query = self::main_query();
  84. } else {
  85. $query = self::custom_query( $settings );
  86. }
  87. do_action( 'fl_builder_loop_after_query', $settings );
  88. return apply_filters( 'fl_builder_loop_query', $query, $settings );
  89. }
  90. /**
  91. * Returns new instance query if we have multiple paginations on a page.
  92. * Else, returns a clone of the main query with the post data reset.
  93. *
  94. * @since 1.10
  95. * @return object A WP_Query instance.
  96. */
  97. static public function main_query() {
  98. global $wp_query, $wp_the_query;
  99. // Setup a new WP_Query instance if we have multiple paginations on a page.
  100. if ( self::$loop_counter > 1 || $wp_the_query->is_singular( 'fl-theme-layout' ) ) {
  101. $query_args = $wp_query->query_vars;
  102. $query_args['paged'] = self::get_paged();
  103. $query_args['fl_original_offset'] = 0;
  104. $query_args['fl_builder_loop'] = true;
  105. $query = new WP_Query( $query_args );
  106. } else {
  107. $query = clone $wp_query;
  108. $query->rewind_posts();
  109. $query->reset_postdata();
  110. }
  111. return $query;
  112. }
  113. /**
  114. * Returns a new instance of WP_Query based on
  115. * the provided module settings.
  116. *
  117. * @since 1.10
  118. * @param object $settings Module settings to use for the query.
  119. * @return object A WP_Query instance.
  120. */
  121. static public function custom_query( $settings ) {
  122. global $post;
  123. $posts_per_page = empty( $settings->posts_per_page ) ? 10 : $settings->posts_per_page;
  124. $post_type = empty( $settings->post_type ) ? 'post' : $settings->post_type;
  125. $order_by = empty( $settings->order_by ) ? 'date' : $settings->order_by;
  126. $order = empty( $settings->order ) ? 'DESC' : $settings->order;
  127. $users = empty( $settings->users ) ? '' : $settings->users;
  128. $fields = empty( $settings->fields ) ? '' : $settings->fields;
  129. $exclude_self = '';
  130. if ( isset( $settings->exclude_self ) && 'yes' == $settings->exclude_self ) {
  131. $exclude_self = $post->ID;
  132. }
  133. $paged = self::get_paged();
  134. // Get the offset.
  135. if ( ! isset( $settings->offset ) || ! is_int( (int) $settings->offset ) ) {
  136. $offset = 0;
  137. } else {
  138. $offset = $settings->offset;
  139. }
  140. // Get the paged offset.
  141. if ( $paged < 2 ) {
  142. $paged_offset = $offset;
  143. } else {
  144. $paged_offset = $offset + ( ( $paged - 1 ) * $posts_per_page );
  145. }
  146. // Build the query args.
  147. $args = array(
  148. 'paged' => $paged,
  149. 'posts_per_page' => $posts_per_page,
  150. 'post_type' => $post_type,
  151. 'orderby' => $order_by,
  152. 'order' => $order,
  153. 'tax_query' => array(
  154. 'relation' => 'AND',
  155. ),
  156. 'ignore_sticky_posts' => true,
  157. 'offset' => $paged_offset,
  158. 'fl_original_offset' => $offset,
  159. 'fl_builder_loop' => true,
  160. 'fields' => $fields,
  161. 'settings' => $settings,
  162. );
  163. // Order by meta value arg.
  164. if ( strstr( $order_by, 'meta_value' ) ) {
  165. $args['meta_key'] = $settings->order_by_meta_key;
  166. }
  167. // Order by author
  168. if ( 'author' == $order_by ) {
  169. $args['orderby'] = array(
  170. 'author' => $order,
  171. 'date' => $order,
  172. );
  173. }
  174. // Build the author query.
  175. if ( ! empty( $users ) ) {
  176. if ( is_string( $users ) ) {
  177. $users = explode( ',', $users );
  178. }
  179. $arg = 'author__in';
  180. // Set to NOT IN if matching is present and set to 0.
  181. if ( isset( $settings->users_matching ) && ! $settings->users_matching ) {
  182. $arg = 'author__not_in';
  183. }
  184. $args[ $arg ] = $users;
  185. }
  186. // Build the taxonomy query.
  187. $taxonomies = self::taxonomies( $post_type );
  188. foreach ( $taxonomies as $tax_slug => $tax ) {
  189. $tax_value = '';
  190. $term_ids = array();
  191. $operator = 'IN';
  192. // Get the value of the suggest field.
  193. if ( isset( $settings->{'tax_' . $post_type . '_' . $tax_slug} ) ) {
  194. // New style slug.
  195. $tax_value = $settings->{'tax_' . $post_type . '_' . $tax_slug};
  196. } elseif ( isset( $settings->{'tax_' . $tax_slug} ) ) {
  197. // Old style slug for backwards compat.
  198. $tax_value = $settings->{'tax_' . $tax_slug};
  199. }
  200. // Get the term IDs array.
  201. if ( ! empty( $tax_value ) ) {
  202. $term_ids = explode( ',', $tax_value );
  203. }
  204. // Handle matching settings.
  205. if ( isset( $settings->{'tax_' . $post_type . '_' . $tax_slug . '_matching'} ) ) {
  206. $tax_matching = $settings->{'tax_' . $post_type . '_' . $tax_slug . '_matching'};
  207. if ( ! $tax_matching ) {
  208. // Do not match these terms.
  209. $operator = 'NOT IN';
  210. } elseif ( 'related' === $tax_matching ) {
  211. // Match posts by related terms from the global post.
  212. global $post;
  213. $terms = wp_get_post_terms( $post->ID, $tax_slug );
  214. $related = array();
  215. foreach ( $terms as $term ) {
  216. if ( ! in_array( $term->term_id, $term_ids ) ) {
  217. $related[] = $term->term_id;
  218. }
  219. }
  220. if ( empty( $related ) ) {
  221. // If no related terms, match all except those in the suggest field.
  222. $operator = 'NOT IN';
  223. } else {
  224. // Don't include posts with terms selected in the suggest field.
  225. $args['tax_query'][] = array(
  226. 'taxonomy' => $tax_slug,
  227. 'field' => 'id',
  228. 'terms' => $term_ids,
  229. 'operator' => 'NOT IN',
  230. );
  231. // Set the term IDs to the related terms.
  232. $term_ids = $related;
  233. }
  234. }
  235. }
  236. if ( ! empty( $term_ids ) ) {
  237. $args['tax_query'][] = array(
  238. 'taxonomy' => $tax_slug,
  239. 'field' => 'id',
  240. 'terms' => $term_ids,
  241. 'operator' => $operator,
  242. );
  243. }
  244. }
  245. // Post in/not in query.
  246. if ( isset( $settings->{'posts_' . $post_type} ) ) {
  247. $ids = $settings->{'posts_' . $post_type};
  248. $arg = 'post__in';
  249. // Set to NOT IN if matching is present and set to 0.
  250. if ( isset( $settings->{'posts_' . $post_type . '_matching'} ) ) {
  251. if ( ! $settings->{'posts_' . $post_type . '_matching'} ) {
  252. $arg = 'post__not_in';
  253. }
  254. }
  255. // Add the args if we have IDs.
  256. if ( ! empty( $ids ) ) {
  257. $args[ $arg ] = explode( ',', $settings->{'posts_' . $post_type} );
  258. }
  259. }
  260. if ( $exclude_self ) {
  261. $args['post__not_in'][] = $exclude_self;
  262. }
  263. /**
  264. * Filter all the args passed to WP_Query.
  265. * @see fl_builder_loop_query_args
  266. * @link https://kb.wpbeaverbuilder.com/article/591-create-a-filter-to-customize-the-display-of-post-data
  267. */
  268. $args = apply_filters( 'fl_builder_loop_query_args', $args );
  269. // Build the query.
  270. $query = new WP_Query( $args );
  271. // Return the query.
  272. return $query;
  273. }
  274. /**
  275. * Called by the found_posts filter to adjust the number of posts
  276. * found based on the user defined offset.
  277. *
  278. * @since 1.2.3
  279. * @param int $found_posts The number of found posts.
  280. * @param object $query An instance of WP_Query.
  281. * @return int
  282. */
  283. static public function found_posts( $found_posts, $query ) {
  284. if ( isset( $query->query ) && isset( $query->query['fl_builder_loop'] ) ) {
  285. return (int) $found_posts - (int) $query->query['fl_original_offset'];
  286. }
  287. return $found_posts;
  288. }
  289. /**
  290. * Add rewrite rules for custom pagination that allows post modules
  291. * on the same page to be paged independently.
  292. *
  293. * @since 1.9.5
  294. * @return void
  295. */
  296. static public function init_rewrite_rules() {
  297. $fronts = self::get_rewrite_fronts();
  298. $paged_regex = self::$paged_regex_base;
  299. $flpaged_rules = array(
  300. // Category archive
  301. $fronts['category'] . '/(.+?)/' . $paged_regex . '/?([0-9]{1,})/?$' => 'index.php?category_name=$matches[1]&flpaged=$matches[2]',
  302. // Tag archive
  303. $fronts['tag'] . '/([^/]+)/' . $paged_regex . '/?([0-9]{1,})/?$' => 'index.php?tag=$matches[1]&flpaged=$matches[2]',
  304. // Year archive
  305. $fronts['date'] . '([0-9]{4})/' . $paged_regex . '/?([0-9]{1,})/?$' => 'index.php?year=$matches[1]&flpaged=$matches[2]',
  306. // Year/month archive
  307. $fronts['date'] . '([0-9]{4})/([0-9]{1,2})/' . $paged_regex . '/?([0-9]{1,})/?$' => 'index.php?year=$matches[1]&monthnum=$matches[2]&flpaged=$matches[3]',
  308. // Day archive
  309. $fronts['date'] . '([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/' . $paged_regex . '/?([0-9]{1,})/?$' => 'index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&flpaged=$matches[4]',
  310. // Author archive
  311. $fronts['author'] . '([^/]+)/' . $paged_regex . '/?([0-9]{1,})/?$' => 'index.php?author_name=$matches[1]&flpaged=$matches[2]',
  312. // Post single - Numeric permastruct (/archives/%post_id%)
  313. $fronts['default'] . '([0-9]+)/' . $paged_regex . '/?([0-9]{1,})/?$' => 'index.php?p=$matches[1]&flpaged=$matches[2]',
  314. // Page
  315. '(.?.+?)/' . $paged_regex . '/?([0-9]{1,})/?$' => 'index.php?pagename=$matches[1]&flpaged=$matches[2]',
  316. // Post single
  317. '(.+?)/' . $paged_regex . '/?([0-9]{1,})/?$' => 'index.php?name=$matches[1]&flpaged=$matches[2]',
  318. );
  319. // Frontpage static
  320. if ( get_option( 'page_on_front' ) ) {
  321. $flpaged_rules[ $paged_regex . '/([0-9]*)/?' ] = 'index.php?page_id=' . get_option( 'page_on_front' ) . '&flpaged=$matches[1]';
  322. }
  323. // Generic Rule for Homepage / Search
  324. $flpaged_rules[ $paged_regex . '/?([0-9]{1,})/?$' ] = 'index.php?&flpaged=$matches[1]';
  325. foreach ( $flpaged_rules as $regex => $redirect ) {
  326. add_rewrite_rule( $regex, $redirect, 'top' );
  327. }
  328. add_rewrite_tag( '%flpaged%', '([^&]+)' );
  329. }
  330. /**
  331. * Get the rewrite front for the generic rules.
  332. *
  333. * @since 1.10.7
  334. * @return array
  335. */
  336. static public function get_rewrite_fronts() {
  337. global $wp_rewrite;
  338. $front = substr( $wp_rewrite->front, 1 );
  339. $category_base = get_option( 'category_base' );
  340. if ( ! $category_base ) {
  341. $category_base = $front . 'category';
  342. }
  343. $tag_base = get_option( 'tag_base' );
  344. if ( ! $tag_base ) {
  345. $tag_base = $front . 'tag';
  346. }
  347. $date_base = $front;
  348. if ( strpos( $wp_rewrite->permalink_structure, '%post_id%' ) !== false ) {
  349. $date_base = $front . 'date/';
  350. }
  351. $author_base = $front . $wp_rewrite->author_base . '/';
  352. return array(
  353. 'category' => $category_base,
  354. 'tag' => $tag_base,
  355. 'date' => $date_base,
  356. 'author' => $author_base,
  357. 'default' => $front,
  358. );
  359. }
  360. /**
  361. * Adding custom rewrite rules for the current post type pagination.
  362. *
  363. * @param string $post_type
  364. * @param array $args
  365. * @since 1.10.7
  366. * @return void
  367. */
  368. static public function post_type_rewrite_rules( $post_type, $args ) {
  369. global $wp_rewrite;
  370. if ( $args->_builtin or ! $args->publicly_queryable ) {
  371. return;
  372. }
  373. if ( false === $args->rewrite ) {
  374. return;
  375. }
  376. // Get our custom pagination if sets.
  377. $custom_paged = self::get_custom_paged();
  378. if ( ! $custom_paged || empty( $custom_paged ) || ! isset( $custom_paged['current_page'] ) ) {
  379. return;
  380. }
  381. $is_single = false;
  382. // Check if it's a CPT archive or CPT single.
  383. if ( $custom_paged['current_page'] != $post_type ) {
  384. // Is a child post of the current post type?
  385. $post_object = get_page_by_path( $custom_paged['current_page'], OBJECT, $post_type );
  386. if ( $post_object ) {
  387. $is_single = true;
  388. } else {
  389. return;
  390. }
  391. }
  392. $slug = $args->rewrite['slug'];
  393. if ( is_string( $args->has_archive ) ) {
  394. $slug = $args->has_archive;
  395. }
  396. if ( $args->rewrite['with_front'] ) {
  397. $slug = substr( $wp_rewrite->front, 1 ) . $slug;
  398. }
  399. // Append $custom_paged[ 'current_page' ] to slug if it's single.
  400. if ( $is_single ) {
  401. $regex = $slug . '/' . $custom_paged['current_page'] . '/' . self::$paged_regex_base . '/?([0-9]{1,})/?$';
  402. $redirect = 'index.php?post_type=' . $post_type . '&name=' . $custom_paged['current_page'] . '&flpaged=$matches[1]';
  403. } else {
  404. $regex = $slug . '/' . self::$paged_regex_base . '/?([0-9]{1,})/?$';
  405. $redirect = 'index.php?post_type=' . $post_type . '&flpaged=$matches[1]';
  406. }
  407. add_rewrite_rule( $regex, $redirect, 'top' );
  408. // Set true for flushing.
  409. self::$_rewrote_post_type = true;
  410. }
  411. /**
  412. * Adding custom rewrite rules for taxonomy pagination.
  413. *
  414. * @param string $taxonomy
  415. * @param string $object_type
  416. * @param array $args
  417. * @since 1.10.7
  418. * @return void
  419. */
  420. static public function taxonomy_rewrite_rules( $taxonomy, $object_type, $args ) {
  421. global $wp_rewrite;
  422. // For 4.7
  423. $args = (array) $args;
  424. if ( ! empty( $args['_builtin'] ) ) {
  425. return;
  426. }
  427. if ( false === $args['rewrite'] ) {
  428. return;
  429. }
  430. // Get our custom pagination request data.
  431. $custom_paged = self::get_custom_paged();
  432. // Taxonomy checks.
  433. if ( empty( $custom_paged['parent_page'] ) || ! isset( $custom_paged['parent_page'] ) ) {
  434. return;
  435. }
  436. // Term checks.
  437. if ( ! $custom_paged || empty( $custom_paged ) || ! isset( $custom_paged['current_page'] ) ) {
  438. return;
  439. }
  440. // Add rewrite to the registered tax only.
  441. if ( isset( $custom_paged['parent_page'] ) && $custom_paged['parent_page'] != $taxonomy ) {
  442. return;
  443. }
  444. // Make sure we have a valid term.
  445. if ( ! term_exists( $custom_paged['current_page'], $taxonomy ) ) {
  446. return;
  447. }
  448. if ( 'category' == $taxonomy ) {
  449. $taxonomy_slug = ( $cb = get_option( 'category_base' ) ) ? $cb : $taxonomy; // @codingStandardsIgnoreLine
  450. $taxonomy_key = 'category_name';
  451. } else {
  452. if ( isset( $args['rewrite']['slug'] ) ) {
  453. $taxonomy_slug = $args['rewrite']['slug'];
  454. } else {
  455. $taxonomy_slug = $taxonomy;
  456. }
  457. $taxonomy_key = $taxonomy;
  458. }
  459. if ( $args['rewrite']['with_front'] ) {
  460. $taxonomy_slug = substr( $wp_rewrite->front, 1 ) . $taxonomy_slug;
  461. }
  462. $rules = array(
  463. // Year
  464. '%s/(.+?)/date/([0-9]{4})/' . self::$paged_regex_base . '/?([0-9]{1,})/?$' => "index.php?{$taxonomy_key}=\$matches[1]&year=\$matches[2]&flpaged=\$matches[3]",
  465. // Month
  466. '%s/(.+?)/date/([0-9]{4})/([0-9]{1,2})/' . self::$paged_regex_base . '/?([0-9]{1,})/?$' => "index.php?{$taxonomy_key}=\$matches[1]&year=\$matches[2]&monthnum=\$matches[3]&flpaged=\$matches[4]",
  467. // Day
  468. '%s/(.+?)/date/([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/' . self::$paged_regex_base . '/?([0-9]{1,})/?$' => "index.php?{$taxonomy_key}=\$matches[1]&year=\$matches[2]&monthnum=\$matches[3]&day=\$matches[4]&flpaged=\$matches[5]",
  469. // Tax Archive
  470. '%s/(.+?)/' . self::$paged_regex_base . '/?([0-9]{1,})/?$' => "index.php?{$taxonomy_key}=\$matches[1]&flpaged=\$matches[2]",
  471. );
  472. foreach ( $rules as $regex => $redirect ) {
  473. $regex = sprintf( $regex, "{$taxonomy_slug}" );
  474. add_rewrite_rule( $regex, $redirect, 'top' );
  475. }
  476. // Set 'true' for flushing.
  477. self::$_rewrote_taxonomy = true;
  478. }
  479. /**
  480. * Flush rewrite rules ONLY when necessary.
  481. *
  482. * @since 1.10.7
  483. * @return void
  484. */
  485. static public function flush_rewrite_rules() {
  486. global $wp_rewrite;
  487. if ( self::$_rewrote_post_type || self::$_rewrote_taxonomy ) {
  488. // Need to flush (soft) so our custom rules will work.
  489. $wp_rewrite->flush_rules( false );
  490. }
  491. self::$_rewrote_post_type = false;
  492. self::$_rewrote_taxonomy = false;
  493. }
  494. /**
  495. * Disable canonical redirection on the frontpage when query var 'flpaged' is found.
  496. *
  497. * Disable canonical on supported CPT single.
  498. *
  499. * @param string $redirect_url The redirect URL.
  500. * @param string $requested_url The requested URL.
  501. * @since 1.9.5
  502. * @return bool|string
  503. */
  504. static public function override_canonical( $redirect_url, $requested_url ) {
  505. global $wp_the_query;
  506. if ( is_array( $wp_the_query->query ) ) {
  507. foreach ( $wp_the_query->query as $key => $value ) {
  508. if ( strpos( $key, 'flpaged' ) === 0 && is_page() && get_option( 'page_on_front' ) ) {
  509. $redirect_url = false;
  510. break;
  511. }
  512. }
  513. // Disable canonical on single post pagination for all post types.
  514. if ( true === $wp_the_query->is_singular
  515. && - 1 == $wp_the_query->current_post
  516. && true === $wp_the_query->is_paged
  517. ) {
  518. $redirect_url = false;
  519. }
  520. }
  521. return $redirect_url;
  522. }
  523. /**
  524. * Theme Builder support - Check to see if current page has Themer layout.
  525. * Short-circuit default header status handling when paginating on themer layout content.
  526. *
  527. * @param bool $prevent_404 Whether to short-circuit default header status handling. Default false.
  528. * @param object $query WP Query object.
  529. * @since 1.10.7
  530. * @return bool
  531. */
  532. static public function pre_404_pagination( $prevent_404, $query ) {
  533. global $wp_actions;
  534. if ( ! class_exists( 'FLThemeBuilder' ) ) {
  535. return $prevent_404;
  536. }
  537. if ( ! $query->is_paged ) {
  538. return false;
  539. }
  540. if ( ! $query->is_archive && ! $query->is_home ) {
  541. return false;
  542. }
  543. if ( $query->is_archive && $query->is_category && $query->post_count < 1 ) {
  544. return false;
  545. }
  546. $is_global_hack = false;
  547. $layout_type = '';
  548. // Manually set globals since filter `pre_handle_404`
  549. // doesn't reach `$wp_query->register_globals()`.
  550. if ( ! isset( $wp_actions['wp'] ) ) {
  551. // This would prevent from throwing PHP Notice
  552. // from FLThemeBuilderRulesLocation::get_current_page_location().
  553. $wp_actions['wp'] = 1;
  554. if ( $query->is_post_type_archive ) {
  555. $post = new stdClass();
  556. $post->post_type = $query->get( 'post_type' );
  557. $GLOBALS['post'] = $post;
  558. }
  559. $is_global_hack = true;
  560. }
  561. if ( FLThemeBuilder::has_layout() ) {
  562. // Reset the hacks.
  563. if ( $is_global_hack ) {
  564. unset( $wp_actions['wp'] );
  565. $GLOBALS['post'] = null;
  566. }
  567. return true;
  568. }
  569. return false;
  570. }
  571. /**
  572. * Builds and renders the pagination for a query.
  573. *
  574. * @since 1.2.3
  575. * @param object $query An instance of WP_Query.
  576. * @return void
  577. */
  578. static public function pagination( $query ) {
  579. $total_pages = $query->max_num_pages;
  580. $permalink_structure = get_option( 'permalink_structure' );
  581. $paged = self::get_paged();
  582. $base = html_entity_decode( get_pagenum_link() );
  583. if ( $total_pages > 1 ) {
  584. if ( ! $current_page = $paged ) { // @codingStandardsIgnoreLine
  585. $current_page = 1;
  586. }
  587. $base = self::build_base_url( $permalink_structure, $base );
  588. $format = self::paged_format( $permalink_structure, $base );
  589. echo paginate_links(array(
  590. 'base' => $base . '%_%',
  591. 'format' => $format,
  592. 'current' => $current_page,
  593. 'total' => $total_pages,
  594. 'type' => 'list',
  595. ));
  596. }
  597. }
  598. /**
  599. * Fix our custom pagination link on the single post when permalink structure is set to Plain or default.
  600. * For some reason WP automatically appending URL parameters to each page link.
  601. *
  602. * @param string $link Pagination link
  603. * @since 1.10.7
  604. * @return string
  605. */
  606. static public function filter_paginate_links( $link ) {
  607. $permalink_structure = get_option( 'permalink_structure' );
  608. $base = html_entity_decode( get_pagenum_link() );
  609. if ( empty( $permalink_structure ) && strrpos( $base, 'paged-' ) ) {
  610. // Compare $link with the current 'paged-' parameter.
  611. $base_params = wp_parse_url( $base, PHP_URL_QUERY );
  612. wp_parse_str( $base_params, $base_args );
  613. $current_paged_args = array_values( preg_grep( '/^paged-(\d+)/', array_keys( $base_args ) ) );
  614. if ( ! empty( $current_paged_args ) ) {
  615. $current_flpaged = $current_paged_args[0];
  616. $current_paged_param = $current_flpaged . '=' . $base_args[ $current_flpaged ];
  617. $link_params = wp_parse_url( $link, PHP_URL_QUERY );
  618. $link_params = str_replace( $current_paged_param, '' , $link_params );
  619. wp_parse_str( $link_params, $link_args );
  620. $link = strtok( $link, '?' );
  621. $link = add_query_arg( $link_args, $link );
  622. }
  623. }
  624. return $link;
  625. }
  626. /**
  627. * Build base URL for our custom pagination.
  628. *
  629. * @param string $permalink_structure The current permalink structure.
  630. * @param string $base The base URL to parse
  631. * @since 1.10.7
  632. * @return string
  633. */
  634. static public function build_base_url( $permalink_structure, $base ) {
  635. // Check to see if we are using pretty permalinks
  636. if ( ! empty( $permalink_structure ) ) {
  637. if ( strrpos( $base, 'paged-' ) ) {
  638. $base = substr_replace( $base, '', strrpos( $base, 'paged-' ), strlen( $base ) );
  639. }
  640. // Remove query string from base URL since paginate_links() adds it automatically.
  641. // This should also fix the WPML pagination issue that was added since 1.10.2.
  642. if ( count( $_GET ) > 0 ) {
  643. $base = strtok( $base, '?' );
  644. }
  645. // Add trailing slash when necessary.
  646. if ( '/' == substr( $permalink_structure, -1 ) ) {
  647. $base = trailingslashit( $base );
  648. } else {
  649. $base = untrailingslashit( $base );
  650. }
  651. } else {
  652. $url_params = wp_parse_url( $base, PHP_URL_QUERY );
  653. if ( empty( $url_params ) ) {
  654. $base = trailingslashit( $base );
  655. }
  656. }
  657. return $base;
  658. }
  659. /**
  660. * Build the custom pagination format.
  661. *
  662. * @param string $permalink_structure
  663. * @param string $base
  664. * @since 1.10.7
  665. * @return string
  666. */
  667. static public function paged_format( $permalink_structure, $base ) {
  668. if ( self::$loop_counter > 1 ) {
  669. $page_prefix = 'paged-' . self::$loop_counter;
  670. } else {
  671. $page_prefix = empty( $permalink_structure ) ? 'paged' : 'page';
  672. }
  673. if ( ! empty( $permalink_structure ) ) {
  674. $format = substr( $base, -1 ) != '/' ? '/' : '';
  675. $format .= $page_prefix . '/';
  676. $format .= '%#%';
  677. $format .= substr( $permalink_structure, -1 ) == '/' ? '/' : '';
  678. } elseif ( empty( $permalink_structure ) || is_search() ) {
  679. $parse_url = wp_parse_url( $base, PHP_URL_QUERY );
  680. $format = empty( $parse_url ) ? '?' : '&';
  681. $format .= $page_prefix . '=%#%';
  682. }
  683. return $format;
  684. }
  685. /**
  686. * Returns the custom pagination request data.
  687. *
  688. * @since 1.10.7
  689. * @return array|bool
  690. */
  691. static public function get_custom_paged() {
  692. if ( ! empty( self::$custom_paged_data ) ) {
  693. return self::$custom_paged_data;
  694. }
  695. if ( did_action( 'wp' ) ) {
  696. global $wp;
  697. $current_url = home_url( $wp->request );
  698. } else {
  699. $current_url = $_SERVER['REQUEST_URI'];
  700. }
  701. // Do a quick test if the current request URL contains the custom `paged-` var
  702. if ( false === strpos( $current_url, 'paged-' ) ) {
  703. return false;
  704. }
  705. // Check the current URL if it matches our custom pagination var.
  706. $paged_matches = preg_match( '/([^.\/]*?)(?:\/)?([^.\/]*?)\/paged-([0-9]{1,})(?:\=|\/)([0-9]{1,})/', $current_url, $matches );
  707. if ( $paged_matches ) {
  708. self::$custom_paged_data = array(
  709. 'parent_page' => $matches[1],
  710. 'current_page' => $matches[2],
  711. 'current_loop' => $matches[3],
  712. 'paged' => $matches[4],
  713. );
  714. }
  715. return self::$custom_paged_data;
  716. }
  717. /**
  718. * Check to see if the posts loop is currently paginated.
  719. *
  720. * @since 1.10.7
  721. * @return bool
  722. */
  723. static public function is_paginated_loop() {
  724. $custom_paged = self::get_custom_paged();
  725. if ( ! isset( $custom_paged['current_loop'] ) ) {
  726. return false;
  727. }
  728. if ( $custom_paged['current_loop'] == self::$loop_counter ) {
  729. return true;
  730. }
  731. return false;
  732. }
  733. /**
  734. * Returns the paged number for the query.
  735. *
  736. * @since 1.9.5
  737. * @return int
  738. */
  739. static public function get_paged() {
  740. global $wp_the_query, $paged;
  741. // Check first for custom pagination from post module
  742. $flpaged = $wp_the_query->get( 'flpaged' );
  743. // In case the site is using default permalink structure and it has multiple paginations.
  744. $permalink_structure = get_option( 'permalink_structure' );
  745. $base = html_entity_decode( get_pagenum_link() );
  746. if ( is_numeric( $flpaged ) && self::is_paginated_loop() ) {
  747. return $flpaged;
  748. } elseif ( empty( $permalink_structure ) && strrpos( $base, 'paged-' ) && self::$loop_counter > 1 ) {
  749. $flpaged = 0;
  750. $url_parts = wp_parse_url( $base, PHP_URL_QUERY );
  751. wp_parse_str( $url_parts, $url_params );
  752. foreach ( $url_params as $paged_key => $paged_val ) {
  753. $get_paged_loop = explode( '-', $paged_key );
  754. if ( false === strpos( $paged_key, 'paged-' ) || ! isset( $get_paged_loop[1] ) ) {
  755. continue;
  756. }
  757. if ( $get_paged_loop[1] == self::$loop_counter ) {
  758. $flpaged = $paged_val;
  759. break;
  760. }
  761. }
  762. return $flpaged;
  763. } elseif ( self::$loop_counter > 1 ) {
  764. // If we have multiple paginations, make sure it won't affect the other loops.
  765. return 0;
  766. }
  767. // Check the 'paged' query var.
  768. $paged_qv = $wp_the_query->get( 'paged' );
  769. if ( is_numeric( $paged_qv ) ) {
  770. return $paged_qv;
  771. }
  772. // Check the 'page' query var.
  773. $page_qv = $wp_the_query->get( 'page' );
  774. if ( is_numeric( $page_qv ) ) {
  775. return $page_qv;
  776. }
  777. // Check the $paged global?
  778. if ( is_numeric( $paged ) ) {
  779. return $paged;
  780. }
  781. return 0;
  782. }
  783. /**
  784. * Returns an array of data for post types supported
  785. * by module loop settings.
  786. *
  787. * @since 1.2.3
  788. * @return array
  789. */
  790. static public function post_types() {
  791. $post_types = get_post_types(array(
  792. 'public' => true,
  793. 'show_ui' => true,
  794. ), 'objects');
  795. unset( $post_types['attachment'] );
  796. unset( $post_types['fl-builder-template'] );
  797. unset( $post_types['fl-theme-layout'] );
  798. return $post_types;
  799. }
  800. /**
  801. * Get an array of supported taxonomy data for a post type.
  802. *
  803. * @since 1.2.3
  804. * @param string $post_type The post type to get taxonomies for.
  805. * @return array
  806. */
  807. static public function taxonomies( $post_type ) {
  808. $taxonomies = get_object_taxonomies( $post_type, 'objects' );
  809. $data = array();
  810. foreach ( $taxonomies as $tax_slug => $tax ) {
  811. if ( ! $tax->public || ! $tax->show_ui ) {
  812. continue;
  813. }
  814. $data[ $tax_slug ] = $tax;
  815. }
  816. return apply_filters( 'fl_builder_loop_taxonomies', $data, $taxonomies, $post_type );
  817. }
  818. /**
  819. * Displays the date for the current post in the loop.
  820. *
  821. * @since 1.7
  822. * @param string $format The date format to use.
  823. * @return void
  824. */
  825. static public function post_date( $format = 'default' ) {
  826. if ( 'default' == $format ) {
  827. $format = get_option( 'date_format' );
  828. }
  829. the_time( $format );
  830. }
  831. /**
  832. * Renders the select for matching or not matching filters in
  833. * a module's loop builder settings.
  834. *
  835. * @since 1.10
  836. * @param string $name
  837. * @param string $value
  838. * @param array $field
  839. * @param object $settings
  840. * @return void
  841. */
  842. static public function render_match_select( $name, $value, $field, $settings ) {
  843. if ( ! isset( $field['matching'] ) || ! $field['matching'] ) {
  844. return;
  845. }
  846. $label = FLBuilderUtils::strtolower( $field['label'] );
  847. if ( ! isset( $settings->{ $name . '_matching' } ) ) {
  848. $settings->{ $name . '_matching' } = '1';
  849. }
  850. include FL_BUILDER_DIR . 'includes/loop-settings-matching.php';
  851. }
  852. }
  853. FLBuilderLoop::init();