PluginProbe ʕ •ᴥ•ʔ
WP All Import – Drag & Drop Import for CSV, XML, Excel & Google Sheets / trunk
WP All Import – Drag & Drop Import for CSV, XML, Excel & Google Sheets vtrunk
3.9.5 3.9.6 4.0.0 4.0.1 4.1.0 trunk 2.12 2.13 2.14 3.0 3.0.1 3.0.2 3.0.3 3.0.4 3.1.0 3.1.1 3.1.2 3.1.3 3.1.4 3.1.5 3.2.0 3.2.1 3.2.2 3.2.3 3.2.4 3.2.5 3.2.6 3.2.7 3.2.8 3.2.9 3.3.0 3.3.1 3.3.2 3.3.3 3.3.4 3.3.5 3.3.6 3.3.7 3.3.8 3.3.9 3.4.0 3.4.1 3.4.2 3.4.3 3.4.4 3.4.5 3.4.6 3.4.7 3.4.8 3.4.9 3.5.0 3.5.1 3.5.2 3.5.3 3.5.4 3.5.5 3.5.6 3.5.7 3.5.8 3.5.9 3.6.0 3.6.1 3.6.2 3.6.3 3.6.4 3.6.5 3.6.6 3.6.7 3.6.8 3.6.9 3.7.0 3.7.1 3.7.2 3.7.3 3.7.3-beta-1.0 3.7.4 3.7.4-beta-1.0 3.7.5 3.7.6 3.7.7 3.7.8 3.7.9 3.8.0 3.9.0 3.9.1 3.9.2 3.9.3 3.9.4
wp-all-import / addon-api / classes / base.php
wp-all-import / addon-api / classes Last commit date
admin.php 3 weeks ago base.php 3 weeks ago data-importer.php 3 weeks ago helpers.php 3 weeks ago importer.php 3 weeks ago manager.php 3 weeks ago parser.php 3 weeks ago post-data-importer.php 3 weeks ago rest.php 3 weeks ago updater.php 3 weeks ago view.php 3 weeks ago
base.php
529 lines
1 <?php
2 namespace Wpai\AddonAPI;
3
4 if ( ! defined( 'ABSPATH' ) ) exit;
5
6 abstract class PMXI_Addon_Base {
7 use HasError, HasRegistration;
8
9 public PMXI_Addon_Importer $importer;
10
11 public $slug = 'not-implemented'; // Must be implemented by end-user
12 public $version = '0.0.0'; // Must be implemented by end-user
13 public $rootDir = ''; // Must be implemented by end-user
14
15 // Extra fields created by the addon
16 public $fields = [];
17
18 // Cast values to something else without having to create a custom field
19 public $casts = [];
20
21 // Add tooltips to fields
22 public $hints = [];
23
24 // Getters
25 abstract public function name(): string;
26
27 abstract public function description(): string;
28
29 public function __construct() {
30 $this->preflight();
31 $this->registerAsAddon();
32
33 $this->importer = PMXI_Addon_Importer::from( $this );
34 $this->initEed();
35
36 add_action('init', function() {
37 $this->hints = [
38 'time' => __( 'Use any format supported by the PHP strtotime function.', 'wp-all-import' ),
39 'date' => __( 'Use any format supported by the PHP strtotime function.', 'wp-all-import' ),
40 'datetime' => __( 'Use any format supported by the PHP strtotime function.', 'wp-all-import' ),
41 'iconpicker' => __( 'Specify the icon class name - e.g. fa-user.', 'wp-all-import' ),
42 'colorpicker' => __( 'Specify the hex code the color preceded with a # - e.g. #ea5f1a.', 'wp-all-import' ),
43 'media' => __( 'Specify the URL to the image or file.', 'wp-all-import' ),
44 'post' => __( 'Enter the ID, slug, or Title. Separate multiple entries with separator character.', 'wp-all-import' ),
45 'user' => __( 'Enter the ID, username, or email for the existing user.', 'wp-all-import' ),
46 'map' => __( 'WP All Import will first try to get your Google Maps API key from the add-on you\'re using. If that fails you must enter the key under \'Google Maps Settings\' below.', 'wp-all-import' )
47 ];
48 });
49
50 add_filter( 'wp_all_import_addon_parse', [ $this, 'registerParseFunction' ] );
51 add_filter( 'wp_all_import_addon_import', [ $this, 'registerImportFunction' ] );
52 add_filter( 'wp_all_import_addon_saved_post', [ $this, 'registerPostSavedFunction' ] );
53 add_filter( 'pmxi_custom_types', [ $this, 'registerCustomTypes' ], 2, 5 );
54 add_filter( 'pmxi_options_options', [ $this, 'registerDefaultOptions' ] );
55 add_filter( 'pmxi_save_options', [ $this, 'updateOptions' ] );
56 add_filter( 'pmxi_custom_field_to_delete', [ $this, 'canDeleteField' ], 99, 5 );
57 add_filter( 'pmxi_visible_template_sections', [ $this, 'getVisibleSections' ], 99, 2 );
58 add_filter( 'pmxi_hidden_data_to_update_options', [ $this, 'getHiddenChooseDataToUpdateOptions' ], 99, 2 );
59 add_filter( 'pmxi_disabled_delete_missing_options', [ $this, 'getDisabledDeleteMissingOptions' ], 99, 2 );
60 add_filter( 'pmxi_hidden_delete_missing_options', [ $this, 'getHiddenDeleteMissingOptions' ], 99, 2 );
61 add_filter( 'pmxi_status_of_removed_options', [ $this, 'getStatusOfRemovedOptions' ], 99, 2 );
62 add_filter( 'pmxi_fire_hooks', [ $this, 'shouldFirePostHooks' ], 10, 2 );
63 add_filter( 'pmxi_types_current_type_supports_title', [ $this, 'supportsTitle' ], 99, 2 );
64 }
65
66 /**
67 * Path to the plugin file relative to the plugins directory.
68 */
69 public function getPluginPath() {
70 return $this->rootDir . '/plugin.php';
71 }
72
73 /**
74 * Do stuff before the plugin is activated
75 *
76 * @return void
77 */
78 public function preflight() {
79 $results = $this->canRun();
80
81 if ( is_wp_error( $results ) ) {
82 $this->showErrorAndDeactivate( $results->get_error_message() );
83 }
84 }
85
86 /**
87 * Determine if the addon can be used for the current import type.
88 *
89 * @param string $importType
90 * @param $options
91 *
92 * @return bool
93 */
94 public function isAvailableForType( string $importType, $options ) {
95 $customTypes = array_keys($this->getCustomTypes());
96 $types = $this->availableForTypes();
97
98 $unprefixedTypes = array_values( array_filter( $types, fn( $type ) => $type[0] !== '-' ) );
99 // If the type is prefixed with a dash, it means the addon not available for it
100 $shouldSkip = in_array( '-' . $importType, $types );
101
102
103 if ( in_array( $importType, $customTypes ) ) {
104 return true;
105 }
106
107 if ( $importType === 'taxonomies' ) {
108 $taxonomy = $options['taxonomy_type'];
109
110 return count( $unprefixedTypes ) === 0 || in_array( 'taxonomy:' . $taxonomy, $types ) || in_array( 'taxonomies', $types );
111 }
112
113 if ( $shouldSkip ) {
114 return false;
115 }
116
117 return count( $unprefixedTypes ) === 0 || in_array( $importType, $types );
118 }
119
120 /**
121 * Provide an interface for developers to create custom importers for Non-WordPress data.
122 *
123 * @param $options
124 *
125 * @return class-string<PMXI_Addon_Importer>|null
126 */
127 public function getCustomImporter( $options ) {
128 return null;
129 }
130
131 /**
132 * List of post types and taxonomies the plugin is available for.
133 * Leave empty to make it available for all post types.
134 *
135 * @return string[]
136 */
137 public function availableForTypes() {
138 return [];
139 }
140
141 /**
142 * Was this import type created by this addon?
143 *
144 * @param string $importType
145 * @param $options
146 *
147 * @return bool
148 */
149 public function ownsImportType( string $importType, $options ) {
150 $types = array_keys( $this->getCustomTypes() );
151 return in_array( $importType, $types );
152 }
153
154 /**
155 * Register Custom Types that are not part of WordPress core.
156 * - This is useful for plugins that have their own data structures.
157 * - Prefix the key with the plugin slug to avoid conflicts.
158 *
159 * @return string[]
160 */
161 public function getCustomTypes() {
162 return [];
163 }
164
165 /**
166 * The function called by the add_filter hook.
167 *
168 * @return string[]
169 */
170 public function registerCustomTypes( $types, $section ) {
171 return array_merge( $types, $this->getCustomTypes() );
172 }
173
174 /**
175 * Show or hide sections based on the import type.
176 *
177 * @param $sections
178 * @param $type
179 *
180 * @return mixed
181 */
182 public function getVisibleSections( $sections, $type ) {
183 return $sections;
184 }
185
186 /**
187 * Show or hide sections based on the import type in "Choose Which Data to Update" options.
188 *
189 * @param $options
190 * @param $type
191 *
192 * @return mixed
193 */
194 public function getHiddenChooseDataToUpdateOptions( $options, $type ) {
195 return $options;
196 }
197
198 /**
199 * Enable or Disable delete sections based on the import type in "What do you want to do with those..." options.
200 *
201 * @param $options
202 * @param $type
203 *
204 * @return mixed
205 */
206 public function getDisabledDeleteMissingOptions( $options, $type ) {
207 return $options;
208 }
209
210 /**
211 * Show or hide delete sections based on the import type in "What do you want to do with those..." options.
212 *
213 * @param $options
214 * @param $type
215 *
216 * @return mixed
217 */
218 public function getHiddenDeleteMissingOptions( $options, $type ) {
219 return $options;
220 }
221
222 /**
223 * Modify statuses based on the import type in "Change status of removed...".
224 *
225 * @param $options
226 * @param $type
227 *
228 * @return mixed
229 */
230 public function getStatusOfRemovedOptions( $options, $type ) {
231 return $options;
232 }
233
234 /**
235 * Decide if we should fire important hooks after custom fields are added.
236 * This is only applicable custom post type.
237 *
238 * @param bool $should_fire
239 * @param string $type
240 *
241 * @return bool
242 */
243 public function shouldFirePostHooks( bool $should_fire, string $type ) {
244 return $should_fire;
245 }
246
247 /**
248 * Determine if the import type supports a title field.
249 *
250 * @param bool $supports
251 * @param string $type
252 *
253 * @return bool
254 */
255 public function supportsTitle(bool $supports, string $type) {
256 return $supports;
257 }
258
259 /**
260 * @return true
261 */
262 public function isAccordionClosed(string $type, ?string $subtype = null) {
263 return true;
264 }
265
266 /**
267 * Allow addons to initialize their own EED classes. Empty by default.
268 *
269 * @return void
270 */
271 public function initEed() {}
272
273 /**
274 * Determine if the plugin can run on the current site otherwise disable it.
275 *
276 * @return bool|\WP_Error
277 */
278 abstract public function canRun();
279
280 /**
281 * Get fields by import type (post, term, user, etc.) and taxonomy (if applicable)
282 *
283 * @param string $type
284 * @param string|null $subtype
285 *
286 * @return mixed
287 */
288 abstract public static function fields( string $type, ?string $subtype = null );
289
290 /**
291 * Get groups by import type (post, term, user, etc.) and taxonomy (if applicable)
292 *
293 * @param string $type
294 * @param string|null $subtype
295 *
296 * @return mixed
297 */
298 abstract public static function groups( string $type, ?string $subtype = null );
299
300 /**
301 * Import fields to the database
302 */
303 abstract public static function import(
304 int $id,
305 array $fields,
306 array $values,
307 \PMXI_Import_Record $record,
308 array $post,
309 $logger
310 );
311
312 /**
313 * Potentially change the class of a field at runtime
314 *
315 * @param array $field
316 * @param class-string<PMXI_Addon_Field> $class
317 */
318 public function resolveFieldClass( $field, $class ) {
319 return $class;
320 }
321
322 /**
323 * Internal method to simplify the import function for end-users.
324 *
325 * @param array $importData
326 * @param array $parsedData
327 */
328 public function transformImport( array $importData, array $parsedData ) {
329 $params = $this->importer->simplify( $importData, $parsedData );
330 if ( ! $params ) {
331 return;
332 }
333 call_user_func_array( [ $this, 'import' ], $params );
334 }
335
336 /**
337 * Parse the data from the XML file
338 *
339 * @param array $data
340 *
341 * @return array
342 */
343 public function parse( array $data ) {
344 $type = $data['import']->options['custom_type'];
345 $subtype = $data['import']->options['taxonomy_type'];
346 $defaults = $this->importer->defaults( $type, $subtype );
347
348 return PMXI_Addon_Parser::from( $this, $data, $defaults );
349 }
350
351 /**
352 * Called after the post has been saved
353 */
354 public function postSaved( array $importData ) {
355 }
356
357 public function defaultOptions( string $type, ?string $subtype = null ) {
358 return $this->importer->defaults( $type, $subtype );
359 }
360
361 public function defaultUpdateOptions() {
362 return [
363 'is_update' => true,
364 'update_logic' => 'full_update',
365 'fields_list' => [],
366 'fields_only_list' => [],
367 'fields_except_list' => [],
368 ];
369 }
370
371 public function updateOptions( $options ) {
372 if ( ! isset( $options['update_addons'][ $this->slug ] ) ) {
373 return $options;
374 }
375
376 $post = $options['update_addons'][ $this->slug ];
377
378 if ( $post['update_logic'] === 'only' && ! empty( $post['fields_only_list'] ) ) {
379 $post['fields_list'] = explode( ",", $post['fields_only_list'] );
380 } elseif ( $post['update_logic'] == 'all_except' && ! empty( $post['fields_except_list'] ) ) {
381 $post['fields_list'] = explode( ",", $post['fields_except_list'] );
382 }
383
384 $options['update_addons'][ $this->slug ] = $post;
385
386 return $options;
387 }
388
389 /**
390 * @param bool $field_to_delete
391 * @param int $pid
392 * @param string $post_type
393 * @param array $options
394 * @param string $cur_meta_key
395 *
396 * @return bool
397 */
398 public function canDeleteField( $field_to_delete, $pid, $post_type, $options, $cur_meta_key ) {
399 $type = $options['custom_type'];
400 $subtype = $options['taxonomy_type'];
401 $data = $options[ $this->slug ] ?? null;
402 $groups = $options[ $this->slug . '_groups' ] ?? [];
403
404 $fields = $this->getImportFields( $type, $subtype, $groups, $data );
405 $field = $this->getFieldByKey( $fields, $cur_meta_key );
406
407 if ( ! $field ) {
408 return apply_filters( "pmxi_is_{$this->slug}_update_allowed", $field_to_delete, $cur_meta_key, $options );
409 }
410
411 $canDelete = $this->importer->canDeleteField( $field, $options );
412
413 return apply_filters( "pmxi_is_{$this->slug}_update_allowed", $canDelete, $cur_meta_key, $options );
414 }
415
416 public function getImportFields( $type, $subtype, $groups, $data ) {
417 // Filter out fields that don't exist in the parsed data
418 $fields = array_filter(
419 $this->fields( $type, $subtype ),
420 fn( $field ) => isset( $data[ $field['key'] ] )
421 );
422
423 // Filter out fields from disabled groups
424 $fields = array_filter(
425 $fields,
426 fn( $field ) => in_array( $field['group'], $groups )
427 );
428
429 return array_values( $fields );
430 }
431
432 // Fields and groups helpers
433
434 public function getFieldByKey( $fields, $key ) {
435 $index = array_column( $fields, 'key' );
436 $map = array_flip( $index );
437
438 return $fields[ $map[ $key ] ?? null ] ?? null;
439 }
440
441 /**
442 * @param string $groupId
443 * @param string $type
444 * @param string|null $subtype
445 *
446 * @return array
447 */
448 public static function getFieldsByGroup( string $groupId, string $type, ?string $subtype = null ) {
449 return pipe( static::fields( $type, $subtype ), [
450 fn( $fields ) => array_filter( $fields, fn( $field ) => $field['group'] === $groupId ),
451 fn( $fields ) => array_values( $fields )
452 ] );
453 }
454
455 public static function getGroupById( string $groupId, string $type, ?string $subtype = null ) {
456 return pipe( static::groups( $type, $subtype ), [
457 fn( $groups ) => array_filter( $groups, fn( $group ) => $group['id'] === $groupId ),
458 fn( $groups ) => array_values( $groups )[0]
459 ] );
460 }
461 }
462
463 trait HasRegistration {
464 // Todo: Maybe refactor this by using the PMXI_Admin_Addons class
465 public function registerAsAddon() {
466 add_filter(
467 'pmxi_new_addons',
468 function ( $addons ) {
469 $addons[ $this->slug ] = $this;
470
471 return $addons;
472 }
473 );
474
475 add_filter( 'pmxi_addons', function ( $addons ) {
476 if ( empty( $addons[ $this->slug ] ) ) {
477 $addons[ $this->slug ] = 1;
478 }
479
480 return $addons;
481 } );
482 }
483
484 public function registerParseFunction( $functions ) {
485 $functions[ $this->slug ] = [ $this, 'parse' ];
486
487 return $functions;
488 }
489
490 public function registerImportFunction( $functions ) {
491 $functions[ $this->slug ] = [ $this, 'transformImport' ];
492
493 return $functions;
494 }
495
496 public function registerPostSavedFunction( $functions ) {
497 $functions[ $this->slug ] = [ $this, 'postSaved' ];
498
499 return $functions;
500 }
501
502 public function registerDefaultOptions( $options ) {
503 $options = $options + $this->defaultOptions( $options['custom_type'], $options['taxonomy_type'] );
504
505 return $options;
506 }
507 }
508
509 trait HasError {
510 public function showErrorAndDeactivate( string $msg ) {
511 if ( ! function_exists( 'get_plugins' ) ) {
512 require_once ABSPATH . 'wp-admin/includes/plugin.php';
513 }
514
515 $notice = new \Wpai\WordPress\AdminErrorNotice( $msg );
516 $notice->render();
517
518 deactivate_plugins( $this->getPluginPath() );
519 }
520
521 public function getMissingDependencyError( $pluginName, $pluginUrl ) {
522 return new \WP_Error( 'missing_dependency', sprintf(
523 /* translators: 1: add-on name, 2: dependency plugin URL, 3: dependency plugin name */
524 __( '<b>%1$s Plugin</b>: <a target="_blank" href="%2$s">%3$s</a> must be installed', 'wp-all-import' ),
525 $this->name(), $pluginUrl, $pluginName
526 ) );
527 }
528 }
529