PluginProbe
Responsive Menu – Mobile Menu & Hamburger Menu Builder / 4.7.4
Responsive Menu – Mobile Menu & Hamburger Menu Builder v4.7.4
4.7.4 4.7.3 3.1.5 3.1.6 3.1.7 3.1.8 3.1.9 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.1.0 4.1.1 4.1.10 4.1.11 4.1.12 4.1.2 4.1.3 4.1.4 4.1.5 4.1.6 4.1.7 4.1.8 4.1.9 All 91 releases
responsive-menu / v4.0.0 / inc / classes / class-theme-manager.php

class-theme-manager.php in Responsive Menu – Mobile Menu & Hamburger Menu Builder 4.7.4, at v4.0.0/inc/classes/class-theme-manager.php

1,569 lines 43.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * This file contain the Theme_Manager class and it's functionalities for menu.
4 *
5 * @version 4.0.0
6 * @author Expresstech System
7 *
8 * @package responsive-menu
9 */
10
11 namespace RMP\Features\Inc;
12
13 use RMP\Features\Inc\Traits\Singleton;
14 use RMP\Features\Inc\Option_Manager;
15
16 // Disable the direct access to this class.
17 if ( ! defined( 'ABSPATH' ) ) {
18 exit;
19 }
20
21 /**
22 * Class Theme_Manager
23 * This class is handling the menu themes and its functionalities.
24 *
25 * @since 4.0.0
26 */
27 class Theme_Manager {
28
29 use Singleton;
30
31 /**
32 * This is option key where saved themes are stored.
33 *
34 * @var string $theme_option
35 */
36 protected static $theme_option = 'rmp_themes';
37
38 /**
39 * This is default theme preview image url
40 *
41 * @var string $theme_preview_img
42 */
43 public $theme_preview_img = RMP_PLUGIN_URL_V4 . '/assets/images/default-theme-preview.png';
44
45 /**
46 * Capability required to upload a menu theme.
47 *
48 * A menu theme ships a PHP index file which the plugin loads with
49 * require_once on every request, so uploading one is equivalent to
50 * installing a plugin and must require the same capability. On multisite
51 * `install_plugins` belongs to Super Admins only, which closes the
52 * privilege boundary a site Administrator could otherwise cross.
53 *
54 * @since 4.7.3
55 *
56 * @var string
57 */
58 const THEME_UPLOAD_CAPABILITY = 'install_plugins';
59
60 /**
61 * File extensions permitted inside an uploaded theme archive.
62 *
63 * PHP is on the list because the theme format requires it — see
64 * self::THEME_UPLOAD_CAPABILITY for the control that actually gates the
65 * upload. The list exists to keep files that could re-configure the web
66 * server (.htaccess, web.config, php.ini, .user.ini) out of the uploads
67 * directory, since those would undo the guards written by
68 * self::protect_upload_dir().
69 *
70 * @since 4.7.3
71 *
72 * @var array
73 */
74 const ALLOWED_THEME_FILE_EXTENSIONS = array(
75 'php',
76 'json',
77 'css',
78 'scss',
79 'sass',
80 'less',
81 'js',
82 'map',
83 'png',
84 'jpg',
85 'jpeg',
86 'gif',
87 'svg',
88 'webp',
89 'ico',
90 'bmp',
91 'woff',
92 'woff2',
93 'ttf',
94 'otf',
95 'eot',
96 'txt',
97 'md',
98 'html',
99 'htm',
100 'xml',
101 'po',
102 'mo',
103 'pot',
104 );
105
106 /**
107 * Archive entries that are ignored rather than rejected.
108 *
109 * Zips built on macOS and Windows carry this housekeeping alongside the
110 * theme files. Rejecting an otherwise valid theme because of them would
111 * be a needless failure.
112 *
113 * @since 4.7.3
114 *
115 * @var array
116 */
117 const IGNORED_THEME_FILES = array(
118 '.ds_store',
119 'thumbs.db',
120 '.gitkeep',
121 '.gitignore',
122 );
123
124 /**
125 * Construct method.
126 */
127 protected function __construct() {
128 $this->setup_hooks();
129 }
130
131 /**
132 * To setup action/filter.
133 *
134 * @since 4.0.0
135 *
136 * @return void
137 */
138 protected function setup_hooks() {
139 add_action( 'wp_ajax_rmp_save_theme', array( $this, 'rmp_save_theme' ) );
140 add_action( 'admin_post_rmp_upload_theme_file', array( $this, 'rmp_upload_theme' ) );
141 add_action( 'wp_ajax_rmp_menu_theme_upload', array( $this, 'rmp_theme_upload_from_wizard' ) );
142 add_action( 'wp_ajax_rmp_theme_delete', array( $this, 'rmp_theme_delete' ) );
143 add_action( 'wp_ajax_rmp_theme_apply', array( $this, 'rmp_theme_apply' ) );
144 add_action( 'wp_ajax_rmp_call_theme_api', array( $this, 'update_theme_api_cache' ) );
145 add_action( 'admin_init', array( $this, 'maybe_protect_upload_dir' ) );
146 }
147
148 /**
149 * Writes the uploads-directory guards once per plugin version.
150 *
151 * The activation hook does not run when a plugin is updated, so installs
152 * that already hold uploaded themes would never receive the guards
153 * otherwise.
154 *
155 * @since 4.7.3
156 *
157 * @return void
158 */
159 public function maybe_protect_upload_dir() {
160
161 if ( RMP_PLUGIN_VERSION === get_option( 'rmp_upload_dir_protected' ) ) {
162 return;
163 }
164
165 if ( $this->protect_upload_dir() ) {
166 update_option( 'rmp_upload_dir_protected', RMP_PLUGIN_VERSION );
167 }
168 }
169
170
171 /**
172 * Function to get the list of pro theme from store.
173 *
174 * @since 4.0.0
175 *
176 * @return array $pro_themes
177 */
178 public function get_themes_by_api() {
179
180 // If theme list is cached then access it.
181 $pro_themes = get_transient( 'rmp_theme_api_response' );
182 if ( ! empty( $pro_themes ) ) {
183 return $pro_themes;
184 }
185
186 $pro_themes = array();
187
188 // These are older version theme which are not compatible with new version.
189 $exclude_theme_ids = array( '47704', '47698', '45318' );
190
191 $endpoint_url = 'https://responsive.menu/edd-api/v2/products/?category=themes';
192 $rmp_response = wp_remote_get( $endpoint_url, array( 'sslverify' => false ) );
193 $rmp_response_body = wp_remote_retrieve_body( $rmp_response );
194 $rmp_response_body = json_decode( $rmp_response_body, true );
195 if ( ! empty( $rmp_response_body ) && is_array( $rmp_response_body ) ) {
196 foreach ( $rmp_response_body['products'] as $key => $product ) {
197 if ( ! in_array( $product['info']['id'], $exclude_theme_ids, true ) ) {
198 $pro_themes[] = array(
199 'name' => $product['info']['title'],
200 'slug' => $product['info']['slug'],
201 'preview_url' => $product['info']['thumbnail'],
202 'demo_link' => ! empty( $product['info']['demo_link'] ) ? $product['info']['demo_link'] : '',
203 'buy_link' => $product['info']['link'],
204 'price' => $product['pricing']['amount'],
205 );
206 }
207 }
208 }
209
210 // Cache the theme response.
211 set_transient( 'rmp_theme_api_response', $pro_themes, DAY_IN_SECONDS );
212
213 return $pro_themes;
214 }
215
216 /**
217 * Function to apply the theme in the menu.
218 *
219 * @since 4.0.0
220 *
221 * @return json
222 */
223 public function rmp_theme_apply() {
224 check_ajax_referer( 'rmp_nonce', 'ajax_nonce' );
225
226 if ( ! current_user_can( 'administrator' ) ) {
227 wp_send_json_error( array( 'message' => __( 'You can not apply themes !', 'responsive-menu' ) ) );
228 }
229
230 $theme_name = isset( $_POST['theme_name'] ) ? sanitize_text_field( wp_unslash( $_POST['theme_name'] ) ) : '';
231 if ( empty( $theme_name ) ) {
232 wp_send_json_error( array( 'message' => esc_html__( 'Theme Name Missing', 'responsive-menu' ) ) );
233 }
234
235 $theme_type = isset( $_POST['theme_type'] ) ? sanitize_text_field( wp_unslash( $_POST['theme_type'] ) ) : '';
236 $menu_id = isset( $_POST['menu_id'] ) ? sanitize_text_field( wp_unslash( $_POST['menu_id'] ) ) : '';
237 $menu_to_use = isset( $_POST['menu_to_use'] ) ? sanitize_text_field( wp_unslash( $_POST['menu_to_use'] ) ) : '';
238
239 if ( 'template' === $theme_type ) {
240 $theme_option = $this->get_saved_theme_options( $theme_name );
241 } else {
242 $theme_option = $this->get_available_theme_settings( $theme_name );
243 }
244
245 $theme_option['menu_id'] = $menu_id;
246 $theme_option['menu_theme'] = $theme_name;
247 $theme_option['theme_type'] = $theme_type;
248 $theme_option['menu_to_use'] = $menu_to_use;
249
250 update_post_meta( $menu_id, 'rmp_menu_meta', $theme_option );
251
252 /**
253 * Fires when menu theme applied and options are saved.
254 *
255 * @since 4.0.0
256 * @param int $menu_id
257 */
258 do_action( 'rmp_theme_apply', $menu_id );
259
260 wp_send_json_success( array( 'message' => esc_html__( 'Theme applied', 'responsive-menu' ) ) );
261 }
262
263 /**
264 * Function to get the theme options from availbale theme.
265 *
266 * @since 4.0.0
267 * @since 4.1.0 Add plugin bundle themes, Rename the function and Check minimum version support.
268 *
269 * @return array
270 */
271 public function get_available_theme_settings( $theme_name ) {
272
273 // Themes from uploads directory.
274 $theme_dir_path = wp_upload_dir()['basedir'] . '/rmp-menu/themes';
275 $upload_theme_url = wp_upload_dir()['baseurl'] . '/rmp-menu/themes';
276 $theme_dirs = glob( $theme_dir_path . '/*', GLOB_ONLYDIR );
277
278 // Themes from plugin bundle.
279 $theme_dirs = array_merge( glob( RMP_PLUGIN_PATH_V4 . '/themes/*', GLOB_ONLYDIR ), $theme_dirs );
280
281 $options = array();
282 $min_version = '4.0.0';
283
284 global $wp_filesystem;
285 if ( empty( $wp_filesystem ) ) {
286 require_once ABSPATH . 'wp-admin/includes/file.php';
287 }
288 WP_Filesystem();
289
290 foreach ( $theme_dirs as $theme_dir ) {
291 $config_file = $theme_dir . '/config.json';
292 if ( file_exists( $config_file ) ) {
293 $config = json_decode( $wp_filesystem->get_contents( $config_file ), true );
294 if ( $config['name'] == $theme_name ) {
295 $min_version = ! empty( $config['min_rm_version'] ) ? $config['min_rm_version'] : '4.0.0';
296 $options = json_decode( $wp_filesystem->get_contents( $theme_dir . '/options.json' ), true );
297 break;
298 }
299 }
300 }
301 // Check menu theme minimum version compatibility.
302 if ( version_compare( RMP_PLUGIN_VERSION, $min_version, '<' ) ) {
303 wp_send_json_error(
304 array(
305 'message' => sprintf(
306 '%s required Responsive Menu %s version or higher. Please update the plugin with the latest version.',
307 $theme_name,
308 $min_version
309 ),
310 )
311 );
312 }
313
314 /**
315 * Filters the theme setting options.
316 *
317 * @since 4.0.1
318 *
319 * @param array $option
320 * @param string $theme_name
321 */
322 $options = apply_filters( 'get_available_theme_settings', $options, $theme_name );
323
324 return $options;
325 }
326
327 /**
328 * Function to delete the theme.
329 *
330 * @since 4.0.0
331 * @since 4.1.0 Added condition for active theme.
332 *
333 * @return json
334 */
335 public function rmp_theme_delete() {
336 check_ajax_referer( 'rmp_nonce', 'ajax_nonce' );
337
338 if ( ! current_user_can( 'administrator' ) ) {
339 wp_send_json_error( array( 'message' => __( 'You can not delete themes !', 'responsive-menu' ) ) );
340 }
341
342 $theme_name = isset( $_POST['theme_name'] ) ? sanitize_text_field( wp_unslash( $_POST['theme_name'] ) ) : '';
343 if ( empty( $theme_name ) ) {
344 wp_send_json_error( array( 'message' => esc_html__( 'Theme Name Missing', 'responsive-menu' ) ) );
345 }
346
347 $theme_type = isset( $_POST['theme_type'] ) ? sanitize_text_field( wp_unslash( $_POST['theme_type'] ) ) : '';
348
349 if ( $this->is_active_theme( $theme_name, $theme_type ) ) {
350 wp_send_json_error( array( 'message' => esc_html__( 'This theme is currently active. Please choose another theme and then try deleting.', 'responsive-menu' ) ) );
351 }
352
353 if ( 'template' === $theme_type ) {
354 $this->delete_template( $theme_name );
355 } else {
356 $this->delete_theme_folder( $theme_name );
357 }
358
359 wp_send_json_success( array( 'message' => esc_html__( 'Theme deleted', 'responsive-menu' ) ) );
360 }
361
362 /**
363 * Function to return the theme dir path.
364 *
365 * @since 4.0.0
366 * @since 4.1.0 Added the plugin bundle theme.
367 *
368 * @return string
369 */
370 public function get_theme_dir( $theme_name ) {
371
372 // Themes from uploads directory.
373 $upload_theme_url = wp_upload_dir()['baseurl'] . '/rmp-menu/themes';
374 $theme_dir_path = wp_upload_dir()['basedir'] . '/rmp-menu/themes';
375 $theme_dirs = glob( $theme_dir_path . '/*', GLOB_ONLYDIR );
376
377 // Themes from plugin bundle.
378 $theme_dirs = array_merge( glob( RMP_PLUGIN_PATH_V4 . '/themes/*', GLOB_ONLYDIR ), $theme_dirs );
379 global $wp_filesystem;
380 if ( empty( $wp_filesystem ) ) {
381 require_once ABSPATH . 'wp-admin/includes/file.php';
382 }
383 WP_Filesystem();
384 foreach ( $theme_dirs as $theme_dir ) {
385 $config_file = $theme_dir . '/config.json';
386 if ( file_exists( $config_file ) ) {
387 $config = json_decode( $wp_filesystem->get_contents( $config_file ), true );
388 if ( $config['name'] == $theme_name ) {
389 return $theme_dir;
390 }
391 }
392 }
393
394 return false;
395 }
396
397 /**
398 * Function to delete the theme dir.
399 *
400 * @since 4.0.0
401 *
402 * @return void
403 */
404 public function delete_theme_folder( $theme_name ) {
405 if ( empty( $theme_name ) ) {
406 return;
407 }
408
409 $theme_dir = $this->get_theme_dir( $theme_name );
410 if ( empty( $theme_dir ) ) {
411 return;
412 }
413
414 $this->delete_files( $theme_dir );
415 }
416
417 /**
418 * Function to delete the theme files.
419 *
420 * @since 4.0.0
421 *
422 * @return void
423 */
424 public function delete_files( $dir ) {
425 foreach ( glob( $dir . '/*' ) as $file ) {
426 if ( is_dir( $file ) ) {
427 delete_files( $file );
428 } else {
429 unlink( $file );
430 }
431 }
432 rmdir( $dir );
433 }
434
435 /**
436 * Function to delete the saved template.
437 *
438 * @since 4.0.0
439 *
440 * @return boolean
441 */
442 public function delete_template( $theme_name ) {
443 $rmp_themes = get_option( self::$theme_option );
444
445 if ( empty( $rmp_themes ) ) {
446 return false;
447 }
448
449 foreach ( $rmp_themes as $theme_key => $options ) {
450 if ( $theme_name === $theme_key ) {
451 unset( $rmp_themes[ $theme_key ] );
452 update_option( self::$theme_option, $rmp_themes );
453 return true;
454 }
455 }
456
457 return false;
458 }
459
460 /**
461 * Returns the directory uploaded menu themes are extracted into.
462 *
463 * @since 4.7.3
464 *
465 * @return string Trailing-slashed absolute path.
466 */
467 public function get_theme_upload_dir() {
468 return trailingslashit( wp_upload_dir()['basedir'] ) . 'rmp-menu/themes/';
469 }
470
471 /**
472 * Writes the hardening guards into the plugin's uploads directory.
473 *
474 * Menu themes legitimately contain a PHP file, and that file is meant to
475 * be loaded by the plugin — never requested over HTTP. This drops an
476 * .htaccess that refuses to serve or execute scripts under
477 * uploads/rmp-menu/, plus a silent index.php so the directory cannot be
478 * browsed. Stylesheets and theme images keep working because only script
479 * types are denied, not the whole directory.
480 *
481 * Both files are written only when missing, so a host that has customised
482 * them is left alone.
483 *
484 * Note: .htaccess is honoured by Apache and LiteSpeed only. nginx has no
485 * per-directory config, so those installs need the equivalent `location`
486 * rule in the server configuration — see readme.txt.
487 *
488 * @since 4.7.3
489 *
490 * @return bool True when both guards are in place.
491 */
492 public function protect_upload_dir() {
493
494 global $wp_filesystem;
495
496 if ( empty( $wp_filesystem ) ) {
497 require_once ABSPATH . 'wp-admin/includes/file.php';
498 }
499
500 WP_Filesystem();
501
502 if ( empty( $wp_filesystem ) ) {
503 return false;
504 }
505
506 $base_dir = trailingslashit( wp_upload_dir()['basedir'] ) . 'rmp-menu/';
507
508 if ( ! $wp_filesystem->is_dir( $base_dir ) && ! wp_mkdir_p( $base_dir ) ) {
509 return false;
510 }
511
512 $htaccess = $base_dir . '.htaccess';
513
514 if ( ! $wp_filesystem->exists( $htaccess ) ) {
515 $rules = "# Responsive Menu: never serve or execute scripts from this directory.\n"
516 . "<IfModule mod_php.c>\n\tphp_flag engine off\n</IfModule>\n"
517 . "<IfModule mod_php7.c>\n\tphp_flag engine off\n</IfModule>\n"
518 . "<IfModule mod_php8.c>\n\tphp_flag engine off\n</IfModule>\n"
519 . "<FilesMatch \"\\.(?i:php|php[0-9]|phps|phtml|pht|phar|inc|hphp|cgi|pl|py|sh)$\">\n"
520 . "\t<IfModule mod_authz_core.c>\n\t\tRequire all denied\n\t</IfModule>\n"
521 . "\t<IfModule !mod_authz_core.c>\n\t\tOrder allow,deny\n\t\tDeny from all\n\t</IfModule>\n"
522 . "</FilesMatch>\n";
523
524 $wp_filesystem->put_contents( $htaccess, $rules, FS_CHMOD_FILE );
525 }
526
527 $index = $base_dir . 'index.php';
528
529 if ( ! $wp_filesystem->exists( $index ) ) {
530 $wp_filesystem->put_contents( $index, "<?php\n// Silence is golden.\n", FS_CHMOD_FILE );
531 }
532
533 return $wp_filesystem->exists( $htaccess ) && $wp_filesystem->exists( $index );
534 }
535
536 /**
537 * Returns the list of entry names held in a zip archive.
538 *
539 * Opening the archive is also how the upload is confirmed to be a real
540 * zip: anything that is not one fails to open here, which is a stronger
541 * check than trusting the uploaded file name.
542 *
543 * Mirrors the readers unzip_file() itself uses, so an archive that lists
544 * here is one that will extract.
545 *
546 * @since 4.7.3
547 *
548 * @param string $file Absolute path to the archive.
549 *
550 * @return array|\WP_Error Entry names, or WP_Error when the file is not a readable zip.
551 */
552 protected function get_archive_file_list( $file ) {
553
554 $error = new \WP_Error(
555 'rmp_invalid_archive',
556 esc_html__( 'This file could not be read as a zip archive.', 'responsive-menu' )
557 );
558
559 $entries = array();
560
561 if ( class_exists( 'ZipArchive' ) ) {
562 $zip = new \ZipArchive();
563
564 if ( true !== $zip->open( $file ) ) {
565 return $error;
566 }
567
568 for ( $index = 0; $index < $zip->numFiles; $index++ ) {
569 $entry = $zip->statIndex( $index );
570
571 if ( false !== $entry ) {
572 $entries[] = $entry['name'];
573 }
574 }
575
576 $zip->close();
577
578 return $entries;
579 }
580
581 require_once ABSPATH . 'wp-admin/includes/class-pclzip.php';
582
583 $archive = new \PclZip( $file );
584 $contents = $archive->listContent();
585
586 if ( ! is_array( $contents ) ) {
587 return $error;
588 }
589
590 foreach ( $contents as $entry ) {
591 $entries[] = $entry['filename'];
592 }
593
594 return $entries;
595 }
596
597 /**
598 * Returns the archive entries that a theme is not allowed to contain.
599 *
600 * @since 4.7.3
601 *
602 * @param array $entries Entry names from the archive.
603 *
604 * @return array Offending entry names.
605 */
606 protected function get_disallowed_theme_files( $entries ) {
607
608 $disallowed = array();
609
610 foreach ( $entries as $entry ) {
611
612 // Directories carry no payload of their own.
613 if ( '/' === substr( $entry, -1 ) ) {
614 continue;
615 }
616
617 // Editor and OS metadata that is harmless but common in theme zips.
618 if ( 0 === strpos( $entry, '__MACOSX/' ) ) {
619 continue;
620 }
621
622 $basename = basename( $entry );
623
624 if ( in_array( strtolower( $basename ), self::IGNORED_THEME_FILES, true ) ) {
625 continue;
626 }
627
628 /*
629 * unzip_file() drops traversing entries itself, but a theme has no
630 * reason to carry one, so treat it as a rejected upload rather than
631 * a silently skipped file.
632 */
633 if ( 0 !== validate_file( $entry ) || '/' === substr( $entry, 0, 1 ) ) {
634 $disallowed[] = $entry;
635 continue;
636 }
637
638 $extension = strtolower( pathinfo( $basename, PATHINFO_EXTENSION ) );
639
640 // Files with no extension cannot be handed to an interpreter.
641 if ( '' === $extension ) {
642 continue;
643 }
644
645 if ( ! in_array( $extension, self::ALLOWED_THEME_FILE_EXTENSIONS, true ) ) {
646 $disallowed[] = $entry;
647 }
648 }
649
650 return $disallowed;
651 }
652
653 /**
654 * Validates and extracts an uploaded menu theme archive.
655 *
656 * Shared by the settings-page upload and the setup-wizard upload so both
657 * entry points enforce the same rules.
658 *
659 * @since 4.7.3
660 *
661 * @return true|\WP_Error True on success, WP_Error describing the refusal otherwise.
662 */
663 protected function process_theme_upload() {
664
665 if ( ! current_user_can( self::THEME_UPLOAD_CAPABILITY ) ) {
666 return new \WP_Error(
667 'rmp_theme_upload_forbidden',
668 esc_html__( 'You can not upload themes !', 'responsive-menu' )
669 );
670 }
671
672 $tmp_name = isset( $_FILES['file']['tmp_name'] ) ? sanitize_text_field( wp_unslash( $_FILES['file']['tmp_name'] ) ) : '';
673
674 if ( empty( $tmp_name ) || ! is_uploaded_file( $tmp_name ) ) {
675 return new \WP_Error(
676 'rmp_theme_upload_missing',
677 esc_html__( 'Please add zip file !', 'responsive-menu' )
678 );
679 }
680
681 // Reject on the file name first — a cheap check before reading the file.
682 $file_name = isset( $_FILES['file']['name'] ) ? sanitize_file_name( wp_unslash( $_FILES['file']['name'] ) ) : '';
683 $file_type = wp_check_filetype( $file_name, array( 'zip' => 'application/zip' ) );
684
685 if ( 'zip' !== $file_type['ext'] ) {
686 return new \WP_Error(
687 'rmp_theme_upload_not_zip',
688 esc_html__( 'Please add zip file !', 'responsive-menu' )
689 );
690 }
691
692 // Reading the archive proves the contents are a zip, not just the name.
693 $entries = $this->get_archive_file_list( $tmp_name );
694
695 if ( is_wp_error( $entries ) ) {
696 return $entries;
697 }
698
699 $disallowed = $this->get_disallowed_theme_files( $entries );
700
701 if ( ! empty( $disallowed ) ) {
702 return new \WP_Error(
703 'rmp_theme_upload_disallowed_file',
704 sprintf(
705 /* translators: %s: comma separated list of file names. */
706 esc_html__( 'This theme was not installed because it contains files that are not allowed: %s', 'responsive-menu' ),
707 esc_html( implode( ', ', array_slice( $disallowed, 0, 5 ) ) )
708 )
709 );
710 }
711
712 if ( ! $this->archive_has_theme_config( $entries ) ) {
713 return new \WP_Error(
714 'rmp_theme_upload_not_a_theme',
715 esc_html__( 'This zip does not look like a menu theme — no config.json was found inside it.', 'responsive-menu' )
716 );
717 }
718
719 // Make sure the guards exist before anything is written next to them.
720 $this->protect_upload_dir();
721
722 $unzipped = unzip_file( $tmp_name, $this->get_theme_upload_dir() );
723
724 if ( is_wp_error( $unzipped ) ) {
725 return $unzipped;
726 }
727
728 return true;
729 }
730
731 /**
732 * Checks that an archive carries a theme manifest.
733 *
734 * @since 4.7.3
735 *
736 * @param array $entries Entry names from the archive.
737 *
738 * @return bool
739 */
740 protected function archive_has_theme_config( $entries ) {
741
742 foreach ( $entries as $entry ) {
743 if ( 'config.json' === basename( $entry ) ) {
744 return true;
745 }
746 }
747
748 return false;
749 }
750
751 /**
752 * Funtion to upload the menu theme zip file.
753 *
754 * @since 4.0.0
755 * @since 4.0.4 Added nonce and user capabilities check.
756 * @since 4.7.3 Validates the archive contents and requires `install_plugins`.
757 *
758 * @since array $status
759 */
760 public function rmp_upload_theme() {
761
762 // Check nonce to verify the authenticate upload file.
763 check_ajax_referer( 'rmp_nonce', 'rmp_theme_upload_nonce' );
764
765 status_header( 200 );
766
767 $uploaded = $this->process_theme_upload();
768
769 if ( is_wp_error( $uploaded ) ) {
770 wp_send_json_error(
771 array( 'message' => $uploaded->get_error_message() )
772 );
773 }
774
775 wp_send_json_success(
776 array( 'message' => esc_html__( 'Theme Imported Successfully.', 'responsive-menu' ) )
777 );
778 }
779
780 /**
781 * Returns the theme list with meta info.
782 *
783 * @since 4.0.0
784 * @since 4.1.0 Added bundle themes.
785 *
786 * @return array $theme
787 */
788 public function get_themes_from_uploads() {
789
790 // Get theme from uploads directory.
791 $upload_dir = wp_upload_dir();
792 $theme_url = $upload_dir['baseurl'] . '/rmp-menu/themes';
793 $theme_dir_path = $upload_dir['basedir'] . '/rmp-menu/themes';
794 $theme_dirs = glob( $theme_dir_path . '/*', GLOB_ONLYDIR );
795
796 // Get themes from plugin bundle.
797 $theme_dirs = array_merge( glob( RMP_PLUGIN_PATH_V4 . '/themes/*', GLOB_ONLYDIR ), $theme_dirs );
798 global $wp_filesystem;
799 if ( empty( $wp_filesystem ) ) {
800 require_once ABSPATH . 'wp-admin/includes/file.php';
801 }
802 WP_Filesystem();
803 $themes = array();
804 foreach ( $theme_dirs as $theme_dir ) {
805 $config_file = $theme_dir . '/config.json';
806 $theme_preview_url = $theme_url . '/' . basename( $theme_dir ) . '/preview.png';
807
808 // Theme preview image from plugin bundle.
809 if ( ! strpos( $theme_dir, 'uploads' ) ) {
810 $theme_preview_url = plugin_dir_url( $config_file ) . '/preview.png';
811 }
812
813 if ( file_exists( $config_file ) ) {
814 $config = json_decode( $wp_filesystem->get_contents( $config_file ), true );
815 $themes[ basename( $theme_dir ) ]['theme_name'] = $config['name'];
816 $themes[ basename( $theme_dir ) ]['theme_version'] = $config['version'];
817 $themes[ basename( $theme_dir ) ]['demo_link'] = ! empty( $config['demo_link'] ) ? $config['demo_link'] : '';
818 $themes[ basename( $theme_dir ) ]['theme_preview_url'] = $theme_preview_url;
819 }
820 }
821
822 return $themes;
823 }
824
825 /**
826 * Returns the theme dir list to supress the theme which are in downloaded list.
827 *
828 * @since 4.0.2
829 *
830 * @return array $theme_dirs
831 */
832 public function get_uploaded_theme_dir() {
833 $themes = $this->get_themes_from_uploads();
834
835 if ( empty( $themes ) ) {
836 return;
837 }
838
839 $theme_dirs = array();
840
841 foreach ( $themes as $theme => $theme_meta ) {
842
843 // Replace the these older themes dir name as slug.
844 if ( 'electric blue theme' === $theme ) {
845 $theme_dirs[] = 'electric-blue-free';
846 } elseif ( 'full-width-theme' === $theme ) {
847 $theme_dirs[] = 'full-width-free';
848 } elseif ( 'simple-red-theme' === $theme ) {
849 $theme_dirs[] = 'simple-red-free';
850 } else {
851 $theme_dirs[] = strtolower( $theme );
852 }
853 }
854
855 return $theme_dirs;
856 }
857
858 public function rmp_save_theme() {
859 check_ajax_referer( 'rmp_nonce', 'ajax_nonce' );
860
861 $theme_name = isset( $_POST['theme_name'] ) ? sanitize_text_field( wp_unslash( $_POST['theme_name'] ) ) : '';
862 if ( empty( $theme_name ) ) {
863 wp_send_json_error( array( 'message' => esc_html__( 'Theme Name Missing', 'responsive-menu' ) ) );
864 }
865
866 $menu_id = isset( $_POST['menu_id'] ) ? sanitize_text_field( wp_unslash( $_POST['menu_id'] ) ) : '';
867 if ( empty( $menu_id ) ) {
868 wp_send_json_error(
869 array( 'message' => esc_html__( 'Menu ID missing !', 'responsive-menu' ) )
870 );
871 }
872
873 if ( ! current_user_can( 'edit_post', $menu_id ) ) {
874 wp_send_json_error( array( 'message' => __( 'You can not edit menu !', 'responsive-menu' ) ) );
875 }
876
877 $options = array();
878 $form_data = isset( $_POST['form'] ) ? rm_sanitize_rec_array( wp_unslash( $_POST['form'] ) ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
879 wp_parse_str( $form_data, $options );
880 $options = $options['menu'];
881
882 $rmp_themes = get_option( self::$theme_option );
883
884 /**
885 * Reset to an empty array when the stored option is missing or corrupt.
886 * Without this, a scalar option value makes the write below a fatal
887 * error on PHP 8: "Cannot use a scalar value as an array".
888 */
889 if ( empty( $rmp_themes ) || ! is_array( $rmp_themes ) ) {
890 $rmp_themes = array();
891 }
892
893 $rmp_themes[ $theme_name ] = $options;
894
895 update_option( self::$theme_option, $rmp_themes );
896
897 $data = $this->saved_theme_list();
898
899 wp_send_json_success(
900 array(
901 'themes' => $data,
902 'message' => $theme_name . ' is saved',
903 )
904 );
905
906 exit();
907 }
908
909
910 public function saved_theme_list() {
911 $rmp_themes = get_option( self::$theme_option );
912
913 if ( empty( $rmp_themes ) ) {
914 return;
915 }
916
917 $theme_list = array();
918 foreach ( $rmp_themes as $theme_name => $options ) {
919 $theme_list[] = $theme_name;
920 }
921
922 return $theme_list;
923 }
924
925 public function get_saved_theme_options( $theme_name ) {
926 $rmp_themes = get_option( self::$theme_option );
927
928 if ( empty( $rmp_themes ) ) {
929 return;
930 }
931
932 foreach ( $rmp_themes as $theme_key => $options ) {
933 if ( $theme_name === $theme_key ) {
934 return $options;
935 }
936 }
937
938 return array();
939 }
940
941 /**
942 * Function to return the list of saved template themes.
943 *
944 * @since 4.0.0
945 * @since Updated the funtion to add the condition
946 *
947 * @return HTML|string
948 */
949 public function rmp_saves_theme_template_list( $in_customizer = false ) {
950 $rmp_themes = $this->saved_theme_list();
951
952 // Check the list is empty or not.
953 if ( empty( $rmp_themes ) ) {
954 ?><div class="rmp-theme-page-empty">
955 <span class="rmp-menu-library-blank-icon dashicons dashicons-welcome-widgets-menus"></span>
956 <h3 class="rmp-menu-library-title"> <?php esc_html_e( 'You have no template !', 'responsive-menu' ); ?> </h3>
957 </div>
958 <?php
959 return;
960 }
961
962 // Prepare the saved theme list and wrapped into html.
963 foreach ( $rmp_themes as $theme_name ) {
964 ?>
965 <div class="rmp-theme-title ">
966 <span class="item-title"> <?php echo esc_attr( $theme_name ); ?> </span>
967 <span class="item-controls">
968 <?php
969 if ( $in_customizer ) {
970 ?>
971 <a theme-name="<?php echo esc_attr( $theme_name ); ?>" class="rmp-theme-apply" theme-type="template"><?php esc_html_e( 'Apply', 'responsive-menu' ); ?></a>
972 <?php
973 } else {
974 ?>
975 <input type="radio" class="rmp-theme-option" name="menu_theme" id="<?php echo esc_attr( $theme_name ); ?>" value="<?php echo esc_attr( $theme_name ); ?>" theme-type="template"/>
976 <label theme-name="<?php echo esc_attr( $theme_name ); ?>" class="rmp-theme-use" for="<?php echo esc_attr( $theme_name ); ?>"><?php esc_html_e( 'Use', 'responsive-menu' ); ?></label>
977 <?php } ?>
978 </span>
979 </div>
980 <?php
981 }
982 }
983
984 /**
985 * Design the theme list which are from stored.
986 *
987 * @since 4.0.0
988 * @return HTML|string $html
989 */
990 public function get_themes_from_theme_store( $in_customizer = false ) {
991 $themes = $this->get_themes_by_api();
992 $uploaded_themes = $this->get_uploaded_theme_dir();
993
994 if ( empty( $uploaded_themes ) || ! is_array( $uploaded_themes ) ) {
995 $uploaded_themes = array();
996 }
997
998 foreach ( $themes as $theme ) {
999
1000 // Avoid the themes which are already uploaded.
1001 if ( in_array( strtolower( $theme['slug'] ), $uploaded_themes, true ) ) {
1002 continue;
1003 }
1004
1005 if ( $in_customizer ) {
1006 $buy_link = add_query_arg(
1007 array(
1008 'utm_source' => 'plugin',
1009 'utm_medium' => 'change_theme_wizard',
1010 ),
1011 $theme['buy_link']
1012 );
1013 } else {
1014 $buy_link = add_query_arg(
1015 array(
1016 'utm_source' => 'plugin',
1017 'utm_medium' => 'new_menu_wizard',
1018 ),
1019 $theme['buy_link']
1020 );
1021 }
1022 ?>
1023 <li class="rmp_theme_grid_item">
1024 <div class="rmp-item-card">
1025 <figure class="rmp-item-card_image">
1026 <img src="<?php echo esc_url( $theme['preview_url'] ); ?>" alt="<?php echo esc_attr( $theme['name'] ); ?>" loading="lazy"/>
1027 </figure>
1028 <div class="rmp-item-card-backside">
1029 <div class="rmp-item-card_contents">
1030 <h4> <?php echo esc_html( $theme['name'] ); ?> </h4>
1031 </div>
1032 <div class="rmp-item-card_action">
1033 <?php
1034 if ( ! empty( $theme['demo_link'] ) ) {
1035 if ( $in_customizer ) {
1036 $link = add_query_arg(
1037 array(
1038 'utm_source' => 'plugin',
1039 'utm_medium' => 'change_theme_wizard',
1040 ),
1041 $theme['demo_link']
1042 );
1043 } else {
1044 $link = add_query_arg(
1045 array(
1046 'utm_source' => 'plugin',
1047 'utm_medium' => 'new_menu_wizard',
1048 ),
1049 $theme['demo_link']
1050 );
1051 }
1052 ?>
1053 <a href="<?php echo esc_url( $link ); ?>" alt="<?php echo esc_attr( $theme['name'] ); ?>" target="_blank" rel="noopener" class="button"><?php esc_html_e( 'View Demo', 'responsive-menu' ); ?></a>
1054 <?php
1055 }
1056 ?>
1057 <a href="<?php echo esc_url( $buy_link ); ?>" target="_blank" rel="noopener" class="button btn-blue">
1058 <?php
1059 if ( 0 === intval( $theme['price'] ) ) {
1060 esc_html_e( 'Download', 'responsive-menu' );
1061 } else {
1062 esc_html_e( 'Purchase', 'responsive-menu' );
1063 }
1064 ?>
1065 </a>
1066 </div>
1067 </div>
1068 </div>
1069 </li>
1070 <?php
1071 }
1072
1073 if ( empty( $themes ) ) {
1074 ?>
1075 <div class="rmp-theme-page-empty">
1076 <span class="rmp-menu-library-blank-icon fas fa-file-download"></span>
1077 <h3 class="rmp-menu-library-title"> <?php esc_html_e( 'No theme available !', 'responsive-menu' ); ?> </h3>
1078 </div>
1079 <?php
1080 }
1081 }
1082
1083 public function all_theme_combine_list() {
1084 $all_themes = array();
1085
1086 // Local saved themes.
1087 $themes = $this->saved_theme_list();
1088
1089 if ( ! empty( $themes ) && is_array( $themes ) ) {
1090 foreach ( $themes as $theme ) {
1091 $all_themes[] = array(
1092 'name' => $theme,
1093 'type' => 'Template',
1094 );
1095 }
1096 }
1097
1098 // Uploaded themes.
1099 $themes = $this->get_themes_from_uploads();
1100 if ( ! empty( $themes ) && is_array( $themes ) ) {
1101 foreach ( $themes as $theme ) {
1102 $all_themes[] = array(
1103 'name' => $theme['theme_name'],
1104 'type' => 'Downloaded',
1105 'preview_url' => $theme['theme_preview_url'],
1106 );
1107 }
1108 }
1109
1110 return $all_themes;
1111 }
1112
1113 /**
1114 * Returns the thumbnail of theme.
1115 *
1116 * @since 4.0.0
1117 *
1118 * @return string|url|null
1119 */
1120 public function get_theme_preview_url( $theme_name ) {
1121
1122 // Get theme from uploads directory.
1123 $upload_dir = wp_upload_dir();
1124 $theme_url = $upload_dir['baseurl'] . '/rmp-menu/themes';
1125 $theme_dir_path = $upload_dir['basedir'] . '/rmp-menu/themes';
1126 $theme_dirs = glob( $theme_dir_path . '/*', GLOB_ONLYDIR );
1127
1128 // Get themes from plugin bundle.
1129 $theme_dirs = array_merge( glob( RMP_PLUGIN_PATH_V4 . '/themes/*', GLOB_ONLYDIR ), $theme_dirs );
1130 global $wp_filesystem;
1131 if ( empty( $wp_filesystem ) ) {
1132 require_once ABSPATH . 'wp-admin/includes/file.php';
1133 }
1134 WP_Filesystem();
1135 foreach ( $theme_dirs as $theme_dir ) {
1136 $config_file = $theme_dir . '/config.json';
1137 $theme_preview_url = $theme_url . '/' . basename( $theme_dir ) . '/preview.png';
1138
1139 // Theme preview image from plugin bundle.
1140 if ( ! strpos( $theme_dir, 'uploads' ) ) {
1141 $theme_preview_url = plugin_dir_url( $config_file ) . '/preview.png';
1142 }
1143
1144 if ( file_exists( $config_file ) ) {
1145 $config = json_decode( $wp_filesystem->get_contents( $config_file ), true );
1146 if ( $config['name'] == $theme_name ) {
1147 return $theme_preview_url;
1148 }
1149 }
1150 }
1151
1152 return;
1153 }
1154
1155 /**
1156 * Function to return the theme thumbnail element.
1157 *
1158 * @since 4.0.0
1159 *
1160 * @return strinh|HTML|null
1161 */
1162 public function get_theme_thumbnail( $theme_name, $theme_type ) {
1163
1164 // If theme is template
1165 if ( 'template' === $theme_type ) {
1166 ?>
1167 <img src="<?php echo esc_url( RMP_PLUGIN_URL_V4 . '/assets/images/no-preview.jpeg' ); ?>" class="theme-thumbnail" alt="<?php echo esc_attr( $theme_type ); ?>" >
1168 <?php
1169 }
1170
1171 // If theme is default.
1172 if ( 'default' === $theme_type ) {
1173 ?>
1174 <img src="<?php echo esc_url( esc_url( $this->theme_preview_img ) ); ?>" class="theme-thumbnail" alt="<?php echo esc_attr( $theme_type ); ?>" >
1175 <?php
1176 }
1177
1178 $theme_preview_url = $this->get_theme_preview_url( $theme_name );
1179 if ( empty( $theme_preview_url ) ) {
1180 return;
1181 }
1182 ?>
1183 <img src="<?php echo esc_url( $theme_preview_url ); ?>" class="theme-thumbnail" alt="<?php echo esc_attr( $theme_type ); ?>" >
1184 <?php
1185 }
1186
1187 /**
1188 * Returns the theme index file path.
1189 *
1190 * The index is read from a theme's own config.json, which for an uploaded
1191 * theme is attacker-supplied, and the caller require_once's whatever comes
1192 * back. It is therefore reduced to a file name inside the theme directory.
1193 *
1194 * @since 4.1.0
1195 * @since 4.7.3 Constrained the configured index to a PHP file in the theme directory.
1196 *
1197 * @return string;
1198 */
1199 public function get_theme_index_file( $theme_name ) {
1200
1201 // Get theme from uploads directory.
1202 $theme_url = wp_upload_dir()['baseurl'] . '/rmp-menu/themes';
1203 $theme_dir_path = wp_upload_dir()['basedir'] . '/rmp-menu/themes';
1204 $theme_dirs = glob( $theme_dir_path . '/*', GLOB_ONLYDIR );
1205
1206 // Get themes from plugin bundle.
1207 $theme_dirs = array_merge( glob( RMP_PLUGIN_PATH_V4 . '/themes/*', GLOB_ONLYDIR ), $theme_dirs );
1208
1209 global $wp_filesystem;
1210 if ( empty( $wp_filesystem ) ) {
1211 require_once ABSPATH . 'wp-admin/includes/file.php';
1212 }
1213 WP_Filesystem();
1214
1215 foreach ( $theme_dirs as $theme_dir ) {
1216
1217 $config_file = $theme_dir . '/config.json';
1218
1219 if ( file_exists( $config_file ) ) {
1220 $config = json_decode( $wp_filesystem->get_contents( $config_file ), true );
1221
1222 if ( empty( $config['name'] ) || empty( $config['index'] ) || $config['name'] !== $theme_name ) {
1223 continue;
1224 }
1225
1226 // Never let the manifest point outside its own directory.
1227 $index_file = basename( $config['index'] );
1228
1229 if ( 'php' !== strtolower( pathinfo( $index_file, PATHINFO_EXTENSION ) ) ) {
1230 continue;
1231 }
1232
1233 return $theme_dir . '/' . $index_file;
1234 }
1235 }
1236
1237 return;
1238 }
1239
1240 /**
1241 * Returns all uploaded theme list.
1242 *
1243 * @since 4.1.0
1244 *
1245 * @return array
1246 */
1247 public function get_menu_active_themes() {
1248 $active_themes = array();
1249 $themes = $this->get_themes_from_uploads();
1250 foreach ( $themes as $key => $theme ) {
1251 if ( empty( $theme['theme_name'] ) ) {
1252 continue;
1253 }
1254
1255 $active_themes[ $key ] = $theme['theme_name'];
1256 }
1257
1258 return $active_themes;
1259 }
1260
1261 /**
1262 * Check theme is active or not.
1263 *
1264 * @since 4.1.0
1265 *
1266 * @return bool
1267 */
1268 public function is_active_theme( $theme_name, $theme_type ) {
1269 if ( empty( $theme_name ) || empty( $theme_type ) ) {
1270 return false;
1271 }
1272
1273 $option_manager = Option_Manager::get_instance();
1274 $menu_ids = get_all_rmp_menu_ids();
1275
1276 foreach ( $menu_ids as $menu_id ) {
1277 $options = $option_manager->get_options( $menu_id );
1278
1279 if ( empty( $options['menu_theme'] ) || empty( $options['theme_type'] ) ) {
1280 continue;
1281 }
1282
1283 if ( $options['menu_theme'] === $theme_name && $options['theme_type'] === $theme_type ) {
1284 return true;
1285 }
1286 }
1287
1288 return false;
1289 }
1290
1291 /**
1292 * Function to returns the available theme list.
1293 *
1294 * @since 4.1.0
1295 * @return HTML|string $html
1296 */
1297 public function get_available_themes( $in_customizer = false ) {
1298 ?>
1299 <ul class="rmp_theme_grids">
1300 <?php
1301
1302 if ( ! $in_customizer ) {
1303 ?>
1304 <li class="rmp_theme_grid_item">
1305 <input type="radio" checked id="default" class="rmp-theme-option" name="menu_theme" value="" theme-type="default"/>
1306 <label class="rmp-item-card default-item" for="default">
1307 <figure class="rmp-item-card_image">
1308 <img src="<?php echo esc_url( esc_url( $this->theme_preview_img ) ); ?>" alt="<?php esc_html_e( 'Default Theme', 'responsive-menu' ); ?>" loading="lazy"/>
1309 </figure>
1310 <div class="rmp-item-card-backside">
1311 <div class="rmp-item-card_contents">
1312 <h4> <?php esc_html_e( 'Default Theme', 'responsive-menu' ); ?> </h4>
1313 </div>
1314 <div class="rmp-item-card_action">
1315 <a href="https://demo.responsive.menu/themes/default-theme/?utm_source=plugin&utm_medium=new_menu_wizard" alt="<?php esc_html_e( 'Default Theme', 'responsive-menu' ); ?>" target="_blank" rel="noopener" class="button"><?php esc_html_e( 'View Demo', 'responsive-menu' ); ?></a>
1316 </div>
1317 </div>
1318 </label>
1319 </li>
1320 <?php
1321 }
1322
1323 $downloaded_themes = $this->get_themes_from_uploads();
1324 foreach ( $downloaded_themes as $theme ) {
1325 $id = 'rmp-theme-' . preg_replace( '/\s+/', '', $theme['theme_name'] );
1326 ?>
1327 <li class="rmp_theme_grid_item">
1328 <?php
1329 if ( ! $in_customizer ) {
1330 ?>
1331 <input type="radio" id="<?php echo esc_attr( $id ); ?>" theme-type="downloaded" class="rmp-theme-option" name="menu_theme" value="<?php echo esc_html( $theme['theme_name'] ); ?>"/>
1332 <?php
1333 }
1334 ?>
1335 <label class="rmp-item-card" for="<?php echo esc_attr( $id ); ?>">
1336 <figure class="rmp-item-card_image">
1337 <img src="<?php echo esc_url( $theme['theme_preview_url'] ); ?>" alt="<?php echo esc_html( $theme['theme_name'] ); ?>" loading="lazy"/>
1338 </figure>
1339 <div class="rmp-item-card-backside">
1340 <div class="rmp-item-card_contents">
1341 <h4> <?php echo esc_html( $theme['theme_name'] ); ?> </h4>
1342 </div>
1343 <div class="rmp-item-card_action">
1344 <?php
1345 if ( ! empty( $theme['demo_link'] ) ) {
1346 if ( $in_customizer ) {
1347 $link = add_query_arg(
1348 array(
1349 'utm_source' => 'plugin',
1350 'utm_medium' => 'change_theme_wizard',
1351 ),
1352 $theme['demo_link']
1353 );
1354 } else {
1355 $link = add_query_arg(
1356 array(
1357 'utm_source' => 'plugin',
1358 'utm_medium' => 'new_menu_wizard',
1359 ),
1360 $theme['demo_link']
1361 );
1362 }
1363 ?>
1364 <a href="<?php echo esc_url( $link ); ?>" alt="<?php echo esc_attr( $theme['theme_name'] ); ?>" target="_blank" rel="noopener" class="button"><?php esc_html_e( 'View Demo', 'responsive-menu' ); ?></a>
1365 <?php
1366 }
1367 if ( $in_customizer ) {
1368 ?>
1369 <button class="button btn-blue rmp-theme-apply" theme-name="<?php echo esc_html( $theme['theme_name'] ); ?>" theme-type="downloaded" ><?php esc_html_e( 'Apply', 'responsive-menu' ); ?></button>
1370 <?php
1371 }
1372 ?>
1373 </div>
1374 </div>
1375 </label>
1376 </li>
1377 <?php
1378 }
1379 ?>
1380 </ul>
1381 <?php
1382 }
1383
1384 /**
1385 * Function to returns the available theme list.
1386 *
1387 * @since 4.1.0
1388 * @return HTML|string $html
1389 */
1390 public function get_available_themes_return( $in_customizer = false ) {
1391 $html = '<ul class="rmp_theme_grids">';
1392
1393 if ( ! $in_customizer ) {
1394 $html .= sprintf(
1395 '<li class="rmp_theme_grid_item">
1396 <input type="radio" checked id="default" class="rmp-theme-option" name="menu_theme" value="" theme-type="default"/>
1397 <label class="rmp-item-card default-item" for="default">
1398 <figure class="rmp-item-card_image">
1399 <img src="%1$s" alt="%2$s" loading="lazy"/>
1400 </figure>
1401 <div class="rmp-item-card-backside">
1402 <div class="rmp-item-card_contents">
1403 <h4> %2$s </h4>
1404 </div>
1405 <div class="rmp-item-card_action">
1406 <a href="https://demo.responsive.menu/themes/default-theme/?utm_source=plugin&utm_medium=new_menu_wizard" alt="%2$s" target="_blank" rel="noopener" class="button">%3$s</a>
1407 </div>
1408 </div>
1409 </label>
1410 </li>',
1411 esc_url( $this->theme_preview_img ),
1412 esc_html__( 'Default Theme', 'responsive-menu' ),
1413 esc_html__( 'View Demo', 'responsive-menu' )
1414 );
1415 }
1416
1417 $downloaded_themes = $this->get_themes_from_uploads();
1418 foreach ( $downloaded_themes as $theme ) {
1419 $id = 'rmp-theme-' . preg_replace( '/\s+/', '', $theme['theme_name'] );
1420
1421 $demo_link = '';
1422 if ( ! empty( $theme['demo_link'] ) ) {
1423 if ( $in_customizer ) {
1424 $link = add_query_arg(
1425 array(
1426 'utm_source' => 'plugin',
1427 'utm_medium' => 'change_theme_wizard',
1428 ),
1429 $theme['demo_link']
1430 );
1431 } else {
1432 $link = add_query_arg(
1433 array(
1434 'utm_source' => 'plugin',
1435 'utm_medium' => 'new_menu_wizard',
1436 ),
1437 $theme['demo_link']
1438 );
1439 }
1440
1441 $demo_link = sprintf(
1442 '<a href="%s" alt="%s" target="_blank" rel="noopener" class="button">%s</a>',
1443 esc_url( $link ),
1444 esc_attr( $theme['theme_name'] ),
1445 esc_html__( 'View Demo', 'responsive-menu' )
1446 );
1447 }
1448
1449 $select_option = $apply_button = '';
1450 if ( $in_customizer ) {
1451 $apply_button = sprintf(
1452 '<button class="button btn-blue rmp-theme-apply" theme-name="%s" theme-type="downloaded" >%s</button>',
1453 esc_html( $theme['theme_name'] ),
1454 esc_html__( 'Apply', 'responsive-menu' )
1455 );
1456 } else {
1457 $select_option = sprintf(
1458 '<input type="radio" id="%1$s" theme-type="downloaded" class="rmp-theme-option" name="menu_theme" value="%2$s"/>',
1459 esc_attr( $id ),
1460 esc_html( $theme['theme_name'] )
1461 );
1462 }
1463
1464 $html .= sprintf(
1465 '
1466 <li class="rmp_theme_grid_item">
1467 %5$s
1468 <label class="rmp-item-card" for="%1$s">
1469 <figure class="rmp-item-card_image">
1470 <img src="%3$s" alt="%2$s" loading="lazy"/>
1471 </figure>
1472 <div class="rmp-item-card-backside">
1473 <div class="rmp-item-card_contents">
1474 <h4> %2$s </h4>
1475 </div>
1476 <div class="rmp-item-card_action">
1477 %4$s
1478 %6$s
1479 </div>
1480 </div>
1481 </label>
1482 </li>',
1483 esc_attr( $id ),
1484 esc_html( $theme['theme_name'] ),
1485 esc_url( $theme['theme_preview_url'] ),
1486 $demo_link,
1487 $select_option,
1488 $apply_button
1489 );
1490 }
1491
1492 $html .= '</ul>';
1493
1494 return $html;
1495 }
1496
1497 /**
1498 * Function to upload the theme by ajax.
1499 *
1500 * @since 4.1.0
1501 * @since 4.7.3 Validates the archive contents and requires `install_plugins`.
1502 *
1503 * @return json
1504 */
1505 public function rmp_theme_upload_from_wizard() {
1506
1507 // Check nonce to verify the authenticate upload file.
1508 check_ajax_referer( 'rmp_nonce', 'ajax_nonce' );
1509
1510 // Upload the file in upload directory.
1511 status_header( 200 );
1512
1513 $uploaded = $this->process_theme_upload();
1514
1515 if ( is_wp_error( $uploaded ) ) {
1516 wp_send_json_error(
1517 array( 'message' => $uploaded->get_error_message() )
1518 );
1519 }
1520
1521 // Return the response
1522 wp_send_json_success(
1523 array(
1524 'message' => esc_html__( 'Theme is uploaded successfully', 'responsive-menu' ),
1525 'html' => $this->get_available_themes_return( $this->is_customizer() ),
1526 )
1527 );
1528 }
1529
1530 /**
1531 * Function to update the theme api cached data.
1532 *
1533 * @since 4.1.0
1534 *
1535 * @return json
1536 */
1537 public function update_theme_api_cache() {
1538
1539 // Check nonce to verify the authenticate upload file.
1540 check_ajax_referer( 'rmp_nonce', 'ajax_nonce' );
1541
1542 return wp_send_json_success(
1543 array(
1544 'message' => esc_html__( 'Cache data updated !', 'responsive-menu' ),
1545 'html' => $this->get_themes_from_theme_store( $this->is_customizer() ),
1546 )
1547 );
1548 }
1549
1550 /**
1551 * Function to check the request origin either from customizer or create menu page.
1552 *
1553 * @since 4.1.3
1554 *
1555 * @return bool
1556 */
1557 public function is_customizer() {
1558 $is_customizer_request = false;
1559 if ( ! empty( $_SERVER['HTTP_REFERER'] ) ) {
1560 wp_parse_str( wp_parse_url( esc_url_raw( wp_unslash( $_SERVER['HTTP_REFERER'] ) ) )['query'], $params );
1561 if ( ! empty( $params['action'] ) && ! empty( $params['editor'] ) ) {
1562 $is_customizer_request = true;
1563 }
1564 }
1565
1566 return $is_customizer_request;
1567 }
1568 }
1569