PluginProbe
Polylang / 2.8.2
Polylang v2.8.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 2.8.2, at include/crud-posts.php

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