GravityView  2.17
The best, easiest way to display Gravity Forms entries on your website.
Logger/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 
16 use GravityKit\GravityView\Monolog\Logger as MonologLogger;
19 use Exception;
20 
21 /**
22  * Logging framework for GravityKit.
23  */
24 class Framework {
25  const DEFAULT_LOGGER_ID = 'gravitykit';
26 
27  const DEFAULT_LOGGER_TITLE = 'GravityKit';
28 
29  /**
30  * @since 1.0.0
31  *
32  * @var array Instances of the logger class instantiated by various plugins.
33  */
34  private static $_instances = [];
35 
36  /**
37  * Settings framework instance.
38  *
39  * @since 1.0.0
40  *
41  * @var SettingsFramework Settings framework instance.
42  */
43  private $_settings;
44 
45  /**
46  * Monolog class instance.
47  *
48  * @since 1.0.0
49  *
50  * @var MonologLogger
51  */
52  private $_logger;
53 
54  /**
55  * @since 1.0.0
56  *
57  * @var string Unique logger ID.
58  */
59  private $_logger_id;
60 
61  /**
62  * @since 1.0.0
63  *
64  * @var string Logger title.
65  */
66  private $_logger_title;
67 
68  /**
69  * @since 1.0.0
70  *
71  * @var string Location where logs are stored relative to WP_CONTENT_DIR.
72  */
73  private $_log_path = 'logs';
74 
75  /**
76  * Class constructor.
77  *
78  * @since 1.0.0
79  *
80  * @param string $logger_id Unique name that's prefixed to each log entry.
81  * @param string $logger_title Logger title (used in the admin UI).
82  *
83  * @return void
84  */
85  private function __construct( $logger_id, $logger_title ) {
86  global $initialized;
87 
88  if ( ! $initialized ) {
89  add_filter( 'gk/foundation/settings/' . FoundationCore::ID . '/save/before', [ $this, 'save_settings' ] );
90  add_filter( 'gk/foundation/settings', [ $this, 'get_settings' ] );
91  }
92 
93  $this->_settings = SettingsFramework::get_instance();
94 
95  $this->_logger_id = $logger_id;
96  $this->_logger_title = $logger_title;
97 
98  /**
99  * @filter `gk/foundation/logger/log-path` Changes path where logs are stored.
100  *
101  * @since 1.0.0
102  *
103  * @param string $log_path Location where logs are stored relative to WP_CONTENT_DIR. Default: WP_CONTENT_DIR . '/logs'.
104  */
105  $this->_log_path = apply_filters( 'gk/foundation/logger/log-path', $this->_log_path );
106 
107  $logger_handler = $this->get_logger_handler();
108 
109  if ( $logger_handler ) {
110  $this->_logger = new MonologLogger( $logger_id );
111 
112  $this->_logger->pushHandler( $logger_handler );
113  }
114 
115  $initialized = true;
116  }
117 
118  /**
119  * Returns class instance.
120  *
121  * @since 1.0.0
122  *
123  * @param string $logger_id (optional) Unique logger identifier that's prefixed to each log entry or used with some handlers.. Default: gravitykit.
124  * @param string $logger_title (optional) Logger title (used in the admin UI). Default: GravityKit.
125  *
126  * @return Framework
127  */
128  public static function get_instance( $logger_id = '', $logger_title = '' ) {
129  $logger_id = $logger_id ?: self::DEFAULT_LOGGER_ID;
130  $logger_title = $logger_title ?: self::DEFAULT_LOGGER_TITLE;
131 
132  if ( empty( self::$_instances[ $logger_id ] ) ) {
133  self::$_instances[ $logger_id ] = new self( $logger_id, $logger_title );
134  }
135 
136  return self::$_instances[ $logger_id ];
137  }
138 
139  /**
140  * Returns handler that will process log messages.
141  *
142  * @since 1.0.0
143  *
144  * @return void|ChromePHPHandler|GravityFormsHandler|StreamHandler|QueryMonitorHandler
145  */
146  public function get_logger_handler() {
147  $settings = $this->_settings->get_plugin_settings( FoundationCore::ID );
148 
149  if ( empty( $settings['logger'] ) ) {
150  if ( class_exists( 'GFLogging' ) && get_option( 'gform_enable_logging' ) ) {
151  return new GravityFormsHandler( $this->_logger_id, $this->_logger_title );
152  }
153 
154  return;
155  }
156 
157  switch ( $settings['logger_type'] ) {
158  case 'file':
159  try {
160  return new StreamHandler( $this->get_log_file() );
161  } catch ( Exception $e ) {
162  error_log( 'Could not initialize file logging for GravityKit:' . $e->getMessage() );
163 
164  return;
165  }
166  case 'query_monitor':
167  return new QueryMonitorHandler();
168  case 'chrome_logger':
169  return new ChromePHPHandler();
170  }
171  }
172 
173  /**
174  * Returns UI settings for the logger.
175  *
176  * @since 1.0.0
177  * @since 1.0.3 Added $gk_settings parameter.
178  *
179  * @param array $gk_settings GravityKit general settings object.
180  *
181  * @return array[]
182  */
183  public function get_settings( $gk_settings ) {
184  $saved_gk_settings_values = $this->_settings->get_plugin_settings( FoundationCore::ID );
185 
186  // If multisite and not the main site, get default settings from the main site.
187  // This allows site admins to configure the default settings for all subsites.
188  // If no settings are found on the main site, default settings (set below) will be used.
189  if ( ! is_main_site() && empty( $saved_gk_settings_values ) ) {
190  $saved_gk_settings_values = $this->_settings->get_plugin_settings( FoundationCore::ID, get_main_site_id() );
191  }
192 
193  $default_logger_settings = [
194  'logger' => 0,
195  'logger_type' => 'file',
196  ];
197 
198  $log_file = $this->get_log_file();
199  $logger = Arr::get( $saved_gk_settings_values, 'logger', $default_logger_settings['logger'] );
200  $logger_type = Arr::get( $saved_gk_settings_values, 'logger_type', $default_logger_settings['logger_type'] );
201 
202  add_filter( 'gk/foundation/inline-styles', function ( $styles ) {
203  $css = <<<CSS
204 .bg-yellow-50 {
205  --tw-bg-opacity: 1;
206  background-color: rgba(255, 251, 235, var(--tw-bg-opacity))
207 }
208 
209 .bg-blue-50 {
210  --tw-bg-opacity: 1;
211  background-color: rgba(239, 246, 255, var(--tw-bg-opacity))
212 }
213 
214 .text-yellow-400 {
215  --tw-text-opacity: 1;
216  color: rgba(251, 191, 36, var(--tw-text-opacity))
217 }
218 
219 .text-yellow-700 {
220  --tw-text-opacity: 1;
221  color: rgba(180, 83, 9, var(--tw-text-opacity))
222 }
223 
224 .text-blue-400 {
225  --tw-text-opacity: 1;
226  color: rgba(96, 165, 250, var(--tw-text-opacity))
227 }
228 
229 .text-blue-700 {
230  --tw-text-opacity: 1;
231  color: rgba(29, 78, 216, var(--tw-text-opacity))
232 }
233 
234 .hover\:text-yellow-600:hover {
235  --tw-text-opacity: 1;
236  color: rgba(217, 119, 6, var(--tw-text-opacity))
237 }
238 
239 .hover\:text-blue-600:hover {
240  --tw-text-opacity: 1;
241  color: rgba(37, 99, 235, var(--tw-text-opacity))
242 }
243 CSS;
244  $styles[] = [
245  'style' => $css,
246  ];
247 
248  return $styles;
249  } );
250 
251  $query_monitor_notice = strtr(
252  esc_html_x( 'You must install [link]Query Monitor[/link] WordPress plugin to use this option.', 'Placeholders inside [] are not to be translated.', 'gk-gravityview' ),
253  [
254  '[link]' => '<a href="https://wordpress.org/plugins/query-monitor/" class="font-medium underline text-yellow-700 hover:text-yellow-600">',
255  '[/link]' => '</a>',
256  ]
257  );
258 
259  $chrome_logger_tip = strtr(
260  esc_html_x( 'You must install [link]Chrome Logger[/link] browser extension to use this option.', 'Placeholders inside [] are not to be translated.', 'gk-gravityview' ),
261  [
262  '[link]' => '<a href="https://craig.is/writing/chrome-logger" class="font-medium underline text-yellow-700 hover:text-yellow-600">',
263  '[/link]' => '</a>',
264  ]
265  );
266 
267  $gravity_forms_logger_tip = strtr(
268  esc_html_x( 'Logging is currently handled by [link]Gravity Forms[/link].', 'Placeholders inside [] are not to be translated.', 'gk-gravityview' ),
269  [
270  '[link]' => '<a href="' . admin_url( 'admin.php?page=gf_settings&subview=gravityformslogging' ) . '" class="font-medium underline text-yellow-700 hover:text-yellow-600">',
271  '[/link]' => '</a>',
272  ]
273  );
274 
275  $info_icon = <<<HTML
276 <svg class="h-5 w-5 text-yellow-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
277  <path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
278 </svg>
279 HTML;
280 
281  $checkmark_icon = <<<HTML
282 <svg class="h-5 w-5 text-blue-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
283  <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" />
284 </svg>
285 HTML;
286 
287  $notice_template = <<<HTML
288 <div class="bg-%color%-50 p-4">
289  <div class="flex">
290  <div class="flex-shrink-0">
291  %icon%
292  </div>
293  <div class="ml-3">
294  <p class="text-sm text-%color%-700">
295  %notice%
296  </p>
297  </div>
298  </div>
299 </div>
300 HTML;
301 
302  $logger_settings = [];
303 
304  $_update_gk_settings = function () use ( &$logger_settings, &$gk_settings, $default_logger_settings ) {
305  // Add logging settings under the Technical section in GravityKit settings.
306  Arr::set( $gk_settings, 'gk_foundation.sections.2.settings', array_merge(
307  Arr::get( $gk_settings, 'gk_foundation.sections.2.settings' ),
308  $logger_settings
309  ) );
310 
311  // Update defaults.
312  Arr::set( $gk_settings, 'gk_foundation.defaults', array_merge(
313  Arr::get( $gk_settings, 'gk_foundation.defaults' ),
314  $default_logger_settings
315  ) );
316  };
317 
318  if ( ! $logger && class_exists( 'GFLogging' ) && get_option( 'gform_enable_logging' ) ) {
319  $logger_settings[] = [
320  'id' => 'gravity_forms_logger_tip',
321  'html' => strtr( $notice_template, [
322  '%color%' => 'yellow',
323  '%icon%' => $info_icon,
324  '%notice%' => $gravity_forms_logger_tip
325  ] ),
326  'requires' => [
327  'id' => 'logger',
328  'operator' => '!=',
329  'value' => '1',
330  ],
331  ];
332  }
333 
334  $logger_settings = array_merge( $logger_settings, [
335  [
336  'id' => 'logger',
337  'type' => 'checkbox',
338  'title' => esc_html__( 'Enable Logging', 'gk-gravityview' ),
339  'value' => $logger,
340  ],
341  [
342  'id' => 'logger_type',
343  'type' => 'select',
344  'title' => esc_html__( 'Log Type', 'gk-gravityview' ),
345  'description' => esc_html__( 'Where to store log output', 'gk-gravityview' ),
346  'value' => $logger_type,
347  'choices' => [
348  [
349  'title' => esc_html__( 'File', 'gk-gravityview' ),
350  'value' => 'file',
351  ],
352  [
353  'title' => esc_html__( 'Query Monitor', 'gk-gravityview' ),
354  'value' => 'query_monitor',
355  ],
356  [
357  'title' => esc_html__( 'Chrome Logger', 'gk-gravityview' ),
358  'value' => 'chrome_logger',
359  ],
360  ],
361  'requires' => [
362  'id' => 'logger',
363  'operator' => '=',
364  'value' => '1',
365  ],
366  ],
367  [
368  'id' => 'chrome_logger_tip',
369  'html' => strtr( $notice_template, [
370  '%color%' => 'yellow',
371  '%icon%' => $info_icon,
372  '%notice%' => $chrome_logger_tip
373  ] ),
374  'requires' => [
375  'id' => 'logger_type',
376  'operator' => '=',
377  'value' => 'chrome_logger',
378  ],
379  'excludeFromSave' => true,
380  ],
381  ] );
382 
383  if ( ! class_exists( 'QueryMonitor' ) ) {
384  $logger_settings[] = [
385  'id' => 'query_monitor_notice',
386  'html' => strtr( $notice_template, [
387  '%color%' => 'yellow',
388  '%icon%' => $info_icon,
389  '%notice%' => $query_monitor_notice
390  ] ),
391  'requires' => [
392  'id' => 'logger_type',
393  'operator' => '=',
394  'value' => 'query_monitor',
395  ],
396  'excludeFromSave' => true,
397  ];
398  }
399 
400  if ( ! $this->_logger || 'file' !== $logger_type || ! file_exists( $log_file ) ) {
401  $_update_gk_settings();
402 
403  return $gk_settings;
404  }
405 
406  $download_link = sprintf( '%s/%s/%s',
407  content_url(),
408  $this->_log_path,
409  basename( $log_file )
410  );
411 
412  $download_notice = strtr(
413  esc_html_x( 'Download [link]log file[/link] ([size] / [date_modified]).', 'Placeholders inside [] are not to be translated.', 'gk-gravityview' ),
414  [
415  '[link]' => '<a href="' . $download_link . '" class="font-medium underline text-blue-700 hover:text-blue-600">',
416  '[/link]' => '</a>',
417  '[size]' => size_format( filesize( $log_file ), 2 ),
418  '[date_modified]' => date_i18n( 'Y-m-d @ H:i:s', filemtime( $log_file ) ),
419  ]
420  );
421 
422  $logger_settings[] = [
423  'id' => 'log_file',
424  'html' => strtr( $notice_template, [
425  '%color%' => 'blue',
426  '%icon%' => $checkmark_icon,
427  '%notice%' => $download_notice
428  ] ),
429  'requires' => [
430  'id' => 'logger_type',
431  'operator' => '=',
432  'value' => 'file',
433  ],
434  ];
435 
436  $_update_gk_settings();
437 
438  return $gk_settings;
439  }
440 
441  /**
442  * Deletes log files when UI savings are saved and the logger type is no longer "file".
443  *
444  * @since 1.0.0
445  *
446  * @return void
447  */
448  public function save_settings( $new_settings ) {
449  $current_settings = $this->_settings->get_plugin_settings( FoundationCore::ID );
450 
451  if ( ! $this->_logger || empty( $current_settings['logger'] ) || 'file' !== $current_settings['logger_type'] ) {
452  return $new_settings;
453  }
454 
455  if ( ! empty( $new_settings['logger'] ) && 'file' === $new_settings['logger_type'] ) {
456  return $new_settings;
457  }
458 
459  $this->_logger->getHandlers()[0]->close();
460 
461  wp_delete_file( $this->get_log_file() );
462 
463  return $new_settings;
464  }
465 
466  /**
467  * Returns log file name with path.
468  *
469  * @return string
470  */
471  public function get_log_file() {
472  $hash = substr( Encryption::get_instance()->hash( FoundationCore::ID ), 0, 10 );
473 
474  return sprintf( '%s/%s/gravitykit-%s.log', WP_CONTENT_DIR, $this->_log_path, $hash );
475  }
476 
477  /**
478  * Magic method to access Monolog's logger class methods.
479  *
480  * @since 1.0.0
481  *
482  * @param string $name Package/class name.
483  * @param array $arguments Optional and not used.
484  *
485  * @return mixed|void
486  */
487  public function __call( $name, array $arguments = [] ) {
488  if ( ! $this->_logger instanceof MonologLogger ) {
489  return;
490  }
491 
492  /**
493  * @filter `gk/foundation/logger/allow-heartbeat-requests` Allows logging of WP heartbeat requests.
494  *
495  * @since 1.0.0
496  *
497  * @param bool $log_heartbeat Default: false.
498  */
499  $log_heartbeat = apply_filters( 'gk/foundation/logger/allow-heartbeat-requests', false );
500 
501  if ( isset( $_REQUEST['action'] ) && 'heartbeat' == $_REQUEST['action'] && ! $log_heartbeat ) {
502  return;
503  }
504 
505  if ( CoreHelpers::is_callable_class_method( [ $this->_logger, $name ] ) ) {
506  return call_user_func_array( [ $this->_logger, $name ], $arguments );
507  }
508  }
509 }
static set(&$array, $key, $value)
{}
Definition: Arr.php:225
get_settings( $gk_settings)
Returns UI settings for the logger.
static get( $array, $key, $default=null)
{}
Definition: Arr.php:99
get_logger_handler()
Returns handler that will process log messages.
get_log_file()
Returns log file name with path.
save_settings( $new_settings)
Deletes log files when UI savings are saved and the logger type is no longer "file".
static get_instance( $secret_key='')
Returns class instance.
Definition: Encryption.php:67
$current_settings
__construct( $logger_id, $logger_title)
Class constructor.
__call( $name, array $arguments=[])
Magic method to access Monolog&#39;s logger class methods.
static get_instance( $logger_id='', $logger_title='')
Returns class instance.