false, 'debug_display' => 'off', /* * What deleting the plugin takes with it, answered in advance * because the question cannot be asked at deletion time: that * screen belongs to core. Independent boxes, all off by default - * the loss cannot be undone (заказчик, 2026-09-28; the single * "delete everything" box of 2026-09-05 is gone). * * - the current log directory. A yes or no rather than a name: * renaming the directory must not quietly untick it; * - leftovers from earlier installs, one box each, so by name. * Renaming one carries the tick over, see DLOGR_Admin; * - the plugin's own data: the journal table and these options. */ 'delete_log_dir_on_uninstall' => false, 'delete_leftovers_on_uninstall' => array(), 'delete_data_on_uninstall' => false, /* * Language of this plugin's own texts: 'default' leaves it to the * profile, anything else is a locale from DLOGR_Plugin::languages() - * since 2026-10-11 that is en_US alone. * Site-wide like every other setting here - per user would mean user * meta past this layer. Разбор - development/docs/perevod.md. */ 'interface_language' => 'default', ); } /** * Known state entries and their defaults. * * @return array */ public static function state_defaults() { return array( 'log_dir' => '', 'plugin_version' => '', 'config_written' => 0, /* * Set by activation, cleared by DLOGR_Admin::print_welcome() the * moment the notice is printed. A flag rather than printing on the * spot: the activation hook runs inside a request the core is about * to redirect itself. */ 'pending_welcome' => false, // Schema version the journal table was last built to, 0 when never. 'journal_schema' => 0, // Whether the journal table is there and usable. See DLOGR_Journal. 'journal_ready' => false, /* * Failed repairs in a row. Lives here rather than being derived from * repeat_count in the journal: there the counter means "how often * this happened", and resetting it on success would rewrite history. */ 'repair_failures' => 0, ); } /** * Reads one setting. * * @param string $key Setting name. * @return mixed Stored value, or null for unknown keys. */ public static function get( $key ) { $values = self::read( self::SETTINGS, self::defaults() ); return isset( $values[ $key ] ) ? $values[ $key ] : null; } /** * Writes one setting. * * @param string $key Setting name. * @param mixed $value New value. * @return bool False when the key is unknown or the write failed. */ public static function set( $key, $value ) { return self::write_key( self::SETTINGS, self::defaults(), $key, $value ); } /** * Reads one state entry. * * @param string $key State entry name. * @return mixed Stored value, or null for unknown keys. */ public static function get_state( $key ) { $values = self::read( self::STATE, self::state_defaults() ); return isset( $values[ $key ] ) ? $values[ $key ] : null; } /** * Writes one state entry. * * @param string $key State entry name. * @param mixed $value New value. * @return bool False when the key is unknown or the write failed. */ public static function set_state( $key, $value ) { return self::write_key( self::STATE, self::state_defaults(), $key, $value ); } /** * Writes the defaults for anything not stored yet. * * Existing values are kept: activating a second time must not undo the * user's choices. * * @return void */ public static function install_defaults() { self::write( self::SETTINGS, self::read( self::SETTINGS, self::defaults() ) ); self::write( self::STATE, self::read( self::STATE, self::state_defaults() ) ); } /** * Removes both option groups. * * For uninstall only, and the last of the four deletions there: while * these rows exist, the name of the log directory and the state of the * journal are still on hand to explain whatever could not be removed * before them. See "Очерёдность удаления" in development/projekt.md. * * Here rather than in uninstall.php for the same reason nothing else in the * plugin calls the options API directly: this class is the one place that * knows the names, and moving to multisite has to stay a change of two * methods. * * @return void */ public static function delete_options() { delete_option( self::SETTINGS ); delete_option( self::STATE ); } /** * Forgets what was written down about our block in wp-config.php. * * Cleared from two places: the button on the Status tab and uninstall.php. * Hence one method rather than two copies of the same line - the copies * had already drifted apart once, and the state went on describing a file * that no longer held the block. * * ⚠️ Only ever after the block has really gone. A removal that failed * leaves the block where it is, and there the record is still the truth: * clearing it would swap one wrong answer about the file for another. * * @return void */ public static function clear_config_state() { self::set_state( 'config_written', 0 ); } /** * Merges what is stored over the given defaults. * * @param string $option Option name. * @param array $defaults Defaults for that option. * @return array */ private static function read( $option, array $defaults ) { $stored = get_option( $option, array() ); if ( ! is_array( $stored ) ) { $stored = array(); } return array_merge( $defaults, array_intersect_key( $stored, $defaults ) ); } /** * Stores a whole group. * * The autoload flag is 'no' on purpose: the plugin does nothing on the * front end, so there is no reason to load its options on every request. * * @param string $option Option name. * @param array $values Values to store. * @return bool */ private static function write( $option, array $values ) { return update_option( $option, $values, 'no' ); } /** * Sanitises and stores a single key of a group. * * Unknown keys are refused rather than stored: this layer is the list of * what the plugin may keep. * * @param string $option Option name. * @param array $defaults Defaults for that option. * @param string $key Key to write. * @param mixed $value New value. * @return bool */ private static function write_key( $option, array $defaults, $key, $value ) { if ( ! array_key_exists( $key, $defaults ) ) { return false; } $values = self::read( $option, $defaults ); $values[ $key ] = self::sanitize( $key, $value ); return self::write( $option, $values ); } /** * Sanitises one value by its key. * * @param string $key Key the value belongs to. * @param mixed $value Raw value. * @return mixed */ private static function sanitize( $key, $value ) { switch ( $key ) { case 'logging_enabled': case 'delete_log_dir_on_uninstall': case 'delete_data_on_uninstall': return (bool) $value; case 'delete_leftovers_on_uninstall': /* * Only names of our own shape get in: uninstall.php deletes what * this list says. Whether each is actually on disk is for the * caller to check - it changes, and this layer does not look at * the disk. */ if ( ! is_array( $value ) ) { return array(); } $names = array_filter( $value, function ( $name ) { return is_string( $name ) && DLOGR_Log_Dir::is_valid_name( $name ); } ); return array_values( array_unique( $names ) ); case 'debug_display': return in_array( $value, array( 'off', 'on', 'server' ), true ) ? $value : 'off'; case 'interface_language': /* * Checked against the one list of languages rather than a copy of * it here: the page offers exactly those keys. Only ever reached on * a write, so uninstall.php, which never writes this key, does not * need DLOGR_Plugin loaded. */ return is_string( $value ) && array_key_exists( $value, DLOGR_Plugin::languages() ) ? $value : 'default'; case 'journal_ready': case 'pending_welcome': return (bool) $value; case 'config_written': case 'journal_schema': case 'repair_failures': return (int) $value; default: return sanitize_text_field( (string) $value ); } } }