-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #655 from carstingaxion/feature/export-import
Export & Import
- Loading branch information
Showing
7 changed files
with
516 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,212 @@ | ||
<?php | ||
/** | ||
* Class responsible for exporting content using WordPress' native export tool. | ||
* | ||
* @package GatherPress\Core | ||
* @since 1.0.0 | ||
*/ | ||
|
||
namespace GatherPress\Core; | ||
|
||
// Exit if accessed directly. | ||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore | ||
|
||
use GatherPress\Core\Traits\Singleton; | ||
use WP_Post; | ||
|
||
/** | ||
* Class Export. | ||
* | ||
* The Export class handles the exporting of content using WordPress' native export tool. | ||
* This class will enhance overall export management, provide effective filtering | ||
* and support validation of the export-objects based on their post type and meta data. | ||
* | ||
* @since 1.0.0 | ||
*/ | ||
class Export extends Migrate { | ||
/** | ||
* Enforces a single instance of this class. | ||
*/ | ||
use Singleton; | ||
|
||
/** | ||
* The post_meta name for GatherPress temporary entry, | ||
* to hook into WordPress export. | ||
* | ||
* @since 1.0.0 | ||
* @var string $POST_META | ||
*/ | ||
const POST_META = 'gatherpress_extend_export'; | ||
|
||
/** | ||
* Class constructor. | ||
* | ||
* This method initializes the object and sets up necessary hooks. | ||
* | ||
* @since 1.0.0 | ||
*/ | ||
public function __construct() { | ||
$this->setup_hooks(); | ||
} | ||
|
||
/** | ||
* Set up hooks for various purposes. | ||
* | ||
* This method adds hooks for different purposes as needed. | ||
* | ||
* @since 1.0.0 | ||
* | ||
* @return void | ||
*/ | ||
protected function setup_hooks(): void { | ||
/** | ||
* Fires at the beginning of an export, before any headers are sent. | ||
*/ | ||
add_action( 'export_wp', array( $this, 'export' ) ); | ||
} | ||
|
||
/** | ||
* Sets up the necessary hooks for the export process. | ||
* | ||
* @since 1.0.0 | ||
* | ||
* @return void | ||
*/ | ||
public function export(): void { | ||
add_action( 'the_post', array( $this, 'prepare' ), 10, 2 ); | ||
add_filter( 'wxr_export_skip_postmeta', array( $this, 'extend' ), 10, 3 ); | ||
} | ||
|
||
/** | ||
* Saves a temporary marker as postmeta, | ||
* which allows to hook into the export process per post later on. | ||
* | ||
* Called via setup_postdata() at the beginning of each singular post export. | ||
* | ||
* Fires once the post data has been set up. | ||
* | ||
* @param WP_Post $post The Post object (passed by reference). | ||
* @return void | ||
*/ | ||
public function prepare( WP_Post $post ): void { | ||
if ( $this->validate( $post ) ) { | ||
add_post_meta( $post->ID, self::POST_META, true ); | ||
} | ||
} | ||
|
||
/** | ||
* Extend WordPress' native Export | ||
* | ||
* WordPress' native Export can be extended in hacky way using `wxr_export_skip_postmeta` | ||
* where GatherPress echos out some pseudo-post-meta fields, | ||
* before returning `false` like the default. | ||
* | ||
* @source https://github.com/WordPress/wordpress-develop/blob/6.5/src/wp-admin/includes/export.php#L655-L677 | ||
* | ||
* Normally this filters whether to selectively skip post meta used for WXR exports. | ||
* Returning a truthy value from the filter will skip the current meta object from being exported. | ||
* | ||
* @see https://developer.wordpress.org/reference/hooks/wxr_export_skip_postmeta/ | ||
* | ||
* But because there is no 'do_action('per-exported-post)', | ||
* GatherPress created a post_meta entry as a temporary marker, to be used as an entry-point into | ||
* WordPress' native export process, which is used now. | ||
* | ||
* @param bool $skip Whether to skip the current post meta. Default false. | ||
* @param string $meta_key Current meta key. | ||
* @param object $meta Current meta object. | ||
* @return bool Whether to skip the current post meta. Default false. | ||
*/ | ||
public function extend( bool $skip, string $meta_key, object $meta ): bool { | ||
if ( self::POST_META === $meta_key ) { | ||
// Echos out xml with pseudo-postmeta. | ||
$this->run( get_post( $meta->post_id ) ); | ||
|
||
// Deletes temporary marker. | ||
delete_post_meta( $meta->post_id, self::POST_META ); | ||
|
||
// Prevent 'normal' export processing for that particular postmeta field, | ||
// because it doesn't exist in real and will trigger an error. | ||
return true; | ||
} | ||
|
||
return $skip; | ||
} | ||
|
||
/** | ||
* Checks if the currently exported post is of type 'gatherpress_event'. | ||
* | ||
* @since 1.0.0 | ||
* | ||
* @param WP_Post $post Current meta key. | ||
* @return bool True, when the currently exported post is of type 'gatherpress_event', false otherwise. | ||
*/ | ||
protected function validate( WP_Post $post ): bool { | ||
return ( Event::POST_TYPE === $post->post_type ); | ||
} | ||
|
||
/** | ||
* Exports all custom data. | ||
* | ||
* Gets all 'pseudopostmetas' and generates WXR-compatible output for each, | ||
* the generated xml markup is rendered into the WordPress export file directly. | ||
* | ||
* An export file like this can be imported into GatherPress using | ||
* the native 'WordPress importer' and its potential replacement the 'WordPress importer (v2)'. | ||
* | ||
* @since 1.0.0 | ||
* | ||
* @param WP_Post $post Current 'gatherpress_event' post being exported. | ||
* @return void | ||
*/ | ||
public function run( WP_Post $post ): void { | ||
$pseudopostmetas = $this->get_pseudopostmetas(); | ||
|
||
array_walk( | ||
$pseudopostmetas, | ||
array( $this, 'render' ), | ||
$post | ||
); | ||
} | ||
|
||
/** | ||
* Render custom post_meta data into xml markup to be used while WordÜress' native export. | ||
* | ||
* @param array $callbacks Associative array with (import & export) callback functions for a the non-existent post_meta entry, named by $key. | ||
* @param string $key Name of the custom post_meta, that should be exported. | ||
* @param WP_Post $post The currently exported 'gatherpress_event' post. | ||
* @return void | ||
*/ | ||
public function render( array $callbacks, string $key, WP_Post $post ) { | ||
if ( ! isset( $callbacks['export_callback'] ) || ! is_callable( $callbacks['export_callback'] ) ) { | ||
return; | ||
} | ||
|
||
$value = call_user_func( $callbacks['export_callback'], $post ); | ||
|
||
?> | ||
<wp:postmeta> | ||
<wp:meta_key><?php echo wxr_cdata( $key ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></wp:meta_key> | ||
<wp:meta_value><?php echo wxr_cdata( $value ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></wp:meta_value> | ||
</wp:postmeta> | ||
<?php | ||
} | ||
|
||
/** | ||
* Returns dates, times and timezone from the 'wp_gatherpress_events' DB table | ||
* as serialized string for the current post being exported. | ||
* | ||
* @since 1.0.0 | ||
* | ||
* @param WP_Post $post Current 'gatherpress_event' post being exported. | ||
* @return string Serialized JSON string with all date, time & timezone data of the current $post. | ||
*/ | ||
public function datetimes_callback( WP_Post $post ): string { | ||
// Make sure to not get any user-related data. | ||
remove_all_filters( 'gatherpress_timezone' ); | ||
|
||
$event = new Event( $post->ID ); | ||
|
||
return maybe_serialize( $event->get_datetime() ); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
<?php | ||
/** | ||
* Class responsible for importing content using WordPress' native import tool(s). | ||
* | ||
* @package GatherPress\Core | ||
* @since 1.0.0 | ||
*/ | ||
|
||
namespace GatherPress\Core; | ||
|
||
// Exit if accessed directly. | ||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore | ||
|
||
use GatherPress\Core\Event; | ||
use GatherPress\Core\Migrate; | ||
use GatherPress\Core\Traits\Singleton; | ||
use WP_Post; | ||
|
||
/** | ||
* Class Import. | ||
* | ||
* The Import class handles the importing of content using WordPress' native import tool. | ||
* This class will provide effective filtering and support validation of the import-objects | ||
* based on their post type and meta data. | ||
* | ||
* Succesfully identified GatherPress data will be saved into custom DB tables. | ||
* | ||
* @since 1.0.0 | ||
*/ | ||
class Import extends Migrate { | ||
/** | ||
* Enforces a single instance of this class. | ||
*/ | ||
use Singleton; | ||
|
||
/** | ||
* Class constructor. | ||
* | ||
* This method initializes the object and sets up necessary hooks. | ||
* | ||
* @since 1.0.0 | ||
*/ | ||
public function __construct() { | ||
$this->setup_hooks(); | ||
} | ||
|
||
/** | ||
* Set up hooks for various purposes. | ||
* | ||
* This method adds hooks for different purposes as needed. | ||
* | ||
* @since 1.0.0 | ||
* | ||
* @return void | ||
*/ | ||
protected function setup_hooks(): void { | ||
if ( class_exists( 'WXR_Importer' ) ) { | ||
/** | ||
* Setup for WordPress Importer (v2). | ||
* | ||
* @see https://github.com/humanmade/Wordpress-Importer | ||
*/ | ||
$hook_name = 'wxr_importer.pre_process.post'; | ||
} else { | ||
/** | ||
* Setup for default WordPress Importer. | ||
* | ||
* @see https://github.com/WordPress/wordpress-importer/issues/42 | ||
*/ | ||
$hook_name = 'wp_import_post_data_raw'; | ||
} | ||
|
||
add_filter( $hook_name, array( $this, 'prepare' ) ); | ||
add_action( 'gatherpress_import', array( $this, 'extend' ) ); | ||
} | ||
|
||
/** | ||
* Extend WordPress' native Import. | ||
* | ||
* @see https://github.com/WordPress/wordpress-importer/blob/71bdd41a2aa2c6a0967995ee48021037b39a1097/src/class-wp-import.php#L631 | ||
* | ||
* @param array $post_data_raw The result of 'wp_import_post_data_raw'. | ||
* @return array Returns the unchanged result of 'wp_import_post_data_raw'. | ||
*/ | ||
public function prepare( array $post_data_raw ): array { | ||
if ( $this->validate( $post_data_raw ) ) { | ||
/** | ||
* Fires for every GatherPress data to be imported. | ||
* | ||
* @since 1.0.0 | ||
* | ||
* @param {array} $post_data_raw Unprocessesd 'gatherpress_event' post being imported. | ||
*/ | ||
do_action( 'gatherpress_import', $post_data_raw ); | ||
} | ||
|
||
return $post_data_raw; | ||
} | ||
|
||
/** | ||
* Checks if the currently imported post is of type 'gatherpress_event'. | ||
* | ||
* @param array $post_data_raw The result of 'wp_import_post_data_raw'. | ||
* @return bool True, when the currently imported post is of type 'gatherpress_event', false otherwise. | ||
*/ | ||
protected function validate( array $post_data_raw ): bool { | ||
return ( isset( $post_data_raw['post_type'] ) && Event::POST_TYPE === $post_data_raw['post_type'] ); | ||
} | ||
|
||
/** | ||
* Import all custom data. | ||
* | ||
* @return void | ||
*/ | ||
public function extend(): void { | ||
add_filter( 'add_post_metadata', array( $this, 'run' ), 10, 5 ); | ||
} | ||
|
||
/** | ||
* Import data with custom scheme. | ||
* | ||
* This method is called on every imported post_meta | ||
* and allows to work with the data to be imported. | ||
* | ||
* It checks if the current meta_key is one of GatherPress' pseudopostmetas | ||
* and if an import-callback for that key exists. | ||
* If both is true, the import callback is provided with all available information and called once per meta_key. | ||
* | ||
* The normal saving into the 'wp_postmeta' DB table is disabled in such a case. | ||
* | ||
* @see https://developer.wordpress.org/reference/hooks/add_meta_type_metadata/ | ||
* @see https://www.ibenic.com/hook-wordpress-metadata/ | ||
* | ||
* @param null|bool $check Whether to allow adding metadata for the given type. | ||
* @param int $object_id ID of the object metadata is for. | ||
* @param string $meta_key Metadata key. | ||
* @param mixed $meta_value Metadata value. Must be serializable if non-scalar. | ||
* @param bool $unique Whether the specified meta key should be unique for the object. | ||
* @return null|bool Returning a non-null value will effectively short-circuit the saving of 'normal' meta data. | ||
*/ | ||
public function run( ?bool $check, int $object_id, string $meta_key, $meta_value, bool $unique ): ?bool { | ||
$pseudopostmetas = $this->get_pseudopostmetas(); | ||
|
||
if ( ! isset( $pseudopostmetas[ $meta_key ] ) ) { | ||
return null; | ||
} | ||
|
||
if ( | ||
! isset( $pseudopostmetas[ $meta_key ]['import_callback'] ) || | ||
! is_callable( $pseudopostmetas[ $meta_key ]['import_callback'] ) | ||
) { | ||
return null; | ||
} | ||
|
||
/* | ||
* Run import callback, | ||
* e.g. Save data into a custom DB table. | ||
*/ | ||
call_user_func( | ||
$pseudopostmetas[ $meta_key ]['import_callback'], | ||
$object_id, | ||
$meta_value | ||
); | ||
|
||
/* | ||
* Disable saving of 'normal' meta data. | ||
*/ | ||
return false; | ||
} | ||
|
||
|
||
/** | ||
* Save dates, times & timezone for the currently imported 'gatherpress_event' post. | ||
* | ||
* @param int $post_id ID of the object metadata is for. | ||
* @param mixed $data Metadata value. Must be serializable if non-scalar. | ||
* @return void | ||
*/ | ||
public function datetimes_callback( int $post_id, $data ): void { | ||
$event = new Event( $post_id ); | ||
|
||
$event->save_datetimes( maybe_unserialize( $data ) ); | ||
} | ||
} |
Oops, something went wrong.