Файл: IPBMafia.ru_IPB_3.4.6_Final_Rus _Nulled/board/upload/admin/sources/classes/like/composite.php
Строк: 1613
<?php
/**
* @file composite.php Provides classes to manage the like system
*~TERABYTE_DOC_READY~
* $Copyright: (c) 2001 - 2011 Invision Power Services, Inc.$
* $License: http://www.invisionpower.com/company/standards.php#license$
* $Author: mmecham $
* @since 11th October 2010
* $LastChangedDate: 2013-08-14 06:45:49 -0400 (Wed, 14 Aug 2013) $
* @version v3.4.6
* $Revision: 12334 $
*
* <b>Example Usage:</b>
* @code
* $app = 'gallery';
* $area = 'images';
* $like = classes_like::bootstrap( $app, $area );
* print $like->isLiked( $relId );
* $html = $like->render( 'summary', $relId );
* print $html;
*
* RESULT: Matt, Joe, Bob and 5 others like this
* @endcode
*/
if ( ! defined( 'IN_IPB' ) )
{
print "<h1>Incorrect access</h1>You cannot access this file directly. If you have recently upgraded, make sure you upgraded all the relevant files.";
exit();
}
/**
*
* @class classes_like_registry
* @brief Quick registry class for common methods
* @author Matt
*/
class classes_like_registry
{
/**
* App key
*
* @var $app
*/
static protected $app = null;
/**
* Area key
*
* @var $area
*/
static protected $area = null;
/**
* Total count
*
* @var $count
*/
static protected $count = null;
/**
* Cached bootstrap loaders
*
* @var $bootstraps
*/
static protected $bootstraps = array();
/**
* Get application key
*
* @return @e string
*/
static public function getApp()
{
return self::$app;
}
/**
* Set application key
*
* @param string $app Application key
* @return @e void
*/
static public function setApp( $app )
{
self::$app = $app;
}
/**
* Get area key
*
* @return @e string
*/
static public function getArea()
{
return self::$area;
}
/**
* Set area key
*
* @param string $area Area key
* @return @e void
*/
static public function setArea( $area )
{
self::$area = $area;
}
/**
* Get total count (saves on queries)
*
* @return @e integer
*/
static public function getTotalCount()
{
return self::$count;
}
/**
* Set total count (saves on queries)
*
* @param integer $count Likes count
* @return @e void
*/
static public function setTotalCount( $count )
{
self::$count = $count;
}
/**
* Build key, either for cache table or full table
*
* @param integer $relId Relationship ID
* @param integer $memberId Member ID
* @return @e binary Binary key
*/
static public function getKey( $relId, $memberId=null )
{
/* Check */
if ( empty( $relId ) AND empty( $memberId ) )
{
trigger_error( "Relationship ID missing in " . __CLASS__ . '::' . __FUNCTION__, E_USER_WARNING );
}
if ( $memberId === null )
{
/* For relative item only */
return md5( self::$app . ';' . self::$area . ';' . $relId );
}
else if ( ! empty( $memberId ) )
{
if ( $relId === null )
{
/* For member speciifc relative item */
return md5( self::$app . ';' . self::$area . ';' . $memberId );
}
else
{
/* For member speciifc relative item */
return md5( self::$app . ';' . self::$area . ';' . $relId . ';' . $memberId );
}
}
else
{
trigger_error( "Member ID missing in " . __CLASS__ . '::' . __FUNCTION__, E_USER_WARNING );
}
}
/**
* Build key for SQL query
*
*
* @param integer $relId Relationship ID
* @param string $memberField Member Field or value
* @return @e binary Binary key
*/
static public function getKeyForSql( $relId, $memberField=NULL )
{
/* Check */
if ( empty( $relId ) AND empty( $memberField ) )
{
//var_dump(IPSDebug::prettifyBackTrace(debug_backtrace()));exit;
trigger_error( "Relationship ID missing in " . __CLASS__ . '::' . __FUNCTION__, E_USER_WARNING );
}
if ( $memberField === null )
{
/* For relative item only */
return ipsRegistry::DB()->buildMd5Statement( ipsRegistry::DB()->buildConcat( array(
array( self::$app, 'string' ),
array( ';', 'string' ),
array( self::$area, 'string' ),
array( ';', 'string' ),
array( $relId, 'string' ),
) ) );
}
else if ( ! empty( $memberField ) )
{
if ( $relId === null )
{
/* For member speciifc relative item */
return ipsRegistry::DB()->buildMd5Statement( ipsRegistry::DB()->buildConcat( array(
array( self::$app, 'string' ),
array( ';', 'string' ),
array( self::$area, 'string' ),
array( ';', 'string' ),
array( $memberField ),
) ) );
}
else
{
/* For member speciifc relative item */
return ipsRegistry::DB()->buildMd5Statement( ipsRegistry::DB()->buildConcat( array(
array( self::$app, 'string' ),
array( ';', 'string' ),
array( self::$area, 'string' ),
array( ';', 'string' ),
array( $relId, 'string' ),
array( ';', 'string' ),
array( $memberField ),
) ) );
}
}
else
{
trigger_error( "Member ID missing in " . __CLASS__ . '::' . __FUNCTION__, E_USER_WARNING );
}
}
}
/**
*
* @class classes_like_meta
* @brief Simple static class for fetching meta data
* @author Matt
*/
class classes_like_meta
{
/**
* Retrieve the content title
*
* @param array $like Like data
* @return @e string Title of content liked
*/
static public function getTitleFromId( $like )
{
if ( empty( $like['like_app'] ) OR empty( $like['like_area'] ) OR empty( $like['like_rel_id'] ) )
{
trigger_error( "You must have like_app, like_rel_id and like_area set", E_USER_WARNING );
}
$_bootstrap = classes_like::bootstrap( $like['like_app'], $like['like_area'] );
return $_bootstrap->getTitleFromId( $like['like_rel_id'] );
}
/**
* Retrieve the content URL
*
* @param array $like Like data
* @return @e string URL of content liked
*/
static public function getUrlFromId( $like )
{
if ( empty( $like['like_app'] ) OR empty( $like['like_area'] ) OR empty( $like['like_rel_id'] ) )
{
trigger_error( "You must have like_app, like_rel_id and like_area set", E_USER_WARNING );
}
$_bootstrap = classes_like::bootstrap( $like['like_app'], $like['like_area'] );
return $_bootstrap->getUrlFromId( $like['like_rel_id'] );
}
/**
* Fetch all meta data from various like rows
*
* @param array $likes Like rows
* @return @e array
*/
static public function get( array $likes )
{
$apps = array();
$process = array();
$lookUp = array();
foreach( $likes as $id => $like )
{
if ( empty( $like['like_app'] ) OR empty( $like['like_area'] ) OR empty( $like['like_rel_id'] ) )
{
continue;
}
$apps[ $like['like_app'] . '-' . $like['like_area'] ][ $id ] = $like;
$lookUp[ $like['like_app'] . '-' . $like['like_area'] . '-' . $like['like_rel_id'] ] = $like['like_id'];
}
/* Process based on apps */
foreach( $apps as $app => $data )
{
list( $_app, $_area ) = explode( '-', $app );
$_bootstrap = classes_like::bootstrap( $_app, $_area );
$relIds = array();
foreach( $data as $_k => $_v )
{
$relIds[] = $_v['like_rel_id'];
}
/* Bulk fetch */
$process[ $app ] = $_bootstrap->getMeta( $relIds );
}
/* Fold up array */
foreach( $process as $app => $data )
{
list( $_app, $_area ) = explode( '-', $app );
foreach( $data as $relId => $_like )
{
if ( $relId )
{
$like_id = $lookUp[ $_app . '-' . $_area . '-' . $relId ];
if ( $like_id AND is_array( $_like ) AND is_array( $likes[ $like_id ] ) )
{
$likes[ $like_id ] = array_merge( $likes[ $like_id ], $_like );
}
}
}
}
return $likes;
}
}
/**
*
* @class classes_like
* @brief Factory class, loads composite and child class
* @author Matt
*/
class classes_like
{
/**
* Applications objects
*
* @var $apps
*/
static protected $apps;
/**
* Bootstrap function to load the required file/class and setup the initial variables
*
* @param string $app Application key
* @param string $area Area
* @return @e object Like object for the loaded key (app.area)
*/
static public function bootstrap( $app=null, $area=null )
{
if ( $app === null OR $area === null )
{
trigger_error( "App or area missing from classes_like", E_USER_WARNING );
}
/* Pointless comment! */
if( $area != 'default' )
{
$_file = IPSLib::getAppDir( $app ) . '/extensions/like/' . $area . '.php';
$_class = 'like_' . $app . '_' . $area . '_composite';
}
$_key = md5( $app . $area );
/* Get from cache if already cached */
if( isset( self::$apps[ $_key ] ) )
{
return self::$apps[ $_key ];
}
/* Otherwise create object and cache */
if ( ! is_file( $_file ) )
{
$_file = IPSLib::getAppDir( $app ) . '/extensions/like/default.php';
$_class = 'like_' . $app . '_composite';
if ( ! is_file( $_file ) )
{
throw new Exception( "No like class available for $app - $area" );
}
}
$classToLoad = IPSLib::loadLibrary( $_file, $_class, $app );
if ( class_exists( $classToLoad ) )
{
classes_like_registry::setApp( $app );
classes_like_registry::setArea( $area );
self::$apps[ $_key ] = new $classToLoad();
self::$apps[ $_key ]->init();
}
else
{
throw new Exception( "No like class available for $app - $area" );
}
return self::$apps[ $_key ];
}
}
/**
*
* @class classes_like_composite
* @brief Composite class, holds main functionality
* @author Matt
*/
abstract class classes_like_composite
{
/**
* Registry Object Shortcuts
*
* @var $registry
* @var $DB
* @var $settings
* @var $request
* @var $lang
* @var $member
* @var $memberData
* @var $cache
* @var $caches
*/
protected $registry;
protected $DB;
protected $settings;
protected $request;
protected $lang;
protected $member;
protected $memberData;
protected $cache;
protected $caches;
/**
* Cache object
*
* @var $likeCache
*/
protected $likeCache = null;
/**
* Application key
*
* @var $_app
*/
protected $_app;
/**
* Area key
*
* @var $_area
*/
protected $_area;
/**
* Can bypass permission checks
*
* @var $_area
*/
protected $_bypassPermissionChecks = false;
/**
* Init all. Yes, it is.
*
* @return @e void
*/
public function init()
{
$this->_app = classes_like_registry::getApp();
$this->_area = classes_like_registry::getArea();
if ( empty( $this->_app ) OR empty( $this->_area ) )
{
trigger_error( "Missing area or app variable in " . __CLASS__ . '::' . __FUNCTION__, E_USER_WARNING );
}
/* Fetch cache class */
$this->likeCache = classes_like_cache::getInstance();
/* Set a default cache expiration of 24 hours */
$this->likeCache->setExpiration( 86400 );
}
/**
* Toggle visibility
*
* @param mixed $relId Relationship ID or array ids
* @param boolean $visible Visible (true) or not (false)
* @return @e boolean
*/
public function toggleVisibility( $relId, $visible=true )
{
if ( empty( $relId ) )
{
trigger_error( "Data missing in " . __CLASS__ . '::' . __FUNCTION__, E_USER_WARNING );
}
/* Update any rows pointing to this rel id to be visible/invisible */
if( is_array($relId) )
{
if( count($relId) )
{
$this->DB->update( 'core_like', array( 'like_visible' => $visible ? 1 : 0 ), "like_app='{$this->_app}' AND like_area='{$this->_area}' AND like_rel_id IN (" . implode( ',', $relId ) . ')' );
}
}
else
{
$this->DB->update( 'core_like', array( 'like_visible' => $visible ? 1 : 0 ), "like_app='{$this->_app}' AND like_area='{$this->_area}' AND like_rel_id=" . $relId );
}
/* Flag cache as stale */
$this->likeCache->isNowStale( $relId );
return true;
}
/**
* Add a like
*
* @param integer $relId Relationship ID
* @param integer $memberID Member ID of user being added
* @param array $notifyOpts Notification options
* @param integer $isAnon Anonymous flag
* @return @e boolean
*/
public function add( $relId, $memberId, array $notifyOpts, $isAnon=0 )
{
if ( empty( $relId ) OR empty( $memberId ) )
{
trigger_error( "Data missing in " . __CLASS__ . '::' . __FUNCTION__, E_USER_WARNING );
}
/* first check to ensure we've not already like'd this item */
if ( $this->isLiked( $relId, $memberId, true ) )
{
/* if any one cares to check, then we're all good */
return false;
}
$memberData = IPSMember::load( $memberId );
/* Build */
$save = array( 'like_id' => classes_like_registry::getKey( $relId, $memberId ),
'like_lookup_id' => classes_like_registry::getKey( $relId ),
'like_lookup_area' => classes_like_registry::getKey( null, $memberId ),
'like_app' => $this->_app,
'like_area' => $this->_area,
'like_rel_id' => $relId,
'like_member_id' => $memberId,
'like_added' => IPS_UNIX_TIME_NOW,
'like_is_anon' => intval($isAnon),
'like_notify_do' => $notifyOpts['like_notify_do'],
'like_notify_meta' => $notifyOpts['like_notify_meta'],
'like_notify_freq' => ( $notifyOpts['like_notify_do'] ) ? $notifyOpts['like_notify_freq'] : '',
'like_visible' => 1,
'like_notify_sent' => 0 );
/* Do we have permission ? */
if ( ! $this->notificationCanSend( array_merge( $save, $memberData ) ) )
{
return false;
}
$notifyOpts = $this->_cleanNotifyOptions($notifyOpts);
/* Save to deebee */
$this->DB->insert( 'core_like', $save );
/* Flag cache as stale */
$this->likeCache->isNowStale( $relId );
return true;
}
/**
* Function to let plugins determine if a notification should not be sent. Return false to send notification, or true to skip sending it.
*
* @param array Notification data
* @return @e bool
*/
public function excludeNotification( $row )
{
return false;
}
/**
* Send notifications to anyone subscribed to item
*
* @param integer $relId Relationship ID
* @param array $type Types of notifications to send (Possible: immediate, offline, daily, weekly)
* @param array $notificationOpts Notification options [Keys: notification_key, notification_url, email_template, email_subject, email_lang_file, email_lang_app, build_message_array, from (optional), ignore_data( type => ids )]
* @param array $sentToIds Pass a value by reference to get the ID numbers of members that were sent a notification
* @return @e boolean
* @see allowedFrequencies()
*/
public function sendNotifications( $relId, $type, $notificationOpts=array(), &$sentToIds=array() )
{
$sentToIds = array();
if ( empty( $relId ) )
{
trigger_error( "Data missing in " . __CLASS__ . '::' . __FUNCTION__, E_USER_WARNING );
}
if ( isset( $notificationOpts['notification_bypass_permission_checks'] ) && $notificationOpts['notification_bypass_permission_checks'] === true )
{
$this->_bypassPermissionChecks = true;
}
else
{
/* Make sure this is reset (Not used currently) */
$this->_bypassPermissionChecks = false;
}
$type = is_array($type) ? $type : array( $type );
/* Use session expiration, unless it is longer than 1 hour, then limit to 1 hour */
$_limit = $this->settings['session_expiration'];
if( $_limit > 3600 )
{
$_limit = 3600;
}
$expire = IPS_UNIX_TIME_NOW - $_limit;
/* Fetch data by relID */
$data = $this->getDataByRelationshipId( $relId, false, (array) $notificationOpts['ignore_data'], $type );
/* Remove ourselves from notifications... */
if ( $this->memberData['member_id'] && isset($data[ $this->memberData['member_id'] ]) && $notificationOpts['includeOwner'] !== true )
{
unset($data[ $this->memberData['member_id'] ]);
}
/* If no users, forget it */
if ( is_null($data) )
{
return false;
}
$classToLoad = IPSLib::loadLibrary( IPS_ROOT_PATH . '/sources/classes/member/notifications.php', 'notifications' );
$notifyLibrary = new $classToLoad( $this->registry );
$htmlMessage = array();
$textMessage = array();
foreach( $data as $row )
{
if ( ! $row['like_notify_freq'] OR ! in_array( $row['like_notify_freq'], $type ) )
{
continue;
}
/* make sure user is offline */
if ( $row['like_notify_freq'] == 'offline' AND $row['last_activity'] > $expire )
{
continue;
}
/* Custom content type exclusion rules (e.g. in forums only send one notification per visit for offline types) */
if( $this->excludeNotification( $row ) )
{
continue;
}
/* Can we actually send this one? */
if ( $this->notificationCanSend( $row ) === false )
{
continue;
}
/* Extra protection in case a ghost record is in the table for a member that doesn't exist
@link http://community.invisionpower.com/tracker/issue-36793-error-when-editing-some-marketplace-files */
if( !$row['like_member_id'] )
{
continue;
}
/* Expensively process HTML/Text versions once per language.. <#NAME#> is converted to -members:members_display_name- for processing later */
if ( ! isset( $htmlMessage[ $row['language'] ] ) )
{
$buildMessage = array();
if ( is_array( $notificationOpts['build_message_array'] ) and count( $notificationOpts['build_message_array'] ) )
{
foreach( $notificationOpts['build_message_array'] as $k => $v )
{
$buildMessage[ $k ] = $v;
}
}
$buildMessage['UNSUBCRIBE_URL'] = '-member:unsubscribe_url-';
$notificationOpts['build_message_array']['UNSUBCRIBE_URL'] = '-member:unsubscribe_url-';
IPSText::getTextClass('email')->setPlainTextTemplate( IPSText::getTextClass('email')->getTemplate( $notificationOpts['email_template'], $row['language'], trim($notificationOpts['email_lang_file']), trim($notificationOpts['email_lang_app']) ) );
$textMessage[ $row['language'] ] = IPSText::getTextClass('email')->buildPlainTextContent( $buildMessage );
$htmlMessage[ $row['language'] ] = IPSText::getTextClass('email')->buildHtmlContent( $buildMessage );
}
$thisTextMessage = $textMessage[ $row['language'] ];
$thisHtmlMessage = $htmlMessage[ $row['language'] ];
/* Add in unsubscribe link */
$unsubKey = $row['like_app'] . ';' . $row['like_area'] . ';' . $row['like_rel_id'] . ';' . $row['like_member_id'] . ';' . $row['member_id'] . ';' . $row['email'];
$unsubUrl = $this->registry->output->buildSEOUrl( 'app=core&module=global&section=like&do=unsubscribe&key=' . IPSText::base64_encode_urlSafe( $unsubKey ), 'publicNoSession', 'unsubscribe', 'likeunsubscribe' );
/* Now process the dynamic replacements */
if ( is_array( $notificationOpts['build_message_array'] ) and count( $notificationOpts['build_message_array'] ) )
{
foreach( $notificationOpts['build_message_array'] as $k => $v )
{
if ( preg_match( '/-member:(.+?)-/', $v, $_matches ) )
{
if ( $_matches[1] == 'unsubscribe_url' )
{
$thisHtmlMessage = str_replace( $_matches[0], $unsubUrl, $thisHtmlMessage );
$thisTextMessage = str_replace( $_matches[0], $unsubUrl, $thisTextMessage );
}
else
{
$thisHtmlMessage = str_replace( $_matches[0], $row[ $_matches[1] ], $thisHtmlMessage );
$thisTextMessage = str_replace( $_matches[0], $row[ $_matches[1] ], $thisTextMessage );
}
}
}
}
$sentToIds[] = $row['like_member_id'];
$notifyLibrary->setMember( $row );
$notifyLibrary->setFrom( $notificationOpts['from'] ? $notificationOpts['from'] : $this->memberData );
$notifyLibrary->setNotificationKey( $notificationOpts['notification_key'] );
$notifyLibrary->setNotificationUrl( $notificationOpts['notification_url'] );
$notifyLibrary->setNotificationTitle( $notificationOpts['email_subject'] ? $notificationOpts['email_subject'] : IPSText::getTextClass('email')->subject );
$notifyLibrary->setNotificationText( $thisTextMessage );
$notifyLibrary->setNotificationHtml( $thisHtmlMessage );
if ( ! empty( $notificationOpts['email_only_subject'] ) && $notificationOpts['email_only_subject'] != IPSText::getTextClass('email')->subject )
{
$notifyLibrary->setEmailSubject( $notificationOpts['email_only_subject'], $row['language'], trim($notificationOpts['email_lang_file']), trim($notificationOpts['email_lang_app']) );
}
try
{
$notifyLibrary->sendNotification();
}
catch( Exception $e ){ }
//-----------------------------------------
// Update sent timestamp
//-----------------------------------------
$this->DB->update( 'core_like', array( 'like_notify_sent' => IPS_UNIX_TIME_NOW ), "like_id='" . classes_like_registry::getKey( $row['like_rel_id'], $row['like_member_id'] ) . "'" );
}
return ( is_array( $sentToIds ) ) ? array_unique( $sentToIds ) : true;
}
/**
* Send digest notifications to anyone subscribed to item
*
* @param string $type Types of notifications to send (Possible: daily, weekly)
* @param integer $sendMax Null (use ipsRegistry::$setting or send INT only)
* @return @e boolean
* @see allowedFrequencies()
*/
public function sendDigestNotifications( $type, $sendMax=null )
{
/* Fetch data */
$blackhole = time(); // mktime( 0, 0 ); - http://community.invisionpower.com/tracker/issue-35320-daily-digests-every-2-days
// The below has been changed based on troubleshooting in a ticket, 10.17.2012. With the code below not commented out, for a weekly digest we grab 15-7 days ago, rather than 8-0 days ago to send.
$time = $blackhole; //( $type == 'daily' ) ? $blackhole - ( 86400 ) : $blackhole - ( 86400 * 7 );
$data = $this->getDataByAreaAndLastSentOlderThanDate( $time, array( $type ), false, $sendMax );
if ( ! count( $data ) )
{
return false;
}
/* Check for outdated notifications and update them as required */
$oldestPossDate = 0;
if ( $type == 'daily' )
{
/* Grab 4 days to account for timezone differences */
$oldestPossDate = $blackhole - ( 86400 * 4 );
}
else
{
/* Grab 10 days to ensure we don't update rows ready to be sent */
$oldestPossDate = $blackhole - ( 86400 * 10 );
}
if ( $oldestPossDate )
{
$this->DB->update( 'core_like', array( 'like_notify_sent' => IPS_UNIX_TIME_NOW ),
'like_app='' . classes_like_registry::getApp() . '' AND like_area='' . classes_like_registry::getArea() . '' AND like_notify_sent < ' . $oldestPossDate . " AND like_notify_freq='{$type}'" );
}
$classToLoad = IPSLib::loadLibrary( IPS_ROOT_PATH . '/sources/classes/member/notifications.php', 'notifications' );
$notifyLibrary = new $classToLoad( $this->registry );
/* Array structure will be $data[ $memberId ][ $likeId ] = array(); */
foreach( $data as $memberId => $_rows )
{
foreach( $_rows as $row )
{
if ( ! $row['like_notify_freq'] OR $row['like_notify_freq'] != $type )
{
continue;
}
/* Make sure we don't send huge digests if this is their first email since subscribing */
$row['like_notify_sent'] = $row['like_notify_sent'] ? $row['like_notify_sent'] : $row['like_added'];
/* Want to fetch the data from the plug in files? */
$notificationOpts = $this->buildNotificationData( $row, $type );
if ( ! is_array( $notificationOpts ) OR ! count( $notificationOpts ) )
{
/* This will get hit if topic hasn't had a post since notification was last sent - to prevent the same row from getting picked up each time this runs
we need to update the last sent timestamp for the like */
$this->DB->update( 'core_like', array( 'like_notify_sent' => IPS_UNIX_TIME_NOW ), 'like_id='' . classes_like_registry::getKey( $row['like_rel_id'], $row['like_member_id'] ) . ''' );
continue;
}
$buildMessage = array();
if ( is_array( $notificationOpts['build_message_array'] ) and count( $notificationOpts['build_message_array'] ) )
{
foreach( $notificationOpts['build_message_array'] as $k => $v )
{
if ( preg_match( '/-member:(.+?)-/', $v, $_matches ) )
{
$v = str_replace( $_matches[0], $row[$_matches[1]], $v );
}
$buildMessage[$k] = $v;
}
}
/* Add in unsubscribe link */
$key = $row['like_app'] . ';' . $row['like_area'] . ';' . $row['like_rel_id'] . ';' . $row['like_member_id'] . ';' . $row['member_id'] . ';' . $row['email'];
$buildMessage['UNSUBCRIBE_URL'] = $this->registry->output->buildSEOUrl( 'app=core&module=global&section=like&do=unsubscribe&key=' . IPSText::base64_encode_urlSafe( $key ), 'publicNoSession', 'unsubscribe', 'likeunsubscribe' );
IPSText::getTextClass('email')->setPlainTextTemplate( IPSText::getTextClass('email')->getTemplate( $notificationOpts['email_template'], $row['language'], trim($notificationOpts['email_lang_file']), trim($notificationOpts['email_lang_app']) ) );
IPSText::getTextClass('email')->buildPlainTextContent( $buildMessage );
$notifyLibrary->setMember( $row );
$notifyLibrary->setFrom( $notificationOpts['from'] ? $notificationOpts['from'] : $this->memberData );
$notifyLibrary->setNotificationKey( $notificationOpts['notification_key'] );
$notifyLibrary->setNotificationUrl( $notificationOpts['notification_url'] );
$notifyLibrary->setNotificationText( IPSText::getTextClass('email')->getPlainTextContent() );
$notifyLibrary->setNotificationTitle( $notificationOpts['email_subject'] ? $notificationOpts['email_subject'] : IPSText::getTextClass('email')->subject );
$notifyLibrary->setNotificationHtml( IPSText::getTextClass('email')->buildHtmlContent( $buildMessage ) );
try
{
$notifyLibrary->sendNotification();
}
catch( Exception $e ){ }
/* Update sent timestamp */
$this->DB->update( 'core_like', array( 'like_notify_sent' => IPS_UNIX_TIME_NOW ), 'like_id='' . classes_like_registry::getKey( $row['like_rel_id'], $row['like_member_id'] ) . ''' );
}
}
return true;
}
/**
* Removes all likes based on member ID
*
* @param integer $memberId Member ID
* @return @e boolean
*/
public function removeByMemberId( $memberId )
{
if ( empty( $memberId ) )
{
trigger_error( "Data missing in " . __CLASS__ . '::' . __FUNCTION__, E_USER_WARNING );
}
$where = "like_app='{$this->_app}' AND like_area='{$this->_area}' AND like_member_id=" . intval($memberId);
/* Get rel ids */
$relIds = array();
$this->DB->build( array( 'select' => 'like_rel_id', 'from' => 'core_like', 'where' => $where ) );
$this->DB->execute();
while( $r = $this->DB->fetch() )
{
$relIds[] = $r['like_rel_id'];
}
/* Delete likes */
$this->DB->delete( 'core_like', $where );
/* Flag cache as stale */
$this->likeCache->isNowStale( $relIds );
return true;
}
/**
* Removes a like
*
* @param mixed $relId Relationship IDs (int|array)
* @param integer $memberId Optional - If supplied, it'll remove that member rel. or it will remove all
* @return @e boolean
*/
public function remove( $relId, $memberId=null )
{
if ( empty( $relId ) )
{
trigger_error( "Data missing in " . __CLASS__ . '::' . __FUNCTION__, E_USER_WARNING );
}
$where = '';
if ( is_numeric($relId) )
{
$where = "='" . classes_like_registry::getKey( $relId, $memberId ) . "'";
}
elseif ( is_array($relId) )
{
$relId = IPSLib::cleanIntArray($relId);
$keys = array();
if ( count($relId) )
{
foreach( $relId as $id )
{
$keys[] = "'" . classes_like_registry::getKey( $id, $memberId ) . "'";
}
$where = " IN (" . implode( ",", $keys ) . ")";
}
}
/* Nowhere to be found? */
if ( $where == '' )
{
return false;
}
/* Still here? Delete then! */
if ( $memberId === null )
{
$this->DB->delete( 'core_like', "like_lookup_id " . $where );
}
else
{
/* Not liked? Save us the bother, then */
if ( ! $this->isLiked($relId, $memberId) )
{
/* if any one cares to check, then we're all good */
return false;
}
$this->DB->delete( 'core_like', "like_id " . $where );
}
/* Flag cache as stale */
$this->likeCache->isNowStale( $relId );
return true;
}
/**
* Updates a member's ID in the like rows
*
* @param integer Old Id
* @param integer New Id
*/
public function updateMemberId( $oldId, $newId )
{
$_likes = array();
/* We might be merging */
$this->DB->build( array( 'select' => 'like_lookup_id', 'from' => 'core_like', 'where' => "like_member_id=" . $newId ) );
$this->DB->execute();
while( $r = $this->DB->fetch() )
{
$_likes[] = $r['like_lookup_id'];
}
/* not done */
if ( count( $_likes ) )
{
$this->DB->update( 'core_like' , array( 'like_member_id' => $newId ), "like_member_id=" . $oldId . " AND like_lookup_id NOT IN('" . implode( "','", array_map( 'addslashes', $_likes ) ) . "')" );
$this->DB->delete( 'core_like_cache' , "like_cache_id IN('" . implode( "','", array_map( 'addslashes', $_likes ) ) . "')" );
}
else
{
$this->DB->update( 'core_like' , array( 'like_member_id' => $newId ), "like_member_id=" . $oldId );
}
/* Now remove duplicates */
$dupes = array();
$this->DB->build( array( 'select' => 'like_id, count(*) as cnt',
'from' => 'core_like',
'where' => 'like_member_id=' . $newId,
'group' => 'like_app, like_area, like_member_id, like_rel_id',
'order' => 'cnt desc' ) );
$this->DB->execute();
while( $row = $this->DB->fetch() )
{
if ( $row['cnt'] < 2 )
{
break;
}
else
{
$dupes[] = $row['like_id'];
}
}
/* Do it? */
if ( count( $dupes ) )
{
$this->DB->delete( 'core_like' , "like_id IN('" . implode( "','", array_map( 'addslashes', $dupes ) ) . "')" );
}
/* Now re-update so that hashes are correct */
$update = "like_id=MD5( CONCAT( like_app, ';', like_area, ';', like_rel_id, ';', like_member_id ) ), " .
"like_lookup_area=MD5( CONCAT( like_app, ';', like_area, ';', like_member_id ) )";
$where = "like_member_id=" . $newId;
$this->DB->update( 'core_like', $update, $where, false, true );
}
/**
* Return number of people who 'like' the item
*
* @param integer $relId Relationship ID
* @return @e integer
*/
public function getCount( $relId )
{
$cache = $this->likeCache->get( $relId );
return $cache['count'];
}
/**
* Render a view
*
* @param string $view Type of View
* @param integer $relId Relationship ID
* @param array $opts Options
* @param integer $memberId Member ID
* @return @e string Formatted HTML
* @todo If $cache['count'] is 0, we can probably skip the getDataByMemberIdAndRelationshipId() call as it will be empty
*/
public function render( $view, $relId, $opts=array(), $memberId=null )
{
/* In the future we could abstract this out to its own class
* At this time we only have a few views so it would be overkill */
if ( $view == 'summary' )
{
$data = array( 'names' => array(), 'count' => 0, 'formatted' => '', 'iLike' => false, 'iLikeAnon' => false );
$cache = $this->likeCache->get( $relId, $memberId );
$max = 0;
/* Fetch data for us? */
if ( $this->memberData['member_id'] && ( $cache['count'] + $cache['count_anon'] ) > 0 )
{
/* We can't use isLiked or it runs twice the query and adding a cache to the function doesn't work either */
$_me = $this->getDataByMemberIdAndRelationshipId( $relId, $this->memberData['member_id'] );
if ( is_array($_me) && count($_me) && $_me['like_id'] )
{
/* Now mix up the data */
$data['names'][] = array( 'name' => $this->lang->words['fave_moi'], 'seo' => $this->memberData['members_seo_name'], 'id' => $this->memberData['member_id'] );
/* Flag as me */
$data['iLike'] = true;
/* Anonymous? Let the template know */
if ( $_me['like_is_anon'] )
{
$data['iLikeAnon'] = true;
}
}
}
/* We had the total count set from cache.get - doesn't count anonymous members thought! */
if ( classes_like_registry::getTotalCount() )
{
if ( is_array( $cache['members'] ) )
{
$i = 1;
$max = 3;
foreach( $cache['members'] as $mid => $mdata )
{
$_last = ( $i == $cache['count'] || $i == $max ) ? 1 : 0;
/* Is this you? */
if ( $mid == $this->memberData['member_id'] )
{
continue;
}
/* Push it on */
$data['names'][] = array( 'name' => $mdata['n'], 'seo' => $mdata['s'], 'id' => $mid, 'last' => $_last );
$i++;
if ( $i > $max )
{
/* Done thanks */
break;
}
}
}
}
/* Finish off */
$data['totalCount'] = $cache['count'] + $cache['count_anon'];
$data['anonCount'] = $cache['count_anon'];
$data['othersCount'] = ( $cache['count'] > $max ) ? $cache['count'] - $max : 0;
$data['app'] = $this->_app;
$data['area'] = $this->_area;
$data['formatted'] = $this->_formatNameString( $data );
$data['vernacular'] = $this->getVernacular();
if ( IPS_IS_AJAX )
{
$_template = $this->templatePrefix() . 'likeSummaryContents';
/* Got an override template? */
if ( method_exists( $this->registry->output->getTemplate( $this->skin() ), $_template ) )
{
return $this->registry->output->getTemplate( $this->skin() )->$_template( $data, $relId, $opts );
}
else
{
/* Fallback on default template */
return $this->registry->output->getTemplate('global_other')->likeSummaryContents( $data, $relId, $opts );
}
}
else
{
$_template = $this->templatePrefix() . 'likeSummary';
/* Got an override template? */
if ( method_exists( $this->registry->output->getTemplate( $this->skin() ), $_template ) )
{
return $this->registry->output->getTemplate( $this->skin() )->$_template( $data, $relId, $opts );
}
else
{
/* Fallback on default template */
return $this->registry->output->getTemplate('global_other')->likeSummary( $data, $relId, $opts );
}
}
}
else if ( $view == 'more' )
{
/* We need some counts here because we need them! */
$cache = $this->likeCache->get( $relId, $memberId );
$cache['totalCount'] = $cache['count'] + $cache['count_anon'];
$cache['anonCount'] = $cache['count_anon'];
/* Fetch members who have wanted to like this item */
$data = $this->getDataByRelationshipId( $relId );
$_template = $this->templatePrefix() . 'likeMoreDialogue';
/* Sort out some numbers if we're following anonymously */
if ( $this->memberData['member_id'] && isset($data[ $this->memberData['member_id'] ]) && $data[ $this->memberData['member_id'] ]['like_is_anon'] )
{
$cache['count_anon']--;
$cache['anonCount']--;
}
/* Got an override template? */
if ( method_exists( $this->registry->output->getTemplate( $this->skin() ), $_template ) )
{
return $this->registry->output->getTemplate( $this->skin() )->$_template( $data, $relId, $cache );
}
else
{
/* Fallback on default template */
return $this->registry->output->getTemplate('global_other')->likeMoreDialogue( $data, $relId, $cache );
}
}
}
/**
* Fetch form data for set dialogue
*
* @param integer $relId Relationship ID
* @return @e array
*/
public function getDataForSetDialogue( $relid )
{
$return = array( 'frequencies' => $this->allowedFrequencies(),
'notifyType' => $this->getNotifyType(),
'vernacular' => $this->getVernacular()
);
return $return;
}
/**
* Has this user made this item a like already dudes?
*
* @param mixed $relId Relationship IDs (int|array)
* @param integer $memberId Member ID
* @param boolean $checkNotVisibleToo return a match even if not visible to the user
* @return @e boolean
*/
public function isLiked( $relId, $memberId, $checkNotVisibleToo=false )
{
/* Grab the data */
return ( $this->getDataByMemberIdAndRelationshipId( $relId, $memberId, $checkNotVisibleToo ) === null ) ? false : true;
}
/**
* Get data based on a relationship ID and a member ID
*
* @param mixed $relId Relationship IDs (int|array)
* @param integer $memberId Member ID
* @param boolean $checkNotVisibleToo return a match even if not visible to the user
* @return @e mixed Array of likes data OR null
*/
public function getDataByMemberIdAndRelationshipId( $relId, $memberId, $checkNotVisibleToo=false )
{
$where = '';
$data = null;
if ( is_numeric( $relId ) )
{
$where = "like_id='" . classes_like_registry::getKey( $relId, $memberId ) . "'";
}
else if ( is_array( $relId ) )
{
$relId = IPSLib::cleanIntArray( $relId );
$keys = array();
foreach( $relId as $id )
{
$keys[] = "'" . classes_like_registry::getKey( $id, $memberId ) . "'";
}
if ( ! count( $keys ) )
{
return null;
}
$where = 'like_id IN (' . implode( ',', $keys ) . ')';
}
if ( $checkNotVisibleToo === false )
{
$where .= ' AND like_visible=1';
}
$this->DB->build( array( 'select' => '*',
'from' => 'core_like',
'where' => $where ) );
$o = $this->DB->execute();
while( $row = $this->DB->fetch( $o ) )
{
$data[ $row['like_rel_id'] ] = $row;
}
/* Just the one? */
if ( is_numeric($relId) && count($data) )
{
$data = array_shift($data);
}
return is_array($data) ? $data : null;
}
/**
* Get data based on a member ID
*
* @param integer $memberId Member ID
* @param integer $limit Max results
* @return @e mixed Array of likes data OR null
*/
public function getDataByMemberIdAndArea( $memberId, $limit=500 )
{
$data = null;
$this->DB->build( array( 'select' => '*',
'from' => 'core_like',
'where' => 'like_lookup_area='' . classes_like_registry::getKey( null, $memberId ) . '' AND like_visible=1',
'order' => 'like_added DESC',
'limit' => array( 0, $limit ) ) );
$o = $this->DB->execute();
while( $row = $this->DB->fetch( $o ) )
{
$data[ $row['like_rel_id'] ] = $row;
}
return ( is_array( $data ) ) ? $data : null;
}
/**
* Get data based on a relationship ID
*
* @param integer $relId Relationship ID
* @param boolean $skipAnon Skip anonymous rows flag
* @param array
* @param array Type of follow (immediate, none, offline, daily, etc)
* @return @e mixed Array of likes data OR null
*/
public function getDataByRelationshipId( $relId, $skipAnon=true, $ignoreData=array(), $followType=array() )
{
/* Init */
$mids = array();
$members = array();
$rows = array();
$followType = is_array( $followType ) ? $followType : array( $followType );
/* Determine where clause */
$where = array( "like_visible=1", "like_is_anon" . ( $skipAnon ? '=0' : ' IN(0,1)' ) );
/*if( $this->memberData['member_id'] )
{
$where[] = "(like_lookup_id='" . classes_like_registry::getKey( $relId ) . "' OR like_id='" . classes_like_registry::getKey( $relId, $this->memberData['member_id'] ) . "')";
}
else
{*/
$where[] = "like_lookup_id='" . classes_like_registry::getKey( $relId ) . "'";
//}
/* Add in follow type */
if ( count( $followType ) )
{
$where[] = "like_notify_freq IN ('" . implode( "','", $followType ) ."')";
}
/* Fetch data */
$this->DB->build( array( 'select' => '*',
'from' => 'core_like',
'where' => implode( " AND ", $where ),
'order' => 'like_added DESC',
'limit' => array( 0, 500 ) ) );
$o = $this->DB->execute();
while( $row = $this->DB->fetch( $o ) )
{
$data[ $row['like_member_id'] ] = $row;
$mids[ $row['like_member_id'] ] = intval( $row['like_member_id'] );
}
/* Just the one? */
if ( count( $mids ) )
{
$members = IPSMember::load( $mids, 'all' );
foreach( $members as $i => $d )
{
$_m = IPSMember::buildProfilePhoto( $d );
$data[ $i ] = array_merge( (array) $_m, (array) $data[ $i ] );
}
}
/* Are we giving this bloke a good ignoring? */
if ( !empty( $ignoreData ) )
{
$ignoreWhere = array();
foreach ( $ignoreData as $type => $ids )
{
$ignoreWhere[] = "( {$type}=1 AND " . $this->DB->buildWherePermission( $ids, 'ignore_ignore_id', FALSE ) . ')';
}
$this->DB->build( array( 'select' => '*', 'from' => 'ignored_users', 'where' => implode( ' OR ', $ignoreWhere ) ) );
$this->DB->execute();
while ( $row = $this->DB->fetch() )
{
if ( isset( $data[ $row['ignore_owner_id'] ] ) )
{
unset( $data[ $row['ignore_owner_id'] ] );
}
}
}
return is_array($data) ? $data : null;
}
/**
* Get data based on an area and last sent greater than date [unix timestampe]
*
* @param integer $date Unix timestamp
* @param array $types Array of notification types (optional)
* @param array $parseMembers Parse extra data for each member and build display photo (false is default)
* @param integer $sendMax Null (use ipsRegistry::$setting or send INT only)
* @return @e mixed Array of likes data OR null
* @see allowedFrequencies()
*/
public function getDataByAreaAndLastSentOlderThanDate( $date, $types=array(), $parseMembers=false, $sendMax=null )
{
/* Init */
$mids = array();
$members = array();
$rows = array();
$joins = array();
$where = ( is_array( $types ) ) ? ' AND l.like_notify_freq IN ('' . implode( "','", $types ) . '')' : '';
$sendMax = ( $sendMax !== null ) ? $sendMax : ipsRegistry::$settings['like_notifications_limit'];
/* figure out joins */
$joins[] = array( 'select' => 'm.*',
'from' => array( 'members' => 'm' ),
'where' => 'm.member_id=l.like_member_id',
'type' => 'left' );
$moreJoins = $this->getDataJoins();
if ( is_array( $moreJoins ) AND count( $moreJoins ) )
{
foreach( $moreJoins as $join )
{
$joins[] = $join;
}
}
/* Prevent it going back EONS AND EONS */
$oldestPossDate = 0;
if ( in_array( 'weekly', $types ) )
{
$oldestPossDate = $date - ( 86400 * 7 );
}
else
{
$oldestPossDate = $date - ( 86400 );
}
if ( $oldestPossDate )
{
$where .= ' AND ( CASE WHEN l.like_notify_sent > 0 THEN l.like_notify_sent ELSE l.like_added END ) < ' . intval( $oldestPossDate );
}
/* Fetch data */
$this->DB->build( array( 'select' => 'l.*',
'from' => array( 'core_like' => 'l' ),
'where' => 'l.like_notify_do=1 AND l.like_app='' . classes_like_registry::getApp() . '' AND l.like_area='' . classes_like_registry::getArea() . '' AND l.like_visible=1 AND ( CASE WHEN l.like_notify_sent > 0 THEN l.like_notify_sent ELSE l.like_added END ) < ' . intval( $date ) . $where,
'order' => 'l.like_notify_sent ASC',
'limit' => array( 0, $sendMax ),
'add_join' => $joins ) );
$o = $this->DB->execute();
while( $row = $this->DB->fetch( $o ) )
{
$row['like_member_id'] = intval($row['like_member_id']);
$mids[ $row['like_member_id'] ] = $row['like_member_id'];
/* Need to apply secondary groups and grab g_perm_id if $parseMembers is false (default)
@link http://community.invisionpower.com/tracker/issue-34691-digest-notifications-not-going-out/ */
$row['mgroup_others'] = ($row['mgroup_others'] != '') ? IPSText::cleanPermString($row['mgroup_others']) : '';
//by denchu
if (is_array($this->caches['group_cache'][ $row['member_group_id'] ])){
$row = array_merge( $row, $this->caches['group_cache'][ $row['member_group_id'] ] );
}
$row = $this->registry->member()->setUpSecondaryGroups( $row );
if( $row['topic_last_post'] )
{
$row['last_post'] = $row['topic_last_post'];
}
/* @link http://community.invisionpower.com/tracker/issue-32204-dailyweekly-notifications */
$data[ $row['like_member_id'] ][ $row['like_id'] ] = $row;
}
/* Just the one? */
if ( $parseMembers && count($mids) )
{
$members = IPSMember::load( $mids, 'all' );
foreach( $members as $i => $d )
{
$_m = IPSMember::buildProfilePhoto( $d );
foreach( $data[ $i ] as $likeId => $likeData )
{
$data[ $i ][ $likeId ] = array_merge( (array) $_m, (array) $data[ $i ][ $likeId ] );
}
}
}
return is_array($data) ? $data : null;
}
/**
* Clean the notification options to make sure they're all nice and fluffy
*
* @param array $notifyOpts Notification options
* @return @e array Cleaned notification options array
*/
protected function _cleanNotifyOptions( array $notifyOpts )
{
if ( isset( $notifyOpts['like_notify_do'] ) )
{
$notifyOpts['like_notify_do'] = intval( $notifyOpts['like_notify_do'] );
}
else
{
$notifyOpts['like_notify_do'] = 0;
}
if ( ! isset( $notifyOpts['like_notify_meta'] ) )
{
$notifyOpts['like_notify_meta'] = '';
}
if ( isset( $notifyOpts['like_notify_freq'] ) )
{
$notifyOpts['like_notify_freq'] = ( in_array( $notifyOpts['like_notify_freq'], $this->allowedFrequencies() ) ) ? $notifyOpts['like_notify_freq'] : '';
}
else
{
$notifyOpts['like_notify_freq'] = 0;
}
return $notifyOpts;
}
/**
* Return an array of acceptable frequencies
* Possible: immediate, offline, daily, weekly
*
* @return @e array
*/
public function allowedFrequencies()
{
return array( 'immediate', 'offline', 'daily', 'weekly' );
}
/**
* Return types of notification available for this item
*
* @return @e array array( key, human readable )
*/
public function getNotifyType()
{
return array( 'comments', ipsRegistry::getClass('class_localization')->words['gbl_comments_like'] );
}
/**
* Fetch the template group
*
* @return @e string
*/
public function skin()
{
return 'global_other';
}
/**
* Fetch the template prefix. This allows you to have two follow implementations in
* one skin file (i.e. skin_calendars -> eventLikeMoreDialog() and skin_calendars -> calendarLikeMoreDialog())
*
* @return @e string
*/
public function templatePrefix()
{
return '';
}
/**
* Gets the vernacular (like or follow)
*
* @return @e string
*/
public function getVernacular()
{
return 'like';
}
/**
* Builds the notification data via the app class
*
* @param array $data like_ DB data and like owner member data
* @param string $type Types of notifications to send
* @return @e array array( notification_key, notification_url, email_template, email_subject, build_message_array )
* @see allowedFrequencies()
*/
public function buildNotificationData( $data, $type )
{
return array();
}
/**
* Check notifications that are to be sent to make sure they're valid and that
*
* @param array $metaData like_ DB data and like owner member data
* @return @e boolean
*/
public function notificationCanSend( $metaData )
{
return true;
}
/**
* Fetches joins for fetching data
*
* @param string $field DB field name (defaults to 'l.like_rel_id')
* @return @e array
*/
public function getDataJoins( $field='l.like_rel_id' )
{
return array();
}
/**
* Return the title based on the passed id
*
* @param mixed $relId Relationship ID or array of IDs
* @return @e mixed Title or array of titles
*/
public function getTitleFromId( $relId )
{
$meta = $this->getMeta( $relId, array( 'title' ) );
if ( is_numeric( $relId ) )
{
return $meta[ $relId ]['like.title'];
}
else
{
$return = array();
foreach( $meta as $id => $data )
{
$return[ $id ] = $data['like.title'];
}
return $return;
}
}
/**
* Return the URL based on the passed id
*
* @param mixed $relId Relationship ID or array of IDs
* @return @e mixed URL or array of URLs
*/
public function getUrlFromId( $relId )
{
$meta = $this->getMeta( $relId, array( 'url' ) );
if ( is_numeric( $relId ) )
{
return $meta[ $relId ]['like.url'];
}
else
{
$return = array();
foreach( $meta as $id => $data )
{
$return[ $id ] = $data['like.url'];
}
return $return;
}
}
/**
* Formats the Bob, Bill, Joe and 2038 Others Hate You
*
* @param array $data Likes data
* @return @e string Formatted names
*/
protected function _formatNameString( array $data )
{
$langString = '';
$seeMoreLink = 'app=core&module=global&section=like&do=more';
if ( ! is_array( $data['names'] ) OR ! count( $data['names'] ) )
{
return false;
}
/* Format up the names */
$i = 0;
$_names = array();
foreach( $data['names'] as $name )
{
if ( $this->memberData['member_id'] AND $this->memberData['member_id'] == $name['id'] )
{
$_names[$i] = $name['name'];
}
else
{
$_names[$i] = IPSMember::makeProfileLink($name['name'], $name['id'], $name['seo'] );
}
$i++;
}
/* More than one? */
if ( $data['totalCount'] > 1 )
{
/* Joe and Matt love you */
if ( $data['totalCount'] == 2 )
{
$_n = $_names[0] . ' ' . $this->lang->words['fave_and'] . ' ' . $_names[1];
$langString = sprintf( $this->lang->words['fave_formatted_many'], $_n );
}
/* Joe, Matt and Mike love you more */
else if ( $data['totalCount'] == 3 )
{
$_n = $_names[0] . ', ' . $_names[1] . ' ' . $this->lang->words['fave_and'] . ' ' . $_names[2];
$langString = sprintf( $this->lang->words['fave_formatted_many'], $_n );
}
/* Joe, Matt, Mike and 1 more love you */
else if ( $data['totalCount'] == 4 )
{
$_n = $_names[0] . ', ' . $_names[1] . ' ' . $this->lang->words['fave_and'] . ' ' . $_names[2];
$langString = sprintf( $this->lang->words['fave_formatted_one_more'], $_n, $seeMoreLink );
}
/* Joe, Matt, Mike and 5 more are indifferent to your redonkulous comments */
else
{
$_n = $_names[0] . ', ' . $_names[1] . ', ' . $_names[2];
$langString = sprintf( $this->lang->words['fave_formatted_more'], $_n, $seeMoreLink, $data['othersCount'] );
}
}
else
{
/* Just the one and it might be you! */
if ( $data['names'][0]['id'] == $this->memberData['member_id'] )
{
$langString = $this->lang->words['fave_formatted_me'];
}
else
{
$langString = sprintf( $this->lang->words['fave_formatted_one'], $_names[0] );
}
}
return $langString;
}
/**
* Replace all likes on one item with another
* Used when merging two items
*
* @param int $id1 The ID of the item being deleted
* @param int $id2 The ID of the item being kept
*/
public function merge( $id1, $id2 )
{
/* We need to tell the driver not to escape our md5 compilation statements */
$_fieldEncapsulate = $this->DB->fieldEncapsulate;
$this->DB->fieldEncapsulate = '';
$this->DB->manual_addslashes = TRUE;
/* Run the query - this looks a bit nuts, but basically we're setting any records for
likes on the old item to now be likes on the new, unelss the user has already liked
the new, in which case do nothing and we'll delete the left-overs in a minute */
$this->DB->update(
array( 'core_like' => 'a' ),
$this->DB->compileUpdateString( array(
'a.like_id' => classes_like_registry::getKeyForSql( $id2, 'a.like_member_id' ),
'a.like_lookup_id' => classes_like_registry::getKeyForSql( $id2 ),
'a.like_lookup_area'=> classes_like_registry::getKeyForSql( null, 'a.like_member_id' ),
'a.like_rel_id' => intval( $id2 ),
) ),
"a.like_app='{$this->_app}' AND a.like_area='{$this->_area}' AND a.like_rel_id={$id1} AND b.like_id " . $this->DB->buildIsNull(),
false,
true,
array( array(
'from' => array( 'core_like' => 'b' ),
'where' => "b.like_app='{$this->_app}' AND b.like_area='{$this->_area}' AND b.like_rel_id={$id2}",
) )
);
/* Reset the DB driver */
$this->DB->manual_addslashes = FALSE;
$this->DB->fieldEncapsulate = $_fieldEncapsulate;
/* Remove any stragglers (would happen if user was subscribed to both topics */
$this->remove( $id1 );
/* Reset the cache */
classes_like_cache::getInstance()->isNowStale( $id1 );
classes_like_cache::getInstance()->isNowStale( $id2 );
}
}
/**
*
* @class classes_like_cache
* @brief Favorites cache class
* @author Matt
*/
class classes_like_cache
{
/**
* Registry Object Shortcuts
*
* @var $registry
* @var $DB
* @var $settings
* @var $request
* @var $lang
* @var $member
* @var $memberData
* @var $cache
* @var $caches
*/
protected $registry;
protected $DB;
protected $settings;
protected $request;
protected $lang;
protected $member;
protected $memberData;
protected $cache;
protected $caches;
/**
* Cache expiration time in seconds
*
* @var $_expire
*/
protected $_expire = 0;
/**
* Application key
*
* @var $_app
*/
protected $_app;
/**
* Area key
*
* @var $_area
*/
protected $_area;
/**
* Instance of an object
*
* @var $instance
*/
private static $instance = null;
/**
* Singleton
*
* @return @e object
*/
public static function getInstance()
{
if ( self::$instance === null )
{
self::$instance = new self();
}
return self::$instance;
}
/**
* Constructor
*
* @return @e void
*/
public function __construct()
{
/* Fetch registry like a good boy */
$this->registry = ipsRegistry::instance();
$this->DB = $this->registry->DB();
$this->settings =& $this->registry->fetchSettings();
$this->request =& $this->registry->fetchRequest();
$this->lang = $this->registry->getClass('class_localization');
$this->member = $this->registry->member();
$this->memberData =& $this->registry->member()->fetchMemberData();
$this->cache = $this->registry->cache();
$this->caches =& $this->registry->cache()->fetchCaches();
$this->_app = classes_like_registry::getApp();
$this->_area = classes_like_registry::getArea();
if ( empty( $this->_app ) OR empty( $this->_area ) )
{
trigger_error( "Missing area or app variable in " . __CLASS__ . '::' . __FUNCTION__, E_USER_WARNING );
}
}
/**
* Set the cache expiration
*
* @param integer $seconds Time in seconds
* @return @e void
*/
public function setExpiration( $seconds )
{
$this->_expire = intval( $seconds );
}
/**
* Fetch an item from the cache
*
* @param integer $relId Relationship ID
* @param integer $memberId Member ID
* @return @e array
*/
public function get( $relId, $memberId=null )
{
$cache = array();
/* Possible future expansion */
if ( $memberId === null )
{
$cache = $this->DB->buildAndFetch( array( 'select' => '*',
'from' => 'core_like_cache',
'where' => 'like_cache_id='' . classes_like_registry::getKey( $relId ) . ''' ) );
if ( !is_array($cache) OR $cache['like_cache_expire'] <= IPS_UNIX_TIME_NOW )
{
/* We don't have a valid cache, so create one and return the data */
$cache = $this->create( $relId, $memberId );
}
$tmp = unserialize($cache['like_cache_data']);
$cache['members'] = $tmp['members'];
$cache['count'] = intval($tmp['count']);
$cache['count_anon']= intval($tmp['count_anon']);
/* Set total count for use elsewhere */
classes_like_registry::setTotalCount( $cache['count'] );
}
return $cache;
}
/**
* Creates/updates the relationship ID's cache
*
* @param integer $relId Relationship ID
* @param integer $memberId Member ID
* @return @e array Array of stored data
*/
public function create( $relId, $memberId=null )
{
/* Init vars */
$store = array();
$items = array();
$members = array();
$mids = array();
$data = array( 'members' => null, 'count' => 0 );
/* Possible future expansion */
if ( $memberId === null )
{
/* Count all public first */
$cou = $this->DB->buildAndFetch( array( 'select' => 'COUNT(*) as nt',
'from' => 'core_like',
'where' => 'like_lookup_id='' . classes_like_registry::getKey( $relId ) . '' AND like_is_anon=0 AND like_visible=1' ) );
$data['count'] = intval( $cou['nt'] );
/* Count all anonymous second */
$ano = $this->DB->buildAndFetch( array( 'select' => 'COUNT(*) as nt',
'from' => 'core_like',
'where' => 'like_lookup_id='' . classes_like_registry::getKey( $relId ) . '' AND like_is_anon!=0 AND like_visible=1' ) );
$data['count_anon'] = intval( $ano['nt'] );
/* Fetch all items for this app:key:$relId */
$this->DB->build( array( 'select' => '*',
'from' => 'core_like',
'where' => 'like_lookup_id='' . classes_like_registry::getKey( $relId ) . '' AND like_is_anon=0 AND like_visible=1',
'order' => 'like_added DESC',
'limit' => array( 0, 5 ) ) );
$o = $this->DB->execute();
while( $row = $this->DB->fetch($o) )
{
$items[ $row['like_rel_id'] ] = $row;
$mids[ $row['like_member_id'] ] = $row['like_member_id'];
}
/* Load members */
$members = IPSMember::load( $mids, 'core' );
/* We don't need all the member's infos */
foreach( $members as $mid => $mdata )
{
$data['members'][ $mid ] = array( 'n' => $mdata['members_display_name'], 's' => $mdata['members_seo_name'] );
}
/* Build save array */
$store = array( 'like_cache_id' => classes_like_registry::getKey( $relId ),
'like_cache_app' => $this->_app,
'like_cache_area' => $this->_area,
'like_cache_rel_id' => $relId,
'like_cache_data' => serialize( $data ),
'like_cache_expire' => IPS_UNIX_TIME_NOW + $this->_expire );
/* Update the cache */
$this->DB->replace( 'core_like_cache', $store, array( 'like_cache_id' ) );
}
else
{
/* We know there's a previous cache row, so delete it */
$this->DB->delete( 'core_like_cache', 'like_cache_id='' . classes_like_registry::getKey( $relId ) . ''' );
}
return $store;
}
/**
* Deletes a cache
*
* @param mixed $relId Relationship ID or array of IDs
* @param integer $memberId Member ID
* @return @e void
*/
public function delete( $relId, $memberId=null )
{
/* Possible future expansion */
if ( $memberId === null )
{
$where = '';
if ( is_numeric($relId) )
{
$where = "='" . classes_like_registry::getKey( $relId ) . "'";
}
elseif ( is_array($relId) )
{
$relId = IPSLib::cleanIntArray($relId);
$keys = array();
foreach( $relId as $id )
{
$keys[] = "'" . classes_like_registry::getKey( $id ) . "'";
}
if ( ! count($keys) )
{
return null;
}
$where = " IN (" . implode( ",", $keys ) . ")";
}
$this->DB->delete( 'core_like_cache', 'like_cache_id ' . $where );
}
}
/**
* Flags a cache as stale. We choose to delete, but the cache class should make the call
* not the application
*
* @param mixed $relId Relationship ID or array of IDs
* @param integer $memberId Member ID
* @return @e void
*/
public function isNowStale( $relId, $memberId = null )
{
return $this->delete( $relId, $memberId );
}
}