GravityView  2.17
The best, easiest way to display Gravity Forms entries on your website.
Settings/Framework.php
Go to the documentation of this file.
1 <?php
2 /**
3  * @license GPL-2.0-or-later
4  *
5  * Modified by gravityview on 13-January-2023 using Strauss.
6  * @see https://github.com/BrianHenryIE/strauss
7  */
8 
10 
11 use Exception;
18 
19 class Framework {
20  const ID = 'gk_settings';
21 
22  const AJAX_ROUTER = 'settings';
23 
24  /**
25  * Class instance.
26  *
27  * @since 1.0.0
28  *
29  * @var Framework
30  */
31  private static $_instance;
32 
33  /**
34  * @since 1.0.0
35  *
36  * @var string Access capabilities.
37  */
38  private $_capability = 'manage_options';
39 
40  /**
41  * @since 1.0.0
42  *
43  * @var SettingsValidator Settings validator instance.
44  */
45  private $_validator;
46 
47  /**
48  * @since 1.0.0
49  *
50  * @var array Cached settings data.
51  */
52  private $_settings_data = [];
53 
54  private function __construct() {
55  /**
56  * @filter `gk/foundation/settings/capability` Modifies capability to access GravityKit Settings.
57  *
58  * @since 1.0.0
59  *
60  * @param string $capability Capability.
61  */
62  $this->_capability = apply_filters( 'gk/foundation/settings/capability', $this->_capability );
63 
64  $this->_validator = new SettingsValidator();
65  }
66 
67  /**
68  * Returns class instance.
69  *
70  * @since 1.0.0
71  *
72  * @return Framework
73  */
74  public static function get_instance() {
75  if ( ! self::$_instance ) {
76  self::$_instance = new self();
77  }
78 
79  return self::$_instance;
80  }
81 
82  /**
83  * Initializes Settings framework.
84  *
85  * @since 1.0.0
86  *
87  * @return void
88  */
89  public function init() {
90  if ( did_action( 'gk/foundation/settings/initialized' ) || is_network_admin() ) {
91  return;
92  }
93 
94  add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
95 
96  add_filter( 'gk/foundation/ajax/' . self::AJAX_ROUTER . '/routes', [ $this, 'configure_ajax_routes' ] );
97 
98  $this->add_gk_submenu_item();
99 
100  /**
101  * @action `gk/foundation/settings/initialized` Fires when the class has finished initializing.
102  *
103  * @since 1.0.0
104  *
105  * @param $this
106  */
107  do_action( 'gk/foundation/settings/initialized', $this );
108  }
109 
110  /**
111  * Configures AJAX routes handled by this class.
112  *
113  * @since 1.0.0
114  *
115  * @see FoundationCore::process_ajax_request()
116  *
117  * @param array $routes AJAX route to class method map.
118  *
119  * @return array
120  */
121  public function configure_ajax_routes( array $routes ) {
122  return array_merge( $routes, [
123  'save_settings' => [ $this, 'save_ui_settings' ],
124  ] );
125  }
126 
127  /**
128  * Gets settings for all GravityKit plugins.
129  *
130  * @since 1.0.0
131  *
132  * @param int|null $site_id (optional) Site ID for which to get settings. Default is null (i.e, current site ID).
133  *
134  * @return array
135  */
136  public function get_all_settings( $site_id = null ) {
137  $site_id = $site_id ?: get_current_blog_id();
138 
139  if ( ! isset( $this->_settings_data[ $site_id ] ) ) {
140  $this->_settings_data[ $site_id ] = is_multisite() ? get_blog_option( $site_id, self::ID, [] ) : get_option( self::ID, [] );
141  }
142 
143  if ( doing_action( 'gk/foundation/settings/data/plugins' ) ) {
144  // Avoid possible infinite loop if this method is called from within the `gk/foundation/settings/data/plugins` filter.
145  return $this->_settings_data[ $site_id ];
146  }
147 
148  // Update cached settings data with default values for each plugin.
149  $plugins_settings = $this->get_plugins_settings_data();
150 
151  foreach ( $plugins_settings as $plugin_id => $plugin_settings ) {
152  if ( ! isset( $this->_settings_data[ $site_id ][ $plugin_id ] ) ) {
153  $this->_settings_data[ $site_id ][ $plugin_id ] = $this->get_default_settings( $plugin_id );
154  } else {
155  $this->_settings_data[ $site_id ][ $plugin_id ] = wp_parse_args( $this->_settings_data[ $site_id ][ $plugin_id ], $this->get_default_settings( $plugin_id ) );
156  }
157  }
158 
159  return $this->_settings_data[ $site_id ];
160  }
161 
162  /**
163  * Returns settings data object for all plugins.
164  *
165  * @since 1.0.3
166  *
167  * @return array
168  */
169  public function get_plugins_settings_data() {
170  $plugins_settings_data = apply_filters( 'gk/foundation/settings/data/plugins', [] );
171 
172  if ( ! is_array( $plugins_settings_data ) ) {
173 
174  LoggerFramework::get_instance()->error( 'Invalid settings data. Expected array, got ' . print_r( $plugins_settings_data, true ) );
175 
176  return [];
177  }
178 
179  /**
180  * @filter `gk/foundation/settings/data/plugins` Modifies plugins' settings.
181  *
182  * @since 1.0.0
183  *
184  * @param array $plugins_data Plugins data.
185  */
186  return array_filter( $plugins_settings_data );
187  }
188 
189  /**
190  * Returns default settings for a plugin all plugins.
191  * Default settings are defined in the plugin's settings object under the `defaults` key.
192  *
193  * @since 1.0.3
194  *
195  * @param $plugin_id
196  *
197  * @return array|array[]
198  */
199  public function get_default_settings( $plugin_id = null ) {
200  $plugins_data = $this->get_plugins_settings_data();
201 
202  if ( empty( $plugins_data ) ) {
203  return [];
204  }
205 
206  if ( ! $plugin_id ) {
207  return array_map( function ( $plugin_data ) {
208  return Arr::get( $plugin_data, 'defaults', [] );
209  }, $plugins_data );
210  }
211 
212  return Arr::get( $plugins_data, "{$plugin_id}.defaults", [] );
213  }
214 
215  /**
216  * Saves settings for all GravityKit plugins.
217  *
218  * @since 1.0.0
219  *
220  * @param array $settings
221  * @param int|null $site_id (optional) Site ID for which to save settings. Default is null (i.e., current site ID).
222  *
223  * @return bool
224  */
225  public function save_all_settings( array $settings, $site_id = null ) {
226  $site_id = $site_id ?: get_current_blog_id();
227 
228  $this->_settings_data[ $site_id ] = $settings;
229 
230  return is_multisite() ? update_blog_option( $site_id, self::ID, $settings ) : update_option( self::ID, $settings );
231  }
232 
233  /**
234  * Gets a single setting for a GravityKit plugin.
235  *
236  * @since 1.0.0
237  *
238  * @param string $plugin Plugin ID as specified in the settings object.
239  * @param string $plugin_setting_name Setting name as specified in the settings object.
240  * @param null|array|mixed $default (optional) Default value to return if the setting is not found. Default is null.
241  * @param int|null $site_id (optional) Site ID for which to get settings. Default is null (i.e., current site ID). *
242  *
243  * @return mixed|null
244  */
245  public function get_plugin_setting( $plugin, $plugin_setting_name, $default = null, $site_id = null ) {
246  $site_id = $site_id ?: get_current_blog_id();
247 
248  $plugin_settings = $this->get_plugin_settings( $plugin, $site_id );
249 
250  if ( array_key_exists( $plugin_setting_name, $plugin_settings ) ) {
251  return $plugin_settings[ $plugin_setting_name ];
252  }
253 
254 
255  if ( is_array( $default ) && array_key_exists( $plugin_setting_name, $default ) ) {
256  return $default[ $plugin_setting_name ];
257  }
258 
259  return $default;
260  }
261 
262  /**
263  * Saves a single setting for a GravityKit plugin.
264  *
265  * @since 1.0.0
266  *
267  * @param string $plugin
268  * @param string $plugin_setting_name
269  * @param mixed $plugin_setting_value
270  * @param int|null $site_id (optional) Site ID for which to save settings. Default is null (i.e., current site ID).
271  *
272  * @return bool
273  */
274  public function save_plugin_setting( $plugin, $plugin_setting_name, $plugin_setting_value, $site_id = null ) {
275  $site_id = $site_id ?: get_current_blog_id();
276 
277  $plugin_settings = $this->get_plugin_settings( $plugin, $site_id );
278 
279  $plugin_settings[ $plugin_setting_name ] = $plugin_setting_value;
280 
281  return $this->save_plugin_settings( $plugin, $plugin_settings, $site_id );
282  }
283 
284  /**
285  * Get all settings for a GravityKit plugin.
286  *
287  * @since 1.0.0
288  *
289  * @param string $plugin
290  * @param int|null $site_id (optional) Site ID for which to get settings. Default is null (i.e., current site ID).
291  *
292  * @return array
293  */
294  public function get_plugin_settings( $plugin, $site_id = null ) {
295  $site_id = $site_id ?: get_current_blog_id();
296 
297  $settings = $this->get_all_settings( $site_id );
298 
299  return ! empty( $settings[ $plugin ] ) ? $settings[ $plugin ] : [];
300  }
301 
302  /**
303  * Saves all settings for a GravityKit plugin.
304  *
305  * @since 1.0.0
306  *
307  * @param string $plugin
308  * @param array $plugin_settings
309  * @param int|null $site_id (optional) Site ID for which to save settings. Default is null (i.e, current site ID).
310  *
311  * @return bool
312  */
313  public function save_plugin_settings( $plugin, array $plugin_settings, $site_id = null ) {
314  $site_id = $site_id ?: get_current_blog_id();
315 
316  $settings_data = $this->get_all_settings( $site_id );
317 
318  $settings_data[ $plugin ] = ! empty( $settings_data[ $plugin ] ) ? $settings_data[ $plugin ] : [];
319 
320  $settings_data[ $plugin ] = array_merge( $settings_data[ $plugin ], $plugin_settings );
321 
322  /**
323  * @filter `gk/foundation/settings/{plugin}/save/before` Modifies plugin settings object before saving.
324  *
325  * @since 1.0.0
326  *
327  * @param array $settings Plugin settings.
328  */
329  $settings_data[ $plugin ] = apply_filters( "gk/foundation/settings/${plugin}/save/before", $settings_data[ $plugin ] );
330 
331  return $this->save_all_settings( $settings_data, $site_id );
332  }
333 
334  /**
335  * Adds Settings submenu to the GravityKit top-level admin menu.
336  *
337  * @since 1.0.0
338  *
339  * @return void
340  */
341  public function add_gk_submenu_item() {
342  $page_title = $menu_title = esc_html__( 'Settings', 'gk-gravityview' );
343 
345  'page_title' => $page_title,
346  'menu_title' => $menu_title,
347  'capability' => $this->_capability,
348  'id' => self::ID,
349  'callback' => '__return_false', // Content will be injected into #wpbody by gk-setting.js (see /UI/Settings/src/main-prod.js)
350  'order' => 2,
351  ], 'top' );
352  }
353 
354  /**
355  * Returns link to the plugin settings page.
356  *
357  * @since 1.0.3
358  *
359  * @param string $plugin_id
360  *
361  * @return string
362  */
363  public function get_plugin_settings_url( $plugin_id ) {
364  return add_query_arg( [
365  'page' => self::ID,
366  'p' => $plugin_id,
367  ], admin_url( 'admin.php' ) );
368  }
369 
370  /**
371  * Enqueues UI assets.
372  *
373  * @since 1.0.0
374  *
375  * @param string $page Current page.
376  *
377  * @return void
378  */
379  public function enqueue_assets( $page ) {
380  $plugins_data = $this->get_plugins_settings_data();
381 
382  foreach ( Arr::pluck( $plugins_data, 'id' ) as $plugin_id ) {
383  add_filter( "gk/foundation/settings/${plugin_id}/settings-url", function () use ( $plugin_id ) {
384  return $this->get_plugin_settings_url( $plugin_id );
385  } );
386  }
387 
388  ksort( $plugins_data );
389 
390  if ( empty( $plugins_data ) ) {
391  // Remove the Settings menu items when there are no settings to display.
392  AdminMenu::remove_submenu_item( self::ID );
393 
394  LoggerFramework::get_instance()->warning( 'There are no plugins with settings to display.' );
395 
396  return;
397  }
398 
399  if ( strpos( $page, self::ID ) === false ) {
400  return;
401  }
402 
403  $script = 'settings.js';
404  $style = 'settings.css';
405 
406  if ( ! file_exists( CoreHelpers::get_assets_path( $script ) ) || ! file_exists( CoreHelpers::get_assets_path( $style ) ) ) {
407  LoggerFramework::get_instance()->warning( 'UI assets not found.' );
408 
409  return;
410  }
411 
412  /**
413  * @filter `gk/foundation/settings/data/config` Modifies global settings configuration.
414  *
415  * @since 1.0.0 Introduced but not (yet) used.
416  *
417  * @param array $config Configuration.
418  */
419  $config = apply_filters( 'gk/foundation/settings/data/config', [] );
420 
421  $script_data = array_merge(
422  [
423  'config' => $config,
424  'plugins' => array_values( $plugins_data ),
425  ],
426  FoundationCore::get_ajax_params( self::AJAX_ROUTER )
427  );
428 
429  wp_enqueue_script(
430  self::ID,
431  CoreHelpers::get_assets_url( $script ),
432  [ 'wp-hooks', 'wp-i18n' ],
433  filemtime( CoreHelpers::get_assets_path( $script ) )
434  );
435 
436  wp_localize_script(
437  self::ID,
438  'gkSettings',
439  [ 'data' => $script_data ]
440  );
441 
442  wp_enqueue_style(
443  self::ID,
444  CoreHelpers::get_assets_url( $style ),
445  [],
446  filemtime( CoreHelpers::get_assets_path( $style ) )
447  );
448 
449  // WP's forms.css interferes with our styles.
450  wp_deregister_style( 'forms' );
451  wp_register_style( 'forms', false );
452 
453  // Load UI translations using the text domain of the plugin that instantiated Foundation.
454  $registered_plugins = FoundationCore::get_instance()->get_registered_plugins();
455  $foundation_source_plugin_data = CoreHelpers::get_plugin_data( $registered_plugins['foundation_source'] );
456  TranslationsFramework::get_instance()->load_frontend_translations( $foundation_source_plugin_data['TextDomain'], '', 'gk-foundation' );
457  }
458 
459  /**
460  * Saves UI settings.
461  *
462  * @since 1.0.0
463  *
464  * @param array $settings_data
465  *
466  * @throws Exception
467  *
468  * @return mixed|void Exit with JSON response or return response message.
469  */
470  public function save_ui_settings( array $settings_data ) {
471  $plugin_id = ! empty( $settings_data['plugin'] ) ? $settings_data['plugin'] : null;
472  $ui_settings = ! empty( $settings_data['settings'] ) ? $settings_data['settings'] : null;
473 
474  if ( ! $plugin_id || ! $ui_settings ) {
475  throw new Exception( esc_html__( 'Invalid request.', 'gk-gravityview' ) );
476  }
477 
478  try {
479  $plugins_data = $this->get_plugins_settings_data();
480 
481  $plugin_data = null;
482 
483  foreach ( $plugins_data as $settings_data ) {
484  if ( empty( $settings_data ) ) {
485  continue;
486  }
487 
488  if ( $plugin_id === $settings_data['id'] ) {
489  $plugin_data = $settings_data;
490 
491  break;
492  }
493  }
494 
495  if ( empty( $plugin_data['sections'] ) ) {
496  throw new ValidatorException( esc_html__( 'Plugin settings data not found.', 'gk-gravityview' ) );
497  }
498 
499  foreach ( $ui_settings as $id => $value ) {
500  $ui_settings['id'] = sanitize_text_field( $value );
501  }
502 
503  $plugin_settings = []; // Flattened plugin settings data without sections/etc.; this is the source of truth against which the UI settings are being validated below.
504 
505  foreach ( $plugin_data['sections'] as $section ) {
506  if ( empty( $section['settings'] ) ) {
507  continue;
508  }
509 
510  foreach ( $section['settings'] as $plugin_setting ) {
511  // A setting can be explicitly excluded from being saved via a flag.
512  if ( ! empty( $plugin_setting['html'] ) || ! empty( $plugin_setting['excludeFromSave'] ) ) {
513  unset( $ui_settings[ $plugin_setting['id'] ] );
514 
515  continue;
516  }
517 
518  // A setting can depend on the value of another setting (i.e., it is conditionally used when the value matches).
519  if ( ! empty( $plugin_setting['requires'] ) && is_array( $plugin_setting['requires'] ) && ! $this->are_setting_requirements_met( $plugin_setting, $ui_settings ) ) {
520  // If the requirements aren't met and the setting isn't among the ones being saved, exclude it.
521  if ( ! isset( $ui_settings[ $plugin_setting['id'] ] ) ) {
522  continue;
523  }
524 
525  throw new Exception(
526  strtr(
527  esc_html_x( 'Setting [setting] has unmet requirements.', 'Placeholders inside [] are not to be translated.', 'gk-gravityview' ),
528  [ '[setting]' => $plugin_setting['title'] ]
529  )
530  );
531  }
532 
533  $plugin_settings[ $plugin_setting['id'] ] = $plugin_setting;
534  }
535  }
536 
537  // Remove UI settings that are not among the plugin settings.
538  foreach ( $ui_settings as $id => $value ) {
539  if ( ! isset( $plugin_settings[ $id ] ) ) {
540  unset( $ui_settings[ $id ] );
541  }
542  }
543 
544  /**
545  * @filter `gk/foundation/settings/${plugin}/validation/before` Modifies plugin settings object before validation.
546  *
547  * @since 1.0.0
548  *
549  * @param array $ui_settings Settings.
550  */
551  $ui_settings = apply_filters( "gk/foundation/settings/${plugin_id}/validation/before", $ui_settings );
552 
553  $this->_validator->validate( $plugin_id, $plugin_settings, $ui_settings );
554 
555  /**
556  * @filter `gk/foundation/settings/${plugin}/validation/after` Modifies plugin settings object after validation.
557  *
558  * @since 1.0.0
559  *
560  * @param array $ui_settings Settings.
561  */
562  $ui_settings = apply_filters( "gk/foundation/settings/${plugin_id}/validation/after", $ui_settings );
563 
564  $this->save_plugin_settings( $plugin_id, $ui_settings );
565 
566  return esc_html__( 'Settings were successfully saved.', 'gk-gravityview' );
567  } catch ( ValidatorException $e ) {
568  throw new Exception( sprintf( '%s %s', esc_html__( 'Error saving settings.', 'gk-gravityview' ), $e->getMessage() ) );
569  }
570  }
571 
572  /**
573  * Determines if a setting's requirements are met based on the value(s) of other setting(s).
574  *
575  * @since 1.0.0
576  *
577  * @param array $plugin_setting Individual plugin setting as configured using the `gk/foundation/settings/data/plugins` filter.
578  * @param array $settings Setting ID:value pairs.
579  *
580  * @return bool
581  */
582  function are_setting_requirements_met( $plugin_setting, $settings ) {
583  $requirements = is_array( array_values( $plugin_setting['requires'] )[0] ) ? $plugin_setting['requires'] : [ $plugin_setting['requires'] ]; // Make requirements an array of arrays.
584 
585  foreach ( $requirements as $requirement ) {
586  $required_value = $requirement['value'];
587  $setting_value = isset( $settings[ $requirement['id'] ] ) ? $settings[ $requirement['id'] ] : null;
588  $operator = $requirement['operator'];
589 
590  if ( ! Helpers::compare_values( $required_value, $setting_value, $operator ) ) {
591  return false;
592  }
593  }
594 
595  return true;
596  }
597 }
get_plugin_setting( $plugin, $plugin_setting_name, $default=null, $site_id=null)
Gets a single setting for a GravityKit plugin.
save_all_settings(array $settings, $site_id=null)
Saves settings for all GravityKit plugins.
static remove_submenu_item( $id)
Removes a submenu from the GravityKit top-level menu in WP admin and if the top-level menu is empty...
Definition: AdminMenu.php:286
static compare_values( $first, $second, $op)
Compares 2 values using an operator.
static add_submenu_item( $submenu, $position='top')
Adds a submenu to the GravityKit top-level menu in WP admin.
Definition: AdminMenu.php:233
static get( $array, $key, $default=null)
{}
Definition: Arr.php:99
save_ui_settings(array $settings_data)
Saves UI settings.
add_gk_submenu_item()
Adds Settings submenu to the GravityKit top-level admin menu.
get_plugins_settings_data()
Returns settings data object for all plugins.
static pluck( $array, $value, $key=null)
{}
Definition: Arr.php:189
get_plugin_settings( $plugin, $site_id=null)
Get all settings for a GravityKit plugin.
configure_ajax_routes(array $routes)
Configures AJAX routes handled by this class.
are_setting_requirements_met( $plugin_setting, $settings)
Determines if a setting&#39;s requirements are met based on the value(s) of other setting(s).
get_plugin_settings_url( $plugin_id)
Returns link to the plugin settings page.
save_plugin_setting( $plugin, $plugin_setting_name, $plugin_setting_value, $site_id=null)
Saves a single setting for a GravityKit plugin.
get_default_settings( $plugin_id=null)
Returns default settings for a plugin all plugins.
get_all_settings( $site_id=null)
Gets settings for all GravityKit plugins.
save_plugin_settings( $plugin, array $plugin_settings, $site_id=null)
Saves all settings for a GravityKit plugin.