Seditio Source
Root |
./othercms/ips_4.3.4/system/Member/Club/Club.php
<?php
/**
 * @brief        Club Model
 * @author        <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
 * @copyright    (c) Invision Power Services, Inc.
 * @license        https://www.invisioncommunity.com/legal/standards/
 * @package        Invision Community
 * @since        13 Feb 2017
 */

namespace IPS\Member;

/* 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;
}

/**
 * Club Model
 */
class _Club extends \IPS\Patterns\ActiveRecord implements \IPS\Content\Embeddable
{    
    const
TYPE_PUBLIC = 'public';
    const
TYPE_OPEN = 'open';
    const
TYPE_CLOSED = 'closed';
    const
TYPE_PRIVATE = 'private';
    const
TYPE_READONLY = 'readonly';
   
    const
STATUS_MEMBER = 'member';
    const
STATUS_INVITED = 'invited';
    const
STATUS_INVITED_BYPASSING_PAYMENT = 'invited_bypassing_payment';
    const
STATUS_REQUESTED = 'requested';
    const
STATUS_WAITING_PAYMENT = 'payment_pending';
    const
STATUS_EXPIRED = 'expired';
    const
STATUS_EXPIRED_MODERATOR = 'expired_moderator';
    const
STATUS_DECLINED = 'declined';
    const
STATUS_BANNED = 'banned';
    const
STATUS_MODERATOR = 'moderator';
    const
STATUS_LEADER = 'leader';
   
   
/**
     * @brief    [ActiveRecord] Multiton Store
     */
   
protected static $multitons;
   
   
/**
     * @brief    [ActiveRecord] Database Table
     */
   
public static $databaseTable = 'core_clubs';
   
   
/**
     * @brief    Use a default cover photo
     */
   
public static $coverPhotoDefault = true;
   
   
/* !Fetch Clubs */
   
    /**
     * Construct ActiveRecord from database row
     *
     * @param    array    $data                            Row from database table
     * @param    bool    $updateMultitonStoreIfExists    Replace current object in multiton store if it already exists there?
     * @return    static
     */
   
public static function constructFromData( $data, $updateMultitonStoreIfExists = TRUE )
    {
       
$return = parent::constructFromData( $data, $updateMultitonStoreIfExists );
       
        if ( isset(
$data['member_id'] ) and isset( $data['status'] ) )
        {
           
$return->_memberStatuses[ $data['member_id'] ] = $data['status'];
        }
       
        return
$return;
    }
       
   
/**
     * Get all clubs a member can see
     *
     * @param    \IPS\Member                $member        The member to base permission off or NULL for all clubs
     * @param    int                        $limit        Number to get
     * @param    string                    $sortOption    The sort option ('last_activity', 'members', 'content' or 'created')
     * @param    bool|\IPS\Member|array    $mineOnly    Limit to clubs a particular member has joined (TRUE to use the same value as $member). Can also provide an array as array( 'member' => \IPS\Member, 'statuses' => array( STATUS_MEMBER... ) ) to limit to certain member statuses
     * @param    array                    $filters    Custom field filters
     * @param    mixed                    $extraWhere    Additional WHERE clause
     * @return    \IPS\Patterns\ActiveRecordIterator|array
     */
   
public static function clubs( \IPS\Member $member = NULL, $limit, $sortOption, $mineOnly=FALSE, $filters=array(), $extraWhere=NULL )
    {
       
$where = array();
       
$joins = array();
       
       
/* Restrict to clubs we can see */
       
if ( $member and !$member->modPermission('can_access_all_clubs') )
        {
           
/* Exclude clubs which are pending approval, unless we are the owner */
           
if ( \IPS\Settings::i()->clubs_require_approval )
            {
               
$where[] = array( '( approved=1 OR owner=? )', $member->member_id );
            }
           
           
/* Specify our memberships */
           
if ( $member->member_id )
            {
               
$joins['membership'] = array( array( 'core_clubs_memberships', 'membership' ), array( 'membership.club_id=core_clubs.id AND membership.member_id=?', $member->member_id ) );
               
$where[] = array( "( type<>? OR membership.status IN('" . static::STATUS_MEMBER .  "','" . static::STATUS_MODERATOR . "','" . static::STATUS_LEADER . "','" . static::STATUS_EXPIRED . "','" . static::STATUS_EXPIRED_MODERATOR . "') )", static::TYPE_PRIVATE );
            }
            else
            {
               
$where[] = array( 'type<>?', static::TYPE_PRIVATE );
            }
        }
       
       
/* Restrict to clubs we have joined */
       
if ( $mineOnly )
        {
            if (
is_array( $mineOnly ) )
            {
               
$statuses = $mineOnly['statuses'];
               
$mineOnly = $mineOnly['member'];
            }
            else
            {            
               
$mineOnly = ( $mineOnly === TRUE ) ? $member : $mineOnly;
               
$statuses = array( static::STATUS_MEMBER, static::STATUS_MODERATOR, static::STATUS_LEADER, static::STATUS_EXPIRED, static::STATUS_EXPIRED_MODERATOR );
            }
            if ( !
$mineOnly->member_id )
            {
                return array();
            }
           
            if (
$member and $mineOnly->member_id === $member->member_id and isset( $joins['membership'] ) )
            {
               
$where[] = array( "membership.status IN('" . static::STATUS_MEMBER .  "','" . static::STATUS_MODERATOR . "','" . static::STATUS_LEADER . "','" . static::STATUS_EXPIRED . "','" . static::STATUS_EXPIRED_MODERATOR . "')" );
            }
            else
            {
               
$joins['others_membership'] = array( array( 'core_clubs_memberships', 'others_membership' ), array( 'others_membership.club_id=core_clubs.id AND others_membership.member_id=?', $mineOnly->member_id ) );
               
$where[] = array( "others_membership.status IN('" . static::STATUS_MEMBER .  "','" . static::STATUS_MODERATOR . "','" . static::STATUS_LEADER . "','" . static::STATUS_EXPIRED . "','" . static::STATUS_EXPIRED_MODERATOR . "')" );
            }
        }
       
       
/* Other filters */
       
if ( $filters )
        {
           
$joins['core_clubs_fieldvalues'] = array( 'core_clubs_fieldvalues', array( 'core_clubs_fieldvalues.club_id=core_clubs.id' ) );
            foreach (
$filters as $k => $v )
            {
                if (
is_array( $v ) )
                {
                   
$where[] = array( \IPS\Db::i()->findInSet( "field_{$k}", $v ) );
                }
                else
                {
                   
$where[] = array( "field_{$k}=?", $v );
                }
            }
        }
       
       
/* Additional where clause */
       
if ( $extraWhere )
        {
            if (
is_array( $extraWhere ) )
            {
               
$where = array_merge( $where, $extraWhere );
            }
            else
            {
               
$where[] = array( $extraWhere );
            }
        }
       
       
/* Query */
       
$select = \IPS\Db::i()->select( '*', 'core_clubs', $where, ( $sortOption === 'name' ? "{$sortOption} ASC" : "{$sortOption} DESC" ), $limit, NULL, NULL, \IPS\Db::SELECT_SQL_CALC_FOUND_ROWS );
        foreach (
$joins as $join )
        {
           
$select->join( $join[0], $join[1] );
        }
       
$select->setKeyField( 'id' );

       
/* Return */
       
return new \IPS\Patterns\ActiveRecordIterator( $select, 'IPS\Member\Club' );
    }    
   
   
/**
     * Get number clubs a member is leader of
     *
     * @param    \IPS\Member    $member    The member
     * @return    int
     */
   
public static function numberOfClubsMemberIsLeaderOf( \IPS\Member $member )
    {
        return \
IPS\Db::i()->select( 'COUNT(*)', 'core_clubs_memberships', array( 'member_id=? AND status=?', $member->member_id, static::STATUS_LEADER ) );
    }
       
   
/* !ActiveRecord */
   
    /**
     * Set Default Values
     *
     * @return    void
     */
   
public function setDefaultValues()
    {
       
$this->type = static::TYPE_OPEN;
       
$this->created = new \IPS\DateTime;
       
$this->last_activity = time();
       
$this->members = 1;
       
$this->owner = NULL;
       
$this->approved = \IPS\Settings::i()->clubs_require_approval ? 0 : 1;
    }
   
   
/**
     * Get owner
     *
     * @return    \IPS\Member|NULL
     */
   
public function get_owner()
    {
        try
        {
           
$owner = \IPS\Member::load( $this->_data['owner'] );
            return
$owner->member_id ? $owner : NULL;
        }
        catch( \
OutOfRangeException $e )
        {
            return
NULL;
        }
    }
   
   
/**
     * Set member
     *
     * @param    \IPS\Member
     * @return    void
     */
   
public function set_owner( \IPS\Member $owner = NULL )
    {
       
$this->_data['owner'] = $owner ? ( (int) $owner->member_id ) : NULL;
    }
   
   
/**
     * Get created date
     *
     * @return    \IPS\DateTime
     */
   
public function get_created()
    {
        return \
IPS\DateTime::ts( $this->_data['created'] );
    }
   
   
/**
     * Set created date
     *
     * @param    \IPS\DateTime    $date    The invoice date
     * @return    void
     */
   
public function set_created( \IPS\DateTime $date )
    {
       
$this->_data['created'] = $date->getTimestamp();
    }
           
   
/**
     * Get club URL
     *
     * @return    \IPS\Http\Url
     */
   
public function url()
    {
        return \
IPS\Http\Url::internal( "app=core&module=clubs&controller=view&id={$this->id}", 'front', 'clubs_view', \IPS\Http\Url\Friendly::seoTitle( $this->name ) );
    }
   
   
/**
     * Columns needed to query for search result / stream view
     *
     * @return    array
     */
   
public static function basicDataColumns()
    {
        return array(
'id', 'name' );
    }
   
   
/**
     * Edit Club Form
     *
     * @param    bool    $acp            TRUE if editing in the ACP
     * @param    bool    $new            TRUE if creating new
     * @param    array    $availableTypes    If creating new, the available types
     * @return    \IPS\Helpers\Form|NULL
     */
   
public function form( $acp=FALSE, $new=FALSE, $availableTypes=NULL )
    {
       
$form = new \IPS\Helpers\Form;
       
       
$form->add( new \IPS\Helpers\Form\Text( 'club_name', $this->name, TRUE, array( 'maxLength' => 255 ) ) );
       
        if (
$acp or ( $new and count( $availableTypes ) > 1 ) )
        {
           
$form->add( new \IPS\Helpers\Form\Radio( 'club_type', $this->type, TRUE, array(
               
'options' => $new ? $availableTypes : array(
                    \
IPS\Member\Club::TYPE_PUBLIC    => 'club_type_' . \IPS\Member\Club::TYPE_PUBLIC,
                    \
IPS\Member\Club::TYPE_OPEN    => 'club_type_' . \IPS\Member\Club::TYPE_OPEN,
                    \
IPS\Member\Club::TYPE_CLOSED    => 'club_type_' . \IPS\Member\Club::TYPE_CLOSED,
                    \
IPS\Member\Club::TYPE_PRIVATE    => 'club_type_' . \IPS\Member\Club::TYPE_PRIVATE,
                    \
IPS\Member\Club::TYPE_READONLY    => 'club_type_' . \IPS\Member\Club::TYPE_READONLY,
                ),
               
'toggles'    => array(
                    \
IPS\Member\Club::TYPE_OPEN        => array( 'club_membership_fee' ),
                    \
IPS\Member\Club::TYPE_CLOSED    => array( 'club_membership_fee' ),
                    \
IPS\Member\Club::TYPE_PRIVATE    => array( 'club_membership_fee' ),
                    \
IPS\Member\Club::TYPE_READONLY    => array( 'club_membership_fee' ),
                )
            ) ) );
           
            if (
$acp )
            {
               
$form->add( new \IPS\Helpers\Form\Member( 'club_owner', $this->owner, TRUE ) );
            }
        }
       
       
$form->add( new \IPS\Helpers\Form\TextArea( 'club_about', $this->about ) );
       
        if ( \
IPS\Application::appIsEnabled( 'nexus' ) and \IPS\Settings::i()->clubs_paid_on and \IPS\Member::loggedIn()->group['gbw_paid_clubs'] )
        {
           
$form->add( new \IPS\Helpers\Form\Radio( 'club_membership_fee', ( $this->id and $this->fee ) ? 'paid' : 'free', TRUE, array(
               
'options' => array(
                   
'free'    => 'club_membership_free',
                   
'paid'    => 'club_membership_paid'
               
),
               
'toggles' => array(
                   
'paid'    => array( 'club_fee', 'club_renewals' )
                )
            ),
NULL, NULL, NULL, 'club_membership_fee' ) );
           
           
$commissionBlurb = NULL;
           
$fees = NULL;
            if (
$_fees = \IPS\Settings::i()->clubs_paid_transfee )
            {
               
$fees = array();
                foreach (
$_fees as $fee )
                {
                   
$fees[] = (string) ( new \IPS\nexus\Money( $fee['amount'], $fee['currency'] ) );
                }
               
$fees = \IPS\Member::loggedIn()->language()->formatList( $fees, \IPS\Member::loggedIn()->language()->get('or_list_format') );
            }
            if ( \
IPS\Settings::i()->clubs_paid_commission and $fees )
            {
               
$commissionBlurb = \IPS\Member::loggedIn()->language()->addToStack( 'club_fee_desc_both', FALSE, array( 'sprintf' => array( \IPS\Settings::i()->clubs_paid_commission, $fees ) ) );
            }
            elseif ( \
IPS\Settings::i()->clubs_paid_commission )
            {
               
$commissionBlurb = \IPS\Member::loggedIn()->language()->addToStack('club_fee_desc_percent', FALSE, array( 'sprintf' => \IPS\Settings::i()->clubs_paid_commission ) );
            }
            elseif (
$fees )
            {
               
$commissionBlurb = \IPS\Member::loggedIn()->language()->addToStack('club_fee_desc_fee', FALSE, array( 'sprintf' => $fees ) );
            }
           
            \
IPS\Member::loggedIn()->language()->words['club_fee_desc'] = $commissionBlurb;            
           
$form->add( new \IPS\nexus\Form\Money( 'club_fee', $this->id ? json_decode( $this->fee, TRUE ) : array(), NULL, array(), NULL, NULL, NULL, 'club_fee' ) );
           
$form->add( new \IPS\Helpers\Form\Radio( 'club_renewals', $this->id ? ( $this->renewal_term ? 1 : 0 ) : 0, TRUE, array(
               
'options'    => array( 0 => 'club_renewals_off', 1 => 'club_renewals_on' ),
               
'toggles'    => array( 1 => array( 'club_renewal_term' ) )
            ),
NULL, NULL, NULL, 'club_renewals' ) );
            \
IPS\Member::loggedIn()->language()->words['club_renewal_term_desc'] = $commissionBlurb;
           
$renewTermForEdit = NULL;
            if (
$this->id and $this->renewal_term )
            {
               
$renewPrices = array();
                foreach (
json_decode( $this->renewal_price, TRUE ) as $currency => $data )
                {
                   
$renewPrices[ $currency ] = new \IPS\nexus\Money( $data['amount'], $currency );
                }
               
$renewTermForEdit = new \IPS\nexus\Purchase\RenewalTerm( $renewPrices, new \DateInterval( 'P' . $this->renewal_term . mb_strtoupper( $this->renewal_units ) ) );
            }
           
$form->add( new \IPS\nexus\Form\RenewalTerm( 'club_renewal_term', $renewTermForEdit, NULL, array( 'allCurrencies' => TRUE ), NULL, NULL, NULL, 'club_renewal_term' ) );

        }
       
       
$form->add( new \IPS\Helpers\Form\Upload( 'club_profile_photo', $this->profile_photo ? \IPS\File::get( 'core_Clubs', $this->profile_photo ) : NULL, FALSE, array( 'storageExtension' => 'core_Clubs', 'image' => array( 'maxWidth' => 200, 'maxHeight' => 200 ) ) ) );
       
        if ( \
IPS\Settings::i()->clubs_locations )
        {
           
$form->add( new \IPS\Helpers\Form\Address( 'club_location', $this->location_json ? \IPS\GeoLocation::buildFromJson( $this->location_json ) : NULL, FALSE, array( 'requireFullAddress' => FALSE ) ) );
        }
       
       
$fieldValues = $this->fieldValues();
        foreach ( \
IPS\Member\Club\CustomField::roots() as $field )
        {
           
$form->add( $field->buildHelper( isset( $fieldValues["field_{$field->id}"] ) ? $fieldValues["field_{$field->id}"] : NULL ) );
        }
       
        if (
$values = $form->values() )
        {
           
$this->name = $values['club_name'];

           
/* If there is only one type available, set it. */
           
if( count( $availableTypes ) == 1 )
            {
               
$values['club_type'] = key( $availableTypes );
            }

           
$needToUpdatePermissions = FALSE;            
            if (
$acp )
            {
                if (
$this->type != $values['club_type'] )
                {
                   
$this->type = $values['club_type'];
                   
$needToUpdatePermissions = TRUE;
                }
                if (
$this->owner != $values['club_owner'] )
                {
                   
$this->owner = $values['club_owner'];
                   
$this->addMember( $values['club_owner'], \IPS\Member\Club::STATUS_LEADER, TRUE );
                }
            }
            elseif (
$new )
            {
               
$this->type = $values['club_type'];
               
$this->owner = \IPS\Member::loggedIn();
            }
           
           
$this->about = $values['club_about'];
           
$this->profile_photo = (string) $values['club_profile_photo'];
           
            if ( isset(
$values['club_location'] ) )
            {
               
$this->location_json = json_encode( $values['club_location'] );
                if (
$values['club_location']->lat and $values['club_location']->long )
                {
                   
$this->location_lat = $values['club_location']->lat;
                   
$this->location_long = $values['club_location']->long;
                }
                else
                {
                   
$this->location_lat = NULL;
                   
$this->location_long = NULL;
                }
            }
            else
            {
               
$this->location_json = NULL;
               
$this->location_lat = NULL;
               
$this->location_long = NULL;
            }
           
            if ( \
IPS\Application::appIsEnabled( 'nexus' ) and \IPS\Settings::i()->clubs_paid_on and \IPS\Member::loggedIn()->group['gbw_paid_clubs'] )
            {
                switch (
$values['club_membership_fee'] )
                {
                    case
'free':
                       
$this->fee = NULL;
                       
$this->renewal_term = 0;
                       
$this->renewal_units = NULL;
                       
$this->renewal_price = NULL;
                        break;
                   
                    case
'paid':
                       
$this->fee = json_encode( $values['club_fee'] );
                        if (
$values['club_renewals'] and $values['club_renewal_term'] )
                        {                        
                           
$term = $values['club_renewal_term']->getTerm();
                           
$this->renewal_term = $term['term'];
                           
$this->renewal_units = $term['unit'];
                           
$this->renewal_price = json_encode( $values['club_renewal_term']->cost );
                        }
                        else
                        {
                           
$this->renewal_term = 0;
                           
$this->renewal_units = NULL;
                           
$this->renewal_price = NULL;
                        }
                        break;
                }
            }
           
           
$this->save();
           
            if (
$new )
            {
               
$this->addMember( \IPS\Member::loggedIn(), \IPS\Member\Club::STATUS_LEADER );
            }
           
$this->recountMembers();
           
           
$customFieldValues = array();
            foreach ( \
IPS\Member\Club\CustomField::roots() as $field )
            {
                if ( isset(
$values["core_clubfield_{$field->id}"] ) )
                {
                   
$helper                                         = $field->buildHelper();
                   
                    if (
$helper instanceof \IPS\Helpers\Form\Upload )
                    {
                       
$customFieldValues[ "field_{$field->id}" ] = (string) $values["core_clubfield_{$field->id}"];
                    }
                    else
                    {
                       
$customFieldValues[ "field_{$field->id}" ]    = $helper::stringValue( $values["core_clubfield_{$field->id}"] );
                    }
                   
                    if (
$field->type === 'Editor' )
                    {
                       
$field->claimAttachments( $this->id );
                    }
                }
            }
            if (
count( $customFieldValues ) )
            {
               
$customFieldValues['club_id'] = $this->id;
                \
IPS\Db::i()->insert( 'core_clubs_fieldvalues', $customFieldValues, TRUE );
            }
                       
            if (
$needToUpdatePermissions )
            {
                foreach (
$this->nodes() as $node )
                {
                    try
                    {
                       
$nodeClass = $node['node_class'];
                       
$node = $nodeClass::load( $node['node_id'] );
                       
$node->setPermissionsToClub( $this );
                    }
                    catch ( \
Exception $e ) { }
                }
            }

            if(
$new and \IPS\Settings::i()->clubs_require_approval and !$this->approved )
            {
               
/* Send notification to mods */
               
$moderators = array( 'm' => array(), 'g' => array() );
                foreach ( \
IPS\Db::i()->select( '*', 'core_moderators' ) as $mod )
                {
                   
$canView = FALSE;
                    if (
$mod['perms'] == '*' )
                    {
                       
$canView = TRUE;
                    }
                    if (
$canView === FALSE )
                    {
                       
$perms = json_decode( $mod['perms'], TRUE );

                        if ( isset(
$perms['can_access_all_clubs'] ) AND $perms['can_access_all_clubs'] === TRUE )
                        {
                           
$canView = TRUE;
                        }
                    }
                    if (
$canView === TRUE )
                    {
                       
$moderators[ $mod['type'] ][] = $mod['id'];
                    }
                }
               
$notification = new \IPS\Notification( \IPS\Application::load('core'), 'unapproved_club', $this, array( $this ) );
                foreach ( \
IPS\Db::i()->select( '*', 'core_members', ( count( $moderators['m'] ) ? \IPS\Db::i()->in( 'member_id', $moderators['m'] ) . ' OR ' : '' ) . \IPS\Db::i()->in( 'member_group_id', $moderators['g'] ) . ' OR ' . \IPS\Db::i()->findInSet( 'mgroup_others', $moderators['g'] ) ) as $member )
                {
                    if(
$member['member_id'] != \IPS\Member::loggedIn()->member_id )
                    {
                       
$notification->recipients->attach( \IPS\Member::constructFromData( $member ) );
                    }
                }

                if(
count( $notification->recipients ) )
                {
                   
$notification->send();
                }
            }

            return
NULL;
        }
       
        return
$form;
    }
   
   
/**
     * Custom Field Values
     *
     * @return    array
     */
   
public function fieldValues()
    {
        try
        {
            return \
IPS\Db::i()->select( '*', 'core_clubs_fieldvalues', array( 'club_id=?', $this->id ) )->first();
        }
        catch ( \
UnderflowException $e )
        {
            return array();
        }
    }
   
   
/**
     * Cover Photo
     *
     * @param    bool    $getOverlay    If FALSE, will not set the overlay, which saves queries if it will not be used (such as in clubCard)
     * @return    \IPS\Helpers\CoverPhoto
     */
   
public function coverPhoto( $getOverlay=TRUE, $position='full' )
    {
       
$photo = new \IPS\Helpers\CoverPhoto;
        if (
$this->cover_photo )
        {
           
$photo->file = \IPS\File::get( 'core_Clubs', $this->cover_photo );
           
$photo->offset = $this->cover_offset;
        }
        if (
$getOverlay )
        {
           
$photo->overlay = \IPS\Theme::i()->getTemplate( 'clubs', 'core', 'front' )->coverPhotoOverlay( $this, $position );
        }
       
$photo->editable = $this->isLeader();
       
$photo->object = $this;

        return
$photo;
    }

   
/**
     * Produce a random hex color for a background
     *
     * @return string
     */
   
public function coverPhotoBackgroundColor()
    {
        return
$this->staticCoverPhotoBackgroundColor( $this->name );
    }
   
   
/**
     * Location
     *
     * @return    \IPS\GeoLocation|NULL
     */
   
public function location()
    {
        if (
$this->location_json )
        {
            return \
IPS\GeoLocation::buildFromJson( $this->location_json );
        }
        return
NULL;
    }
   
   
/**
     * Is paid?
     *
     * @return    string|NULL
     */
   
public function isPaid()
    {
        if ( \
IPS\Application::appIsEnabled( 'nexus' ) and \IPS\Settings::i()->clubs_paid_on and $this->fee )
        {
            return
TRUE;
        }
       
        return
FALSE;
    }
   
   
/**
     * Message to explain paid club joining process
     *
     * @return    string
     */
   
public function memberFeeMessage()
    {
        if (
$this->type === static::TYPE_CLOSED )
        {
            return \
IPS\Member::loggedIn()->language()->addToStack( 'club_closed_join_fee', FALSE, array( 'sprintf' => array( $this->priceBlurb() ) ) );
        }
        else
        {
            return \
IPS\Member::loggedIn()->language()->addToStack( 'club_open_join_fee', FALSE, array( 'sprintf' => array( $this->priceBlurb() ) ) );
        }
    }
   
   
/**
     * Joining fee
     *
     * @param    string|NULL    $currency    Desired currency, or NULL to choose based on member's chosen currency
     * @return    \IPS\nexus\Money|NULL
     */
   
public function joiningFee( $currency = NULL )
    {
        if (
$this->isPaid() )
        {
            if ( !
$currency )
            {
               
$currency = ( isset( $_SESSION['currency'] ) and in_array( $_SESSION['currency'], \IPS\nexus\Money::currencies() ) ) ? $_SESSION['currency'] : \IPS\nexus\Customer::loggedIn()->defaultCurrency();
            }
           
           
$costs = json_decode( $this->fee, TRUE );
           
            if (
is_array( $costs ) and isset( $costs[ $currency ]['amount'] ) and $costs[ $currency ]['amount'] )
            {
                return new \
IPS\nexus\Money( $costs[ $currency ]['amount'], $currency );
            }
        }
       
        return
NULL;
    }
   
   
/**
     * Joining fee
     *
     * @param    string|NULL    $currency    Desired currency, or NULL to choose based on member's chosen currency
     * @return    \IPS\nexus\Money|NULL
     * @throws    \OutOfRangeException
     */
   
public function renewalTerm( $currency = NULL )
    {
        if (
$this->renewal_price and $renewalPrices = json_decode( $this->renewal_price, TRUE ) )
        {
            if ( !
$currency )
            {
               
$currency = ( isset( $_SESSION['currency'] ) and in_array( $_SESSION['currency'], \IPS\nexus\Money::currencies() ) ) ? $_SESSION['currency'] : \IPS\nexus\Customer::loggedIn()->defaultCurrency();
            }
           
            if ( isset(
$renewalPrices[ $currency ] ) )
            {
                return new \
IPS\nexus\Purchase\RenewalTerm( new \IPS\nexus\Money( $renewalPrices[ $currency ]['amount'], $currency ), new \DateInterval( 'P' . $this->renewal_term . mb_strtoupper( $this->renewal_units ) ) );
            }
            else
            {
                throw new \
OutOfRangeException;
            }
        }
       
        return
NULL;
    }
   
   
/**
     * Price Blurb
     *
     * @return    string|NULL
     */
   
public function priceBlurb()
    {
        if ( \
IPS\Application::appIsEnabled( 'nexus' ) and \IPS\Settings::i()->clubs_paid_on )
        {
            if (
$this->isPaid() )
            {                
                if (
$fee = $this->joiningFee() )
                {
                    try
                    {
                       
$renewalTerm = $this->renewalTerm( $fee->currency );
                        if (
$renewalTerm->cost->amount == $fee->amount )
                        {
                            return
$renewalTerm;
                        }
                        else
                        {
                            return \
IPS\Member::loggedIn()->language()->addToStack( 'club_fee_plus_renewal', FALSE, array( 'sprintf' => array( $fee, $renewalTerm ) ) );
                        }
                    }
                    catch ( \
OutOfRangeException $e )
                    {
                        return \
IPS\Member::loggedIn()->language()->addToStack('club_paid_unavailable');
                    }
                }
                else
                {
                    return \
IPS\Member::loggedIn()->language()->addToStack('club_paid_unavailable');
                }
            }
            else
            {
                return \
IPS\Member::loggedIn()->language()->addToStack('club_membership_free');
            }
        }
        return
NULL;
    }
   
       
   
/* !Manage Memberships */
       
    /**
     * Get members
     *
     * @param    array    $statuses            The membership statuses to get
     * @param    int        $limit                Number to get
     * @param    string    $order                ORDER BY clause
     * @param    int        $returnType            0 = core_clubs_memberships rows, 1 = core_clubs_memberships plus \IPS\Member::columnsForPhoto(), 2 = full core_members rows, 3 = same as 1 but also getting name of adder/invitee, 4 = count only, 5 = same as 3 but also getting expire date
     * @return    \IPS\Db\Select|int
     */
   
public function members( $statuses = array( 'member', 'moderator', 'leader' ), $limit = 25, $order = 'core_clubs_memberships.joined ASC', $returnType = 1 )
    {    
        if (
$returnType === 4 )
        {
            return \
IPS\Db::i()->select( 'COUNT(*)', 'core_clubs_memberships', array( array( 'club_id=?', $this->id ), array( \IPS\Db::i()->in( 'status', $statuses ) ) ) )->first();
        }
        else
        {
            if (
$returnType === 2 )
            {
               
$columns = 'core_members.*';
            }
            else
            {
               
$columns = 'core_clubs_memberships.member_id,core_clubs_memberships.joined,core_clubs_memberships.status,core_clubs_memberships.added_by,core_clubs_memberships.invited_by';
                if (
$returnType === 1 or $returnType === 3 or $returnType === 5 )
                {
                   
$columns .= ',' . implode( ',', array_map( function( $column ) {
                        return
'core_members.' . $column;
                    }, \
IPS\Member::columnsForPhoto() ) );
                }
                if (
$returnType === 3 or $returnType === 5 )
                {
                   
$columns .= ',added_by.name,invited_by.name';
                   
                    if (
$returnType === 5 and \IPS\Application::appIsEnabled('nexus') and \IPS\Settings::i()->clubs_paid_on and $this->isPaid() and $this->renewal_price )
                    {
                       
$columns .= ',nexus_purchases.ps_active,nexus_purchases.ps_expire';
                    }
                }
            }
           
           
$select = \IPS\Db::i()->select( $columns, 'core_clubs_memberships', array( array( 'club_id=?', $this->id ), array( \IPS\Db::i()->in( 'status', $statuses ) ) ), $order, $limit, NULL, NULL, \IPS\Db::SELECT_SQL_CALC_FOUND_ROWS + \IPS\Db::SELECT_MULTIDIMENSIONAL_JOINS );
        }
       
        if (
$returnType === 1 or $returnType === 2 or $returnType === 3 or $returnType === 5 )
        {
           
$select->join( 'core_members', 'core_members.member_id=core_clubs_memberships.member_id' );
        }
        if (
$returnType === 3 or $returnType === 5 )
        {
           
$select->join( array( 'core_members', 'added_by' ), 'added_by.member_id=core_clubs_memberships.added_by' );
           
$select->join( array( 'core_members', 'invited_by' ), 'invited_by.member_id=core_clubs_memberships.invited_by' );
           
            if (
$returnType === 5 and \IPS\Application::appIsEnabled('nexus') and \IPS\Settings::i()->clubs_paid_on and $this->isPaid() and $this->renewal_price )
            {
               
$select->join( 'nexus_purchases', array( 'ps_app=? AND ps_type=? AND ps_member=core_clubs_memberships.member_id AND ps_item_id=? AND ps_cancelled=0', 'core', 'club', $this->id ) );
            }
        }
                       
        return
$select;
    }    
   
   
/**
     * @brief    Cache of randomTenMembers()
     */
   
protected $_randomTenMembers = NULL;
   
   
/**
     * Get basic data of a random ten members in the club (for cards)
     *
     * @return    array
     */
   
public function randomTenMembers()
    {
        if ( !isset(
$this->_randomTenMembers ) )
        {
           
$this->_randomTenMembers = iterator_to_array( $this->members( array( 'leader', 'moderator', 'member' ), 10, 'RAND()' ) );
        }
        return
$this->_randomTenMembers;
    }
   
   
/**
     * Add a member
     *
     * @param    \IPS\Member            $member        The member
     * @param    bool                $status        Status
     * @param    bool                $update        Update membership if already a member?
     * @param    \IPS\Member|NULL    $addedBy    The leader who added them, or NULL if joining themselves
     * @param    \IPS\Member|NULL    $invitedBy    The member who invited them, or NULL if joining themselves
     * @param    bool                $updateJoinedDate    Whether to update the joined date or not (FALSE by default, set to TRUE when an invited member accepts)
     * @return    void
     * @throws    \OverflowException    Member is already in the club and $update was FALSE
     */
   
public function addMember( \IPS\Member $member, $status = 'member', $update = FALSE, \IPS\Member $addedBy = NULL, \IPS\Member $invitedBy = NULL, $updateJoinedDate = FALSE )
    {
        try
        {
            \
IPS\Db::i()->insert( 'core_clubs_memberships', array(
               
'club_id'    => $this->id,
               
'member_id'    => $member->member_id,
               
'joined'    => time(),
               
'status'    => $status,
               
'added_by'    => $addedBy ? $addedBy->member_id : NULL,
               
'invited_by'=> $invitedBy ? $invitedBy->member_id : NULL
           
) );

           
$member->rebuildPermissionArray();
            if ( \
IPS\Settings::i()->club_nodes_in_apps )
            {
               
$member->create_menu = NULL;
               
$member->save();
            }
        }
        catch ( \
IPS\Db\Exception $e )
        {
            if (
$e->getCode() === 1062 )
            {
                if (
$update )
                {
                   
$save = array( 'status'    => $status );
                    if (
$addedBy )
                    {
                       
$save['added_by'] = $addedBy->member_id;
                    }

                    if(
$updateJoinedDate === TRUE )
                    {
                       
$save['joined']    = time();
                    }
                                       
                    \
IPS\Db::i()->update( 'core_clubs_memberships', $save, array( 'club_id=? AND member_id=?', $this->id, $member->member_id ) );
                   
                   
$member->rebuildPermissionArray();
                    if ( \
IPS\Settings::i()->club_nodes_in_apps )
                    {
                       
$member->create_menu = NULL;
                       
$member->save();
                    }
                }
                else
                {
                    throw new \
OverflowException;
                }
            }
            else
            {
                throw
$e;
            }            
        }
    }
   
   
/**
     * Remove a member
     *
     * @param    \IPS\Member    $member        The member
     * @return    void
     */
   
public function removeMember( \IPS\Member $member )
    {
        \
IPS\Db::i()->delete( 'core_clubs_memberships', array( 'club_id=? AND member_id=?', $this->id, $member->member_id ) );
       
       
$member->rebuildPermissionArray();
        if ( \
IPS\Settings::i()->club_nodes_in_apps )
        {
           
$member->create_menu = NULL;
           
$member->save();
        }
    }
   
   
/**
     * Recount members
     *
     * @return    void
     */
   
public function recountMembers()
    {
       
$this->members = \IPS\Db::i()->select( 'COUNT(*)', 'core_clubs_memberships', array( 'club_id=? AND ( status=? OR status=? OR status=? OR status=? OR status=? )', $this->id, static::STATUS_MEMBER, static::STATUS_MODERATOR, static::STATUS_LEADER, static::STATUS_EXPIRED, static::STATUS_EXPIRED_MODERATOR ) );
       
$this->save();
    }
   
   
/* !Manage Nodes */
   
    /**
     * Get available features
     *
     * @param    \IPS\Member|NULL    If a member object is provided, will opnly get the types that member can create
     * @return    array
     */
   
public static function availableNodeTypes( \IPS\Member $member = NULL )
    {
       
$return = array();
                       
        foreach ( \
IPS\Application::allExtensions( 'core', 'ContentRouter' ) as $contentRouter )
        {
            foreach (
$contentRouter->classes as $class )
            {
                if ( isset(
$class::$containerNodeClass ) and \IPS\IPS::classUsesTrait( $class::$containerNodeClass, 'IPS\Content\ClubContainer' ) )
                {                    
                    if (
$member === NULL or $member->group['g_club_allowed_nodes'] === '*' or in_array( $class::$containerNodeClass, explode( ',', $member->group['g_club_allowed_nodes'] ) ) )
                    {
                       
$return[] = $class::$containerNodeClass;
                    }
                }
            }
        }
               
        return
array_unique( $return );
    }

   
/**
     * @brief    Cached nodes
     */
   
protected $cachedNodes    = NULL;
   
   
/**
     * Get Node names and URLs
     *
     * @return    array
     */
   
public function nodes()
    {
        if(
$this->cachedNodes === NULL )
        {
           
$this->cachedNodes = array();
           
            foreach ( \
IPS\Db::i()->select( '*', 'core_clubs_node_map', array( 'club_id=?', $this->id ) ) as $row )
            {
               
$class        = $row['node_class'];
               
$classBits    = explode( '\\', $class );

                if( !\
IPS\Application::load( $classBits[1] )->_enabled )
                {
                    continue;
                }
               
               
$this->cachedNodes[ $row['id'] ] = array(
                   
'name'            => $row['name'],
                   
'url'            => \IPS\Http\Url::internal( $class::$urlBase . $row['node_id'], 'front', $class::$urlTemplate, array( \IPS\Http\Url\Friendly::seoTitle( $row['name'] ) ) ),
                   
'node_class'    => $row['node_class'],
                   
'node_id'        => $row['node_id'],
                );
            }
        }
       
        return
$this->cachedNodes;
    }
   
   
/* !Permissions */
   
    /**
     * Load and check permissions
     *
     * @param    mixed    $id        ID
     * @return    static
     * @throws    \OutOfRangeException
     */
   
public static function loadAndCheckPerms( $id )
    {
       
$obj = static::load( $id );

        if ( !
$obj->canView() )
        {
            throw new \
OutOfRangeException;
        }

        return
$obj;
    }
   
   
/**
     * Can a member see this club and who's in it?
     *
     * @param    \IPS\Member    $member    The member (NULL for currently logged in member)
     * @return    bool
     */
   
public function canView( \IPS\Member $member = NULL )
    {
       
$member = $member ?: \IPS\Member::loggedIn();
       
       
/* If we can't access the module, stop here */
       
if ( !$member->canAccessModule( \IPS\Application\Module::get( 'core', 'clubs', 'front' ) ) )
        {
            return
FALSE;
        }

       
/* If it's not approved, only moderators and the person who created it can see it */
       
if ( \IPS\Settings::i()->clubs_require_approval and !$this->approved )
        {
            return (
$member->modPermission('can_access_all_clubs') or ( $this->owner AND $member->member_id == $this->owner->member_id ) );
        }
       
       
/* Unless it's private, everyone can see it exists */
       
if ( $this->type !== static::TYPE_PRIVATE )
        {
            return
TRUE;
        }
       
       
/* Moderators can see everything */
       
if ( $member->modPermission('can_access_all_clubs') )
        {
            return
TRUE;
        }
               
       
/* Otherwise, only if they're a member or have been invited */        
       
return in_array( $this->memberStatus( $member ), array( static::STATUS_MEMBER, static::STATUS_MODERATOR, static::STATUS_LEADER, static::STATUS_INVITED, static::STATUS_INVITED_BYPASSING_PAYMENT, static::STATUS_EXPIRED, static::STATUS_EXPIRED_MODERATOR ) );
    }
   
   
/**
     * Can a member join (or ask to join) this club?
     *
     * @param    \IPS\Member    $member    The member (NULL for currently logged in member)
     * @return    bool
     */
   
public function canJoin( \IPS\Member $member = NULL )
    {
       
/* If it's not approved, nobody can join it */
       
if ( \IPS\Settings::i()->clubs_require_approval and !$this->approved )
        {
            return
FALSE;
        }
       
       
/* Nobody can join public clubs */
       
if ( $this->type === static::TYPE_PUBLIC )
        {
            return
FALSE;
        }
       
       
/* Guests cannot join clubs */
       
$member = $member ?: \IPS\Member::loggedIn();
        if ( !
$member->member_id )
        {
            return
FALSE;
        }
       
       
/* If they're already a member, or have aleready asked to join, they can't join again */
       
$memberStatus = $this->memberStatus( $member );
        if (
in_array( $memberStatus, array( static::STATUS_MEMBER, static::STATUS_MODERATOR, static::STATUS_LEADER, static::STATUS_REQUESTED, static::STATUS_DECLINED, static::STATUS_EXPIRED, static::STATUS_EXPIRED_MODERATOR ) ) )
        {
            return
FALSE;
        }

       
/* If they are banned, they cannot join */
       
if ( $memberStatus === static::STATUS_BANNED )
        {
            return
FALSE;
        }
       
       
/* If it's private or read-only, they have to be invited */
       
if ( $this->type === static::TYPE_PRIVATE or $this->type === static::TYPE_READONLY )
        {
            return
in_array( $memberStatus, array( static::STATUS_INVITED, static::STATUS_INVITED_BYPASSING_PAYMENT ) );
        }
       
       
/* Otherwise they can join */
       
return TRUE;
    }
   
   
/**
     * Can a member see the posts in this club?
     *
     * @param    \IPS\Member    $member    The member (NULL for currently logged in member)
     * @return    bool
     */
   
public function canRead( \IPS\Member $member = NULL )
    {
        switch (
$this->type )
        {
            case static::
TYPE_PUBLIC:
            case static::
TYPE_OPEN:
            case static::
TYPE_READONLY:
                return
TRUE;
               
            case static::
TYPE_CLOSED:
            case static::
TYPE_PRIVATE:
               
$member = $member ?: \IPS\Member::loggedIn();
                return
$member->modPermission('can_access_all_clubs') or in_array( $this->memberStatus( $member ), array( static::STATUS_MEMBER, static::STATUS_MODERATOR, static::STATUS_LEADER ) );
        }
    }
   
   
/**
     * Can a member participate this club?
     *
     * @param    \IPS\Member    $member    The member (NULL for currently logged in member)
     * @return    bool
     */
   
public function canPost( \IPS\Member $member = NULL )
    {
        switch (
$this->type )
        {
            case static::
TYPE_PUBLIC:
                return
TRUE;
               
            case static::
TYPE_OPEN:
            case static::
TYPE_CLOSED:
            case static::
TYPE_PRIVATE:
            case static::
TYPE_READONLY:
               
$member = $member ?: \IPS\Member::loggedIn();
                return
$member->modPermission('can_access_all_clubs') or in_array( $this->memberStatus( $member ), array( static::STATUS_MEMBER, static::STATUS_MODERATOR, static::STATUS_LEADER ) );
        }
    }
   
   
/**
     * Can a member invite other members
     *
     * @param    \IPS\Member    $member    The member (NULL for currently logged in member)
     * @return    bool
     */
   
public function canInvite( \IPS\Member $member = NULL )
    {
        if ( \
IPS\Settings::i()->clubs_require_approval and !$this->approved )
        {
            return
FALSE;
        }
       
        switch (
$this->type )
        {
            case static::
TYPE_PUBLIC:
                return
FALSE;
               
            case static::
TYPE_OPEN:
               
$member = $member ?: \IPS\Member::loggedIn();
                return
$member->modPermission('can_access_all_clubs') or in_array( $this->memberStatus( $member ), array( static::STATUS_MEMBER, static::STATUS_MODERATOR, static::STATUS_LEADER ) );
               
            case static::
TYPE_CLOSED:
            case static::
TYPE_PRIVATE:
            case static::
TYPE_READONLY:
                return
$this->isLeader( $member );
        }
    }
   
   
/**
     * Does this user have leader permissions in the club?
     *
     * @param    \IPS\Member    $member    The member (NULL for currently logged in member)
     * @return    bool
     */
   
public function isLeader( \IPS\Member $member = NULL )
    {
       
$member = $member ?: \IPS\Member::loggedIn();
        return
$member->modPermission('can_access_all_clubs') or $this->memberStatus( $member ) === static::STATUS_LEADER;
    }
   
   
/**
     * Does this user have moderator permissions in the club?
     *
     * @param    \IPS\Member    $member    The member (NULL for currently logged in member)
     * @return    bool
     */
   
public function isModerator( \IPS\Member $member = NULL )
    {
       
$member = $member ?: \IPS\Member::loggedIn();
        return
$member->modPermission('can_access_all_clubs') or in_array( $this->memberStatus( $member ), array( static::STATUS_MODERATOR, static::STATUS_LEADER ) );
    }

   
   
/**
     * @brief    Membership status cache
     */
   
protected $_memberStatuses = array();
   
   
/**
     * Get status of a particular member
     *
     * @param    \IPS\Member    $member        The member
     * @param    int            $returnType    1 will return a string with the type or NULL if not applicable. 2 will return array with status, joined, accepted_by, invited_by
     * @return    mixed
     */
   
public function memberStatus( \IPS\Member $member, $returnType = 1 )
    {
        if ( !
$member->member_id )
        {
            return
NULL;
        }
               
        if ( !isset(
$this->_memberStatuses[ $member->member_id ] ) or $returnType === 2 )
        {
            try
            {
               
$val = \IPS\Db::i()->select( $returnType === 2 ? '*' : array( 'status' ), 'core_clubs_memberships', array( 'club_id=? AND member_id=?', $this->id, $member->member_id ) )->first();
               
                if (
$returnType === 2 )
                {
                    return
$val;
                }
                else
                {
                   
$this->_memberStatuses[ $member->member_id ] = $val;
                }
            }
            catch ( \
UnderflowException $e )
            {
               
$this->_memberStatuses[ $member->member_id ] = NULL;
            }
        }
       
        return
$this->_memberStatuses[ $member->member_id ];
    }
   
   
/* ! Following */
   
   
const FOLLOW_PUBLIC = 1;
    const
FOLLOW_ANONYMOUS = 2;
   
   
/**
     * @brief    Cache for current follow data, used on "My Followed Content" screen
     */
   
public $_followData;
       
   
/**
     * @brief    Application
     */
   
public static $application = 'core';
   
   
/**
     * Container Follower Count
     *
     * @return    int
     */
   
public function followerCount()
    {
        return \
IPS\Db::i()->select( 'COUNT(*)', 'core_follow', array( 'follow_app=? AND follow_area=? AND follow_rel_id=?', 'core', 'club', $this->id ) )->first();
    }
   
   
/**
     * Followers
     *
     * @param    int                        $privacy        static::FOLLOW_PUBLIC + static::FOLLOW_ANONYMOUS
     * @param    array                    $frequencyTypes    array( 'immediate', 'daily', 'weekly' )
     * @param    \IPS\DateTime|int|NULL    $date            Only users who started following before this date will be returned. NULL for no restriction
     * @param    int|array                $limit            LIMIT clause
     * @param    string                    $order            Column to order by
     * @param    int                        $flags            Flags to pass to select (e.g. \IPS\Db::SELECT_SQL_CALC_FOUND_ROWS)
     * @param    int
     * @return    \IPS\Db\Select|NULL
     * @throws    \BadMethodCallException
     */
   
public function followers( $privacy=3, $frequencyTypes=array( 'immediate', 'daily', 'weekly' ), $date=NULL, $limit=array( 0, 25 ), $order=NULL, $flags=\IPS\Db::SELECT_SQL_CALC_FOUND_ROWS )
    {        
        return static::
_followers( 'club', $this->id, $privacy, $frequencyTypes, $date, $limit, $order, $flags );
    }
   
   
/* ! Utility */
   
    /**
     * Remove nodes that are owned by a specific application. Used when uninstalling an app
     *
     * @param    \IPS\Application    $app    The application being deleted
     * @return void
     */
   
public static function deleteByApplication( \IPS\Application $app )
    {
        foreach( \
IPS\Db::i()->select( 'node_class', 'core_clubs_node_map', NULL, NULL, NULL, 'node_class' ) as $class )
        {
            if ( isset(
$class::$contentItemClass ) )
            {
               
$contentItemClass = $class::$contentItemClass;

                if (
$contentItemClass::$application == $app->directory )
                {
                    \
IPS\Db::i()->delete( 'core_clubs_node_map', array( 'node_class=?', $class  ) );
                }
            }
        }
    }
}