|
1
|
|
|
<?php |
|
2
|
|
|
/** |
|
3
|
|
|
* Shortcodes: Glossary Shortcode. |
|
4
|
|
|
* |
|
5
|
|
|
* `wl_vocabulary` implementation. |
|
6
|
|
|
* |
|
7
|
|
|
* @since 3.16.0 |
|
8
|
|
|
* @package Wordlift |
|
9
|
|
|
* @subpackage Wordlift/includes |
|
10
|
|
|
*/ |
|
11
|
|
|
|
|
12
|
|
|
/** |
|
13
|
|
|
* Define the {@link Wordlift_Glossary_Shortcode} class. |
|
14
|
|
|
* |
|
15
|
|
|
* @since 3.16.0 |
|
16
|
|
|
* @package Wordlift |
|
17
|
|
|
* @subpackage Wordlift/includes |
|
18
|
|
|
*/ |
|
19
|
|
|
class Wordlift_Vocabulary_Shortcode extends Wordlift_Shortcode { |
|
20
|
|
|
|
|
21
|
|
|
/** |
|
22
|
|
|
* The shortcode. |
|
23
|
|
|
* |
|
24
|
|
|
* @since 3.17.0 |
|
25
|
|
|
*/ |
|
26
|
|
|
const SHORTCODE = 'wl_vocabulary'; |
|
27
|
|
|
|
|
28
|
|
|
/** |
|
29
|
|
|
* The {@link Wordlift_Configuration_Service} instance. |
|
30
|
|
|
* |
|
31
|
|
|
* @since 3.11.0 |
|
32
|
|
|
* @access private |
|
33
|
|
|
* @var \Wordlift_Configuration_Service $configuration_service The {@link Wordlift_Configuration_Service} instance. |
|
34
|
|
|
*/ |
|
35
|
|
|
private $configuration_service; |
|
36
|
|
|
|
|
37
|
|
|
/** |
|
38
|
|
|
* A {@link Wordlift_Log_Service} instance. |
|
39
|
|
|
* |
|
40
|
|
|
* @since 3.17.0 |
|
41
|
|
|
* @access private |
|
42
|
|
|
* @var \Wordlift_Log_Service $log A {@link Wordlift_Log_Service} instance. |
|
43
|
|
|
*/ |
|
44
|
|
|
private $log; |
|
45
|
|
|
|
|
46
|
|
|
/** |
|
47
|
|
|
* The vocabulary id |
|
48
|
|
|
* |
|
49
|
|
|
* @since 3.18.3 |
|
50
|
|
|
* @access private |
|
51
|
|
|
* @var int $vocabulary_id The vocabulary unique id. |
|
52
|
|
|
*/ |
|
53
|
|
|
private static $vocabulary_id = 0; |
|
54
|
|
|
|
|
55
|
|
|
/** |
|
56
|
|
|
* Create a {@link Wordlift_Glossary_Shortcode} instance. |
|
57
|
|
|
* |
|
58
|
|
|
* @since 3.16.0 |
|
59
|
|
|
* |
|
60
|
|
|
* @param \Wordlift_Configuration_Service $configuration_service The {@link Wordlift_Configuration_Service} instance. |
|
61
|
|
|
*/ |
|
62
|
|
|
public function __construct( $configuration_service ) { |
|
63
|
|
|
parent::__construct(); |
|
64
|
|
|
|
|
65
|
|
|
$this->log = Wordlift_Log_Service::get_logger( get_class() ); |
|
66
|
|
|
|
|
67
|
|
|
$this->configuration_service = $configuration_service; |
|
68
|
|
|
|
|
69
|
|
|
} |
|
70
|
|
|
|
|
71
|
|
|
/** |
|
72
|
|
|
* Check whether the requirements for this shortcode to work are available. |
|
73
|
|
|
* |
|
74
|
|
|
* @since 3.17.0 |
|
75
|
|
|
* @return bool True if the requirements are satisfied otherwise false. |
|
76
|
|
|
*/ |
|
77
|
|
|
private static function are_requirements_satisfied() { |
|
78
|
|
|
|
|
79
|
|
|
return function_exists( 'mb_strlen' ) && |
|
80
|
|
|
function_exists( 'mb_substr' ) && |
|
81
|
|
|
function_exists( 'mb_strtolower' ) && |
|
82
|
|
|
function_exists( 'mb_strtoupper' ) && |
|
83
|
|
|
function_exists( 'mb_convert_case' ); |
|
84
|
|
|
} |
|
85
|
|
|
|
|
86
|
|
|
/** |
|
87
|
|
|
* Render the shortcode. |
|
88
|
|
|
* |
|
89
|
|
|
* @since 3.16.0 |
|
90
|
|
|
* |
|
91
|
|
|
* @param array $atts An array of shortcode attributes as set by the editor. |
|
92
|
|
|
* |
|
93
|
|
|
* @return string The output html code. |
|
94
|
|
|
*/ |
|
95
|
|
|
public function render( $atts ) { |
|
96
|
|
|
|
|
97
|
|
|
// Bail out if the requirements aren't satisfied: we need mbstring for |
|
98
|
|
|
// the vocabulary widget to work. |
|
99
|
|
|
if ( ! self::are_requirements_satisfied() ) { |
|
100
|
|
|
$this->log->warn( "The vocabulary widget cannot be displayed because this WordPress installation doesn't satisfy its requirements." ); |
|
101
|
|
|
|
|
102
|
|
|
return ''; |
|
103
|
|
|
} |
|
104
|
|
|
|
|
105
|
|
|
wp_enqueue_style( 'wl-vocabulary-shortcode', dirname( plugin_dir_url( __FILE__ ) ) . '/public/css/wordlift-vocabulary-shortcode.css' ); |
|
106
|
|
|
|
|
107
|
|
|
// Extract attributes and set default values. |
|
108
|
|
|
$atts = shortcode_atts( |
|
109
|
|
|
array( |
|
110
|
|
|
// The entity type, such as `person`, `organization`, ... |
|
|
|
|
|
|
111
|
|
|
'type' => 'all', |
|
112
|
|
|
// Limit the number of posts to 100 by default. Use -1 to remove the limit. |
|
113
|
|
|
'limit' => 100, |
|
114
|
|
|
// Sort by title. |
|
115
|
|
|
'orderby' => 'post_date', |
|
116
|
|
|
// Sort DESC. |
|
117
|
|
|
'order' => 'DESC', |
|
118
|
|
|
// Allow to specify the category ID. |
|
119
|
|
|
'cat' => '', |
|
120
|
|
|
), $atts |
|
121
|
|
|
); |
|
122
|
|
|
|
|
123
|
|
|
// Get the posts. Note that if a `type` is specified before, then the |
|
124
|
|
|
// `tax_query` from the `add_criterias` call isn't added. |
|
125
|
|
|
$posts = $this->get_posts( $atts ); |
|
126
|
|
|
|
|
127
|
|
|
// Get the alphabet. |
|
128
|
|
|
$language_code = $this->configuration_service->get_language_code(); |
|
129
|
|
|
$alphabet = Wordlift_Alphabet_Service::get( $language_code ); |
|
130
|
|
|
|
|
131
|
|
|
// Add posts to the alphabet. |
|
132
|
|
|
foreach ( $posts as $post ) { |
|
133
|
|
|
$this->add_to_alphabet( $alphabet, $post->ID ); |
|
134
|
|
|
} |
|
135
|
|
|
|
|
136
|
|
|
$header = ''; |
|
137
|
|
|
$sections = ''; |
|
138
|
|
|
|
|
139
|
|
|
// Get unique id for each vocabulary shortcode. |
|
140
|
|
|
$vocabulary_id = self::get_and_increment_vocabulary_id(); |
|
141
|
|
|
|
|
142
|
|
|
// Generate the header. |
|
143
|
|
|
foreach ( $alphabet as $item => $translations ) { |
|
144
|
|
|
$template = ( empty( $translations ) |
|
145
|
|
|
? '<span class="wl-vocabulary-widget-disabled">%s</span>' |
|
146
|
|
|
: '<a href="#wl-vocabulary-%3$d-%2$s">%1$s</a>' ); |
|
147
|
|
|
|
|
148
|
|
|
$header .= sprintf( $template, esc_html( $item ), esc_attr( $item ), $vocabulary_id ); |
|
149
|
|
|
} |
|
150
|
|
|
|
|
151
|
|
|
// Generate the sections. |
|
152
|
|
|
foreach ( $alphabet as $item => $translations ) { |
|
153
|
|
|
// @since 3.19.3 we use `mb_strtolower` and `mb_strtoupper` with a custom function to handle sorting, |
|
154
|
|
|
// since we had `AB` being placed before `Aa` with `asort`. |
|
155
|
|
|
// |
|
156
|
|
|
// Order the translations alphabetically. |
|
157
|
|
|
// asort( $translations ); |
|
|
|
|
|
|
158
|
|
|
uasort( $translations, function ( $a, $b ) { |
|
159
|
|
|
if ( mb_strtolower( $a ) === mb_strtolower( $b ) |
|
160
|
|
|
|| mb_strtoupper( $a ) === mb_strtoupper( $b ) ) { |
|
161
|
|
|
return 0; |
|
162
|
|
|
} |
|
163
|
|
|
|
|
164
|
|
|
return ( mb_strtolower( $a ) < mb_strtolower( $b ) ) ? - 1 : 1; |
|
165
|
|
|
} ); |
|
166
|
|
|
$sections .= $this->get_section( $item, $translations, $vocabulary_id ); |
|
167
|
|
|
} |
|
168
|
|
|
|
|
169
|
|
|
// Return HTML template. |
|
170
|
|
|
ob_start(); |
|
171
|
|
|
?> |
|
172
|
|
|
<div class='wl-vocabulary'> |
|
173
|
|
|
<nav class='wl-vocabulary-alphabet-nav'> |
|
174
|
|
|
<?php echo $header; ?> |
|
175
|
|
|
</nav> |
|
176
|
|
|
<div class='wl-vocabulary-grid'> |
|
177
|
|
|
<?php echo $sections; ?> |
|
178
|
|
|
</div> |
|
179
|
|
|
</div> |
|
180
|
|
|
<?php |
|
181
|
|
|
$html = ob_get_clean(); |
|
182
|
|
|
|
|
183
|
|
|
return $html; |
|
184
|
|
|
|
|
185
|
|
|
} |
|
186
|
|
|
|
|
187
|
|
|
/** |
|
188
|
|
|
* Generate the html code for the section. |
|
189
|
|
|
* |
|
190
|
|
|
* @since 3.17.0 |
|
191
|
|
|
* |
|
192
|
|
|
* @param string $letter The section's letter. |
|
193
|
|
|
* @param array $posts An array of `$post_id => $post_title` associated with |
|
194
|
|
|
* the section. |
|
195
|
|
|
* @param int $vocabulary_id Unique vocabulary id. |
|
196
|
|
|
* |
|
197
|
|
|
* @return string The section html code (or an empty string if the section has |
|
198
|
|
|
* no posts). |
|
199
|
|
|
*/ |
|
200
|
|
|
private function get_section( $letter, $posts, $vocabulary_id ) { |
|
201
|
|
|
|
|
202
|
|
|
// Return an empty string if there are no posts. |
|
203
|
|
|
if ( 0 === count( $posts ) ) { |
|
204
|
|
|
return ''; |
|
205
|
|
|
} |
|
206
|
|
|
|
|
207
|
|
|
return sprintf( |
|
208
|
|
|
' |
|
209
|
|
|
<div class="wl-vocabulary-letter-block" id="wl-vocabulary-%d-%s"> |
|
210
|
|
|
<aside class="wl-vocabulary-left-column">%s</aside> |
|
211
|
|
|
<div class="wl-vocabulary-right-column"> |
|
212
|
|
|
<ul class="wl-vocabulary-items-list"> |
|
213
|
|
|
%s |
|
214
|
|
|
</ul> |
|
215
|
|
|
</div> |
|
216
|
|
|
</div> |
|
217
|
|
|
', $vocabulary_id, esc_attr( $letter ), esc_html( $letter ), $this->format_posts_as_list( $posts ) |
|
218
|
|
|
); |
|
219
|
|
|
} |
|
220
|
|
|
|
|
221
|
|
|
/** |
|
222
|
|
|
* Format an array post `$post_id => $post_title` as a list. |
|
223
|
|
|
* |
|
224
|
|
|
* @since 3.17.0 |
|
225
|
|
|
* |
|
226
|
|
|
* @param array $posts An array of `$post_id => $post_title` key, value pairs. |
|
227
|
|
|
* |
|
228
|
|
|
* @return string A list. |
|
229
|
|
|
*/ |
|
230
|
|
|
private function format_posts_as_list( $posts ) { |
|
231
|
|
|
|
|
232
|
|
|
return array_reduce( |
|
233
|
|
|
array_keys( $posts ), function ( $carry, $item ) use ( $posts ) { |
|
234
|
|
|
return $carry . sprintf( '<li><a href="%s">%s</a></li>', esc_attr( get_permalink( $item ) ), esc_html( $posts[ $item ] ) ); |
|
235
|
|
|
}, '' |
|
236
|
|
|
); |
|
237
|
|
|
} |
|
238
|
|
|
|
|
239
|
|
|
/** |
|
240
|
|
|
* Get the posts from WordPress using the provided attributes. |
|
241
|
|
|
* |
|
242
|
|
|
* @since 3.17.0 |
|
243
|
|
|
* |
|
244
|
|
|
* @param array $atts The shortcode attributes. |
|
245
|
|
|
* |
|
246
|
|
|
* @return array An array of {@link WP_Post}s. |
|
247
|
|
|
*/ |
|
248
|
|
|
private function get_posts( $atts ) { |
|
249
|
|
|
// The default arguments for the query. |
|
250
|
|
|
$args = array( |
|
251
|
|
|
'posts_per_page' => intval( $atts['limit'] ), |
|
252
|
|
|
'update_post_meta_cache' => false, |
|
253
|
|
|
'update_post_term_cache' => false, |
|
254
|
|
|
'orderby' => $atts['orderby'], |
|
255
|
|
|
'order' => $atts['order'], |
|
256
|
|
|
// Exclude the publisher. |
|
257
|
|
|
'post__not_in' => array( $this->configuration_service->get_publisher_id() ), |
|
258
|
|
|
); |
|
259
|
|
|
|
|
260
|
|
|
// Limit the based entity type if needed. |
|
261
|
|
|
if ( 'all' !== $atts['type'] ) { |
|
262
|
|
|
$args['tax_query'] = array( |
|
263
|
|
|
array( |
|
264
|
|
|
'taxonomy' => Wordlift_Entity_Type_Taxonomy_Service::TAXONOMY_NAME, |
|
265
|
|
|
'field' => 'slug', |
|
266
|
|
|
'terms' => $atts['type'], |
|
267
|
|
|
), |
|
268
|
|
|
); |
|
269
|
|
|
} |
|
270
|
|
|
|
|
271
|
|
|
if ( ! empty( $atts['cat'] ) ) { |
|
272
|
|
|
$args['cat'] = $atts['cat']; |
|
273
|
|
|
} |
|
274
|
|
|
|
|
275
|
|
|
// Get the posts. Note that if a `type` is specified before, then the |
|
276
|
|
|
// `tax_query` from the `add_criterias` call isn't added. |
|
277
|
|
|
return get_posts( Wordlift_Entity_Service::add_criterias( $args ) ); |
|
278
|
|
|
|
|
279
|
|
|
} |
|
280
|
|
|
|
|
281
|
|
|
/** |
|
282
|
|
|
* Populate the alphabet with posts. |
|
283
|
|
|
* |
|
284
|
|
|
* @since 3.17.0 |
|
285
|
|
|
* |
|
286
|
|
|
* @param array $alphabet An array of letters. |
|
287
|
|
|
* @param int $post_id The {@link WP_Post} id. |
|
288
|
|
|
*/ |
|
289
|
|
|
private function add_to_alphabet( &$alphabet, $post_id ) { |
|
290
|
|
|
|
|
291
|
|
|
// Get the title without accents. |
|
292
|
|
|
$title = remove_accents( get_the_title( $post_id ) ); |
|
293
|
|
|
|
|
294
|
|
|
// Get the initial letter. |
|
295
|
|
|
$letter = $this->get_first_letter_in_alphabet_or_hash( $alphabet, $title ); |
|
296
|
|
|
|
|
297
|
|
|
// Add the post. |
|
298
|
|
|
$alphabet[ $letter ][ $post_id ] = $title; |
|
299
|
|
|
|
|
300
|
|
|
} |
|
301
|
|
|
|
|
302
|
|
|
/** |
|
303
|
|
|
* Find the first letter in the alphabet. |
|
304
|
|
|
* |
|
305
|
|
|
* In some alphabets a letter is a compound of letters, therefore this function |
|
306
|
|
|
* will look for groups of 2 or 3 letters in the alphabet before looking for a |
|
307
|
|
|
* single letter. In case the letter is not found a # (hash) key is returned. |
|
308
|
|
|
* |
|
309
|
|
|
* @since 3.17.0 |
|
310
|
|
|
* |
|
311
|
|
|
* @param array $alphabet An array of alphabet letters. |
|
312
|
|
|
* @param string $title The title to match. |
|
313
|
|
|
* |
|
314
|
|
|
* @return string The initial letter or a `#` key. |
|
315
|
|
|
*/ |
|
316
|
|
|
private function get_first_letter_in_alphabet_or_hash( $alphabet, $title ) { |
|
317
|
|
|
|
|
318
|
|
|
// Need to handle letters which consist of 3 and 2 characters. |
|
319
|
|
|
for ( $i = 3; $i > 0; $i -- ) { |
|
320
|
|
|
$letter = mb_convert_case( mb_substr( $title, 0, $i ), MB_CASE_UPPER ); |
|
321
|
|
|
if ( isset( $alphabet[ $letter ] ) ) { |
|
322
|
|
|
return $letter; |
|
323
|
|
|
} |
|
324
|
|
|
} |
|
325
|
|
|
|
|
326
|
|
|
return '#'; |
|
327
|
|
|
} |
|
328
|
|
|
|
|
329
|
|
|
/** |
|
330
|
|
|
* Get and increment the `$vocabulary_id`. |
|
331
|
|
|
* |
|
332
|
|
|
* @since 3.18.3 |
|
333
|
|
|
* |
|
334
|
|
|
* @return int The incremented vocabulary id. |
|
335
|
|
|
*/ |
|
336
|
|
|
private static function get_and_increment_vocabulary_id() { |
|
337
|
|
|
return self::$vocabulary_id ++; |
|
338
|
|
|
} |
|
339
|
|
|
|
|
340
|
|
|
} |
|
341
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.