gschoppe
6/25/2018 - 3:26 AM

WP Object Relationships Core

<?php if(!defined('ABSPATH')) { die(); }

if( !class_exists('WPObjectRelationships') ) {
	class WPObjectRelationships {
		protected $version = '0.1.0';
		protected $domain  = 'object-relationships';
		protected $plugin_dir;
		protected $plugin_uri;
		const TABLE_PREFIX = 'wpor_';
		const RELATE_TABLE_BASE = 'relationships';
		const RELATE_META_TABLE_BASE = 'relationshipmeta';
		protected $relate_table;
		protected $meta_table;

		public static function Instance() {
			static $instance = null;
			if ($instance === null) {
				$instance = new self();
			}
			return $instance;
		}

		protected function __construct() {
			$this->register_tables();
			register_activation_hook( __FILE__, array( $this, 'install' ) );
		}

		public function register_tables() {
			global $wpdb;
			$this->relate_table = $wpdb->prefix . self::TABLE_PREFIX . self::RELATE_TABLE_BASE;
			$this->meta_table   = $wpdb->prefix . self::TABLE_PREFIX . self::RELATE_META_TABLE_BASE;
		}

		public function install() {
			global $wpdb;
			$charset_collate = $wpdb->get_charset_collate();
			$relate_table = "CREATE TABLE ".$this->relate_table." (\n".
							" relationship_id bigint(20) unsigned NOT NULL auto_increment,\n".
							" object_1_id bigint(20) unsigned NOT NULL default 0,\n".
							" object_2_id bigint(20) unsigned NOT NULL default 0,\n".
							" object_1_type varchar(255) NULL,\n".
							" object_2_type varchar(255) NULL,\n".
							" relationship_type varchar(255) NULL,\n".
							" object_1_order int(11) NOT NULL default 0,\n".
							" object_2_order int(11) NOT NULL default 0,\n".
							" PRIMARY KEY  (relationship_id),\n".
							" KEY object_1 (object_1_id,object_1_type),\n".
							" KEY object_2 (object_2_id,object_2_type)\n".
							") ".$charset_collate.";";
			$meta_table   = "CREATE TABLE ".$this->meta_table." (\n".
							" meta_id bigint(20) unsigned NOT NULL auto_increment,\n".
							" relationship_id bigint(20) unsigned NOT NULL default 0,\n".
							" meta_key varchar(255) NULL,\n".
							" meta_value longtext NULL,\n".
							" PRIMARY KEY  (meta_id),\n".
							" KEY relationship_id (relationship_id),\n".
							" KEY meta_key (meta_key)\n".
							") ".$charset_collate.";";
			require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
			dbDelta( $relate_table );
			dbDelta( $meta_table );
		}

		public function get_relationship( $relationship_id = null ) {
			global $wpdb;
			if( !$relationship_id || !is_numeric( $relationship_id ) ) {
				return false;
			}
			$sql = "SELECT * FROM ".$this->relate_table." WHERE relationship_id=%d LIMIT 1";
			return $wpdb->get_row( $wpdb->prepare( $sql, $relationship_id ), ARRAY_A );
		}

		public function add_relationship( $args = array(), $error = false ) {
			return $this->update_relationship( $args );
		}

		public function update_relationship( $args = array(), $error = false ) {
			global $wpdb;
			// normalize all attributes
			$filtered_args = shortcode_atts( array(
				'relationship_type'  => 'related',
				'object_1_type'      => 'post',
				'object_1_id'        => 0,
				'object_2_type'      => 'post',
				'object_2_id'        => 0,
				'object_1_order'     => 'default',
				'object_2_order'     => 'default',
				'meta'               => array(),
				'replace_meta'       => false
			), $args );
			// do all my input checks
			if( !$filtered_args['object_1_type'] || !$filtered_args['object_1_id'] || !is_numeric( $filtered_args['object_1_id'] ) ) {
				if( $error ) {
					return new WP_Error( 'relationship_update_error', __( 'Could not update relationship. Object 1 not properly defined', $this->domain ) );
				}
				return false;
			}
			if( !$filtered_args['object_2_type'] || !$filtered_args['object_2_id'] || !is_numeric( $filtered_args['object_2_id'] ) ) {
				if( $error ) {
					return new WP_Error( 'relationship_update_error', __( 'Could not update relationship. Object 2 not properly defined', $this->domain ) );
				}
				return false;
			}
			if( !$filtered_args['relationship_type'] ) {
				if( $error ) {
					return new WP_Error( 'relationship_update_error', __( 'Could not update relationship. Relationship type not defined', $this->domain ) );
				}
				return false;
			}

			if( $filtered_args['meta'] && !is_array( $filtered_args['meta'] ) ) {
				if( $error ) {
					return new WP_Error( 'relationship_update_error', __( 'Could not update relationship. Meta must be an array', $this->domain ) );
				}
				return false;
			}
			$data = array(
				'relationship_type'  => $filtered_args['relationship_type'],
			);
			// order ids, to prevent duplicate rows
			if( $filtered_args['object_1_id'] <= $filtered_args['object_2_id'] ) {
				$data['object_1_type'] = $filtered_args['object_1_type'];
				$data['object_1_id'  ] = $filtered_args['object_1_id'];
				$data['object_1_order'] = $filtered_args['object_1_order'];
				$data['object_2_type'] = $filtered_args['object_2_type'];
				$data['object_2_id'  ] = $filtered_args['object_2_id'];
				if( is_numeric( $filtered_args['object_1_order'] ) ) {
					$data['object_1_order'] = $filtered_args['object_1_order'];
				}
				if( is_numeric( $filtered_args['object_2_order'] ) ) {
					$data['object_2_order'] = $filtered_args['object_2_order'];
				}
			} else {
				$data['object_1_type'] = $filtered_args['object_2_type'];
				$data['object_1_id'  ] = $filtered_args['object_2_id'];
				$data['object_2_type'] = $filtered_args['object_1_type'];
				$data['object_2_id'  ] = $filtered_args['object_1_id'];
				if( is_numeric( $filtered_args['object_1_order'] ) ) {
					$data['object_2_order'] = $filtered_args['object_1_order'];
				}
				if( is_numeric( $filtered_args['object_2_order'] ) ) {
					$data['object_1_order'] = $filtered_args['object_2_order'];
				}
			}
			// check if this pairing exists
			$sql = "SELECT relationship_id FROM ".$this->relate_table." WHERE".
				   " relationship_type=%s AND".
				   " object_1_type=%s AND object_1_id=%d AND".
				   " object_2_type=%s AND object_2_id=%d".
				   " LIMIT 1";
			$params = array(
				$data['relationship_type'],
				$data['object_1_type'],
				$data['object_1_id'],
				$data['object_2_type'],
				$data['object_2_id']
			);
			$id = $wpdb->get_var( $wpdb->prepare( $sql, $params ) );
			if( $id ) {
				// update
				$where = array(
					'relationship_id' => $id
				);
				$updated = $wpdb->update( $this->relate_table, $data, $where );
				if( !$updated ) {
					if( $error ) {
						return new WP_Error( 'relationship_update_error', __( 'Could not update relationship in the database', $this->domain ), $wpdb->last_error );
					}
					return false;
				}
				if( $filtered_args['replace_meta'] ) {
					$this->delete_relationship_meta( $id );
				}
			} else {
				$inserted = $wpdb->insert( $this->relate_table, $data );
				if( !$inserted ) {
					if( $error ) {
						return new WP_Error( 'relationship_update_error', __( 'Could not insert relationship in the database', $this->domain ), $wpdb->last_error );
					}
					return false;
				}
				$id = $wpdb->insert_id;
			}
			// clear caches
			if( isset( $data['object_1_id'] ) && isset( $data['object_1_type'] ) ) {
				wp_cache_delete( $data['object_1_id'], $data['object_1_type'] . '_relationships');
			}
			if( isset( $data['object_2_id'] ) && isset( $data['object_2_type'] ) ) {
				wp_cache_delete( $data['object_2_id'], $data['object_2_type'] . '_relationships');
			}

			if( $filtered_args['meta'] ) {
				foreach( $filtered_args['meta'] as $key => $val ) {
					$this->update_relationship_meta( $id, $key, $val );
				}
			}
			// TODO: Clear Meta Caches
			return $id;
		}

		public function delete_relationship( $args, $error = false ) {
			global $wpdb;
			$id = 0;
			$row = array();
			if( is_numeric( $args ) ) {
				$possible_id = $args;
				$sql = "SELECT * FROM ".$this->relate_table." WHERE relationship_id=%d LIMIT 1";
				$row = $wpdb->get_row( $wpdb->prepare( $sql, $possible_id ) );
				if( !$row || !isset( $row['relationship_id'] ) ) {
					if( $error ) {
						return new WP_Error( 'relationship_delete_error', __( 'Could not delete relationship. No matching relationship found', $this->domain ) );
					}
					return false;
				}
				$id = $row['relationship_id'];
			} else {
				$filtered_args = shortcode_atts( array(
					'relationship_type'  => 'related',
					'object_1_type'      => 'post',
					'object_1_id'        => 0,
					'object_2_type'      => 'post',
					'object_2_id'        => 0
				), $args );
				// do all my input checks
				if( !$filtered_args['object_1_type'] || !$filtered_args['object_1_id'] || !is_numeric( $filtered_args['object_1_id'] ) ) {
					if( $error ) {
						return new WP_Error( 'relationship_delete_error', __( 'Could not delete relationship. Object 1 not properly defined', $this->domain ) );
					}
					return false;
				}
				if( !$filtered_args['object_2_type'] || !$filtered_args['object_2_id'] || !is_numeric( $filtered_args['object_2_id'] ) ) {
					if( $error ) {
						return new WP_Error( 'relationship_delete_error', __( 'Could not delete relationship. Object 2 not properly defined', $this->domain ) );
					}
					return false;
				}
				if( !$filtered_args['relationship_type'] ) {
					if( $error ) {
						return new WP_Error( 'relationship_delete_error', __( 'Could not delete relationship. Relationship type not defined', $this->domain ) );
					}
					return false;
				}
				// strictly order the query parameters to match our standard
				if( $filtered_args['object_1_id'] <= $filtered_args['object_2_id'] ) {
					$data['object_1_type'] = $filtered_args['object_1_type'];
					$data['object_1_id'  ] = $filtered_args['object_1_id'];
					$data['object_2_type'] = $filtered_args['object_2_type'];
					$data['object_2_id'  ] = $filtered_args['object_2_id'];
				} else {
					$data['object_1_type'] = $filtered_args['object_2_type'];
					$data['object_1_id'  ] = $filtered_args['object_2_id'];
					$data['object_2_type'] = $filtered_args['object_1_type'];
					$data['object_2_id'  ] = $filtered_args['object_1_id'];
				}
				// check if this pairing exists
				$sql = "SELECT * FROM ".$this->relate_table." WHERE".
					   " relationship_type=%s AND".
					   " object_1_type=%s AND object_1_id=%d AND".
					   " object_2_type=%s AND object_2_id=%d".
					   " LIMIT 1";
				$params = array(
					$data['relationship_type'],
					$data['object_1_type'],
					$data['object_1_id'],
					$data['object_2_type'],
					$data['object_2_id']
				);
				$row = $wpdb->get_row( $wpdb->prepare( $sql, $params ) );
				if( !$row || !isset( $row['relationship_id'] ) ) {
					if( $error ) {
						return new WP_Error( 'relationship_delete_error', __( 'Could not delete relationship. No matching relationship found', $this->domain ) );
					}
					return false;
				}
				$id = $row['relationship_id'];
			}
			if( !$id ) {
				if( $error ) {
					return new WP_Error( 'relationship_delete_error', __( 'Could not delete relationship. No matching relationship found', $this->domain ) );
				}
				return false;
			}
			// try deleting
			$deleted = $wpdb->delete( $this->relate_table, array(
				'relationship_id' => $id
			) );
			if( !$deleted ) {
				if( $error ) {
					return new WP_Error( 'relationship_delete_error', __( 'Could not delete relationship. No matching relationship found', $this->domain ) );
				}
				return false;
			}
			// clear caches
			if( isset( $row['object_1_id'] ) && isset( $row['object_1_type'] ) ) {
				wp_cache_delete( $row['object_1_id'], $row['object_1_type'] . '_relationships');
			}
			if( isset( $row['object_2_id'] ) && isset( $row['object_2_type'] ) ) {
				wp_cache_delete( $row['object_2_id'], $row['object_2_type'] . '_relationships');
			}
			// clear meta
			$wpdb->delete( $this->meta_table, array(
				'relationship_id' => $id
			) );

			// TODO: Clear Meta Caches
			return true;
		}

		public function set_relationships( $object_type = null, $object_id = null, $relationship_type = null, $relationships = array() ) {
			if( !$object_type || !is_numeric( $object_id ) ) {
				return false;
			}
			if( $relationship_type ) {
				$relationships = array(
					$relationship_type => $relationships
				);
			}
			// TODO: Add shortcircuit filter?
			// get cached relationships
			$relationship_cache = wp_cache_get($object_id, $object_type . '_relationships');
			if( !$relationship_cache ) {
				$relationship_cache = $this->update_relationship_cache( $object_type, array( $object_id ) );
				$relationship_cache = $relationship_cache[$object_id];
			}
			$old_relationships = $relationship_cache;
			if( $relationships && is_array( $relationships ) ) {
				foreach( $relationships as $name => $values ) {
					if( !$values ) {
						continue;
					}
					$temp = array();
					if( isset( $old_relationships[$name] ) ) {
						$temp = $old_relationships[$name];
					}
					foreach( $values as $relationship ) {
						if( !is_array( $relationship ) ) {
							continue;
						}
						if( !isset( $relationship['object_type'] ) || !isset( $relationship['object_id'] ) ) {
							continue;
						}
						if( !isset( $relationship['meta'] ) ) {
							$relationship['meta'] = array();
						}
						$insert = array(
							'relationship_type'  => $name,
							'object_1_type'      => $object_type,
							'object_1_id'        => $object_id,
							'object_2_type'      => $relationship['object_type'],
							'object_2_id'        => $relationship['object_id'],
							'meta'               => $relationship['meta'],
							'replace_meta'       => true
						);
						if( isset( $relationship['object_1_order'] ) ) {
							$insert['object_1_order'] = $relationship['object_1_order'];
						}
						if( isset( $relationship['object_2_order'] ) ) {
							$insert['object_2_order'] = $relationship['object_2_order'];
						}
						$updated = $this->update_relationship( $insert );
						foreach( $temp as $i => $rel ) {
							if( ( $rel['object_type'] == $relationship['object_type'] ) && ( $rel['object_id'] == $relationship['object_id'] ) ) {
								unset( $temp[$i] );
							}
						}
					}
					// delete old relationships
					foreach( $temp as $rel ) {
						$deleted = $this->delete_relationship( $rel['relationship_id'] );
					}
				}
			}
		}

		public function get_relationships( $object_type = null, $object_id = null, $relationship_type = null ) {
			if( !$object_type || !is_numeric( $object_id ) ) {
				return false;
			}

			$object_id = absint( $object_id );
			if ( !$object_id ) {
				return false;
			}

			// TODO: Add shortcircuit filter?
			// get cached relationships
			$relationship_cache = wp_cache_get($object_id, $object_type . '_relationships');
			if( !$relationship_cache ) {
				$relationship_cache = $this->update_relationship_cache( $object_type, array( $object_id ) );
				$relationship_cache = $relationship_cache[$object_id];
			}

			if( !$relationship_type ) {
				return $relationship_cache;
			}

			if( isset( $relationship_cache[$relationship_type] ) ) {
				return $relationship_cache[$relationship_type];
			}

			return array();
		}

		public function update_relationship_cache( $object_type, $object_ids ) {
			global $wpdb;

			if( !$object_type || !$object_ids ) {
				return false;
			}

			if( !is_array( $object_ids ) ) {
				$object_ids = preg_replace( '|[^0-9,]|', '', $object_ids );
				$object_ids = explode( ',', $object_ids );
			}

			$object_ids = array_map( 'intval', $object_ids );

			$cache_key = $object_type . '_relationships';
			$ids = array();
			$cache = array();
			foreach( $object_ids as $id ) {
				$cached_object = wp_cache_get( $id, $cache_key );
				if( false === $cached_object ) {
					$ids[] = $id;
				} else {
					$cache[$id] = $cached_object;
				}
			}

			if( empty( $ids ) ) {
				return $cache;
			}

			// Get meta info
			$id_list = join( ',', $ids );
			$query = "SELECT * FROM ".$this->relate_table." WHERE (\n".
					 "object_1_type=%s AND object_1_id IN (".$id_list."\n".
					 ") OR (\n".
					 "object_2_type=%s AND object_2_id IN (".$id_list."\n".
					 ") ORDER BY case \n".
					 "    when object_1_type=%s AND object_1_id IN(".$id_list.")  then object_1_order\n".
					 "    when object_2_type=%s AND object_2_id IN(".$id_list.")  then object_2_order\n".
					 "end ASC";
			$query = $wpdb->prepare( $query, array( $object_type, $object_type ) );
			$relate_list = $wpdb->get_results( $query, ARRAY_A );

			if( !empty( $relate_list ) ) {
				foreach( $relate_list as $row ) {
					$o1id = intval( $row['object_1_id'] );
					$o2id = intval( $row['object_2_id'] );
					$parsed_row = array();
					if( $row['object_1_type'] === $object_type && in_array( $o1id, $ids ) ) {
						$parsed_row[$o1id] = array(
							'relationship_id'   => $row['relationship_id'],
							'object_id'         => $o2id,
							'object_type'       => $row['object_2_type'],
							'relationship_type' => $row['relationship_type'],
						);
					}
					if( $row['object_2_type'] === $object_type && in_array( $o2id, $ids ) ) {
						$parsed_row[$o2id] = array(
							'relationship_id'   => $row['relationship_id'],
							'object_id'         => $o1id,
							'object_type'       => $row['object_1_type'],
							'relationship_type' => $row['relationship_type'],
						);
					}
					foreach( $parsed_row as $oid => $val ) {
						// Force subkeys to be array type:
						if( !isset( $cache[$oid] ) || !is_array( $cache[$oid] ) ) {
							$cache[$oid] = array();
						}
						$rtype = $val['relationship_type'];
						unset( $val['relationship_type'] );
						if( !isset( $cache[$oid][$rtype] ) || !is_array( $cache[$oid][$rtype] ) ) {
							$cache[$oid][$rtype] = array();
						}
						$cache[$oid][$rtype][] = $val;
					}
				}
			}

			foreach( $ids as $id ) {
				if( !isset( $cache[$id] ) ) {
					$cache[$id] = array();
				}
				wp_cache_add( $id, $cache[$id], $cache_key );
			}

			return $cache;
		}

		public function get_relationship_meta( $relationship_id, $meta_key, $single = false ) {
			if( !is_numeric( $relationship_id ) ) {
				return false;
			}

			$relationship_id = absint( $relationship_id );
			if( !$relationship_id ) {
				return false;
			}

			$meta_cache = wp_cache_get( $relationship_id, 'relationship_meta' );

			if( !$meta_cache ) {
				$meta_cache = $this->update_meta_cache( array( $relationship_id ) );
				$meta_cache = $meta_cache[$relationship_id];
			}

			if( !$meta_key ) {
				return $meta_cache;
			}

			if( isset( $meta_cache[$meta_key] ) ) {
				if ( $single ) {
					return maybe_unserialize( $meta_cache[$meta_key][0] );
				} else {
					return array_map( 'maybe_unserialize', $meta_cache[$meta_key] );
				}
			}

			if( $single ) {
				return '';
			} else {
				return array();
			}
		}

		public function add_relationship_meta( $relationship_id, $meta_key, $meta_value, $unique = false ) {
			global $wpdb;

			if( !$meta_key || !is_numeric( $relationship_id ) ) {
				return false;
			}

			$object_id = absint( $object_id );
			if( !$object_id ) {
				return false;
			}

			// expected_slashed ($meta_key)
			$meta_key = wp_unslash($meta_key);
			$meta_value = wp_unslash($meta_value);
			$meta_value = sanitize_meta( $meta_key, $meta_value, 'relationship' );

			$sql = "SELECT COUNT(*) FROM " . $this->meta_table . " WHERE meta_key = %s AND relationship_id = %d";
			$row_exists = $wpdb->get_var( $wpdb->prepare( $sql, $meta_key, $object_id ) );
			if( $unique && $row_exists ) {
				return false;
			}

			$_meta_value = $meta_value;
			$meta_value = maybe_serialize( $meta_value );

			do_action( "add_relationship_meta", $relationship_id, $meta_key, $_meta_value );

			$result = $wpdb->insert( $this->meta_table, array(
				'relationship_id' => $relationship_id,
				'meta_key' => $meta_key,
				'meta_value' => $meta_value
			) );

			if ( !$result ) {
				return false;
			}

			$mid = (int) $wpdb->insert_id;

			wp_cache_delete( $relationship_id, 'relationship_meta');

			do_action( "added_relationship_meta", $mid, $relationship_id, $meta_key, $_meta_value );

			return $mid;
		}

		public function update_relationship_meta( $relationship_id, $meta_key, $meta_value, $prev_value = '' ) {
			global $wpdb;

			if ( !$meta_key || !is_numeric( $relationship_id ) ) {
				return false;
			}

			$relationship_id = absint( $relationship_id );
			if ( !$relationship_id ) {
				return false;
			}

			// expected_slashed ($meta_key)
			$raw_meta_key = $meta_key;
			$meta_key = wp_unslash($meta_key);
			$passed_value = $meta_value;
			$meta_value = wp_unslash($meta_value);
			$meta_value = sanitize_meta( $meta_key, $meta_value, 'relationship' );

			// Compare existing value to new value if no prev value given and the key exists only once.
			if( empty( $prev_value ) ) {
				$old_value = $this->get_relationship_meta( $relationship_id, $meta_key );
				if( count( $old_value ) == 1 ) {
					if( $old_value[0] === $meta_value ) {
						return false;
					}
				}
			}

			$sql = "SELECT meta_id FROM " . $this->meta_table . " WHERE meta_key = %s AND relationship_id = %d";
			$meta_ids = $wpdb->get_col( $wpdb->prepare( $sql, $meta_key, $relationship_id ) );
			if( empty( $meta_ids ) ) {
				return $this->add_relationship_meta( $relationship_id, $raw_meta_key, $passed_value );
			}

			$_meta_value = $meta_value;
			$meta_value = maybe_serialize( $meta_value );

			$data  = compact( 'meta_value' );
			$where = array( 'relationship_id' => $relationship_id, 'meta_key' => $meta_key );

			if ( !empty( $prev_value ) ) {
				$prev_value = maybe_serialize( $prev_value );
				$where['meta_value'] = $prev_value;
			}

			foreach ( $meta_ids as $meta_id ) {
				do_action( "update_relationship_meta", $meta_id, $relationship_id, $meta_key, $_meta_value );
			}

			$result = $wpdb->update( $this->meta_table, $data, $where );
			if( !$result ){
				return false;
			}

			wp_cache_delete( $relationship_id, 'relationship_meta' );

			foreach( $meta_ids as $meta_id ) {
				do_action( "updated_relationship_meta", $meta_id, $relationship_id, $meta_key, $_meta_value );
			}

			return true;
		}

		public function delete_relationship_meta( $relationship_id, $meta_key = '', $meta_value = '', $delete_all = false ) {
			global $wpdb;

			if( !$meta_key || !is_numeric( $relationship_id ) && !$delete_all ) {
				return false;
			}

			$relationship_id = absint( $relationship_id );
			if( !$relationship_id && !$delete_all ) {
				return false;
			}

			// expected_slashed ($meta_key)
			$meta_key = wp_unslash( $meta_key );
			$meta_value = wp_unslash( $meta_value );

			$_meta_value = $meta_value;
			$meta_value = maybe_serialize( $meta_value );

			$query = $wpdb->prepare( "SELECT meta_id FROM " . $this->meta_table . " WHERE meta_key = %s", $meta_key );

			if( !$delete_all ) {
				$query .= $wpdb->prepare(" AND relationship_id = %d", $relationship_id );
			}

			if( '' !== $meta_value && null !== $meta_value && false !== $meta_value ) {
				$query .= $wpdb->prepare(" AND meta_value = %s", $meta_value );
			}

			$meta_ids = $wpdb->get_col( $query );
			if( !count( $meta_ids ) ) {
				return false;
			}

			if( $delete_all ) {
				if( '' !== $meta_value && null !== $meta_value && false !== $meta_value ) {
					$object_ids = $wpdb->get_col( $wpdb->prepare( "SELECT relationship_id FROM " . $this->meta_table . " WHERE meta_key = %s AND meta_value = %s", $meta_key, $meta_value ) );
				} else {
					$object_ids = $wpdb->get_col( $wpdb->prepare( "SELECT relationship_id FROM " . $this->meta_table . " WHERE meta_key = %s", $meta_key ) );
				}
			}

			do_action( "delete_relationship_meta", $meta_ids, $relationship_id, $meta_key, $_meta_value );

			$query = "DELETE FROM " . $this->meta_table . " WHERE meta_id IN( " . implode( ',', $meta_ids ) . " )";

			$count = $wpdb->query( $query );

			if( !$count ) {
				return false;
			}

			if( $delete_all ) {
				foreach( (array) $object_ids as $o_id ) {
					wp_cache_delete($o_id, 'relationship_meta');
				}
			} else {
				wp_cache_delete($relationship_id, 'relationship_meta');
			}

			do_action( "deleted_relationship_meta", $meta_ids, $relationship_id, $meta_key, $_meta_value );


			return true;
		}

		public function update_meta_cache( $relationship_ids = array() ) {
			global $wpdb;

			if ( !$relationship_ids ) {
				return false;
			}

			if ( !is_array( $relationship_ids ) ) {
				$relationship_ids = preg_replace( '|[^0-9,]|', '', $relationship_ids );
				$relationship_ids = explode( ',', $relationship_ids );
			}

			$relationship_ids = array_map( 'intval', $relationship_ids );

			$cache_key = 'relationship_meta';
			$ids = array();
			$cache = array();
			foreach( $relationship_ids as $id ) {
				$cached_object = wp_cache_get( $id, $cache_key );
				if( false === $cached_object ) {
					$ids[] = $id;
				} else {
					$cache[$id] = $cached_object;
				}
			}

			if ( empty( $ids ) ) {
				return $cache;
			}

			// Get meta info
			$id_list = join( ',', $ids );
			$meta_list = $wpdb->get_results( "SELECT relationship_id, meta_key, meta_value FROM " . $this->meta_table . " WHERE relationship_id IN (" . $id_list . ") ORDER BY meta_id ASC", ARRAY_A );

			if ( !empty( $meta_list ) ) {
				foreach ( $meta_list as $meta_row) {
					$mpid = intval( $meta_row['relationship_id'] );
					$mkey = $meta_row['meta_key'];
					$mval = $meta_row['meta_value'];

					// Force subkeys to be array type:
					if ( !isset($cache[$mpid]) || !is_array($cache[$mpid]) ) {
						$cache[$mpid] = array();
					}
					if ( !isset($cache[$mpid][$mkey]) || !is_array($cache[$mpid][$mkey]) ) {
						$cache[$mpid][$mkey] = array();
					}
					// Add a value to the current pid/key:
					$cache[$mpid][$mkey][] = $mval;
				}
			}

			foreach ( $ids as $id ) {
				if ( !isset( $cache[$id] ) ) {
					$cache[$id] = array();
				}
				wp_cache_add( $id, $cache[$id], $cache_key );
			}

			return $cache;
		}
	}
	WPObjectRelationships::Instance();
}