JezK
Edit File: Review.php
<?php namespace GeminiLabs\SiteReviews; use GeminiLabs\SiteReviews\Database\OptionManager; use GeminiLabs\SiteReviews\Database\PostMeta; use GeminiLabs\SiteReviews\Database\Query; use GeminiLabs\SiteReviews\Database\ReviewManager; use GeminiLabs\SiteReviews\Defaults\CustomFieldsDefaults; use GeminiLabs\SiteReviews\Defaults\ReviewDefaults; use GeminiLabs\SiteReviews\Defaults\StatDefaults; use GeminiLabs\SiteReviews\Helpers\Arr; use GeminiLabs\SiteReviews\Helpers\Cast; use GeminiLabs\SiteReviews\Helpers\Text; use GeminiLabs\SiteReviews\Modules\Avatar; use GeminiLabs\SiteReviews\Modules\Date; use GeminiLabs\SiteReviews\Modules\Encryption; use GeminiLabs\SiteReviews\Modules\Html\ReviewHtml; use GeminiLabs\SiteReviews\Modules\Multilingual; use GeminiLabs\SiteReviews\Modules\Sanitizer; /** * @property bool $approved This property is mapped to $is_approved * @property array $assigned_posts * @property array $assigned_terms * @property array $assigned_users * @property string $author * @property int $author_id * @property string $avatar * @property string $content * @property Arguments $custom * @property string $date * @property string $date_gmt * @property string $name This property is mapped to $author * @property string $email * @property bool $has_revisions This property is mapped to $is_modified * @property int $ID * @property string $ip_address * @property bool $is_approved * @property bool $is_modified * @property bool $is_pinned * @property bool $is_verified * @property bool $modified This property is mapped to $is_modified * @property bool $pinned This property is mapped to $is_pinned * @property int $rating * @property int $rating_id * @property string $response * @property int $score * @property string $status * @property bool $terms * @property string $title * @property string $type * @property string $url * @property int $user_id This property is mapped to $author_id */ class Review extends Arguments { protected const KEY_ALIASES = [ 'approved' => 'is_approved', 'has_revisions' => 'is_modified', 'modified' => 'is_modified', 'name' => 'author', 'pinned' => 'is_pinned', 'user_id' => 'author_id', ]; protected ?Arguments $_custom = null; protected ?Arguments $_meta = null; protected ?\WP_Post $_post = null; protected bool $has_checked_revisions = false; /** * @param array|object $values */ public function __construct($values = [], bool $init = true) { $args = glsr(ReviewDefaults::class)->restrict( // maps ID to rating_id, then review_id to ID glsr()->args($values)->toArray() ); parent::__construct($args); if (!$init || empty($values)) { return; } if (empty($this->author)) { $this->set('author', glsr(Sanitizer::class)->sanitizeUserName( $this->user(), __('Anonymous', 'site-reviews') )); } $this->hydrate(); } /** * @return mixed */ public function __call(string $method, array $args) { array_unshift($args, $this); $result = apply_filters_ref_array(glsr()->id."/review/call/{$method}", $args); if (!is_a($result, get_class($this))) { return $result; } } public function __toString(): string { return (string) $this->build(); } public function approveUrl(): string { return $this->tokenUrl('approve', [$this->ID], admin_url()); } public function assignedPosts(bool $multilingual = true): array { $postIds = $this->assigned_posts; if ($multilingual) { $postIds = glsr(Multilingual::class)->getPostIds($postIds); } if (empty($postIds)) { return []; } return get_posts([ 'post__in' => $postIds, 'post_type' => 'any', 'posts_per_page' => -1, ]); } public function assignedTerms(bool $multilingual = true): array { $termIds = $this->assigned_terms; if ($multilingual) { $termIds = glsr(Multilingual::class)->getTermIds($termIds); } if (empty($termIds)) { return []; } $terms = get_terms([ 'hide_empty' => !$multilingual, 'include' => $termIds, 'taxonomy' => glsr()->taxonomy, ]); if (is_wp_error($terms)) { return []; } return $terms; } public function assignedUsers(): array { if (empty($this->assigned_users)) { return []; } return get_users([ 'fields' => ['display_name', 'ID', 'user_email', 'user_nicename', 'user_url'], 'include' => $this->assigned_users, ]); } public function author(): string { return Text::name( $this->author, glsr_get_option('reviews.name.format', '', 'string'), glsr_get_option('reviews.name.initial', '', 'string') ); } public function avatar(int $size = 0): string { return glsr(Avatar::class)->img($this, $size); } public function build(array $args = []): ReviewHtml { return new ReviewHtml($this, $args); } public function custom(): Arguments { return $this->_custom ??= glsr()->args($this->customValues()); } public function date(string $format = ''): string { $format = $format ?: $this->dateFormat(); if ('relative' === $format) { return glsr(Date::class)->relative($this->date); } return Cast::toString(mysql2date($format, $this->date)); } public function editUrl(): string { $obj = get_post_type_object(glsr()->post_type); $link = admin_url(sprintf($obj->_edit_link.'&action=edit', $this->ID)); $link = apply_filters('get_edit_post_link', $link, $this->ID, 'display'); return Cast::toString($link); } /** @param int|\WP_Post $post */ public static function isEditable($post): bool { $postId = Helper::getPostId($post); return static::isReview($postId) && in_array(glsr_get_review($postId)->type, ['', 'local']); } /** @param \WP_Post|int|false $post */ public static function isReview($post): bool { return glsr()->post_type === get_post_type($post); } public function isValid(): bool { return !empty($this->ID) && !empty($this->rating_id); } public function location(): array { return glsr(StatDefaults::class)->restrict( glsr(PostMeta::class)->get($this->ID, 'geolocation') ); } public function meta(): Arguments { return $this->_meta ??= glsr()->args($this->metaValues()); } /** * @param mixed $key */ #[\ReturnTypeWillChange] public function offsetExists($key): bool { $key = static::KEY_ALIASES[$key] ?? $key; return parent::offsetExists($key) || !is_null($this->custom()->$key); } /** * @param mixed $key * * @return mixed */ #[\ReturnTypeWillChange] public function offsetGet($key) { $key = static::KEY_ALIASES[$key] ?? $key; if ('is_modified' === $key) { return $this->hasRevisions(); } return parent::offsetGet($key) ?? $this->custom()->$key; } /** * @param mixed $key */ #[\ReturnTypeWillChange] public function offsetSet($key, $value): void { // This class is read-only, except for custom fields if ('custom' === $key) { $value = Arr::consolidate($value); $value = Arr::prefixKeys($value, '_custom_'); $meta = wp_parse_args($value, $this->meta()->toArray()); $this->_meta = glsr()->args($meta); $this->_custom = null; parent::offsetSet($key, $this->custom()); } } /** * @param mixed $key */ #[\ReturnTypeWillChange] public function offsetUnset($key): void { // This class is read-only } /** * @return \WP_Post|null */ public function post() { if (!$this->_post instanceof \WP_Post) { $this->_post = get_post($this->ID); } return $this->_post; } public function rating(): string { return glsr_star_rating($this->rating); } public function refresh(): Review { $values = glsr(ReviewManager::class)->get($this->ID, true)->toArray(); $this->merge($values); $this->_custom = null; $this->_meta = null; $this->_post = null; $this->has_checked_revisions = false; $this->hydrate(); return $this; } public function render(array $args = []): void { echo $this->build($args); } public function toArray(array $excludedKeys = []): array { $values = parent::toArray(); $values['name'] = $this->author; // fallback return array_diff_key($values, array_fill_keys($excludedKeys, null)); } public function type(): string { $type = $this->type; $reviewTypes = glsr()->retrieveAs('array', 'review_types'); return Arr::get($reviewTypes, $type, _x('Unknown', 'admin-text', 'site-reviews')); } /** * @return \WP_User|false */ public function user() { return get_user_by('id', $this->author_id); } public function verifyUrl(string $path = ''): string { if ($this->is_verified && !empty($this->meta()->_verified_on)) { return ''; } return $this->tokenUrl('verify', [$this->ID, trailingslashit($path)], get_home_url()); } protected function customValues(): array { $custom = array_filter($this->meta()->toArray(), fn ($key) => str_starts_with($key, '_custom'), \ARRAY_FILTER_USE_KEY ); $custom = Arr::unprefixKeys($custom, '_custom_'); $custom = Arr::unprefixKeys($custom, '_'); return glsr(CustomFieldsDefaults::class)->merge($custom); } protected function dateFormat(): string { return match (glsr_get_option('reviews.date.format', 'default')) { 'relative' => 'relative', 'custom' => glsr_get_option('reviews.date.custom', 'M j, Y'), default => glsr(OptionManager::class)->wp('date_format', 'F j, Y'), }; } protected function hasRevisions(): bool { if (!$this->has_checked_revisions) { $isModified = glsr(Query::class)->hasRevisions($this->ID); $this->has_checked_revisions = true; $this->set('is_modified', $isModified); } return $this->cast('is_modified', 'bool'); } protected function hydrate(): void { $this->set('avatar', glsr(Avatar::class)->url($this)); $this->set('custom', $this->custom()); $this->set('response', $this->meta()->_response); } protected function metaValues(): array { $meta = Arr::consolidate(get_post_meta($this->ID)); $meta = array_map(fn ($item) => array_shift($item), array_filter($meta)); $meta = array_filter($meta, '\GeminiLabs\SiteReviews\Helper::isNotEmpty'); return array_map('maybe_unserialize', $meta); } protected function tokenUrl(string $action, array $args, string $baseUrl): string { $token = glsr(Encryption::class)->encryptRequest($action, $args); return !empty($token) ? add_query_arg(glsr()->prefix, $token, $baseUrl) : ''; } }