PluginProbe
Polylang / 3.0.2
Polylang v3.0.2
3.8.9 3.8.8 3.8.7 3.8.6 3.8.5 3.8.4 3.8.3 2.7 2.7.0.1 2.7.1 2.7.2 2.7.3 2.7.4 2.8 2.8.1 2.8.2 2.8.3 2.8.4 2.9 2.9.1 2.9.2 3.0 3.0.1 3.0.2 3.0.3 All 233 releases
polylang / include / crud-posts.php

crud-posts.php in Polylang 3.0.2, at include/crud-posts.php

331 lines 11.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * @package Polylang
4 */
5
6 /**
7 * Adds actions and filters related to languages when creating, updating or deleting posts.
8 * Actions and filters triggered when reading posts are handled separately.
9 *
10 * @since 2.4
11 */
12 class PLL_CRUD_Posts {
13 /**
14 * @var PLL_Model
15 */
16 protected $model;
17
18 /**
19 * Preferred language to assign to a new post.
20 *
21 * @var PLL_Language
22 */
23 protected $pref_lang;
24
25 /**
26 * Current language.
27 *
28 * @var PLL_Language
29 */
30 protected $curlang;
31
32 /**
33 * Constructor
34 *
35 * @since 2.4
36 *
37 * @param object $polylang
38 */
39 public function __construct( &$polylang ) {
40 $this->model = &$polylang->model;
41 $this->pref_lang = &$polylang->pref_lang;
42 $this->curlang = &$polylang->curlang;
43
44 add_action( 'save_post', array( $this, 'save_post' ), 10, 2 );
45 add_action( 'set_object_terms', array( $this, 'set_object_terms' ), 10, 4 );
46 add_filter( 'wp_insert_post_parent', array( $this, 'wp_insert_post_parent' ), 10, 4 );
47 add_action( 'before_delete_post', array( $this, 'delete_post' ) );
48
49 // Specific for media
50 if ( $polylang->options['media_support'] ) {
51 add_action( 'add_attachment', array( $this, 'set_default_language' ) );
52 add_action( 'delete_attachment', array( $this, 'delete_post' ) );
53 add_filter( 'wp_delete_file', array( $this, 'wp_delete_file' ) );
54 }
55 }
56
57 /**
58 * Allows to set a language by default for posts if it has no language yet
59 *
60 * @since 1.5
61 *
62 * @param int $post_id
63 * @return void
64 */
65 public function set_default_language( $post_id ) {
66 if ( ! $this->model->post->get_language( $post_id ) ) {
67 if ( ! empty( $_GET['new_lang'] ) && $lang = $this->model->get_language( sanitize_key( $_GET['new_lang'] ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification
68 // Defined only on admin.
69 $this->model->post->set_language( $post_id, $lang );
70 } elseif ( ! isset( $this->pref_lang ) && ! empty( $_REQUEST['lang'] ) && $lang = $this->model->get_language( sanitize_key( $_REQUEST['lang'] ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification
71 // Testing $this->pref_lang makes this test pass only on admin.
72 $this->model->post->set_language( $post_id, $lang );
73 } elseif ( ( $parent_id = wp_get_post_parent_id( $post_id ) ) && $parent_lang = $this->model->post->get_language( $parent_id ) ) {
74 $this->model->post->set_language( $post_id, $parent_lang );
75 } elseif ( isset( $this->pref_lang ) ) {
76 // Always defined on admin, never defined on frontend
77 $this->model->post->set_language( $post_id, $this->pref_lang );
78 } else {
79 // Only on frontend due to the previous test always true on admin
80 $this->model->post->set_language( $post_id, $this->curlang );
81 }
82 }
83 }
84
85 /**
86 * Called when a post ( or page ) is saved, published or updated.
87 *
88 * @since 0.1
89 * @since 2.3 Does not save the language and translations anymore, unless the post has no language yet.
90 *
91 * @param int $post_id Post id of the post being saved.
92 * @param WP_Post $post The post being saved.
93 * @return void
94 */
95 public function save_post( $post_id, $post ) {
96 // Does nothing except on post types which are filterable.
97 if ( $this->model->is_translated_post_type( $post->post_type ) ) {
98 if ( $id = wp_is_post_revision( $post_id ) ) {
99 $post_id = $id;
100 }
101
102 $lang = $this->model->post->get_language( $post_id );
103
104 if ( empty( $lang ) ) {
105 $this->set_default_language( $post_id );
106 }
107
108 /**
109 * Fires after the post language and translations are saved.
110 *
111 * @since 1.2
112 *
113 * @param int $post_id Post id.
114 * @param WP_Post $post Post object.
115 * @param int[] $translations The list of translations post ids.
116 */
117 do_action( 'pll_save_post', $post_id, $post, $this->model->post->get_translations( $post_id ) );
118 }
119 }
120
121 /**
122 * Makes sure that saved terms are in the right language (especially tags with same name in different languages).
123 *
124 * @since 2.3
125 *
126 * @param int $object_id Object ID.
127 * @param WP_Term[] $terms An array of object terms.
128 * @param int[] $tt_ids An array of term taxonomy IDs.
129 * @param string $taxonomy Taxonomy slug.
130 * @return void
131 */
132 public function set_object_terms( $object_id, $terms, $tt_ids, $taxonomy ) {
133 static $avoid_recursion;
134
135 if ( ! $avoid_recursion && $this->model->is_translated_taxonomy( $taxonomy ) && ! empty( $terms ) ) {
136 $lang = $this->model->post->get_language( $object_id );
137
138 if ( ! empty( $lang ) && is_array( $terms ) ) {
139 // Convert to term ids if we got tag names
140 $strings = array_filter( $terms, 'is_string' );
141 if ( ! empty( $strings ) ) {
142 $_terms = get_terms( $taxonomy, array( 'name' => $strings, 'object_ids' => $object_id, 'fields' => 'ids' ) );
143 $terms = array_merge( array_diff( $terms, $strings ), $_terms );
144 }
145
146 $term_ids = array_combine( $terms, $terms );
147 $languages = array_map( array( $this->model->term, 'get_language' ), $term_ids );
148 $languages = array_filter( $languages ); // Remove terms without language.
149 $languages = wp_list_pluck( $languages, 'slug' );
150 $wrong_terms = array_diff( $languages, array( $lang->slug ) );
151
152 if ( ! empty( $wrong_terms ) ) {
153 // We got terms in a wrong language
154 $wrong_term_ids = array_keys( $wrong_terms );
155 $terms = get_the_terms( $object_id, $taxonomy );
156 wp_remove_object_terms( $object_id, $wrong_term_ids, $taxonomy );
157
158 if ( is_array( $terms ) ) {
159 $newterms = array();
160
161 foreach ( $terms as $term ) {
162 if ( in_array( $term->term_id, $wrong_term_ids ) ) {
163 // Check if the term is in the correct language or if a translation exist ( mainly for default category )
164 if ( $newterm = $this->model->term->get( $term->term_id, $lang ) ) {
165 $newterms[] = (int) $newterm;
166 }
167
168 // Or choose the correct language for tags ( initially defined by name )
169 elseif ( $newterm = $this->model->term_exists( $term->name, $taxonomy, $term->parent, $lang ) ) {
170 $newterms[] = (int) $newterm; // Cast is important otherwise we get 'numeric' tags
171 }
172
173 // Or create the term in the correct language
174 elseif ( ! is_wp_error( $term_info = wp_insert_term( $term->name, $taxonomy ) ) ) {
175 $newterms[] = (int) $term_info['term_id'];
176 }
177 }
178 }
179
180 $avoid_recursion = true;
181 wp_set_object_terms( $object_id, array_unique( $newterms ), $taxonomy, true ); // Append
182 $avoid_recursion = false;
183 }
184 }
185 }
186 }
187 }
188
189 /**
190 * Make sure that the post parent is in the correct language when using bulk edit
191 *
192 * @since 1.8
193 *
194 * @param int $post_parent Post parent ID.
195 * @param int $post_id Post ID.
196 * @param array $new_postarr Array of parsed post data.
197 * @param array $postarr Array of sanitized, but otherwise unmodified post data.
198 * @return int
199 */
200 public function wp_insert_post_parent( $post_parent, $post_id, $new_postarr, $postarr ) {
201 if ( isset( $postarr['bulk_edit'], $postarr['inline_lang_choice'] ) ) {
202 check_admin_referer( 'bulk-posts' );
203 $lang = -1 == $postarr['inline_lang_choice'] ?
204 $this->model->post->get_language( $post_id ) :
205 $this->model->get_language( $postarr['inline_lang_choice'] );
206 // Dont break the hierarchy in case the post has no language
207 if ( ! empty( $lang ) ) {
208 $post_parent = $this->model->post->get_translation( $post_parent, $lang );
209 }
210 }
211 return $post_parent;
212 }
213
214 /**
215 * Called when a post, page or media is deleted
216 * Don't delete translations if this is a post revision thanks to AndyDeGroo who catched this bug
217 * http://wordpress.org/support/topic/plugin-polylang-quick-edit-still-breaks-translation-linking-of-pages-in-072
218 *
219 * @since 0.1
220 *
221 * @param int $post_id
222 * @return void
223 */
224 public function delete_post( $post_id ) {
225 if ( ! wp_is_post_revision( $post_id ) ) {
226 $this->model->post->delete_translation( $post_id );
227 }
228 }
229
230 /**
231 * Prevents WP deleting files when there are still media using them
232 * Thanks to Bruno "Aesqe" Babic and its plugin file gallery in which I took all the ideas for this function
233 *
234 * @since 0.9
235 *
236 * @param string $file Path to the file to delete.
237 * @return string Empty or unmodified path.
238 */
239 public function wp_delete_file( $file ) {
240 global $wpdb;
241
242 $uploadpath = wp_upload_dir();
243
244 $ids = $wpdb->get_col(
245 $wpdb->prepare(
246 "SELECT post_id FROM $wpdb->postmeta
247 WHERE meta_key = '_wp_attached_file' AND meta_value = %s",
248 substr_replace( $file, '', 0, strlen( trailingslashit( $uploadpath['basedir'] ) ) )
249 )
250 );
251
252 if ( ! empty( $ids ) ) {
253 // Regenerate intermediate sizes if it's an image ( since we could not prevent WP deleting them before ).
254 require_once ABSPATH . 'wp-admin/includes/image.php'; // In case the file is deleted outside admin.
255 wp_update_attachment_metadata( $ids[0], wp_slash( wp_generate_attachment_metadata( $ids[0], $file ) ) ); // Directly uses update_post_meta, so expects slashed.
256 return ''; // Prevent deleting the main file.
257 }
258
259 return $file;
260 }
261
262 /**
263 * Creates a media translation
264 *
265 * @since 1.8
266 *
267 * @param int $post_id Original attachment id.
268 * @param string|object $lang New translation language.
269 * @return int Attachment id of the translated media.
270 */
271 public function create_media_translation( $post_id, $lang ) {
272 if ( empty( $post_id ) ) {
273 return 0;
274 }
275
276 $post = get_post( $post_id, ARRAY_A );
277
278 if ( empty( $post ) ) {
279 return 0;
280 }
281
282 $lang = $this->model->get_language( $lang ); // Make sure we get a valid language slug.
283
284 if ( empty( $lang ) ) {
285 return 0;
286 }
287
288 // Create a new attachment ( translate attachment parent if exists ).
289 add_filter( 'pll_enable_duplicate_media', '__return_false', 99 ); // Avoid a conflict with automatic duplicate at upload.
290 unset( $post['ID'] ); // Will force the creation.
291 $post['post_parent'] = ( $post['post_parent'] && $tr_parent = $this->model->post->get_translation( $post['post_parent'], $lang->slug ) ) ? $tr_parent : 0;
292 $post['tax_input'] = array( 'language' => array( $lang->slug ) ); // Assigns the language.
293 $tr_id = wp_insert_attachment( wp_slash( $post ) );
294 remove_filter( 'pll_enable_duplicate_media', '__return_false', 99 ); // Restore automatic duplicate at upload.
295
296 // Copy metadata.
297 $data = wp_get_attachment_metadata( $post_id, true ); // Unfiltered.
298 if ( is_array( $data ) ) {
299 wp_update_attachment_metadata( $tr_id, wp_slash( $data ) ); // Directly uses update_post_meta, so expects slashed.
300 }
301
302 // Copy attached file.
303 if ( $file = get_attached_file( $post_id, true ) ) { // Unfiltered.
304 update_attached_file( $tr_id, wp_slash( $file ) ); // Directly uses update_post_meta, so expects slashed.
305 }
306
307 // Copy alternative text. Direct use of the meta as there is no filtered wrapper to manipulate it.
308 if ( $text = get_post_meta( $post_id, '_wp_attachment_image_alt', true ) ) {
309 add_post_meta( $tr_id, '_wp_attachment_image_alt', wp_slash( $text ) );
310 }
311
312 $this->model->post->set_language( $tr_id, $lang );
313
314 $translations = $this->model->post->get_translations( $post_id );
315 $translations[ $lang->slug ] = $tr_id;
316 $this->model->post->save_translations( $tr_id, $translations );
317
318 /**
319 * Fires after a media translation is created
320 *
321 * @since 1.6.4
322 *
323 * @param int $post_id Post id of the source media.
324 * @param int $tr_id Post id of the new media translation.
325 * @param string $slug Language code of the new translation.
326 */
327 do_action( 'pll_translate_media', $post_id, $tr_id, $lang->slug );
328 return $tr_id;
329 }
330 }
331