slug = get_stylesheet(); $this->setting_page = get_option( $this->slug . '_backup_settings', false ); $this->hash = $this->calculate_hash(); if ( ! $this->setting_page || null === get_post( $this->setting_page ) ) { $args = array( 'post_title' => $theme->get( 'Name' ) . ' Backup Settings', 'post_status' => 'draft', 'post_type' => 'page', 'post_author' => 0, ); $this->setting_page = wp_insert_post( $args ); if ( ! is_wp_error( $this->setting_page ) ) { update_option( $this->slug . '_backup_settings', $this->setting_page ); } } /** * Add a notice, inform user that this page is only for backup purposes */ add_action( 'add_meta_boxes', array( $this, 'add_notice_to_page' ), 10, 2 ); /** * We need to keep this page as draft, forever. */ add_action( 'admin_init', array( $this, 'check_page_status' ) ); /** * We need to use this hook so we have a reference of the fields that are required as back up */ add_action( 'customize_save_after', array( $this, 'backup_settings' ) ); /** * Save page builder */ add_action( 'customize_save_after', array( $this, 'save_page_builder' ) ); /** * Disable the form editor if we're in production */ add_action( 'edit_form_after_title', array( $this, 'disable_front_page_editor' ) ); } /** * @since 1.0.0 * @return Epsilon_Content_Backup */ public static function get_instance() { if ( is_null( self::$_instance ) ) { self::$_instance = new self(); } return self::$_instance; } /** * Registers a field in the "backup" collection * * @since 1.0.0 */ public function add_field( $id, $args ) { $this->fields[ $id ] = $args; } /** * Pages who should be backed up * * @param $page_id * @param $id * @param $args */ public function add_pages( $page_id, $id, $args ) { $this->pages[ $page_id ] = array( 'id' => $page_id, 'field_id' => $id, 'fields' => $args, ); } /** * Disables the frontend editor * * @since 1.3.4 */ public function disable_front_page_editor( $post ) { if ( true === WP_DEBUG ) { return false; } if ( $this->setting_page == $post->ID ) { remove_post_type_support( $post->post_type, 'editor' ); } } /** * Calculates the hash of the settings */ private function calculate_hash() { $hash = array( 'theme_mods' => md5( json_encode( get_option( 'theme_mods_' . $this->slug ) ) ), 'post_meta' => md5( json_encode( get_post_meta( $this->setting_page ) ) ), ); return $hash; } /** * Check the status of the settings page, it should always be draft * * @since 1.0.0 */ public function check_page_status() { $post = get_post( $this->setting_page ); if ( 'draft' !== $post->post_status ) { $settings = array( 'ID' => $this->setting_page, 'post_status' => 'draft', ); wp_update_post( $settings ); } } /** * Adds a notice to the page * * @since 1.0.0 */ public function add_notice_to_page( $post_type, $post ) { $continue = false; if ( 'page' !== $post_type ) { return; } /** * Need to make sure we are in a page that has content saved in the customizer * * Verify the last element of the explode meta, * if it's the same as the post ID it means we're doing it right */ $meta = get_post_meta( $post->ID ); foreach ( $meta as $key => $value ) { $key = explode( '_', $key ); if ( end( $key ) === $post->ID ) { $continue = true; } } if ( $this->setting_page === $post->ID ) { $continue = true; } if ( ! $continue ) { return; } $notifications = Epsilon_Notifications::get_instance(); $notifications->add_notice( array( 'id' => $this->slug . '_content_backup', 'type' => 'notice notice-info', 'message' => '
' . esc_html__( 'This page contains the content created by the customizer.', 'epsilon-framework' ) . '
', ) ); } /** * @since 1.0.0 * * @param $manager WordPress Customizer Manager */ public function backup_settings( $manager ) { $check = $this->check_hash(); $this->manager = $manager; if ( $check['status'] ) { return; }; $settings = array( 'ID' => $this->setting_page, 'post_content' => $this->parse_content(), ); wp_update_post( $settings ); } /** * @since 1.0.0 * * @param $manager WordPress Customizer Manager */ public function save_page_builder( $manager ) { $this->manager = $manager; $check = $this->check_advanced_hash(); $this->mode = 'post_meta'; foreach ( $this->pages as $page ) { if ( $check[ $page['id'] ]['status'] ) { continue; }; $meta = get_post_meta( $page['id'], $page['field_id'], true ); if ( empty( $meta[ $page['field_id'] ] ) ) { continue; } $settings = array( 'ID' => $page['id'], 'post_content' => $this->parse_content_advanced( $page ), ); wp_update_post( $settings ); }; } /** * @since 1.0.0 * @return array */ private function check_hash() { $arr = array( 'status' => true, ); $temp = reset( $this->fields ); /** * In case we save options as post_meta, we need to use that particular hash */ if ( is_array( $temp ) && isset( $temp['save_as_meta'] ) && $this->setting_page === $temp['save_as_meta'] ) { $this->mode = 'post_meta'; } $last_known_hash = get_transient( $this->slug . '_hash_update' ); if ( false === $last_known_hash ) { set_transient( $this->slug . '_hash_update', $this->hash[ $this->mode ], 5 * MINUTE_IN_SECONDS ); } if ( $last_known_hash !== $this->hash[ $this->mode ] ) { $arr['status'] = false; } return $arr; } /** * @since 1.0.0 * @return array */ private function check_advanced_hash() { $arr = array(); foreach ( $this->pages as $page ) { $arr[ $page['id'] ] = array( 'status' => true, ); $last_known_hash = get_transient( $this->slug . '_' . $page['id'] . '_hash_update' ); $hash = md5( json_encode( get_post_meta( $page['id'] ) ) ); if ( false === $last_known_hash ) { set_transient( $this->slug . '_' . $page['id'] . '_hash_update', $hash, 5 * MINUTE_IN_SECONDS ); } if ( $last_known_hash !== $hash ) { $arr[ $page['id'] ] = array( 'status' => false, ); } } return $arr; } /** * @since 1.2.0 * @return string */ private function parse_content_advanced( $page ) { $content = ''; $collection = array(); $options = get_post_meta( $page['id'] ); foreach ( $page['fields'] as $field ) { $collection[ $page['field_id'] ] = array( 'id' => $page['field_id'], 'content' => get_post_meta( $page['id'], $page['field_id'], true ), 'type' => 'epsilon-section-repeater', ); } foreach ( $collection as $field => $props ) { $content .= $this->_parse_content( $props ); } return $content; } /** * @since 1.0.0 * @return string */ private function parse_content() { $content = ''; $collection = array(); switch ( $this->mode ) { case 'post_meta': $options = get_post_meta( $this->setting_page ); foreach ( $this->fields as $id => $field ) { if ( array_key_exists( $id, $options ) ) { $collection[ $id ] = array( 'id' => $id, 'content' => get_post_meta( $this->setting_page, $id, true ), 'type' => $field['type'], ); } } break; default: $options = get_option( 'theme_mods_' . $this->slug ); foreach ( $this->fields as $id => $field ) { if ( array_key_exists( $id, $options ) ) { $collection[ $id ] = array( 'id' => $id, 'content' => get_theme_mod( $id ), 'type' => $field['type'], ); } } break; } foreach ( $collection as $field => $props ) { $content .= $this->_parse_content( $props ); } return $content; } /** * @since 1.0.0 * @return string; */ private function _parse_content( $field ) { if ( empty( $field['content'] ) ) { return ''; } $control = $this->manager->get_control( $field['id'] ); $content = ''; if ( 'post_meta' === $this->mode ) { $field['content'] = $field['content'][ $field['id'] ]; } switch ( $field['type'] ) { case 'epsilon-section-repeater': foreach ( $field['content'] as $single_section ) { $content .= '' . "\n"; foreach ( $single_section as $id => $val ) { $args = array( 'val' => $val, 'id' => $id, 'fields' => $control->repeatable_sections[ $single_section['type'] ]['fields'], ); $condition = $this->check_backup_condition( $args ); if ( ! $condition ) { continue; } $content .= $this->create_content_value( $args['val'], $args['fields'][ $id ]['type'] ); } $content .= '' . "\n \n"; } $content .= "\n \n"; break; case 'epsilon-repeater': $content .= $this->_format_default( $control, $field['content'], $control->id ); break; default: $content .= "\n"; $content .= $field['content']; $content .= "\n \n"; break; }// End switch(). return $content; } /** * Checks if we need to generate backup for this item * * @param $args Array Array of arguments. * * @return bool */ private function check_backup_condition( $args ) { /** * Empty values don't need to be saved */ if ( empty( $args['val'] ) ) { return false; } /** * Id of the field doesn't need saving */ if ( 'type' === $args['id'] ) { return false; } /** * Design related items should not be saved */ $skip = array( 'epsilon-customizer-navigation', 'epsilon-icon-picker', 'epsilon-toggle', 'epsilon-slider', 'epsilon-color-picker', 'select', 'selectize', 'epsilon-button-group', ); /** * Customization fields, should bot be backedup */ if ( ! array_key_exists( $args['id'], $args['fields'] ) ) { return false; } if ( in_array( $args['fields'][ $args['id'] ]['type'], $skip ) ) { return false; } /** * If conditions are false, we return true */ return true; } /** * Parse the value and create "readable" content. * * * @param $value array|string Can be both. * @param $type string Type of field we are saving content. * * @return string */ private function create_content_value( $value, $type ) { switch ( $type ) { case 'epsilon-image': return '