Wrong Assumptions
@Rarst or @Dan-Cameron answers may work, but assume that when you are viewing a post type editing page, the metaboxes for the other post type are all registered.
There are different case in which that won’t happen:
- If metaboxes are registered using
"add_meta_boxes_{$post_type}"
hook, instead of the more common, generic"add_meta_boxes"
they won’t be available in the admin page for the stealer post type - Even the generic post hook
"add_meta_boxes"
pass to hooking callbacks the current post type and the current post object as arguments. It means that registering callbacks may use those arguments in someif
conditions, so is possible that we are in same situation of previous point
In short, unless you have control on how the metaboxes for some post type A are registered, you can’t be sure they will be able in post editing page for some post type B.
In case you have that control, then @Rarst answer would do the trick.
In case you have not that control, the only way would be a bit tricky.
IMO your only chance, in that case, is to send an HTTP request (AJAX or via WP HTTP API) to the post edit page of the CPT you want to steal the boxes from. You have to add some request variable that will make the page return the boxes array (probaly serialized or JSON-enconded).
After that, you can merge the boxes returned with the boxes for current post, and you are done.
The point is that such code
- is slow (because additional request)
- is not trivial
So, if you have control on which are all the boxes registered for post a certain post type, is much simpler and faster just register those boxes again for the stealer post type.
If you can’t be sure which are all the boxes for that post type, then the additional HTTP request is your only chance.
I’ll post here a class, that implements that workflow (here as Gist for better readability):
class MetaboxStealer
{
private static $stealing;
private $post_type;
private $boxes = [];
/**
* When the request contain special variable, this function will make it
* return a serialized version of $wp_meta_boxes array and die.
*/
public static function init()
{
add_filter('post_updated_messages', function ($messages) {
if (MetaboxStealer::stealing()) {
ob_start();
return [];
}
return $messages;
});
add_action('do_meta_boxes', function () {
if (MetaboxStealer::stealing()) {
ob_end_clean();
global $wp_meta_boxes;
echo serialize($wp_meta_boxes);
die();
}
});
}
/**
* Checks that the request contain a special variable that will make request
* return a serialized version of $wp_meta_boxes array and die.
*
* @return bool
*/
public static function stealing()
{
if (is_null(self::$stealing)) {
$screen = function_exists('get_current_screen') ? get_current_screen() : null;
$stealing = filter_input(INPUT_GET, 'stealing-boxes', FILTER_SANITIZE_STRING);
self::$stealing =
$screen instanceof \WP_Screen
&& $stealing
&& wp_verify_nonce($stealing, $screen->post_type);
}
return self::$stealing;
}
/**
* @param string $post_type Current post type
*/
public function __construct($post_type)
{
$this->post_type = $post_type;
}
/**
* Send a HTTP request to post edit page of a given CPT setting a special
* variable that will make that page return serialized $wp_meta_boxes array.
* After that, so obtained boxes are merged with the boxes for current post type.
*
* @param string $cpt CPT to steal metaboxes from
*/
public function steal($cpt)
{
$vars = [
'post_type' => $cpt,
'stealing-boxes' => wp_create_nonce($cpt),
];
$url = add_query_arg($vars, admin_url('/post-new.php'));
$cookies = [];
foreach ($_COOKIE as $name => $value) {
if ('PHPSESSID' !== strtoupper($name)) {
$cookies[] = new \WP_Http_Cookie([
'name' => $name,
'value' => $value,
]);
}
}
$response = wp_remote_get($url, ['cookies' => $cookies]);
if (! is_wp_error($response)) {
$body = wp_remote_retrieve_body($response);
if (is_serialized($body)) {
$boxes = unserialize($body);
$this->boxes = isset($boxes[$cpt]) ? $boxes[$cpt] : [];
empty($this->boxes) or $this->merge();
}
}
}
/**
* Merge metaboxes for current post type with boxes obtained with
* a HTTP request to another CPT post edit page.
*/
private function merge()
{
global $wp_meta_boxes;
isset($wp_meta_boxes[$this->post_type]) or $wp_meta_boxes[$this->post_type] = [];
foreach ($this->boxes as $context => $priorities) {
foreach ($priorities as $priority => $boxes) {
if (! isset($wp_meta_boxes[$this->post_type][$context])) {
$wp_meta_boxes[$this->post_type][$context] = [];
}
if (! isset($wp_meta_boxes[$this->post_type][$context][$priority])) {
$wp_meta_boxes[$this->post_type][$context][$priority] = [];
}
$wp_meta_boxes[$this->post_type][$context][$priority] = array_merge(
$wp_meta_boxes[$this->post_type][$context][$priority],
$boxes
);
}
}
}
}
Notes:
- the method
merge()
is highly derived from Rarst answer - I’ve used WP HTTP API to send additional request to keep all in one place, but an AJAX implementation would be nicer
How To Use
Pretty simple.
// init the class
add_action('admin_init', ['MetaboxStealer', 'init']);
// use the class to merge boxes for current CPT with boxes for another CPT
add_action('edit_form_after_editor', function ($post) {
$stealer_cpt="stealer-cpt";
$steal_from = 'events';
if ($post->post_type === $stealer_cpt) {
$stealer = new MetaboxStealer($post->post_type);
$stealer->steal($steal_from);
// note that you can steal from different CPTs
// $stealer->steal($another_steal_from);
}
});
The Saving Issue
No matter how do you manage to show boxes from one CPT in the post edit page of another CPT, is possible that the saving routine checks the post type before saving post meta in it.
In that case, metaboxes from the other CPT, even if showed, will not be saved and you’ll probably need to write another saving routine, if you have no access on the original
one.