|
1
|
|
|
<?php |
|
2
|
|
|
/** |
|
3
|
|
|
* Services: Relation Service. |
|
4
|
|
|
* |
|
5
|
|
|
* The Relation Service provides helpful function to get related posts/entities. |
|
6
|
|
|
* |
|
7
|
|
|
* @since 3.15.0 |
|
8
|
|
|
* @package Wordlift |
|
9
|
|
|
* @subpackage Wordlift/includes |
|
10
|
|
|
*/ |
|
11
|
|
|
|
|
12
|
|
|
/** |
|
13
|
|
|
* Define the {@link Wordlift_Relation_Service} class. |
|
14
|
|
|
* |
|
15
|
|
|
* @since 3.15.0 |
|
16
|
|
|
* @package Wordlift |
|
17
|
|
|
* @subpackage Wordlift/includes |
|
18
|
|
|
*/ |
|
19
|
|
|
class Wordlift_Relation_Service { |
|
20
|
|
|
|
|
21
|
|
|
/** |
|
22
|
|
|
* The singleton instance. |
|
23
|
|
|
* |
|
24
|
|
|
* @since 3.15.0 |
|
25
|
|
|
* @access private |
|
26
|
|
|
* @var \Wordlift_Relation_Service $instance The singleton instance. |
|
27
|
|
|
*/ |
|
28
|
|
|
private static $instance; |
|
29
|
|
|
|
|
30
|
|
|
/** |
|
31
|
|
|
* The relation table name in MySQL, set during instantiation. |
|
32
|
|
|
* |
|
33
|
|
|
* @since 3.15.0 |
|
34
|
|
|
* @access private |
|
35
|
|
|
* @var string $relation_table The relation table name. |
|
36
|
|
|
*/ |
|
37
|
|
|
private $relation_table; |
|
38
|
|
|
|
|
39
|
|
|
/** |
|
40
|
|
|
* A {@link Wordlift_Log_Service} instance. |
|
41
|
|
|
* |
|
42
|
|
|
* @since 3.15.3 |
|
43
|
|
|
* |
|
44
|
|
|
* @var Wordlift_Log_Service $log A {@link Wordlift_Log_Service} instance. |
|
45
|
|
|
*/ |
|
46
|
|
|
private static $log; |
|
47
|
|
|
|
|
48
|
|
|
/** |
|
49
|
|
|
* Create a {@link Wordlift_Relation_Service} instance. |
|
50
|
|
|
* |
|
51
|
|
|
* @since 3.15.0 |
|
52
|
|
|
*/ |
|
53
|
|
|
public function __construct() { |
|
54
|
|
|
|
|
55
|
|
|
self::$log = Wordlift_Log_Service::get_logger( get_class() ); |
|
56
|
|
|
|
|
57
|
|
|
global $wpdb; |
|
58
|
|
|
|
|
59
|
|
|
// The relations table. |
|
60
|
|
|
$this->relation_table = "{$wpdb->prefix}wl_relation_instances"; |
|
61
|
|
|
|
|
62
|
|
|
self::$instance = $this; |
|
63
|
|
|
|
|
64
|
|
|
} |
|
65
|
|
|
|
|
66
|
|
|
/** |
|
67
|
|
|
* Get the singleton instance. |
|
68
|
|
|
* |
|
69
|
|
|
* @return \Wordlift_Relation_Service The {@link Wordlift_Relation_Service} |
|
70
|
|
|
* singleton instance. |
|
71
|
|
|
* @since 3.15.0 |
|
72
|
|
|
* @access public |
|
73
|
|
|
*/ |
|
74
|
|
|
public static function get_instance() { |
|
75
|
|
|
|
|
76
|
|
|
return self::$instance; |
|
77
|
|
|
} |
|
78
|
|
|
|
|
79
|
|
|
/** |
|
80
|
|
|
* Get the articles referencing the specified entity {@link WP_Post}. |
|
81
|
|
|
* |
|
82
|
|
|
* @param int|array $object_id The entity {@link WP_Post}'s id. |
|
83
|
|
|
* @param string $fields The fields to return, 'ids' to only return ids or |
|
84
|
|
|
* '*' to return all fields, by default '*'. |
|
85
|
|
|
* @param null|string $predicate The predicate (who|what|...), by default all. |
|
86
|
|
|
* @param null|string $status The status, by default all. |
|
87
|
|
|
* @param array $excludes An array of ids to exclude from the results. |
|
88
|
|
|
* @param null|int $limit The maximum number of results, by default |
|
89
|
|
|
* no limit. |
|
90
|
|
|
* @param null|array $include The {@link WP_Post}s' ids to include. |
|
91
|
|
|
* |
|
92
|
|
|
* @param null | string $order_by |
|
93
|
|
|
* |
|
94
|
|
|
* @return array|object|null Database query results |
|
95
|
|
|
* @since 3.15.0 |
|
96
|
|
|
* |
|
97
|
|
|
*/ |
|
98
|
|
|
public function get_article_subjects( $object_id, $fields = '*', $predicate = null, $status = null, $excludes = array(), $limit = null, $include = null, $order_by = null ) { |
|
99
|
|
|
global $wpdb; |
|
100
|
|
|
|
|
101
|
|
|
// The output fields. |
|
102
|
|
|
$actual_fields = self::fields( $fields ); |
|
103
|
|
|
|
|
104
|
|
|
self::$log->trace( 'Getting article subjects for object ' . implode( ', ', (array) $object_id ) . '...' ); |
|
105
|
|
|
|
|
106
|
|
|
$objects = $this->article_id_to_entity_id( $object_id ); |
|
107
|
|
|
|
|
108
|
|
|
// If there are no related objects, return an empty array. |
|
109
|
|
|
if ( empty( $objects ) ) { |
|
110
|
|
|
self::$log->debug( 'No entities found for object ' . implode( ', ', (array) $object_id ) . '.' ); |
|
111
|
|
|
|
|
112
|
|
|
return array(); |
|
113
|
|
|
} |
|
114
|
|
|
|
|
115
|
|
|
self::$log->debug( count( $objects ) . ' entity id(s) found for object ' . implode( ', ', (array) $object_id ) . '.' ); |
|
116
|
|
|
|
|
117
|
|
|
$sql = |
|
118
|
|
|
" |
|
119
|
|
|
SELECT DISTINCT p.$actual_fields |
|
120
|
|
|
FROM {$this->relation_table} r |
|
121
|
|
|
INNER JOIN $wpdb->posts p |
|
122
|
|
|
ON p.id = r.subject_id |
|
123
|
|
|
" |
|
124
|
|
|
// Add the status clause. |
|
125
|
|
|
. self::and_status( $status ) |
|
126
|
|
|
. self::inner_join_is_article() |
|
127
|
|
|
. self::where_object_id( $objects ) |
|
128
|
|
|
// Since `object_id` can be an article ID we need to exclude it from |
|
129
|
|
|
// the results. |
|
130
|
|
|
. self::and_article_not_in( array_merge( $excludes, (array) $object_id ) ) |
|
131
|
|
|
. self::and_article_in( $include ) |
|
132
|
|
|
. self::and_post_type_in() |
|
133
|
|
|
. self::and_predicate( $predicate ) |
|
134
|
|
|
. self::order_by( $order_by ) |
|
135
|
|
|
. self::limit( $limit ); |
|
136
|
|
|
|
|
137
|
|
|
return '*' === $actual_fields ? $wpdb->get_results( $sql ) : $wpdb->get_col( $sql ); |
|
138
|
|
|
} |
|
139
|
|
|
|
|
140
|
|
|
/** |
|
141
|
|
|
* The `post_type IN` clause. |
|
142
|
|
|
* |
|
143
|
|
|
* @return string The `post_type IN` clause. |
|
144
|
|
|
* @since 3.15.3 |
|
145
|
|
|
* |
|
146
|
|
|
*/ |
|
147
|
|
|
private static function and_post_type_in() { |
|
148
|
|
|
|
|
149
|
|
|
return " AND p.post_type IN ( '" |
|
150
|
|
|
. implode( |
|
151
|
|
|
"','", |
|
152
|
|
|
array_map( 'esc_sql', Wordlift_Entity_Service::valid_entity_post_types() ) |
|
153
|
|
|
) |
|
154
|
|
|
. "' )"; |
|
155
|
|
|
} |
|
156
|
|
|
|
|
157
|
|
|
/** |
|
158
|
|
|
* Add the limit clause if specified. |
|
159
|
|
|
* |
|
160
|
|
|
* @param null|int $limit The maximum number of results. |
|
161
|
|
|
* |
|
162
|
|
|
* @return string The limit clause (empty if no limit has been specified). |
|
163
|
|
|
* @since 3.15.0 |
|
164
|
|
|
* |
|
165
|
|
|
*/ |
|
166
|
|
|
private static function limit( $limit = null ) { |
|
167
|
|
|
|
|
168
|
|
|
if ( null === $limit ) { |
|
169
|
|
|
return ''; |
|
170
|
|
|
} |
|
171
|
|
|
|
|
172
|
|
|
return "LIMIT $limit"; |
|
173
|
|
|
} |
|
174
|
|
|
|
|
175
|
|
|
/** |
|
176
|
|
|
* @param $order_by string | null |
|
177
|
|
|
* |
|
178
|
|
|
* @return string |
|
179
|
|
|
*/ |
|
180
|
|
|
private static function order_by( $order_by ) { |
|
181
|
|
|
if ( ! $order_by ) { |
|
182
|
|
|
return ''; |
|
183
|
|
|
} |
|
184
|
|
|
$order_by = (string) $order_by; |
|
185
|
|
|
$order_by_clauses = array( 'DESC', 'ASC' ); |
|
186
|
|
|
|
|
187
|
|
|
if ( in_array( $order_by, $order_by_clauses, true ) ) { |
|
188
|
|
|
return " ORDER BY p.ID ${order_by} "; |
|
189
|
|
|
} else { |
|
190
|
|
|
return ' ORDER BY p.ID DESC '; |
|
191
|
|
|
} |
|
192
|
|
|
} |
|
193
|
|
|
|
|
194
|
|
|
/** |
|
195
|
|
|
* Map the provided ids into entities (i.e. return the id if it's an entity |
|
196
|
|
|
* or get the entities if it's a post). |
|
197
|
|
|
* |
|
198
|
|
|
* @param int|array $object_id An array of posts/entities' ids. |
|
199
|
|
|
* |
|
200
|
|
|
* @return array An array of entities' ids. |
|
201
|
|
|
* @since 3.15.0 |
|
202
|
|
|
* |
|
203
|
|
|
*/ |
|
204
|
|
|
private function article_id_to_entity_id( $object_id ) { |
|
205
|
|
|
|
|
206
|
|
|
$entity_service = Wordlift_Entity_Service::get_instance(); |
|
207
|
|
|
|
|
208
|
|
|
$relation_service = $this; |
|
209
|
|
|
|
|
210
|
|
|
return array_reduce( (array) $object_id, function ( $carry, $item ) use ( $entity_service, $relation_service ) { |
|
211
|
|
|
if ( $entity_service->is_entity( $item ) ) { |
|
212
|
|
|
return array_merge( $carry, (array) $item ); |
|
213
|
|
|
} |
|
214
|
|
|
|
|
215
|
|
|
return array_merge( $carry, $relation_service->get_objects( $item, 'ids' ) ); |
|
216
|
|
|
}, array() ); |
|
217
|
|
|
|
|
218
|
|
|
} |
|
219
|
|
|
|
|
220
|
|
|
/** |
|
221
|
|
|
* Add the WHERE clause. |
|
222
|
|
|
* |
|
223
|
|
|
* @param int|array $object_id An array of {@link WP_Post}s' ids. |
|
224
|
|
|
* |
|
225
|
|
|
* @return string The WHERE clause. |
|
226
|
|
|
* @since 3.15.0 |
|
227
|
|
|
* |
|
228
|
|
|
*/ |
|
229
|
|
|
private static function where_object_id( $object_id ) { |
|
230
|
|
|
|
|
231
|
|
|
if ( empty( $object_id ) ) { |
|
232
|
|
|
// self::$log->warn( sprintf( "%s `where_object_id` called with empty `object_id`.", var_export( debug_backtrace( false, 3 ), true ) ) ); |
|
233
|
|
|
|
|
234
|
|
|
return ' WHERE 1 = 1'; |
|
235
|
|
|
} |
|
236
|
|
|
|
|
237
|
|
|
return ' WHERE r.object_id IN ( ' . implode( ',', wp_parse_id_list( (array) $object_id ) ) . ' )'; |
|
238
|
|
|
} |
|
239
|
|
|
|
|
240
|
|
|
/** |
|
241
|
|
|
* Add the exclude clause. |
|
242
|
|
|
* |
|
243
|
|
|
* @param int|array $exclude An array of {@link WP_Post}s' ids to exclude. |
|
244
|
|
|
* |
|
245
|
|
|
* @return string The exclude clause. |
|
246
|
|
|
* @since 3.15.0 |
|
247
|
|
|
* |
|
248
|
|
|
*/ |
|
249
|
|
|
private static function and_article_not_in( $exclude ) { |
|
250
|
|
|
|
|
251
|
|
|
return ' AND NOT p.ID IN ( ' . implode( ',', wp_parse_id_list( (array) $exclude ) ) . ' )'; |
|
252
|
|
|
} |
|
253
|
|
|
|
|
254
|
|
|
/** |
|
255
|
|
|
* Add the include clause. |
|
256
|
|
|
* |
|
257
|
|
|
* @param null|int|array $include An array of {@link WP_Post}s' ids. |
|
258
|
|
|
* |
|
259
|
|
|
* @return string An empty string if $include is null otherwise the include |
|
260
|
|
|
* clause. |
|
261
|
|
|
* @since 3.15.0 |
|
262
|
|
|
* |
|
263
|
|
|
*/ |
|
264
|
|
|
private static function and_article_in( $include = null ) { |
|
265
|
|
|
|
|
266
|
|
|
if ( null === $include ) { |
|
267
|
|
|
return ''; |
|
268
|
|
|
} |
|
269
|
|
|
|
|
270
|
|
|
return ' AND p.ID IN ( ' . implode( ',', wp_parse_id_list( (array) $include ) ) . ' )'; |
|
271
|
|
|
} |
|
272
|
|
|
|
|
273
|
|
|
/** |
|
274
|
|
|
* Get the entities' {@link WP_Post}s' ids referencing the specified {@link WP_Post}. |
|
275
|
|
|
* |
|
276
|
|
|
* @param int $object_id The object {@link WP_Post}'s id. |
|
277
|
|
|
* @param string $fields The fields to return, 'ids' to only return ids or |
|
278
|
|
|
* '*' to return all fields, by default '*'. |
|
279
|
|
|
* @param null|string $status The status, by default all. |
|
280
|
|
|
* |
|
281
|
|
|
* @return array|object|null Database query results |
|
282
|
|
|
* @since 3.15.0 |
|
283
|
|
|
* |
|
284
|
|
|
*/ |
|
285
|
|
View Code Duplication |
public function get_non_article_subjects( $object_id, $fields = '*', $status = null ) { |
|
|
|
|
|
|
286
|
|
|
global $wpdb; |
|
287
|
|
|
|
|
288
|
|
|
// The output fields. |
|
289
|
|
|
$actual_fields = self::fields( $fields ); |
|
290
|
|
|
|
|
291
|
|
|
$sql = $wpdb->prepare( |
|
292
|
|
|
" |
|
293
|
|
|
SELECT p.$actual_fields |
|
294
|
|
|
FROM {$this->relation_table} r |
|
295
|
|
|
INNER JOIN $wpdb->posts p |
|
296
|
|
|
ON p.id = r.subject_id |
|
297
|
|
|
" |
|
298
|
|
|
// Add the status clause. |
|
299
|
|
|
. self::and_status( $status ) |
|
300
|
|
|
. self::inner_join_is_not_article() |
|
301
|
|
|
. " WHERE r.object_id = %d " |
|
302
|
|
|
. self::and_post_type_in() |
|
303
|
|
|
, |
|
304
|
|
|
$object_id |
|
305
|
|
|
); |
|
306
|
|
|
|
|
307
|
|
|
return '*' === $actual_fields ? $wpdb->get_results( $sql ) : $wpdb->get_col( $sql ); |
|
308
|
|
|
} |
|
309
|
|
|
|
|
310
|
|
|
/** |
|
311
|
|
|
* Get the entities referenced by the specified {@link WP_Post}. |
|
312
|
|
|
* |
|
313
|
|
|
* @param int $subject_id The {@link WP_Post}'s id. |
|
314
|
|
|
* @param string $fields The fields to return, 'ids' to only return ids or |
|
315
|
|
|
* '*' to return all fields, by default '*'. |
|
316
|
|
|
* @param null|string $predicate The predicate (who|what|...), by default all. |
|
317
|
|
|
* @param null|string $status The status, by default all. |
|
318
|
|
|
* |
|
319
|
|
|
* @return array|object|null Database query results |
|
320
|
|
|
* @since 3.15.0 |
|
321
|
|
|
* |
|
322
|
|
|
*/ |
|
323
|
|
View Code Duplication |
public function get_objects( $subject_id, $fields = '*', $predicate = null, $status = null ) { |
|
|
|
|
|
|
324
|
|
|
global $wpdb; |
|
325
|
|
|
|
|
326
|
|
|
// The output fields. |
|
327
|
|
|
$actual_fields = self::fields( $fields ); |
|
328
|
|
|
|
|
329
|
|
|
$sql = $wpdb->prepare( |
|
330
|
|
|
" |
|
331
|
|
|
SELECT p.$actual_fields |
|
332
|
|
|
FROM {$this->relation_table} r |
|
333
|
|
|
INNER JOIN $wpdb->posts p |
|
334
|
|
|
ON p.id = r.object_id |
|
335
|
|
|
" |
|
336
|
|
|
// Add the status clause. |
|
337
|
|
|
. self::and_status( $status ) |
|
338
|
|
|
. self::inner_join_is_not_article() |
|
339
|
|
|
. " WHERE r.subject_id = %d " |
|
340
|
|
|
. self::and_post_type_in() |
|
341
|
|
|
. self::and_predicate( $predicate ) |
|
342
|
|
|
, |
|
343
|
|
|
$subject_id |
|
344
|
|
|
); |
|
345
|
|
|
|
|
346
|
|
|
return '*' === $actual_fields ? $wpdb->get_results( $sql ) : $wpdb->get_col( $sql ); |
|
347
|
|
|
} |
|
348
|
|
|
|
|
349
|
|
|
/** |
|
350
|
|
|
* Add the `post_status` clause. |
|
351
|
|
|
* |
|
352
|
|
|
* @param null|string|array $status The status values. |
|
353
|
|
|
* |
|
354
|
|
|
* @return string An empty string if $status is null, otherwise the status clause. |
|
355
|
|
|
* @since 3.15.0 |
|
356
|
|
|
* |
|
357
|
|
|
*/ |
|
358
|
|
View Code Duplication |
private static function and_status( $status = null ) { |
|
|
|
|
|
|
359
|
|
|
|
|
360
|
|
|
if ( null === $status ) { |
|
361
|
|
|
return ''; |
|
362
|
|
|
} |
|
363
|
|
|
|
|
364
|
|
|
return " AND p.post_status IN ('" . implode( "', '", array_map( 'esc_sql', (array) $status ) ) . "')"; |
|
365
|
|
|
} |
|
366
|
|
|
|
|
367
|
|
|
/** |
|
368
|
|
|
* Add the `predicate` clause. |
|
369
|
|
|
* |
|
370
|
|
|
* @param null|string|array $predicate An array of predicates. |
|
371
|
|
|
* |
|
372
|
|
|
* @return string An empty string if $predicate is null otherwise the predicate |
|
373
|
|
|
* clause. |
|
374
|
|
|
* @since 3.15.0 |
|
375
|
|
|
* |
|
376
|
|
|
*/ |
|
377
|
|
View Code Duplication |
private static function and_predicate( $predicate = null ) { |
|
|
|
|
|
|
378
|
|
|
|
|
379
|
|
|
if ( null === $predicate ) { |
|
380
|
|
|
return ''; |
|
381
|
|
|
} |
|
382
|
|
|
|
|
383
|
|
|
return " AND r.predicate IN ('" . implode( "', '", array_map( 'esc_sql', (array) $predicate ) ) . "')"; |
|
384
|
|
|
} |
|
385
|
|
|
|
|
386
|
|
|
/** |
|
387
|
|
|
* The select fields. |
|
388
|
|
|
* |
|
389
|
|
|
* @param string $fields Either 'ids' or '*', by default '*'. |
|
390
|
|
|
* |
|
391
|
|
|
* @return string The `id` field if `ids` otherwise `*`. |
|
392
|
|
|
* @since 3.15.0 |
|
393
|
|
|
* |
|
394
|
|
|
*/ |
|
395
|
|
|
private static function fields( $fields = '*' ) { |
|
396
|
|
|
|
|
397
|
|
|
// The output fields. |
|
398
|
|
|
return 'ids' === $fields ? 'id' : '*'; |
|
399
|
|
|
} |
|
400
|
|
|
|
|
401
|
|
|
/** |
|
402
|
|
|
* The inner join clause for articles. |
|
403
|
|
|
* |
|
404
|
|
|
* @return string The articles inner join clause. |
|
405
|
|
|
* @since 3.15.0 |
|
406
|
|
|
* |
|
407
|
|
|
*/ |
|
408
|
|
|
private static function inner_join_is_article() { |
|
409
|
|
|
global $wpdb; |
|
410
|
|
|
|
|
411
|
|
|
return $wpdb->prepare( |
|
412
|
|
|
" |
|
413
|
|
|
INNER JOIN $wpdb->term_relationships tr |
|
414
|
|
|
ON p.id = tr.object_id |
|
415
|
|
|
INNER JOIN $wpdb->term_taxonomy tt |
|
416
|
|
|
ON tt.term_taxonomy_id = tr.term_taxonomy_id |
|
417
|
|
|
AND tt.taxonomy = %s |
|
418
|
|
|
INNER JOIN $wpdb->terms t |
|
419
|
|
|
ON t.term_id = tt.term_id |
|
420
|
|
|
AND t.slug = %s |
|
421
|
|
|
", |
|
422
|
|
|
'wl_entity_type', |
|
423
|
|
|
'article' |
|
424
|
|
|
); |
|
425
|
|
|
} |
|
426
|
|
|
|
|
427
|
|
|
/** |
|
428
|
|
|
* The inner join clause for non-articles. |
|
429
|
|
|
* |
|
430
|
|
|
* @return string The non-articles inner join clause. |
|
431
|
|
|
* @since 3.15.0 |
|
432
|
|
|
* |
|
433
|
|
|
*/ |
|
434
|
|
|
private static function inner_join_is_not_article() { |
|
435
|
|
|
global $wpdb; |
|
436
|
|
|
|
|
437
|
|
|
return $wpdb->prepare( |
|
438
|
|
|
" |
|
439
|
|
|
INNER JOIN $wpdb->term_relationships tr |
|
440
|
|
|
ON p.id = tr.object_id |
|
441
|
|
|
INNER JOIN $wpdb->term_taxonomy tt |
|
442
|
|
|
ON tt.term_taxonomy_id = tr.term_taxonomy_id |
|
443
|
|
|
AND tt.taxonomy = %s |
|
444
|
|
|
INNER JOIN $wpdb->terms t |
|
445
|
|
|
ON t.term_id = tt.term_id |
|
446
|
|
|
AND NOT t.slug = %s |
|
447
|
|
|
", |
|
448
|
|
|
'wl_entity_type', |
|
449
|
|
|
'article' |
|
450
|
|
|
); |
|
451
|
|
|
} |
|
452
|
|
|
|
|
453
|
|
|
/** |
|
454
|
|
|
* Find all the subject IDs and their referenced/related object IDs. The |
|
455
|
|
|
* object IDs are returned as comma separated IDs in the `object_ids` key. |
|
456
|
|
|
* |
|
457
|
|
|
* @return mixed Database query results |
|
458
|
|
|
* @since 3.18.0 |
|
459
|
|
|
*/ |
|
460
|
|
|
public function find_all_grouped_by_subject_id() { |
|
461
|
|
|
global $wpdb; |
|
462
|
|
|
|
|
463
|
|
|
return $wpdb->get_results( |
|
464
|
|
|
" |
|
465
|
|
|
SELECT subject_id, GROUP_CONCAT( DISTINCT object_id ORDER BY object_id SEPARATOR ',' ) AS object_ids |
|
466
|
|
|
FROM $this->relation_table |
|
467
|
|
|
GROUP BY subject_id |
|
468
|
|
|
" |
|
469
|
|
|
); |
|
470
|
|
|
|
|
471
|
|
|
} |
|
472
|
|
|
|
|
473
|
|
|
} |
|
474
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.