array(),
'products' => array(),
);
}
}
return $data;
}
/* ---------------------------------------------------------------------------
* Menu, welcome redirect and notice.
* ------------------------------------------------------------------------- */
add_action(
'admin_menu',
function () {
add_theme_page( __( 'Forma Setup', 'forma-3d-toolkit' ), __( 'Forma Setup', 'forma-3d-toolkit' ), 'manage_options', FORMA_3D_TK_SETUP_SLUG, 'forma_3d_tk_setup_page' );
}
);
/**
* Whether the Forma 3D theme (or a child theme of it) is the active theme.
*/
function forma_3d_tk_theme_active() {
return 'forma-3d' === get_template();
}
// Land on the setup screen right after the Forma 3D theme is activated (until the demo has been imported).
add_action(
'admin_init',
function () {
global $pagenow;
if ( 'themes.php' !== $pagenow || empty( $_GET['activated'] ) || wp_doing_ajax() || ! forma_3d_tk_theme_active() || ! current_user_can( 'manage_options' ) || get_option( 'forma_demo_imported' ) ) { // phpcs:ignore WordPress.Security.NonceVerification
return;
}
wp_safe_redirect( admin_url( 'themes.php?page=' . FORMA_3D_TK_SETUP_SLUG ) );
exit;
}
);
/**
* Whether to show the welcome notice: with the Forma 3D theme, until the demo is imported or the notice is dismissed.
*/
function forma_3d_tk_show_notice() {
$screen = get_current_screen();
return forma_3d_tk_theme_active() && current_user_can( 'manage_options' ) && ! get_option( 'forma_demo_imported' ) && ! get_user_meta( get_current_user_id(), 'forma_3d_tk_notice_dismissed', true ) && ! ( $screen && 'appearance_page_' . FORMA_3D_TK_SETUP_SLUG === $screen->id );
}
add_action(
'admin_enqueue_scripts',
function () {
if ( forma_3d_tk_show_notice() ) {
wp_enqueue_script( 'forma-3d-tk-notice', FORMA_3D_TK_URI . 'assets/notice.js', array(), FORMA_3D_TK_VERSION, true );
}
}
);
add_action(
'admin_notices',
function () {
if ( ! forma_3d_tk_show_notice() ) {
return;
}
printf(
'
',
esc_html__( 'Welcome to Forma!', 'forma-3d-toolkit' ),
esc_html__( 'Import the 3D demo store in one click.', 'forma-3d-toolkit' ),
esc_url( admin_url( 'themes.php?page=' . FORMA_3D_TK_SETUP_SLUG ) ),
esc_html__( 'Open Forma Setup', 'forma-3d-toolkit' ),
esc_attr( wp_create_nonce( 'forma-3d-tk-dismiss' ) )
);
}
);
add_action(
'wp_ajax_forma_3d_tk_dismiss_notice',
function () {
check_ajax_referer( 'forma-3d-tk-dismiss', 'nonce' );
update_user_meta( get_current_user_id(), 'forma_3d_tk_notice_dismissed', 1 );
wp_send_json_success();
}
);
/* ---------------------------------------------------------------------------
* Plugin status helpers.
* ------------------------------------------------------------------------- */
/**
* Status of a plugin: 'active', 'installed' or 'missing', plus its basename.
*
* @param string $folder Plugin folder prefix to look for (e.g. "woocommerce", "3d-viewer").
*/
function forma_3d_tk_plugin_status( $folder ) {
if ( ! function_exists( 'get_plugins' ) ) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
foreach ( get_plugins() as $file => $info ) {
if ( 0 === strpos( $file, $folder . '/' ) || 0 === strpos( $file, $folder . '-' ) ) {
return array(
'status' => is_plugin_active( $file ) ? 'active' : 'installed',
'file' => $file,
'name' => $info['Name'],
);
}
}
return array(
'status' => 'missing',
'file' => '',
'name' => '',
);
}
/**
* The required plugins, with how to get each one.
*/
function forma_3d_tk_required_plugins() {
return array(
array(
'key' => 'woocommerce',
'title' => 'WooCommerce',
'desc' => __( 'Products, cart and checkout.', 'forma-3d-toolkit' ),
'install' => admin_url( 'plugin-install.php?s=woocommerce&tab=search&type=term' ),
'state' => forma_3d_tk_plugin_status( 'woocommerce' ),
),
array(
'key' => '3d-viewer',
'title' => '3D Viewer by bPlugins',
'desc' => __( 'Renders the 3D models, dimensions and AR on product pages.', 'forma-3d-toolkit' ),
'install' => admin_url( 'plugin-install.php?s=3d-viewer&tab=search&type=term' ),
'state' => forma_3d_tk_plugin_status( '3d-viewer' ),
),
);
}
/* ---------------------------------------------------------------------------
* Screen.
* ------------------------------------------------------------------------- */
/**
* Render the setup screen.
*/
function forma_3d_tk_setup_page() {
$plugins = forma_3d_tk_required_plugins();
$ready = 'active' === $plugins[0]['state']['status'];
$data = forma_3d_tk_demo_data();
$imported = (bool) get_option( 'forma_demo_imported' );
wp_enqueue_style( 'forma-3d-tk-setup', FORMA_3D_TK_URI . 'assets/setup.css', array(), FORMA_3D_TK_VERSION );
wp_enqueue_script( 'forma-3d-tk-setup', FORMA_3D_TK_URI . 'assets/setup.js', array(), FORMA_3D_TK_VERSION, true );
wp_localize_script(
'forma-3d-tk-setup',
'FORMA_SETUP',
array(
'ajax' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'forma-3d-tk-demo' ),
'products' => wp_list_pluck( $data['products'], 'name' ),
'i18n' => array(
'setup' => __( 'Creating categories and store settings…', 'forma-3d-toolkit' ),
'finish' => __( 'Building the homepage and menus…', 'forma-3d-toolkit' ),
'done' => __( 'Your 3D store is ready!', 'forma-3d-toolkit' ),
'error' => __( 'Something went wrong. Please try again.', 'forma-3d-toolkit' ),
/* translators: 1: product name, 2: product number, 3: product count. */
'product' => __( 'Importing %1$s (%2$d of %3$d)…', 'forma-3d-toolkit' ),
),
)
);
?>
getMessage() );
}
wp_send_json_success();
}
/**
* Copy a bundled demo file into the media library (once) and return its attachment ID.
*
* @param string $rel Path relative to /demo.
* @param int $parent_id Parent post.
* @param string $title Attachment title.
*/
function forma_3d_tk_demo_media( $rel, $parent_id = 0, $title = '' ) {
$existing = get_posts(
array(
'post_type' => 'attachment',
'post_status' => 'inherit',
'meta_key' => '_forma_demo_file', // phpcs:ignore WordPress.DB.SlowDBQuery
'meta_value' => $rel, // phpcs:ignore WordPress.DB.SlowDBQuery
'fields' => 'ids',
'numberposts' => 1,
)
);
if ( $existing ) {
return (int) $existing[0];
}
require_once ABSPATH . 'wp-admin/includes/media.php';
require_once ABSPATH . 'wp-admin/includes/file.php';
require_once ABSPATH . 'wp-admin/includes/image.php';
$src = FORMA_3D_TK_DIR . 'demo/' . $rel;
if ( ! file_exists( $src ) ) {
return 0;
}
$tmp = wp_tempnam( basename( $src ) );
copy( $src, $tmp );
$id = media_handle_sideload(
array(
'name' => basename( $src ),
'tmp_name' => $tmp,
),
$parent_id,
$title
);
if ( is_wp_error( $id ) ) {
wp_delete_file( $tmp );
throw new \RuntimeException( esc_html( basename( $src ) . ': ' . $id->get_error_message() ) );
}
update_post_meta( $id, '_forma_demo_file', $rel );
return (int) $id;
}
/**
* Step 1: categories and store-wide settings.
*/
function forma_3d_tk_demo_setup() {
$data = forma_3d_tk_demo_data();
foreach ( $data['categories'] as $c ) {
$term = get_term_by( 'slug', $c['slug'], 'product_cat' );
$tid = $term ? $term->term_id : 0;
if ( ! $tid ) {
$r = wp_insert_term(
$c['name'],
'product_cat',
array(
'slug' => $c['slug'],
'description' => $c['description'],
)
);
if ( is_wp_error( $r ) ) {
throw new \RuntimeException( esc_html( $r->get_error_message() ) );
}
$tid = $r['term_id'];
}
update_term_meta( $tid, 'order', (int) $c['order'] );
update_term_meta( $tid, 'forma_bg', sanitize_hex_color( $c['bg'] ) );
}
update_option( 'woocommerce_dimension_unit', 'cm' );
update_option( 'woocommerce_weight_unit', 'kg' );
// Store settings that change how the demo looks (set before the photos are imported, so thumbnails use the same crop).
$allowed = array( 'woocommerce_price_num_decimals', 'woocommerce_enable_reviews', 'woocommerce_thumbnail_cropping' );
foreach ( (array) ( $data['options'] ?? array() ) as $name => $value ) {
if ( in_array( $name, $allowed, true ) ) {
update_option( $name, sanitize_text_field( (string) $value ) );
}
}
// The footer shows the tagline; only fill it in when the site has none.
if ( ! empty( $data['site']['tagline'] ) && '' === trim( (string) get_option( 'blogdescription' ) ) ) {
update_option( 'blogdescription', sanitize_text_field( $data['site']['tagline'] ) );
}
// 3D Viewer: switch on its WooCommerce integration and a gentle auto-rotate.
if ( class_exists( '\\BP3D\\Woocommerce\\Product' ) ) {
$settings = get_option( '_bp3d_settings_', array() );
$settings = is_array( $settings ) ? $settings : array();
update_option(
'_bp3d_settings_',
array_merge(
$settings,
array(
'3d_woo_switcher' => '1',
'bp_camera_control' => '1',
'bp_3d_zooming' => '1',
'bp_3d_rotate' => '1',
'3d_rotate_speed' => 14,
'3d_rotate_delay' => 2500,
'bp_3d_fullscreen' => '1',
'bp_3d_progressbar' => '1',
)
)
);
}
}
/**
* Step 2..n: one product with its 3D model and photos.
*
* @param int $index Index into demo.json products.
*/
function forma_3d_tk_demo_product( $index ) {
$data = forma_3d_tk_demo_data();
if ( empty( $data['products'][ $index ] ) ) {
throw new \RuntimeException( 'No such demo product.' );
}
$it = $data['products'][ $index ];
$sku = 'FRM-' . strtoupper( substr( md5( $it['slug'] ), 0, 6 ) );
$pid = wc_get_product_id_by_sku( $sku );
$product = $pid ? wc_get_product( $pid ) : new WC_Product_Simple();
$product->set_name( $it['name'] );
$product->set_slug( $it['slug'] );
$product->set_status( 'publish' );
if ( ! $pid ) {
$product->set_sku( $sku );
}
$product->set_regular_price( (string) $it['regular_price'] );
$product->set_sale_price( (string) $it['sale_price'] );
$product->set_short_description( $it['short_description'] );
$product->set_description( $it['description'] );
$term = get_term_by( 'slug', $it['category'], 'product_cat' );
if ( $term ) {
$product->set_category_ids( array( $term->term_id ) );
}
$product->set_featured( ! empty( $it['featured'] ) );
$product->set_menu_order( (int) $it['menu_order'] );
// 3D Viewer convention: width = side to side, length = depth.
$product->set_width( (string) $it['width'] );
$product->set_length( (string) $it['depth'] );
$product->set_height( (string) $it['height'] );
$product->set_weight( (string) $it['weight'] );
$product->set_stock_status( 'instock' );
$product->set_reviews_allowed( false );
$pid = $product->save();
$model = forma_3d_tk_demo_media( 'models/' . $it['model'] . '.glb', $pid, $it['name'] . ' 3D model' );
$images = array();
foreach ( array( 0, 1, 2 ) as $k ) {
$img = forma_3d_tk_demo_media( 'images/' . $it['model'] . '-' . $k . '.webp', $pid, $it['name'] );
update_post_meta( $img, '_wp_attachment_image_alt', $it['name'] );
$images[] = $img;
}
$product->set_image_id( $images[0] );
$product->set_gallery_image_ids( array_slice( $images, 1 ) );
$product->update_meta_data( '_forma_model_id', $model );
$product->update_meta_data( '_forma_bg', $it['bg'] );
$product->update_meta_data( '_forma_materials', $it['materials'] );
$product->update_meta_data( '_forma_designer', $it['designer'] );
$product->update_meta_data( '_forma_finishes', $it['finishes'] ? wp_json_encode( $it['finishes'] ) : '' );
$product->save();
// Hand the model to the 3D Viewer plugin (read by it even before it is activated).
$meta = get_post_meta( $pid, '_bp3d_product_', true );
$meta = is_array( $meta ) ? $meta : array();
update_post_meta(
$pid,
'_bp3d_product_',
array_merge(
$meta,
array(
'bp3d_models' => array(
array(
'model_src' => wp_get_attachment_url( $model ),
'poster_src' => wp_get_attachment_image_url( $images[0], 'woocommerce_single' ),
'exposure' => '1',
'enable_ar' => '1',
'ar_placement' => 'floor',
'ar_mode' => 'webxr',
'hotspots' => array(),
'initial_view' => '[]',
),
),
'viewer_position' => 'replace',
'currentViewer' => 'modelViewer',
'bp_model_template' => 'none',
'replace_model_with_thumbnail' => '0',
'show_model_instead_thumbnail' => '0',
'show_thumbs' => '0',
'show_arrows' => '0',
'bp_3d_zooming' => '1',
'bp_3d_dimensions_mode' => 'button',
'bp_3d_dimensions_source' => 'product',
'bp_3d_dimension_color' => '#171513',
'bp_3d_height' => array(
'height' => '640',
'unit' => 'px',
),
'bp_model_bg' => 'transparent',
)
)
);
}
/**
* Final step: category images, front page, permalinks.
*/
function forma_3d_tk_demo_finish() {
$data = forma_3d_tk_demo_data();
foreach ( $data['categories'] as $c ) {
$term = get_term_by( 'slug', $c['slug'], 'product_cat' );
if ( $term && $c['image'] ) {
$img = forma_3d_tk_demo_media( 'images/' . $c['image'], 0, $c['name'] );
update_term_meta( $term->term_id, 'thumbnail_id', $img );
}
}
// The demo's own homepage (its block settings and text), or every Forma block with its defaults.
$markup = ! empty( $data['home'] ) ? (string) $data['home'] : forma_3d_tk_home_markup();
$home = get_page_by_path( 'home' );
$home_id = $home ? $home->ID : wp_insert_post(
array(
'post_type' => 'page',
'post_title' => 'Home',
'post_name' => 'home',
'post_status' => 'publish',
'post_content' => wp_slash( $markup ), // Block attributes contain < escapes; wp_insert_post() unslashes.
)
);
// An existing, empty Home page gets the Forma blocks so it can be edited in the block editor.
if ( $home && '' === trim( $home->post_content ) ) {
wp_update_post(
array(
'ID' => $home->ID,
'post_content' => wp_slash( $markup ),
)
);
}
update_option( 'show_on_front', 'page' );
update_option( 'page_on_front', (int) $home_id );
// The demo footer has no widget column. On the first import, park the widgets WordPress puts
// there on a new site under Appearance > Widgets > Inactive widgets (nothing is deleted).
$sidebars = get_option( 'sidebars_widgets', array() );
if ( ! get_option( 'forma_demo_imported' ) && ! empty( $sidebars['footer-1'] ) && is_array( $sidebars['footer-1'] ) ) {
$sidebars['wp_inactive_widgets'] = array_merge( (array) ( $sidebars['wp_inactive_widgets'] ?? array() ), $sidebars['footer-1'] );
$sidebars['footer-1'] = array();
update_option( 'sidebars_widgets', $sidebars );
}
if ( ! get_option( 'permalink_structure' ) ) {
update_option( 'permalink_structure', '/%postname%/' );
}
flush_rewrite_rules();
update_option( 'forma_demo_imported', time() );
}