PluginProbe
Polylang / 3.1.2
Polylang v3.1.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.1.2, at include/crud-posts.php

324 lines 10.6 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, 2 );
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.
191 *
192 * @since 1.8
193 *
194 * @param int $post_parent Post parent ID.
195 * @param int $post_id Post ID.
196 * @return int
197 */
198 public function wp_insert_post_parent( $post_parent, $post_id ) {
199 $lang = $this->model->post->get_language( $post_id );
200 // Dont break the hierarchy in case the post has no language
201 if ( ! empty( $lang ) ) {
202 $post_parent = $this->model->post->get_translation( $post_parent, $lang );
203 }
204 return $post_parent;
205 }
206
207 /**
208 * Called when a post, page or media is deleted
209 * Don't delete translations if this is a post revision thanks to AndyDeGroo who catched this bug
210 * http://wordpress.org/support/topic/plugin-polylang-quick-edit-still-breaks-translation-linking-of-pages-in-072
211 *
212 * @since 0.1
213 *
214 * @param int $post_id
215 * @return void
216 */
217 public function delete_post( $post_id ) {
218 if ( ! wp_is_post_revision( $post_id ) ) {
219 $this->model->post->delete_translation( $post_id );
220 }
221 }
222
223 /**
224 * Prevents WP deleting files when there are still media using them.
225 *
226 * @since 0.9
227 *
228 * @param string $file Path to the file to delete.
229 * @return string Empty or unmodified path.
230 */
231 public function wp_delete_file( $file ) {
232 global $wpdb;
233
234 $uploadpath = wp_upload_dir();
235
236 // Get the main attached file.
237 $attached_file = substr_replace( $file, '', 0, strlen( trailingslashit( $uploadpath['basedir'] ) ) );
238 $attached_file = preg_replace( '#-\d+x\d+\.([a-z]+)$#', '.$1', $attached_file );
239
240 $ids = $wpdb->get_col(
241 $wpdb->prepare(
242 "SELECT post_id FROM $wpdb->postmeta
243 WHERE meta_key = '_wp_attached_file' AND meta_value = %s",
244 $attached_file
245 )
246 );
247
248 if ( ! empty( $ids ) ) {
249 return ''; // Prevent deleting the file.
250 }
251
252 return $file;
253 }
254
255 /**
256 * Creates a media translation
257 *
258 * @since 1.8
259 *
260 * @param int $post_id Original attachment id.
261 * @param string|object $lang New translation language.
262 * @return int Attachment id of the translated media.
263 */
264 public function create_media_translation( $post_id, $lang ) {
265 if ( empty( $post_id ) ) {
266 return 0;
267 }
268
269 $post = get_post( $post_id, ARRAY_A );
270
271 if ( empty( $post ) ) {
272 return 0;
273 }
274
275 $lang = $this->model->get_language( $lang ); // Make sure we get a valid language slug.
276
277 if ( empty( $lang ) ) {
278 return 0;
279 }
280
281 // Create a new attachment ( translate attachment parent if exists ).
282 add_filter( 'pll_enable_duplicate_media', '__return_false', 99 ); // Avoid a conflict with automatic duplicate at upload.
283 unset( $post['ID'] ); // Will force the creation.
284 $post['post_parent'] = ( $post['post_parent'] && $tr_parent = $this->model->post->get_translation( $post['post_parent'], $lang->slug ) ) ? $tr_parent : 0;
285 $post['tax_input'] = array( 'language' => array( $lang->slug ) ); // Assigns the language.
286 $tr_id = wp_insert_attachment( wp_slash( $post ) );
287 remove_filter( 'pll_enable_duplicate_media', '__return_false', 99 ); // Restore automatic duplicate at upload.
288
289 // Copy metadata.
290 $data = wp_get_attachment_metadata( $post_id, true ); // Unfiltered.
291 if ( is_array( $data ) ) {
292 wp_update_attachment_metadata( $tr_id, wp_slash( $data ) ); // Directly uses update_post_meta, so expects slashed.
293 }
294
295 // Copy attached file.
296 if ( $file = get_attached_file( $post_id, true ) ) { // Unfiltered.
297 update_attached_file( $tr_id, wp_slash( $file ) ); // Directly uses update_post_meta, so expects slashed.
298 }
299
300 // Copy alternative text. Direct use of the meta as there is no filtered wrapper to manipulate it.
301 if ( $text = get_post_meta( $post_id, '_wp_attachment_image_alt', true ) ) {
302 add_post_meta( $tr_id, '_wp_attachment_image_alt', wp_slash( $text ) );
303 }
304
305 $this->model->post->set_language( $tr_id, $lang );
306
307 $translations = $this->model->post->get_translations( $post_id );
308 $translations[ $lang->slug ] = $tr_id;
309 $this->model->post->save_translations( $tr_id, $translations );
310
311 /**
312 * Fires after a media translation is created
313 *
314 * @since 1.6.4
315 *
316 * @param int $post_id Post id of the source media.
317 * @param int $tr_id Post id of the new media translation.
318 * @param string $slug Language code of the new translation.
319 */
320 do_action( 'pll_translate_media', $post_id, $tr_id, $lang->slug );
321 return $tr_id;
322 }
323 }
324