/includes/ContentImport/ContentImporter.php

https://github.com/lloc/Multisite-Language-Switcher · PHP · 371 lines · 182 code · 66 blank · 123 comment · 26 complexity · adfee3aaf991ef3f163dce5536e6fdcd MD5 · raw file

  1. <?php
  2. namespace lloc\Msls\ContentImport;
  3. use lloc\Msls\ContentImport\Importers\Importer;
  4. use lloc\Msls\ContentImport\Importers\Map;
  5. use lloc\Msls\ContentImport\Importers\WithRequestPostAttributes;
  6. use lloc\Msls\MslsBlogCollection;
  7. use lloc\Msls\MslsMain;
  8. use lloc\Msls\MslsOptionsPost;
  9. use lloc\Msls\MslsRegistryInstance;
  10. /**
  11. * Class ContentImporter
  12. *
  13. * Handles the request for a content import.
  14. *
  15. * @package lloc\Msls\ContentImport
  16. */
  17. class ContentImporter extends MslsRegistryInstance {
  18. use WithRequestPostAttributes;
  19. /**
  20. * @var MslsMain
  21. */
  22. protected $main;
  23. /**
  24. * @var ImportLogger
  25. */
  26. protected $logger;
  27. /**
  28. * @var Relations
  29. */
  30. protected $relations;
  31. /**
  32. * @var bool Whether the class should handle requests or not.
  33. */
  34. protected $handle = true;
  35. /**
  36. * @var int The ID of the post the class created while handling the request, if any.
  37. */
  38. protected $has_created_post = 0;
  39. /**
  40. * ContentImporter constructor.
  41. *
  42. * @param \lloc\Msls\MslsMain|null $main
  43. */
  44. public function __construct( MslsMain $main = null ) {
  45. $this->main = $main ?: MslsMain::init();
  46. }
  47. /**
  48. * @return \lloc\Msls\ContentImport\ImportLogger
  49. */
  50. public function get_logger() {
  51. return $this->logger;
  52. }
  53. /**
  54. * @param \lloc\Msls\ContentImport\ImportLogger $logger
  55. */
  56. public function set_logger( $logger ) {
  57. $this->logger = $logger;
  58. }
  59. /**
  60. * @return \lloc\Msls\ContentImport\Relations
  61. */
  62. public function get_relations() {
  63. return $this->relations;
  64. }
  65. /**
  66. * @param \lloc\Msls\ContentImport\Relations $relations
  67. */
  68. public function set_relations( $relations ) {
  69. $this->relations = $relations;
  70. }
  71. /**
  72. * Handles an import request happening during a post save or a template redirect.
  73. *
  74. * @param array|null $data
  75. *
  76. * @return array The updated, if needed, data array.
  77. */
  78. public function handle_import( array $data = [] ) {
  79. if ( ! $this->pre_flight_check() || false === $sources = $this->parse_sources() ) {
  80. return $data;
  81. }
  82. list( $source_blog_id, $source_post_id ) = $sources;
  83. if ( $source_blog_id === get_current_blog_id() ) {
  84. return $data;
  85. }
  86. $source_lang = MslsBlogCollection::get_blog_language( $source_blog_id );
  87. $dest_blog_id = get_current_blog_id();
  88. $dest_lang = MslsBlogCollection::get_blog_language( get_current_blog_id() );
  89. $dest_post_id = $this->get_the_blog_post_ID( $dest_blog_id );
  90. if ( empty( $dest_post_id ) ) {
  91. return $data;
  92. }
  93. switch_to_blog( $source_blog_id );
  94. $source_post = get_post( $source_post_id );
  95. restore_current_blog();
  96. if ( ! $source_post instanceof \WP_Post ) {
  97. return $data;
  98. }
  99. $import_coordinates = new ImportCoordinates();
  100. $import_coordinates->source_blog_id = $source_blog_id;
  101. $import_coordinates->source_post_id = $source_post_id;
  102. $import_coordinates->dest_blog_id = $dest_blog_id;
  103. $import_coordinates->dest_post_id = $dest_post_id;
  104. $import_coordinates->source_post = $source_post;
  105. $import_coordinates->source_lang = $source_lang;
  106. $import_coordinates->dest_lang = $dest_lang;
  107. $import_coordinates->parse_importers_from_request();
  108. $data = $this->import_content( $import_coordinates, $data );
  109. if ( $this->has_created_post ) {
  110. $this->update_inserted_blog_post_data($dest_blog_id, $dest_post_id, $data );
  111. $this->redirect_to_blog_post( $dest_blog_id, $dest_post_id );
  112. }
  113. return $data;
  114. }
  115. /**
  116. * Whether the importer should run or not.
  117. *
  118. * @return bool
  119. */
  120. protected function pre_flight_check( array $data = [] ) {
  121. if ( ! $this->handle ) {
  122. return false;
  123. }
  124. if ( ! $this->main->verify_nonce() ) {
  125. return false;
  126. }
  127. if ( ! isset( $_POST['msls_import'] ) ) {
  128. return false;
  129. }
  130. return true;
  131. }
  132. /**
  133. * Parses the source blog and post IDs from the $_POST array validating them.
  134. *
  135. * @return array|bool
  136. */
  137. public function parse_sources() {
  138. if ( ! isset( $_POST['msls_import'] ) ) {
  139. return false;
  140. }
  141. $import_data = array_filter( explode( '|', trim( $_POST['msls_import'] ) ), 'is_numeric' );
  142. if ( count( $import_data ) !== 2 ) {
  143. return false;
  144. }
  145. return array_map( 'intval', $import_data );
  146. }
  147. protected function get_the_blog_post_ID( $blog_id ) {
  148. switch_to_blog( $blog_id );
  149. $id = get_the_ID();
  150. if ( ! empty( $id ) ) {
  151. restore_current_blog();
  152. return $id;
  153. }
  154. if ( isset( $_REQUEST['post'] ) && filter_var( $_REQUEST['post'], FILTER_VALIDATE_INT ) ) {
  155. return (int) $_REQUEST['post'];
  156. }
  157. $data = [
  158. 'post_type' => $this->read_post_type_from_request( 'post' ),
  159. 'post_title' => 'MSLS Content Import Draft - ' . date( 'Y-m-d H:i:s' )
  160. ];
  161. return $this->insert_blog_post( $blog_id, $data );
  162. }
  163. protected function insert_blog_post( $blog_id, array $data = [] ) {
  164. if ( empty( $data ) ) {
  165. return false;
  166. }
  167. switch_to_blog( $blog_id );
  168. $this->handle( false );
  169. if ( isset( $data['ID'] ) ) {
  170. $post_id = wp_update_post( $data );
  171. } else {
  172. $post_id = wp_insert_post( $data );
  173. }
  174. $this->handle( true );
  175. $this->has_created_post = $post_id ?: false;
  176. restore_current_blog();
  177. return $this->has_created_post;
  178. }
  179. public function handle( $handle ) {
  180. $this->handle = $handle;
  181. // also prevent MSLS from saving
  182. if ( false === $handle ) {
  183. add_action( 'msls_main_save', '__return_false' );
  184. } else {
  185. remove_action( 'msls_main_save', '__return_false' );
  186. }
  187. }
  188. /**
  189. * Imports content according to the provided coordinates.
  190. *
  191. * @param ImportCoordinates $import_coordinates
  192. * @param array $post_fields An optional array of post fields; this can be
  193. * left empty if the method is not called as a consequence
  194. * of filtering the `wp_insert_post_data` filter.
  195. *
  196. * @return array An array of modified post fields.
  197. */
  198. public function import_content( ImportCoordinates $import_coordinates, array $post_fields = [] ) {
  199. if ( ! $import_coordinates->validate() ) {
  200. return $post_fields;
  201. }
  202. /**
  203. * Fires before the import runs.
  204. *
  205. * @param ImportCoordinates $import_coordinates
  206. */
  207. do_action( 'msls_content_import_before_import', $import_coordinates );
  208. /**
  209. * Filters the data before the import runs.
  210. *
  211. * @since TBD
  212. *
  213. * @param array $post_fields
  214. * @param ImportCoordinates $import_coordinates
  215. */
  216. $post_fields = apply_filters( 'msls_content_import_data_before_import', $post_fields, $import_coordinates );
  217. /**
  218. * Filters the importers map before it's populated.
  219. *
  220. * Returning a non `null` value here will override the creation of the importers map completely
  221. * and use the one returned in the filter.
  222. *
  223. * @param null $importers
  224. * @param ImportCoordinates $import_coordinates
  225. */
  226. $importers = apply_filters( 'msls_content_import_importers', null, $import_coordinates );
  227. if ( null === $importers ) {
  228. $importers = Map::instance()->make( $import_coordinates );
  229. }
  230. $this->logger = $this->logger ?: new ImportLogger( $import_coordinates );
  231. $this->relations = $this->relations ?: new Relations( $import_coordinates );
  232. if ( ! empty( $importers ) && is_array( $importers ) ) {
  233. $source_post_id = $import_coordinates->source_post_id;
  234. $dest_lang = $import_coordinates->dest_lang;
  235. $dest_post_id = $import_coordinates->dest_post_id;
  236. $this->relations->should_create( MslsOptionsPost::create( $source_post_id ), $dest_lang, $dest_post_id );
  237. foreach ( $importers as $key => $importer ) {
  238. /** @var Importer $importer */
  239. $post_fields = $importer->import( $post_fields );
  240. $this->logger->merge( $importer->get_logger() );
  241. $this->relations->merge( $importer->get_relations() );
  242. }
  243. $this->relations->create();
  244. $this->logger->save();
  245. }
  246. /**
  247. * Fires after the import ran.
  248. *
  249. * @since TBD
  250. *
  251. * @param ImportCoordinates $import_coordinates
  252. * @param ImportLogger $logger
  253. * @param Relations $relations
  254. */
  255. do_action( 'msls_content_import_after_import', $import_coordinates, $this->logger, $this->relations );
  256. /**
  257. * Filters the data after the import ran.
  258. *
  259. * @param array $post_fields
  260. * @param ImportCoordinates $import_coordinates
  261. * @param ImportLogger $logger
  262. * @param Relations $relations
  263. */
  264. return apply_filters( 'msls_content_import_data_after_import', $post_fields, $import_coordinates, $this->logger, $this->relations );
  265. }
  266. /**
  267. * @param array $data
  268. * @param int $post_id
  269. *
  270. * @return array
  271. */
  272. protected function update_inserted_blog_post_data($blog_id, $post_id, array $data ) {
  273. $data['ID'] = $post_id;
  274. $data['post_status'] = empty( $data['post_status'] ) || $data['post_status'] === 'auto-draft'
  275. ? 'draft'
  276. : $data['post_status'];
  277. $this->insert_blog_post( $blog_id, $data );
  278. return $data;
  279. }
  280. protected function redirect_to_blog_post( $dest_blog_id, $post_id ) {
  281. switch_to_blog( $dest_blog_id );
  282. $edit_post_link = html_entity_decode( get_edit_post_link( $post_id ) );
  283. wp_redirect( $edit_post_link );
  284. die();
  285. }
  286. /**
  287. * Filters whether the post should be considered empty or not.
  288. *
  289. * Empty posts would not be saved to database but it's fine if in
  290. * the context of a content import as it will be populated.
  291. *
  292. * @param bool $empty
  293. *
  294. * @return bool
  295. */
  296. public function filter_empty( $empty ) {
  297. if ( ! $this->main->verify_nonce() ) {
  298. return $empty;
  299. }
  300. if ( ! isset( $_POST['msls_import'] ) ) {
  301. return $empty;
  302. }
  303. return false;
  304. }
  305. }