items = $items; $this->controller = $controller; } /** * Whether the current user can see the card: those who manage items. * * @return bool */ public function can_view(): bool { return current_user_can( ScheduledContent::CAPABILITY ); } /** * The card's data. * * @param int $page Page number, from 1. * @param int $per_page Items per page. * @param int|null $now UTC timestamp; now by default. * @return array{counts: array{active: int, upcoming: int, ended: int}, purgeProblems: int, total: int, page: int, pages: int, items: array>, screenUrl: string} */ public function data( int $page = 1, int $per_page = self::CARD_ITEMS, ?int $now = null ): array { $now = $now ?? time(); $counts = array( ScheduledElements::ACTIVE => 0, ScheduledElements::UPCOMING => 0, ScheduledElements::ENDED => 0, ); $problems = 0; $listed = array(); foreach ( $this->items->published() as $item ) { if ( ! $this->can_see( $item ) ) { continue; } $status = ScheduledElements::status( $item, $now ); ++$counts[ $status ]; if ( ScheduledContentPurges::has_problem( ScheduledContentPurges::last_purge( $item['id'] ) ) ) { ++$problems; } if ( ScheduledElements::ENDED !== $status ) { $listed[] = $item + array( '_status' => $status ); } } usort( $listed, array( $this, 'compare' ) ); $per_page = max( 1, $per_page ); $pages = (int) max( 1, ceil( count( $listed ) / $per_page ) ); $page = min( max( 1, $page ), $pages ); $items = array(); foreach ( array_slice( $listed, ( $page - 1 ) * $per_page, $per_page ) as $item ) { $items[] = $this->prepare( $item ); } return array( 'counts' => $counts, 'purgeProblems' => $problems, 'total' => count( $listed ), 'page' => $page, 'pages' => $pages, 'items' => $items, 'screenUrl' => admin_url( ScheduledContentScreens::LIST_SLUG ), ); } /** * Items whose visitor conditions are skipped because Pro isn't active: * published, not ended, with a rule that is on and has conditions. Counted * among the items the current user sees on the card. * * @param int|null $now UTC timestamp; now by default. * @return int */ public function items_skipping_conditions( ?int $now = null ): int { if ( ScheduledContent::conditions_active() ) { return 0; } $count = 0; foreach ( $this->items->published() as $item ) { if ( ScheduledElements::ENDED === ScheduledElements::status( $item, $now ?? time() ) || ! $this->can_see( $item ) ) { continue; } $rule = get_post_meta( $item['id'], ScheduledContent::META_RULE, true ); if ( is_array( $rule ) && ! empty( $rule['enabled'] ) && ! empty( $rule['groups'] ) ) { ++$count; } } return $count; } /** * Administrators see every item; others only items whose every target they can edit. * * @param array $item Item. * @return bool */ private function can_see( array $item ): bool { if ( current_user_can( 'manage_options' ) ) { return true; } foreach ( $item['targets'] as $target ) { if ( ! current_user_can( 'edit_post', $target ) ) { return false; } } return true; } /** * An item for the card. * * @param array $item Item with `_status`. * @return array */ private function prepare( array $item ): array { $settings = array_intersect_key( $item, array_flip( array( 'targets', 'position', 'show_at', 'hide_at', 'order' ) ) ); $prepared = $this->controller->prepare( $item['id'], $settings ); $title = html_entity_decode( wp_strip_all_tags( (string) $item['title'] ), ENT_QUOTES, 'UTF-8' ); return array( 'id' => $item['id'], 'title' => '' !== $title ? $title : __( '(no title)', 'yangra-conditional' ), 'editUrl' => (string) get_edit_post_link( $item['id'], 'raw' ), 'targets' => wp_list_pluck( $prepared['targets'], 'title' ), 'positionLabel' => ScheduledContentScreens::position_label( $item['position'] ), 'showLabel' => $prepared['showLabel'], 'hideLabel' => $prepared['hideLabel'], 'status' => $item['_status'], ); } /** * Active items first (soonest to end first, never-ending last), then * upcoming ones (soonest to start first). * * @param array $a Item with `_status`. * @param array $b Item with `_status`. * @return int */ private function compare( array $a, array $b ): int { return array( $this->rank( $a ), $this->next_change( $a ), $a['id'] ) <=> array( $this->rank( $b ), $this->next_change( $b ), $b['id'] ); } /** * 0 for active, 1 for upcoming. * * @param array $item Item with `_status`. * @return int */ private function rank( array $item ): int { return ScheduledElements::ACTIVE === $item['_status'] ? 0 : 1; } /** * When the item next changes: its hide time while active, its show time * while upcoming. No end sorts last. * * @param array $item Item with `_status`. * @return int */ private function next_change( array $item ): int { $time = ScheduledElements::ACTIVE === $item['_status'] ? $item['hide_at'] : $item['show_at']; return null === $time ? PHP_INT_MAX : (int) $time; } }