1
0

admin-options.class.php 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726
  1. <?php if ( ! defined( 'ABSPATH' ) ) { die; } // Cannot access directly.
  2. /**
  3. *
  4. * Options Class
  5. *
  6. * @since 1.0.0
  7. * @version 1.0.0
  8. *
  9. */
  10. if ( ! class_exists( 'CSF_Options' ) ) {
  11. class CSF_Options extends CSF_Abstract {
  12. // constans
  13. public $unique = '';
  14. public $notice = '';
  15. public $abstract = 'options';
  16. public $sections = array();
  17. public $options = array();
  18. public $errors = array();
  19. public $pre_tabs = array();
  20. public $pre_fields = array();
  21. public $pre_sections = array();
  22. public $args = array(
  23. // framework title
  24. 'framework_title' => '<img src="https://s.nmxc.ltd/sakurairo_vision/@2.5/series/login_logo.webp" style="width:50px;height:50px;transform:translateY(32%);margin-right: 13px;">iro 主题设置 <small> / Theme Options / テーマの設定</small>',
  25. 'framework_class' => '',
  26. // menu settings
  27. 'menu_title' => '',
  28. 'menu_slug' => '',
  29. 'menu_type' => 'menu',
  30. 'menu_capability' => 'manage_options',
  31. 'menu_icon' => null,
  32. 'menu_position' => null,
  33. 'menu_hidden' => false,
  34. 'menu_parent' => '',
  35. 'sub_menu_title' => '',
  36. // menu extras
  37. 'show_bar_menu' => true,
  38. 'show_sub_menu' => true,
  39. 'show_in_network' => true,
  40. 'show_in_customizer' => false,
  41. 'show_search' => true,
  42. 'show_reset_all' => true,
  43. 'show_reset_section' => true,
  44. 'show_footer' => true,
  45. 'show_all_options' => true,
  46. 'show_form_warning' => true,
  47. 'sticky_header' => true,
  48. 'save_defaults' => true,
  49. 'ajax_save' => true,
  50. 'form_action' => '',
  51. // admin bar menu settings
  52. 'admin_bar_menu_icon' => '',
  53. 'admin_bar_menu_priority' => 50,
  54. // footer
  55. 'footer_text' => 'Sakurairo 使用 Fuukei 定制的 <a href="https://github.com/Fuukei/Sakurairo_CSF" target="_blank">CSF</a> 设置框架,感谢你的使用 0v0',
  56. 'footer_after' => '',
  57. 'footer_credit' => '',
  58. // database model
  59. 'database' => '', // options, transient, theme_mod, network
  60. 'transient_time' => 0,
  61. // contextual help
  62. 'contextual_help' => array(),
  63. 'contextual_help_sidebar' => '',
  64. // typography options
  65. 'enqueue_webfont' => true,
  66. 'async_webfont' => false,
  67. // others
  68. 'output_css' => true,
  69. // theme
  70. 'nav' => 'normal',
  71. 'theme' => 'light',
  72. 'class' => '',
  73. // external default values
  74. 'defaults' => array(),
  75. );
  76. // run framework construct
  77. public function __construct( $key, $params = array() ) {
  78. $this->unique = $key;
  79. $this->args = apply_filters( "csf_{$this->unique}_args", wp_parse_args( $params['args'], $this->args ), $this );
  80. $this->sections = apply_filters( "csf_{$this->unique}_sections", $params['sections'], $this );
  81. // run only is admin panel options, avoid performance loss
  82. $this->pre_tabs = $this->pre_tabs( $this->sections );
  83. $this->pre_fields = $this->pre_fields( $this->sections );
  84. $this->pre_sections = $this->pre_sections( $this->sections );
  85. $this->get_options();
  86. $this->set_options();
  87. $this->save_defaults();
  88. add_action( 'admin_menu', array( &$this, 'add_admin_menu' ) );
  89. add_action( 'admin_bar_menu', array( &$this, 'add_admin_bar_menu' ), $this->args['admin_bar_menu_priority'] );
  90. add_action( 'wp_ajax_csf_'. $this->unique .'_ajax_save', array( &$this, 'ajax_save' ) );
  91. if ( $this->args['database'] === 'network' && ! empty( $this->args['show_in_network'] ) ) {
  92. add_action( 'network_admin_menu', array( &$this, 'add_admin_menu' ) );
  93. }
  94. // wp enqeueu for typography and output css
  95. parent::__construct();
  96. }
  97. // instance
  98. public static function instance( $key, $params = array() ) {
  99. return new self( $key, $params );
  100. }
  101. public function pre_tabs( $sections ) {
  102. $result = array();
  103. $parents = array();
  104. $count = 100;
  105. foreach ( $sections as $key => $section ) {
  106. if ( ! empty( $section['parent'] ) ) {
  107. $section['priority'] = ( isset( $section['priority'] ) ) ? $section['priority'] : $count;
  108. $parents[$section['parent']][] = $section;
  109. unset( $sections[$key] );
  110. }
  111. $count++;
  112. }
  113. foreach ( $sections as $key => $section ) {
  114. $section['priority'] = ( isset( $section['priority'] ) ) ? $section['priority'] : $count;
  115. if ( ! empty( $section['id'] ) && ! empty( $parents[$section['id']] ) ) {
  116. $section['subs'] = wp_list_sort( $parents[$section['id']], array( 'priority' => 'ASC' ), 'ASC', true );
  117. }
  118. $result[] = $section;
  119. $count++;
  120. }
  121. return wp_list_sort( $result, array( 'priority' => 'ASC' ), 'ASC', true );
  122. }
  123. public function pre_fields( $sections ) {
  124. $result = array();
  125. foreach ( $sections as $key => $section ) {
  126. if ( ! empty( $section['fields'] ) ) {
  127. foreach ( $section['fields'] as $field ) {
  128. $result[] = $field;
  129. }
  130. }
  131. }
  132. return $result;
  133. }
  134. public function pre_sections( $sections ) {
  135. $result = array();
  136. foreach ( $this->pre_tabs as $tab ) {
  137. if ( ! empty( $tab['subs'] ) ) {
  138. foreach ( $tab['subs'] as $sub ) {
  139. $sub['ptitle'] = $tab['title'];
  140. $result[] = $sub;
  141. }
  142. }
  143. if ( empty( $tab['subs'] ) ) {
  144. $result[] = $tab;
  145. }
  146. }
  147. return $result;
  148. }
  149. // add admin bar menu
  150. public function add_admin_bar_menu( $wp_admin_bar ) {
  151. if ( ! current_user_can( $this->args['menu_capability'] ) ) {
  152. return;
  153. }
  154. if ( is_network_admin() && ( $this->args['database'] !== 'network' || $this->args['show_in_network'] !== true ) ) {
  155. return;
  156. }
  157. if ( ! empty( $this->args['show_bar_menu'] ) && empty( $this->args['menu_hidden'] ) ) {
  158. global $submenu;
  159. $menu_slug = $this->args['menu_slug'];
  160. $menu_icon = ( ! empty( $this->args['admin_bar_menu_icon'] ) ) ? '<span class="csf-ab-icon ab-icon '. esc_attr( $this->args['admin_bar_menu_icon'] ) .'"></span>' : '';
  161. $wp_admin_bar->add_node( array(
  162. 'id' => $menu_slug,
  163. 'title' => $menu_icon . esc_attr( $this->args['menu_title'] ),
  164. 'href' => esc_url( ( is_network_admin() ) ? network_admin_url( 'admin.php?page='. $menu_slug ) : admin_url( 'admin.php?page='. $menu_slug ) ),
  165. ) );
  166. if ( ! empty( $submenu[$menu_slug] ) ) {
  167. foreach ( $submenu[$menu_slug] as $menu_key => $menu_value ) {
  168. $wp_admin_bar->add_node( array(
  169. 'parent' => $menu_slug,
  170. 'id' => $menu_slug .'-'. $menu_key,
  171. 'title' => $menu_value[0],
  172. 'href' => esc_url( ( is_network_admin() ) ? network_admin_url( 'admin.php?page='. $menu_value[2] ) : admin_url( 'admin.php?page='. $menu_value[2] ) ),
  173. ) );
  174. }
  175. }
  176. }
  177. }
  178. public function ajax_save() {
  179. $result = $this->set_options( true );
  180. if ( ! $result ) {
  181. wp_send_json_error( array( 'error' => esc_html__( 'Error while saving the changes.', 'sakurairo_csf' ) ) );
  182. } else {
  183. wp_send_json_success( array( 'notice' => $this->notice, 'errors' => $this->errors ) );
  184. }
  185. }
  186. // get default value
  187. public function get_default( $field ) {
  188. $default = ( isset( $field['default'] ) ) ? $field['default'] : '';
  189. $default = ( isset( $this->args['defaults'][$field['id']] ) ) ? $this->args['defaults'][$field['id']] : $default;
  190. return $default;
  191. }
  192. // save defaults and set new fields value to main options
  193. public function save_defaults() {
  194. $tmp_options = $this->options;
  195. foreach ( $this->pre_fields as $field ) {
  196. if ( ! empty( $field['id'] ) ) {
  197. $this->options[$field['id']] = ( isset( $this->options[$field['id']] ) ) ? $this->options[$field['id']] : $this->get_default( $field );
  198. }
  199. }
  200. if ( $this->args['save_defaults'] && empty( $tmp_options ) ) {
  201. $this->save_options( $this->options );
  202. }
  203. }
  204. // set options
  205. public function set_options( $ajax = false ) {
  206. // XSS ok.
  207. // No worries, This "POST" requests is sanitizing in the below foreach. see #L337 - #L341
  208. $response = ( $ajax && ! empty( $_POST['data'] ) ) ? json_decode( wp_unslash( trim( $_POST['data'] ) ), true ) : $_POST;
  209. // Set variables.
  210. $data = array();
  211. $noncekey = 'csf_options_nonce'. $this->unique;
  212. $nonce = ( ! empty( $response[$noncekey] ) ) ? $response[$noncekey] : '';
  213. $options = ( ! empty( $response[$this->unique] ) ) ? $response[$this->unique] : array();
  214. $transient = ( ! empty( $response['csf_transient'] ) ) ? $response['csf_transient'] : array();
  215. if ( wp_verify_nonce( $nonce, 'csf_options_nonce' ) ) {
  216. $importing = false;
  217. $section_id = ( ! empty( $transient['section'] ) ) ? $transient['section'] : '';
  218. if ( ! $ajax && ! empty( $response[ 'csf_import_data' ] ) ) {
  219. // XSS ok.
  220. // No worries, This "POST" requests is sanitizing in the below foreach. see #L337 - #L341
  221. $import_data = json_decode( wp_unslash( trim( $response[ 'csf_import_data' ] ) ), true );
  222. $options = ( is_array( $import_data ) && ! empty( $import_data ) ) ? $import_data : array();
  223. $importing = true;
  224. $this->notice = esc_html__( 'Settings successfully imported.', 'sakurairo_csf' );
  225. }
  226. if ( ! empty( $transient['reset'] ) ) {
  227. foreach ( $this->pre_fields as $field ) {
  228. if ( ! empty( $field['id'] ) ) {
  229. $data[$field['id']] = $this->get_default( $field );
  230. }
  231. }
  232. $this->notice = esc_html__( 'Default settings restored.', 'sakurairo_csf' );
  233. } else if ( ! empty( $transient['reset_section'] ) && ! empty( $section_id ) ) {
  234. if ( ! empty( $this->pre_sections[$section_id-1]['fields'] ) ) {
  235. foreach ( $this->pre_sections[$section_id-1]['fields'] as $field ) {
  236. if ( ! empty( $field['id'] ) ) {
  237. $data[$field['id']] = $this->get_default( $field );
  238. }
  239. }
  240. }
  241. $data = wp_parse_args( $data, $this->options );
  242. $this->notice = esc_html__( 'Default settings restored.', 'sakurairo_csf' );
  243. } else {
  244. // sanitize and validate
  245. foreach ( $this->pre_fields as $field ) {
  246. if ( ! empty( $field['id'] ) ) {
  247. $field_id = $field['id'];
  248. $field_value = isset( $options[$field_id] ) ? $options[$field_id] : '';
  249. // Ajax and Importing doing wp_unslash already.
  250. if ( ! $ajax && ! $importing ) {
  251. $field_value = wp_unslash( $field_value );
  252. }
  253. // Sanitize "post" request of field.
  254. if ( ! isset( $field['sanitize'] ) ) {
  255. if( is_array( $field_value ) ) {
  256. $data[$field_id] = wp_kses_post_deep( $field_value );
  257. } else {
  258. $data[$field_id] = wp_kses_post( $field_value );
  259. }
  260. } else if( isset( $field['sanitize'] ) && is_callable( $field['sanitize'] ) ) {
  261. $data[$field_id] = call_user_func( $field['sanitize'], $field_value );
  262. } else {
  263. $data[$field_id] = $field_value;
  264. }
  265. // Validate "post" request of field.
  266. if ( isset( $field['validate'] ) && is_callable( $field['validate'] ) ) {
  267. $has_validated = call_user_func( $field['validate'], $field_value );
  268. if ( ! empty( $has_validated ) ) {
  269. $data[$field_id] = ( isset( $this->options[$field_id] ) ) ? $this->options[$field_id] : '';
  270. $this->errors[$field_id] = $has_validated;
  271. }
  272. }
  273. }
  274. }
  275. }
  276. $data = apply_filters( "csf_{$this->unique}_save", $data, $this );
  277. do_action( "csf_{$this->unique}_save_before", $data, $this );
  278. $this->options = $data;
  279. $this->save_options( $data );
  280. do_action( "csf_{$this->unique}_save_after", $data, $this );
  281. if ( empty( $this->notice ) ) {
  282. $this->notice = esc_html__( 'Settings saved.', 'sakurairo_csf' );
  283. }
  284. return true;
  285. }
  286. return false;
  287. }
  288. // save options database
  289. public function save_options( $data ) {
  290. if ( $this->args['database'] === 'transient' ) {
  291. set_transient( $this->unique, $data, $this->args['transient_time'] );
  292. } else if ( $this->args['database'] === 'theme_mod' ) {
  293. set_theme_mod( $this->unique, $data );
  294. } else if ( $this->args['database'] === 'network' ) {
  295. update_site_option( $this->unique, $data );
  296. } else {
  297. update_option( $this->unique, $data );
  298. }
  299. do_action( "csf_{$this->unique}_saved", $data, $this );
  300. }
  301. // get options from database
  302. public function get_options() {
  303. if ( $this->args['database'] === 'transient' ) {
  304. $this->options = get_transient( $this->unique );
  305. } else if ( $this->args['database'] === 'theme_mod' ) {
  306. $this->options = get_theme_mod( $this->unique );
  307. } else if ( $this->args['database'] === 'network' ) {
  308. $this->options = get_site_option( $this->unique );
  309. } else {
  310. $this->options = get_option( $this->unique );
  311. }
  312. if ( empty( $this->options ) ) {
  313. $this->options = array();
  314. }
  315. return $this->options;
  316. }
  317. // admin menu
  318. public function add_admin_menu() {
  319. extract( $this->args );
  320. if ( $menu_type === 'submenu' ) {
  321. $menu_page = call_user_func( 'add_submenu_page', $menu_parent, esc_attr( $menu_title ), esc_attr( $menu_title ), $menu_capability, $menu_slug, array( &$this, 'add_options_html' ) );
  322. } else {
  323. $menu_page = call_user_func( 'add_menu_page', esc_attr( $menu_title ), esc_attr( $menu_title ), $menu_capability, $menu_slug, array( &$this, 'add_options_html' ), $menu_icon, $menu_position );
  324. if ( ! empty( $sub_menu_title ) ) {
  325. call_user_func( 'add_submenu_page', $menu_slug, esc_attr( $sub_menu_title ), esc_attr( $sub_menu_title ), $menu_capability, $menu_slug, array( &$this, 'add_options_html' ) );
  326. }
  327. if ( ! empty( $this->args['show_sub_menu'] ) && count( $this->pre_tabs ) > 1 ) {
  328. // create submenus
  329. foreach ( $this->pre_tabs as $section ) {
  330. call_user_func( 'add_submenu_page', $menu_slug, esc_attr( $section['title'] ), esc_attr( $section['title'] ), $menu_capability, $menu_slug .'#tab='. sanitize_title( $section['title'] ), '__return_null' );
  331. }
  332. remove_submenu_page( $menu_slug, $menu_slug );
  333. }
  334. if ( ! empty( $menu_hidden ) ) {
  335. remove_menu_page( $menu_slug );
  336. }
  337. }
  338. add_action( 'load-'. $menu_page, array( &$this, 'add_page_on_load' ) );
  339. }
  340. public function add_page_on_load() {
  341. if ( ! empty( $this->args['contextual_help'] ) ) {
  342. $screen = get_current_screen();
  343. foreach ( $this->args['contextual_help'] as $tab ) {
  344. $screen->add_help_tab( $tab );
  345. }
  346. if ( ! empty( $this->args['contextual_help_sidebar'] ) ) {
  347. $screen->set_help_sidebar( $this->args['contextual_help_sidebar'] );
  348. }
  349. }
  350. if ( ! empty( $this->args['footer_credit'] ) ) {
  351. add_filter( 'admin_footer_text', array( $this, 'add_admin_footer_text' ) );
  352. }
  353. }
  354. public function add_admin_footer_text() {
  355. echo wp_kses_post( $this->args['footer_credit'] );
  356. }
  357. public function error_check( $sections, $err = '' ) {
  358. if ( ! $this->args['ajax_save'] ) {
  359. if ( ! empty( $sections['fields'] ) ) {
  360. foreach ( $sections['fields'] as $field ) {
  361. if ( ! empty( $field['id'] ) ) {
  362. if ( array_key_exists( $field['id'], $this->errors ) ) {
  363. $err = '<span class="csf-label-error">!</span>';
  364. }
  365. }
  366. }
  367. }
  368. if ( ! empty( $sections['subs'] ) ) {
  369. foreach ( $sections['subs'] as $sub ) {
  370. $err = $this->error_check( $sub, $err );
  371. }
  372. }
  373. if ( ! empty( $sections['id'] ) && array_key_exists( $sections['id'], $this->errors ) ) {
  374. $err = $this->errors[$sections['id']];
  375. }
  376. }
  377. return $err;
  378. }
  379. // option page html output
  380. public function add_options_html() {
  381. $has_nav = count( $this->pre_tabs ) > 1;
  382. $show_all = ( ! $has_nav ) ? ' csf-show-all' : '';
  383. $ajax_class = ( $this->args['ajax_save'] ) ? ' csf-save-ajax' : '';
  384. $sticky_class = ( $this->args['sticky_header'] ) ? ' csf-sticky-header' : '';
  385. $wrapper_class = ( $this->args['framework_class'] ) ? ' '. $this->args['framework_class'] : '';
  386. $theme = ( $this->args['theme'] ) ? ' csf-theme-'. $this->args['theme'] : '';
  387. $class = ( $this->args['class'] ) ? ' '. $this->args['class'] : '';
  388. $nav_type = ( $this->args['nav'] === 'inline' ) ? 'inline' : 'normal';
  389. $form_action = ( $this->args['form_action'] ) ?: '';
  390. do_action( 'csf_options_before' );
  391. echo '<div class="csf csf-options'. esc_attr( $theme . $class . $wrapper_class ) .'" data-slug="'. esc_attr( $this->args['menu_slug'] ) .'" data-unique="'. esc_attr( $this->unique ) .'">';
  392. echo '<div class="csf-container">';
  393. echo '<form method="post" action="'. esc_attr( $form_action ) .'" enctype="multipart/form-data" id="csf-form" autocomplete="off" novalidate="novalidate">';
  394. echo '<input type="hidden" class="csf-section-id" name="csf_transient[section]" value="1">';
  395. wp_nonce_field( 'csf_options_nonce', 'csf_options_nonce'. $this->unique );
  396. echo '<div class="csf-header'. esc_attr( $sticky_class ) .'">';
  397. echo '<div class="csf-header-inner">';
  398. echo '<div class="csf-header-left">';
  399. echo '<h1>'. $this->args['framework_title'] .'</h1>';
  400. echo '</div>';
  401. echo '<div class="csf-header-right">';
  402. $notice_class = ( ! empty( $this->notice ) ) ? 'csf-form-show' : '';
  403. $notice_text = ( ! empty( $this->notice ) ) ? $this->notice : '';
  404. echo '<div class="csf-form-result csf-form-success '. esc_attr( $notice_class ) .'">'. $notice_text .'</div>';
  405. echo ( $this->args['show_form_warning'] ) ? '<div class="csf-form-result csf-form-warning">'. esc_html__( 'You have unsaved changes, save your changes!', 'sakurairo_csf' ) .'</div>' : '';
  406. echo ( $has_nav && $this->args['show_all_options'] ) ? '<div class="csf-expand-all" title="'. esc_html__( 'show all settings', 'sakurairo_csf' ) .'"><i class="fas fa-outdent"></i></div>' : '';
  407. echo ( $this->args['show_search'] ) ? '<div class="csf-search"><input type="text" name="csf-search" placeholder="'. esc_html__( 'Search...', 'sakurairo_csf' ) .'" autocomplete="off" /></div>' : '';
  408. echo '<div class="csf-buttons">';
  409. echo '<input type="submit" name="'. esc_attr( $this->unique ) .'[_nonce][save]" class="button button-primary csf-top-save csf-save'. esc_attr( $ajax_class ) .'" value="'. esc_html__( 'Save', 'sakurairo_csf' ) .'" data-save="'. esc_html__( 'Saving...', 'sakurairo_csf' ) .'">';
  410. echo ( $this->args['show_reset_section'] ) ? '<input type="submit" name="csf_transient[reset_section]" class="button button-secondary csf-reset-section csf-confirm" value="'. esc_html__( 'Reset Section', 'sakurairo_csf' ) .'" data-confirm="'. esc_html__( 'Are you sure to reset this section options?', 'sakurairo_csf' ) .'">' : '';
  411. echo ( $this->args['show_reset_all'] ) ? '<input type="submit" name="csf_transient[reset]" class="button csf-warning-primary csf-reset-all csf-confirm" value="'. ( ( $this->args['show_reset_section'] ) ? esc_html__( 'Reset All', 'sakurairo_csf' ) : esc_html__( 'Reset', 'sakurairo_csf' ) ) .'" data-confirm="'. esc_html__( 'Are you sure you want to reset all settings to default values?', 'sakurairo_csf' ) .'">' : '';
  412. echo '</div>';
  413. echo '</div>';
  414. echo '<div class="clear"></div>';
  415. echo '</div>';
  416. echo '</div>';
  417. echo '<div class="csf-wrapper'. esc_attr( $show_all ) .'">';
  418. if ( $has_nav ) {
  419. echo '<div class="csf-nav csf-nav-'. esc_attr( $nav_type ) .' csf-nav-options">';
  420. echo '<ul>';
  421. foreach ( $this->pre_tabs as $tab ) {
  422. $tab_id = sanitize_title( $tab['title'] );
  423. $tab_error = $this->error_check( $tab );
  424. $tab_icon = ( ! empty( $tab['icon'] ) ) ? '<i class="csf-tab-icon '. esc_attr( $tab['icon'] ) .'"></i>' : '';
  425. if ( ! empty( $tab['subs'] ) ) {
  426. echo '<li class="csf-tab-item">';
  427. echo '<a href="#tab='. esc_attr( $tab_id ) .'" data-tab-id="'. esc_attr( $tab_id ) .'" class="csf-arrow">'. $tab_icon . $tab['title'] . $tab_error .'</a>';
  428. echo '<ul>';
  429. foreach ( $tab['subs'] as $sub ) {
  430. $sub_id = $tab_id .'/'. sanitize_title( $sub['title'] );
  431. $sub_error = $this->error_check( $sub );
  432. $sub_icon = ( ! empty( $sub['icon'] ) ) ? '<i class="csf-tab-icon '. esc_attr( $sub['icon'] ) .'"></i>' : '';
  433. echo '<li><a href="#tab='. esc_attr( $sub_id ) .'" data-tab-id="'. esc_attr( $sub_id ) .'">'. $sub_icon . $sub['title'] . $sub_error .'</a></li>';
  434. }
  435. echo '</ul>';
  436. echo '</li>';
  437. } else {
  438. echo '<li class="csf-tab-item"><a href="#tab='. esc_attr( $tab_id ) .'" data-tab-id="'. esc_attr( $tab_id ) .'">'. $tab_icon . $tab['title'] . $tab_error .'</a></li>';
  439. }
  440. }
  441. echo '</ul>';
  442. echo '</div>';
  443. }
  444. echo '<div class="csf-content">';
  445. echo '<div class="csf-sections">';
  446. foreach ( $this->pre_sections as $section ) {
  447. $section_onload = ( ! $has_nav ) ? ' csf-onload' : '';
  448. $section_class = ( ! empty( $section['class'] ) ) ? ' '. $section['class'] : '';
  449. $section_icon = ( ! empty( $section['icon'] ) ) ? '<i class="csf-section-icon '. esc_attr( $section['icon'] ) .'"></i>' : '';
  450. $section_title = ( ! empty( $section['title'] ) ) ? $section['title'] : '';
  451. $section_parent = ( ! empty( $section['ptitle'] ) ) ? sanitize_title( $section['ptitle'] ) .'/' : '';
  452. $section_slug = ( ! empty( $section['title'] ) ) ? sanitize_title( $section_title ) : '';
  453. echo '<div class="csf-section hidden'. esc_attr( $section_onload . $section_class ) .'" data-section-id="'. esc_attr( $section_parent . $section_slug ) .'">';
  454. echo ( $has_nav ) ? '<div class="csf-section-title"><h3>'. $section_icon . $section_title .'</h3></div>' : '';
  455. echo ( ! empty( $section['description'] ) ) ? '<div class="csf-field csf-section-description">'. $section['description'] .'</div>' : '';
  456. if ( ! empty( $section['fields'] ) ) {
  457. foreach ( $section['fields'] as $field ) {
  458. $is_field_error = $this->error_check( $field );
  459. if ( ! empty( $is_field_error ) ) {
  460. $field['_error'] = $is_field_error;
  461. }
  462. if ( ! empty( $field['id'] ) ) {
  463. $field['default'] = $this->get_default( $field );
  464. }
  465. $value = ( ! empty( $field['id'] ) && isset( $this->options[$field['id']] ) ) ? $this->options[$field['id']] : '';
  466. Sakurairo_CSF::field( $field, $value, $this->unique, 'options' );
  467. }
  468. } else {
  469. echo '<div class="csf-no-option">'. esc_html__( 'No data available.', 'sakurairo_csf' ) .'</div>';
  470. }
  471. echo '</div>';
  472. }
  473. echo '</div>';
  474. echo '<div class="clear"></div>';
  475. echo '</div>';
  476. echo ( $has_nav && $nav_type === 'normal' ) ? '<div class="csf-nav-background"></div>' : '';
  477. echo '</div>';
  478. if ( ! empty( $this->args['show_footer'] ) ) {
  479. echo '<div class="csf-footer">';
  480. echo '<div class="csf-buttons">';
  481. echo '<input type="submit" name="csf_transient[save]" class="button button-primary csf-save'. esc_attr( $ajax_class ) .'" value="'. esc_html__( 'Save', 'sakurairo_csf' ) .'" data-save="'. esc_html__( 'Saving...', 'sakurairo_csf' ) .'">';
  482. echo ( $this->args['show_reset_section'] ) ? '<input type="submit" name="csf_transient[reset_section]" class="button button-secondary csf-reset-section csf-confirm" value="'. esc_html__( 'Reset Section', 'sakurairo_csf' ) .'" data-confirm="'. esc_html__( 'Are you sure to reset this section options?', 'sakurairo_csf' ) .'">' : '';
  483. echo ( $this->args['show_reset_all'] ) ? '<input type="submit" name="csf_transient[reset]" class="button csf-warning-primary csf-reset-all csf-confirm" value="'. ( ( $this->args['show_reset_section'] ) ? esc_html__( 'Reset All', 'sakurairo_csf' ) : esc_html__( 'Reset', 'sakurairo_csf' ) ) .'" data-confirm="'. esc_html__( 'Are you sure you want to reset all settings to default values?', 'sakurairo_csf' ) .'">' : '';
  484. echo '</div>';
  485. echo ( ! empty( $this->args['footer_text'] ) ) ? '<div class="csf-copyright">'. $this->args['footer_text'] .'</div>' : '';
  486. echo '<div class="clear"></div>';
  487. echo '</div>';
  488. }
  489. echo '</form>';
  490. echo '</div>';
  491. echo '<div class="clear"></div>';
  492. echo ( ! empty( $this->args['footer_after'] ) ) ? $this->args['footer_after'] : '';
  493. echo '</div>';
  494. do_action( 'csf_options_after' );
  495. }
  496. }
  497. }