14.65KiB; PHP | 2020-03-23 19:12:58+01 | SLOC 417
1
<?php
2
/**
3
 * Link/Bookmark API
4
 *
5
 * @package WordPress
6
 * @subpackage Bookmark
7
 */
8
9
/**
10
 * Retrieve Bookmark data
11
 *
12
 * @since 2.1.0
13
 *
14
 * @global wpdb $wpdb WordPress database abstraction object.
15
 *
16
 * @param int|stdClass $bookmark
17
 * @param string $output Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which correspond to
18
 *                       an stdClass object, an associative array, or a numeric array, respectively. Default OBJECT.
19
 * @param string $filter Optional, default is 'raw'.
20
 * @return array|object|null Type returned depends on $output value.
21
 */
22 16
function get_bookmark( $bookmark, $output = OBJECT, $filter = 'raw' ) {
23
	global $wpdb;
24
25
	if ( empty( $bookmark ) ) {
26
		if ( isset( $GLOBALS['link'] ) ) {
27
			$_bookmark = & $GLOBALS['link'];
28 1
		} else {
29
			$_bookmark = null;
30
		}
31
	} elseif ( is_object( $bookmark ) ) {
32
		wp_cache_add( $bookmark->link_id, $bookmark, 'bookmark' );
33
		$_bookmark = $bookmark;
34 1
	} else {
35
		if ( isset( $GLOBALS['link'] ) && ( $GLOBALS['link']->link_id == $bookmark ) ) {
36
			$_bookmark = & $GLOBALS['link'];
37 1
		} else {
38
			$_bookmark = wp_cache_get( $bookmark, 'bookmark' );
39
			if ( ! $_bookmark ) {
40
				$_bookmark = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $wpdb->links WHERE link_id = %d LIMIT 1", $bookmark ) );
41
				if ( $_bookmark ) {
42
					$_bookmark->link_category = array_unique( wp_get_object_terms( $_bookmark->link_id, 'link_category', array( 'fields' => 'ids' ) ) );
43
					wp_cache_add( $_bookmark->link_id, $_bookmark, 'bookmark' );
44
				}
45
			}
46
		}
47
	}
48
49
	if ( ! $_bookmark ) {
50
		return $_bookmark;
51
	}
52
53
	$_bookmark = sanitize_bookmark( $_bookmark, $filter );
54
55
	if ( $output == OBJECT ) {
56
		return $_bookmark;
57
	} elseif ( $output == ARRAY_A ) {
58
		return get_object_vars( $_bookmark );
59
	} elseif ( $output == ARRAY_N ) {
60
		return array_values( get_object_vars( $_bookmark ) );
61 1
	} else {
62
		return $_bookmark;
63
	}
64
}
65
66
/**
67
 * Retrieve single bookmark data item or field.
68
 *
69
 * @since 2.3.0
70
 *
71
 * @param string $field The name of the data field to return
72
 * @param int $bookmark The bookmark ID to get field
73
 * @param string $context Optional. The context of how the field will be used.
74
 * @return string|WP_Error
75
 */
76
function get_bookmark_field( $field, $bookmark, $context = 'display' ) {
77
	$bookmark = (int) $bookmark;
78
	$bookmark = get_bookmark( $bookmark );
79
80
	if ( is_wp_error( $bookmark ) ) {
81
		return $bookmark;
82
	}
83
84
	if ( ! is_object( $bookmark ) ) {
85
		return '';
86
	}
87
88
	if ( ! isset( $bookmark->$field ) ) {
89
		return '';
90
	}
91
92
	return sanitize_bookmark_field( $field, $bookmark->$field, $bookmark->link_id, $context );
93
}
94
95
/**
96
 * Retrieves the list of bookmarks
97
 *
98
 * Attempts to retrieve from the cache first based on MD5 hash of arguments. If
99
 * that fails, then the query will be built from the arguments and executed. The
100
 * results will be stored to the cache.
101
 *
102
 * @since 2.1.0
103
 *
104
 * @global wpdb $wpdb WordPress database abstraction object.
105
 *
106
 * @param string|array $args {
107
 *     Optional. String or array of arguments to retrieve bookmarks.
108
 *
109
 *     @type string   $orderby        How to order the links by. Accepts 'id', 'link_id', 'name', 'link_name',
110
 *                                    'url', 'link_url', 'visible', 'link_visible', 'rating', 'link_rating',
111
 *                                    'owner', 'link_owner', 'updated', 'link_updated', 'notes', 'link_notes',
112
 *                                    'description', 'link_description', 'length' and 'rand'.
113
 *                                    When `$orderby` is 'length', orders by the character length of
114
 *                                    'link_name'. Default 'name'.
115
 *     @type string   $order          Whether to order bookmarks in ascending or descending order.
116
 *                                    Accepts 'ASC' (ascending) or 'DESC' (descending). Default 'ASC'.
117
 *     @type int      $limit          Amount of bookmarks to display. Accepts any positive number or
118
 *                                    -1 for all.  Default -1.
119
 *     @type string   $category       Comma-separated list of category ids to include links from.
120
 *                                    Default empty.
121
 *     @type string   $category_name  Category to retrieve links for by name. Default empty.
122
 *     @type int|bool $hide_invisible Whether to show or hide links marked as 'invisible'. Accepts
123
 *                                    1|true or 0|false. Default 1|true.
124
 *     @type int|bool $show_updated   Whether to display the time the bookmark was last updated.
125
 *                                    Accepts 1|true or 0|false. Default 0|false.
126
 *     @type string   $include        Comma-separated list of bookmark IDs to include. Default empty.
127
 *     @type string   $exclude        Comma-separated list of bookmark IDs to exclude. Default empty.
128
 *     @type string   $search         Search terms. Will be SQL-formatted with wildcards before and after
129
 *                                    and searched in 'link_url', 'link_name' and 'link_description'.
130
 *                                    Default empty.
131
 * }
132
 * @return array List of bookmark row objects.
133
 */
134 3
function get_bookmarks( $args = '' ) {
135
	global $wpdb;
136
137
	$defaults = array(
138
		'orderby'        => 'name',
139
		'order'          => 'ASC',
140
		'limit'          => -1,
141
		'category'       => '',
142
		'category_name'  => '',
143
		'hide_invisible' => 1,
144
		'show_updated'   => 0,
145
		'include'        => '',
146
		'exclude'        => '',
147
		'search'         => '',
148
	);
149
150
	$parsed_args = wp_parse_args( $args, $defaults );
151
152
	$key   = md5( serialize( $parsed_args ) );
153
	$cache = wp_cache_get( 'get_bookmarks', 'bookmark' );
154
155
	if ( 'rand' !== $parsed_args['orderby'] && $cache ) {
156
		if ( is_array( $cache ) && isset( $cache[ $key ] ) ) {
157
			$bookmarks = $cache[ $key ];
158
			/**
159
			 * Filters the returned list of bookmarks.
160
			 *
161
			 * The first time the hook is evaluated in this file, it returns the cached
162
			 * bookmarks list. The second evaluation returns a cached bookmarks list if the
163
			 * link category is passed but does not exist. The third evaluation returns
164
			 * the full cached results.
165
			 *
166
			 * @since 2.1.0
167
			 *
168
			 * @see get_bookmarks()
169
			 *
170
			 * @param array $bookmarks List of the cached bookmarks.
171
			 * @param array $parsed_args         An array of bookmark query arguments.
172
			 */
173
			return apply_filters( 'get_bookmarks', $bookmarks, $parsed_args );
174
		}
175
	}
176
177
	if ( ! is_array( $cache ) ) {
178
		$cache = array();
179
	}
180
181
	$inclusions = '';
182
	if ( ! empty( $parsed_args['include'] ) ) {
183
		$parsed_args['exclude']       = '';  //ignore exclude, category, and category_name params if using include
184
		$parsed_args['category']      = '';
185
		$parsed_args['category_name'] = '';
186
187
		$inclinks = wp_parse_id_list( $parsed_args['include'] );
188
		if ( count( $inclinks ) ) {
189
			foreach ( $inclinks as $inclink ) {
190
				if ( empty( $inclusions ) ) {
191
					$inclusions = ' AND ( link_id = ' . $inclink . ' ';
192 1
				} else {
193
					$inclusions .= ' OR link_id = ' . $inclink . ' ';
194
				}
195
			}
196
		}
197
	}
198
	if ( ! empty( $inclusions ) ) {
199
		$inclusions .= ')';
200
	}
201
202
	$exclusions = '';
203
	if ( ! empty( $parsed_args['exclude'] ) ) {
204
		$exlinks = wp_parse_id_list( $parsed_args['exclude'] );
205
		if ( count( $exlinks ) ) {
206
			foreach ( $exlinks as $exlink ) {
207
				if ( empty( $exclusions ) ) {
208
					$exclusions = ' AND ( link_id <> ' . $exlink . ' ';
209 1
				} else {
210
					$exclusions .= ' AND link_id <> ' . $exlink . ' ';
211
				}
212
			}
213
		}
214
	}
215
	if ( ! empty( $exclusions ) ) {
216
		$exclusions .= ')';
217
	}
218
219
	if ( ! empty( $parsed_args['category_name'] ) ) {
220
		$parsed_args['category'] = get_term_by( 'name', $parsed_args['category_name'], 'link_category' );
221
		if ( $parsed_args['category'] ) {
222
			$parsed_args['category'] = $parsed_args['category']->term_id;
223 1
		} else {
224
			$cache[ $key ] = array();
225
			wp_cache_set( 'get_bookmarks', $cache, 'bookmark' );
226
			/** This filter is documented in wp-includes/bookmark.php */
227
			return apply_filters( 'get_bookmarks', array(), $parsed_args );
228
		}
229
	}
230
231
	$search = '';
232
	if ( ! empty( $parsed_args['search'] ) ) {
233
		$like   = '%' . $wpdb->esc_like( $parsed_args['search'] ) . '%';
234
		$search = $wpdb->prepare( ' AND ( (link_url LIKE %s) OR (link_name LIKE %s) OR (link_description LIKE %s) ) ', $like, $like, $like );
235
	}
236
237
	$category_query = '';
238
	$join           = '';
239
	if ( ! empty( $parsed_args['category'] ) ) {
240
		$incategories = wp_parse_id_list( $parsed_args['category'] );
241
		if ( count( $incategories ) ) {
242
			foreach ( $incategories as $incat ) {
243
				if ( empty( $category_query ) ) {
244
					$category_query = ' AND ( tt.term_id = ' . $incat . ' ';
245 1
				} else {
246
					$category_query .= ' OR tt.term_id = ' . $incat . ' ';
247
				}
248
			}
249
		}
250
	}
251
	if ( ! empty( $category_query ) ) {
252
		$category_query .= ") AND taxonomy = 'link_category'";
253
		$join            = " INNER JOIN $wpdb->term_relationships AS tr ON ($wpdb->links.link_id = tr.object_id) INNER JOIN $wpdb->term_taxonomy as tt ON tt.term_taxonomy_id = tr.term_taxonomy_id";
254
	}
255
256
	if ( $parsed_args['show_updated'] ) {
257 1
		$recently_updated_test = ', IF (DATE_ADD(link_updated, INTERVAL 120 MINUTE) >= NOW(), 1,0) as recently_updated ';
258 1
	} else {
259
		$recently_updated_test = '';
260
	}
261
262
	$get_updated = ( $parsed_args['show_updated'] ) ? ', UNIX_TIMESTAMP(link_updated) AS link_updated_f ' : '';
263
264
	$orderby = strtolower( $parsed_args['orderby'] );
265
	$length  = '';
266
	switch ( $orderby ) {
267
		case 'length':
268
			$length = ', CHAR_LENGTH(link_name) AS length';
269
			break;
270
		case 'rand':
271
			$orderby = 'rand()';
272
			break;
273
		case 'link_id':
274
			$orderby = "$wpdb->links.link_id";
275
			break;
276
		default:
277
			$orderparams = array();
278
			$keys        = array( 'link_id', 'link_name', 'link_url', 'link_visible', 'link_rating', 'link_owner', 'link_updated', 'link_notes', 'link_description' );
279
			foreach ( explode( ',', $orderby ) as $ordparam ) {
280
				$ordparam = trim( $ordparam );
281
282
				if ( in_array( 'link_' . $ordparam, $keys ) ) {
283
					$orderparams[] = 'link_' . $ordparam;
284
				} elseif ( in_array( $ordparam, $keys ) ) {
285
					$orderparams[] = $ordparam;
286
				}
287
			}
288
			$orderby = implode( ',', $orderparams );
289
	}
290
291
	if ( empty( $orderby ) ) {
292
		$orderby = 'link_name';
293
	}
294
295
	$order = strtoupper( $parsed_args['order'] );
296
	if ( '' !== $order && ! in_array( $order, array( 'ASC', 'DESC' ) ) ) {
297
		$order = 'ASC';
298
	}
299
300
	$visible = '';
301
	if ( $parsed_args['hide_invisible'] ) {
302
		$visible = "AND link_visible = 'Y'";
303
	}
304
305
	$query  = "SELECT * $length $recently_updated_test $get_updated FROM $wpdb->links $join WHERE 1=1 $visible $category_query";
306
	$query .= " $exclusions $inclusions $search";
307
	$query .= " ORDER BY $orderby $order";
308
	if ( $parsed_args['limit'] != -1 ) {
309
		$query .= ' LIMIT ' . $parsed_args['limit'];
310
	}
311
312
	$results = $wpdb->get_results( $query );
313
314
	if ( 'rand()' !== $orderby ) {
315
		$cache[ $key ] = $results;
316
		wp_cache_set( 'get_bookmarks', $cache, 'bookmark' );
317
	}
318
319
	/** This filter is documented in wp-includes/bookmark.php */
320
	return apply_filters( 'get_bookmarks', $results, $parsed_args );
321
}
322
323
/**
324
 * Sanitizes all bookmark fields
325
 *
326
 * @since 2.3.0
327
 *
328
 * @param stdClass|array $bookmark Bookmark row
329
 * @param string $context Optional, default is 'display'. How to filter the
330
 *      fields
331
 * @return stdClass|array Same type as $bookmark but with fields sanitized.
332
 */
333
function sanitize_bookmark( $bookmark, $context = 'display' ) {
334
	$fields = array(
335
		'link_id',
336
		'link_url',
337
		'link_name',
338
		'link_image',
339
		'link_target',
340
		'link_category',
341
		'link_description',
342
		'link_visible',
343
		'link_owner',
344
		'link_rating',
345
		'link_updated',
346
		'link_rel',
347
		'link_notes',
348
		'link_rss',
349
	);
350
351
	if ( is_object( $bookmark ) ) {
352
		$do_object = true;
353
		$link_id   = $bookmark->link_id;
354 1
	} else {
355
		$do_object = false;
356
		$link_id   = $bookmark['link_id'];
357
	}
358
359
	foreach ( $fields as $field ) {
360
		if ( $do_object ) {
361
			if ( isset( $bookmark->$field ) ) {
362
				$bookmark->$field = sanitize_bookmark_field( $field, $bookmark->$field, $link_id, $context );
363
			}
364
		} else {
365
			if ( isset( $bookmark[ $field ] ) ) {
366
				$bookmark[ $field ] = sanitize_bookmark_field( $field, $bookmark[ $field ], $link_id, $context );
367
			}
368
		}
369
	}
370
371
	return $bookmark;
372
}
373
374
/**
375
 * Sanitizes a bookmark field.
376
 *
377
 * Sanitizes the bookmark fields based on what the field name is. If the field
378
 * has a strict value set, then it will be tested for that, else a more generic
379
 * filtering is applied. After the more strict filter is applied, if the `$context`
380
 * is 'raw' then the value is immediately return.
381
 *
382
 * Hooks exist for the more generic cases. With the 'edit' context, the {@see 'edit_$field'}
383
 * filter will be called and passed the `$value` and `$bookmark_id` respectively.
384
 *
385
 * With the 'db' context, the {@see 'pre_$field'} filter is called and passed the value.
386
 * The 'display' context is the final context and has the `$field` has the filter name
387
 * and is passed the `$value`, `$bookmark_id`, and `$context`, respectively.
388
 *
389
 * @since 2.3.0
390
 *
391
 * @param string $field       The bookmark field.
392
 * @param mixed  $value       The bookmark field value.
393
 * @param int    $bookmark_id Bookmark ID.
394
 * @param string $context     How to filter the field value. Accepts 'raw', 'edit', 'attribute',
395
 *                            'js', 'db', or 'display'
396
 * @return mixed The filtered value.
397
 */
398 2
function sanitize_bookmark_field( $field, $value, $bookmark_id, $context ) {
CyclomaticComplexity The function sanitize_bookmark_field() has a Cyclomatic Complexity of 13. The configured cyclomatic complexity threshold is 10. (kritika/PHPMD) Filter like this
CamelCaseParameterName The parameter $bookmark_id is not named in camelCase. (kritika/PHPMD) Filter like this
399
	switch ( $field ) {
400
		case 'link_id': // ints
401
		case 'link_rating':
402
			$value = (int) $value;
403
			break;
404
		case 'link_category': // array( ints )
405
			$value = array_map( 'absint', (array) $value );
406
			// We return here so that the categories aren't filtered.
407
			// The 'link_category' filter is for the name of a link category, not an array of a link's link categories
408
			return $value;
409
410
		case 'link_visible': // bool stored as Y|N
411
			$value = preg_replace( '/[^YNyn]/', '', $value );
412
			break;
413
		case 'link_target': // "enum"
414
			$targets = array( '_top', '_blank' );
415
			if ( ! in_array( $value, $targets ) ) {
416
				$value = '';
417
			}
418
			break;
419
	}
420
421
	if ( 'raw' == $context ) {
422
		return $value;
423
	}
424
425
	if ( 'edit' == $context ) {
426
		/** This filter is documented in wp-includes/post.php */
427
		$value = apply_filters( "edit_{$field}", $value, $bookmark_id );
428
429
		if ( 'link_notes' == $field ) {
430
			$value = esc_html( $value ); // textarea_escaped
431
		} else {
432
			$value = esc_attr( $value );
433
		}
434
	} elseif ( 'db' == $context ) {
435
		/** This filter is documented in wp-includes/post.php */
436
		$value = apply_filters( "pre_{$field}", $value );
437
	} else {
438
		/** This filter is documented in wp-includes/post.php */
439
		$value = apply_filters( "{$field}", $value, $bookmark_id, $context );
440
441
		if ( 'attribute' == $context ) {
442
			$value = esc_attr( $value );
443
		} elseif ( 'js' == $context ) {
444
			$value = esc_js( $value );
445
		}
446
	}
447
448
	return $value;
449
}
450
451
/**
452
 * Deletes the bookmark cache.
453
 *
454
 * @since 2.7.0
455
 *
456
 * @param int $bookmark_id Bookmark ID.
457
 */
458 1
function clean_bookmark_cache( $bookmark_id ) {
459
	wp_cache_delete( $bookmark_id, 'bookmark' );
460
	wp_cache_delete( 'get_bookmarks', 'bookmark' );
461
	clean_object_term_cache( $bookmark_id, 'link' );
462
}