setHelpers(); $this->setOptions(); $this->initHooks(); } /** * Fires immediately after a post is deleted from the database. * Hooked into `deleted_post` action hook. * * @since 4.4.0 * * @param int $postId Post ID. * @return void */ public function deletedPostCallback( $postId ) { $objectRevisions = new ObjectRevisions( $postId ); $objectRevisions->deleteRevisions(); } /** * Fires after a term is deleted from the database. * Hooked into `delete_term` action hook. * * @since 4.4.0 * * @param int $termId Term ID. * @return void */ public function deleteTermCallback( $termId ) { $objectRevisions = new ObjectRevisions( $termId, 'term' ); $objectRevisions->deleteRevisions(); } /** * Fires once an AIOSEO post has been saved. * Hooked into `aioseo_insert_post` action hook. * * @since 4.4.0 * * @param int $postId Post ID. * @return void */ public function aioseoInsertPostCallback( $postId ) { // Bail if this callback is not being carried under certain action hooks or the request was not to the REST API, // and prevent unintended revisions from being created. if ( ! doing_action( 'add_attachment' ) && ! doing_action( 'edit_attachment' ) && ! doing_action( 'save_post' ) && ! aioseo()->helpers->isRestApiRequest() ) { return; } $wpPost = get_post( $postId ); if ( ! is_a( $wpPost, 'WP_Post' ) ) { return; } $this->maybeAddRevision( $wpPost->ID ); } /** * Fires once an AIOSEO term has been saved. * Hooked into `aioseo_insert_term` action hook. * * @since 4.4.0 * * @param int $termId Term ID. * @return void */ public function aioseoInsertTermCallback( $termId ) { // Bail if this callback is not being carried under the `edit_term` action hook or the request was not to the REST API and prevent unintended revisions from being created. if ( ! doing_action( 'edit_term' ) && ! aioseo()->helpers->isRestApiRequest() ) { return; } $wpTerm = get_term( $termId ); if ( ! is_a( $wpTerm, 'WP_Term' ) ) { return; } $this->maybeAddRevision( $wpTerm->term_id, 'term' ); } /** * Retrieve the per-object database revisions limit (license level based). * * @since 4.4.0 * * @return int The amount of revisions allowed for the activated license. */ public function getLicenseRevisionsLimit() { if ( ! aioseo()->license->hasCoreFeature( 'seo-revisions', 'revisions' ) ) { return 0; } $limit = (int) aioseo()->license->getCoreFeatureValue( 'seo-revisions', 'revisions' ); if ( ! $limit ) { $limit = 0; } return $limit; } /** * Returns the data for Vue. * * @since 4.4.0 * @version 4.8.3 Added params `$objectId` and `$objectType`. * * @param int|null $objectId The object ID. * @param string $objectType The object type (post or term). Default: 'post'. * @return array The data. */ public function getVueDataEdit( $objectId = null, $objectType = 'post' ) { $objectId = $objectId ?: absint( get_the_ID() ); if ( ! empty( $_GET['tag_ID'] ) ) { // phpcs:ignore HM.Security.NonceVerification.Recommended $objectId = absint( $_GET['tag_ID'] ); // phpcs:ignore HM.Security.NonceVerification.Recommended $objectType = 'term'; } $objectRevisions = new ObjectRevisions( $objectId, $objectType ); return [ 'currentUser' => $this->getVueDataCurrentUserMeta(), 'items' => $objectRevisions->getFormattedRevisions( [ 'limit' => aioseo()->seoRevisions->options['revisions']['per_page'] ] ), 'itemsLimit' => $this->getLicenseRevisionsLimit(), 'itemsTotalCount' => $objectRevisions->getCount(), 'noteMaxlength' => aioseo()->seoRevisions->options['note']['maxlength'] ]; } /** * Returns the data for Vue. * * @since 4.4.0 * * @return array The data. */ public function getVueDataCompare() { $data = [ 'currentUser' => $this->getVueDataCurrentUserMeta(), 'error' => '' ]; $itemToId = ! empty( $_GET['to'] ) ? absint( $_GET['to'] ) : 0; // phpcs:ignore HM.Security.NonceVerification.Recommended $itemTo = new ProModels\SeoRevision( $itemToId ); if ( ! $itemTo->exists() ) { // Translators: 1 - The requested revision ID. $data['error'] = sprintf( esc_html__( 'The revision %1$d does not exist.', 'aioseo-pro' ), $itemToId ); return $data; } $itemFromId = ! empty( $_GET['from'] ) ? absint( $_GET['from'] ) : 0; // phpcs:ignore HM.Security.NonceVerification.Recommended if ( $itemFromId ) { $itemFrom = new ProModels\SeoRevision( $itemFromId ); if ( ! $itemFrom->exists() ) { // Translators: 1 - The requested revision ID. $data['error'] = sprintf( esc_html__( 'The revision %1$d does not exist.', 'aioseo-pro' ), $itemFromId ); return $data; } } if ( $itemFromId >= $itemToId ) { // Translators: 1 - The requested revision ID (old), 2 - The requested revision ID (new). $data['error'] = sprintf( esc_html__( 'The revision %1$d is greater than or equal to the revision %2$d.', 'aioseo-pro' ), $itemFromId, $itemToId ); return $data; } $objectRevisions = new ObjectRevisions( $itemTo->object_id, $itemTo->object_type ); $itemFrom = $itemFrom ?? $objectRevisions->getPreviousRevision( $itemTo->id ); return array_merge( $data, [ 'items' => $objectRevisions->getFormattedRevisions(), 'itemFrom' => $itemFrom->exists() ? $itemFrom->formatRevision() : null, 'itemTo' => $itemTo->formatRevision(), 'itemContext' => ! empty( $_GET['context'] ) ? sanitize_text_field( wp_unslash( $_GET['context'] ) ) : '', // phpcs:ignore HM.Security.NonceVerification.Recommended 'noteMaxlength' => aioseo()->seoRevisions->options['note']['maxlength'] ] ); } /** * Maybe create a new revision. * * @since 4.4.0 * * @param int $objectId The object ID. * @param string $objectType The object type (post or term). Default: 'post'. * @return void */ private function maybeAddRevision( $objectId, $objectType = 'post' ) { $objectRevisions = new ObjectRevisions( $objectId, $objectType ); $aioseoObject = $objectRevisions->getAioseoObject(); if ( ! is_object( $aioseoObject ) ) { return; } $newRevisionData = []; foreach ( aioseo()->seoRevisions->getEligibleFields() as $key => $label ) { if ( ! property_exists( $aioseoObject, $key ) ) { continue; } $newRevisionData[ $key ] = $aioseoObject->$key; } $this->helpers->parseNewRevisionData( $newRevisionData ); if ( empty( $newRevisionData ) || ! $objectRevisions->canAddRevision( $newRevisionData ) ) { return; } $currentUserId = get_current_user_id(); // If this creation was triggered by e.g. WP CLI a user ID might not have been set. In this case we bail. if ( ! $currentUserId ) { return; } $objectRevisions->addRevision( $newRevisionData, $currentUserId ); } /** * Register SEO Revisions related hooks. * * @since 4.4.0 * * @return void */ private function initHooks() { add_action( 'deleted_post', [ $this, 'deletedPostCallback' ] ); add_action( 'delete_term', [ $this, 'deleteTermCallback' ] ); if ( ! aioseo()->license->hasCoreFeature( 'seo-revisions' ) ) { return; } add_action( 'aioseo_insert_post', [ $this, 'aioseoInsertPostCallback' ] ); add_action( 'aioseo_insert_term', [ $this, 'aioseoInsertTermCallback' ] ); } /** * Set {@see self::$helpers}. * * @since 4.4.0 * * @return void */ private function setHelpers() { $this->helpers = new Helpers(); } /** * Retrieves which post/term AIOSEO fields are to be watched and saved in the database. * * @since 4.4.0 * @version 4.7.7 Renamed from `setEligibleFields` to `getEligibleFields`. * * @return array The eligible fields. */ public function getEligibleFields() { $lateFields = $this->getLateEligibleFields(); // Keys with no value serve to compare data later and make sure the column changed (and they have "special" columns under the comparison page). return [ 'title' => __( 'SEO Title', 'aioseo-pro' ), 'description' => __( 'SEO Description', 'aioseo-pro' ), 'keyphrases' => '', // 'focus' and 'additional' aren't AIOSEO DB columns. 'focus' => __( 'Focus Keyword', 'aioseo-pro' ), 'additional' => __( 'Additional Keywords', 'aioseo-pro' ), 'og_title' => __( 'Facebook Title', 'aioseo-pro' ), 'og_description' => __( 'Facebook Description', 'aioseo-pro' ), 'og_object_type' => __( 'Facebook Object Type', 'aioseo-pro' ), 'og_video' => __( 'Facebook Video URL', 'aioseo-pro' ), 'og_image_type' => __( 'Facebook Image Source', 'aioseo-pro' ), 'og_image_custom_url' => __( 'Facebook Custom Image URL', 'aioseo-pro' ), 'og_article_section' => __( 'Facebook Article Section', 'aioseo-pro' ), 'og_article_tags' => __( 'Facebook Article Tags', 'aioseo-pro' ), 'twitter_use_og' => __( 'Use Data from Facebook Tab', 'aioseo-pro' ), 'twitter_card' => __( 'Twitter Card Type', 'aioseo-pro' ), 'twitter_image_type' => __( 'Twitter Image Source', 'aioseo-pro' ), 'twitter_image_custom_url' => __( 'Twitter Custom Image URL', 'aioseo-pro' ), 'twitter_title' => __( 'Twitter Title', 'aioseo-pro' ), 'twitter_description' => __( 'Twitter Description', 'aioseo-pro' ), 'canonical_url' => __( 'Canonical URL', 'aioseo-pro' ), 'schema' => __( 'Schema In Use', 'aioseo-pro' ), 'pillar_content' => $lateFields['pillar_content'], 'keywords' => $lateFields['keywords'], 'primary_term' => $lateFields['primary_term'], 'breadcrumb_settings' => $lateFields['breadcrumb_settings'], 'robots_default' => '', 'robots_noindex' => '', 'robots_noarchive' => '', 'robots_nosnippet' => '', 'robots_nofollow' => '', 'robots_noimageindex' => '', 'robots_noodp' => '', 'robots_notranslate' => '', 'robots_max_snippet' => '', 'robots_max_videopreview' => '', 'robots_max_imagepreview' => '', // 'robots_all_settings' isn't an AIOSEO DB column. 'robots_all_settings' => __( 'Robots Setting', 'aioseo-pro' ), 'priority' => __( 'Priority Score (value)', 'aioseo-pro' ), 'frequency' => __( 'Priority Score (frequency)', 'aioseo-pro' ), ]; } /** * Retrieves which post/term fields started being watched after this feature was introduced. * * @since 4.5.3 * @version 4.7.7 Renamed from `setLateEligibleFields` to `getLateEligibleFields`. * * @return array The late eligible fields. */ public function getLateEligibleFields() { return [ 'pillar_content' => __( 'Cornerstone Content', 'aioseo-pro' ), 'keywords' => __( 'Keywords', 'aioseo-pro' ), 'primary_term' => __( 'Primary Term(s)', 'aioseo-pro' ), 'breadcrumb_settings' => __( 'Breadcrumbs', 'aioseo-pro' ), ]; } /** * Set {@see self::$options}. * Ideally set options only for Vue usage on the front-end. * * @since 4.4.0 * * @return void */ private function setOptions() { $this->options = [ 'note' => [ 'maxlength' => 90 // Max char length. ], 'revisions' => [ 'per_page' => 10 // Amount of items/rows shown when editing an object. ] ]; } }