class-vamtam-customizer.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453
  1. <?php
  2. class Vamtam_Customizer {
  3. private static $instance;
  4. private $sections;
  5. private $args;
  6. protected $class_from_type_cache = array();
  7. private $controls;
  8. public static $version = '1.0.0';
  9. public static function get_instance() {
  10. if ( ! isset( self::$instance ) ) {
  11. self::$instance = new self();
  12. }
  13. return self::$instance;
  14. }
  15. public function __construct( $args = array() ) {
  16. // Autoload classes on demand
  17. if ( function_exists( '__autoload' ) )
  18. spl_autoload_register( '__autoload' );
  19. spl_autoload_register( array( $this, 'autoload' ) );
  20. $this->args = $args;
  21. $this->sections = array();
  22. $this->dir = plugin_dir_path( __FILE__ );
  23. $this->class_from_type_cache = array();
  24. add_action( 'customize_register', array( $this, 'customize_register' ), 5 );
  25. add_action( 'customize_save_after', array( $this, 'customize_save_after' ) );
  26. add_action( 'after_setup_theme', array( $this, 'setup_options' ), 5 );
  27. add_action( 'wp_ajax_vamtam-customizer-control', array( $this, 'control_ajax' ) );
  28. add_action( 'customize_controls_enqueue_scripts', array( $this, 'customize_controls_enqueue_scripts' ), 7 );
  29. }
  30. public function customize_controls_enqueue_scripts() {
  31. }
  32. public function customize_save_after( $wp_customize ) {
  33. global $vamtam_fonts;
  34. $fonts_by_family = vamtam_get_fonts_by_family();
  35. $google_fonts = array();
  36. $customized = json_decode( stripslashes_deep( $_POST['customized'] ), true );
  37. $fields = $this->get_fields_by_id();
  38. $options = $this->get_options();
  39. $compiler = false;
  40. foreach ( $fields as $id => $field ) {
  41. $full_id = $this->args['opt_name'] . '[' . $id . ']';
  42. // cache google fonts, so we can just load them later
  43. if ( 'typography' === $field['type'] ) {
  44. $font_id = $fonts_by_family[ $options[ $id ]['font-family'] ];
  45. $font = $vamtam_fonts[ $font_id ];
  46. if ( isset( $font['gf'] ) && $font['gf'] ) {
  47. $google_fonts[ $font_id ][] = isset( $options[ $id ]['variant'] ) ? $options[ $id ]['variant'] : 'normal';
  48. }
  49. }
  50. // if a compiler option was changed
  51. if ( isset( $customized[ $full_id ] ) && ( isset( $field['compiler'] ) && $field['compiler'] ) ) {
  52. $compiler = true;
  53. }
  54. }
  55. $options['google_fonts'] = self::build_google_fonts_url( $google_fonts, $options['gfont-subsets'] );
  56. $this->set_options( $options );
  57. if ( $compiler ) {
  58. do_action( "vamtam_customizer/{$this->args['opt_name']}/compiler", $options );
  59. }
  60. do_action( 'vamtam_saved_options' );
  61. }
  62. public static function build_google_fonts_url( $google_fonts, $subsets ) {
  63. $font_imports_url = '';
  64. if ( is_array( $google_fonts ) && count( $google_fonts ) ) {
  65. $param = array();
  66. foreach ( $google_fonts as $font => $weights ) {
  67. $weights[] = 'bold'; // always include a bold version, if available
  68. $weights = str_replace( ' ', '', implode( ',', array_unique( $weights ) ) );
  69. $param[] = urlencode( $font ) . ':' . $weights;
  70. }
  71. $param = implode( '|', $param );
  72. $subsets_filtered = array();
  73. foreach ( $subsets as $subset => $set ) {
  74. if ( $set === '1' ) {
  75. $subsets_filtered[] = $subset;
  76. }
  77. }
  78. $subsets = implode( ',', $subsets_filtered );
  79. $font_imports_url = 'https://fonts.googleapis.com/css?family=' . $param . '&subset=' . $subsets;
  80. }
  81. return $font_imports_url;
  82. }
  83. public function autoload( $class ) {
  84. $file = 'class-' . str_replace( '_', '-', strtolower( $class ) ) . '.php';
  85. if ( is_readable( $this->dir . $file ) ) {
  86. include_once $this->dir . $file;
  87. return;
  88. }
  89. if ( is_readable( $this->dir . 'controls/' . $file ) ) {
  90. include_once $this->dir . 'controls/' . $file;
  91. return;
  92. }
  93. if ( is_readable( $this->dir . 'ajax/' . $file ) ) {
  94. include_once $this->dir . 'ajax/' . $file;
  95. return;
  96. }
  97. }
  98. public function control_ajax() {
  99. // ucwords $delimiter param was added in 5.4.32, 5.5.16
  100. $type = str_replace( ' ', '_', ucwords( str_replace( '-', ' ', $_POST['control'] ) ) );
  101. $class = 'Vamtam_Customize_' . $type . '_Ajax';
  102. if ( class_exists( $class ) ) {
  103. $ajax_obj = new $class( $this->get_options() );
  104. $method = array( $ajax_obj, 'ajax_' . $_POST['method'] );
  105. if ( is_callable( $method ) ) {
  106. call_user_func( $method );
  107. exit;
  108. }
  109. }
  110. header( 'Content-type: application/json' );
  111. echo json_encode( array(
  112. 'error' => 'cannot call ' . $class . '::ajax_' . $_POST['method'],
  113. ) );
  114. exit;
  115. }
  116. public function get_arg( $key ) {
  117. return $this->args[ $key ];
  118. }
  119. public function setup_options() {
  120. if ( ! isset( $GLOBALS[ $this->args['opt_name'] ] ) ) {
  121. $GLOBALS[ $this->args['opt_name'] ] = $this->get_options();
  122. if ( is_customize_preview() && isset( $_POST['customized'] ) ) {
  123. $raw_options = json_decode( stripslashes_deep( $_POST['customized'] ), true );
  124. if ( ! empty( $raw_options ) ) {
  125. if ( is_array( $raw_options ) ) {
  126. foreach ( $raw_options as $key => $value ) {
  127. if ( strpos( $key, $this->args['opt_name'] ) !== false ) {
  128. $key = str_replace( $this->args['opt_name'] . '[', '', rtrim( $key, ']' ) );
  129. $GLOBALS[ $this->args['opt_name'] ][ $key ] = $value;
  130. }
  131. }
  132. }
  133. }
  134. }
  135. }
  136. }
  137. public function get_options() {
  138. $options = get_option( $this->args['opt_name'] );
  139. if ( false === $options ) {
  140. $options = array();
  141. }
  142. return wp_parse_args( $options, $this->get_defaults() );
  143. }
  144. public function get_defaults() {
  145. $options = array();
  146. $fields = $this->get_fields_by_id();
  147. foreach ( $fields as $id => $field ) {
  148. if ( isset( $field['default'] ) ) {
  149. $options[ $id ] = $field['default'];
  150. }
  151. }
  152. return $options;
  153. }
  154. public function set_options( $options ) {
  155. return update_option( $this->args['opt_name'], $options );
  156. }
  157. public function add_section( array $args ) {
  158. $top_level_sections = count( $this->sections );
  159. if ( isset( $args['fields'] ) ) {
  160. $args['fields'] = apply_filters( 'vamtam_customizer_fields_options', $args['fields'] );
  161. }
  162. $args['permissions'] = isset( $args['permissions'] ) ? $args['permissions'] : 'edit_theme_options';
  163. if ( isset( $args['subsection'] ) && $args['subsection'] && $top_level_sections > 0 ) {
  164. $this->sections[ $top_level_sections - 1 ]['children'][] = $args;
  165. } else {
  166. $args['children'] = array();
  167. $this->sections[] = $args;
  168. }
  169. }
  170. public function get_sections() {
  171. return $this->sections;
  172. }
  173. public function get_fields_by_id() {
  174. if ( ! isset( $this->fields_by_id ) ) {
  175. $this->fields_by_id = array();
  176. foreach ( $this->sections as $section ) {
  177. if ( count( $section['children'] ) > 0 ) {
  178. foreach ( $section['children'] as $child ) {
  179. foreach ( $child['fields'] as $field ) {
  180. $this->fields_by_id[ $field['id'] ] = $field;
  181. }
  182. }
  183. } else {
  184. if ( isset( $section['fields'] ) ) {
  185. foreach ( $section['fields'] as $field ) {
  186. $this->fields_by_id[ $field['id'] ] = $field;
  187. }
  188. }
  189. }
  190. }
  191. }
  192. return $this->fields_by_id;
  193. }
  194. public function get_fields_by_type( $type ) {
  195. $fields = array();
  196. $options = $this->get_fields_by_id();
  197. foreach ( $options as $id => $opt ) {
  198. if ( isset( $opt['type'] ) && $opt['type'] === $type ) {
  199. $fields[ $id ] = $opt;
  200. }
  201. }
  202. return $fields;
  203. }
  204. public function customize_register( WP_Customize_Manager $wp_customize ) {
  205. if ( method_exists( $wp_customize, 'register_section_type' ) ) {
  206. $wp_customize->register_section_type( 'Vamtam_Customizer_Section' );
  207. }
  208. if ( method_exists( $wp_customize, 'register_panel_type' ) ) {
  209. $wp_customize->register_panel_type( 'Vamtam_Customizer_Panel' );
  210. }
  211. $priority = 1;
  212. foreach ( $this->sections as $section ) {
  213. if ( empty( $section['priority'] ) ) {
  214. $section['priority'] = $priority++;
  215. }
  216. $parent_id = $this->args['opt_name'] . '-' . $section['id'];
  217. if ( count( $section['children'] ) > 0 ) {
  218. $wp_customize->add_panel( $parent_id, array(
  219. 'priority' => $section['priority'],
  220. 'capability' => $section['permissions'],
  221. 'title' => $section['title'],
  222. 'section' => $section,
  223. 'description' => '',
  224. ) );
  225. foreach ( $section['children'] as $child ) {
  226. if ( empty( $child['priority'] ) ) {
  227. $child['priority'] = $priority++;
  228. }
  229. $wp_customize->add_section( $this->args['opt_name'] . '-' . $child['id'], array(
  230. 'title' => $child['title'],
  231. 'priority' => $child['priority'],
  232. 'description' => $child['description'],
  233. 'section' => $child,
  234. 'capability' => $child['permissions'],
  235. 'panel' => $parent_id,
  236. ) );
  237. $this->setup_fields( $child, $wp_customize );
  238. }
  239. } else {
  240. if ( ! isset( $section['preexisting'] ) || ! $section['preexisting'] ) {
  241. $wp_customize->add_section( $parent_id, array(
  242. 'priority' => $section['priority'],
  243. 'capability' => $section['permissions'],
  244. 'title' => $section['title'],
  245. 'section' => $section,
  246. 'description' => '',
  247. ) );
  248. }
  249. $this->setup_fields( $section, $wp_customize );
  250. }
  251. }
  252. }
  253. private function setup_fields( $section, WP_Customize_Manager $wp_customize ) {
  254. $priority = 1;
  255. foreach ( $section['fields'] as $field ) {
  256. $field['id'] = $this->args['opt_name'] . '[' . $field['id'] . ']';
  257. if ( ! isset( $field['priority'] ) ) {
  258. $field['priority'] = $priority++;
  259. }
  260. $class = $this->class_from_type( $field['type'], $wp_customize );
  261. if ( $class ) {
  262. $type = $field['type'];
  263. if ( $class !== 'Vamtam_Customize_Control' && strpos( $class, 'Vamtam' ) !== false ) {
  264. $type = 'vamtam-' . $type;
  265. }
  266. $wp_customize->add_setting( $field['id'], array(
  267. 'default' => isset( $field['default'] ) ? $field['default'] : '',
  268. 'type' => 'option',
  269. 'transport' => isset( $field['transport'] ) ? $field['transport'] : 'refresh',
  270. 'sanitize_callback' => $this->get_sanitize_callback( $field, $wp_customize ),
  271. ) );
  272. $control_attrs = array_merge( $field, array(
  273. 'section' => isset( $field['section'] ) ? $field['section'] : $this->args['opt_name'] . '-' . $section['id'],
  274. 'settings' => $field['id'],
  275. 'type' => $type,
  276. 'field' => $field,
  277. ) );
  278. $wp_customize->add_control( new $class( $wp_customize, $field['id'], $control_attrs ) );
  279. }
  280. }
  281. }
  282. private function get_sanitize_callback( $field, $wp_customize ) {
  283. $callback = '';
  284. if ( isset( $field['sanitize_callback'] ) ) {
  285. $callback = $field['sanitize_callback'];
  286. } else {
  287. $class = $this->class_from_type( $field['type'], $wp_customize );
  288. $common_callbacks = array(
  289. 'color' => 'sanitize_hex_color',
  290. 'number' => array( __CLASS__, 'sanitize_number' ),
  291. 'image' => 'esc_url_raw',
  292. );
  293. if ( isset( $common_callbacks[ $field['type'] ] ) ) {
  294. $callback = $common_callbacks[ $field['type'] ];
  295. } elseif ( is_callable( array( $class, 'sanitize_callback' ) ) ) {
  296. $callback = array( $class, 'sanitize_callback' );
  297. }
  298. }
  299. return apply_filters( 'vamtam_customize_setting_sanitize_callback', $callback );
  300. }
  301. public static function sanitize_number( $value ) {
  302. $can_validate = method_exists( 'WP_Customize_Setting', 'validate' );
  303. if ( ! is_numeric( $value ) ) {
  304. return $can_validate ? new WP_Error( 'nan', __( 'Not a number', 'vamtam-consulting' ) ) : null;
  305. }
  306. return intval( $value );
  307. }
  308. protected function class_from_type( $type, WP_Customize_Manager $wp_customize ) {
  309. if ( isset( $this->class_from_type_cache[ $type ] ) ) {
  310. return $this->class_from_type_cache[ $type ];
  311. }
  312. $orig_type = $type;
  313. // ucwords $delimiter param was added in 5.4.32, 5.5.16
  314. $type = str_replace( ' ', '_', ucwords( str_replace( '-', ' ', $type ) ) );
  315. $class = '';
  316. $test_core = 'WP_Customize_' . $type . '_Control';
  317. $test_vamtam = 'Vamtam_Customize_' . $type . '_Control';
  318. if ( class_exists( $test_vamtam ) ) {
  319. $class = $test_vamtam;
  320. try {
  321. $reflection = new \ReflectionMethod( $test_vamtam, 'content_template' );
  322. $declaringClass = $reflection->getDeclaringClass()->getName();
  323. $proto = $reflection->getPrototype();
  324. if ( $proto && $proto->getDeclaringClass()->getName() !== $declaringClass ) {
  325. $wp_customize->register_control_type( $class );
  326. }
  327. } catch ( Exception $e ) {
  328. // getPrototype will throw if content_template() is not overriden
  329. }
  330. } elseif ( class_exists( $test_core ) ) {
  331. $class = $test_core;
  332. } else {
  333. $class = 'Vamtam_Customize_Control'; // default WP Core control - implements text, radio, select, etc.
  334. }
  335. $this->class_from_type_cache[ $orig_type ] = $class;
  336. return $class;
  337. }
  338. }