What it does
An AchievementAction extension allows you to define custom actions for Achievement Rules.
- Note that you do not ever have to think directly about points or badges. You just tell the system about your actions, and when they happen. The framework will handle including your actions in the UI that allows administrators to configure what points and badges are awarded from there, and handle giving them out (although you don't nee to worry about this, details of how this happens is below).
- A action always has a subject member. For example, the user making the post, logging in, or receiving a reaction. When you call the user-land code defined above, you call it on the subject member's \IPS\Member object.
- An action can also optionally have any other members (with the awardOptions() method in the extension). For example, the user giving a reaction, the user who originally posted a question which is getting an answer marked best, or the club leader(s) of a club being joined. This allows you to create single actions which can award points to multiple members.
-
An action can have filters allowing the administrator to create different rules for different scenarios. For example, an administrator might want to award certain amounts of points for posting in one forum and other amounts for other forums. Again, all you need to do is tell the system about your filters (with the filters() and formatFilterValues() methods in the extension) and provide a callback in your extension for checking if a given scenario matches (with the filtersMatch() method in the extension). You do not need to do anything in your user-land code.
- In addition to filters for things like which node a thing is happening in, a common filter type will be a "milestone" filter - i.e. "if this is the member's first/100th/whatever time they've done this thing". This will be especially useful for allowing administrators to create badge-earning rules on specific occasions rather than points-based rules every time a thing happens. Despite it probably being a feature of most actions, it is ultimately just the same as any other filter.
-
The framework will automatically log the event and the points/badges that were issued because of it, including checking that log to make sure the user doesn't earn rewards more than once. For example, if a member likes a post, unlikes it, and re-likes it, the framework will see from the log they have already earned rewards for that and not give them again. You do not need to do anything in user-land code to facilitate this, except provide a callback that return a unique string identifier (with the identifier() method in the extension). For example, if your action is for when a post is created, you would return the post ID. If your action is for when a reaction is given you would need to return a string that encapsulates both the content ID and the giver's member ID.
- Sometimes you can use this to get a bit creative. For example, the SessionStartDaily action is called every time a member logs in, and the identifier used is a string representation of the member ID + current date. This means the action can give points every unique day that the member logs in, and we don't need to implement any logic to facilitate that: we just set the identifier to be unique for the timeframe that it should occur within, and the system figures it out.
- If more than one rule matches for a particular action, they all execute. For example, if you have a rule that gives 1 point for posting in any forum, and another which gives 5 points for posting in a special forum, the user will earn 6 points for posting in that second forum.
How to use
1. Create the extension and implement the below methods.
2. In userland code, call Member::achievementAction() to trigger the action.
$member->achievementAction( 'your_app_key', 'YourAchievementActionExtensionKey', $anyExtraDataThatYouNeed );
/**
* Get filter form elements
*
* @param array|NULL $filters Current filter values (if editing)
* @param \IPS\Http\Url $url The URL the form is being shown on
* @return array
*/
public function filters( ?array $filters, \IPS\Http\Url $url ): array
{
return [];
}
The filters method defines the custom form fields when the specific action is being used.
/**
* Format filter form values
*
* @param array $values The values from the form
* @return array
*/
public function formatFilterValues( array $values ): array
{
return [];
}
The formatFieldValues method is called before the form is saved, allowing you to prepare the form values.
/**
* Work out if the filters applies for a given action
*
* Important note for milestones: consider the context. This method is called by \IPS\Member::achievementAction(). If your code
* calls that BEFORE making its change in the database (or there is read/write separation), you will need to add
* 1 to the value being considered for milestones
*
* @param \IPS\Member $subject The subject member
* @param array $filters The value returned by formatFilterValues()
* @param mixed $extra Any additional information about what is happening (e.g. if a post is being made: the post object)
* @return bool
*/
public function filtersMatch( \IPS\Member $subject, array $filters, $extra = NULL ): bool
{
return TRUE;
}
/**
* Return a description for this action to show in the log
*
* @param string $identifier The identifier as returned by identifier()
* @param array $actor If the member was the "subject", "other", or both
* @return string
*/
public function logRow( string $identifier, array $actor ): string
{
return "{class}";
}
/**
* Get "description" for rule
*
* @param \IPS\core\Achievements\Rule $rule The rule
* @return string|NULL
*/
public function ruleDescription( \IPS\core\Achievements\Rule $rule ): ?string
{
return "{class}";
}
/**
* Process the rebuild row
*
* @param array $row Row from database
* @param array $data Data collected when starting rebuild [table, pkey...]
* @return void
*/
public static function rebuildRow( $row, $data )
{
// This method runs the achievementAction on the table row from the table (or tables) you specified in rebuildData()
// You are welcome to load any classes, etc you need here.
//IPS\Member::loggedIn()->achievementAction( '{app}', '{class}', $row['field_id'] ), \IPS\DateTime::ts( $row['field_date'] ) );
}
/**
* Get rebuild data
*
* @param array $data Data stored with the queue item
* @param array $filters The value returned by formatFilterValues()
* @param \IPS\DataTime|null $time Any time limit to add
* @return void
*/
public function preRebuildData( &$data, $filters, \IPS\DateTime $time = NULL )
{
/* Any data for the rebuild task, must set $data['count'] for the progress bar */
}
The preRebuildData() method allows the extension to calculate and store any data necessary prior to initiating the rebuild task and it also needs to include the number of items in the DB which are going to be processed for the progress bar.
Report Document