PluginProbe
Post Snippets – Custom WordPress Code Snippets Customizer / trunk
Post Snippets – Custom WordPress Code Snippets Customizer vtrunk
4.2.4 4.2.3 4.2.2 4.2.1 4.2.0 4.1.1 1.7.2 1.7.3 1.8 1.8.1 1.8.2 1.8.3 1.8.5 1.8.6 1.8.7 1.8.8 1.8.9 1.8.9.1 1.8.9.2 1.9 1.9.1 1.9.2 1.9.3 1.9.4 1.9.5 All 115 releases
post-snippets / src / PostSnippets / Admin.php

Admin.php in Post Snippets – Custom WordPress Code Snippets Customizer trunk, at src/PostSnippets/Admin.php

1,208 lines 44.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace PostSnippets;
3
4 if ( ! defined( 'ABSPATH' ) ) exit;
5
6 /**
7 * Post Snippets Settings.
8 *
9 * Class that renders out the HTML for the settings screen and contains helpful
10 * methods to simply the maintainance of the admin screen.
11 *
12 */
13 class Admin
14 {
15 /**
16 * Plugin settings.
17 *
18 * @var array
19 */
20 protected $settings;
21
22 public $_admin_page = 'post-snippets';
23 public $_admin_upgrade_page = 'https://postsnippets.com/pricing/';
24
25 // const GROUP_CLASS = 'PostSnippets\Group';
26
27 /**
28 * Defines hooks and filters for admin page.
29 */
30 public function __construct()
31 {
32 add_action('admin_menu', array(&$this, 'menu'));
33 add_action('admin_init', array(&$this, 'init'));
34 add_action('current_screen', array(&$this, 'addHeaderXss'));
35 add_filter('plugin_action_links_' . plugin_basename(PS_MAIN_FILE_PATH), array(
36 $this,
37 'actionLinks'
38 ));
39
40 // Newsletter sign-up admin notice
41 add_action('admin_notices', array($this, 'admin_notice_newsletter'));
42
43 // Get started admin notice
44 add_action('admin_notices', array($this, 'admin_notice_get_started'));
45
46 // add_action( 'wp_ajax_sync_up', array( $this, 'sync_up' ) );
47 // add_action( 'wp_ajax_sync_down', array( $this, 'sync_down' ) );
48
49 add_action('init', array($this, 'load_block'));
50
51 add_filter('set-screen-option', array($this, 'all_snippets_set_option'), 10, 3);
52
53 add_action('wp_footer', array('PostSnippets\PSallSnippets', 'run_frontend_footer_js_css'));
54
55 add_action('wp_head', array('PostSnippets\PSallSnippets', 'run_frontend_header_js'));
56
57 add_action('admin_print_scripts', function () {
58
59 PSallSnippets::run_admin_js_css();
60
61 });
62
63 add_action('admin_footer', array($this, 'chatbot_ai_modal') );
64
65 }
66
67 /**
68 * Render the chatbot modal HTML
69 */
70 public function chatbot_ai_modal() {
71 if ( ! isset ( $_GET['page'] ) || ! in_array( $_GET['page'], ['post-snippets-edit','post-snippets-edit-css','post-snippets-edit-js'] ) ) {
72 return;
73 }
74
75 ?>
76 <button id="chatbot-toggle" type="button" aria-label="Open AI Assistant">
77 <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
78 <path d="M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 13.5997 2.37562 15.1116 3.04346 16.4525L2.06947 20.4839C1.91746 21.1137 2.50286 21.6811 3.1255 21.5032L7.04504 20.3833C8.5447 21.4194 10.2032 22 12 22Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
79 <path d="M8 12H8.01" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
80 <path d="M12 12H12.01" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
81 <path d="M16 12H16.01" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
82 </svg>
83 </button>
84
85 <div id="chatbot-wrapper" style="display: none;">
86 <div id="chatbot-header">
87 <div class="header-main">
88 <h3><span class="status-dot"></span> AI Assistant</h3>
89 </div>
90 <div class="header-actions">
91 <button id="chatbot-close" type="button" aria-label="Close Assistant">
92 <svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
93 <path d="M13 1L1 13M1 1L13 13" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
94 </svg>
95 </button>
96 </div>
97 </div>
98 <div id="chatbot-messages"></div>
99 <div id="chatbot-input-container">
100 <textarea id="chatbot-input" rows="1" placeholder="Describe the code you need..."></textarea>
101 <button id="chatbot-send" type="button" aria-label="Send message">
102 <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
103 <path d="M22 2L11 13M22 2L15 22L11 13M11 13L2 9L22 2" />
104 </svg>
105 </button>
106 </div>
107 </div>
108 <?php
109 }
110
111 public function load_block()
112 {
113
114 wp_register_script(
115 'post-snippets-block',
116 PS_URL . 'src/blocks/blocks.build.js',
117 array('wp-blocks', 'wp-editor', 'wp-element')
118 );
119
120 register_block_type('greentreelabs/post-snippets-block', array(
121 'editor_script' => 'post-snippets-block',
122 // 'render_callback' => array($this, 'render_block_content'),
123 'render_callback' => function ($attributes) {
124 return PSallSnippets::render_block_content($attributes);
125 },
126 ));
127
128 wp_localize_script('post-snippets-block', 'all_snippets', array(
129
130 'all_snippets' => PSallSnippets::get_all_snippets_gutenberg()
131
132 ));
133
134 }
135
136 // -------------------------------------------------------------------------
137 // Setup
138 // -------------------------------------------------------------------------
139
140 /**
141 * Register the administration page.
142 *
143 * @return void
144 */
145 public function menu()
146 {
147 $capability = $this->get_allowed_capability();
148
149 if ($capability == 'edit_posts') {
150 $allowed = true;
151 }
152
153 if (current_user_can('manage_options') or isset($allowed)) {
154
155 $optionPage = add_menu_page(
156 __('Post Snippets', 'post-snippets'),
157 __('Post Snippets', 'post-snippets'),
158 $capability,
159 'post-snippets',
160 array(
161 $this,
162 'allSnippetsPage',
163 ),
164 PS_URL . 'assets/icon.svg'
165 );
166
167 add_action("load-$optionPage", array($this, 'add_screen_options'));
168
169
170 $allSnippets = add_submenu_page(
171 'post-snippets',
172 __('All Snippets', 'post-snippets'),
173 __('All Snippets', 'post-snippets'),
174 $capability,
175 'post-snippets',
176 array(
177 $this,
178 'allSnippetsPage',
179 )
180 );
181
182 $editSnippets = add_submenu_page(
183 'post-snippets',
184 __('Add Custom Code', 'post-snippets'),
185 __('Add Custom Code', 'post-snippets'),
186 $capability,
187 'post-snippets-edit',
188 array(
189 $this,
190 'editPage',
191 )
192 );
193
194 add_action('load-' . $editSnippets, array(\PostSnippets::EDIT_CLASS, 'page_actions'), 9);
195 add_action('admin_footer-' . $editSnippets, array(\PostSnippets::EDIT_CLASS, 'footer_scripts'));
196 add_filter('admin_title', array(\PostSnippets::EDIT_CLASS, 'change_edit_snippet_page_title'), 10, 2);
197
198 /**Add CSS */
199 $editCSS = add_submenu_page(
200 'post-snippets',
201 __('Add Custom CSS', 'post-snippets'),
202 __('Add Custom CSS', 'post-snippets'),
203 $capability,
204 'post-snippets-edit-css',
205 array(
206 $this,
207 'editPage',
208 )
209 );
210
211 add_action('load-' . $editCSS, array(\PostSnippets::EDIT_CLASS, 'page_actions'), 9);
212 add_action('admin_footer-' . $editCSS, array(\PostSnippets::EDIT_CLASS, 'footer_scripts'));
213 add_filter('admin_title', array(\PostSnippets::EDIT_CLASS, 'change_edit_snippet_page_title'), 10, 2);
214
215 /**Add JS */
216 $editJS = add_submenu_page(
217 'post-snippets',
218 __('Add Custom JS', 'post-snippets'),
219 __('Add Custom JS', 'post-snippets'),
220 $capability,
221 'post-snippets-edit-js',
222 array(
223 $this,
224 'editPage',
225 )
226 );
227
228 add_action('load-' . $editJS, array(\PostSnippets::EDIT_CLASS, 'page_actions'), 9);
229 add_action('admin_footer-' . $editJS, array(\PostSnippets::EDIT_CLASS, 'footer_scripts'));
230 add_filter('admin_title', array(\PostSnippets::EDIT_CLASS, 'change_edit_snippet_page_title'), 10, 2);
231
232
233
234 $snippetsOptions = add_submenu_page(
235 'post-snippets',
236 __('Options', 'post-snippets'),
237 __('Options', 'post-snippets'),
238 $capability,
239 'post-snippets-options',
240 array(
241 $this,
242 // 'optionsPage',
243 'tabOptions'
244 )
245 );
246
247
248
249 $snippetsImpExp = add_submenu_page(
250 'post-snippets',
251 __('Import/Export', 'post-snippets'),
252 __('Import/Export', 'post-snippets'),
253 $capability,
254 'post-snippets-imp-exp',
255 array(
256 $this,
257 'tabTools',
258 )
259 );
260
261
262 $newsPage = add_submenu_page(
263 'post-snippets',
264 __('News', 'post-snippets'),
265 __('News', 'post-snippets'),
266 $capability,
267 'post-snippets-news',
268 array(
269 $this,
270 'newsPage',
271 )
272 );
273
274 $chatbot_ai = add_submenu_page( 'post-snippets',
275 __( 'AI Chatbot', 'post-snippets' ),
276 __( 'AI Chatbot', 'post-snippets' ), $capability, 'post-snippets-chatbot', array(
277 $this,
278 'chatbot_ai',
279 ) );
280
281 add_submenu_page($this->_admin_page, __('👉 Get Pro Bundle', 'post-snippets'), sprintf('<span style="color:#adff2f!important;">👉 %1$s <b>%2$s</b>&nbsp;&nbsp;➤</span>', __('Get', 'post-snippets'), __('Pro Bundle', 'post-snippets')), $capability, $this->_admin_upgrade_page, '');
282
283 /*$optionPage = add_submenu_page(
284 __( 'Post Snippets', 'post-snippets'),
285 __( 'Post Snippets', 'post-snippets'),
286 $capability,
287 'post-snippets',
288 array ( &$this, 'optionsPage' )
289 );*/
290 new Help($editSnippets);
291 } else {
292 add_menu_page(
293 __('Post Snippets', 'post-snippets'),
294 __('Post Snippets', 'post-snippets'),
295 'manage_options',
296 'post-snippets',
297 array(
298 $this,
299 'overviewPage',
300 ),
301 PS_URL . 'assets/icon.svg'
302 );
303 }
304 }
305
306 function add_screen_options()
307 {
308 $option = 'per_page';
309 $args = array(
310 'label' => __('Number of items per page', 'post-snippets'),
311 'default' => 10,
312 'option' => 'psp_snippets_per_page'
313 );
314 add_screen_option($option, $args);
315 }
316
317 function all_snippets_set_option($status, $option, $value)
318 {
319 return $value;
320
321 }
322
323 /**
324 * Initialize assets for the administration page.
325 *
326 * @return void
327 */
328 public function init()
329 {
330 wp_register_script('post-snippets', plugins_url('/assets/post-snippets.js', \PostSnippets::FILE), array('jquery', 'wp-i18n'), PS_VERSION, true);
331 if (postsnippets_fs()->can_use_premium_code__premium_only()) {
332 wp_register_script('post-snippets-pro', plugins_url('/assets-pro/post-snippets-pro.js', \PostSnippets::FILE), array(
333 'jquery',
334 'post-snippets'
335 ), PS_VERSION, true);
336 // wp_register_script( 'quill', plugins_url( '/assets-pro/quill.min.js', \PostSnippets::FILE ), array(
337 // 'post-snippets-pro'
338 // ), PS_VERSION, true );
339 }
340 wp_set_script_translations('post-snippets', 'post-snippets'); /**To use wp i18n in JS */
341 $this->scripts();
342 $this->registerSettings();
343 }
344
345 /**
346 * Enqueue scripts to be loaded.
347 *
348 * @return void
349 */
350 public function scripts()
351 {
352 // Localize the strings in the script
353 $translation_array = array(
354 'invalid_shortcode' => __('Invalid shortcode name', 'post-snippets'),
355 'save_title_nonce' => wp_create_nonce('ps-save-title-nonce'),
356 'update_snippet_nonce' => wp_create_nonce('ps-update-snippet-nonce'),
357 );
358 wp_localize_script('post-snippets', 'post_snippets', $translation_array);
359
360 // Add CSS for Pro features page
361 $features_style_url = plugins_url('/assets/features.css', \PostSnippets::FILE);
362 wp_register_style('post-snippets-icons', $features_style_url, array(), PS_VERSION);
363 wp_enqueue_style('post-snippets-icons');
364
365 // Add CSS for icons
366 $features_style_url = plugins_url('/assets/icons.css', \PostSnippets::FILE);
367 wp_register_style('post-snippets-features', $features_style_url, array(), PS_VERSION);
368 wp_enqueue_style('post-snippets-features');
369
370 // Add CSS for newsletter opt-in
371 $features_style_url = plugins_url('/assets/newsletter.css', \PostSnippets::FILE);
372 wp_register_style('post-snippets-newsletter', $features_style_url, array(), PS_VERSION);
373 wp_enqueue_style('post-snippets-newsletter');
374
375 // wp_enqueue_script( 'jquery-ui-sortable' );
376 wp_enqueue_script('jquery-ui-dialog');
377 wp_enqueue_script('underscore');
378
379
380
381 wp_enqueue_script('post-snippets');
382 }
383
384
385 /**
386 * Add X-XSS-Protection header.
387 *
388 * Newer versions of Chrome does not allow form tags to be submitted in the
389 * forms. This header disables that functionlity on the Post Snippets admin
390 * screen only.
391 */
392 public function addHeaderXss($current_screen)
393 {
394 if ($current_screen->base == 'settings_page_post-snippets/post-snippets') {
395 header('X-XSS-Protection: 0');
396 }
397 }
398
399 /**
400 * Quick link to the Post Snippets Settings page from the Plugins page.
401 *
402 * @param array $links Array of all plugin links
403 *
404 * @return array $links Array with all the plugin's action links
405 */
406 public function actionLinks($links)
407 {
408
409 $links[] = '<a href="' . PS_MAIN_PAGE_URL . '">' . __('Settings', 'post-snippets') . '</a>';
410
411 return $links;
412 }
413
414
415 public static function pspro_object_to_array($obj)
416 {
417 //only process if it's an object or array being passed to the function
418 if (is_object($obj) || is_array($obj)) {
419 $ret = (array) $obj;
420 foreach ($ret as &$item) {
421 //recursively process EACH element regardless of type
422 $item = self::pspro_object_to_array($item);
423 }
424 return $ret;
425 }
426 //otherwise (i.e. for scalar values) return without modification
427 else {
428 return $obj;
429 }
430 }
431
432
433
434 /**
435 * Update User Option.
436 *
437 * Sets the per user option for the read-only overview page.
438 *
439 * @since Post Snippets 1.9.7
440 */
441 private function setUserOptions()
442 {
443 if (
444 isset($_POST['post_snippets_user_nonce'])
445 && wp_verify_nonce($_POST['post_snippets_user_nonce'], 'post_snippets_user_options')
446 ) {
447 $id = get_current_user_id();
448 $render = isset($_POST['render']) ? true : false;
449 update_user_meta($id, \PostSnippets::USER_META_KEY, $render);
450 }
451 }
452
453 /**
454 * Get User Option.
455 *
456 * Gets the per user option for the read-only overview page.
457 *
458 * @since Post Snippets 1.9.7
459 * @return boolean If overview should be rendered on output or not
460 */
461 private function getUserOptions()
462 {
463 $id = get_current_user_id();
464 $options = get_user_meta($id, \PostSnippets::USER_META_KEY, true);
465
466 return $options;
467 }
468
469
470 // -------------------------------------------------------------------------
471 // HTML generation for option pages
472 // -------------------------------------------------------------------------
473
474 /**
475 * Display Flashing Message.
476 *
477 * @param string $message Message to display to the user.
478 */
479 private function message($message)
480 {
481 if ($message) {
482 echo "<div class='updated'><p><strong>{$message}</strong></p></div>";
483 }
484 }
485
486 public function newsPage()
487 {
488 $plugins = array(
489 array(
490 "name" => "myCred",
491 "description" => "myCred makes it simple to create a loyalty program or gamify your website so that you can increase the average customer value with less marketing effort.",
492 "image" => "myCred.png",
493 "url" => "https://wordpress.org/plugins/mycred",
494 ),
495 array(
496 "name" => "WP Contact Slider",
497 "description" => "WP contact slider is simple contact slider to display contactform7, Gravity forms, Wp Forms, Caldera forms, Constant Contact Forms or display random text or HTML.",
498 "image" => "ContactSlider.png",
499 "url" => "https://wordpress.org/plugins/wp-contact-slider/",
500 ),
501 array(
502 "name" => "Wholesale For WooCommerce",
503 "description" => "Sign up wholesale customers and display wholesale prices based on multiple wholesale user roles on your existing WooCommerce store",
504 "image" => "WholesaleForWoocommerce.png",
505 "url" => "https://woocommerce.com/products/wholesale-for-woocommerce/",
506 ),
507 array(
508 "name" => "WooCommerce Product Disclaimer",
509 "description" => "Woocommerce extension where you can set some products to accept terms and conditions before adding product to cart. ",
510 "image" => "ProductDisclaimer.png",
511 "url" => "https://wordpress.org/plugins/woo-product-disclaimer/",
512 )
513 );
514
515 include PS_PATH . "/views/admin_news.php";
516 }
517
518 public function chatbot_ai() {
519 if (isset($_POST['ps_openai_api_key'])) {
520 check_admin_referer('ps_settings_group-options');
521 $api_key = sanitize_text_field($_POST['ps_openai_api_key']);
522 update_option('ps_openai_api_key', $api_key);
523 echo '<div class="updated"><p>Settings saved successfully.</p></div>';
524 }
525
526 $api_key = get_option('ps_openai_api_key');
527 ?>
528 <div class="wrap ps-ai-settings-wrap">
529 <div class="ps-ai-header-dashboard">
530 <div class="header-content">
531 <h1>AI Chatbot Mastery</h1>
532 <p class="subtitle">Connect and configure your advanced AI assistant for intelligent code generation.</p>
533 </div>
534 <div class="status-badge <?php echo $api_key ? 'active' : 'inactive'; ?>">
535 <span class="dot"></span> <?php echo $api_key ? 'Connected' : 'Disconnected'; ?>
536 </div>
537 </div>
538
539 <div class="ps-ai-dashboard-grid">
540 <div class="ps-ai-card main-settings">
541 <div class="card-header">
542 <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.778-7.778zM12 12V4m0 8l4-4m-4 4l-4-4"/></svg>
543 <h2>Authentication</h2>
544 </div>
545 <form method="post" action="">
546 <?php settings_fields('ps_settings_group'); ?>
547 <div class="input-group">
548 <label for="ps_openai_api_key">OpenAI API Key</label>
549 <div class="input-wrapper">
550 <input type="password"
551 name="ps_openai_api_key"
552 id="ps_openai_api_key"
553 value="<?php echo esc_attr($api_key); ?>"
554 placeholder="sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx">
555 <span class="input-hint">Your keys are encrypted and stored securely.</span>
556 </div>
557 </div>
558 <div class="card-footer">
559 <?php submit_button('Update Configuration', 'primary large'); ?>
560 </div>
561 </form>
562 </div>
563
564 <div class="ps-ai-card help-card">
565 <div class="card-header">
566 <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
567 <h2>Guide</h2>
568 </div>
569 <div class="card-body">
570 <ul class="steps">
571 <li>
572 <span class="step-num">1</span>
573 <div>
574 <strong>Get Your Key</strong>
575 <p>Visit <a href="https://platform.openai.com/api-keys" target="_blank">OpenAI Dashboard</a> to create a new secret key.</p>
576 </div>
577 </li>
578 <li>
579 <span class="step-num">2</span>
580 <div>
581 <strong>Set Quotas</strong>
582 <p>Ensure your account has an active balance or credit to use the API.</p>
583 </div>
584 </li>
585 <li>
586 <span class="step-num">3</span>
587 <div>
588 <strong>Go Live</strong>
589 <p>Paste the key here and start chatting with the assistant on any snippet edit page.</p>
590 </div>
591 </li>
592 </ul>
593 </div>
594 </div>
595 </div>
596
597 <style>
598 .ps-ai-settings-wrap { margin-top: 30px; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; max-width: 1000px; }
599 .ps-ai-header-dashboard { display: flex; justify-content: space-between; align-items: center; margin-bottom: 40px; padding: 20px; background: white; border-radius: 16px; box-shadow: 0 4px 6px -1px rgba(0,0,0,0.05); }
600 .ps-ai-header-dashboard h1 { margin: 0; font-size: 28px; font-weight: 800; color: #0f172a; }
601 .ps-ai-header-dashboard .subtitle { margin: 8px 0 0; color: #64748b; font-size: 15px; }
602 .status-badge { display: flex; align-items: center; gap: 8px; padding: 8px 16px; border-radius: 99px; font-size: 13px; font-weight: 600; }
603 .status-badge.active { background: #f0fdf4; color: #166534; }
604 .status-badge.inactive { background: #fef2f2; color: #991b1b; }
605 .status-badge .dot { width: 8px; height: 8px; border-radius: 50%; }
606 .status-badge.active .dot { background: #22c55e; box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.2); }
607 .status-badge.inactive .dot { background: #ef4444; }
608
609 .ps-ai-dashboard-grid { display: grid; grid-template-columns: 1.5fr 1fr; gap: 30px; }
610 .ps-ai-card { background: white; border-radius: 16px; border: 1px solid #f1f5f9; box-shadow: 0 10px 15px -3px rgba(0,0,0,0.05); display: flex; flex-direction: column; overflow: hidden; }
611 .ps-ai-card .card-header { padding: 24px; border-bottom: 1px solid #f1f5f9; display: flex; align-items: center; gap: 12px; }
612 .ps-ai-card .card-header h2 { margin: 0; font-size: 18px; font-weight: 600; color: #0f172a; }
613 .ps-ai-card .card-header svg { color: #3b82f6; }
614
615 .ps-ai-card .card-body, .ps-ai-card form { padding: 30px; flex: 1; }
616 .input-group label { display: block; font-weight: 600; margin-bottom: 10px; color: #334155; }
617 .input-wrapper input { width: 100%; padding: 12px 16px; border-radius: 10px; border: 1px solid #e2e8f0; background: #f8fafc; font-size: 14px; transition: all 0.2s; }
618 .input-wrapper input:focus { border-color: #3b82f6; box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.1); outline: none; background: #fff; }
619 .input-hint { display: block; font-size: 12px; color: #94a3b8; margin-top: 8px; }
620
621 .card-footer { padding: 20px 30px; background: #f8fafc; border-top: 1px solid #f1f5f9; }
622 .card-footer .submit { margin: 0; }
623
624 .steps { list-style: none; margin: 0; padding: 0; }
625 .steps li { display: flex; gap: 16px; margin-bottom: 24px; }
626 .step-num { width: 28px; height: 28px; background: #3b82f6; color: white; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 13px; font-weight: bold; flex-shrink: 0; }
627 .steps li strong { display: block; margin-bottom: 4px; color: #0f172a; }
628 .steps li p { margin: 0; font-size: 13px; color: #64748b; line-height: 1.5; }
629 .steps li a { color: #3b82f6; text-decoration: none; font-weight: 500; }
630 .steps li a:hover { text-decoration: underline; }
631 </style>
632 </div>
633 <?php
634 }
635
636 public function restPage()
637 {
638 ?>
639 <div class="wrap">
640 <form method="post" action="options.php">
641 <?php settings_fields('ps_rest_fields'); ?>
642
643 <?php do_settings_sections('ps-rest-settings'); ?>
644 <?php submit_button(); ?>
645 </form>
646 </div>
647 <?php
648 }
649
650
651
652 /**
653 * Creates the all snippets administration page.
654 *
655 * For users with manage_options capability (admin, super admin).
656 *
657 * @since Post Snippets 3.1.4
658 */
659 public function allSnippetsPage()
660 {
661
662 $allSnippets = new PSallSnippets();
663
664 ?>
665 <div class="wrap">
666 <div id="icon-users" class="icon32"></div>
667 <!-- H4 is used below cause admin notices are appended to first H1 or H2 -->
668 <h4 class="wp-heading-inline- all_snippets_heading"><?php esc_html_e('All', 'post-snippets'); ?> Snippets</h4>
669 <a href="<?php echo esc_url("?page=post-snippets-edit") ?>"
670 class="page-title-action"><?php esc_html_e('Add New', 'post-snippets'); ?></a>
671 <form id="posts-filter" method="get">
672 <input type="hidden" name="page" value="post-snippets">
673
674 <?php
675 wp_nonce_field('pspro_all_snippets', 'pspro_all_snippets_nonce', false);
676 // wp_nonce_field('sync_up', 'sync_up');
677 // wp_nonce_field('sync_down', 'sync_down');
678 $allSnippets->prepare_items();
679 $allSnippets->display();
680
681 ?>
682 </form>
683 </div>
684
685 <?php
686
687
688 }
689
690 public function editPage()
691 {
692
693 $snippet = new Edit();
694
695 }
696
697
698
699
700
701 /**
702 * Tab to set options for the plugin.
703 *
704 * @return void
705 */
706 public function tabOptions()
707 {
708 // echo '<p class="description post-snippets-documentation-note">';
709 // _e( 'Click \'Help\' in the top right for the documentation!', 'post-snippets' );
710 // echo '</p>';
711
712 ?>
713 <div class="wrap">
714 <div id="icon-users" class="icon32"></div>
715 <h1 class="wp-heading-inline"><?php esc_html_e('Options', 'post-snippets'); ?></h1>
716 <!-- <form id="posts-filter" method="post"> -->
717 <?php
718 $data = array();
719 echo View::render('admin_options', $data);
720 ?>
721 <!-- </form> -->
722 </div>
723
724 <?php
725
726
727 }
728
729 /**
730 * Tab for Import/Export
731 *
732 * @since Post Snippets 2.0
733 */
734 public function tabTools()
735 {
736 // echo '<p class="description post-snippets-documentation-note">';
737 // _e( 'Click \'Help\' in the top right for the documentation!', 'post-snippets' );
738 // echo '</p>';
739
740 $ie = new ImportExport();
741
742 // Create header and export html form
743 printf("<h3>%s</h3>", __('Import/Export', 'post-snippets'));
744 printf("<h4>%s</h4>", __('Export', 'post-snippets'));
745 echo '<form method="post">';
746 wp_nonce_field('postsnippets_export', 'postsnippets_export_nonce');
747 echo '<p>';
748 _e('Export your snippets for backup or to import them on another site.', 'post-snippets');
749 echo '</p>';
750 printf("<input type='submit' class='button' name='postsnippets_export' value='%s' />", __('Export Snippets', 'post-snippets'));
751 echo '</form>';
752
753 // Export logic, and import html form and logic
754 $ie->exportAllSnippets();
755 echo $ie->importSnippets();
756 }
757
758 /**
759 * Tab for Pro features
760 *
761 * @since Post Snippets 2.5.4
762 */
763 private function tabFeatures()
764 {
765 $features = new Features();
766
767 echo $features->showFeatures();
768
769 }
770
771
772 /**
773 * Creates a read-only overview page.
774 *
775 * For users with edit_posts capability but without manage_options
776 * capability.
777 *
778 * @since Post Snippets 1.9.7
779 */
780 public function overviewPage()
781 {
782 // Header
783 echo '<div class="wrap">';
784 echo '<h2>Post Snippets</h2>';
785 echo '<p>';
786 _e('This is an overview of all snippets defined for this site. These snippets are inserted into posts from the post editor using the Post Snippets button. You can choose to see the snippets here as-is or as they are actually rendered on the website. Enabling rendered snippets for this overview might look strange if the snippet have dependencies on variables, CSS or other parameters only available on the frontend. If that is the case it is recommended to keep this option disabled.', 'post-snippets');
787 echo '</p>';
788
789 // Form
790 $this->setUserOptions();
791 $render = $this->getUserOptions();
792
793 echo '<form method="post" action="">';
794 wp_nonce_field('post_snippets_user_options', 'post_snippets_user_nonce');
795
796 $this->checkbox(__('Display rendered snippets', 'post-snippets'), 'render', $render);
797 $this->submit('update-post-snippets-user', __('Update', 'post-snippets'));
798 echo '</form>';
799
800 // Snippet List
801 $snippets = get_option(\PostSnippets::OPTION_KEY);
802 if (!empty($snippets)) {
803 foreach ($snippets as $key => $snippet) {
804 echo "<hr style='border: none;border-top:1px dashed #aaa; margin:24px 0;' />";
805
806 echo "<h3>{$snippet['title']}";
807 if ($snippet['description']) {
808 echo "<span class='description'> {$snippet['description']}</span>";
809 }
810 echo "</h3>";
811
812 if ($snippet['vars']) {
813 printf("<strong>%s:</strong> {$snippet['vars']}<br/>", __('Variables', 'post-snippets'));
814 }
815
816 // echo "<strong>Variables:</strong> {$snippet['vars']}<br/>";
817
818 $options = array();
819 if ($snippet['shortcode']) {
820 array_push($options, 'Shortcode');
821 }
822 if ($snippet['php']) {
823 array_push($options, 'PHP');
824 }
825 if ($snippet['wptexturize']) {
826 array_push($options, 'wptexturize');
827 }
828 if ($options) {
829 printf("<strong>%s:</strong> %s<br/>", __('Options', 'post-snippets'), implode(', ', $options));
830 }
831
832 printf("<br/><strong>%s:</strong><br/>", __('Snippet', 'post-snippets'));
833 if ($render) {
834 echo do_shortcode($snippet['snippet']);
835 } else {
836 echo "<code>";
837 echo nl2br(htmlspecialchars($snippet['snippet'], ENT_NOQUOTES));
838 echo "</code>";
839 }
840 }
841 }
842 // Close
843 echo '</div>';
844 }
845
846
847 // -------------------------------------------------------------------------
848 // Register and callbacks for the options tab
849 // -------------------------------------------------------------------------
850
851 /**
852 * Register settings for the options tab.
853 *
854 * @return void
855 */
856 protected function registerSettings()
857 {
858 $this->settings = get_option(\PostSnippets::SETTINGS);
859
860 register_setting(
861 \PostSnippets::SETTINGS,
862 \PostSnippets::SETTINGS
863 );
864
865 add_settings_section(
866 'general_section',
867 __('General', 'post-snippets'),
868 null,
869 'post-snippets'
870 );
871
872 add_settings_field(
873 'exclude_from_custom_editors',
874 __('Exclude from Custom Editors', 'post-snippets'),
875 array($this, 'cbExcludeFromCustomEditors'),
876 'post-snippets',
877 'general_section',
878 array(
879 'id' => 'exclude_from_custom_editors',
880 'label_for' => 'exclude_from_custom_editors',
881 'description' => __('Checking this only includes Post Snippets on standard WordPress post editing screens.', 'post-snippets')
882 )
883 );
884
885
886 add_settings_field(
887 'complete_uninstall',
888 __('Complete Uninstall', 'post-snippets'),
889 array($this, 'cbCompleteUninstall'),
890 'post-snippets',
891 'general_section',
892 array(
893 'id' => 'complete_uninstall',
894 'label_for' => 'complete_uninstall',
895 'description' => __('When the plugin is deleted, also delete all snippets and plugin settings.', 'post-snippets')
896 )
897 );
898
899
900 add_settings_section(
901 'duplicate_section',
902 __('Duplicate Snippets', 'post-snippets'),
903 null,
904 'post-snippets'
905 );
906
907 add_settings_field(
908 'allow_duplicate',
909 __('Allow Duplicate', 'post-snippets'),
910 array($this, 'cbAllowDuplicate'),
911 'post-snippets',
912 'duplicate_section',
913 array(
914 'id' => 'allow_duplicate',
915 'label_for' => 'allow_duplicate',
916 'description' => __('Allow duplicate snippets: import all snippets from the file regardless and leave all existing snippets unchanged.', 'post-snippets')
917 )
918 );
919
920
921 add_settings_field(
922 'replace_duplicate',
923 __('Replace Duplicate', 'post-snippets'),
924 array($this, 'cbReplaceDuplicate'),
925 'post-snippets',
926 'duplicate_section',
927 array(
928 'id' => 'replace_duplicate',
929 'label_for' => 'replace_duplicate',
930 'description' => __('Replace any existing snippets with a newly imported snippet of the same name.', 'post-snippets')
931 )
932 );
933
934
935 add_settings_field(
936 'skip_duplicate',
937 __('Do not Import Duplicate', 'post-snippets'),
938 array($this, 'cbSkipDuplicate'),
939 'post-snippets',
940 'duplicate_section',
941 array(
942 'id' => 'skip_duplicate',
943 'label_for' => 'skip_duplicate',
944 'description' => __('Do not import any duplicate snippets; leave all existing snippets unchanged.', 'post-snippets')
945 )
946 );
947
948 }
949
950 /**
951 * Callback for HTML generator for exlusion of custom editors.
952 *
953 * @param array $args
954 *
955 * @return void
956 */
957 public function cbExcludeFromCustomEditors($args)
958 {
959 $checked = isset($this->settings[$args['id']]) ?
960 $this->settings[$args['id']] :
961 false;
962
963 echo "<input type='checkbox' id='{$args['id']}' ";
964 echo "name='" . \PostSnippets::SETTINGS . "[{$args['id']}]' value='1' ";
965 if ($checked) {
966 echo 'checked ';
967 }
968 echo " />";
969
970 echo "<span class='description'>{$args['description']}</span>";
971 }
972
973
974 /**
975 * Callback for Complete Uninstallation Option.
976 *
977 * @param array $args
978 *
979 * @return void
980 */
981 public function cbCompleteUninstall($args)
982 {
983 $checked = isset($this->settings[$args['id']]) ?
984 $this->settings[$args['id']] :
985 false;
986
987 echo "<input type='checkbox' id='{$args['id']}' ";
988 echo "name='" . \PostSnippets::SETTINGS . "[{$args['id']}]' value='1' ";
989 if ($checked) {
990 echo 'checked ';
991 }
992 echo " />";
993
994 echo "<span class='description'>{$args['description']}</span>";
995 }
996
997
998 /**
999 * Callback for Allow Duplicate selection.
1000 *
1001 * @param array $args
1002 *
1003 * @return void
1004 */
1005 public function cbAllowDuplicate($args)
1006 {
1007 $checked = $this->pspro_isDuplicateOptionChecked($args['id']);
1008
1009 echo "<input type='radio' id='{$args['id']}' ";
1010 echo "name='" . \PostSnippets::SETTINGS . "[" . \PostSnippets::DUPLICATE_SETTINGS . "]' value='{$args['id']}' ";
1011 if ($checked) {
1012 echo 'checked ';
1013 }
1014 echo " />";
1015
1016 echo "<span class='description'>{$args['description']}</span>";
1017 }
1018
1019
1020 /**
1021 * Callback for Complete Uninstallation Option.
1022 *
1023 * @param array $args
1024 *
1025 * @return void
1026 */
1027 public function cbReplaceDuplicate($args)
1028 {
1029 $checked = $this->pspro_isDuplicateOptionChecked($args['id']);
1030
1031 echo "<input type='radio' id='{$args['id']}' ";
1032 echo "name='" . \PostSnippets::SETTINGS . "[" . \PostSnippets::DUPLICATE_SETTINGS . "]' value='{$args['id']}' ";
1033 if ($checked) {
1034 echo 'checked ';
1035 }
1036 echo " />";
1037
1038 echo "<span class='description'>{$args['description']}</span>";
1039 }
1040
1041
1042 /**
1043 * Callback for Complete Uninstallation Option.
1044 *
1045 * @param array $args
1046 *
1047 * @return void
1048 */
1049 public function cbSkipDuplicate($args)
1050 {
1051 $checked = $this->pspro_isDuplicateOptionChecked($args['id']);
1052
1053 echo "<input type='radio' id='{$args['id']}' ";
1054 echo "name='" . \PostSnippets::SETTINGS . "[" . \PostSnippets::DUPLICATE_SETTINGS . "]' value='{$args['id']}' ";
1055 if ($checked) {
1056 echo 'checked ';
1057 }
1058 echo " />";
1059
1060 echo "<span class='description'>{$args['description']}</span>";
1061 }
1062
1063 public function pspro_isDuplicateOptionChecked($option = '')
1064 {
1065
1066 if (empty($option)) {
1067 return false;
1068 }
1069
1070 if (isset($this->settings['duplicate_option']) && $this->settings['duplicate_option'] == $option) {
1071 return true;
1072 }
1073
1074 return false;
1075
1076 }
1077
1078
1079 // -------------------------------------------------------------------------
1080 // HTML and Form element methods for Snippets form
1081 // -------------------------------------------------------------------------
1082
1083 /**
1084 * Checkbox.
1085 *
1086 * Renders the HTML for an input checkbox.
1087 *
1088 * @param string $label The label rendered to screen
1089 * @param string $name The unique name and id to identify the input
1090 * @param boolean $checked If the input is checked or not
1091 *
1092 * @return void
1093 */
1094 public static function checkbox($label, $name, $checked)
1095 {
1096 echo "<label for=\"{$name}\">";
1097 printf('<input type="checkbox" name="%1$s" id="%1$s" value="true"', $name);
1098 if ($checked) {
1099 echo ' checked';
1100 }
1101 echo ' />';
1102 echo " {$label}</label><br/>";
1103 }
1104
1105 /**
1106 * Submit.
1107 *
1108 * Renders the HTML for a submit button.
1109 *
1110 * @since Post Snippets 1.9.7
1111 *
1112 * @param string $name The name that identifies the button on submit
1113 * @param string $label The label rendered on the button
1114 * @param string $class Optional. Button class. Default: button-primary
1115 * @param boolean $wrap Optional. Wrap in a submit div. Default: true
1116 *
1117 * @return void
1118 */
1119 public static function submit($name, $label, $class = 'button-primary', $wrap = true)
1120 {
1121 $btn = sprintf('<input type="submit" name="%s" value="%s" class="%s" />&nbsp;&nbsp;&nbsp;', $name, $label, $class);
1122
1123 if ($wrap) {
1124 $btn = "<div class=\"submit\">{$btn}</div>";
1125 }
1126
1127 echo $btn;
1128 }
1129
1130 /**
1131 *
1132 * Show newsletter opt-in, only in Post Snippets.
1133 * Not on Pro features tab/page.
1134 * Not when user selected to Hide opt-in.
1135 *
1136 * @since 2.5.4
1137 */
1138 public function admin_notice_newsletter()
1139 {
1140
1141 // Hide newsletter opt-in if option is true
1142 if (get_option('ps_hide_admin_notice_newsletter') == true) {
1143 return;
1144 }
1145
1146 // Set option if "hide" button click detected (custom querystring value set to 1).
1147 if (!empty($_REQUEST['ps-dismiss-newsletter-nag'])) {
1148 update_option('ps_hide_admin_notice_newsletter', true);
1149
1150 return;
1151 }
1152
1153 // Show newsletter notice.
1154 if (get_current_screen()->id == 'settings_page_post-snippets/post-snippets') {
1155 $active_tab = isset($_GET['tab']) ? $_GET['tab'] : 'snippets';
1156 if ($active_tab != 'features') {
1157 include_once(PS_PATH . '/views/admin_notice_newsletter.php');
1158 }
1159
1160 }
1161 }
1162
1163
1164 /**
1165 *
1166 * Show 'Get started' admin notice', everywhere.
1167 * Not when user already clicked or dismissed.
1168 *
1169 * @since 2.5.4
1170 */
1171 public function admin_notice_get_started()
1172 {
1173
1174 // Hide newsletter opt-in if option is true
1175 if (get_option('ps_hide_admin_notice_get_started') == true) {
1176 return;
1177 }
1178
1179 // Set option if "hide" button click detected (custom query string value set to 1).
1180 if (!empty($_REQUEST['ps-dismiss-get-started-nag'])) {
1181 update_option('ps_hide_admin_notice_get_started', true);
1182
1183 return;
1184 }
1185
1186 // Show newsletter notice.
1187 if (strpos(get_current_screen()->id, '/post-snippets') == false) {
1188 $active_tab = isset($_GET['tab']) ? $_GET['tab'] : 'snippets';
1189 if ($active_tab != 'features') {
1190 include_once(PS_PATH . '/views/admin_notice_get_started.php');
1191 }
1192
1193 }
1194 }
1195
1196 public function get_allowed_capability()
1197 {
1198 $capability = 'manage_options';
1199
1200 if (defined('POST_SNIPPETS_ALLOW_EDIT_POSTS') and current_user_can('edit_posts')) {
1201 $capability = 'edit_posts';
1202 }
1203
1204 return $capability;
1205 }
1206
1207 }
1208