post_type = $post_type; $this->is_public = (bool) $is_public; } /** {@inheritDoc} */ public function personal_data() { return ! $this->is_public; } /** {@inheritDoc} */ protected function statuses() { if ( $this->is_public ) { return parent::statuses(); } // A portal often has statuses of its own (submitted, approved); everything except trash and drafts WordPress made. $statuses = array_diff( array_keys( get_post_stati() ), array( 'trash', 'auto-draft', 'inherit' ) ); return array_values( array_merge( parent::statuses(), array_diff( $statuses, parent::statuses() ) ) ); } /** * Meta keys used by this post type, as fields for the inventory (names only). * * @return array */ private function meta_fields() { global $wpdb; if ( in_array( $this->post_type, array( 'post', 'page' ), true ) ) { return array(); } // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Only names, read once per inventory. $keys = (array) $wpdb->get_col( $wpdb->prepare( "SELECT DISTINCT pm.meta_key FROM {$wpdb->postmeta} pm INNER JOIN {$wpdb->posts} p ON p.ID = pm.post_id WHERE p.post_type = %s LIMIT 400", $this->post_type ) ); $fields = array(); foreach ( $keys as $key ) { $key = (string) $key; if ( '' === $key || Hypamoon_Connect_Record::is_secret_key( $key ) || Hypamoon_Connect_Record::is_internal_post_meta( $key ) || ( '_' === $key[0] && in_array( substr( $key, 1 ), $keys, true ) ) ) { continue; } $fields[] = array( 'name' => $key, 'type' => 'meta', ); if ( count( $fields ) >= 150 ) { break; } } return $fields; } /** {@inheritDoc} */ public function key() { return 'content:' . $this->post_type; } /** {@inheritDoc} */ public function type() { return 'content'; } /** {@inheritDoc} */ public function label() { $object = get_post_type_object( $this->post_type ); return $object ? (string) $object->labels->name : $this->post_type; } /** {@inheritDoc} */ protected function post_type() { return $this->post_type; } /** {@inheritDoc} */ public function date_field() { return 'date'; } /** {@inheritDoc} */ public function inventory() { $item = $this->item( $this->post_stats() ); $item['post_type'] = $this->post_type; if ( ! $this->is_public ) { $item['public'] = false; } $fields = $this->meta_fields(); if ( ! empty( $fields ) ) { $item['fields'] = $fields; } return $item; } /** * Build a content record. * * @param int $id Post id. * @return array|null */ public function build( $id ) { $post = get_post( $id ); if ( ! $post ) { return null; } // Some blocks and shortcodes expect the global post to be set. $previous = isset( $GLOBALS['post'] ) ? $GLOBALS['post'] : null; $GLOBALS['post'] = $post; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited setup_postdata( $post ); $content_html = Hypamoon_Connect_Record::render_content( $post->post_content ); $GLOBALS['post'] = $previous; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited if ( $previous instanceof WP_Post ) { setup_postdata( $previous ); } $author = get_userdata( (int) $post->post_author ); $taxonomies = get_object_taxonomies( $post->post_type, 'names' ); $terms = array(); foreach ( $taxonomies as $taxonomy ) { if ( in_array( $taxonomy, array( 'category', 'post_tag' ), true ) ) { continue; } $names = Hypamoon_Connect_Record::term_names( $post->ID, $taxonomy ); if ( ! empty( $names ) ) { $terms[ $taxonomy ] = $names; } } $thumbnail_id = (int) get_post_thumbnail_id( $post ); $acf_keys = Hypamoon_Connect_Acf::meta_keys( 'post', $post->ID ); $record = array( 'wp_id' => (int) $post->ID, 'post_type' => $post->post_type, 'status' => $post->post_status, 'title' => (string) $post->post_title, 'slug' => (string) $post->post_name, 'link' => (string) get_permalink( $post ), 'date' => Hypamoon_Connect_Record::iso( $post->post_date_gmt, $post->post_date ), 'modified' => Hypamoon_Connect_Record::iso( $post->post_modified_gmt, $post->post_modified ), 'author' => $author ? $author->display_name : '', 'parent' => (int) $post->post_parent, 'menu_order' => (int) $post->menu_order, 'excerpt' => wp_strip_all_tags( (string) $post->post_excerpt ), 'content_html' => $content_html, 'template' => (string) get_page_template_slug( $post ), 'categories' => in_array( 'category', $taxonomies, true ) ? Hypamoon_Connect_Record::term_names( $post->ID, 'category' ) : array(), 'tags' => in_array( 'post_tag', $taxonomies, true ) ? Hypamoon_Connect_Record::term_names( $post->ID, 'post_tag' ) : array(), 'terms' => (object) $terms, 'featured_image' => $thumbnail_id ? (string) wp_get_attachment_url( $thumbnail_id ) : null, 'fields' => (object) Hypamoon_Connect_Record::public_meta( 'post', $post->ID, $acf_keys ), 'password_protected' => '' !== (string) $post->post_password, ); if ( Hypamoon_Connect_Acf::is_active() ) { $record['acf'] = (object) Hypamoon_Connect_Acf::values( $post->ID ); } if ( $this->include_meta() ) { $meta = Hypamoon_Connect_Record::all_meta( 'post', $post->ID, $acf_keys ); $record['meta'] = (object) $meta; $relations = Hypamoon_Connect_Relations::find( array_merge( (array) $record['fields'], $meta ), array( '_thumbnail_id' => true ) ); if ( $post->post_author ) { $relations[] = array( 'field' => 'author', 'kind' => 'user', 'wp_id' => (int) $post->post_author, ); } $record['relations'] = $relations; } return $record; } } /** * Media library items, as URLs only. */ class Hypamoon_Connect_Media_Source extends Hypamoon_Connect_Posts_Source { /** {@inheritDoc} */ public function key() { return 'media'; } /** {@inheritDoc} */ public function type() { return 'media'; } /** {@inheritDoc} */ public function label() { return __( 'Media (URLs only)', 'hypamoon-connect' ); } /** {@inheritDoc} */ protected function post_type() { return 'attachment'; } /** {@inheritDoc} */ protected function statuses() { return array( 'inherit', 'private' ); } /** {@inheritDoc} */ protected function rule_statuses( $rule ) { return $this->statuses(); } /** {@inheritDoc} */ public function date_field() { return 'date'; } /** {@inheritDoc} */ public function inventory() { $stats = $this->post_stats(); unset( $stats['statuses'] ); return $this->item( $stats ); } /** * Build a media record. * * @param int $id Attachment id. * @return array|null */ public function build( $id ) { $post = get_post( $id ); if ( ! $post ) { return null; } $meta = wp_get_attachment_metadata( $post->ID ); $meta = is_array( $meta ) ? $meta : array(); $filesize = isset( $meta['filesize'] ) ? (int) $meta['filesize'] : null; if ( null === $filesize ) { $file = get_attached_file( $post->ID ); if ( $file && file_exists( $file ) ) { $filesize = (int) filesize( $file ); } } return array( 'wp_id' => (int) $post->ID, 'url' => (string) wp_get_attachment_url( $post->ID ), 'title' => (string) $post->post_title, 'alt' => (string) get_post_meta( $post->ID, '_wp_attachment_image_alt', true ), 'caption' => (string) $post->post_excerpt, 'mime_type' => (string) $post->post_mime_type, 'date' => Hypamoon_Connect_Record::iso( $post->post_date_gmt, $post->post_date ), 'parent' => (int) $post->post_parent, 'width' => isset( $meta['width'] ) ? (int) $meta['width'] : null, 'height' => isset( $meta['height'] ) ? (int) $meta['height'] : null, 'filesize' => $filesize, ); } } /** * Navigation menus with their items. */ class Hypamoon_Connect_Menus_Source extends Hypamoon_Connect_Source { /** {@inheritDoc} */ public function key() { return 'menus'; } /** {@inheritDoc} */ public function type() { return 'menus'; } /** {@inheritDoc} */ public function label() { return __( 'Menus', 'hypamoon-connect' ); } /** * Menu term ids. * * @return int[] */ private function ids() { $menus = wp_get_nav_menus(); return is_array( $menus ) ? array_map( 'intval', wp_list_pluck( $menus, 'term_id' ) ) : array(); } /** {@inheritDoc} */ public function inventory() { return $this->item( array( 'count' => count( $this->ids() ) ) ); } /** {@inheritDoc} */ public function fetch( $sub, $cursor, $limit, $rule ) { $slice = $this->slice_ids( $this->ids(), $cursor, $limit ); return $this->items_from_ids( $slice['ids'], array( $this, 'build' ), $slice['done'] ); } /** * Build a menu record. * * @param int $id Menu term id. * @return array|null */ public function build( $id ) { $menu = wp_get_nav_menu_object( $id ); if ( ! $menu ) { return null; } $registered = get_registered_nav_menus(); $locations = array(); foreach ( (array) get_nav_menu_locations() as $location => $menu_id ) { if ( (int) $menu_id === (int) $menu->term_id ) { $locations[] = isset( $registered[ $location ] ) ? (string) $registered[ $location ] : (string) $location; } } $items = array(); foreach ( (array) wp_get_nav_menu_items( $menu->term_id ) as $item ) { if ( ! is_object( $item ) ) { continue; } $items[] = array( 'wp_id' => (int) $item->ID, 'title' => (string) $item->title, 'url' => (string) $item->url, 'parent' => (int) $item->menu_item_parent, 'order' => (int) $item->menu_order, 'object' => (string) $item->object, 'object_id' => (int) $item->object_id, 'type' => (string) $item->type, ); } return array( 'wp_id' => (int) $menu->term_id, 'name' => (string) $menu->name, 'slug' => (string) $menu->slug, 'locations' => $locations, 'items' => $items, ); } } /** * Categories, tags and public custom taxonomies. */ class Hypamoon_Connect_Terms_Source extends Hypamoon_Connect_Source { /** {@inheritDoc} */ public function key() { return 'terms'; } /** {@inheritDoc} */ public function type() { return 'terms'; } /** {@inheritDoc} */ public function label() { return __( 'Categories, tags and other terms', 'hypamoon-connect' ); } /** {@inheritDoc} */ public function streams() { $taxonomies = get_taxonomies( array( 'public' => true ), 'names' ); $taxonomies = array_diff( array_values( $taxonomies ), array( 'nav_menu', 'link_category', 'post_format' ) ); sort( $taxonomies ); return array_values( $taxonomies ); } /** * Term ids of one taxonomy. * * @param string $taxonomy Taxonomy. * @return int[] */ private function ids( $taxonomy ) { $ids = get_terms( array( 'taxonomy' => $taxonomy, 'hide_empty' => false, 'fields' => 'ids', ) ); return is_array( $ids ) ? array_map( 'intval', $ids ) : array(); } /** {@inheritDoc} */ public function inventory() { $count = 0; $fields = array(); foreach ( $this->streams() as $taxonomy ) { $total = count( $this->ids( $taxonomy ) ); $count += $total; $object = get_taxonomy( $taxonomy ); // Taxonomies are listed as "fields" so the platform can show what is inside. $fields[] = array( 'name' => $taxonomy, 'label' => $object ? (string) $object->labels->name : $taxonomy, 'type' => 'taxonomy', 'count' => $total, ); } return $this->item( array( 'count' => $count, 'fields' => $fields, ) ); } /** {@inheritDoc} */ public function fetch( $sub, $cursor, $limit, $rule ) { $slice = $this->slice_ids( $this->ids( $sub ), $cursor, $limit ); return $this->items_from_ids( $slice['ids'], array( $this, 'build' ), $slice['done'] ); } /** * Build a term record. * * @param int $id Term id. * @return array|null */ public function build( $id ) { $term = get_term( $id ); if ( ! $term || is_wp_error( $term ) ) { return null; } $record = array( 'wp_id' => (int) $term->term_id, 'taxonomy' => (string) $term->taxonomy, 'name' => (string) $term->name, 'slug' => (string) $term->slug, 'description' => (string) $term->description, 'parent' => (int) $term->parent, 'count' => (int) $term->count, ); if ( Hypamoon_Connect_Acf::is_active() ) { $record['acf'] = (object) Hypamoon_Connect_Acf::values( 'term_' . $term->term_id ); } return $record; } } /** * Site users: name, email and role only. */ class Hypamoon_Connect_Users_Source extends Hypamoon_Connect_Source { /** {@inheritDoc} */ public function key() { return 'users'; } /** {@inheritDoc} */ public function type() { return 'users'; } /** {@inheritDoc} */ public function label() { return __( 'Users (name, email and role only)', 'hypamoon-connect' ); } /** {@inheritDoc} */ public function personal_data() { return true; } /** {@inheritDoc} */ public function date_field() { return 'registered'; } /** * All user ids of this site. * * @return int[] */ protected function ids() { return array_map( 'intval', get_users( array( 'fields' => 'ID', 'orderby' => 'ID', 'order' => 'ASC', ) ) ); } /** {@inheritDoc} */ public function inventory() { $range = array( 'oldest' => null, 'newest' => null, ); foreach ( array( 'oldest' => 'ASC', 'newest' => 'DESC', ) as $name => $order ) { $users = get_users( array( 'orderby' => 'registered', 'order' => $order, 'number' => 1, ) ); if ( ! empty( $users ) ) { $range[ $name ] = Hypamoon_Connect_Record::iso( $users[0]->user_registered ); } } $fields = array( array( 'name' => 'display_name', 'label' => __( 'Name', 'hypamoon-connect' ), 'type' => 'text', ), array( 'name' => 'email', 'label' => __( 'Email', 'hypamoon-connect' ), 'type' => 'email', ), array( 'name' => 'roles', 'label' => __( 'Roles', 'hypamoon-connect' ), 'type' => 'list', ), ); return $this->item( array_merge( $range, array( 'count' => count( $this->ids() ), // Other user fields (like a client id) move only with "all fields" (include_meta); their names are listed here. 'fields' => array_merge( $fields, $this->meta_fields() ), ) ) ); } /** {@inheritDoc} */ public function fetch( $sub, $cursor, $limit, $rule ) { $this->rule = (array) $rule; $slice = $this->slice_ids( $this->ids(), $cursor, $limit ); return $this->items_from_ids( $slice['ids'], array( $this, 'build' ), $slice['done'] ); } /** * User meta keys of this site, as fields for the inventory (names only, never internals or secrets). * * @return array */ private function meta_fields() { global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Only names, read once per inventory. $keys = (array) $wpdb->get_col( "SELECT DISTINCT meta_key FROM {$wpdb->usermeta} LIMIT 1000" ); $fields = array(); foreach ( $keys as $key ) { $key = (string) $key; if ( '' === $key || '_' === $key[0] || Hypamoon_Connect_Record::is_secret_key( $key ) || Hypamoon_Connect_Record::is_private_user_meta( $key ) || in_array( $key, array( 'first_name', 'last_name', 'nickname', 'description' ), true ) ) { continue; } $fields[] = array( 'name' => $key, 'label' => $key, 'type' => 'meta', ); if ( count( $fields ) >= 150 ) { break; } } return $fields; } /** * Build a user record. Never passwords, hashes, sessions or keys. * * @param int $id User id. * @return array|null */ public function build( $id ) { $user = get_userdata( $id ); if ( ! $user ) { return null; } $record = array( 'wp_id' => (int) $user->ID, 'display_name' => (string) $user->display_name, 'first_name' => (string) $user->first_name, 'last_name' => (string) $user->last_name, 'email' => (string) $user->user_email, 'roles' => array_values( (array) $user->roles ), 'registered' => Hypamoon_Connect_Record::iso( $user->user_registered ), ); if ( Hypamoon_Connect_Acf::is_active() ) { $record['acf'] = (object) Hypamoon_Connect_Acf::values( 'user_' . $user->ID ); } if ( $this->include_meta() ) { $meta = Hypamoon_Connect_Record::all_meta( 'user', $user->ID, Hypamoon_Connect_Acf::meta_keys( 'user', $user->ID ) + array_fill_keys( array( 'first_name', 'last_name', 'nickname' ), true ) ); $record['meta'] = (object) $meta; $record['relations'] = Hypamoon_Connect_Relations::find( $meta ); } return $record; } }