__( 'Quote requests', 'boring-quote' ), 'callback' => array( $this, 'export' ), ); return $exporters; } /** * Register eraser. * * @param array $erasers Erasers. * @return array */ public function register_eraser( $erasers ) { $erasers['boring-quote'] = array( 'eraser_friendly_name' => __( 'Quote requests', 'boring-quote' ), 'callback' => array( $this, 'erase' ), ); return $erasers; } /** * Quote ids for an email address, one page at a time. * * @param string $email Email. * @param int $page Page (1-based). * @return int[] */ protected function quote_ids( $email, $page ) { return get_posts( array( 'post_type' => QuoteCPT::POST_TYPE, // 'any' would skip trashed quotes, which still hold personal data. 'post_status' => array_merge( array_keys( QuoteCPT::statuses() ), array( 'trash' ) ), 'fields' => 'ids', 'posts_per_page' => self::PER_PAGE, 'paged' => max( 1, (int) $page ), 'orderby' => 'ID', 'order' => 'ASC', 'meta_key' => '_bbquote_customer_email', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key 'meta_value' => sanitize_email( $email ), // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value ) ); } /** * Exporter callback. * * @param string $email Email. * @param int $page Page. * @return array */ public function export( $email, $page = 1 ) { $ids = $this->quote_ids( $email, $page ); $fields = SubmitHandler::fields(); $data = array(); foreach ( $ids as $quote_id ) { $item_data = array( array( 'name' => __( 'Date', 'boring-quote' ), 'value' => get_the_date( get_option( 'date_format' ), $quote_id ), ), ); foreach ( QuoteCPT::get_customer( $quote_id ) as $key => $value ) { if ( '' === (string) $value ) { continue; } $item_data[] = array( 'name' => SubmitHandler::field_label( $fields, $key ), 'value' => 'yes' === $value ? __( 'Yes', 'boring-quote' ) : $value, ); } $products = array(); foreach ( QuoteCPT::get_items( $quote_id ) as $item ) { $product = QuoteList::item_product( $item ); $products[] = ( $product ? $product->get_name() : '#' . (int) $item['product_id'] ) . ' x ' . (int) $item['quantity']; } $item_data[] = array( 'name' => __( 'Products', 'boring-quote' ), 'value' => implode( ', ', $products ), ); $data[] = array( 'group_id' => 'boring-quote', 'group_label' => __( 'Quote requests', 'boring-quote' ), 'item_id' => 'bbquote-quote-' . $quote_id, 'data' => $item_data, ); } return array( 'data' => $data, 'done' => count( $ids ) < self::PER_PAGE, ); } /** * Eraser callback: anonymizes customer data, keeps the quote (business record) and its products. * Always reads page 1, because erased quotes no longer match the email. * * @param string $email Email. * @param int $page Page (unused, see above). * @return array */ public function erase( $email, $page = 1 ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed -- signature required by WordPress. $ids = $this->quote_ids( $email, 1 ); foreach ( $ids as $quote_id ) { update_post_meta( $quote_id, '_bbquote_customer', self::anonymize( QuoteCPT::get_customer( $quote_id ) ) ); delete_post_meta( $quote_id, '_bbquote_customer_email' ); delete_post_meta( $quote_id, '_bbquote_customer_user' ); wp_update_post( array( 'ID' => $quote_id, /* translators: %d: quote id */ 'post_title' => sprintf( __( 'Quote #%d (anonymized)', 'boring-quote' ), $quote_id ), ) ); } return array( 'items_removed' => count( $ids ) > 0, 'items_retained' => false, 'messages' => array(), 'done' => count( $ids ) < self::PER_PAGE, ); } /** * Replace every customer value with a placeholder. Pure function (tested). * * @param array $customer Customer fields. * @return array */ public static function anonymize( array $customer ) { foreach ( $customer as $key => $value ) { if ( '' === (string) $value ) { continue; } $customer[ $key ] = 'email' === $key ? 'deleted@site.invalid' : '[deleted]'; } return $customer; } /** * Suggested text for the privacy policy page (Settings > Privacy > Policy Guide). */ public function policy_content() { if ( ! function_exists( 'wp_add_privacy_policy_content' ) ) { return; } $content = '

' . __( 'When you send a quote request, we store the data you enter in the form (such as your name, email address, phone number and message) together with the products you asked about. We use this data only to prepare and send you a quote and to answer your questions.', 'boring-quote' ) . '

' . '

' . __( 'Before you send the request, the products you add to the quote list are kept in a cookie-identified session for up to 48 hours. A second cookie stores only the number of products in the list.', 'boring-quote' ) . '

' . '

' . __( 'Quote requests are kept as business records until you ask us to delete them. You can request an export or deletion of your data at any time.', 'boring-quote' ) . '

'; wp_add_privacy_policy_content( __( 'Boring Quote', 'boring-quote' ), wp_kses_post( $content ) ); } }