Invision Power Services, Inc. * @copyright (c) Invision Power Services, Inc. * @license https://www.invisioncommunity.com/legal/standards/ * @package Invision Community * @since 4 Nov 2013 */ namespace IPS\Content; /* To prevent PHP errors (extending class does not exist) revealing path */ if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) ) { header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' ); exit; } /** * Content Review Model */ abstract class _Review extends \IPS\Content\Comment { /** * @brief [Content\Comment] Comment Template */ public static $commentTemplate = array( array( 'global', 'core', 'front' ), 'reviewContainer' ); /** * Create first comment (created with content item) * * @param \IPS\Content\Item $item The content item just created * @param string $comment The comment * @param bool $first Is the first comment? * @param int $rating The rating (1-5) * @param string $guestName If author is a guest, the name to use * @param \IPS\Member|NULL $member The author of this comment. If NULL, uses currently logged in member. * @param \IPS\DateTime|NULL $time The time * @param string|NULL $ipAddress The IP address or NULL to detect automatically * @param int|NULL $hiddenStatus NULL to set automatically or override: 0 = unhidden; 1 = hidden, pending moderator approval; -1 = hidden (as if hidden by a moderator) * @return static * @throws \InvalidArgumentException */ public static function create( $item, $comment, $first=FALSE, $rating=NULL, $guestName=NULL, $member=NULL, \IPS\DateTime $time=NULL, $ipAddress=NULL, $hiddenStatus=NULL ) { if ( !is_int( $rating ) or $rating < 1 or $rating > intval( \IPS\Settings::i()->reviews_rating_out_of ) ) { throw new \InvalidArgumentException; } $obj = parent::create( $item, $comment, $first, $guestName, NULL, $member, $time, $ipAddress ); foreach ( array( 'rating', 'votes_data' ) as $k ) { if ( isset( static::$databaseColumnMap[ $k ] ) ) { $val = NULL; switch ( $k ) { case 'rating': $val = $rating; break; case 'votes_data': $val = json_encode( array() ); break; } foreach ( is_array( static::$databaseColumnMap[ $k ] ) ? static::$databaseColumnMap[ $k ] : array( static::$databaseColumnMap[ $k ] ) as $column ) { $obj->$column = $val; } } } if( $obj->author()->member_id ) { $obj->author()->member_last_post = time(); $obj->author()->save(); } $obj->save(); /* Have to do these AFTER rating is set */ $itemClass = static::$itemClass; $ratingField = $itemClass::$databaseColumnMap['rating']; $obj->item()->$ratingField = $obj->item()->averageReviewRating() ?: 0; $obj->item()->save(); /* Send notifications */ if ( !$obj->hidden() and ( !$first or !$item::$firstCommentRequired ) ) { $obj->sendNotifications(); } else if( $obj->hidden() === 1 ) { $obj->sendUnapprovedNotification(); } return $obj; } /** * Do stuff after creating (abstracted as comments and reviews need to do different things) * * @return void */ public function postCreate() { $item = $this->item(); if ( !$this->hidden() ) { if ( isset( $item::$databaseColumnMap['last_review'] ) ) { $lastReviewField = $item::$databaseColumnMap['last_review']; if ( is_array( $lastReviewField ) ) { foreach ( $lastReviewField as $column ) { $item->$column = time(); } } else { $item->$lastReviewField = time(); } } if ( isset( $item::$databaseColumnMap['last_review_by'] ) ) { $lastReviewByField = $item::$databaseColumnMap['last_review_by']; $item->$lastReviewByField = $this->author()->member_id; } if ( isset( $item::$databaseColumnMap['last_review_name'] ) ) { $lastReviewNameField = $item::$databaseColumnMap['last_review_name']; $item->$lastReviewNameField = $this->author()->name; } if ( isset( $item::$databaseColumnMap['num_reviews'] ) ) { $numReviewsField = $item::$databaseColumnMap['num_reviews']; $item->$numReviewsField++; } if ( !$item->hidden() and $item->containerWrapper() and $item->container()->_reviews !== NULL ) { $item->container()->_reviews = ( $item->container()->_reviews + 1 ); $item->container()->setLastReview( $this ); $item->container()->save(); } } else { if ( isset( $item::$databaseColumnMap['unapproved_reviews'] ) ) { $numReviewsField = $item::$databaseColumnMap['unapproved_reviews']; $item->$numReviewsField++; } if ( $item->containerWrapper() AND $item->container()->_unapprovedReviews !== NULL ) { $item->container()->_unapprovedReviews = $item->container()->_unapprovedReviews + 1; $item->container()->save(); } } $item->save(); } /** * @brief Cached URLs */ protected $_url = array(); /** * Get URL * * @param string|NULL $action Action * @return \IPS\Http\Url */ public function url( $action=NULL ) { $_key = md5( $action ); if( !isset( $this->_url[ $_key ] ) ) { $itemClass = static::$itemClass; $itemField = static::$databaseColumnMap['item']; $idColumn = static::$databaseColumnId; $this->_url[ $_key ] = $this->item()->url(); if( $action ) { $this->_url[ $_key ] = $this->_url[ $_key ]->setQueryString( array( 'do' => $action . 'Review', 'review' => $this->$idColumn ) ); } else { $where = array( array( static::$databasePrefix . static::$databaseColumnMap['item'] . '=? AND ' . static::$databasePrefix . static::$databaseColumnId . '<=?', $this->$itemField, $this->$idColumn ) ); if ( static::commentWhere() !== NULL ) { $where[] = static::commentWhere(); } if ( static::modPermission( 'view_hidden' ) === FALSE ) { if ( isset( static::$databaseColumnMap['approved'] ) ) { $where[] = array( static::$databasePrefix . static::$databaseColumnMap['approved'] . '=?', 1 ); } elseif( isset( static::$databaseColumnMap['hidden'] ) ) { $where[] = array( static::$databasePrefix . static::$databaseColumnMap['hidden'] . '=?', 0 ); } } $commentPosition = \IPS\Db::i()->select( 'COUNT(*) AS position', static::$databaseTable, $where )->first(); $page = ceil( $commentPosition / $itemClass::$reviewsPerPage ); if ( $page != 1 ) { $this->_url[ $_key ] = $this->_url[ $_key ]->setQueryString( 'page', $page ); } $this->_url[ $_key ] = $this->_url[ $_key ]->setFragment( 'review-' . $this->$idColumn ); } } return $this->_url[ $_key ]; } /** * Get line which says how many users found review helpful * * @return string */ public function helpfulLine() { return \IPS\Theme::i()->getTemplate( 'global', 'core', 'front' )->reviewHelpful( $this->mapped('votes_helpful'), $this->mapped('votes_total') ); } /** * Syncing to run when hiding * * @param \IPS\Member|NULL|FALSE $member The member doing the action (NULL for currently logged in member, FALSE for no member) * @return void */ public function onHide( $member ) { $item = $this->item(); if ( $item->container()->_reviews !== NULL ) { $item->container()->_reviews = $item->container()->_reviews - 1; $item->container()->setLastReview(); $item->container()->save(); } if ( isset( $item::$databaseColumnMap['hidden_reviews'] ) ) { $column = $item::$databaseColumnMap['hidden_reviews']; $item->$column = $item->mapped('hidden_reviews') + 1; } if ( isset( $item::$databaseColumnMap['num_reviews'] ) ) { $column = $item::$databaseColumnMap['num_reviews']; $item->$column = $item->mapped('num_reviews') - 1; } $ratingField = $item::$databaseColumnMap['rating']; $item->$ratingField = $item->averageReviewRating() ?: 0; $item->resyncLastReview(); $item->save(); } /** * Syncing to run when unhiding * * @param bool $approving If true, is being approved for the first time * @param \IPS\Member|NULL|FALSE $member The member doing the action (NULL for currently logged in member, FALSE for no member) * @return void */ public function onUnhide( $approving, $member ) { $item = $this->item(); if ( $approving ) { if ( isset( $item::$databaseColumnMap['unapproved_reviews'] ) ) { $column = $item::$databaseColumnMap['unapproved_reviews']; $item->$column = $item->mapped('unapproved_reviews') - 1; } if ( $item->container()->_unapprovedReviews !== NULL ) { $item->container()->_unapprovedReviews = $item->container()->_unapprovedReviews - 1; $item->container()->setLastReview(); $item->container()->save(); } } else if ( isset( $item::$databaseColumnMap['hidden_reviews'] ) ) { $column = $item::$databaseColumnMap['hidden_reviews']; $item->$column = $item->mapped('hidden_reviews') - 1; } if ( isset( $item::$databaseColumnMap['num_reviews'] ) ) { $column = $item::$databaseColumnMap['num_reviews']; $item->$column = $item->mapped('num_reviews') + 1; } if ( $item->container()->_reviews !== NULL ) { $item->container()->_reviews = $item->container()->_reviews + 1; $item->container()->setLastReview(); $item->container()->save(); } $ratingField = $item::$databaseColumnMap['rating']; $item->$ratingField = $item->averageReviewRating() ?: 0; $item->resyncLastReview(); $item->save(); } /** * Can split this comment off? * * @param \IPS\Member|NULL $member The member to check for (NULL for currently logged in member) * @return bool */ public function canSplit( $member=NULL ) { return FALSE; } /** * Can view? * * @param \IPS\Member|NULL $member The member to check for or NULL for the currently logged in member * @return bool */ public function canView( $member=NULL ) { if( $member === NULL ) { $member = \IPS\Member::loggedIn(); } if ( $this instanceof \IPS\Content\Hideable and $this->hidden() and !$this->item()->canViewHiddenReviews( $member ) and ( $this->hidden() !== 1 or $this->author() !== $member ) ) { return FALSE; } return $this->item()->canView( $member ); } /** * Warning Reference Key * * @return string */ public function warningRef() { /* If the member cannot warn, return NULL so we're not adding ugly parameters to the profile URL unnecessarily */ if ( !\IPS\Member::loggedIn()->modPermission('mod_can_warn') ) { return NULL; } $itemClass = static::$itemClass; $idColumn = static::$databaseColumnId; return base64_encode( json_encode( array( 'app' => $itemClass::$application, 'module' => $itemClass::$module . '-review' , 'id_1' => $this->mapped('item'), 'id_2' => $this->$idColumn ) ) ); } /** * Get attachment IDs * * @return array */ public function attachmentIds() { $item = $this->item(); $idColumn = $item::$databaseColumnId; $commentIdColumn = static::$databaseColumnId; return array( $this->item()->$idColumn, $this->$commentIdColumn, 'review' ); } /** * Create Notification * * @param string|NULL $extra Additional data * @return \IPS\Notification */ protected function createNotification( $extra=NULL ) { return new \IPS\Notification( \IPS\Application::load( 'core' ), 'new_review', $this->item(), array( $this ) ); } /** * Delete Review * * @return void */ public function delete() { parent::delete(); $itemClass = static::$itemClass; $ratingField = $itemClass::$databaseColumnMap['rating']; $this->item()->$ratingField = $this->item()->averageReviewRating() ?: 0; $this->item()->save(); } /** * Get output for API * * @return array * @apiresponse int id ID number * @apiresponse int item The ID number of the item this belongs to * @apiresponse \IPS\Member author Author * @apiresponse datetime date Date * @apiresponse int rating The number of stars this review gave * @apiresponse int votesTotal The number of users that have voted if this review was helpful or unhelpful * @apiresponse int votesHelpful The number of users that voted helpful * @apiresponse string content The content * @apiresponse bool hidden Is hidden? * @apiresponse string url URL to content */ public function apiOutput() { $idColumn = static::$databaseColumnId; $itemColumn = static::$databaseColumnMap['item']; return array( 'id' => $this->$idColumn, 'item_id' => $this->$itemColumn, 'author' => $this->author()->apiOutput(), 'date' => \IPS\DateTime::ts( $this->mapped('date') )->rfc3339(), 'rating' => $this->mapped('rating'), 'votesTotal' => $this->mapped('votes_total'), 'votesHelpful' => $this->mapped('votes_helpful'), 'content' => $this->content(), 'hidden' => (bool) $this->hidden(), 'url' => (string) $this->url() ); } /* !Embeddable */ /** * Get content for embed * * @param array $params Additional parameters to add to URL * @return string */ public function embedContent( $params ) { return \IPS\Theme::i()->getTemplate( 'global', 'core' )->embedReview( $this->item(), $this, $this->url()->setQueryString( $params ), $this->item()->embedImage() ); } }