Changeset 3789030
- Timestamp:
- 11/07/2026 06:11:14 AM (9 hours ago)
- Location:
- klusio-builder-bridge
- Files:
-
- 4 added
- 2 edited
-
tags/1.14.0 (added)
-
tags/1.14.0/klusio-builder-bridge.php (added)
-
tags/1.14.0/readme.txt (added)
-
tags/1.14.0/uninstall.php (added)
-
trunk/klusio-builder-bridge.php (modified) (15 diffs)
-
trunk/readme.txt (modified) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
-
klusio-builder-bridge/trunk/klusio-builder-bridge.php
r3789018 r3789030 3 3 * Plugin Name: Klusio 4 4 * Description: Lets Klusio update texts on your Elementor and Bricks pages after your approval, with a backup of every change and a restore option. Works as a regular plugin and as a must-use plugin. 5 * Version: 1.1 3.05 * Version: 1.14.0 6 6 * Requires at least: 5.6 7 7 * Requires PHP: 7.2 … … 72 72 return; 73 73 } 74 define('KLUSIO_BRIDGE_VERSION', '1.1 3.0');74 define('KLUSIO_BRIDGE_VERSION', '1.14.0'); 75 75 define('KLUSIO_BRIDGE_NAAM', 'klusio-builder-bridge'); 76 76 define('KLUSIO_BRIDGE_BACKUP_PREFIX', '_klusio_el_backup_'); … … 3777 3777 if ($request->get_method() === 'GET') { 3778 3778 $settings = klusio_bridge_workspace_settings(); 3779 return new WP_REST_Response(array('enabled' => !empty($settings['enabled']), 'schema' => 1, ' account' => (int)(isset($settings['account']) ? $settings['account'] : 0)), 200);3779 return new WP_REST_Response(array('enabled' => !empty($settings['enabled']), 'schema' => 1, 'reading' => true, 'account' => (int)(isset($settings['account']) ? $settings['account'] : 0)), 200); 3780 3780 } 3781 3781 if (!klusio_bridge_workspace_lock()) { … … 3813 3813 return new WP_Error('klusio_workspace_disabled', __('Enable the account overview in WordPress first.', 'klusio-builder-bridge'), array('status' => 409)); 3814 3814 } 3815 if (strlen($request->get_body()) > 65536) {3815 if (strlen($request->get_body()) > 262144) { 3816 3816 return new WP_Error('klusio_workspace_size', __('The overview is too large.', 'klusio-builder-bridge'), array('status' => 413)); 3817 3817 } … … 3849 3849 } 3850 3850 $v = array('id' => $item['id'],'kind' => $item['kind']); 3851 foreach (array('title','detail','status','date','page_url','measurement' ) as $key) {3851 foreach (array('title','detail','status','date','page_url','measurement','plan','your_step','verification') as $key) { 3852 3852 if (isset($item[$key]) && (!is_string($item[$key]) || strlen($item[$key]) > 2000)) { 3853 3853 return new WP_Error('klusio_workspace_text', __('Invalid overview text.', 'klusio-builder-bridge'), array('status' => 400)); … … 3858 3858 return new WP_Error('klusio_workspace_status', __('Invalid overview status.', 'klusio-builder-bridge'), array('status' => 400)); 3859 3859 } 3860 if (isset($item['reading'])) { 3861 if ($group !== 'drafts' || $item['kind'] !== 'draft' || !is_string($item['reading']) || strlen($item['reading']) > 18000 || !isset($item['reading_complete']) || !is_bool($item['reading_complete'])) { 3862 return new WP_Error('klusio_workspace_reading', __('Invalid draft preview.', 'klusio-builder-bridge'), array('status' => 400)); 3863 } 3864 $v['reading'] = sanitize_textarea_field($item['reading']); 3865 $v['reading_complete'] = $item['reading_complete']; 3866 } 3867 if (isset($item['local_post_id'])) { 3868 if ($group !== 'page_drafts' || !is_int($item['local_post_id']) || $item['local_post_id'] < 0) { 3869 return new WP_Error('klusio_workspace_local_post', __('Invalid local page reference.', 'klusio-builder-bridge'), array('status' => 400)); 3870 } 3871 $v['local_post_id'] = $item['local_post_id']; 3872 } 3860 3873 $v['verified_at'] = isset($item['verified_at']) && is_int($item['verified_at']) && $item['verified_at'] > 0 && $item['verified_at'] <= time() + 120 ? $item['verified_at'] : 0; 3861 3874 // Page URLs are contextual text only; all clickable account URLs are built locally. … … 3864 3877 } 3865 3878 $clean['unavailable'] = array(); 3866 foreach (array('growth','drafts','page_drafts','preferences','measurements','page_actions' ) as $part) {3879 foreach (array('growth','drafts','page_drafts','preferences','measurements','page_actions','details') as $part) { 3867 3880 if (!empty($d['unavailable']) && is_array($d['unavailable']) && in_array($part, $d['unavailable'], true)) { 3868 3881 $clean['unavailable'][] = $part; … … 3988 4001 if ($hook !== 'toplevel_page_klusio') { return; } 3989 4002 wp_add_inline_style('klusio-workspace', ' 4003 .kbw-prose{white-space:pre-wrap;overflow-wrap:anywhere;max-width:72ch;margin:28px auto;font-size:16px;line-height:1.85;color:#253b53}.kbw-back{display:inline-block;margin:0 0 22px;font-weight:600}.kbw-search{margin:0 0 24px}.kbw-search label{display:block;margin-bottom:8px;font-weight:600}.kbw-search>div{display:flex;gap:10px}.kbw-search input{width:100%;max-width:540px;min-width:0;border:1px solid #d9e3ed;border-radius:8px;min-height:44px}.kbw-page-list>li{display:flex;justify-content:space-between;gap:22px;align-items:center}.kbw-page-list h2{font-size:18px;margin:10px 0 4px}.kbw-page-list .kbw-btn{flex-shrink:0}.kbw-outline{padding-left:0;list-style:none}.kbw-outline span{font-size:12px;font-weight:600;color:#31668f;margin-right:8px}.kbw-reading{border-top:4px solid #2175b8}.kbw :focus-visible{outline:2px solid #2175b8;outline-offset:3px}@media(max-width:782px){.kbw-page-list>li{display:block}.kbw-page-list .kbw-btn{margin-top:10px}.kbw-prose{font-size:15px}.kbw-search>div{flex-wrap:wrap}} 3990 4004 .kbw{max-width:1600px}.kbw-shell{display:grid;grid-template-columns:206px minmax(0,1fr);gap:28px}.kbw-main{min-width:0}.kbw-shell>.kbw-nav{display:flex;flex-direction:column;align-items:stretch;align-self:start;position:sticky;top:52px;border:0;border-radius:12px 12px 12px 0;background:#fff;padding:10px;margin:0;gap:5px;width:auto}.kbw-shell>.kbw-nav a{display:flex;align-items:center;gap:10px;padding:12px;font-size:13px;white-space:normal;border:0}.kbw-shell>.kbw-nav a[aria-current=page]{background:#e9f2fc;box-shadow:none;color:#165a96}.kbw-shell>.kbw-nav .dashicons{font-size:18px;width:18px;height:18px}.kbw-setup{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:24px;padding-left:20px!important}.kbw-setup li{padding:0 0 12px;border:0!important;list-style:decimal}.kbw-fields select{width:100%;max-width:100%;min-height:44px;white-space:normal}.kbw-measurement{padding:12px;background:#edf5fc;border-radius:8px 8px 8px 0}.kbw-card--done li{grid-template-columns:130px 1fr}.kbw-card--done li>a{grid-column:2;grid-row:auto;justify-self:start}.kbw-task-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:20px;margin:24px 0}.kbw-task-grid .kbw-card{background:#f5f8fc}.kbw-task-grid ul{margin:0}.kbw-task-grid li:first-child{padding-top:4px} 3991 4005 @media(max-width:1200px){.kbw-shell{grid-template-columns:180px minmax(0,1fr);gap:18px}.kbw-main .kbw-grid,.kbw-task-grid{grid-template-columns:1fr}.kbw-setup{grid-template-columns:1fr;gap:8px}} … … 4026 4040 return klusio_bridge_workspace_link($section, $item['id'], '', $item['kind']); 4027 4041 } 4042 /** A local detail is always looked up in the consented, tenant-bound snapshot. */ 4043 function klusio_bridge_workspace_detail_link($item) 4044 { 4045 return add_query_arg(array('page' => 'klusio', 'view' => 'detail', 'kind' => $item['kind'], 'item' => $item['id']), admin_url('admin.php')); 4046 } 4047 function klusio_bridge_workspace_find_item($data, $kind, $id) 4048 { 4049 foreach (array('next','doing','done','drafts','page_drafts') as $group) { 4050 foreach (isset($data[$group]) ? $data[$group] : array() as $item) { 4051 if ($item['kind'] === $kind && (int)$item['id'] === $id) { return $item; } 4052 } 4053 } 4054 return null; 4055 } 4056 function klusio_bridge_workspace_local_post($id) 4057 { 4058 if (!is_int($id) || $id < 1) { return null; } 4059 $post = get_post($id); 4060 return $post && in_array($post->post_type, array('page','post'), true) && in_array($post->post_status, array('publish','draft','pending','future','private'), true) && current_user_can('edit_post', $post->ID) ? $post : null; 4061 } 4062 function klusio_bridge_workspace_local_buttons($post) 4063 { 4064 echo '<div class="kbw-buttons"><a class="kbw-btn" href="' . esc_url(get_edit_post_link($post->ID, 'raw')) . '">' . esc_html__('Bewerken in WordPress', 'klusio-builder-bridge') . ' →</a>'; 4065 $preview = $post->post_status === 'publish' ? get_permalink($post) : get_preview_post_link($post); 4066 if ($preview) { 4067 echo '<a class="kbw-btn" href="' . esc_url($preview) . '" target="_blank" rel="noopener noreferrer">' . esc_html__('Bekijk in de websiteopmaak', 'klusio-builder-bridge') . ' ↗</a>'; 4068 } 4069 echo '</div>'; 4070 } 4071 function klusio_bridge_workspace_detail($data) 4072 { 4073 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only navigation in the authorized snapshot. 4074 $kind = isset($_GET['kind']) && is_string($_GET['kind']) ? sanitize_key(wp_unslash($_GET['kind'])) : ''; 4075 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only navigation; an ID never grants access. 4076 $id = isset($_GET['item']) && is_scalar($_GET['item']) ? absint($_GET['item']) : 0; 4077 $item = klusio_bridge_workspace_find_item($data, $kind, $id); 4078 $draft = in_array($kind, array('draft','page_draft'), true); 4079 echo '<a class="kbw-back" href="' . esc_url(admin_url('admin.php?page=klusio&view=' . ($draft ? 'drafts' : 'growth'))) . '">← ' . esc_html($draft ? __('Terug naar concepten', 'klusio-builder-bridge') : __('Terug naar Groeimotor', 'klusio-builder-bridge')) . '</a>'; 4080 if (!$item) { 4081 echo '<section class="kbw-card"><h1>' . esc_html__('Dit item staat niet meer in je overzicht', 'klusio-builder-bridge') . '</h1><p>' . esc_html__('Het kan zijn afgerond of vervangen. Kies een actueel item uit de lijst of bekijk de volledige historie in Klusio.', 'klusio-builder-bridge') . '</p></section>'; 4082 return; 4083 } 4084 echo '<header class="kbw-viewhead"><span class="kbw-kicker">' . esc_html($draft ? __('Concept beoordelen', 'klusio-builder-bridge') : __('Je groeivoorstel', 'klusio-builder-bridge')) . '</span><h1>' . esc_html($item['title']) . '</h1><p>' . esc_html($item['detail']) . '</p></header>'; 4085 if ($kind === 'draft') { 4086 echo '<section class="kbw-card kbw-reading"><div class="kbw-head"><h2>' . esc_html__('Lees je concept', 'klusio-builder-bridge') . '</h2><span class="kbw-status">' . esc_html__('Concept bij laatste ontvangst', 'klusio-builder-bridge') . '</span></div><p class="kbw-small">' . esc_html__('Tekstversie uit je Klusio-account. Afbeeldingen en de definitieve opmaak bekijk je bij de publicatiecontrole.', 'klusio-builder-bridge') . '</p>'; 4087 if (!empty($item['reading'])) { 4088 if (empty($item['reading_complete'])) { 4089 echo '<p class="kbw-note">' . esc_html__('Dit is het eerste deel van een langer concept. Open het volledige artikel voordat je publiceert.', 'klusio-builder-bridge') . '</p>'; 4090 } 4091 // Plain text only. Never run shortcodes, blocks, scripts or external embeds. 4092 echo '<div class="kbw-prose">' . esc_html($item['reading']) . '</div>'; 4093 } else { 4094 echo '<p>' . esc_html__('De tekst is nog niet in dit overzicht beschikbaar. Open het artikel in Klusio; bij de volgende geslaagde synchronisatie verschijnt de tekst hier.', 'klusio-builder-bridge') . '</p>'; 4095 } 4096 echo '</section>'; 4097 } elseif ($kind === 'page_draft') { 4098 $post = !empty($item['local_post_id']) ? klusio_bridge_workspace_local_post($item['local_post_id']) : null; 4099 echo '<section class="kbw-card"><h2>' . esc_html__('Bekijk je volledige pagina', 'klusio-builder-bridge') . '</h2>'; 4100 if ($post && $post->post_type === 'page') { 4101 echo '<p>' . esc_html__('Dit is de huidige versie op deze WordPress-website. Eigen aanpassingen blijven behouden; Klusio controleert opnieuw voordat je vanuit je account publiceert.', 'klusio-builder-bridge') . '</p>'; 4102 klusio_bridge_workspace_local_buttons($post); 4103 } else { 4104 echo '<p>' . esc_html__('Een bewerkbaar WordPress-concept is hier nog niet bevestigd. Bekijk de pagina-opzet en de verbindingscontrole in Klusio.', 'klusio-builder-bridge') . '</p>'; 4105 } 4106 echo '</section>'; 4107 } else { 4108 echo '<div class="kbw-grid">'; 4109 foreach (array('plan' => __('Wat wordt er gedaan?', 'klusio-builder-bridge'), 'your_step' => __('Wat vraagt dit van jou?', 'klusio-builder-bridge'), 'verification' => __('Hoe wordt dit gecontroleerd?', 'klusio-builder-bridge'), 'measurement' => __('Beschikbare resultaatmeting', 'klusio-builder-bridge')) as $key => $label) { 4110 if (empty($item[$key])) { continue; } 4111 echo '<section class="kbw-card"><h2>' . esc_html($label) . '</h2><p>' . esc_html($item[$key]) . '</p></section>'; 4112 } 4113 echo '</div>'; 4114 if (!empty($item['page_url'])) { 4115 echo '<p class="kbw-small">' . esc_html__('Betrokken pagina:', 'klusio-builder-bridge') . ' ' . esc_html($item['page_url']) . '</p>'; 4116 } 4117 } 4118 if (!empty($item['date'])) { echo '<p class="kbw-small">' . esc_html($item['date']) . '</p>'; } 4119 echo '<section class="kbw-note"><strong>' . esc_html($draft ? __('Klaar voor de volgende stap?', 'klusio-builder-bridge') : __('Beslissen met de actuele stand', 'klusio-builder-bridge')) . '</strong><p>' . esc_html__('Je bekijkt de laatst ontvangen versie. Bij bewerken, akkoord of publicatie controleert Klusio opnieuw je rechten, de huidige inhoud en je instellingen.', 'klusio-builder-bridge') . '</p><div class="kbw-buttons"><a class="kbw-btn kbw-primary" target="_blank" rel="noopener noreferrer" href="' . esc_url(klusio_bridge_workspace_item_link($item)) . '">' . esc_html($draft ? __('Bewerken en publicatie controleren', 'klusio-builder-bridge') : __('Actueel voorstel beoordelen', 'klusio-builder-bridge')) . ' ↗</a></div></section>'; 4120 } 4121 /** Extract only known static editorial fields; never run a builder renderer. */ 4122 function klusio_bridge_workspace_builder_text($nodes, $builder, &$budget, $depth = 0) 4123 { 4124 if (!is_array($nodes) || $depth > 24 || $budget <= 0) { return ''; } 4125 $html = ''; 4126 foreach ($nodes as $node) { 4127 if (!is_array($node) || --$budget < 0) { break; } 4128 $s = isset($node['settings']) && is_array($node['settings']) ? $node['settings'] : array(); 4129 $type = $builder === 'Elementor' ? (isset($node['widgetType']) ? $node['widgetType'] : '') : (isset($node['name']) ? $node['name'] : ''); 4130 if (empty($s['__dynamic__']) && empty($s['query']) && empty($node['global'])) { 4131 $text = ''; 4132 if ($type === 'heading') { 4133 $value = $builder === 'Elementor' ? (isset($s['title']) ? $s['title'] : '') : (isset($s['text']) ? $s['text'] : ''); 4134 $tag = $builder === 'Elementor' ? (isset($s['header_size']) ? $s['header_size'] : '') : (isset($s['tag']) ? $s['tag'] : ''); 4135 if (is_string($value) && is_string($tag) && preg_match('/^h[1-6]$/D', $tag)) { 4136 $text = '<' . $tag . '>' . esc_html(wp_strip_all_tags($value)) . '</' . $tag . '>'; 4137 } 4138 } elseif (($builder === 'Elementor' && $type === 'text-editor') || ($builder === 'Bricks' && in_array($type, array('text','text-basic'), true))) { 4139 $value = $builder === 'Elementor' ? (isset($s['editor']) ? $s['editor'] : '') : (isset($s['text']) ? $s['text'] : ''); 4140 if (is_string($value)) { $text = $value; } 4141 } 4142 if ($text !== '' && strlen($text) < 65536 && !preg_match('/\{[^}]+\}|\[[a-zA-Z_][^\]]*\]/', $text)) { $html .= $text; } 4143 } 4144 if ($builder === 'Elementor' && !empty($node['elements'])) { $html .= klusio_bridge_workspace_builder_text($node['elements'], $builder, $budget, $depth + 1); } 4145 if (strlen($html) > 262144) { return ''; } 4146 } 4147 return $html; 4148 } 4149 /** Read stored editorial HTML only. No rendering, shortcodes, network or writes. */ 4150 function klusio_bridge_workspace_page_check($post) 4151 { 4152 $out = array('source' => 'WordPress', 'partial' => false, 'headings' => array(), 'images' => 0, 'alt_review' => 0, 'vague_links' => 0, 'heading_jumps' => 0, 'empty_title' => trim($post->post_title) === ''); 4153 $html = $post->post_content; 4154 if (get_post_meta($post->ID, '_elementor_edit_mode', true) === 'builder' || get_post_meta($post->ID, '_bricks_editor_mode', true) === 'bricks' || get_post_meta($post->ID, '_bricks_page_content_2', true)) { 4155 // post_content may be stale. Read known static fields from builder data; 4156 // templates, loops, image widgets and unknown components remain unassessed. 4157 $out['source'] = get_post_meta($post->ID, '_elementor_edit_mode', true) === 'builder' ? 'Elementor' : 'Bricks'; 4158 $out['partial'] = true; 4159 $raw = get_post_meta($post->ID, $out['source'] === 'Elementor' ? '_elementor_data' : '_bricks_page_content_2', true); 4160 if (is_string($raw)) { $raw = strlen($raw) <= 2097152 ? json_decode($raw, true, 32) : array(); } 4161 $budget = 1000; 4162 $html = klusio_bridge_workspace_builder_text($raw, $out['source'], $budget); 4163 } 4164 if (strlen($html) > 262144 || !class_exists('DOMDocument')) { $out['partial'] = true; return $out; } 4165 $out['partial'] = $out['partial'] || strpos($html, '[') !== false || strpos($html, '<!-- wp:') !== false; 4166 if (trim($html) === '') { return $out; } 4167 $doc = new DOMDocument(); 4168 $before = libxml_use_internal_errors(true); 4169 try { 4170 $doc->loadHTML('<?xml encoding="UTF-8"><html><body>' . $html . '</body></html>', LIBXML_NONET); 4171 $xp = new DOMXPath($doc); 4172 $previous = 0; 4173 foreach ($xp->query('//h1|//h2|//h3|//h4|//h5|//h6') as $node) { 4174 $level = (int)substr($node->nodeName, 1); 4175 if ($previous && $level > $previous + 1) { $out['heading_jumps']++; } 4176 $previous = $level; 4177 if (count($out['headings']) < 40) { $out['headings'][] = array('level' => $level, 'text' => wp_html_excerpt(trim($node->textContent), 300, '…')); } 4178 } 4179 foreach ($xp->query('//img') as $img) { 4180 $out['images']++; 4181 if (trim($img->getAttribute('alt')) === '') { $out['alt_review']++; } 4182 } 4183 foreach ($xp->query('//a[@href]') as $a) { 4184 if (in_array(strtolower(trim($a->textContent)), array('klik hier','lees meer','meer','hier','read more','click here'), true)) { $out['vague_links']++; } 4185 } 4186 } finally { libxml_clear_errors(); libxml_use_internal_errors($before); } 4187 return $out; 4188 } 4189 function klusio_bridge_workspace_pages($data) 4190 { 4191 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only page selection, checked against edit_post below. 4192 $id = isset($_GET['post']) && is_scalar($_GET['post']) ? absint($_GET['post']) : 0; 4193 echo '<header class="kbw-viewhead"><span class="kbw-kicker">' . esc_html__('Je eigen WordPress-inhoud', 'klusio-builder-bridge') . '</span><h1>' . esc_html__('Werk gericht aan je pagina’s', 'klusio-builder-bridge') . '</h1><p>' . esc_html__('Bekijk de opgeslagen inhoud, vind aandachtspunten en open meteen de juiste WordPress-bewerker. Bij gekoppelde pagina’s zie je ook het werk van Klusio.', 'klusio-builder-bridge') . '</p></header>'; 4194 if ($id) { 4195 $post = klusio_bridge_workspace_local_post($id); 4196 echo '<a class="kbw-back" href="' . esc_url(admin_url('admin.php?page=klusio&view=pages')) . '">← ' . esc_html__('Alle pagina’s', 'klusio-builder-bridge') . '</a>'; 4197 if (!$post) { echo '<section class="kbw-card"><p>' . esc_html__('Deze pagina is niet beschikbaar of je hebt geen bewerkrechten.', 'klusio-builder-bridge') . '</p></section>'; return; } 4198 $check = klusio_bridge_workspace_page_check($post); 4199 echo '<section class="kbw-card"><span class="kbw-status">' . esc_html($check['source']) . '</span><h2>' . esc_html(get_the_title($post) ?: __('Zonder titel', 'klusio-builder-bridge')) . '</h2><p class="kbw-small">' . esc_html__('Bron: opgeslagen WordPress-inhoud, gecontroleerd bij het openen.', 'klusio-builder-bridge') . ' ' . esc_html__('Laatst gewijzigd:', 'klusio-builder-bridge') . ' ' . esc_html(get_the_modified_date('j M Y H:i', $post)) . '</p>'; 4200 klusio_bridge_workspace_local_buttons($post); 4201 echo '</section><div class="kbw-grid"><section class="kbw-card"><h2>' . esc_html__('Dit kun je nu nalopen', 'klusio-builder-bridge') . '</h2>'; 4202 $tips = array(); 4203 if ($check['empty_title']) { $tips[] = __('De WordPress-titel is leeg. Geef de pagina een herkenbare naam.', 'klusio-builder-bridge'); } 4204 if ($check['heading_jumps']) { $tips[] = __('De opgeslagen koppen slaan een niveau over. Controleer of de indeling logisch is.', 'klusio-builder-bridge'); } 4205 if ($check['alt_review']) { 4206 /* translators: %d is the number of stored images with empty alternative text. */ 4207 $tips[] = sprintf(__('%d afbeeldingen hebben een lege alt-tekst. Laat die leeg bij puur decoratieve beelden; beschrijf betekenisvolle beelden in de bewerker.', 'klusio-builder-bridge'), $check['alt_review']); 4208 } 4209 if ($check['vague_links']) { 4210 /* translators: %d is the number of links with generic link text. */ 4211 $tips[] = sprintf(__('%d links hebben een algemene tekst zoals “lees meer”. Controleer of duidelijk is waar de link naartoe gaat.', 'klusio-builder-bridge'), $check['vague_links']); 4212 } 4213 if ($tips) { echo '<ul>'; foreach ($tips as $tip) { echo '<li>' . esc_html($tip) . '</li>'; } echo '</ul>'; } 4214 else { echo '<p>' . esc_html($check['partial'] ? __('Voor deze inhoud is een controle in de echte paginaopmaak nodig.', 'klusio-builder-bridge') : __('In deze beperkte controle zijn geen aandachtspunten gevonden.', 'klusio-builder-bridge')) . '</p>'; } 4215 echo '<p class="kbw-small">' . esc_html($check['partial'] ? __('Bekende statische tekstvelden worden gecontroleerd. Templates, afbeeldingswidgets, herbruikbare blokken, shortcodes en dynamische inhoud vallen buiten deze controle. Open ook de websiteopmaak.', 'klusio-builder-bridge') : __('Deze controle betreft titel, kopvolgorde, lege alt-teksten en algemene linkteksten in de opgeslagen inhoud. Je thema, bereikbaarheid van links en vindbaarheid vallen hierbuiten.', 'klusio-builder-bridge')) . '</p></section><section class="kbw-card"><h2>' . esc_html__('Koppen in de opgeslagen tekst', 'klusio-builder-bridge') . '</h2><ol class="kbw-outline">'; 4216 foreach ($check['headings'] as $heading) { echo '<li><span>H' . esc_html((string)$heading['level']) . '</span> ' . esc_html($heading['text']) . '</li>'; } 4217 echo '</ol><p class="kbw-small">' . esc_html__('Een paginatitel uit je thema of builder kan buiten deze tekst staan. Deze lijst is geen beoordeling van de complete openbare pagina.', 'klusio-builder-bridge') . '</p></section></div>'; 4218 $related = array(); 4219 foreach (array('next','doing','done') as $group) { 4220 foreach (isset($data[$group]) ? $data[$group] : array() as $item) { 4221 if (rtrim($item['page_url'], '/') === rtrim((string)get_permalink($post), '/')) { $related[] = $item; } 4222 } 4223 } 4224 echo '<section class="kbw-card"><h2>' . esc_html__('Klusio bij deze pagina', 'klusio-builder-bridge') . '</h2>'; 4225 klusio_bridge_workspace_items($related, __('Er staat voor deze pagina geen werk in het ontvangen overzicht.', 'klusio-builder-bridge')); 4226 echo '<div class="kbw-buttons"><a class="kbw-btn" target="_blank" rel="noopener noreferrer" href="' . esc_url(klusio_bridge_workspace_link($data ? 'improve' : 'setup', 0, klusio_bridge_workspace_page_url($post))) . '">' . esc_html($data ? __('Uitgebreid onderzoeken met Klusio', 'klusio-builder-bridge') : __('Verbind je Klusio-account', 'klusio-builder-bridge')) . ' ↗</a></div></section>'; 4227 return; 4228 } 4229 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only bounded search. 4230 $search = isset($_GET['q']) && is_string($_GET['q']) ? substr(sanitize_text_field(wp_unslash($_GET['q'])), 0, 120) : ''; 4231 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only pagination. 4232 $page = isset($_GET['pagenum']) && is_scalar($_GET['pagenum']) ? max(1, min(1000, absint($_GET['pagenum']))) : 1; 4233 $query = new WP_Query(array('post_type' => array('page','post'), 'post_status' => array('publish','draft','pending','future','private'), 'posts_per_page' => 15, 'paged' => $page, 's' => $search, 'orderby' => 'modified', 'order' => 'DESC', 'perm' => 'editable')); 4234 echo '<form method="get" class="kbw-search"><input type="hidden" name="page" value="klusio"><input type="hidden" name="view" value="pages"><label for="kbw-search">' . esc_html__('Zoek een pagina of bericht', 'klusio-builder-bridge') . '</label><div><input id="kbw-search" type="search" name="q" value="' . esc_attr($search) . '" maxlength="120"><button class="kbw-btn" type="submit">' . esc_html__('Zoeken', 'klusio-builder-bridge') . '</button></div></form><section class="kbw-card"><ul class="kbw-page-list">'; 4235 $shown = 0; 4236 foreach ($query->posts as $post) { 4237 if (!current_user_can('edit_post', $post->ID)) { continue; } 4238 $shown++; 4239 $status = get_post_status_object($post->post_status); 4240 echo '<li><div><span class="kbw-status">' . esc_html($status ? $status->label : '') . '</span><h2>' . esc_html(get_the_title($post) ?: __('Zonder titel', 'klusio-builder-bridge')) . '</h2><p class="kbw-small">' . esc_html(get_the_modified_date('j M Y H:i', $post)) . '</p></div><a class="kbw-btn" href="' . esc_url(add_query_arg(array('page' => 'klusio', 'view' => 'pages', 'post' => $post->ID), admin_url('admin.php'))) . '">' . esc_html__('Bekijk aandachtspunten', 'klusio-builder-bridge') . ' →</a></li>'; 4241 } 4242 echo '</ul>'; 4243 if (!$shown) { echo '<p>' . esc_html__('Geen bewerkbare pagina’s gevonden voor deze selectie.', 'klusio-builder-bridge') . '</p>'; } 4244 echo '<nav class="kbw-buttons" aria-label="' . esc_attr__('Paginering', 'klusio-builder-bridge') . '">'; 4245 foreach (array(-1 => __('Vorige', 'klusio-builder-bridge'), 1 => __('Volgende', 'klusio-builder-bridge')) as $offset => $label) { 4246 $next = $page + $offset; 4247 if ($next < 1 || $next > (int)$query->max_num_pages || $next > 1000) { continue; } 4248 echo '<a class="kbw-btn" href="' . esc_url(add_query_arg(array('page' => 'klusio','view' => 'pages','q' => $search,'pagenum' => $next), admin_url('admin.php'))) . '">' . esc_html($label) . '</a>'; 4249 } 4250 echo '</nav></section>'; 4251 } 4028 4252 function klusio_bridge_workspace_items($items, $empty) 4029 4253 { … … 4048 4272 echo '<p class="kbw-small">' . esc_html($item['date']) . '</p>'; 4049 4273 } 4050 echo '<a href="' . esc_url(klusio_bridge_workspace_ item_link($item)) . '" target="_blank" rel="noopener noreferrer">' . esc_html__('Bekijk in Klusio', 'klusio-builder-bridge') . ' <span aria-hidden="true">↗</span></a></li>';4274 echo '<a href="' . esc_url(klusio_bridge_workspace_detail_link($item)) . '">' . esc_html__( 'Bekijk details', 'klusio-builder-bridge') . ' <span aria-hidden="true">→</span></a></li>'; 4051 4275 }echo '</ul>'; 4052 4276 } … … 4060 4284 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only allowlisted navigation, not a settings mutation. 4061 4285 $view = isset($_GET['view']) && is_string($_GET['view']) ? sanitize_key(wp_unslash($_GET['view'])) : 'overview'; 4062 $tabs = array('overview' => __('Overzicht', 'klusio-builder-bridge'),'growth' => __('Groeimotor', 'klusio-builder-bridge'),'drafts' => __('Concepten', 'klusio-builder-bridge'),' results' => __('Resultaten', 'klusio-builder-bridge'),'settings' => __('Meldingen & rechten', 'klusio-builder-bridge'),'connection' => __('Verbinding', 'klusio-builder-bridge'));4063 if (!isset($tabs[$view]) ) {4286 $tabs = array('overview' => __('Overzicht', 'klusio-builder-bridge'),'growth' => __('Groeimotor', 'klusio-builder-bridge'),'drafts' => __('Concepten', 'klusio-builder-bridge'),'pages' => __('Pagina’s', 'klusio-builder-bridge'),'results' => __('Resultaten', 'klusio-builder-bridge'),'settings' => __('Meldingen & rechten', 'klusio-builder-bridge'),'connection' => __('Verbinding', 'klusio-builder-bridge')); 4287 if (!isset($tabs[$view]) && $view !== 'detail') { 4064 4288 $view = 'overview'; 4065 4289 } 4066 4290 $stale = $d && time() - (int)$d['generated_at'] > 7200; 4291 $active_view = $view; 4292 if ($view === 'detail') { 4293 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Navigation highlight only. 4294 $active_view = isset($_GET['kind']) && is_string($_GET['kind']) && in_array(sanitize_key(wp_unslash($_GET['kind'])), array('draft','page_draft'), true) ? 'drafts' : 'growth'; 4295 } 4067 4296 echo '<div class="wrap kbw"><div class="kbw-top"><div class="kbw-brand">' . wp_kses(klusio_bridge_workspace_logo(), array('img' => array('src' => true, 'alt' => true, 'width' => true, 'height' => true)), array('data')) . '<span class="kbw-brand-note">' . esc_html__('In je WordPress-omgeving', 'klusio-builder-bridge') . '</span></div><a class="kbw-btn" target="_blank" rel="noopener noreferrer" href="' . esc_url($d ? klusio_bridge_workspace_link() : 'https://klusio.nl/account/') . '">' . esc_html__('Open je Klusio-account', 'klusio-builder-bridge') . ' ↗</a></div><div class="kbw-shell"><nav class="kbw-nav" aria-label="' . esc_attr__('Klusio-onderdelen', 'klusio-builder-bridge') . '">'; 4068 $tab_icons = array('overview' => 'dashboard', 'growth' => 'chart-area', 'drafts' => 'media-document', 'connection' => 'admin-links', 'settings' => 'admin-settings','results' => 'chart-line' );4297 $tab_icons = array('overview' => 'dashboard', 'growth' => 'chart-area', 'drafts' => 'media-document', 'connection' => 'admin-links', 'settings' => 'admin-settings','results' => 'chart-line', 'pages' => 'admin-page'); 4069 4298 foreach ($tabs as $key => $label) { 4070 echo '<a href="' . esc_url(add_query_arg(array('page' => 'klusio','view' => $key), admin_url('admin.php'))) . '"' . ($ view === $key ? ' aria-current="page"' : '') . '><span class="dashicons dashicons-' . esc_attr($tab_icons[$key]) . '" aria-hidden="true"></span>' . esc_html($label) . '</a>';4299 echo '<a href="' . esc_url(add_query_arg(array('page' => 'klusio','view' => $key), admin_url('admin.php'))) . '"' . ($active_view === $key ? ' aria-current="page"' : '') . '><span class="dashicons dashicons-' . esc_attr($tab_icons[$key]) . '" aria-hidden="true"></span>' . esc_html($label) . '</a>'; 4071 4300 } 4072 4301 echo '</nav><main class="kbw-main">'; … … 4077 4306 echo '<div class="kbw-note" role="status"><p>' . esc_html__('Een deel van je account kon niet worden opgehaald. De lijsten hieronder zijn daardoor niet volledig. Open Klusio voor de actuele stand.', 'klusio-builder-bridge') . '</p></div>'; 4078 4307 } 4079 if (!$d && $view !== 'connection' && $view !== 'settings') {4308 if (!$d && !in_array($view, array('connection','settings','pages'), true)) { 4080 4309 klusio_bridge_workspace_onboarding(); 4081 4310 echo '</main></div></div>'; 4082 4311 return; 4083 4312 } 4084 if ($view === 'settings') { 4313 if ($view === 'pages') { 4314 klusio_bridge_workspace_pages($d); 4315 } elseif ($view === 'detail') { 4316 klusio_bridge_workspace_detail($d); 4317 } elseif ($view === 'settings') { 4085 4318 klusio_bridge_workspace_preferences_render($d); 4086 4319 } elseif ($view === 'connection') { … … 4089 4322 } 4090 4323 4091 echo '<section class="kbw-card"><h1>' . esc_html__('Je website en Klusio', 'klusio-builder-bridge') . '</h1><p>' . esc_html__('Ontvang hier een compacte kopie van je voorstellen, concepttitelsen voortgang. Klusio ververst dit via je bestaande WordPress-koppeling. Je website plaatst hierdoor niets automatisch.', 'klusio-builder-bridge') . '</p><p><strong>' . esc_html(site_url()) . '</strong></p>';4324 echo '<section class="kbw-card"><h1>' . esc_html__('Je website en Klusio', 'klusio-builder-bridge') . '</h1><p>' . esc_html__('Ontvang hier een kopie van je voorstellen, conceptteksten en voortgang. Klusio ververst dit via je bestaande WordPress-koppeling. Je website plaatst hierdoor niets automatisch.', 'klusio-builder-bridge') . '</p><p><strong>' . esc_html(site_url()) . '</strong></p>'; 4092 4325 if (current_user_can('manage_options')) { 4093 4326 echo '<form class="kbw-fields" method="post" action="' . esc_url(admin_url('admin-post.php')) . '"><input type="hidden" name="action" value="klusio_workspace_settings">'; … … 4139 4372 echo '<div class="kbw-flow"><span>' . esc_html__('Voorstel bekijken', 'klusio-builder-bridge') . '</span><span aria-hidden="true">→</span><span>' . esc_html__('Concept beoordelen', 'klusio-builder-bridge') . '</span><span aria-hidden="true">→</span><span>' . esc_html__('Resultaat volgen', 'klusio-builder-bridge') . '</span></div></div><div class="kbw-focus"><span class="kbw-kicker">' . esc_html__('Jouw volgende stap', 'klusio-builder-bridge') . '</span>'; 4140 4373 if ($next) { 4141 echo '<h2>' . esc_html($next['title']) . '</h2><p>' . esc_html($next['detail']) . '</p><a class="kbw-btn kbw-primary" href="' . esc_url(klusio_bridge_workspace_ item_link($next)) . '" target="_blank" rel="noopener noreferrer">' . esc_html__('Bekijk het voorstel', 'klusio-builder-bridge') . ' ↗</a>';4374 echo '<h2>' . esc_html($next['title']) . '</h2><p>' . esc_html($next['detail']) . '</p><a class="kbw-btn kbw-primary" href="' . esc_url(klusio_bridge_workspace_detail_link($next)) . '">' . esc_html__('Bekijk het voorstel', 'klusio-builder-bridge') . ' →</a>'; 4142 4375 } else { 4143 4376 echo '<h2>' . esc_html($d ? __('Bekijk je Groeimotor', 'klusio-builder-bridge') : __('Verbind je accountoverzicht', 'klusio-builder-bridge')) . '</h2><p>' . esc_html($d ? __('Open Klusio voor alle kansen en de actuele planning.', 'klusio-builder-bridge') : __('Schakel het overzicht in bij Verbinding en werk het daarna bij vanuit je Klusio-account.', 'klusio-builder-bridge')) . '</p><a class="kbw-btn kbw-primary" href="' . esc_url($d ? klusio_bridge_workspace_link('growth') : admin_url('admin.php?page=klusio&view=connection')) . '">' . esc_html($d ? __('Open de Groeimotor', 'klusio-builder-bridge') : __('Verbinding instellen', 'klusio-builder-bridge')) . ' →</a>'; … … 4171 4404 { 4172 4405 if (in_array($post->post_type, array('page','post'), true) && current_user_can('edit_post', $post->ID)) { 4173 $link = klusio_bridge_workspace_ data() ? klusio_bridge_workspace_link('improve', 0, klusio_bridge_workspace_page_url($post)) : admin_url('admin.php?page=klusio');4406 $link = klusio_bridge_workspace_can_view() ? add_query_arg(array('page' => 'klusio', 'view' => 'pages', 'post' => $post->ID), admin_url('admin.php')) : klusio_bridge_workspace_link('improve', 0, klusio_bridge_workspace_page_url($post)); 4174 4407 $actions['klusio_improve'] = '<a href="' . esc_url($link) . '" target="_blank" rel="noopener noreferrer">' . esc_html__('Verbeter met Klusio', 'klusio-builder-bridge') . '</a>'; 4175 4408 } -
klusio-builder-bridge/trunk/readme.txt
r3789018 r3789030 5 5 Tested up to: 7.1 6 6 Requires PHP: 7.2 7 Stable tag: 1.1 3.07 Stable tag: 1.14.0 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 49 49 The Growth screen includes existing internal-link and conversion actions. Their links open the exact existing account workflow, with proposal review, permissions, backups and verification. Results show live-verified work and available Search Console comparisons after 14, 28 and 56 days. Sources, periods and uncertainty remain visible. Missing measurements are never replaced with invented results. 50 50 51 = Read and work without leaving WordPress = 52 53 The Pages section searches your editable WordPress pages and posts. A read-only check covers the stored title, heading order, empty image alternatives and generic link labels. It does not render shortcodes, run dynamic blocks, crawl links or claim to audit the complete public page. Elementor and Bricks content is identified as requiring inspection in the real builder. Native edit and preview links respect your WordPress permissions. 54 55 Draft article text is available after the next successful synchronization with a compatible Klusio account. Text previews are bounded and clearly marked when incomplete; embedded images, scripts and trackers are not loaded. Proposal details show the same execution contract used by the account. Changes, final approvals and publishing continue through the existing checked account workflow. For full page drafts already present in this WordPress, you can open the current local preview and editor directly. 56 51 57 = What the plugin does not do = 52 58 … … 119 125 120 126 == Changelog == 127 128 = 1.14.0 = 129 * Read draft article text and the existing proposal execution plan directly in WordPress. 130 * Search local pages, inspect bounded editorial checks and open native editors and previews without leaving WordPress. 131 * Open mapped full-page drafts in the current website layout, respecting local edit capabilities. 132 * Explicitly distinguish incomplete, dynamic and builder content checks from a full website audit. 133 * Extended previews use the existing opt-in, tenant binding, freshness checks and bounded authenticated synchronization. No new outbound requests or publication permissions. 134 121 135 122 136 = 1.13.0 =
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)