Jump to content

Community

Developer Documentation

Clubs

Invision Community 4.2 has introduced the new Clubs feature. This feature allows members to create their own sub-communities within the community. 3rd party developers can extend Clubs to allow users to add Nodes specific to that Club.

 

Implementing Club Support

In order to add Club support to your nodes, the following adjustments must be made to your Node model. First, you must add the \IPS\Content\ClubContainer trait to your Model, which will then expose the following methods from the trait. All of which can be overridden in your Model directly, if specific changes or checks need to be made.

	/**
	 * Get the database column which stores the club ID
	 *
	 * @return	string
	 */
	public static function clubIdColumn()
	
	/**
	 * Get front-end language string
	 *
	 * @return	string
	 */
	public static function clubFrontTitle()
	
	/**
	 * Get acp language string
	 *
	 * @return	string
	 */
	public static function clubAcpTitle()
	
	/**
	 * Get the associated club
	 *
	 * @return	\IPS\Member\Club|NULL
	 */
	public function club()
		
	/**
	 * Set form for creating a node of this type in a club
	 *
	 * @param	\IPS\Helpers\Form	$form	Form object
	 * @return	void
	 */
	public function clubForm( \IPS\Helpers\Form $form )
	
	/**
	 * Save club form
	 *
	 * @param	\IPS\Member\Club	$club	The club
	 * @param	array				$values	Values
	 * @return	void
	 */
	public function saveClubForm( \IPS\Member\Club $club, $values )
	
	/**
	 * Class-specific routine when saving club form
	 *
	 * @param	\IPS\Member\Club	$club	The club
	 * @param	array				$values	Values
	 * @return	void
	 */
	public function _saveClubForm( \IPS\Member\Club $club, $values )
	
	/**
	 * Set the permission index permissions to a specific club
	 *
	 * @param	\IPS\Member\Club	$club	The club
	 * @return  void
	 */
	public function setPermissionsToClub( \IPS\Member\Club $club )
	
	/**
	 * Check Moderator Permission
	 *
	 * @param	string						$type		'edit', 'hide', 'unhide', 'delete', etc.
	 * @param	\IPS\Member|NULL			$member		The member to check for or NULL for the currently logged in member
	 * @return	bool
	 */
	public function modPermission( $type, \IPS\Member $member )
		
	/**
	 * [Node] Get parent list
	 *
	 * @return	\SplStack
	 */
	public function parents()

Some important notes about specific methods:

  • clubIdColumn() - This will be the name of the column without the database prefix. So, if your column is node_club_id and your database prefix is node_, then this method should only return club_id. The default return value from the trait is club_id.
  • clubForm() - By default, this method will simply add a Text field for the name of the Node within the club. You can optionally, however, overload this method to add more elements to the form (such as a description, for example). If your node only needs a title, then it is not necessary to overload this method.
  • _saveClubForm() - By default, this method does nothing. If Nodes need to perform additional processing before the club node is saved, then they should overload this method, rather than saveClubForm().
  • modPermission() - This method automatically overloads \IPS\Node\Model::modPermission() and will automatically check the permissions of both club specific moderators (as assigned by club leaders), club leaders, club owners,, as well as actual site moderators who are not already a part of the club.
  • parents() - This method automatically overloads \IPS\Node\Mode::parents() and will always return an empty SplStack object if the node is associated with a club.

 

Interface

A few alterations to your templates, as well as your controllers, need to be made to fully implements the Clubs look and feel into your nodes. Club nodes will load from within the context of your Application, and thus share the same URL structure as normal nodes, and will also use the same controllers and templates.

For your controllers, you need to adjust your breadcrumb navigation to account for the fact that the node is a part of a club, rather than as a normal, standalone, node.

If your application uses \IPS\Helpers\Table\Content to display content from within a node, then this will automatically be taken care of for you. Otherwise, implementing proper navigation is as simple as:

			if ( \IPS\IPS::classUsesTrait( $container, 'IPS\Content\ClubContainer' ) and $club = $container->club() )
			{
				\IPS\core\FrontNavigation::$clubTabActive = TRUE;
				\IPS\Output::i()->breadcrumb = array();
				\IPS\Output::i()->breadcrumb[] = array( \IPS\Http\Url::internal( 'app=core&module=clubs&controller=directory', 'front', 'clubs_list' ), \IPS\Member::loggedIn()->language()->addToStack('module__core_clubs') );
				\IPS\Output::i()->breadcrumb[] = array( $club->url(), $club->name );
				
				if ( \IPS\Settings::i()->clubs_header == 'sidebar' )
				{
					\IPS\Output::i()->sidebar['contextual'] = \IPS\Theme::i()->getTemplate( 'clubs', 'core' )->header( $club, $container, 'sidebar' );
				}
			}
			else
			{
				foreach ( $container->parents() as $parent )
				{
					\IPS\Output::i()->breadcrumb[] = array( $parent->url(), $parent->_title );
				}
			}

The club header / sidebar, however, is not handled automatically and must be manually inserted into your templates when viewing a node, or content item within a node, that belongs to a club. For example:

{{if $club = $node->club()}}
	{{if settings.clubs and settings.clubs_header == 'full'}}
		{template="header" app="core" group="clubs" params="$club, $node"}
	{{endif}}
	<div id='elClubContainer'>
{{endif}}

<1-- My Template Content Here -->

{{if $node->club()}}
	</div>
{{endif}}

If a node is associated with a club, then the inner content must be wrapped in a <div> element, with the id elClubContainer. The same also applies when viewing a content item from within a club node:

{{if $club = $item->container()->club()}}
	{{if settings.clubs and settings.clubs_header == 'full'}}
		{template="header" app="core" group="clubs" params="$club, $item->container()"}
	{{endif}}
	<div id='elClubContainer'>
{{endif}}

<!-- My Content Item Template Content Here -->

{{if $item->container()->club()}}
	</div>
{{endif}}

 

The Clubs

All Clubs are an ActiveRecord using the \IPS\Member\Club class.

namespace IPS\Member;

/**
 * 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 STATUS_MEMBER = 'member';
	const STATUS_INVITED = 'invited';
	const STATUS_REQUESTED = 'requested';
	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';
	
	/**
	 * 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 )
		
	/**
	 * 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		$mineOnly	Limit to clubs a particular member has joined (TRUE to use the same value as $member)
	 * @param	array					$filters	Custom field filters
	 * @param	mixed					$extraWhere	Additional WHERE clause
	 * @return	\IPS\Patterns\ActiveRecordIterator
	 */
	public static function clubs( \IPS\Member $member = NULL, $limit, $sortOption, $mineOnly=FALSE, $filters=array(), $extraWhere=NULL )	
	
	/**
	 * Get number clubs a member is leader of
	 *
	 * @param	\IPS\Member	$member	The member
	 * @return	int
	 */
	public static function numberOfClubsMemberIsLeaderOf( \IPS\Member $member )
		
	/* !ActiveRecord */
	
	/**
	 * Set Default Values
	 *
	 * @return	void
	 */
	public function setDefaultValues()
	
	/**
	 * Get owner
	 *
	 * @return	\IPS\Member|NULL
	 */
	public function get_owner()
	
	/**
	 * Set member
	 *
	 * @param	\IPS\Member
	 * @return	void
	 */
	public function set_owner( \IPS\Member $owner = NULL )
	
	/**
	 * Get created date
	 *
	 * @return	\IPS\DateTime
	 */
	public function get_created()
	
	/**
	 * Set created date
	 *
	 * @param	\IPS\DateTime	$date	The invoice date
	 * @return	void
	 */
	public function set_created( \IPS\DateTime $date )
    
	/**
	 * Get club URL
	 *
	 * @return	\IPS\Http\Url
	 */
	public function url()
	
	/**
	 * Columns needed to query for search result / stream view
	 *
	 * @return	array
	 */
	public static function basicDataColumns()
	
	/**
	 * 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 )
	
	/**
	 * Custom Field Values
	 *
	 * @return	array
	 */
	public function fieldValues()
	
	/**
	 * 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' )
	
	/**
	 * Location
	 *
	 * @return	\IPS\GeoLocation|NULL
	 */
	public function location()
		
	/**
	 * 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
	 * @return	\IPS\Db\Select|int
	 */
	public function members( $statuses = array( 'member', 'moderator', 'leader' ), $limit = 25, $order = 'core_clubs_memberships.joined ASC', $returnType = 1 )
	
	/**
	 * Get basic data of a random ten members in the club (for cards)
	 *
	 * @return	array
	 */
	public function 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
	 * @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 )
	
	/**
	 * Remove a member
	 *
	 * @param	\IPS\Member	$member		The member
	 * @return	void
	 */
	public function removeMember( \IPS\Member $member )
	
	/**
	 * Recount members
	 *
	 * @return	void
	 */
	public function recountMembers()
	
	/* !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 )
	
	/**
	 * Get Node names and URLs
	 *
	 * @return	array
	 */
	public function nodes()
	
	/**
	 * 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 )
	
	/**
	 * 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 )
	
	/**
	 * 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 )
	
	/**
	 * 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 )
	
	/**
	 * 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 )
	
	/**
	 * 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 )
	
	/**
	 * 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 )
	
	/**
	 * 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 )
}

 

Custom Fields

Clubs can also have Custom Fields defined. These are on a per club basis, rather than per Node. If your Node implements Custom Fields per node as a feature, then you will need to ensure those custom fields are present within your Node models clubForm() method.

Aside from the methods defined in the base \IPS\CustomField class, the following methods are defined within the \IPS\Member\Club\CustomField class.

namespace IPS\Member\Club;

/**
 * Clubs Customer Field Node
 */
class _CustomField extends \IPS\CustomField
{
	/**
	 * @brief	[ActiveRecord] Multiton Store
	 */
	protected static $multitons;
	
	/**
	 * @brief	[ActiveRecord] Database Table
	 */
	public static $databaseTable = 'core_clubs_fields';
	
	/**
	 * @brief	[ActiveRecord] Database Prefix
	 */
	public static $databasePrefix = 'f_';
		
	/**
	 * @brief	[Node] Order Database Column
	 */
	public static $databaseColumnOrder = 'position';
	
	/**
	 * @brief	[CustomField] Title/Description lang prefix
	 */
	protected static $langKey = 'core_clubfield';
	
	/**
	 * @brief	[CustomField] Content database table
	 */
	protected static $contentDatabaseTable = 'core_clubs_fieldvalues';
	
	/**
	 * @brief	[Node] ACP Restrictions
	 * @code
	 	array(
	 		'app'		=> 'core',				// The application key which holds the restrictrions
	 		'module'	=> 'foo',				// The module key which holds the restrictions
	 		'map'		=> array(				// [Optional] The key for each restriction - can alternatively use "prefix"
	 			'add'			=> 'foo_add',
	 			'edit'			=> 'foo_edit',
	 			'permissions'	=> 'foo_perms',
	 			'delete'		=> 'foo_delete'
	 		),
	 		'all'		=> 'foo_manage',		// [Optional] The key to use for any restriction not provided in the map (only needed if not providing all 4)
	 		'prefix'	=> 'foo_',				// [Optional] Rather than specifying each  key in the map, you can specify a prefix, and it will automatically look for restrictions with the key "[prefix]_add/edit/permissions/delete"
	 * @endcode
	 */
	protected static $restrictions = array(
		'app'		=> 'core',
		'module'	=> 'clubs',
		'all'		=> 'fields_manage'
	);
	
	/**
	 * @brief	[Node] Node Title
	 */
	public static $nodeTitle = 'clubs_custom_fields';

	/**
	 * @brief	[Node] Title prefix.  If specified, will look for a language key with "{$key}_title" as the key
	 */
	public static $titleLangPrefix = 'core_clubfield_';
	
	/**
	 * @brief	[CustomField] Column Map
	 */
	public static $databaseColumnMap = array(
		'content'	=> 'extra',
		'not_null'	=> 'required',
	);
	
	/**
	 * @brief	[CustomField] Additional Field Toggles
	 */
	public static $additionalFieldToggles = array(
		'Checkbox'		=> array( 'f_filterable' ),
		'CheckboxSet'	=> array( 'f_filterable' ),
		'Radio'			=> array( 'f_filterable' ),
		'Select'		=> array( 'f_filterable' ),
		'YesNo'			=> array( 'f_filterable' ),
	);
	
	/**
	 * @brief	[CustomField] Editor Options
	 */
	public static $editorOptions = array( 'app' => 'core', 'key' => 'Clubs' );
	
	/**
	 * @brief	[CustomField] Upload Storage Extension
	 */
	public static $uploadStorageExtension = 'core_';
	
	/**
	 * Get fields
	 *
	 * @return	array
	 */
	public static function fields()
	
	/**
	 * Get if there are any filterable fields
	 *
	 * @return	bool
	 */
	public static function areFilterableFields()
			
	/**
	 * [Node] Add/Edit Form
	 *
	 * @param	\IPS\Helpers\Form	$form	The form
	 * @return	void
	 */
	public function form( &$form )
		
	/**
	 * [ActiveRecord] Save Record
	 *
	 * @return	void
	 */
	public function save()
	
	/**
	 * [ActiveRecord] Delete Record
	 *
	 * @return	void
	 */
	public function delete()

	/**
	 * [Node] Format form values from add/edit form for save
	 *
	 * @param	array	$values	Values from the form
	 * @return	array
	 */
	public function formatFormValues( $values )
}

 

Edited by Ryan Ashbrook

  Report Document


×
×
  • Create New...