--- a/metasync/MetaSyncDebug.php
+++ b/metasync/MetaSyncDebug.php
@@ -1,7 +1,7 @@
<?php
/**
* Transforms a wp-config.php file.
- *
+ *
* @package MetaSync
* @since 1.0.0
*/
@@ -12,458 +12,503 @@
}
/**
+ * Exception thrown when wp-config.php file is missing.
+ */
+class WPConfigFileNotFoundException extends Exception
+{
+}
+
+/**
+ * Exception thrown when wp-config.php file is not writable.
+ */
+class WPConfigFileNotWritableException extends Exception
+{
+}
+
+/**
+ * Exception thrown when wp-config.php file is empty.
+ */
+class WPConfigFileEmptyException extends Exception
+{
+}
+
+/**
+ * Exception thrown when config type is invalid.
+ */
+class WPConfigInvalidTypeException extends Exception
+{
+}
+
+/**
+ * Exception thrown when config value is invalid.
+ */
+class WPConfigInvalidValueException extends Exception
+{
+}
+
+/**
+ * Exception thrown when placement anchor cannot be located.
+ */
+class WPConfigAnchorNotFoundException extends Exception
+{
+}
+
+/**
+ * Exception thrown when normalization fails.
+ */
+class WPConfigNormalizationException extends Exception
+{
+}
+
+/**
+ * Exception thrown when saving wp-config.php fails.
+ */
+class WPConfigSaveException extends Exception
+{
+}
+
+/**
* Transforms a wp-config.php file.
*/
-class WPConfigTransformerMetaSync {
- /**
- * Append to end of file
- */
- const ANCHOR_EOF = 'EOF';
-
- /**
- * Path to the wp-config.php file.
- *
- * @var string
- */
- protected $wp_config_path;
-
- /**
- * Original source of the wp-config.php file.
- *
- * @var string
- */
- protected $wp_config_src;
-
- /**
- * Array of parsed configs.
- *
- * @var array
- */
- protected $wp_configs = array();
-
- /**
- * Instantiates the class with a valid wp-config.php.
- *
- * @throws Exception If the wp-config.php file is missing.
- * @throws Exception If the wp-config.php file is not writable.
- *
- * @param string $wp_config_path Path to a wp-config.php file.
- */
- public function __construct( $wp_config_path ) {
- $basename = basename( $wp_config_path );
-
- if ( ! file_exists( $wp_config_path ) ) {
- throw new Exception( "{$basename} does not exist." );
- }
-
- if ( ! is_writable( $wp_config_path ) ) {
- throw new Exception( "{$basename} is not writable." );
- }
-
- $this->wp_config_path = $wp_config_path;
- }
-
- /**
- * Checks if a config exists in the wp-config.php file.
- *
- * @throws Exception If the wp-config.php file is empty.
- * @throws Exception If the requested config type is invalid.
- *
- * @param string $type Config type (constant or variable).
- * @param string $name Config name.
- *
- * @return bool
- */
- public function exists( $type, $name ) {
- $wp_config_src = file_get_contents( $this->wp_config_path );
-
- if ( ! trim( $wp_config_src ) ) {
- throw new Exception( 'Config file is empty.' );
- }
-
- // Normalize the newline to prevent an issue coming from OSX.
- $this->wp_config_src = str_replace( array( "nr", "r" ), "n", $wp_config_src );
- $this->wp_configs = $this->parse_wp_config( $this->wp_config_src );
-
- if ( ! isset( $this->wp_configs[ $type ] ) ) {
-
- throw new Exception( "Config type '{$type}' does not exist." );
-
- return false;
- }
- if($name =='WP_DEBUG_LOG'){
-
- }
- return isset( $this->wp_configs[ $type ][ $name ] );
- }
-
- /**
- * Get the value of a config in the wp-config.php file.
- *
- * @throws Exception If the wp-config.php file is empty.
- * @throws Exception If the requested config type is invalid.
- *
- * @param string $type Config type (constant or variable).
- * @param string $name Config name.
- *
- * @return array
- */
- public function get_value( $type, $name ) {
- $wp_config_src = file_get_contents( $this->wp_config_path );
-
- if ( ! trim( $wp_config_src ) ) {
- throw new Exception( 'Config file is empty.' );
- }
-
- $this->wp_config_src = $wp_config_src;
- $this->wp_configs = $this->parse_wp_config( $this->wp_config_src );
-
- if ( ! isset( $this->wp_configs[ $type ] ) ) {
-// throw new Exception( "Config type '{$type}' does not exist." );
+class WPConfigTransformerMetaSync
+{
+ /**
+ * Append to end of file
+ */
+ const ANCHOR_EOF = 'EOF';
+
+ /**
+ * Path to the wp-config.php file.
+ *
+ * @var string
+ */
+ protected $wpConfigPath;
+
+ /**
+ * Original source of the wp-config.php file.
+ *
+ * @var string
+ */
+ protected $wpConfigSrc;
+
+ /**
+ * Array of parsed configs.
+ *
+ * @var array
+ */
+ protected $wpConfigs = [];
+
+ /**
+ * Instantiates the class with a valid wp-config.php.
+ *
+ * @throws WPConfigFileNotFoundException If the wp-config.php file is missing.
+ * @throws WPConfigFileNotWritableException If the wp-config.php file is not writable.
+ *
+ * @param string $wpConfigPath Path to a wp-config.php file.
+ */
+ public function __construct($wpConfigPath)
+ {
+ $basename = basename($wpConfigPath);
+
+ if (!file_exists($wpConfigPath)) {
+ throw new WPConfigFileNotFoundException("{$basename} does not exist.");
+ }
+
+ if (!is_writable($wpConfigPath)) {
+ throw new WPConfigFileNotWritableException("{$basename} is not writable.");
+ }
+
+ $this->wpConfigPath = $wpConfigPath;
+ }
+
+ /**
+ * Checks if a config exists in the wp-config.php file.
+ *
+ * @throws WPConfigFileEmptyException If the wp-config.php file is empty.
+ * @throws WPConfigInvalidTypeException If the requested config type is invalid.
+ *
+ * @param string $type Config type (constant or variable).
+ * @param string $name Config name.
+ *
+ * @return bool
+ */
+ public function exists($type, $name)
+ {
+ $wpConfigSrc = file_get_contents($this->wpConfigPath);
+
+ if (!trim($wpConfigSrc)) {
+ throw new WPConfigFileEmptyException('Config file is empty.');
+ }
+
+ // Normalize the newline to prevent an issue coming from OSX.
+ $this->wpConfigSrc = str_replace(["nr", "r"], "n", $wpConfigSrc);
+ $this->wpConfigs = $this->parseWpConfig($this->wpConfigSrc);
+
+ if (!isset($this->wpConfigs[$type])) {
+ throw new WPConfigInvalidTypeException("Config type '{$type}' does not exist.");
+ }
+
+ return isset($this->wpConfigs[$type][$name]);
+ }
+
+ /**
+ * Get the value of a config in the wp-config.php file.
+ *
+ * @throws WPConfigFileEmptyException If the wp-config.php file is empty.
+ *
+ * @param string $type Config type (constant or variable).
+ * @param string $name Config name.
+ *
+ * @return mixed|null
+ */
+ public function getValue($type, $name)
+ {
+ $wpConfigSrc = file_get_contents($this->wpConfigPath);
+
+ if (!trim($wpConfigSrc)) {
+ throw new WPConfigFileEmptyException('Config file is empty.');
+ }
+
+ $this->wpConfigSrc = $wpConfigSrc;
+ $this->wpConfigs = $this->parseWpConfig($this->wpConfigSrc);
+
+ if (!isset($this->wpConfigs[$type])) {
return null;
- }
-
- return $this->wp_configs[ $type ][ $name ]['value'];
- }
-
- /**
- * Adds a config to the wp-config.php file.
- *
- * @throws Exception If the config value provided is not a string.
- * @throws Exception If the config placement anchor could not be located.
- *
- * @param string $type Config type (constant or variable).
- * @param string $name Config name.
- * @param string $value Config value.
- * @param array $options (optional) Array of special behavior options.
- *
- * @return bool
- */
- public function add( $type, $name, $value, array $options = array() ) {
-
-
- if ( ! is_string( $value ) ) {
- return ;
- throw new Exception( 'Config value must be a string.' );
- }
-
- if ( $this->exists( $type, $name ) ) {
- return false;
- }
-
- $defaults = array(
- 'raw' => false, // Display value in raw format without quotes.
- 'anchor' => "/* That's all, stop editing!", // Config placement anchor string.
- 'separator' => PHP_EOL, // Separator between config definition and anchor string.
- 'placement' => 'before', // Config placement direction (insert before or after).
- );
-
- list( $raw, $anchor, $separator, $placement ) = array_values( array_merge( $defaults, $options ) );
-
- $raw = (bool) $raw;
- $anchor = (string) $anchor;
- $separator = (string) $separator;
- $placement = (string) $placement;
-
- if ( self::ANCHOR_EOF === $anchor ) {
-
- $contents = $this->wp_config_src . $this->normalize( $type, $name, $this->format_value( $value, $raw ) );
- } else {
-
- if ( false === strpos( $this->wp_config_src, $anchor ) ) {
- throw new Exception( 'Unable to locate placement anchor.' );
- }
-
- $new_src = $this->normalize( $type, $name, $this->format_value( $value, $raw ) );
- $new_src = ( 'after' === $placement ) ? $anchor . $separator . $new_src : $new_src . $separator . $anchor;
- $contents = str_replace( $anchor, $new_src, $this->wp_config_src );
- }
-
-
- return $this->save( $contents );
- }
-
- /**
- * Updates an existing config in the wp-config.php file.
- *
- * @throws Exception If the config value provided is not a string.
- *
- * @param string $type Config type (constant or variable).
- * @param string $name Config name.
- * @param string $value Config value.
- * @param array $options (optional) Array of special behavior options.
- *
- * @return bool
- */
- public function update( $type, $name, $value, array $options = array() ) {
- if ( ! is_string( $value ) ) {
- throw new Exception( 'Config value must be a string.' );
- }
-
-
- $defaults = array(
- 'add' => true, // Add the config if missing.
- 'raw' => false, // Display value in raw format without quotes.
- 'normalize' => true, // Normalize config output using WP Coding Standards.
- );
-
- list( $add, $raw, $normalize ) = array_values( array_merge( $defaults, $options ) );
-
- $add = (bool) $add;
- $raw = (bool) $raw;
- $normalize = (bool) $normalize;
-
- if ( ! $this->exists( $type, $name ) ) {
-
- return ( $add ) ? $this->add( $type, $name, $value, $options ) : false;
- }else{
-
- }
-
- $old_src = $this->wp_configs[ $type ][ $name ]['src'];
- $old_value = $this->wp_configs[ $type ][ $name ]['value'];
- $new_value = $this->format_value( $value, $raw );
-
- if ( $normalize ) {
- $new_src = $this->normalize( $type, $name, $new_value );
- } else {
- $new_parts = $this->wp_configs[ $type ][ $name ]['parts'];
- $new_parts[1] = str_replace( $old_value, $new_value, $new_parts[1] ); // Only edit the value part.
- $new_src = implode( '', $new_parts );
- }
- // echo json_encode([$this->wp_config_src]);
- // echo '<br/><br/><br/>';
- if($value=="true"){
- $contents = preg_replace(
- sprintf( '/(?<=^|;|<?phps|<?s)(s*?)%s/m', preg_quote( trim( $old_src ), '/' ) ),
- '$1' . str_replace( '$', '$', trim( $new_src ) ),
- $this->wp_config_src
- );
- }else{
- if ( ! $this->exists( $type, $name ) ) {
- return $this->save( '' );
- }
-
- $pattern = sprintf( '/(?<=^|;|<?phps|<?s)%ss*(S|$)/m', preg_quote( $this->wp_configs[ $type ][ $name ]['src'], '/' ) );
- $contents = preg_replace( $pattern, '$1', $this->wp_config_src );
-
- }
-
- return $this->save( $contents );
- }
-
- /**
- * Removes a config from the wp-config.php file.
- *
- * @param string $type Config type (constant or variable).
- * @param string $name Config name.
- *
- * @return bool
- */
- public function remove( $type, $name ) {
- if ( ! $this->exists( $type, $name ) ) {
- return false;
- }
-
- $pattern = sprintf( '/(?<=^|;|<?phps|<?s)%ss*(S|$)/m', preg_quote( $this->wp_configs[ $type ][ $name ]['src'], '/' ) );
- $contents = preg_replace( $pattern, '$1', $this->wp_config_src );
-
- return $this->save( $contents );
- }
-
- /**
- * Applies formatting to a config value.
- *
- * @throws Exception When a raw value is requested for an empty string.
- *
- * @param string $value Config value.
- * @param bool $raw Display value in raw format without quotes.
- *
- * @return mixed
- */
- protected function format_value( $value, $raw ) {
- if ( $raw && '' === trim( $value ) ) {
- throw new Exception( 'Raw value for empty string not supported.' );
- }
-
- return ( $raw ) ? $value : var_export( $value, true );
- }
-
- /**
- * Normalizes the source output for a name/value pair.
- *
- * @throws Exception If the requested config type does not support normalization.
- *
- * @param string $type Config type (constant or variable).
- * @param string $name Config name.
- * @param mixed $value Config value.
- *
- * @return string
- */
- protected function normalize( $type, $name, $value ) {
- if ( 'constant' === $type ) {
- $placeholder = "define( '%s', %s );";
- } elseif ( 'variable' === $type ) {
- $placeholder = '$%s = %s;';
- } else {
- throw new Exception( "Unable to normalize config type '{$type}'." );
- }
-
- return sprintf( $placeholder, $name, $value );
- }
-
- /**
- * Parses the source of a wp-config.php file.
- *
- * @param string $src Config file source.
- *
- * @return array
- */
- protected function parse_wp_config( $src ) {
- $configs = array();
- $configs['constant'] = array();
- $configs['variable'] = array();
-
- // Strip comments.
- foreach ( token_get_all( $src ) as $token ) {
- if ( in_array( $token[0], array( T_COMMENT, T_DOC_COMMENT ), true ) ) {
- $src = str_replace( $token[1], '', $src );
- }
- }
-
- preg_match_all( '/(?<=^|;|<?phps|<?s)(h*defines*(s*['"](w*?)['"]s*)(,s*(''|""|'.*?[^\\]'|".*?[^\\]"|.*?)s*)((?:,s*(?:true|false)s*)?)s*;)/ims', $src, $constants );
- preg_match_all( '/(?<=^|;|<?phps|<?s)(h*$(w+)s*=)(s*(''|""|'.*?[^\\]'|".*?[^\\]"|.*?)s*;)/ims', $src, $variables );
-
- if ( ! empty( $constants[0] ) && ! empty( $constants[1] ) && ! empty( $constants[2] ) && ! empty( $constants[3] ) && ! empty( $constants[4] ) && ! empty( $constants[5] ) ) {
- foreach ( $constants[2] as $index => $name ) {
- $configs['constant'][ $name ] = array(
- 'src' => $constants[0][ $index ],
- 'value' => $constants[4][ $index ],
- 'parts' => array(
- $constants[1][ $index ],
- $constants[3][ $index ],
- $constants[5][ $index ],
- ),
- );
- }
- }
-
- if ( ! empty( $variables[0] ) && ! empty( $variables[1] ) && ! empty( $variables[2] ) && ! empty( $variables[3] ) && ! empty( $variables[4] ) ) {
- // Remove duplicate(s), last definition wins.
- $variables[2] = array_reverse( array_unique( array_reverse( $variables[2], true ) ), true );
- foreach ( $variables[2] as $index => $name ) {
- $configs['variable'][ $name ] = array(
- 'src' => $variables[0][ $index ],
- 'value' => $variables[4][ $index ],
- 'parts' => array(
- $variables[1][ $index ],
- $variables[3][ $index ],
- ),
- );
- }
- }
-
- return $configs;
- }
-
- /**
- * Saves new contents to the wp-config.php file.
- *
- * @throws Exception If the config file content provided is empty.
- * @throws Exception If there is a failure when saving the wp-config.php file.
- *
- * @param string $contents New config contents.
- *
- * @return bool
- */
- protected function save( $contents ) {
-
- if ( ! trim( $contents ) ) {
- throw new Exception( 'Cannot save the config file with empty contents.' );
- }
-
- if ( $contents === $this->wp_config_src ) {
- return false;
- }
-
- // Create backup before modifying wp-config.php
- $backup_path = $this->wp_config_path . '.metasync-backup-' . time();
- if ( ! copy( $this->wp_config_path, $backup_path ) ) {
- throw new Exception( 'Failed to create backup of wp-config.php' );
- }
-
- $result = file_put_contents( $this->wp_config_path, $contents, LOCK_EX );
-
- if ( false === $result ) {
- // Restore from backup on failure
- copy( $backup_path, $this->wp_config_path );
- unlink( $backup_path );
- throw new Exception( 'Failed to update the config file.' );
- }
-
- // Clean up old backups (keep last 5)
- $this->cleanup_old_backups();
-
- return true;
- }
-
- /**
- * Clean up old wp-config backup files, keeping only the last 5
- */
- protected function cleanup_old_backups() {
- $config_dir = dirname( $this->wp_config_path );
- $backup_pattern = basename( $this->wp_config_path ) . '.metasync-backup-*';
- $backups = glob( $config_dir . '/' . $backup_pattern );
-
- if ( count( $backups ) > 5 ) {
- // Sort by modification time (oldest first)
- usort( $backups, function( $a, $b ) {
- return filemtime( $a ) - filemtime( $b );
- });
-
- // Delete oldest backups, keep last 5
- $to_delete = array_slice( $backups, 0, count( $backups ) - 5 );
- foreach ( $to_delete as $old_backup ) {
- unlink( $old_backup );
- }
- }
- }
-
+ }
-}
+ return $this->wpConfigs[$type][$name]['value'];
+ }
+ /**
+ * Adds a config to the wp-config.php file.
+ *
+ * @throws WPConfigAnchorNotFoundException If the config placement anchor could not be located.
+ *
+ * @param string $type Config type (constant or variable).
+ * @param string $name Config name.
+ * @param string $value Config value.
+ * @param array $options Optional. Array of special behavior options.
+ *
+ * @return bool
+ */
+ public function add($type, $name, $value, array $options = [])
+ {
+ if (!is_string($value)) {
+ return false;
+ }
+
+ if ($this->exists($type, $name)) {
+ return false;
+ }
+
+ $defaults = [
+ 'raw' => false, // Display value in raw format without quotes.
+ 'anchor' => "/* That's all, stop editing!", // Config placement anchor string.
+ 'separator' => PHP_EOL, // Separator between config definition and anchor string.
+ 'placement' => 'before', // Config placement direction (insert before or after).
+ ];
+
+ list($raw, $anchor, $separator, $placement) = array_values(array_merge($defaults, $options));
+
+ $raw = (bool) $raw;
+ $anchor = (string) $anchor;
+ $separator = (string) $separator;
+ $placement = (string) $placement;
+
+ if (self::ANCHOR_EOF === $anchor) {
+ $contents = $this->wpConfigSrc . $this->normalize($type, $name, $this->formatValue($value, $raw));
+ } else {
+ if (false === strpos($this->wpConfigSrc, $anchor)) {
+ throw new WPConfigAnchorNotFoundException('Unable to locate placement anchor.');
+ }
+ $newSrc = $this->normalize($type, $name, $this->formatValue($value, $raw));
+ $newSrc = ('after' === $placement) ? $anchor . $separator . $newSrc : $newSrc . $separator . $anchor;
+ $contents = str_replace($anchor, $newSrc, $this->wpConfigSrc);
+ }
+
+ return $this->save($contents);
+ }
+
+ /**
+ * Updates an existing config in the wp-config.php file.
+ *
+ * @throws WPConfigInvalidValueException If the config value provided is not a string.
+ *
+ * @param string $type Config type (constant or variable).
+ * @param string $name Config name.
+ * @param string $value Config value.
+ * @param array $options Optional. Array of special behavior options.
+ *
+ * @return bool
+ */
+ public function update($type, $name, $value, array $options = [])
+ {
+ if (!is_string($value)) {
+ throw new WPConfigInvalidValueException('Config value must be a string.');
+ }
+
+ $defaults = [
+ 'add' => true, // Add the config if missing.
+ 'raw' => false, // Display value in raw format without quotes.
+ 'normalize' => true, // Normalize config output using WP Coding Standards.
+ ];
+
+ list($add, $raw, $normalize) = array_values(array_merge($defaults, $options));
+
+ $add = (bool) $add;
+ $raw = (bool) $raw;
+ $normalize = (bool) $normalize;
+
+ if (!$this->exists($type, $name)) {
+ return ($add) ? $this->add($type, $name, $value, $options) : false;
+ }
+
+ $oldSrc = $this->wpConfigs[$type][$name]['src'];
+ $oldValue = $this->wpConfigs[$type][$name]['value'];
+ $newValue = $this->formatValue($value, $raw);
+
+ if ($normalize) {
+ $newSrc = $this->normalize($type, $name, $newValue);
+ } else {
+ $newParts = $this->wpConfigs[$type][$name]['parts'];
+ $newParts[1] = str_replace($oldValue, $newValue, $newParts[1]); // Only edit the value part.
+ $newSrc = implode('', $newParts);
+ }
+
+ if ($value === "true") {
+ $contents = preg_replace(
+ sprintf('/(?<=^|;|<?phps|<?s)(s*?)%s/m', preg_quote(trim($oldSrc), '/')),
+ '$1' . str_replace('$', '$', trim($newSrc)),
+ $this->wpConfigSrc
+ );
+ } else {
+ if (!$this->exists($type, $name)) {
+ return $this->save('');
+ }
+
+ $pattern = sprintf('/(?<=^|;|<?phps|<?s)%ss*(S|$)/m', preg_quote($this->wpConfigs[$type][$name]['src'], '/'));
+ $contents = preg_replace($pattern, '$1', $this->wpConfigSrc);
+ }
+
+ return $this->save($contents);
+ }
+
+ /**
+ * Removes a config from the wp-config.php file.
+ *
+ * @param string $type Config type (constant or variable).
+ * @param string $name Config name.
+ *
+ * @return bool
+ */
+ public function remove($type, $name)
+ {
+ if (!$this->exists($type, $name)) {
+ return false;
+ }
+
+ $pattern = sprintf('/(?<=^|;|<?phps|<?s)%ss*(S|$)/m', preg_quote($this->wpConfigs[$type][$name]['src'], '/'));
+ $contents = preg_replace($pattern, '$1', $this->wpConfigSrc);
+
+ return $this->save($contents);
+ }
+
+ /**
+ * Applies formatting to a config value.
+ *
+ * @throws WPConfigInvalidValueException When a raw value is requested for an empty string.
+ *
+ * @param string $value Config value.
+ * @param bool $raw Display value in raw format without quotes.
+ *
+ * @return mixed
+ */
+ protected function formatValue($value, $raw)
+ {
+ if ($raw && '' === trim($value)) {
+ throw new WPConfigInvalidValueException('Raw value for empty string not supported.');
+ }
+
+ return ($raw) ? $value : var_export($value, true);
+ }
+
+ /**
+ * Normalizes the source output for a name/value pair.
+ *
+ * @throws WPConfigNormalizationException If the requested config type does not support normalization.
+ *
+ * @param string $type Config type (constant or variable).
+ * @param string $name Config name.
+ * @param mixed $value Config value.
+ *
+ * @return string
+ */
+ protected function normalize($type, $name, $value)
+ {
+ if ('constant' === $type) {
+ $placeholder = "define( '%s', %s );";
+ } elseif ('variable' === $type) {
+ $placeholder = '$%s = %s;';
+ } else {
+ throw new WPConfigNormalizationException("Unable to normalize config type '{$type}'.");
+ }
+
+ return sprintf($placeholder, $name, $value);
+ }
+
+ /**
+ * Parses the source of a wp-config.php file.
+ *
+ * @param string $src Config file source.
+ *
+ * @return array
+ */
+ protected function parseWpConfig($src)
+ {
+ $configs = [];
+ $configs['constant'] = [];
+ $configs['variable'] = [];
+
+ // Strip comments.
+ foreach (token_get_all($src) as $token) {
+ if (in_array($token[0], [T_COMMENT, T_DOC_COMMENT], true)) {
+ $src = str_replace($token[1], '', $src);
+ }
+ }
+
+ preg_match_all('/(?<=^|;|<?phps|<?s)(h*defines*(s*['"](w*?)['"]s*)(,s*(''|""|'.*?[^\\]'|".*?[^\\]"|.*?)s*)((?:,s*(?:true|false)s*)?)s*;)/ims', $src, $constants);
+ preg_match_all('/(?<=^|;|<?phps|<?s)(h*$(w+)s*=)(s*(''|""|'.*?[^\\]'|".*?[^\\]"|.*?)s*;)/ims', $src, $variables);
+
+ if (!empty($constants[0]) && !empty($constants[1]) && !empty($constants[2]) && !empty($constants[3]) && !empty($constants[4]) && !empty($constants[5])) {
+ foreach ($constants[2] as $index => $name) {
+ $configs['constant'][$name] = [
+ 'src' => $constants[0][$index],
+ 'value' => $constants[4][$index],
+ 'parts' => [
+ $constants[1][$index],
+ $constants[3][$index],
+ $constants[5][$index],
+ ],
+ ];
+ }
+ }
+
+ if (!empty($variables[0]) && !empty($variables[1]) && !empty($variables[2]) && !empty($variables[3]) && !empty($variables[4])) {
+ // Remove duplicate(s), last definition wins.
+ $variables[2] = array_reverse(array_unique(array_reverse($variables[2], true)), true);
+ foreach ($variables[2] as $index => $name) {
+ $configs['variable'][$name] = [
+ 'src' => $variables[0][$index],
+ 'value' => $variables[4][$index],
+ 'parts' => [
+ $variables[1][$index],
+ $variables[3][$index],
+ ],
+ ];
+ }
+ }
+
+ return $configs;
+ }
+
+ /**
+ * Saves new contents to the wp-config.php file.
+ *
+ * @throws WPConfigFileEmptyException If the config file content provided is empty.
+ * @throws WPConfigSaveException If there is a failure when saving the wp-config.php file.
+ *
+ * @param string $contents New config contents.
+ *
+ * @return bool
+ */
+ protected function save($contents)
+ {
+ if (!trim($contents)) {
+ throw new WPConfigFileEmptyException('Cannot save the config file with empty contents.');
+ }
+
+ if ($contents === $this->wpConfigSrc) {
+ return false;
+ }
+
+ // Create backup before modifying wp-config.php
+ $backupPath = $this->wpConfigPath . '.metasync-backup-' . time();
+ if (!copy($this->wpConfigPath, $backupPath)) {
+ throw new WPConfigSaveException('Failed to create backup of wp-config.php');
+ }
+
+ $result = file_put_contents($this->wpConfigPath, $contents, LOCK_EX);
+
+ if (false === $result) {
+ // Restore from backup on failure
+ copy($backupPath, $this->wpConfigPath);
+ unlink($backupPath);
+ throw new WPConfigSaveException('Failed to update the config file.');
+ }
+
+ // Clean up old backups (keep last 5)
+ $this->cleanupOldBackups();
+
+ return true;
+ }
+
+ /**
+ * Clean up old wp-config backup files, keeping only the last 5
+ *
+ * @return void
+ */
+ protected function cleanupOldBackups()
+ {
+ $configDir = dirname($this->wpConfigPath);
+ $backupPattern = basename($this->wpConfigPath) . '.metasync-backup-*';
+ $backups = glob($configDir . '/' . $backupPattern);
+
+ if (count($backups) > 5) {
+ // Sort by modification time (oldest first)
+ usort($backups, function ($a, $b) {
+ return filemtime($a) - filemtime($b);
+ });
+
+ // Delete oldest backups, keep last 5
+ $toDelete = array_slice($backups, 0, count($backups) - 5);
+ foreach ($toDelete as $oldBackup) {
+ unlink($oldBackup);
+ }
+ }
+ }
+}
class ConfigControllerMetaSync
{
const WPDD_DEBUGGING_PREDEFINED_CONSTANTS_STATE = 'dlct_data_initial';
private static $configfilePath;
-
+
protected $optionKey = 'debuglogconfigtool_updated_constant';
public $debugConstants = ['WP_DEBUG', 'WP_DEBUG_LOG', 'SCRIPT_DEBUG'];
- protected $config_file_manager;
+ protected $configFileManager;
private static $configArgs = [
'normalize' => true,
'raw' => true,
'add' => true,
];
-
+
public function __construct()
{
$this->initialize();
}
-
+
private function initialize()
{
self::$configfilePath = $this->getConfigFilePath();
- //set anchor for the constants to write
+ // Set anchor for the constants to write
$configContents = file_get_contents(self::$configfilePath);
if (false === strpos($configContents, "/* That's all, stop editing!")) {
preg_match('@$table_prefix = (.*);@', $configContents, $matches);
self::$configArgs['anchor'] = $matches[0] ?? '';
self::$configArgs['placement'] = 'after';
}
-
+
if (!is_writable(self::$configfilePath)) {
add_action('admin_notices', function () {
$class = 'notice notice-error is-dismissible';
@@ -472,62 +517,60 @@
});
return;
}
-
- $this->config_file_manager = new WPConfigTransformerMetaSync(self::$configfilePath);
+
+ $this->configFileManager = new WPConfigTransformerMetaSync(self::$configfilePath);
}
-
-
+
public function store()
{
try {
// Whitelist of allowed constants to prevent arbitrary constant modification
- $allowed_constants = ['WP_DEBUG', 'WP_DEBUG_LOG', 'WP_DEBUG_DISPLAY'];
-
+ $allowedConstants = ['WP_DEBUG', 'WP_DEBUG_LOG', 'WP_DEBUG_DISPLAY'];
+
$updatedConstants = [];
- $wp_debug_enabled = get_option('wp_debug_enabled', 'false');
- $wp_debug_log_enabled = get_option('wp_debug_log_enabled', 'false');
- $wp_debug_display_enabled = get_option('wp_debug_display_enabled', 'false');
- $constants = [
- 'WP_DEBUG' => [
- 'name' => 'WP_DEBUG',
- 'value' => ($wp_debug_enabled=='true'?true:false),
- 'info' => 'Enable WP_DEBUG mode',
- ],
- 'WP_DEBUG_LOG' => [
- 'name' => 'WP_DEBUG_LOG',
- 'value' => ($wp_debug_log_enabled=='true'?true:false),
- 'info' => 'Enable Debug logging to the /wp-content/debug.log file',
- ],
- 'WP_DEBUG_DISPLAY' => [
- 'name' => 'WP_DEBUG_DISPLAY',
- 'value' => ($wp_debug_display_enabled=='true'?true:false),
- 'info' => 'Disable or hide display of errors and warnings in html pages'
- ]
- ];
+ $wpDebugEnabled = get_option('wp_debug_enabled', 'false');
+ $wpDebugLogEnabled = get_option('wp_debug_log_enabled', 'false');
+ $wpDebugDisplayEnabled = get_option('wp_debug_display_enabled', 'false');
+ $constants = [
+ 'WP_DEBUG' => [
+ 'name' => 'WP_DEBUG',
+ 'value' => ($wpDebugEnabled === 'true' ? true : false),
+ 'info' => 'Enable WP_DEBUG mode',
+ ],
+ 'WP_DEBUG_LOG' => [
+ 'name' => 'WP_DEBUG_LOG',
+ 'value' => ($wpDebugLogEnabled === 'true' ? true : false),
+ 'info' => 'Enable Debug logging to the /wp-content/debug.log file',
+ ],
+ 'WP_DEBUG_DISPLAY' => [
+ 'name' => 'WP_DEBUG_DISPLAY',
+ 'value' => ($wpDebugDisplayEnabled === 'true' ? true : false),
+ 'info' => 'Disable or hide display of errors and warnings in html pages'
+ ]
+ ];
$this->maybeRemoveDeletedConstants($constants);
-
+
foreach ($constants as $constant) {
// Use sanitize_key instead of sanitize_title for constant names
$key = strtoupper(sanitize_key($constant['name']));
-
+
// Whitelist validation - only allow specific constants
- if (!in_array($key, $allowed_constants, true)) {
+ if (!in_array($key, $allowedConstants, true)) {
error_log('MetaSync: Attempted to modify non-whitelisted constant: ' . $key);
continue;
}
-
+
if (empty($key)) {
continue;
}
-
+
// Sanitize value - only allow boolean values
$value = is_bool($constant['value']) ? $constant['value'] : ($constant['value'] === 'true' || $constant['value'] === true);
$value = $value ? 'true' : 'false';
-
- $this->config_file_manager->update('constant', $key, $value, self::$configArgs);
+
+ $this->configFileManager->update('constant', $key, $value, self::$configArgs);
$updatedConstants[] = $constant;
}
-
} catch (Exception $e) {
error_log('MetaSync: Error updating wp-config.php - ' . $e->getMessage());
wp_send_json_error([
@@ -536,37 +579,34 @@
]);
}
}
-
+
public function exists($constant)
{
- return $this->config_file_manager->exists('constant', strtoupper($constant));
+ return $this->configFileManager->exists('constant', strtoupper($constant));
}
-
+
public function getValue($constant)
{
if ($this->exists(strtoupper($constant))) {
-
- return $this->config_file_manager->get_value('constant', strtoupper($constant));
+ return $this->configFileManager->getValue('constant', strtoupper($constant));
}
return null;
}
-
-
+
public function update($key, $value)
{
- try{
- //By default, when attempting to update a config that doesn't exist, one will be added.
+ try {
+ // By default, when attempting to update a config that doesn't exist, one will be added.
$option = self::$configArgs;
if (is_bool($value)) {
$value = $value ? 'true' : 'false';
}
- return $this->config_file_manager->update('constant', strtoupper($key), $value, $option);
- } catch (Exception $e){
-
+ return $this->configFileManager->update('constant', strtoupper($key), $value, $option);
+ } catch (Exception $e) {
+ return false;
}
-
}
-
+
public function getConfigFilePath()
{
$file = ABSPATH . 'wp-config.php';
@@ -577,21 +617,20 @@
}
return apply_filters('wp_dlct_config_file_manager_path', $file);
}
-
+
/**
- * remove deleted constant from config
- * @param $constants
+ * Remove deleted constant from config
+ *
+ * @param array $constants Array of constants.
+ *
+ * @return void
*/
protected function maybeRemoveDeletedConstants($constants)
{
- $previousSavedData = [];
$deletedConstant = array_diff(array_column($constants, 'name'), array_column($constants, 'name'));
- //die(json_encode($constants));
+
foreach ($deletedConstant as $item) {
- $this->config_file_manager->remove('constant', strtoupper($item));
+ $this->configFileManager->remove('constant', strtoupper($item));
}
}
-
}
-
-
--- a/metasync/admin/class-metasync-admin.php
+++ b/metasync/admin/class-metasync-admin.php
@@ -35,6 +35,7 @@
const SECTION_COMMON_META_SETTINGS = "common_meta_settings";
const SECTION_SOCIAL_META = "social_meta";
const SECTION_SEO_CONTROLS = "seo_controls";
+ const SECTION_SEO_CONTROLS_ADVANCED = "seo_controls_advanced";
const SECTION_PLUGIN_VISIBILITY = "plugin_visibility_settings";
/**
@@ -169,6 +170,7 @@
}
add_action('admin_menu', array($this, 'add_plugin_settings_page'));
+ add_action('admin_menu', array($this, 'add_import_external_data_page'));
add_action('admin_init', array($this, 'settings_page_init'));
add_filter('all_plugins', array($this,'metasync_plugin_white_label'));
add_filter( 'plugin_row_meta',array($this,'metasync_view_detials_url'),10,3);
@@ -235,6 +237,12 @@
# Add AJAX handler for theme switcher
add_action('wp_ajax_metasync_save_theme', array($this, 'ajax_save_theme'));
+
+ # Add AJAX handler for external data import
+ add_action('wp_ajax_metasync_import_external_data', array($this, 'ajax_import_external_data'));
+
+ # Add admin-post handler for exporting whitelabel settings (file download)
+ add_action('admin_post_metasync_export_whitelabel_settings', array($this, 'handle_export_whitelabel_settings'));
// Add REST API endpoint for ping
add_action('rest_api_init', array($this, 'register_ping_rest_endpoint'));
@@ -728,10 +736,16 @@
$searchatlas_api_key = isset($general_settings['searchatlas_api_key']) ? $general_settings['searchatlas_api_key'] : '';
$otto_pixel_uuid = isset($general_settings['otto_pixel_uuid']) ? $general_settings['otto_pixel_uuid'] : '';
+ // Only generate SSO nonce for administrators to prevent unauthorized access
+ $sso_nonce = '';
+ if (current_user_can('manage_options')) {
+ $sso_nonce = wp_create_nonce('metasync_sso_nonce');
+ }
+
wp_localize_script( $this->plugin_name, 'metaSync', array(
'ajax_url' => admin_url( 'admin-ajax.php' ),
'admin_url'=>admin_url('admin.php'),
- 'sso_nonce' => wp_create_nonce('metasync_sso_nonce'),
+ 'sso_nonce' => $sso_nonce,
'reset_auth_nonce' => wp_create_nonce('metasync_reset_auth_nonce'),
'dashboard_domain' => self::get_effective_dashboard_domain(),
'support_email' => Metasync::SUPPORT_EMAIL,
@@ -820,6 +834,79 @@
}
/**
+ * Add Import External Data page
+ */
+ public function add_import_external_data_page()
+ {
+ add_submenu_page(
+ null, // Hidden from menu, linked from other pages
+ 'Import External Data',
+ 'Import External Data',
+ 'manage_options',
+ 'metasync-import-external',
+ array($this, 'render_import_external_data_page')
+ );
+ }
+
+ /**
+ * Render Import External Data page
+ */
+ public function render_import_external_data_page()
+ {
+ require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-metasync-external-importer.php';
+ require_once plugin_dir_path(dirname(__FILE__)) . 'views/metasync-import-external-data.php';
+ }
+
+ /**
+ * AJAX handler for external data import
+ */
+ public function ajax_import_external_data()
+ {
+ check_ajax_referer('metasync_import_external_data', 'nonce');
+
+ if (!current_user_can('manage_options')) {
+ wp_send_json_error(['message' => 'Insufficient permissions.']);
+ }
+
+ $type = isset($_POST['type']) ? sanitize_text_field($_POST['type']) : '';
+ $plugin = isset($_POST['plugin']) ? sanitize_text_field($_POST['plugin']) : '';
+
+ if (empty($type) || empty($plugin)) {
+ wp_send_json_error(['message' => 'Missing required parameters.']);
+ }
+
+ require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-metasync-external-importer.php';
+
+ // Pass DB redirection resource if needed
+ $importer = new Metasync_External_Importer($this->db_redirection);
+ $result = ['success' => false, 'message' => 'Unknown import type.'];
+
+ switch ($type) {
+ case 'redirections':
+ $result = $importer->import_redirections($plugin);
+ break;
+ case 'sitemap':
+ $result = $importer->import_sitemap($plugin);
+ break;
+ case 'robots':
+ $result = $importer->import_robots($plugin);
+ break;
+ case 'indexation':
+ $result = $importer->import_indexation($plugin);
+ break;
+ case 'schema':
+ $result = $importer->import_schema($plugin);
+ break;
+ }
+
+ if ($result['success']) {
+ wp_send_json_success($result);
+ } else {
+ wp_send_json_error($result);
+ }
+ }
+
+ /**
* Conditional SSO Validation wrapper
* Only performs SSO validation when there's actually a token in the request
*/
@@ -1041,7 +1128,11 @@
*/
public function generate_sso_url()
{
-
+ // Check user capabilities - only administrators should be able to generate SSO URLs
+ if (!current_user_can('manage_options')) {
+ wp_send_json_error(array('message' => 'Insufficient permissions. Administrator access required.'));
+ return;
+ }
// Verify nonce for security
if (!wp_verify_nonce($_POST['nonce'], 'metasync_sso_nonce')) {
@@ -1099,6 +1190,12 @@
*/
public function check_sso_status()
{
+ // Check user capabilities - only administrators should be able to check SSO status
+ if (!current_user_can('manage_options')) {
+ wp_send_json_error(array('message' => 'Insufficient permissions. Administrator access required.'));
+ return;
+ }
+
// Verify nonce for security
if (!wp_verify_nonce($_POST['nonce'], 'metasync_sso_nonce')) {
wp_send_json_error(array('message' => 'Invalid nonce'));
@@ -1544,7 +1641,7 @@
// Check if OTTO transient cache class exists
if (!class_exists('Metasync_Otto_Transient_Cache')) {
echo '<div class="notice notice-error inline"><p>';
- echo '❌ <strong>Error:</strong> OTTO Transient Cache class not found.';
+ echo '❌ <strong>Error:</strong> Transient Cache class not found.';
echo '</p></div>';
return;
}
@@ -1554,13 +1651,13 @@
?>
<div style="margin-bottom: 30px;">
- <h3>🗄️ OTTO Transient Cache</h3>
- <p style="margin-bottom: 15px; color: #666;">
- Manage OTTO suggestions cache. Clearing cache will force fresh API calls on next page load.
+ <h3 style="color: #1d2327;">🗄️ Transient Cache</h3>
+ <p style="margin-bottom: 15px; color: #50575e;">
+ Manage suggestions cache. Clearing cache will force fresh API calls on next page load.
</p>
<div style="background: #f0f0f1; padding: 12px; border-radius: 4px; margin-bottom: 20px;">
- <strong>Current Cache Status:</strong>
+ <strong style="color: #1d2327;">Current Cache Status:</strong>
<span style="color: #2271b1;"><?php echo esc_html($cache_count); ?> cached entries</span>
</div>
@@ -1574,7 +1671,7 @@
if (!empty($url)) {
echo '✅ <strong>Success!</strong> Cleared cache for URL: <code>' . esc_html($url) . '</code> (' . $cleared_count . ' entries)';
} else {
- echo '✅ <strong>Success!</strong> Cleared entire OTTO transient cache (' . $cleared_count . ' entries)';
+ echo '✅ <strong>Success!</strong> Cleared entire transient cache (' . $cleared_count . ' entries)';
}
echo '</p></div>';
}
@@ -1589,12 +1686,12 @@
<!-- Clear Entire Cache -->
<div style="margin-bottom: 30px; padding: 20px; border: 1px solid #ddd; border-radius: 4px; background: #fff;">
- <h4 style="margin-top: 0;">1. Clear Entire Transient Cache</h4>
- <p style="color: #666; margin-bottom: 15px;">
- This will clear all OTTO transient cache entries (suggestions, locks, stale cache, rate limits).
+ <h4 style="margin-top: 0; color: #1d2327;">1. Clear Entire Transient Cache</h4>
+ <p style="color: #50575e; margin-bottom: 15px;">
+ This will clear all transient cache entries (suggestions, locks, stale cache, rate limits).
</p>
<form method="post" action="<?php echo esc_url(admin_url('admin.php?page=' . self::$page_slug . '&tab=advanced')); ?>"
- onsubmit="return confirm('Are you sure you want to clear the entire OTTO transient cache? This will force fresh API calls for all URLs.');">
+ onsubmit="return confirm('Are you sure you want to clear the entire transient cache? This will force fresh API calls for all URLs.');">
<input type="hidden" name="clear_otto_transient_cache_all" value="yes" />
<?php wp_nonce_field('metasync_clear_otto_cache_nonce', 'clear_otto_cache_nonce'); ?>
<?php submit_button('🗑️ Clear Entire Cache', 'primary', 'clear-otto-cache-all', false, array('class' => 'button button-primary')); ?>
@@ -1603,17 +1700,17 @@
<!-- Clear Cache by URL -->
<div style="padding: 20px; border: 1px solid #ddd; border-radius: 4px; background: #fff;">
- <h4 style="margin-top: 0;">2. Clear Cache by URL</h4>
- <p style="color: #666; margin-bottom: 15px;">
- Enter a specific URL to clear its cached OTTO suggestions. Use the full URL including protocol (e.g., https://example.com/page/).
+ <h4 style="margin-top: 0; color: #1d2327;">2. Clear Cache by URL</h4>
+ <p style="color: #50575e; margin-bottom: 15px;">
+ Enter a specific URL to clear its cached suggestions. Use the full URL including protocol (e.g., https://example.com/page/).
</p>
<form method="post" action="<?php echo esc_url(admin_url('admin.php?page=' . self::$page_slug . '&tab=advanced')); ?>">
<input type="hidden" name="clear_otto_transient_cache_url" value="yes" />
<?php wp_nonce_field('metasync_clear_otto_cache_nonce', 'clear_otto_cache_nonce'); ?>
<table class="form-table">
<tr>
- <th scope="row">
- <label for="otto_cache_url">URL to Clear</label>
+ <th scope="row" style="color: #1d2327;">
+ <label for="otto_cache_url" style="color: #1d2327;">URL to Clear</label>
</th>
<td>
<input type="url"
@@ -1623,7 +1720,7 @@
class="regular-text"
placeholder="https://example.com/page/"
required />
- <p class="description">Enter the full URL of the page whose cache you want to clear.</p>
+ <p class="description" style="color: #646970;">Enter the full URL of the page whose cache you want to clear.</p>
</td>
</tr>
</table>
@@ -1830,7 +1927,7 @@
exit;
}
} else {
- wp_redirect(admin_url('admin.php?page=' . self::$page_slug . '&tab=advanced&otto_cache_error=1&message=' . urlencode('OTTO Transient Cache class not found')));
+ wp_redirect(admin_url('admin.php?page=' . self::$page_slug . '&tab=advanced&otto_cache_error=1&message=' . urlencode('Transient Cache class not found')));
exit;
}
} else {
@@ -1892,7 +1989,7 @@
exit;
}
} else {
- wp_redirect(admin_url('admin.php?page=' . self::$page_slug . '&tab=advanced&otto_cache_error=1&message=' . urlencode('OTTO Transient Cache class not found')));
+ wp_redirect(admin_url('admin.php?page=' . self::$page_slug . '&tab=advanced&otto_cache_error=1&message=' . urlencode('Transient Cache class not found')));
exit;
}
} else {
@@ -4266,19 +4363,20 @@
// Check if password protection is needed
$password_protection_enabled = !empty($user_password);
- // Check validation status from session (session was started earlier in admin_init)
- $password_validated = isset($_SESSION['whitelabel_access_granted']) && $_SESSION['whitelabel_access_granted'] === true;
+ // Check validation status using Auth Manager
+ $auth = new Metasync_Auth_Manager('whitelabel', 1800);
+ $password_validated = $auth->has_access();
- // Set error message based on session status
+ // Set error message based on authentication status
if (isset($_POST['whitelabel_password_submit']) && !$password_validated) {
- if (isset($_SESSION['whitelabel_access_granted']) && $_SESSION['whitelabel_access_granted'] === false) {
- $password_error = 'Incorrect password. Please try again.';
- } else {
- $password_error = 'Security verification failed. Please try again.';
- }
+ $password_error = 'Incorrect password. Please try again.';
}
}
+ // Helper variable for displaying authenticated UI elements (lock buttons, success messages)
+ // This replaces all $_SESSION['whitelabel_access_granted'] checks throughout the page
+ $is_authenticated = $password_validated;
+
# Use whitelabel OTTO name if configured, fallback to 'OTTO'
$whitelabel_otto_name = Metasync::get_whitelabel_otto_name();
@@ -4407,14 +4505,14 @@
<h2 style="margin: 0;">🔧 General Configuration</h2>
<p style="color: var(--dashboard-text-secondary); margin: 5px 0 0 0;">Configure your <?php echo esc_html(Metasync::get_effective_plugin_name()); ?> API, plugin features, caching, and general settings.</p>
</div>
- <?php if ($hide_settings_enabled && !empty($user_password) && isset($_SESSION['whitelabel_access_granted']) && $_SESSION['whitelabel_access_granted'] === true): ?>
+ <?php if ($hide_settings_enabled && !empty($user_password) && $is_authenticated): ?>
<div>
<?php $this->render_lock_button('general'); ?>
</div>
<?php endif; ?>
</div>
- <?php if ($hide_settings_enabled && !empty($user_password) && isset($_SESSION['whitelabel_access_granted']) && $_SESSION['whitelabel_access_granted'] === true): ?>
+ <?php if ($hide_settings_enabled && !empty($user_password) && $is_authenticated): ?>
<div style="background: #d4edda; border: 1px solid #c3e6cb; color: #155724; padding: 12px; border-radius: 4px; margin-bottom: 20px;">
<strong>✅ Access Granted:</strong> You have successfully authenticated and can now modify settings.
</div>
@@ -4447,14 +4545,14 @@
<h2>🎨 White Label Branding</h2>
<p style="color: var(--dashboard-text-secondary); margin: 5px 0 0 0;">Customize the plugin appearance with your own branding and logo.</p>
</div>
- <?php if ($password_protection_enabled && isset($_SESSION['whitelabel_access_granted']) && $_SESSION['whitelabel_access_granted'] === true): ?>
+ <?php if ($password_protection_enabled && $is_authenticated): ?>
<div>
<?php $this->render_lock_button('whitelabel'); ?>
</div>
<?php endif; ?>
</div>
- <?php if ($password_protection_enabled && isset($_SESSION['whitelabel_access_granted']) && $_SESSION['whitelabel_access_granted'] === true): ?>
+ <?php if ($password_protection_enabled && $is_authenticated): ?>
<div style="background: #d4edda; border: 1px solid #c3e6cb; color: #155724; padding: 12px; border-radius: 4px; margin-bottom: 20px; display: flex; justify-content: space-between; align-items: center;">
<div>
<strong>✅ Access Granted:</strong> You have successfully authenticated and can now modify white label settings.
@@ -4469,6 +4567,16 @@
?>
</div>
+ <!-- Export Whitelabel Settings section -->
+ <div class="dashboard-card">
+ <h2>📦 Export Whitelabel Plugin</h2>
+ <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Export the entire plugin with all whitelabel settings pre-configured. This creates a complete plugin zip file ready for installation on another WordPress site.</p>
+ <button type="button" class="button button-primary" id="metasync-export-whitelabel-btn" style="padding: 10px 20px; font-size: 14px; font-weight: 600;">
+ 📥 Export Plugin with Whitelabel Settings
+ </button>
+ <p class="description" style="margin-top: 10px;">This will create a zip file containing the complete plugin with all your whitelabel settings included. Upload and install this zip file on another WordPress site via Plugins → Add New → Upload Plugin. All whitelabel configurations will be automatically applied upon activation.</p>
+ </div>
+
<!-- Plugin Settings section as separate card -->
<div class="dashboard-card">
<h2>🔧 Plugin Settings</h2>
@@ -4668,6 +4776,46 @@
$('#metasync-custom-modal').fadeOut(200);
}
});
+
+ // Handle export whitelabel settings button
+ $('#metasync-export-whitelabel-btn').on('click', function(e) {
+ e.preventDefault();
+
+ var $button = $(this);
+ var originalText = $button.html();
+
+ // Disable button and show loading state
+ $button.prop('disabled', true).html('⏳ Exporting...');
+
+ // Create a form to submit the export request
+ var form = $('<form>', {
+ 'method': 'POST',
+ 'action': '<?php echo esc_js(admin_url('admin-post.php')); ?>',
+ 'target': '_blank'
+ });
+
+ form.append($('<input>', {
+ 'type': 'hidden',
+ 'name': 'action',
+ 'value': 'metasync_export_whitelabel_settings'
+ }));
+
+ form.append($('<input>', {
+ 'type': 'hidden',
+ 'name': '_wpnonce',
+ 'value': '<?php echo wp_create_nonce('metasync_export_whitelabel'); ?>'
+ }));
+
+ // Append form to body, submit, then remove
+ $('body').append(form);
+ form.submit();
+
+ // Remove form after a short delay
+ setTimeout(function() {
+ form.remove();
+ $button.prop('disabled', false).html(originalText);
+ }, 2000);
+ });
});
</script>
<?php
@@ -4680,13 +4828,13 @@
<h2 style="margin: 0;">🧰 Advanced Settings</h2>
<p style="color: var(--dashboard-text-secondary); margin: 6px 0 0 0;">Technical utilities for troubleshooting and connectivity checks.</p>
</div>
- <?php if ($hide_settings_enabled && !empty($user_password) && isset($_SESSION['whitelabel_access_granted']) && $_SESSION['whitelabel_access_granted'] === true): ?>
+ <?php if ($hide_settings_enabled && !empty($user_password) && $is_authenticated): ?>
<div>
<?php $this->render_lock_button('advanced'); ?>
</div>
<?php endif; ?>
</div>
- <?php if ($hide_settings_enabled && !empty($user_password) && isset($_SESSION['whitelabel_access_granted']) && $_SESSION['whitelabel_access_granted'] === true): ?>
+ <?php if ($hide_settings_enabled && !empty($user_password) && $is_authenticated): ?>
<div style="background: #d4edda; border: 1px solid #c3e6cb; color: #155724; padding: 12px; border-radius: 4px; margin-top: 16px;">
<strong>✅ Access Granted:</strong> You can access advanced settings.
</div>
@@ -4707,8 +4855,8 @@
<summary style="cursor:pointer; list-style:none;">
<div style="display:flex; justify-content: space-between; align-items:center;">
<div style="flex:1;">
- <h2 style="margin: 0;">🗄️ OTTO Transient Cache Management</h2>
- <p style="color: var(--dashboard-text-secondary); margin: 6px 0 0 0;">Clear OTTO suggestions cache to force fresh API calls.</p>
+ <h2 style="margin: 0;">🗄️ Transient Cache Management</h2>
+ <p style="color: var(--dashboard-text-secondary); margin: 6px 0 0 0;">Clear suggestions cache to force fresh API calls.</p>
</div>
</div>
</summary>
@@ -5383,7 +5531,7 @@
// Get whitelabel logo from the centralized whitelabel settings
$whitelabel_logo = Metasync::get_whitelabel_logo();
- $is_whitelabel = $whitelabel_settings['is_whitelabel'];
+ $is_whitelabel = isset($whitelabel_settings['is_whitelabel']) ? $whitelabel_settings['is_whitelabel'] : false;
// Get the display title - use effective plugin name if no page title provided
$effective_plugin_name = Metasync::get_effective_plugin_name();
@@ -5499,7 +5647,7 @@
# new field added hide_dashboard_framework
# new fields added for disabling meta boxes on post/page edit screens
- $bool_fields = ['enable_schema', 'enable_metadesc', 'otto_enable', 'otto_disable_on_loggedin', 'disable_single_signup_login', 'hide_dashboard_framework', 'show_admin_bar_status', 'enable_auto_updates', 'di