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

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