Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
2.78% covered (danger)
2.78%
2 / 72
20.00% covered (danger)
20.00%
1 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
Meta
1.43% covered (danger)
1.43%
1 / 70
20.00% covered (danger)
20.00%
1 / 5
529.65
0.00% covered (danger)
0.00%
0 / 1
 name
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 get_objects_by_id
0.00% covered (danger)
0.00%
0 / 30
0.00% covered (danger)
0.00%
0 / 1
110
 get_object_by_id
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
20
 fetch_prepared_meta_from_db
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
30
 get_prepared_meta_object
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2/**
3 * Meta sync module.
4 *
5 * @package automattic/jetpack-sync
6 */
7
8namespace Automattic\Jetpack\Sync\Modules;
9
10if ( ! defined( 'ABSPATH' ) ) {
11    exit( 0 );
12}
13
14/**
15 * Class to handle sync for meta.
16 */
17class Meta extends Module {
18    /**
19     * Sync module name.
20     *
21     * @access public
22     *
23     * @return string
24     */
25    public function name() {
26        return 'meta';
27    }
28
29    /**
30     * This implementation of get_objects_by_id() is a bit hacky since we're not passing in an array of meta IDs,
31     * but instead an array of post or comment IDs for which to retrieve meta for. On top of that,
32     * we also pass in an associative array where we expect there to be 'meta_key' and 'ids' keys present.
33     *
34     * This seemed to be required since if we have missing meta on WP.com and need to fetch it, we don't know what
35     * the meta key is, but we do know that we have missing meta for a given post or comment.
36     *
37     * @todo Refactor the $wpdb->prepare call to use placeholders.
38     *
39     * @param string $object_type The type of object for which we retrieve meta. Either 'post' or 'comment'.
40     * @param array  $config      Must include 'meta_key' and 'ids' keys.
41     *
42     * @return array
43     */
44    public function get_objects_by_id( $object_type, $config ) {
45        global $wpdb;
46
47        $table = _get_meta_table( $object_type );
48
49        if ( ! $table ) {
50            return array();
51        }
52
53        if ( ! is_array( $config ) ) {
54            return array();
55        }
56
57        $object_id_column = $object_type . '_id';
58        $object_key_pairs = array();
59
60        foreach ( $config as $item ) {
61            if ( isset( $item['id'] ) && isset( $item['meta_key'] ) ) {
62                $object_key_pairs[ (int) $item['id'] ][] = (string) $item['meta_key'];
63            }
64        }
65
66        $meta_objects         = array();
67        $where_sql            = '';
68        $current_query_length = 0;
69
70        foreach ( $object_key_pairs as $object_id => $keys ) {
71            $keys_placeholders = implode( ',', array_fill( 0, count( $keys ), '%s' ) );
72            $where_condition   = trim(
73                $wpdb->prepare(
74                    // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
75                    "( `$object_id_column` = %d AND meta_key IN ( $keys_placeholders ) )",
76                    array_merge( array( $object_id ), $keys )
77                )
78            );
79
80            $where_sql = empty( $where_sql ) ? $where_condition : $where_sql . ' OR ' . $where_condition;
81
82            $current_query_length += strlen( $where_sql );
83
84            if ( $current_query_length > self::MAX_DB_QUERY_LENGTH ) {
85                $meta_objects         = $this->fetch_prepared_meta_from_db( $object_type, $where_sql, $meta_objects );
86                $where_sql            = '';
87                $current_query_length = 0;
88            }
89        }
90
91        if ( ! empty( $where_sql ) ) {
92            $meta_objects = $this->fetch_prepared_meta_from_db( $object_type, $where_sql, $meta_objects );
93        }
94
95        return $meta_objects;
96    }
97
98    /**
99     * Get a single Meta Result.
100     *
101     * @param string      $object_type  post, comment, term, user.
102     * @param int|null    $id           Object ID.
103     * @param string|null $meta_key     Meta Key.
104     *
105     * @return mixed|null
106     */
107    public function get_object_by_id( $object_type, $id = null, $meta_key = null ) {
108        global $wpdb;
109
110        if ( ! is_int( $id ) || ! is_string( $meta_key ) ) {
111            return null;
112        }
113
114        $table = _get_meta_table( $object_type );
115
116        if ( ! $table ) {
117            return null;
118        }
119
120        $object_id_column = $object_type . '_id';
121
122        // Sanitize so that the array only has integer values.
123        $where_condition = $wpdb->prepare(
124            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
125            "{$object_id_column} = %d AND meta_key = %s",
126            $id,
127            $meta_key
128        );
129
130        $meta_objects = $this->fetch_prepared_meta_from_db( $object_type, $where_condition );
131
132        $key = $id . '-' . $meta_key;
133
134        return $meta_objects[ $key ] ?? null;
135    }
136
137    /**
138     * Fetch meta from DB and return them in a standard format.
139     *
140     * @param  string $object_type   The meta object type, eg 'post', 'user' etc.
141     * @param  string $where         Prepared SQL 'where' statement.
142     * @param  array  $meta_objects  An existing array of meta to populate. Defaults to an empty array.
143     * @return array
144     */
145    private function fetch_prepared_meta_from_db( $object_type, $where, $meta_objects = array() ) {
146        global $wpdb;
147
148        $table            = _get_meta_table( $object_type );
149        $object_id_column = $object_type . '_id';
150
151        $meta = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.DirectDatabaseQuery.DirectQuery
152            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
153            "SELECT * FROM {$table} WHERE {$where}",
154            ARRAY_A
155        );
156
157        if ( ! is_wp_error( $meta ) && ! empty( $meta ) ) {
158            foreach ( $meta as $meta_entry ) {
159                $object_id = $meta_entry[ $object_id_column ];
160                $meta_key  = $meta_entry['meta_key'];
161                $key       = $object_id . '-' . $meta_key;
162
163                if ( ! isset( $meta_objects[ $key ] ) ) {
164                    $meta_objects[ $key ] = array();
165                }
166
167                $meta_objects[ $key ][] = $this->get_prepared_meta_object( $object_type, $meta_entry );
168            }
169        }
170
171        return $meta_objects;
172    }
173
174    /**
175     * Accepts a DB meta entry and returns it in a standard format.
176     *
177     * @param  string $object_type The meta object type, eg 'post', 'user' etc.
178     * @param  array  $meta_entry  A meta array.
179     * @return array
180     */
181    private function get_prepared_meta_object( $object_type, $meta_entry ) {
182        $object_id_column = $object_type . '_id';
183
184        if ( 'post' === $object_type && strlen( $meta_entry['meta_value'] ) >= Posts::MAX_META_LENGTH ) {
185            $meta_entry['meta_value'] = '';
186        }
187
188        return array(
189            'meta_type'  => $object_type,
190            'meta_id'    => $meta_entry['meta_id'],
191            'meta_key'   => $meta_entry['meta_key'],
192            'meta_value' => $meta_entry['meta_value'],
193            'object_id'  => $meta_entry[ $object_id_column ],
194        );
195    }
196}