Developer Docs
  • Getting started
  • Tutorials
    • Integrate your theme with HivePress
    • Create a custom HivePress extension
  • Framework
    • Blocks
      • Callback
      • Container
      • Content
      • Form
      • Menu
      • Modal
      • Part
      • Section
      • Template
      • Toggle
    • Components
      • Cache
      • Helper
      • Request
      • Router
      • Translator
    • Configurations
      • Comment types
      • Image sizes
      • Meta boxes
      • Post types
      • Scripts
      • Settings
      • Strings
      • Styles
      • Taxonomies
    • Controllers
    • Emails
    • Fields
      • Checkbox
      • Checkboxes
      • Date
      • Date Range
      • Email
      • File
      • Number
      • Number Range
      • Password
      • Phone
      • Radio Buttons
      • Repeater
      • Select
      • Text
      • Textarea
      • Time
      • URL
    • Forms
    • Menus
    • Models
      • Making queries
      • Creating models
      • Customizing models
    • Templates
  • Resources
    • Code snippets
    • Code reference
    • Hook reference
    • REST API
Powered by GitBook
On this page
  • Create the main file
  • Create a component
  • Create a model
  • Create a template
  • Create a controller
  • Create a block
  • Create a form
  • Create an email
  • Create a POT file
  • Keep developing

Was this helpful?

  1. Tutorials

Create a custom HivePress extension

PreviousIntegrate your theme with HivePressNextBlocks

Last updated 2 years ago

Was this helpful?

In this tutorial, we'll create a custom HivePress extension that allows users to:

  • Follow or unfollow any vendor

  • View listings from the followed vendors on a single page

  • Get emails about new listings from the followed vendors

  • Unfollow all vendors with one click

While this extension is pretty simple, it covers the main aspects of the HivePress framework, so by the end of this tutorial, you should be able to create your own extension for HivePress.

Before we begin, please make sure that you have a working WordPress installation and at least basic WordPress development skills. The result of this tutorial is available on , so you can browse the complete source code or download the extension to test it locally.

Create the main file

First, create a directory in the wp-content/plugins WordPress subdirectory. The directory name will be used as the extension identifier, so make sure it's unique enough to avoid conflicts with other HivePress extensions (use lowercase letters, numbers, and hyphens only).

For this tutorial, we'll name it "foo-followers", where "foo" is a unique prefix (e.g. your company name or abbreviation), and the "followers" part describes the extension purpose.

Next, create a PHP file with the same name inside the extension directory. This is the main file that is loaded by WordPress automatically when the extension is active.

<?php
/**
 * Plugin Name: Followers for HivePress
 * Description: Allow users to follow vendors.
 * Version: 1.0.0
 * Author: Foo
 * Author URI: https://example.com/
 * Text Domain: foo-followers
 * Domain Path: /languages/
 */

// Exit if accessed directly.
defined( 'ABSPATH' ) || exit;

// Register extension directory.
add_filter(
	'hivepress/v1/extensions',
	function( $extensions ) {
		$extensions[] = __DIR__;

		return $extensions;
	}
);

As you can see, this file contains the extension details, such as:

  • Plugin Name & Description - the extension name and short description of its purpose;

  • Author & Author URI - your or your company name with an optional website URL;

  • Text Domain - used for translating the extension, matches the main file name.

If you plan to distribute your extension, please name it "Something for HivePress" rather than "HivePress Something" to avoid trademark violation.

Also, there’s a simple code that prevents direct file access by URL so that it can be loaded by WordPress only. We’ll add the same code to every file created in this tutorial:

defined( 'ABSPATH' ) || exit;

The only thing the main file does is register the extension directory via the hivepress/v1/extensions hook to allow HivePress to detect and load all the other extension files automatically:

add_filter(
	'hivepress/v1/extensions',
	function( $extensions ) {
		$extensions[] = __DIR__;

		return $extensions;
	}
);

After you create the main file, go to WordPress > Plugins and activate the extension:

Create a component

Create a class-followers.php file in the includes/components extension subdirectory. Notice that it has the class- prefix, and its name matches the extension name. It must be unique enough to avoid conflicts with other HivePress components (use lowercase letters, numbers, and hyphens only).

The PHP class name must be based on the file name, but with underscores instead of hyphens and no lowercase restriction (e.g. Foo_Bar class for the class-foo-bar.php file).

<?php
namespace HivePress\Components;

use HivePress\Helpers as hp;
use HivePress\Models;
use HivePress\Emails;

// Exit if accessed directly.
defined( 'ABSPATH' ) || exit;

/**
 * Component class.
 */
final class Followers extends Component {

	/**
	 * Class constructor.
	 *
	 * @param array $args Component arguments.
	 */
	public function __construct( $args = [] ) {
		// Attach functions to hooks here (e.g. add_action, add_filter).

		parent::__construct( $args );
	}

	// Implement the attached functions here.
}

If your extension is simple enough, you can fully implement it within a component and skip other steps, but we recommend using the HivePress framework where possible.

Create a model

Let’s create a Follow model for storing the follower ID along with the followed vendor ID. Create a class-follow.php file in the includes/models extension subdirectory. The file and PHP class naming conventions are the same as for components.

<?php
namespace HivePress\Models;

use HivePress\Helpers as hp;

// Exit if accessed directly.
defined( 'ABSPATH' ) || exit;

/**
 * Model class.
 */
class Follow extends Comment {

	/**
	 * Class constructor.
	 *
	 * @param array $args Model arguments.
	 */
	public function __construct( $args = [] ) {
		$args = hp\merge_arrays(
			[
				'fields' => [
					'user'   => [
						'type'     => 'id',
						'required' => true,
						'_alias'   => 'user_id',
						'_model'   => 'user',
					],

					'vendor' => [
						'type'     => 'id',
						'required' => true,
						'_alias'   => 'comment_post_ID',
						'_model'   => 'vendor',
					],
				],
			],
			$args
		);

		parent::__construct( $args );
	}
}

The Follow model contains 2 fields:

  • user - mapped to the user_id database field, used for storing the follower ID;

  • vendor - mapped to the comment_post_ID database field, used for storing the followed vendor ID.

Also, if the model is based on the Comment class, WordPress will show the model objects in the comment feeds. To keep the model objects hidden, create a comment-types.php file in the includes/configs extension subdirectory:

<?php
// Exit if accessed directly.
defined( 'ABSPATH' ) || exit;

return [
	'follow' => [
		'public' => false,
	],
];

The code above contains a configuration that makes the follow comment type registered by the model we created private.

Create a template

Let’s create a template for a page that displays all listings from the followed vendors. Create a class-listings-feed-page.php file in the includes/templates extension subdirectory. The file and PHP class naming conventions are the same as for components.

It’s good practice to follow the {entity}-{context}-{layout} pattern for naming templates (e.g. Listing_Edit_Page, Vendor_View_Block).

<?php
namespace HivePress\Templates;

use HivePress\Helpers as hp;

// Exit if accessed directly.
defined( 'ABSPATH' ) || exit;

/**
 * Template class.
 */
class Listings_Feed_Page extends User_Account_Page {

	/**
	 * Class constructor.
	 *
	 * @param array $args Template arguments.
	 */
	public function __construct( $args = [] ) {
		$args = hp\merge_trees(
			[
				'blocks' => [
					'page_content' => [
						'blocks' => [
							'listings'               => [
								'type'    => 'listings',
								'columns' => 2,
								'_order'  => 10,
							],

							'listing_pagination'     => [
								'type'   => 'part',
								'path'   => 'page/pagination',
								'_order' => 20,
							],
						],
					],
				],
			],
			$args
		);

		parent::__construct( $args );
	}
}

As you can see, the template class is based on the User_Account_Page class. This means that the template inherits the user account page layout and adds custom blocks to it.

The template we created adds 2 blocks to the page_content area:

  • listings - displays listings for the current page;

  • listing_pagination - displays the page links for navigation.

The block names must be unique within the template scope. Each block is defined as an array containing the block type and extra parameters used to render the block.

Create a controller

Create a class-followers.php file in the includes/controllers extension subdirectory. The file and PHP class naming conventions are the same as for components.

<?php
namespace HivePress\Controllers;

use HivePress\Helpers as hp;
use HivePress\Models;
use HivePress\Blocks;

// Exit if accessed directly.
defined( 'ABSPATH' ) || exit;

/**
 * Controller class.
 */
final class Followers extends Controller {

	/**
	 * Class constructor.
	 *
	 * @param array $args Controller arguments.
	 */
	public function __construct( $args = [] ) {
		$args = hp\merge_arrays(
			[
				'routes' => [
					// Define custom URL routes here.
				],
			],
			$args
		);

		parent::__construct( $args );
	}
	
	// Implement the route actions here.
}

Add the template route

Let's define a new URL route in the controller constructor:

'listings_feed_page' => [
	'title'     => esc_html__( 'Feed', 'foo-followers' ),
	'base'      => 'user_account_page',
	'path'      => '/feed',
	'redirect'  => [ $this, 'redirect_feed_page' ],
	'action'    => [ $this, 'render_feed_page' ],
	'paginated' => true,
],

If you add new or change any of the existing URL routes, don't forget to refresh permalinks in the Settings > Permalinks section.

The route we defined uses these parameters:

  • title - used for the page title and menu label;

  • base - used for inheriting another route path;

  • path - the relative route URL path;

  • redirect - points to the URL redirect function;

  • action - points to the route action function;

  • paginated - flag required for paginated URLs.

Based on the parameter values, the listings_feed_page route has the /account/feed URL, renders a page with the “Feed” title and supports pagination.

Next, let's implement the redirect and action functions for this route below the constructor:

/**
 * Redirects listing feed page.
 *
 * @return mixed
 */
public function redirect_feed_page() {

	// Check authentication.
	if ( ! is_user_logged_in() ) {
		return hivepress()->router->get_return_url( 'user_login_page' );
	}

	// Check followed vendors.
	if ( ! hivepress()->request->get_context( 'vendor_follow_ids' ) ) {
		return hivepress()->router->get_url( 'user_account_page' );
	}

	return false;
}

/**
 * Renders listing feed page.
 *
 * @return string
 */
public function render_feed_page() {

	// Create listing query.
	$query = Models\Listing::query()->filter(
		[
			'status'     => 'publish',
			'vendor__in' => hivepress()->request->get_context( 'vendor_follow_ids' ),
		]
	)->order( [ 'created_date' => 'desc' ] )
	->limit( get_option( 'hp_listings_per_page' ) )
	->paginate( hivepress()->request->get_page_number() );

	// Set request context.
	hivepress()->request->set_context(
		'post_query',
		$query->get_args()
	);

	// Render page template.
	return ( new Blocks\Template(
		[
			'template' => 'listings_feed_page',

			'context'  => [
				'listings' => [],
			],
		]
	) )->render();
}

When the route URL is visited, the redirect function is called first. In our case, it checks if the current user is logged in and has any followed vendor IDs. It returns the corresponding redirect URL or false if all checks are passed.

If there was no redirect, the action function is called next. As you can see, it creates a query for listings published by the followed vendors, sets it as the main page query, and finally renders the template we created earlier. Notice that this function returns the rendered HTML instead of outputting it with echo.

Now, if you refresh permalinks in Settings > Permalinks and try to visit the /account/feed URL, you will be redirected because you haven’t followed any vendors yet.

Update the component

We already used a code that checks if the current user follows any vendors by checking the vendor_follow_ids value in the request context, but there’s no function that sets this value in context yet. Add this code to the component constructor:

add_filter( 'hivepress/v1/components/request/context', [ $this, 'set_request_context' ] );

The code above hooks a custom filtering function to the request context values. Let’s implement this function in the component below the constructor:

/**
 * Sets request context for pages.
 *
 * @param array $context Context values.
 * @return array
 */
public function set_request_context( $context ) {

	// Get user ID.
	$user_id = get_current_user_id();

	// Get cached vendor IDs.
	$vendor_ids = hivepress()->cache->get_user_cache( $user_id, 'vendor_follow_ids', 'models/follow' );

	if ( is_null( $vendor_ids ) ) {

		// Get follows.
		$follows = Models\Follow::query()->filter(
			[
				'user' => $user_id,
			]
		)->get();

		// Get vendor IDs.
		$vendor_ids = [];

		foreach ( $follows as $follow ) {
			$vendor_ids[] = $follow->get_vendor__id();
		}

		// Cache vendor IDs.
		hivepress()->cache->set_user_cache( $user_id, 'vendor_follow_ids', 'models/follow', $vendor_ids );
	}

	// Set request context.
	$context['vendor_follow_ids'] = $vendor_ids;

	return $context;
}

This function checks if the followed vendor IDs are cached for the current user, and if not, it queries the Follow model objects by user ID and fills an array of vendor IDs, then caches this array. Finally, it sets an array of vendor IDs in the vendor_follow_ids context, this allows us to get it anywhere in the code this way:

$vendor_ids = hivepress()->request->get_context( 'vendor_follow_ids' );

Also, the listing feed page we created doesn’t have any links on the front-end yet, so let’s add it to the user account menu. Add this code to the component constructor:

add_filter( 'hivepress/v1/menus/user_account', [ $this, 'add_menu_item' ] );

The code above hooks a custom filtering function to the user account menu parameters. Next, implement this function in the component below the constructor:

/**
 * Adds menu item to user account.
 *
 * @param array $menu Menu arguments.
 * @return array
 */
public function add_menu_item( $menu ) {
	if ( hivepress()->request->get_context( 'vendor_follow_ids' ) ) {
		$menu['items']['listings_feed'] = [
			'route'  => 'listings_feed_page',
			'_order' => 20,
		];
	}

	return $menu;
}

As you can see, it adds a custom menu item linked to the listings_feed_page route we created previously. You can adjust the _order parameter value to change the menu item position. The menu item will appear only if the current user follows any vendors.

Add REST API routes

  • method - restricts the accepted HTTP method (e.g. GET, POST);

  • rest - flag required for REST API routes.

It’s good practice to follow the {entity}-{context}-{type} pattern for naming routes (e.g. listing_view_page, vendor_update_action).

'vendor_follow_action'    => [
	'base'   => 'vendor_resource',
	'path'   => '/follow',
	'method' => 'POST',
	'action' => [ $this, 'follow_vendor' ],
	'rest'   => true,
],

'vendors_unfollow_action' => [
	'base'   => 'vendors_resource',
	'path'   => '/unfollow',
	'method' => 'POST',
	'action' => [ $this, 'unfollow_vendors' ],
	'rest'   => true,
],

The code above defines 2 REST API routes, both accept requests via the POST method. The first route will follow or unfollow a vendor on every subsequent request, while the second one will unfollow all vendors at once. Next, implement the action functions for these routes:

/**
 * Follows or unfollows vendor.
 *
 * @param WP_REST_Request $request API request.
 * @return WP_Rest_Response
 */
public function follow_vendor( $request ) {

	// Check authentication.
	if ( ! is_user_logged_in() ) {
		return hp\rest_error( 401 );
	}

	// Get vendor.
	$vendor = Models\Vendor::query()->get_by_id( $request->get_param( 'vendor_id' ) );

	if ( ! $vendor || $vendor->get_status() !== 'publish' ) {
		return hp\rest_error( 404 );
	}

	// Get follows.
	$follows = Models\Follow::query()->filter(
		[
			'user'   => get_current_user_id(),
			'vendor' => $vendor->get_id(),
		]
	)->get();

	if ( $follows->count() ) {

		// Delete follows.
		$follows->delete();
	} else {

		// Add new follow.
		$follow = ( new Models\Follow() )->fill(
			[
				'user'   => get_current_user_id(),
				'vendor' => $vendor->get_id(),
			]
		);

		if ( ! $follow->save() ) {
			return hp\rest_error( 400, $follow->_get_errors() );
		}
	}

	return hp\rest_response(
		200,
		[
			'data' => [],
		]
	);
}

/**
 * Unfollows all vendors.
 *
 * @param WP_REST_Request $request API request.
 * @return WP_Rest_Response
 */
public function unfollow_vendors( $request ) {

	// Check authentication.
	if ( ! is_user_logged_in() ) {
		return hp\rest_error( 401 );
	}

	// Delete follows.
	$follows = Models\Follow::query()->filter(
		[
			'user' => get_current_user_id(),
		]
	)->delete();

	return hp\rest_response(
		200,
		[
			'data' => [],
		]
	);
}

The follow_vendor function checks if the current user is logged in, gets a Vendor object by ID, and queries the Follow objects by the user and vendor IDs.

Then, if any Follow objects are found, they are deleted. If not, a new Follow object is created and saved in the database. This way, every subsequent call of this function will follow or unfollow a vendor, creating or deleting a Follow object.

The unfollow_vendors function checks if the current user is logged in, then queries the Follow objects by the user ID and deletes them, thus unfollowing all vendors.

Create a block

We created REST API routes, but there are no links or forms on the front-end that send requests to these routes yet. Let’s add a toggle link that sends a request to the vendor_follow_action on click and add it somewhere on the vendor page. We need to create a new block type for this.

Create a class-follow-toggle.php file in the includes/blocks extension subdirectory. The file and PHP class naming conventions are the same as for components.

<?php
namespace HivePress\Blocks;

use HivePress\Helpers as hp;

// Exit if accessed directly.
defined( 'ABSPATH' ) || exit;

/**
 * Block class.
 */
class Follow_Toggle extends Toggle {

	/**
	 * Class constructor.
	 *
	 * @param array $args Block arguments.
	 */
	public function __construct( $args = [] ) {
		$args = hp\merge_arrays(
			[
				'states' => [
					[
						'icon'    => 'user-plus',
						'caption' => esc_html__( 'Follow', 'foo-followers' ),
					],
					[
						'icon'    => 'user-minus',
						'caption' => esc_html__( 'Unfollow', 'foo-followers' ),
					],
				],
			],
			$args
		);

		parent::__construct( $args );
	}

	/**
	 * Bootstraps block properties.
	 */
	protected function boot() {

		// Get vendor from the block context.
		$vendor = $this->get_context( 'vendor' );

		if ( $vendor ) {

			// Set URL for sending requests on click.
			$this->url = hivepress()->router->get_url(
				'vendor_follow_action',
				[
					'vendor_id' => $vendor->get_id(),
				]
			);

			// Set active state if vendor is followed.
			if ( in_array(
				$vendor->get_id(),
				hivepress()->request->get_context( 'vendor_follow_ids', [] )
			) ) {
				$this->active = true;
			}
		}

		parent::boot();
	}
}

Notice that the block class is based on the Toggle class – this is an existing block type available in HivePress, so all the properties and methods are inherited from it.

Before the block is rendered, it fetches the Vendor object from the current template context and sets the toggle url to the vendor_follow_action route we created previously. It also enables the active flag if the vendor ID is among the followed vendor IDs.

This way, the toggle will show the “Follow” or “Unfollow” label on every subsequent click and send an AJAX request to the vendor_follow_action route URL. Also, it will show the “Unfollow” label by default if the vendor is already followed.

It’s good practice to re-use the existing HivePress block types and avoid creating new ones if possible. In this case, we had to create a new block type because it implements a custom logic (the toggle state depends on the followed vendor IDs).

Update the component

Next, let’s add this block to the vendor templates. Add this code to the component constructor:

add_filter( 'hivepress/v1/templates/vendor_view_block', [ $this, 'add_toggle_block' ] );
add_filter( 'hivepress/v1/templates/vendor_view_page', [ $this, 'add_toggle_block' ] );

The code above hooks a custom filtering function to the vendor template parameters. Now, implement this function in the component below the constructor:

/**
 * Adds toggle block to vendor templates.
 *
 * @param array $template Template arguments.
 * @return array
 */
public function add_toggle_block( $template ) {
	return hp\merge_trees(
		$template,
		[
			'blocks' => [
				'vendor_actions_primary' => [
					'blocks' => [
						'vendor_follow_toggle' => [
							'type'       => 'follow_toggle',
							'_order'     => 50,

							'attributes' => [
								'class' => [ 'hp-vendor__action', 'hp-vendor__action--follow' ],
							],
						],
					],
				],
			],
		]
	);
}

The function above filters the template parameters and adds a new block using the block type we created earlier. Let’s check if the toggle link is added on the front-end:

Now you can try clicking on the Follow toggle and check if the vendor listings appear on the listing feed page, and unfollow a vendor to check if listings disappear.

Create a form

We still have one action left that is not used anywhere, the one that unfollows all vendors at once. So let’s create a form for it and add a button to show the form on click.

Create a class-vendors-unfollow.php file in the includes/forms extension subdirectory. The file and PHP class naming conventions are the same as for components.

<?php
namespace HivePress\Forms;

use HivePress\Helpers as hp;

// Exit if accessed directly.
defined( 'ABSPATH' ) || exit;

/**
 * Form class.
 */
class Vendors_Unfollow extends Form {

	/**
	 * Class constructor.
	 *
	 * @param array $args Form arguments.
	 */
	public function __construct( $args = [] ) {
		$args = hp\merge_arrays(
			[
				'description' => esc_html__( 'Are you sure you want to unfollow all vendors?', 'foo-followers' ),
				'action'      => hivepress()->router->get_url( 'vendors_unfollow_action' ),
				'method'      => 'POST',
				'redirect'    => true,

				'button'      => [
					'label' => esc_html__( 'Unfollow', 'foo-followers' ),
				],
			],
			$args
		);

		parent::__construct( $args );
	}
}

The form we’ve created defines these parameters:

  • description - text displayed before the form;

  • action - URL for sending requests on submission;

  • method - HTTP method for sending requests (e.g. POST, GET);

  • redirect - flag to refresh or redirect the page;

  • button - the submit button parameters.

This form doesn’t contain fields, but you can define an array of fields in the fields parameter and the form will render them, sending the entered values with the request.

Update the template

Next, let’s add new blocks to the Listings_Feed_Page template we created earlier:

'vendors_unfollow_link' => [
	'type'   => 'part',
	'path'   => 'vendor/follow/vendors-unfollow-link',
	'_order' => 30,
],

'vendors_unfollow_modal' => [
	'title'  => esc_html__( 'Unfollow Vendors', 'foo-followers' ),
	'type'   => 'modal',

	'blocks' => [
		'vendors_unfollow_form' => [
			'type' => 'form',
			'form' => 'vendors_unfollow',
		],
	],
],

As you can see, there’s a part block that loads a specific HTML file and a modal block that contains the form we’ve just created. The part block points to a non-existing file, so we need to create a vendors-unfollow-link.php file in the templates/vendor/follow extension subdirectory:

<?php
// Exit if accessed directly.
defined( 'ABSPATH' ) || exit;
?>
<a href="#vendors_unfollow_modal" class="button"><?php esc_html_e( 'Unfollow Vendors', 'foo-followers' ); ?></a>

It’s good practice to follow the {entity}/{context}/{layout} directory structure for template parts, this way you can easily find the template where the part is used.

Now, let’s check the listing feed page. It should have the “Unfollow” button that opens a modal window on click. The modal window contains a form that sends a request to the vendors_unfollow_action route URL, thus unfollowing all vendors at once.

Create an email

Finally, let’s add an email notification sent to users about new listings from the followed vendors.

Create a class-listing-feed.php file in the includes/emails extension subdirectory. The file and PHP class naming conventions are the same as for components.

<?php
namespace HivePress\Emails;

use HivePress\Helpers as hp;

// Exit if accessed directly.
defined( 'ABSPATH' ) || exit;

/**
 * Email class.
 */
class Listing_Feed extends Email {

	/**
	 * Class constructor.
	 *
	 * @param array $args Email arguments.
	 */
	public function __construct( $args = [] ) {
		$args = hp\merge_arrays(
			[
				'subject' => esc_html__( 'New Listing', 'foo-followers' ),
				'body'    => esc_html__( 'Hi, %user_name%! There is a new listing "%listing_title%" in your feed, click on the following link to view it: %listing_url%', 'foo-followers' ),
			],
			$args
		);

		parent::__construct( $args );
	}
}

The email we’ve just created defines these parameters:

  • subject - the email subject;

  • body - the email message with placeholders.

Update the component

Next, let’s add a function that sends an email to the vendor followers if there’s a newly published listing. Add this code to the component constructor:

add_action( 'hivepress/v1/models/listing/update_status', [ $this, 'send_feed_emails' ], 10, 4 );

The code above hooks a custom function to the listing status change action. Now, implement this function in the component below the constructor:

/**
 * Sends emails about a new listing.
 *
 * @param int    $listing_id Listing ID.
 * @param string $new_status New status.
 * @param string $old_status Old status.
 * @param object $listing Listing object.
 */
public function send_feed_emails( $listing_id, $new_status, $old_status, $listing ) {

	// Check listing status.
	if ( 'publish' !== $new_status || ! in_array( $old_status, [ 'auto-draft', 'pending' ] ) ) {
		return;
	}

	// Get follows.
	$follows = Models\Follow::query()->filter(
		[
			'vendor' => $listing->get_vendor__id(),
		]
	)->get();

	foreach ( $follows as $follow ) {

		// Get user.
		$user = $follow->get_user();

		// Send email.
		( new Emails\Listing_Feed(
			[
				'recipient' => $user->get_email(),

				'tokens'    => [
					'user_name'     => $user->get_display_name(),
					'listing_title' => $listing->get_title(),
					'listing_url'   => hivepress()->router->get_url( 'listing_view_page', [ 'listing_id' => $listing->get_id() ] ),
				],
			]
		) )->send();
	}
}

The function above checks if the listing got the "published" status, gets all the Follow objects by vendor ID and sends an email to each follower, providing the email address and tokens to be replaced in the email text.

Now, if you follow a vendor and this vendor publishes a new listing, you will get an email notification that contains the listing title and URL.

Create a POT file

That's it. Now website owners can translate or change any of the extension texts via Loco Translate or POEdit without editing the source code directly.

Keep developing

Congratulations! You’ve just developed a fully-functional HivePress extension. Even though there's a lot more to the HivePress framework than what you’ve seen so far, you’re now ready to start developing your own HivePress extensions.

If you plan to submit your extension to the repository, make sure to follow and add the readme.txt along with the license file.

In HivePress, are PHP classes used to group actions, filters, and helper functions. Let’s create an empty component and add the extension-specific functions to it later.

In HivePress, are PHP classes that represent the WordPress database entities such as posts, comments, or terms. Using models makes working with the database much easier.

Notice that the model class is based on the Comment class. This means that the model objects will be stored as WordPress comments of a custom type. We implement the model this way because comments are linked to both users and posts in the WordPress . Since vendors are implemented as posts of a custom type, we can use this model to store both the follower (user) and the followed vendor IDs.

In HivePress, are defined as PHP classes that contain arrays of . With blocks, it’s easy to re-use and customize specific layout parts without affecting the whole template.

Now, we need to define custom URLs that will render the template we created and perform specific actions, such as following or unfollowing a vendor. In HivePress, this can be done using – PHP classes that define URL routes and implement actions corresponding to them.

Let’s also create URL routes that allow users to follow or unfollow vendors. Since these routes don’t render anything and are used for performing actions only, we will define them as routes. These routes don’t require the title, redirect, and paginated parameters, but other parameters are needed instead:

Now we have all the URL routes and actions according to the extension requirements. You can view the complete controller on GitHub for reference.

In HivePress, are defined as PHP classes with properties and methods that determine the behavior and rendering of the block.

The Follow_Toggle block type we created defines 2 states for the toggle, setting the icon name and a label for each state.

You probably noticed that we wrapped all the texts in the code with the . We also need to generate a POT file for translation to work properly.

Create a new languages extension subdirectory and install the plugin. Go to Loco Translate > Plugins > Followers for HivePress and click Create template, then proceed.

For example, you can develop a custom HivePress extension for a client, share it on the repository or even sell it on the marketplace.

If you have any questions about the HivePress framework, please check the available docs and feel free to join the HivePress .

GitHub
WordPress.org
its guidelines
components
models
database schema
templates
blocks
controllers
REST API
source code
block types
Font Awesome
translation functions
Loco Translate
WordPress.org
CodeCanyon
developer community