' . esc_html__( 'About', 'custom-post-type-ui' ) . '', '' . esc_html__( 'Help', 'custom-post-type-ui' ) . '', ), $links ); } add_filter( 'plugin_action_links_' . plugin_basename( dirname( __DIR__ ) ) . '/custom-post-type-ui.php', 'cptui_edit_plugin_list_links' ); /** * Returns SVG icon for custom menu icon * * @since 1.2.0 * * @return string */ function cptui_menu_icon() { return 'dashicons-forms'; } /** * Return boolean status depending on passed in value. * * @since 0.5.0 * * @param mixed $bool_text text to compare to typical boolean values. * @return bool Which bool value the passed in value was. */ function get_disp_boolean( $bool_text ) { $bool_text = (string) $bool_text; if ( empty( $bool_text ) || '0' === $bool_text || 'false' === $bool_text ) { return false; } return true; } /** * Return string versions of boolean values. * * @since 0.1.0 * * @param string $bool_text String boolean value. * @return string standardized boolean text. */ function disp_boolean( $bool_text ) { $bool_text = (string) $bool_text; if ( empty( $bool_text ) || '0' === $bool_text || 'false' === $bool_text ) { return 'false'; } return 'true'; } /** * Display footer links and plugin credits. * * @since 0.3.0 * * @internal * * @param string $original Original footer content. Optional. Default empty string. * @return string $value HTML for footer. */ function cptui_footer( $original = '' ) { $screen = get_current_screen(); if ( ! is_object( $screen ) || 'cptui_main_menu' !== $screen->parent_base ) { return $original; } return sprintf( // translators: Placeholder will hold the name of the plugin, version of the plugin and a link to WebdevStudios. esc_attr__( '%1$s version %2$s by %3$s', 'custom-post-type-ui' ), esc_attr__( 'Custom Post Type UI', 'custom-post-type-ui' ), CPTUI_VERSION, 'WebDevStudios' ) . ' - ' . sprintf( // translators: Placeholders are just for HTML markup that doesn't need translated. '%s', esc_attr__( 'Support forums', 'custom-post-type-ui' ) ) . ' - ' . sprintf( // translators: Placeholders are just for HTML markup that doesn't need translated. '%s', sprintf( // translators: Placeholder will hold `` tag for CPTUI. esc_attr__( 'Review %s', 'custom-post-type-ui' ), sprintf( // translators: Placeholders are just for HTML markup that doesn't need translated. '%s', esc_attr__( 'Custom Post Type UI', 'custom-post-type-ui' ), 'CPTUI' ) ) ) . ' - ' . esc_attr__( 'Follow on X:', 'custom-post-type-ui' ) . sprintf( // translators: Placeholders are just for HTML markup that doesn't need translated. ' %s', 'WebDevStudios' ); } add_filter( 'admin_footer_text', 'cptui_footer' ); /** * Conditionally flushes rewrite rules if we have reason to. * * @since 1.3.0 */ function cptui_flush_rewrite_rules() { if ( wp_doing_ajax() ) { return; } /* * Wise men say that you should not do flush_rewrite_rules on init or admin_init. Due to the nature of our plugin * and how new post types or taxonomies can suddenly be introduced, we need to...potentially. For this, * we rely on a short lived transient. Only 5 minutes life span. If it exists, we do a soft flush before * deleting the transient to prevent subsequent flushes. The only times the transient gets created, is if * post types or taxonomies are created, updated, deleted, or imported. Any other time and this condition * should not be met. */ $flush_it = get_transient( 'cptui_flush_rewrite_rules' ); if ( 'true' === $flush_it ) { flush_rewrite_rules( false ); // So we only run this once. delete_transient( 'cptui_flush_rewrite_rules' ); } } add_action( 'admin_init', 'cptui_flush_rewrite_rules' ); /** * Return the current action being done within CPTUI context. * * @since 1.3.0 * * @return string Current action being done by CPTUI */ function cptui_get_current_action() { $current_action = ''; if ( ! empty( $_GET ) && isset( $_GET['action'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification $current_action .= esc_textarea( wp_unslash( $_GET['action'] ) ); // phpcs:ignore } return $current_action; } /** * Return an array of all post type slugs from Custom Post Type UI. * * @since 1.3.0 * * @return array CPTUI post type slugs. */ function cptui_get_post_type_slugs() { $post_types = get_option( 'cptui_post_types' ); if ( ! empty( $post_types ) ) { return array_keys( $post_types ); } return []; } /** * Return an array of all taxonomy slugs from Custom Post Type UI. * * @since 1.3.0 * * @return array CPTUI taxonomy slugs. */ function cptui_get_taxonomy_slugs() { $taxonomies = get_option( 'cptui_taxonomies' ); if ( ! empty( $taxonomies ) ) { return array_keys( $taxonomies ); } return []; } /** * Return the appropriate admin URL depending on our context. * * @since 1.3.0 * * @param string $path URL path. * @return string */ function cptui_admin_url( $path ) { if ( is_multisite() && is_network_admin() ) { return network_admin_url( $path ); } return admin_url( $path ); } /** * Construct action tag for `
', esc_url( isset( $ad['url'] ) ? $ad['url'] : '' ), esc_attr( $ad['image'] ), esc_attr( isset( $ad['text'] ) ? $ad['text'] : '' ) ); return; } if ( empty( $ad['name'] ) || empty( $ad['url'] ) ) { return; } $url = cptui_promo_url_for_placement( $ad['url'], $placement ); $cta_label = isset( $ad['cta_label'] ) ? $ad['cta_label'] : __( 'Learn more', 'custom-post-type-ui' ); ?>'; $messagewrapend = '
'; /** * Filters the custom admin notice for CPTUI. * * @deprecated * * @param string $value Complete HTML output for notice. * @param string $action Action whose message is being generated. * @param string $message The message to be displayed. * @param string $messagewrapstart Beginning wrap HTML. * @param string $messagewrapend Ending wrap HTML. * * @since 1.0.0 */ $notice = apply_filters_deprecated( 'cptui_admin_notice', [ $messagewrapstart . $message . $messagewrapend, '', $message, $messagewrapstart, $messagewrapend ], '1.18.3', '', esc_html__( 'No filter replacement. Deprecated', 'custom-post-type-ui' ) ); $type = $success ? 'success' : 'error'; if ( ! empty( $success_override_type ) ) { $type = $success_override_type; } wp_admin_notice( $message, [ 'id' => 'cptui-message', 'type' => $type, 'dismissible' => true, ] ); } /** * Grab post type or taxonomy slug from $_POST global, if available. * * @since 1.4.0 * * @internal * * @return string */ function cptui_get_object_from_post_global() { if ( isset( $_POST['cpt_custom_post_type']['name'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification $type_item = filter_input( INPUT_POST, 'cpt_custom_post_type', FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_REQUIRE_ARRAY ); if ( $type_item ) { return sanitize_text_field( $type_item['name'] ); } } if ( isset( $_POST['cpt_custom_tax']['name'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification $tax_item = filter_input( INPUT_POST, 'cpt_custom_tax', FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_REQUIRE_ARRAY ); if ( $tax_item ) { return sanitize_text_field( $tax_item['name'] ); } } return esc_html__( 'Object', 'custom-post-type-ui' ); } /** * Successful add callback. * * @since 1.4.0 */ function cptui_add_success_admin_notice() { cptui_admin_notices_helper( // phpcs:ignore WordPress.Security.EscapeOutput sprintf( /* translators: Placeholders are just for HTML markup that doesn't need translated */ esc_html__( '%s has been successfully added', 'custom-post-type-ui' ), cptui_get_object_from_post_global() ) ); } /** * Fail to add callback. * * @since 1.4.0 */ function cptui_add_fail_admin_notice() { cptui_admin_notices_helper( // phpcs:ignore WordPress.Security.EscapeOutput sprintf( /* translators: Placeholders are just for HTML markup that doesn't need translated */ esc_html__( '%s has failed to be added', 'custom-post-type-ui' ), cptui_get_object_from_post_global() ), false ); } /** * Successful update callback. * * @since 1.4.0 */ function cptui_update_success_admin_notice() { cptui_admin_notices_helper( // phpcs:ignore WordPress.Security.EscapeOutput sprintf( /* translators: Placeholders are just for HTML markup that doesn't need translated */ esc_html__( '%s has been successfully updated', 'custom-post-type-ui' ), cptui_get_object_from_post_global() ) ); } /** * Fail to update callback. * * @since 1.4.0 */ function cptui_update_fail_admin_notice() { cptui_admin_notices_helper( // phpcs:ignore WordPress.Security.EscapeOutput sprintf( /* translators: Placeholders are just for HTML markup that doesn't need translated */ esc_html__( '%s has failed to be updated', 'custom-post-type-ui' ), cptui_get_object_from_post_global() ), false ); } /** * Successful delete callback. * * @since 1.4.0 */ function cptui_delete_success_admin_notice() { cptui_admin_notices_helper( // phpcs:ignore WordPress.Security.EscapeOutput sprintf( /* translators: Placeholders are just for HTML markup that doesn't need translated */ esc_html__( '%s has been successfully deleted', 'custom-post-type-ui' ), cptui_get_object_from_post_global() ) ); } /** * Fail to delete callback. * * @since 1.4.0 */ function cptui_delete_fail_admin_notice() { cptui_admin_notices_helper( // phpcs:ignore WordPress.Security.EscapeOutput sprintf( /* translators: Placeholders are just for HTML markup that doesn't need translated */ esc_html__( '%s has failed to be deleted', 'custom-post-type-ui' ), cptui_get_object_from_post_global() ), false ); } /** * Success to import callback. * * @since 1.5.0 */ function cptui_import_success_admin_notice() { cptui_admin_notices_helper( // phpcs:ignore WordPress.Security.EscapeOutput esc_html__( 'Successfully imported data.', 'custom-post-type-ui' ) ); } /** * Failure to import callback. * * @since 1.5.0 */ function cptui_import_fail_admin_notice() { cptui_admin_notices_helper( // phpcs:ignore WordPress.Security.EscapeOutput esc_html__( 'Invalid data provided', 'custom-post-type-ui' ), false ); } /** * Failure to verify nonce, callback * * @since 1.7.4 */ function cptui_nonce_fail_admin_notice() { cptui_admin_notices_helper( // phpcs:ignore WordPress.Security.EscapeOutput esc_html__( 'Nonce failed verification', 'custom-post-type-ui' ), false ); } /** * Returns error message for if trying to register existing post type. * * @since 1.4.0 * * @return string */ function cptui_slug_matches_post_type() { return sprintf( /* translators: Placeholders are just for HTML markup that doesn't need translated */ esc_html__( 'Please choose a different post type name. %s is already registered.', 'custom-post-type-ui' ), cptui_get_object_from_post_global() ); } /** * Returns error message for if trying to register existing taxonomy. * * @since 1.4.0 * * @return string */ function cptui_slug_matches_taxonomy() { return sprintf( /* translators: Placeholders are just for HTML markup that doesn't need translated */ esc_html__( 'Please choose a different taxonomy name. %s is already registered.', 'custom-post-type-ui' ), cptui_get_object_from_post_global() ); } /** * Returns error message for if not providing a post type to associate taxonomy to. * * @since 1.6.0 * * @return string */ function cptui_empty_cpt_on_taxonomy() { return esc_html__( 'Please provide a post type to attach to.', 'custom-post-type-ui' ); } /** * Returns error message for if trying to register post type with matching page slug. * * @since 1.4.0 * * @return string */ function cptui_slug_matches_page() { $slug = cptui_get_object_from_post_global(); $matched_slug = get_page_by_path( cptui_get_object_from_post_global() ); if ( $matched_slug instanceof WP_Post ) { $slug = sprintf( /* translators: Placeholders are just for HTML markup that doesn't need translated */ '%s', get_edit_post_link( $matched_slug->ID ), cptui_get_object_from_post_global() ); } return sprintf( /* translators: Placeholders are just for HTML markup that doesn't need translated */ esc_html__( 'Please choose a different post type name. %s matches an existing page slug, which can cause conflicts.', 'custom-post-type-ui' ), $slug ); } /** * Returns error message for if trying to use quotes in slugs or rewrite slugs. * * @since 1.4.0 * * @return string */ function cptui_slug_has_quotes() { return sprintf( esc_html__( 'Please do not use quotes in post type/taxonomy names or rewrite slugs', 'custom-post-type-ui' ), cptui_get_object_from_post_global() ); } /** * Error admin notice. * * @since 1.4.0 */ function cptui_error_admin_notice() { cptui_admin_notices_helper( // phpcs:ignore WordPress.Security.EscapeOutput apply_filters( 'cptui_custom_error_message', '' ), false ); } /** * Mark site as not a new CPTUI install upon update to 1.5.0 * * @since 1.5.0 * * @param object $wp_upgrader WP_Upgrader instance. * @param array $extras Extra information about performed upgrade. */ function cptui_not_new_install( $wp_upgrader, $extras ) { if ( $wp_upgrader instanceof \Plugin_Upgrader ) { return; } if ( ! array_key_exists( 'plugins', $extras ) || ! is_array( $extras['plugins'] ) ) { return; } // Was CPTUI updated? if ( ! in_array( 'custom-post-type-ui/custom-post-type-ui.php', $extras['plugins'], true ) ) { return; } // If we are already known as not new, return. if ( cptui_is_new_install() ) { return; } // We need to mark ourselves as not new. cptui_set_not_new_install(); } add_action( 'upgrader_process_complete', 'cptui_not_new_install', 10, 2 ); /** * Check whether or not we're on a new install. * * @since 1.5.0 * * @return bool */ function cptui_is_new_install() { $new_or_not = true; $saved = get_option( 'cptui_new_install', '' ); if ( 'false' === $saved ) { $new_or_not = false; } /** * Filters the new install status. * * Offers third parties the ability to override if they choose to. * * @since 1.5.0 * * @param bool $new_or_not Whether or not site is a new install. */ return (bool) apply_filters( 'cptui_is_new_install', $new_or_not ); } /** * Set our activation status to not new. * * @since 1.5.0 */ function cptui_set_not_new_install() { update_option( 'cptui_new_install', 'false' ); } /** * Returns saved values for single post type from CPTUI settings. * * @since 1.5.0 * * @param string $post_type Post type to retrieve CPTUI object for. * @return string */ function cptui_get_cptui_post_type_object( $post_type = '' ) { $post_types = get_option( 'cptui_post_types', [] ); if ( is_array( $post_types ) && array_key_exists( $post_type, $post_types ) ) { return $post_types[ $post_type ]; } return []; } /** * Returns saved values for single taxonomy from CPTUI settings. * * @since 1.5.0 * * @param string $taxonomy Taxonomy to retrieve CPTUI object for. * @return string */ function cptui_get_cptui_taxonomy_object( $taxonomy = '' ) { $taxonomies = get_option( 'cptui_taxonomies', [] ); if ( is_array( $taxonomies ) && array_key_exists( $taxonomy, $taxonomies ) ) { return $taxonomies[ $taxonomy ]; } return []; } /** * Checks if a requested post type has a custom CPTUI feature supported. * * @since 1.5.0 * * @param string $post_type Post type slug. * @param string $feature Feature to check for. * @return bool */ function cptui_post_type_supports( $post_type, $feature ) { $object = cptui_get_cptui_post_type_object( $post_type ); if ( ! empty( $object ) ) { if ( array_key_exists( $feature, $object ) && ! empty( $object[ $feature ] ) ) { return true; } return false; } return false; } /** * Add missing post_format taxonomy support for CPTUI post types. * * Addresses bug wih previewing changes for published posts with post types that * have post-formats support. * * @since 1.5.8 * * @param array $post_types Array of CPTUI post types. */ function cptui_published_post_format_fix( $post_types ) { if ( empty( $post_types ) || ! is_array( $post_types ) ) { return; } foreach ( $post_types as $type ) { if ( ! is_array( $type['supports'] ) ) { continue; } if ( in_array( 'post-formats', $type['supports'], true ) ) { add_post_type_support( $type['name'], 'post-formats' ); register_taxonomy_for_object_type( 'post_format', $type['name'] ); } } } add_action( 'cptui_post_register_post_types', 'cptui_published_post_format_fix' ); /** * Return a ready-to-use admin url for adding a new content type. * * @since 1.7.0 * * @param string $content_type Content type to link to. * @return string */ function cptui_get_add_new_link( $content_type = '' ) { if ( ! in_array( $content_type, [ 'post_types', 'taxonomies' ], true ) ) { return cptui_admin_url( 'admin.php?page=cptui_manage_post_types' ); } return cptui_admin_url( 'admin.php?page=cptui_manage_' . $content_type ); } /** * Register theme support for CPTUI based content types, for extra assurance. * * @since 1.14.0 */ function cptui_post_thumbnail_theme_support() { $post_types = cptui_get_post_type_data(); if ( empty( $post_types ) || ! is_array( $post_types ) ) { return; } $supported = []; foreach ( $post_types as $post_type ) { if ( empty( $post_type['supports'] ) ) { continue; } if ( // Some way, somehow, null can end up saved in the supports spot. // Lets check for an array first. is_array( $post_type['supports'] ) && in_array( 'thumbnail', $post_type['supports'] ) ) { $supported[] = $post_type['name']; } } if ( ! empty( $supported ) ) { add_theme_support( 'post-thumbnails', $supported ); } } add_action( 'after_setup_theme', 'cptui_post_thumbnail_theme_support' ); function cptui_add_dialog_missing_post_type_confirm() { ?> CPT UI Pro' ); } /** * Output a CPT UI Pro upsell message for use with admin notifications in WP_List_Table views. * * @since 1.20.0 * * @param string $post_type_slug * * @return string; */ function cptui_post_type_list_pro_upsell_messaging( $post_type_slug ) { return sprintf( // translators: Placeholder will hold the name of the plugin, a link to Pluginize, and a dismiss link. esc_attr__( '%1$s lets you display this post type anywhere with a visual Shortcode Builder and a dedicated Gutenberg block. Display with %2$s — %3$s', 'custom-post-type-ui' ), 'CPT UI Pro', 'CPT UI Pro', sprintf( '%2$s', esc_url( add_query_arg( [ 'cptui-action' => 'cptui-dismiss', 'cptui-dismiss-nonce' => wp_create_nonce( 'cptui-dismiss-nonce' ) ], admin_url( 'edit.php?post_type=' . $post_type_slug ) ) ), esc_html__( 'Dismiss', 'custom-post-type-ui' ) ) ); } /** * Conditionally output an admin notification for our CPT UI Pro upsell. * * @since 1.20.0 */ function cptui_pro_upsell_notification() { if ( ! current_user_can( 'manage_options' ) ) { return; } if ( wp_doing_ajax() ) { return; } // If CPT UI Pro already exists and is active. if ( class_exists( 'CPTUI_Pro' ) ) { return; } $screen = get_current_screen(); // our Add new content type tabs. if ( is_object( $screen ) && ( in_array( $screen->base, [ 'cpt-ui_page_cptui_manage_post_types', 'cpt-ui_page_cptui_manage_taxonomies' ], true ) && empty( $_GET['action'] ) ) ) { cptui_admin_notices_helper( cptui_add_new_pro_upsell_messaging(), false, 'warning' ); } $public = get_post_types( [ '_builtin' => false, 'public' => true, ] ); if ( $screen->base === 'edit' && ! empty( $_GET['post_type'] ) && in_array( $_GET['post_type'], $public, true ) ) { if ( cptui_user_dismissed_pro_upsell() ) { return; } cptui_admin_notices_helper( cptui_post_type_list_pro_upsell_messaging( sanitize_text_field( $_GET['post_type'] ) ), false, 'warning' ); } } /** * Whether the current user has dismissed the CPT UI Pro upsell notice. * * @since 1.20.0 * * @return bool */ function cptui_user_dismissed_pro_upsell() { $dismissals = get_option( 'cptui-user-dismissed-pro-upsell', [] ); if ( empty( $dismissals ) ) { return false; } $user_id = get_current_user_id(); return ! empty( $dismissals[ 'user_id_' . $user_id ] ) && 'true' === $dismissals[ 'user_id_' . $user_id ]; } add_action( 'admin_notices', 'cptui_pro_upsell_notification', 11 ); /** * Enqueue the CPT UI Pro sidebar panel for the block editor. * * Renders a PluginDocumentSettingPanel pitching the Pro Gutenberg display * block in the right-hand Document Settings sidebar. Only loads on the post * editor screens for CPTUI-registered post types and only when CPT UI Pro is * not already active. * * @since 1.20.0 */ function cptui_enqueue_pro_panel_assets() { if ( ! current_user_can( 'manage_options' ) ) { return; } if ( class_exists( 'CPTUI_Pro' ) ) { return; } if ( cptui_user_dismissed_pro_upsell() ) { return; } $screen = get_current_screen(); if ( ! is_object( $screen ) || empty( $screen->post_type ) ) { return; } $cptui_post_types = cptui_get_post_type_slugs(); if ( ! in_array( $screen->post_type, $cptui_post_types, true ) ) { return; } $asset_file = plugin_dir_path( __DIR__ ) . 'build/cptui-editor.asset.php'; if ( ! file_exists( $asset_file ) ) { return; } $asset = include $asset_file; wp_enqueue_script( 'cptui-pro-panel', plugin_dir_url( __DIR__ ) . 'build/cptui-editor.js', $asset['dependencies'], $asset['version'], true ); wp_localize_script( 'cptui-pro-panel', 'cptuiProPanel', [ 'postTypes' => $cptui_post_types, 'proUrl' => 'https://pluginize.com/plugins/custom-post-type-ui-pro/?utm_source=cptui-editor-panel&utm_medium=plugin&utm_campaign=cptui', ] ); } add_action( 'enqueue_block_editor_assets', 'cptui_enqueue_pro_panel_assets' ); /** * Register REST route used by the block editor panel to dismiss the Pro upsell. * * @since 1.20.0 */ function cptui_register_pro_panel_rest_route() { register_rest_route( 'cptui/v1', '/dismiss-pro-upsell', [ 'methods' => 'POST', 'callback' => 'cptui_rest_dismiss_pro_upsell', 'permission_callback' => function () { return current_user_can( 'manage_options' ); }, ] ); } add_action( 'rest_api_init', 'cptui_register_pro_panel_rest_route' ); /** * REST callback that marks the Pro upsell as dismissed for the current user. * * @since 1.20.0 * * @return WP_REST_Response */ function cptui_rest_dismiss_pro_upsell() { $dismissed = get_option( 'cptui-user-dismissed-pro-upsell', [] ); $user_id = get_current_user_id(); $dismissed[ 'user_id_' . $user_id ] = 'true'; update_option( 'cptui-user-dismissed-pro-upsell', $dismissed ); return new WP_REST_Response( [ 'dismissed' => true ], 200 ); } /** * Mark upsell as dismissed for current user. * * @since 1.18.3 */ function cptui_handle_upsell_dismissal() { if ( ! current_user_can( 'manage_options' ) ) { return; } if ( wp_doing_ajax() ) { return; } if ( empty( $_GET['cptui-dismiss-nonce'] ) ) { return; } if ( ! wp_verify_nonce( $_GET['cptui-dismiss-nonce'], 'cptui-dismiss-nonce' ) ) { return; } $dismissed = get_option( 'cptui-user-dismissed-pro-upsell', [] ); $user_id = get_current_user_id(); $dismissed[ 'user_id_' . $user_id ] = 'true'; update_option( 'cptui-user-dismissed-pro-upsell', $dismissed ); } add_action( 'admin_init', 'cptui_handle_upsell_dismissal' ); /** * Clear our upsell option dismissal upon plugin upgrade. * * @since 1.18.3 * * @param $upgrader_object * @param $options */ function cptui_clear_upsell_dismissed_cache( $upgrader_object, $options ) { // If an update has taken place and the updated type is plugins and the plugins element exists if ( 'update' === $options['action'] && 'plugin' === $options['type'] && ! empty( $options['plugins'] ) ) { foreach ( $options['plugins'] as $plugin ) { if ( $plugin === 'custom-post-type-ui/custom-post-type-ui.php' ) { delete_option( 'cptui-user-dismissed-pro-upsell' ); delete_option( 'cptui-user-dismissed-extended-upsell' ); } } } } add_action( 'upgrader_process_complete', 'cptui_clear_upsell_dismissed_cache', 10, 2 );