['logsRetention'] ) ? $options['searchAppearance']['advanced']['blockArgs']['logsRetention'] : null; $oldLogsRetention = aioseo()->options->searchAppearance->advanced->blockArgs->logsRetention; // Remove category base. $removeCategoryBase = isset( $options['searchAppearance']['advanced']['removeCategoryBase'] ) ? $options['searchAppearance']['advanced']['removeCategoryBase'] : null; $removeCategoryBaseOld = aioseo()->options->searchAppearance->advanced->removeCategoryBase; $options = $this->maybeRemoveUnfilteredHtmlFields( $options ); $this->init(); if ( ! is_array( $options ) ) { return; } $this->sanitizeEmailSummary( $options ); // First, recursively replace the new options into the cached state. // It's important we use the helper method since we want to replace populated arrays with empty ones if needed (when a setting was cleared out). $cachedOptions = aioseo()->core->optionsCache->getOptions( $this->optionsName ); $dbOptions = aioseo()->helpers->arrayReplaceRecursive( $cachedOptions, $this->addValueToValuesArray( $cachedOptions, $options, [], true ) ); // Now, we must also intersect both arrays to delete any individual keys that were unset. // We must do this because, while arrayReplaceRecursive will update the values for keys or empty them out, // it will keys that aren't present in the replacement array unaffected in the target array. $dbOptions = aioseo()->helpers->arrayIntersectRecursive( $dbOptions, $this->addValueToValuesArray( $cachedOptions, $options, [], true ), 'value' ); if ( isset( $options['social']['profiles']['additionalUrls'] ) ) { $dbOptions['social']['profiles']['additionalUrls'] = preg_replace( '/\h/', "\n", $options['social']['profiles']['additionalUrls'] ); } $newOptions = ! empty( $options['sitemap']['html'] ) ? $options['sitemap']['html'] : null; if ( ! empty( $newOptions ) && aioseo()->options->sitemap->html->enable ) { $newOptions = ! empty( $options['sitemap']['html'] ) ? $options['sitemap']['html'] : null; $pageUrl = wp_parse_url( $newOptions['pageUrl'] ); $path = ! empty( $pageUrl['path'] ) ? untrailingslashit( $pageUrl['path'] ) : ''; if ( $path ) { $existingPage = get_page_by_path( $path, OBJECT ); if ( is_object( $existingPage ) ) { // If the page exists, don't override the previous URL. $options['sitemap']['html']['pageUrl'] = $oldHtmlSitemapUrl; } } } // Update the cache state. aioseo()->core->optionsCache->setOptions( $this->optionsName, $dbOptions ); // Update localized options. update_option( $this->optionsName . '_localized', $this->localized ); // Finally, save the new values to the DB. $this->save( true ); // If phone settings have changed, let's see if we need to dump the phone number notice. if ( $phoneNumberOptions && $phoneNumberOptions !== $oldPhoneOption ) { $notification = Models\Notification::getNotificationByName( 'v3-migration-schema-number' ); if ( $notification->exists() ) { Models\Notification::deleteNotificationByName( 'v3-migration-schema-number' ); } } // If sitemap settings were changed, static files need to be regenerated. if ( ! empty( $deprecatedGeneralSitemapOptions ) && ! empty( $generalSitemapOptions ) ) { if ( ( aioseo()->helpers->arraysDifferent( $oldGeneralSitemapOptions, $generalSitemapOptions ) || aioseo()->helpers->arraysDifferent( $oldDeprecatedGeneralSitemapOptions, $deprecatedGeneralSitemapOptions ) ) && $generalSitemapOptions['advancedSettings']['enable'] && ! $deprecatedGeneralSitemapOptions['advancedSettings']['dynamic'] ) { aioseo()->sitemap->scheduleRegeneration(); } } // Add or remove schedule for clearing crawl cleanup logs. if ( ! empty( $logsRetention ) && $oldLogsRetention !== $logsRetention ) { aioseo()->crawlCleanup->scheduleClearingLogs(); } if ( ! empty( $sitemapOptions ) ) { aioseo()->searchStatistics->sitemap->maybeSync( $oldSitemapOptions, $sitemapOptions ); } if ( null !== $removeCategoryBase && $removeCategoryBase !== $removeCategoryBaseOld ) { aioseo()->options->flushRewriteRules(); } // This is required in order for the Pro options to be refreshed before they save data again. $this->refresh(); } /** * Sanitizes the `emailSummary` option. * * @since 4.7.2 * * @param array $options All options, passed by reference. * @return void */ private function sanitizeEmailSummary( &$options ) { foreach ( ( $options['advanced']['emailSummary']['recipients'] ?? [] ) as $k => &$recipient ) { $recipient['email'] = is_email( $recipient['email'] ); // Remove empty emails. if ( empty( $recipient['email'] ) ) { unset( $options['advanced']['emailSummary']['recipients'][ $k ] ); continue; } // Remove duplicate emails. $emails = array_column( $options['advanced']['emailSummary']['recipients'], 'email' ); $emails = array_count_values( $emails ); if ( $emails[ $recipient['email'] ] > 1 ) { unset( $options['advanced']['emailSummary']['recipients'][ $k ] ); } } } /** * If the user does not have access to unfiltered HTML, we need to remove them from saving. * * @since 4.0.0 * * @param array $options An array of options. * @return array An array of options. */ private function maybeRemoveUnfilteredHtmlFields( $options ) { if ( current_user_can( 'unfiltered_html' ) ) { return $options; } if ( ! empty( $options['webmasterTools'] ) && isset( $options['webmasterTools']['miscellaneousVerification'] ) ) { unset( $options['webmasterTools']['miscellaneousVerification'] ); } if ( ! empty( $options['rssContent'] ) && isset( $options['rssContent']['before'] ) ) { unset( $options['rssContent']['before'] ); } if ( ! empty( $options['rssContent'] ) && isset( $options['rssContent']['after'] ) ) { unset( $options['rssContent']['after'] ); } return $options; } /** * Indicate we need to flush rewrite rules on next load. * * @since 4.0.17 * * @return void */ public function flushRewriteRules() { update_option( 'aioseo_flush_rewrite_rules_flag', true ); } /** * Flush rewrite rules if needed. * * @since 4.0.17 * * @return void */ public function maybeFlushRewriteRules() { if ( get_option( 'aioseo_flush_rewrite_rules_flag' ) ) { flush_rewrite_rules(); delete_option( 'aioseo_flush_rewrite_rules_flag' ); } } }