/wp-content/plugins/the-events-calendar/src/Tribe/Amalgamator.php

https://github.com/livinglab/openlab · PHP · 305 lines · 190 code · 34 blank · 81 comment · 21 complexity · afca76f2acdd4b393f6d9d62738a9ba8 MD5 · raw file

  1. <?php
  2. /**
  3. * Merge pre-3.0 duplicate venues and organizers
  4. */
  5. class Tribe__Events__Amalgamator {
  6. private $default_venue = 0;
  7. private $default_community_venue = 0;
  8. private $default_organizer = 0;
  9. private $default_community_organizer = 0;
  10. /**
  11. * constructor
  12. */
  13. public function __construct() {
  14. $this->default_venue = (int) tribe_get_option( 'eventsDefaultVenueID', 0 );
  15. $this->default_organizer = (int) tribe_get_option( 'eventsDefaultOrganizerID', 0 );
  16. if ( class_exists( 'Tribe__Events__Community__Main' ) ) {
  17. $community = Tribe__Events__Community__Main::instance();
  18. $this->default_community_venue = (int) $community->getOption( 'defaultCommunityVenueID', 0 );
  19. $this->default_community_organizer = (int) $community->getOption( 'defaultCommunityOrganizerID', 0 );
  20. }
  21. }
  22. /**
  23. * Merge all duplicate event-related posts
  24. *
  25. */
  26. public function merge_duplicates() {
  27. $this->merge_identical_organizers();
  28. $this->merge_identical_venues();
  29. $events = Tribe__Events__Main::instance();
  30. wp_cache_flush();
  31. }
  32. /**
  33. * Merge identical organizers
  34. *
  35. */
  36. public function merge_identical_organizers() {
  37. $titles = $this->get_redundant_titles( Tribe__Events__Main::ORGANIZER_POST_TYPE );
  38. $buckets = [];
  39. foreach ( $titles as $t ) {
  40. $organizer_ids = $this->get_posts_with_title( $t, Tribe__Events__Main::ORGANIZER_POST_TYPE );
  41. foreach ( $organizer_ids as $id ) {
  42. $post = get_post( $id );
  43. $data = [
  44. 'title' => $post->post_title,
  45. 'status' => $post->post_status,
  46. 'content' => $post->post_content,
  47. '_OrganizerPhone' => get_post_meta( $id, '_OrganizerPhone', true ),
  48. '_OrganizerWebsite' => get_post_meta( $id, '_OrganizerWebsite', true ),
  49. '_OrganizerEmail' => get_post_meta( $id, '_OrganizerEmail', true ),
  50. ];
  51. $hash = md5( serialize( $data ) );
  52. if ( ! isset( $buckets[ $hash ] ) ) {
  53. $buckets[ $hash ] = [];
  54. }
  55. // prioritize organizers with an eventbrite id
  56. $eventbrite = get_post_meta( $id, '_OrganizerEventBriteID', true );
  57. if ( empty( $eventbrite ) ) {
  58. array_push( $buckets[ $hash ], $id );
  59. } else {
  60. array_unshift( $buckets[ $hash ], $id );
  61. }
  62. }
  63. }
  64. foreach ( $buckets as $organizer_ids ) {
  65. $this->amalgamate_organizers( $organizer_ids );
  66. }
  67. }
  68. /**
  69. * Merge identical venues
  70. *
  71. */
  72. public function merge_identical_venues() {
  73. $titles = $this->get_redundant_titles( Tribe__Events__Main::VENUE_POST_TYPE );
  74. $buckets = [];
  75. foreach ( $titles as $t ) {
  76. $venue_ids = $this->get_posts_with_title( $t, Tribe__Events__Main::VENUE_POST_TYPE );
  77. foreach ( $venue_ids as $id ) {
  78. $post = get_post( $id );
  79. $data = [
  80. 'title' => $post->post_title,
  81. 'status' => $post->post_status,
  82. 'content' => $post->post_content,
  83. '_VenueAddress' => get_post_meta( $id, '_VenueAddress', true ),
  84. '_VenueCity' => get_post_meta( $id, '_VenueCity', true ),
  85. '_VenueProvince' => get_post_meta( $id, '_VenueProvince', true ),
  86. '_VenueState' => get_post_meta( $id, '_VenueState', true ),
  87. '_VenueCountry' => get_post_meta( $id, '_VenueCountry', true ),
  88. '_VenueZip' => get_post_meta( $id, '_VenueZip', true ),
  89. '_VenuePhone' => get_post_meta( $id, '_VenuePhone', true ),
  90. '_VenueURL' => get_post_meta( $id, '_VenueURL', true ),
  91. ];
  92. $hash = md5( serialize( $data ) );
  93. if ( ! isset( $buckets[ $hash ] ) ) {
  94. $buckets[ $hash ] = [];
  95. }
  96. // prioritize venues with an eventbrite id
  97. $eventbrite = get_post_meta( $id, '_VenueEventBriteID', true );
  98. if ( empty( $eventbrite ) ) {
  99. array_push( $buckets[ $hash ], $id );
  100. } else {
  101. array_unshift( $buckets[ $hash ], $id );
  102. }
  103. }
  104. }
  105. foreach ( $buckets as $venue_ids ) {
  106. $this->amalgamate_venues( $venue_ids );
  107. }
  108. }
  109. /**
  110. * Get all post titles of the given post type that have duplicates
  111. *
  112. * @param string $type The post type to query
  113. *
  114. * @return array
  115. */
  116. private function get_redundant_titles( $type ) {
  117. global $wpdb;
  118. $sql = "SELECT post_title FROM {$wpdb->posts} WHERE post_type=%s GROUP BY post_title HAVING COUNT(*) > 1";
  119. $sql = $wpdb->prepare( $sql, $type );
  120. $titles = $wpdb->get_col( $sql );
  121. return $titles;
  122. }
  123. /**
  124. * Find all posts of the given type with the given title
  125. *
  126. * @param string $title
  127. * @param string $type
  128. *
  129. * @return array
  130. */
  131. private function get_posts_with_title( $title, $type ) {
  132. global $wpdb;
  133. $sql = "SELECT ID FROM {$wpdb->posts} WHERE post_type=%s AND post_title=%s ORDER BY ID ASC";
  134. $sql = $wpdb->prepare( $sql, $type, $title );
  135. $posts = $wpdb->get_col( $sql );
  136. return $posts;
  137. }
  138. /**
  139. * Merge all venues in the given list into one post (keeping the first)
  140. *
  141. * @param array $venue_ids
  142. *
  143. */
  144. private function amalgamate_venues( $venue_ids ) {
  145. if ( empty( $venue_ids ) || count( $venue_ids ) < 2 ) {
  146. return;
  147. }
  148. global $wpdb;
  149. array_map( 'intval', $venue_ids );
  150. $keep = array_shift( $venue_ids );
  151. $old_ids = implode( ',', $venue_ids );
  152. $sql = "UPDATE {$wpdb->postmeta} SET meta_value=%d WHERE meta_key=%s AND meta_value IN($old_ids)";
  153. $sql = $wpdb->prepare( $sql, $keep, '_EventVenueID' );
  154. $wpdb->query( $sql );
  155. $this->update_default_venues( $keep, $venue_ids );
  156. $this->delete_posts( $venue_ids );
  157. }
  158. /**
  159. * Merge all organizers in the given list into one post (keeping the first)
  160. *
  161. * @param array $organizer_ids
  162. *
  163. */
  164. public function amalgamate_organizers( $organizer_ids ) {
  165. if ( empty( $organizer_ids ) || count( $organizer_ids ) < 2 ) {
  166. return;
  167. }
  168. global $wpdb;
  169. array_map( 'intval', $organizer_ids );
  170. $keep = array_shift( $organizer_ids );
  171. $old_ids = implode( ',', $organizer_ids );
  172. $sql = "UPDATE {$wpdb->postmeta} SET meta_value=%d WHERE meta_key=%s AND meta_value IN($old_ids)";
  173. $sql = $wpdb->prepare( $sql, $keep, '_EventOrganizerID' );
  174. $wpdb->query( $sql );
  175. $this->update_default_organizers( $keep, $organizer_ids );
  176. $this->delete_posts( $organizer_ids );
  177. }
  178. /**
  179. * If a removed venue is being used as a default, change the default to
  180. * its replacement.
  181. *
  182. * @param int $keep
  183. * @param array $replace
  184. *
  185. */
  186. private function update_default_venues( $keep, array $replace ) {
  187. if ( $this->default_venue && in_array( $this->default_venue, $replace ) ) {
  188. $events = Tribe__Events__Main::instance();
  189. $events->setOption( 'eventsDefaultVenueID', $keep );
  190. }
  191. if ( $this->default_community_venue && in_array( $this->default_community_venue, $replace ) ) {
  192. $community = Tribe__Events__Community__Main::instance();
  193. $community->setOption( 'defaultCommunityVenueID', $keep );
  194. }
  195. }
  196. /**
  197. * If a removed organizer is being used as a default, change the default to
  198. * its replacement.
  199. *
  200. * @param int $keep
  201. * @param array $replace
  202. *
  203. */
  204. private function update_default_organizers( $keep, array $replace ) {
  205. if ( $this->default_organizer && in_array( $this->default_organizer, $replace ) ) {
  206. $events = Tribe__Events__Main::instance();
  207. $events->setOption( 'eventsDefaultOrganizerID', $keep );
  208. }
  209. if ( $this->default_community_organizer && in_array( $this->default_community_organizer, $replace ) ) {
  210. $community = Tribe__Events__Community__Main::instance();
  211. $community->setOption( 'defaultCommunityOrganizerID', $keep );
  212. }
  213. }
  214. /**
  215. * Delete all the posts given
  216. *
  217. * @param array $post_ids
  218. */
  219. private function delete_posts( $post_ids ) {
  220. foreach ( $post_ids as $id ) {
  221. $force = apply_filters( 'tribe_force_delete_duplicates', true );
  222. wp_delete_post( $id, $force );
  223. }
  224. }
  225. /**
  226. * Make a button to trigger the amalgamation process
  227. *
  228. * @param string $text
  229. *
  230. * @return string
  231. */
  232. public static function migration_button( $text = '' ) {
  233. $text = $text ? $text : __( 'Merge Duplicates', 'the-events-calendar' );
  234. $settings = Tribe__Settings::instance();
  235. // get the base settings page url
  236. $url = apply_filters(
  237. 'tribe_settings_url', add_query_arg(
  238. [
  239. 'post_type' => Tribe__Events__Main::POSTTYPE,
  240. 'page' => $settings->adminSlug,
  241. ], admin_url( 'edit.php' )
  242. )
  243. );
  244. $url = add_query_arg( [ 'amalgamate' => '1' ], $url );
  245. $url = wp_nonce_url( $url, 'amalgamate_duplicates' );
  246. return sprintf( '<a href="%s" class="button">%s</a>', $url, $text );
  247. }
  248. /**
  249. * If the migration button is clicked, start working
  250. *
  251. */
  252. public static function listen_for_migration_button() {
  253. if ( empty( $_REQUEST['amalgamate'] ) || ! wp_verify_nonce( $_REQUEST['_wpnonce'], 'amalgamate_duplicates' ) ) {
  254. return;
  255. }
  256. $amalgamator = new self();
  257. $amalgamator->merge_duplicates();
  258. // redirect to base settings page
  259. $settings = Tribe__Settings::instance();
  260. $url = apply_filters(
  261. 'tribe_settings_url', add_query_arg(
  262. [
  263. 'post_type' => Tribe__Events__Main::POSTTYPE,
  264. 'page' => $settings->adminSlug,
  265. ], admin_url( 'edit.php' )
  266. )
  267. );
  268. wp_redirect( esc_url_raw( $url ) );
  269. exit();
  270. }
  271. }