$group['key'], 'post' => 0, 'previewed' => false)); } catch (\Throwable $ignored) { /* Guidance cannot fail a saved schema. */ } break; } } private static function key($name) { global $wpdb; return $wpdb->get_blog_prefix().$name; } public static function goals() { return array('directory'=>__('A directory or catalog','customfields-com'),'events'=>__('Events or locations','customfields-com'),'profiles'=>__('People or profiles','customfields-com'),'submissions'=>__('Collect content from visitors','customfields-com'),'other'=>__('Something else','customfields-com')); } public static function preferences() { $value=get_user_meta(get_current_user_id(),self::key(self::PREF),true); return is_array($value)?array_merge(array('goal'=>'','group'=>'','post'=>0,'previewed'=>false,'skipped'=>false,'measure'=>false),$value):array('goal'=>'','group'=>'','post'=>0,'previewed'=>false,'skipped'=>false,'measure'=>false); } public static function save_preferences($changes) { if(!current_user_can('manage_options')||!is_array($changes)){return new \WP_Error('cf_outcome_access',__('Administrator access is required.','customfields-com'));} $allowed=array('goal','group','post','previewed','skipped','measure'); if(array_diff(array_keys($changes),$allowed)){return new \WP_Error('cf_outcome_input',__('This preference could not be saved.','customfields-com'));} if(array_key_exists('goal',$changes)&&(!is_string($changes['goal'])||($changes['goal']!==''&&!isset(self::goals()[$changes['goal']])))){return new \WP_Error('cf_outcome_goal',__('Choose a supported goal.','customfields-com'));} if(array_key_exists('group',$changes)&&(!is_string($changes['group'])||strlen($changes['group'])>120)){return new \WP_Error('cf_outcome_group',__('Choose a field group.','customfields-com'));} if(array_key_exists('post',$changes)&&(!is_int($changes['post'])||$changes['post']<0)){return new \WP_Error('cf_outcome_post',__('Choose an existing record.','customfields-com'));} foreach(array('previewed','skipped','measure')as$key){if(array_key_exists($key,$changes)&&!is_bool($changes[$key]))return new \WP_Error('cf_outcome_input',__('This preference could not be saved.','customfields-com'));} $value=array_merge(self::preferences(),$changes);$key=self::key(self::PREF);update_user_meta(get_current_user_id(),$key,$value);wp_cache_delete(get_current_user_id(),'user_meta'); if(get_user_meta(get_current_user_id(),$key,true)!==$value){return new \WP_Error('cf_outcome_storage',__('The local preference was not saved. Please retry.','customfields-com'));}return true; } public static function menu(){add_submenu_page('customfields',__('Use your fields','customfields-com'),__('Use your fields','customfields-com'),'manage_options','customfields-start',array(__CLASS__,'page'));} public static function assets(){if(current_user_can('manage_options')&&in_array($_GET['page']??'',array('customfields','customfields-start'),true)){wp_enqueue_style('customfields-outcomes',CUSTOMFIELDS_URL.'assets/outcomes.css',array('customfields-admin'),CUSTOMFIELDS_VERSION);}} // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only routing, filtering or pagination; no mutation is authorized by this query parameter. Object access remains checked by the renderer. private static function url($page='customfields-start'){return admin_url('admin.php?page='.$page);} private static function form($operation){echo '
';wp_nonce_field('customfields_outcomes');echo '';} public static function group($key){foreach(customfields_get_groups()as$group){if(($group['key']??'')===$key)return $group;}return null;} /** Read-only navigation, revalidated against the current schema and object permissions. */ public static function guide_url($key, $id = null) { $args = array('group' => $key); if ($id !== null) { $args['record'] = (int) $id; } return add_query_arg($args, self::url()); } public static function context($group, $id) { if (!current_user_can('manage_options') || !is_array($group) || !is_int($id)) { return null; } $kind = $group['target'] ?? 'post'; if ($kind === 'options') { $config = array_merge(array('slug' => 'site', 'title' => __('Site options', 'customfields-com'), 'capability' => 'manage_options'), $group['options_page'] ?? array()); if ($id !== 0 || !current_user_can($config['capability'])) { return null; } $page = 'customfields-pro-options' . ($config['slug'] === 'site' ? '' : '-' . $config['slug']); return array('kind' => $kind, 'id' => 0, 'title' => $config['title'], 'edit_url' => self::url($page)); } if ($id < 1 || !Editor::can_edit($kind, $id)) { return null; } if ($kind === 'post') { $post = get_post($id); if (!$post || !Editor::matches($group, $post) || !current_user_can('read_post', $id) || !in_array($post->post_status, array('publish', 'draft', 'pending', 'private', 'future'), true)) { return null; } $url = get_edit_post_link($id, ''); $title = $post->post_title ?: __('Untitled', 'customfields-com'); } elseif ($kind === 'user') { $user = get_userdata($id); if (!$user || (is_multisite() && !is_user_member_of_blog($id, get_current_blog_id()))) { return null; } $url = get_edit_user_link($id); $title = $user->display_name ?: __('Unnamed user', 'customfields-com'); } elseif ($kind === 'term') { $term = get_term($id); if (!$term || is_wp_error($term) || !in_array($term->taxonomy, $group['taxonomies'] ?? array(), true)) { return null; } $url = get_edit_term_link($id, $term->taxonomy); $title = $term->name; } else { return null; } return $url ? array('kind' => $kind, 'id' => $id, 'title' => $title, 'edit_url' => $url) : null; } /** Display a contextual return link without disturbing unsaved native-editor work. */ public static function editor_next($group, $kind, $id) { if (!is_admin() || !current_user_can('manage_options') || ($group['target'] ?? 'post') !== $kind || !self::context($group, (int) $id)) { return; } echo '

' . esc_html__('After saving your values:', 'customfields-com') . ' ' . esc_html__('Preview saved values and choose how to display them (opens in a new tab)', 'customfields-com') . '

'; } /** A real, private display preview: only stored, registered and permitted values. */ public static function preview($key, $id) { if (!current_user_can('manage_options') || !is_string($key) || !is_int($id)) { return new \WP_Error('cf_outcome_access', __('Choose a permitted record and field group.', 'customfields-com')); } $group = self::group($key); $context = self::context($group, $id); if (!$context) { return new \WP_Error('cf_outcome_scope', __('This record does not match a field group you can preview.', 'customfields-com')); } $rows = array(); $unsupported = array(); $kind = $context['kind']; $options = $kind === 'options' ? get_option('customfields_pro_values', array()) : array(); foreach ($group['fields'] as $field) { $name = $field['name']; if ($kind !== 'options' && !Editor::can_edit($kind, $id, $name)) { continue; } $exists = $kind === 'options' ? is_array($options) && array_key_exists($name, $options) : metadata_exists($kind, $id, $name); if (!$exists) { continue; } $value = $kind === 'options' ? $options[$name] : get_metadata($kind, $id, $name, true); if (is_wp_error(Fields::storage_error($value, $field))) { $unsupported[] = $field['label']; continue; } if ($kind === 'post') { $html = Display::render(array('name' => $name, 'tag' => 'div'), '', (object) array('context' => array('postId' => $id))); } elseif (in_array($field['type'], array('multiselect', 'checkboxes'), true) && is_array($value)) { $html = esc_html(implode(', ', array_map(static function($choice) use ($field) { return $field['choices'][(string) $choice] ?? (string) $choice; }, $value))); } elseif (is_scalar($value) && !in_array($field['type'], array('image', 'file', 'post', 'user', 'taxonomy'), true)) { $html = $field['type'] === 'wysiwyg' ? wp_kses_post($value) : esc_html(is_bool($value) ? ($value ? '1' : '0') : (string) $value); } else { $html = ''; } if ($html !== '') { $rows[] = array('label' => $field['label'], 'name' => $name, 'type' => $field['type'], 'html' => $html); } elseif ($value !== '' && $value !== array()) { $unsupported[] = $field['label']; } } if (!$rows) { return new \WP_Error('cf_outcome_empty', __('No supported saved values are visible yet. Save a value in the editor, then refresh here. Nested fields and references may need a custom template.', 'customfields-com')); } return array_merge($context, array('post' => $id, 'group' => $group['key'], 'rows' => $rows, 'unsupported' => $unsupported)); } public static function record_event($event){ // Optional counters must never turn an already-completed task into a failed response. try { $events=array('content_preview','csv_analyzed','csv_prepared','csv_applied','health_scanned','health_corrected','baseline_compared','submission_received','submission_reviewed','deployment_applied'); if(!is_string($event)||!in_array($event,$events,true)||!get_current_user_id()||!current_user_can('manage_options')||!self::preferences()['measure'])return; $key=self::key(self::METRICS);$rows=get_user_meta(get_current_user_id(),$key,true);if(!is_array($rows))$rows=array(); $day=gmdate('Y-m-d');if(!isset($rows[$day])||!is_array($rows[$day]))$rows[$day]=array();$rows[$day][$event]=min(100000,(int)($rows[$day][$event]??0)+1);ksort($rows);$rows=array_slice($rows,-90,null,true);update_user_meta(get_current_user_id(),$key,$rows); } catch(\Throwable $ignored) { /* Best-effort, approximate local counts. */ } } public static function measurement_report(){ if(!current_user_can('manage_options'))return new \WP_Error('cf_outcome_access',__('Administrator access is required.','customfields-com')); $rows=get_user_meta(get_current_user_id(),self::key(self::METRICS),true); return array('format'=>'customfields-local-workflow-counts','version'=>1,'generated_utc'=>gmdate('c'),'enabled'=>(bool)self::preferences()['measure'],'counts'=>is_array($rows)?$rows:array(),'scope'=>'This administrator on this site only; approximate local opt-in activity. No site URL, content, account, licence or customer identifier. Not an installation count, paid conversion rate or proof of payment. Concurrent activity can be undercounted.'); } public static function summaries(){ $items=array();foreach(array('csv'=>array('CustomFields\\CsvInspector','customfields-csv-check'),'health'=>array('CustomFields\\ContentHealth','customfields-health'),'maintenance'=>array('CustomFieldsPro\\Maintenance','customfields-maintenance'),'submissions'=>array('CustomFieldsPro\\Submissions','customfields-submissions'))as$key=>$source){ if(!is_callable(array($source[0],'summary_for_current_user')))continue; try{$value=call_user_func(array($source[0],'summary_for_current_user'));if(is_array($value)){$value['url']=self::url($source[1]);$items[$key]=$value;}} catch(\Throwable $error){$items[$key]=array('state'=>'unavailable','url'=>self::url($source[1]));} }return $items; } public static function dashboard(){ if(!current_user_can('manage_options'))return;$p=self::preferences(); echo '

'.esc_html__('YOUR NEXT ACTION','customfields-com').'

'.esc_html($p['previewed']?__('Keep your content working.','customfields-com'):__('See your first saved content.','customfields-com')).'

'.esc_html($p['previewed']?__('Check your content, compare an incoming file, or continue building.','customfields-com'):__('Open a field group, enter a real value, then see how to display it. The guide is optional.','customfields-com')).'

'.esc_html($p['previewed']?__('Open your next step','customfields-com'):($p['skipped']?__('Open the optional guide','customfields-com'):__('Follow the quick guide','customfields-com'))).'
'; $summaries=self::summaries();echo '
'; foreach(array('csv'=>__('Incoming CSV','customfields-com'),'health'=>__('Content checks','customfields-com'),'maintenance'=>__('Maintenance','customfields-com'),'submissions'=>__('Submission reviews','customfields-com'))as$key=>$label){ if(!isset($summaries[$key]))continue;$s=$summaries[$key]; $message=__('Open this workspace for its latest scope and results.','customfields-com'); if(($s['state']??'')==='unavailable')$message=__('The summary is unavailable. Open the workspace to check its status.','customfields-com'); elseif(!empty($s['stale']))$message=__('The saved result needs a fresh check.','customfields-com'); elseif($key==='csv'&&isset($s['changes'],$s['issues']))$message=sprintf(/* translators: 1: Proposed change count; 2: issue count. */ __('%1$d proposed changes · %2$d issues to review','customfields-com'),(int)$s['changes'],(int)$s['issues']); elseif(isset($s['pending']))$message=sprintf(/* translators: %d: Pending entry count. */ __('%d entries awaiting review','customfields-com'),(int)$s['pending']); elseif(isset($s['findings'],$s['checked']))$message=sprintf(/* translators: 1: Finding count; 2: checked record count. */ __('%1$d findings across %2$d checked records in the saved scope','customfields-com'),(int)$s['findings'],(int)$s['checked']); elseif($key==='maintenance'&&isset($s['counts']['plans']))$message=sprintf(/* translators: 1: Saved plan count; 2: scheduled plan count. */ __('%1$d saved plans · %2$d scheduled','customfields-com'),(int)$s['counts']['plans'],(int)($s['counts']['scheduled']??0)); if($key==='health'&&isset($s['coverage'])){$c=$s['coverage'];$message.=' '.sprintf(/* translators: 1: Processed post count; 2: captured post count; 3: unsupported check count. */ __('%1$d of %2$d captured posts processed; %3$d unsupported checks.','customfields-com'),(int)($c['processed']??0),(int)($c['captured']??0),(int)($c['unsupported']??0));if(!empty($c['more']))$message.=' '.__('More candidate posts remain.','customfields-com');} if(!empty($s['updated'])&&is_numeric($s['updated']))$message.=' '.sprintf(/* translators: %s: UTC date and time. */ __('Saved %s UTC; open to recheck current data.','customfields-com'),gmdate('Y-m-d H:i',(int)$s['updated'])); echo ''; }echo '
'; } public static function handle() { if (!current_user_can('manage_options')) { wp_die(esc_html__('Administrator access is required.', 'customfields-com'), '', array('response' => 403)); } // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Exact HTTP method comparison; never displayed or stored. if ('POST' !== (isset($_SERVER['REQUEST_METHOD']) && is_string($_SERVER['REQUEST_METHOD']) ? wp_unslash($_SERVER['REQUEST_METHOD']) : '')) { wp_die(esc_html__('A POST request is required.', 'customfields-com'), '', array('response' => 405)); } $nonce = isset($_POST['_wpnonce']) && is_string($_POST['_wpnonce']) ? sanitize_text_field(wp_unslash($_POST['_wpnonce'])) : ''; if (!wp_verify_nonce($nonce, 'customfields_outcomes')) { wp_die(esc_html__('Your session expired. Reload this page before continuing.', 'customfields-com'), '', array('response' => 403)); } $complete = isset($_POST['complete']) && is_string($_POST['complete']) ? filter_var(wp_unslash($_POST['complete']), FILTER_VALIDATE_REGEXP, array('options' => array('regexp' => '/^1$/D'))) : false; if ('1' !== $complete) { wp_die(esc_html__('The complete form is required. Nothing was changed.', 'customfields-com'), '', array('response' => 400)); } $op = isset($_POST['operation']) && is_string($_POST['operation']) ? sanitize_text_field(wp_unslash($_POST['operation'])) : ''; $result = true; if ('goal' === $op) { $goal = isset($_POST['goal']) && is_string($_POST['goal']) ? sanitize_text_field(wp_unslash($_POST['goal'])) : null; $result = self::save_preferences(array('goal' => $goal, 'skipped' => false)); } elseif ('skip' === $op) { $result = self::save_preferences(array('skipped' => true)); } elseif ('group' === $op) { $key = isset($_POST['group']) && is_string($_POST['group']) ? sanitize_text_field(wp_unslash($_POST['group'])) : ''; $group = self::group($key); $result = $group ? self::save_preferences(array('group' => $key, 'post' => 0, 'previewed' => false)) : new \WP_Error('cf_outcome_group', __('Choose an existing field group.', 'customfields-com')); } elseif ('preview' === $op) { $group = isset($_POST['group']) && is_string($_POST['group']) ? sanitize_text_field(wp_unslash($_POST['group'])) : ''; $post_id = isset($_POST['post']) && is_string($_POST['post']) ? filter_var(wp_unslash($_POST['post']), FILTER_VALIDATE_REGEXP, array('options' => array('regexp' => '/^(0|[1-9][0-9]*)$/D'))) : false; $id = false === $post_id ? false : filter_var($post_id, FILTER_VALIDATE_INT, array('options' => array('min_range' => 0))); $result = false === $id ? new \WP_Error('cf_outcome_scope', __('Choose a permitted record and field group.', 'customfields-com')) : self::preview($group, $id); if (!is_wp_error($result)) { $result = self::save_preferences(array('group' => $group, 'post' => $id, 'previewed' => true, 'skipped' => false)); if (!is_wp_error($result)) { self::record_event('content_preview'); } } } elseif ('measurement' === $op) { $measure = isset($_POST['measure']) && is_string($_POST['measure']) ? filter_var(wp_unslash($_POST['measure']), FILTER_VALIDATE_REGEXP, array('options' => array('regexp' => '/^1$/D'))) : false; $result = self::save_preferences(array('measure' => '1' === $measure)); } elseif ('clear-measurement' === $op) { $key = self::key(self::METRICS); delete_user_meta(get_current_user_id(), $key); wp_cache_delete(get_current_user_id(), 'user_meta'); if (metadata_exists('user', get_current_user_id(), $key)) { $result = new \WP_Error('cf_outcome_storage', __('The local counts could not be removed.', 'customfields-com')); } } elseif ('export' === $op) { $report = self::measurement_report(); nocache_headers(); header('Content-Type: application/json; charset=utf-8'); header('Content-Disposition: attachment; filename="customfields-local-workflows.json"'); echo wp_json_encode($report, JSON_PRETTY_PRINT); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Authorized JSON download, not an HTML response. exit; } else { $result = new \WP_Error('cf_outcome_input', __('Choose an available action.', 'customfields-com')); } if (is_wp_error($result)) { wp_die(esc_html($result->get_error_message()), '', array('response' => 400, 'back_link' => true)); } wp_safe_redirect('skip' === $op ? self::url('customfields') : self::url() . ('preview' === $op ? '&preview=1' : '&saved=1')); exit; } /** GET choices only affect this view. They never mark a task complete or save preferences. */ private static function query($name) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only navigation; context() checks current object access before reading values or building editor links. return isset($_GET[$name]) && is_string($_GET[$name]) ? substr(sanitize_text_field(wp_unslash($_GET[$name])), 0, 120) : ''; } private static function choices($group, $search) { $kind = $group['target'] ?? 'post'; $ids = array(); if ($kind === 'post') { $args = array('post_type' => $group['post_types'] ?? array(), 'post_status' => array('publish', 'draft', 'pending', 'private', 'future'), 'posts_per_page' => 50, 's' => $search, 'orderby' => 'modified', 'order' => 'DESC', 'no_found_rows' => true, 'fields' => 'ids'); if (!empty($group['post_ids'])) { $args['post__in'] = $group['post_ids']; } $query = new \WP_Query($args); $ids = $query->posts; } elseif ($kind === 'user') { $args = array('number' => 50, 'fields' => 'ID', 'orderby' => 'display_name', 'order' => 'ASC', 'blog_id' => get_current_blog_id()); if ($search !== '') { $args['search'] = '*' . $search . '*'; $args['search_columns'] = array('display_name'); } $ids = get_users($args); } elseif ($kind === 'term') { $ids = get_terms(array('taxonomy' => $group['taxonomies'] ?? array(), 'hide_empty' => false, 'number' => 50, 'search' => $search, 'fields' => 'ids')); if (is_wp_error($ids)) { $ids = array(); } } elseif ($kind === 'options') { $ids = array(0); } $choices = array(); foreach ($ids as $id) { $context = self::context($group, (int) $id); if ($context) { $choices[(int) $id] = $context; } } return $choices; } private static function browse_links($group) { $kind = $group['target'] ?? 'post'; if ($kind === 'post') { foreach ($group['post_types'] ?? array() as $name) { $type = get_post_type_object($name); if (!$type || !$type->show_ui || !current_user_can($type->cap->edit_posts)) { continue; } echo '

' . esc_html(sprintf(/* translators: %s: WordPress content type or taxonomy label. */ __('Open %s in WordPress', 'customfields-com'), $type->labels->name)) . '

'; } } elseif ($kind === 'term') { foreach ($group['taxonomies'] ?? array() as $name) { $taxonomy = get_taxonomy($name); if (!$taxonomy || !$taxonomy->show_ui || !current_user_can($taxonomy->cap->manage_terms)) { continue; } echo '

' . esc_html(sprintf(/* translators: %s: WordPress content type or taxonomy label. */ __('Open %s in WordPress', 'customfields-com'), $taxonomy->labels->name)) . '

'; } } elseif ($kind === 'user' && current_user_can('list_users')) { echo '

' . esc_html__('Open users in WordPress', 'customfields-com') . '

'; } } private static function selection_form($group, $id = null) { echo ''; if ($group) { echo ''; } if ($id !== null) { echo ''; } } /** These instructions describe existing renderers; no block, page or template is inserted. */ private static function display_help($group, $context) { $kind = $group['target'] ?? 'post'; $field = null; $simple = array('text', 'textarea', 'number', 'email', 'url', 'select', 'radio', 'checkbox', 'date', 'time', 'datetime', 'color'); foreach ($group['fields'] as $candidate) { if (in_array($candidate['type'], $simple, true)) { $field = $candidate; break; } } if ($kind === 'post') { echo '

' . esc_html__('Show a value on this content', 'customfields-com') . '

  1. ' . esc_html__('Open the record in WordPress and add a Custom field block.', 'customfields-com') . '
  2. ' . esc_html__('Choose your field in the block settings. The block uses the record it is placed on.', 'customfields-com') . '
  3. ' . esc_html__('Use WordPress Preview to check the page before you decide to save or publish it.', 'customfields-com') . '
'; if ($context) { echo '' . esc_html__('Open editor to add a block', 'customfields-com') . ''; } if ($field) { echo '
' . esc_html__('Use a shortcode instead', 'customfields-com') . '

' . esc_html__('For this simple field, add a Shortcode block to the same record and paste:', 'customfields-com') . '

' . esc_html('[customfield name="' . $field['name'] . '"]') . '
'; if ($context) { echo '

' . esc_html__('To reuse that record’s value on a different page:', 'customfields-com') . '

' . esc_html('[customfield name="' . $field['name'] . '" post_id="' . $context['id'] . '"]') . '
'; } echo '

' . esc_html__('Visitors only see source content they can access. A private preview does not make a draft public.', 'customfields-com') . '

'; } echo '
' . esc_html__('Collections, references and nested fields', 'customfields-com') . '

' . esc_html__('A Custom field block displays supported simple values and media. Reference IDs are not automatically turned into names. Repeaters, groups, reusable groups and flexible layouts need a matching theme template; a simple shortcode does not render their rows.', 'customfields-com') . '

' . esc_html__('Build a listing of published records · Pro', 'customfields-com') . '

'; } else { echo '

' . esc_html__('Display through your theme', 'customfields-com') . '

' . esc_html__('The Custom field block and shortcode read post fields. User, term and site-option fields need a theme or integration that reads the matching value. This guide does not insert PHP or change your theme.', 'customfields-com') . '

'; if ($field && $context) { $call = $kind === 'options' ? "customfields_get_option('" . $field['name'] . "')" : "customfields_get_object('" . $field['name'] . "', '" . $kind . "', " . $context['id'] . ')'; echo '
' . esc_html__('Simple-text template example', 'customfields-com') . '

' . esc_html__('For a developer: authorize who may see this value first, then escape it for the output context. This example is only for the selected simple-text field; media and nested values need their own renderer.', 'customfields-com') . '

' . esc_html("") . '
'; } } } private static function member_help($group, $context) { if (($group['target'] ?? 'post') !== 'post') { return; } if (!class_exists('CustomFieldsPro\\Access') || !\CustomFieldsPro\Access::unlocked()) { echo '
' . esc_html__('Member editing · Pro preview', 'customfields-com') . '

' . esc_html__('Let permitted signed-in members edit supported fields from a site page.', 'customfields-com') . '

' . esc_html__('Preview this workflow', 'customfields-com') . '
'; return; } echo '
' . esc_html__('Let signed-in members edit with a Pro form', 'customfields-com') . '

' . esc_html__('Add a Shortcode block to a page. The compatible Pro add-on and a signed-in user with permission are required to edit the chosen record and its fields. It is an editor, without Pro’s private approval queue. Test with the intended user before publishing the form page.', 'customfields-com') . '

'; if ($context) { echo '

' . esc_html__('Edit this existing record:', 'customfields-com') . '

' . esc_html('[customfields_form group="' . $group['key'] . '" post_id="' . $context['id'] . '"]') . '
'; } if (empty($group['post_ids']) && empty($group['page_templates'])) { echo '

' . esc_html__('For members allowed to create content, this form requests a new draft when they submit:', 'customfields-com') . '

' . esc_html('[customfields_form mode="create" group="' . $group['key'] . '" post_type="' . ($group['post_types'][0] ?? 'post') . '"]') . '
'; } else { echo '

' . esc_html__('This group has record or template restrictions. Use an eligible existing record; a new draft may not match those rules.', 'customfields-com') . '

'; } echo '
'; } /** Deliberate local goal selection. Neither browsing nor choosing a goal contacts a service. */ private static function goal_picker($preferences) { $selected = $preferences['goal']; $descriptions = array( 'directory' => __('Create entry details in Free. Pro builds directories with configurable listing layouts and queries.', 'customfields-com'), 'events' => __('Store dates, venue details and links with your content.', 'customfields-com'), 'profiles' => __('Give people or team profiles consistent information.', 'customfields-com'), 'submissions' => __('Build the fields first, then explore a private submission review.', 'customfields-com'), 'other' => __('Choose your own fields and where they appear.', 'customfields-com'), ); echo '
'; if ($selected !== '' || $preferences['skipped']) { echo '
' . esc_html__('Change your building goal', 'customfields-com') . ''; } echo '

' . esc_html__('What are you building?', 'customfields-com') . '

' . esc_html__('Choose a starting point. You can change it later, and nothing is created automatically.', 'customfields-com') . '

'; self::form('goal'); echo '
' . esc_html__('Your building goal', 'customfields-com') . ''; foreach (self::goals() as $key => $label) { echo ''; } echo '
'; self::form('skip'); echo ''; if ($selected !== '' || $preferences['skipped']) { echo '
'; } echo '
'; } public static function page() { if (!current_user_can('manage_options')) { return; } nocache_headers(); $p = self::preferences(); $groups = customfields_get_groups(); $requested = self::query('group'); $selected = $requested !== '' ? self::group($requested) : self::group($p['group']); if (!$selected && $requested === '' && $groups) { $selected = reset($groups); } $search = self::query('content_search'); $choices = $selected ? self::choices($selected, $search) : array(); $record = self::query('record'); $context = null; if ($selected) { if ($record !== '' && ctype_digit($record)) { $context = self::context($selected, (int) $record); } elseif ($record === '') { if ($selected['key'] === $p['group'] && !empty($p['post']) && $search === '') { $context = self::context($selected, (int) $p['post']); } if (!$context && ($selected['target'] ?? '') === 'user' && $search === '') { $context = self::context($selected, get_current_user_id()); } if (!$context && $choices) { $context = reset($choices); } } if ($context) { $choices[$context['id']] = $context; } } echo '

' . esc_html__('Use your fields', 'customfields-com') . '

' . esc_html__('Save a real value, check the result, then choose how to show it. Nothing is created or published by this guide.', 'customfields-com') . '

'; self::goal_picker($p); if (class_exists(Welcome::class)) { Welcome::dashboard(); } if ($requested !== '' && !$selected) { echo ''; } echo '
1

' . esc_html($selected ? $selected['title'] : __('Choose your field group', 'customfields-com')) . '

'; if ($selected) { echo '

' . esc_html__('Your fields are saved and ready to use.', 'customfields-com') . ' ' . esc_html__('Edit fields', 'customfields-com') . '

' . esc_html__('Change field group', 'customfields-com') . ''; } if ($groups) { self::form('group'); echo ''; } else { $first_steps = array('directory' => __('Start with a description, website link and location field for your directory entries.', 'customfields-com'), 'events' => __('Start with an event date, venue and booking link for your event content.', 'customfields-com'), 'profiles' => __('Start with a role, biography and contact link for your profiles.', 'customfields-com'), 'submissions' => __('Start with the fields you want visitors to contribute. You can use them in WordPress now and preview Pro submission reviews when ready.', 'customfields-com')); echo '

' . esc_html($first_steps[$p['goal']] ?? __('Start with a field group and choose where it appears.', 'customfields-com')) . '

' . esc_html__('Create your first field group', 'customfields-com') . ''; } if ($selected) { echo '
'; } echo '
'; if ($selected) { echo '
2

' . esc_html__('Enter real values', 'customfields-com') . '

' . esc_html__('Open the editor, fill in your fields and use its Save or Update button. Return here afterwards to check what was saved.', 'customfields-com') . '

'; if (($selected['target'] ?? 'post') === 'post') { echo '

' . esc_html__('Fields hidden in the WordPress block editor? Expand Meta Boxes at the bottom of the editor to find your field group.', 'customfields-com') . '

'; } if (($selected['target'] ?? 'post') !== 'options') { self::selection_form($selected); echo ''; } if ($context) { echo '

' . esc_html($context['title']) . '

' . esc_html__('Open editor to enter values', 'customfields-com') . ''; } else { echo '

' . esc_html__('No permitted matching record is selected. Find an existing record, or create and save one in WordPress using this group’s location rules.', 'customfields-com') . '

'; self::browse_links($selected); } if (($selected['target'] ?? 'post') !== 'options') { echo '' . esc_html__('Find another record', 'customfields-com') . ''; self::selection_form($selected); echo '

' . esc_html__('Shows permitted matches from up to 50 results. You can also return directly from the link below your fields in an editor.', 'customfields-com') . '

'; } echo '
3

' . esc_html__('Check and display', 'customfields-com') . '

'; if ($context) { $preview = self::preview($selected['key'], $context['id']); if (is_wp_error($preview)) { echo '

' . esc_html($preview->get_error_message()) . '

'; } else { echo '

' . esc_html__('SAVED VALUES · PRIVATE PREVIEW', 'customfields-com') . '

'; foreach ($preview['rows'] as $row) { echo '
' . esc_html($row['label']) . '
' . $row['html'] . '
'; } // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- preview() uses the existing escaped Display renderer or context-escaped scalar values; no submitted HTML is concatenated. echo '
'; if (!$p['previewed'] || $p['group'] !== $selected['key'] || (int) $p['post'] !== $context['id']) { self::form('preview'); echo ''; } else { echo '

' . esc_html__('Saved values checked. Your guide progress is up to date.', 'customfields-com') . '

'; } if ($preview['unsupported']) { echo '

' . esc_html__('Not shown in this preview:', 'customfields-com') . ' ' . esc_html(implode(', ', $preview['unsupported'])) . '

'; } } echo '

' . esc_html__('Refresh saved values', 'customfields-com') . '

' . esc_html__('This preview updates after you save in the editor and refresh here.', 'customfields-com') . '

'; } else { echo '

' . esc_html__('Choose a permitted record to preview its saved values.', 'customfields-com') . '

'; } self::display_help($selected, $context); echo '
'; self::member_help($selected, $context); } echo '
' . esc_html__('Keep your content accurate', 'customfields-com') . '

' . esc_html__('Compare an incoming spreadsheet or check existing content before deciding how to update it.', 'customfields-com') . '

' . esc_html__('Compare a CSV', 'customfields-com') . ' ' . esc_html__('Check content', 'customfields-com') . '
'; echo '
' . esc_html__('Optional local workflow measurement', 'customfields-com') . '

' . esc_html__('Off by default. Count supported completed workflow events by day for this administrator on this site, retaining up to 90 active days. No field values, files, site URL or licence details are recorded. Nothing is sent. These approximate counts cannot measure Free-to-Pro conversion or verify purchases.', 'customfields-com') . '

'; self::form('measurement'); echo '

'; self::form('export'); echo ''; self::form('clear-measurement'); echo '

' . esc_html__('Back to field groups', 'customfields-com') . '

'; self::form('skip'); echo '
'; } }