/*
	This is a sample hook class with every vbulletin code hook defined.
*/
class sample_Hooks
{
	/*
		This determines the order the class is called.  If it is not defined then
		default of 10 will be used.  The class with the lowest value will be
		invoked first if multiple classes define an implementation for the
		same hook.
	*/
	public static $order = 10;


	/*
		This hook is called when rendering the "External Connections" field
		in the AdminCP's user edit/profile page.
	*/
	public static function hookAdminCPUserExternalConnections($params)
	{
		// ID of the user being edited/displayed
		$params['userid'];

		// Nested array of external connections to display.
		$params['externalConnections'];
		// By default the following facebook array is populated:
		/*
		$params['externalConnections'][0] = array(
			'titlephrase' => 'facebook_connected',
			'connected' => !empty($user['fbuserid']),
			'helpname' => 'facebookconnect',
			'displayorder' => 10,
		);
		*/
		// Each subarray should look like above, where lower value of displayorder will
		// show on the list first.

		$params['externalConnections'][] = array(
			'titlephrase' => '',  // String phrase varname to display
			'connected' => false, // Bool Controls what's displayed. True = "Yes", false = "No"
			'helpname' => '',     // String varname for adminCP help link.
			'displayorder' => 20, // Integer. Ex. Lower than 10 will show before facebook, higher will show after.
		);
	}


	/*
		This hook is called while generating the list of search options for display or validation,
		e.g. in admin CP > Settings > Search Type page
	 */
	public static function hookSearchOptions($params)
	{
		//array of key {searchimplementation} => value {label phraeid} pairs
		//searchimplementation must be the class name of a vB_Search_Core implementation
		//Begins with the default pairs of
		// [
		// 'vBDBSearch_Core' => 'db_search_implementation',
		// 'vBSphinxSearch_Core' => 'sphinx_search_implementation',
		// ]
		//this parameter is editable
		$params'['options'];

		//e.g.
		$params'['options']['custom_search_core'] => 'my_custom_phrase';
		$params'['options']['custom_search_core'] => 'some unphrased/unrendered label';
	}

	public static function hookAdminSettingsSelectOptions($params)
	{
		//the id of the setting being displayed.
		//this parameter is read only
		$params['settingid'];

		//the exact type of the select list.  Currently the values supported are:
		//* checkbox:json_array
		//* select:piped
		//* select:eval
		//* multiselect:eval
		//there are some specialized select options for user groups and other
		//system features that are not yet included
		//this parameter is read only
		$params['optioncode'];

		//the options that will be displayed in the settings screen.  They are
		//of the form "value" => "display text".
		//this parameter is editable
		$params['options'];

		//standard use case is
		if($params['settingid'] == 'someselectsetting')
		{
			$params['options']['mynewoption'] = 'My New Option Text';
		}
	}

	public static function hookAdminClearedCache($params)
	{
		/*
			This hooks is called when the admin explicitly clears the cache
			in the admincp
		*/

		//this hook does not have any parameters
	}

	/*
		This hook is called immediately after the output is generated but before
		it is displayed.  This does not include the preheader portion (which may
		already have been sent to the browser at this point depending on
		configuration).
	*/
	public static function hookFrontendBeforeOutput($params)
	{
		//the style used to render the page
		//this parameter is read only
		$params['styleid'];

		//html from the end of the preheader to the end of the page
		//this include the the entire <body> tag
		//this parameter is editable
		$params['pageHtml'];
	}

	/*
		This hook is called immediately after the output is generated for an ajax
		template render (ajax/render/*) but before it is formatted for JSON to be
		returned to the browser.
		The ajax/render equivalent to hookFrontendBeforeOutput.
	*/
	public static function hookFrontendAfterAjaxRender($params)
	{
		//the style used to render the page
		//this parameter is read only
		$params['styleid'];

		//rendered template html
		//this parameter is editable
		$params['template'];
	}

	/*
		This is called after the preheader is generated but before it is output to
		the browser.
	*/
	public static function hookFrontendPreheader($params)
	{
		//html from start of page to end of preheader
		//this parameter is editable
		$params['preheaderHtml'];
	}

	public static function hookFrontendPrepareRestoreSession($params)
	{
		/*
		WARNING:
		This hook is invoked before a session exists.
		Only use library or utility methods here, not API methods.
		API methods require a session to exist and calling them will likely
		result in vB_Api_State::checkBeforeView() throwing an error.
		Note that some vbulletin default library methods may depend on a
		session as well, in which case your product will need to implement
		its own library methods that are session independent.
		 */

		//editable parameter.  Will be passed as an array (though if there are multiple
		//implemented, a prior one could change that).  By default, it can contain the
		//'userid' & 'remembermetoken' fields, which come from the 'userid' & 'password'
		//cookies respectively, if available.  Custom products should map cookie values
		//into this array for latter consumption via hookWebApiAfterCreateSessionNew
		$params['restoreSessionInfo']
		//e.g.
		$params['restoreSessionInfo']['myproduct'] = [
			'importantcookie' => $params['cookie']['importantcookie'],
			'anothercookie' => $params['cookie']['something'],
		];
		//data doesn't have to be from the cookies:
		session_name('mysite');
		if (session_start())
		{
			//some session data set by your product or external app
			$params['restoreSessionInfo']['myproduct'] = [
				'somesessionvar' => $_SESSION['hello'],
			];
		}

		//editable parameter.  Will be passed as string (though if there are multiple
		//implemented, a prior one could change that).  By default fetched from the
		//'sessionhash' cookie, may not be present if new visit or user cleared browser
		//cache. Set this to empty if you need to disable the default vbulletin session
		//fetch logic outright.
		$params['sessionhash']

		//The $_COOKIE array, passed in as parameter to detach globals-dependencies. Map important values
		//from this array into 'restoreSessionInfo' for consumption via session generation logic in
		//hookWebApiAfterCreateSessionNew later.
		//this parameter is read only
		$params['cookie'];

		//The http method if available.  If redirecting is necesary to process the restoration it's only 
		//appropriate if it's a get request.
		//this parameter is read only
		$params['method'];
	}

	public static function hookWebApiAfterCreateSessionNew($params)
	{
		/*
		This is placed here because of its close relationship with hookFrontendPrepareRestoreSession()
		This hook is called immediately after the system tries to restore a vbulletin session from
		the sessionhash provided in the request, but before any "remember me" logic is processed.
		It is safe to use API methods here, as a session is guaranteed to exist, but note that the
		session may be a guest session, especially if the sessionhash is associated with an expired
		session (cookietimeout) as the "remember me" logic has not run yet.
		If you rely on the vbulletin default remember logic, you can call :
			$params['session']->doRememberMe($params['restoreSessionInfo']);
			$params['doRememberMe'] = false;
		before your own code.
		 */

		//editable parameter.  The vB_Session_WebApi instance that was restored from
		//the sessionhash.  May be a guest session.  Update this or set it to a different
		//instance of vB_Session_WebApi, and the current request session will be updated
		//to this at the end of the hook (unless it's modified by another hook).
		$params['session']


		//editable parameter.  Will be passed as an array (though if there are multiple hooks
		//implemented, a prior one could change that).  This likely contains the useful data
		//set in hookFrontendPrepareRestoreSession().  Warning, modifying data outside the ones
		//you set in your own hook make interfere with other hooks.
		$params['restoreSessionInfo']

		//editable parameter.  Will be passed as a boolean true (though if there are
		//multiple hooks implemented, a prior one could change that).  Set this to false
		//if your code already handled the rememberme logic and want to skip vBulletin's
		//default rememberme/facebook logic to avoid interference.  It may be worthwhile
		//to check this value and skip processing if false to avoid interfering with a
		//previous hook's rememberme handling.
		$params['doRememberMe']
		//e.g.
		if ($params['doRememberMe'])
		{
			//check cookies/other data and see if we know this vb user
			if ($vbuserid = $mylib->fetchVBUserid($params['restoreSessionInfo']['myproduct']))
			{
				//log this vb user in.
				$mylib->updateSession($params['session'], $vbuserid);
				//disable default VB remember me processing since above effectively
				//rememebered me.
				$params['doRememberMe'] = false;
			}
		}

		//Int userid for the vbulletin session restored from the sessionhash.
		//Same as $session->get('userid'), present in params just for convenience.
		//this is read only.
		$params['userid']
		//e.g.
		$vbuserid = $mylib->fetchVBUserid($params['restoreSessionInfo']['myproduct']);
		if ($params['userid'] !== $vbuserid)
		{
			$mylib->logInOrSwitchUserAndStuff($vbuser);
		}
	}

	public static function hookFrontendContentBeforeAdd($params)
	{
		//Read only parameter.  True if this is a comment being added, false otherwise.
		//Comments are added via a different action from other content and, in particular,
		//the expected return values are very different.
		$params['iscomment'];

		//editable parameter.  Will be passed as an empty string (though if there are multiple
		//implemented, a prior one could change that).  Set this to something else to
		//skip the add.  The value set will be returned instead.
		//
		//Be careful when returning a value other than an error when the iscomment flag is true.
		//The is return value from adding a comment is different from adding other items.
		$params['altreturn'];

		//to return an error array use
		//$params['altreturn'] = array('errors' => array(array('phraseid_id', $param1, $param2)));

		//The string value for the apilibrary being used to add the content.
		//this is read only.
		$params['apilib'];

		//the data array being passed to the add function
		//this parameter is editable
		$params['data'];

		//the options array being passed to the add function
		//this parameter is editable
		$params['options'];
	}

	public static function hookFrontendContentAfterAdd($params)
	{
		//Read only parameter.  True if this is a comment being added, false otherwise.
		//Comments are added via a different action from other content
		$params['iscomment'];

		//whether or not the add succeeded. Read only.
		$params['success'];

		//The output that will be returned to the browser.  Read only.
		$params['output'];

		//the id of the node just added. Can be used to fetch the node
		//content and perform additional actions based on the node
		//just added.
		$params['nodeid'];
	}


	public static function hookFrontendContentBeforeUpdate($params)
	{
		//editable parameter.  Will be passed as an empty string (though if there are multiple
		//implemented, a prior one could change that).  Set this to something else to
		//skip the update.  The value set will be returned instead.
		$params['altreturn']

		//to return an array use
		//$params['altreturn'] = array('errors' => array(array('phraseid_id', $param1, $param2)));

		//The string value for the apilibrary being used to add the content.
		//this is read only.
		$params['apilib'];

		//the node being edited
		//this is read only.
		$params['nodeid'];

		//the data array being passed to the update function
		//this parameter is editable
		$params['data'];

	}

	public static function hookFrontendContentAfterUpdate($params)
	{
		//The results of the update call. Read only.
		$params['updateResult'];

		//The string value for the apilibrary being used to add the content.
		//this is read only.
		$params['apilib'];

		//the id of the node just updated. Can be used to fetch the node
		//content and perform additional actions based on the node
		//just added.
		$params['nodeid'];
	}


	/*
		This is called when processing a 404 error vBulletin fails to
		recognize a url.  It triggers in the error handler.
	*/
	public static function hookFrontendOn404($params)
	{
		//read only.  The path used by the router that failed to match a vbulletin page
		$params['path'];

		//read/write paramerater.  Set to false to signal that the hook has handled
		//the 404 error and the normal application response isn't desired.
		$params['normalErrorResponse'];
	}

	/*
		This is called after a successful save of a user.
	*/
	public static function hookUserAfterSave($params)
	{
		//all parameters are read only.
		//the user id of the user
		$params['userid'];

		//if the save is being done as an administrative function
		$params['adminoverride'];

		//is this a new user?
		$params['newuser'];

		//does this user require email verification before registration is considered
		//complete (note this may be false even if activation is required if the
		//activation is skipped such as when a user is created in the admincp).
		$params['emailVerificationRequired'];

		//does the user require moderation before registration is considered
		//complete?
		$params['userIsModerated'];
	}


	/*
		This is called before a successful activation action.  This includes when
		user is placed in the moderation queue or when an existing user is
		required to reactivate due to a change in email.
	*/
	public static function hookUserAfterActivation($params)
	{
		//all parameters are read only.

		//the user id of the user
		$params['userid'];

		//is this a new user or a reactivation?
		$params['newuser'];

		//does the user require moderation before registration is considered
		//complete?
		$params['userIsModerated'];
	}

	/*
		This is called at the start of a regular login attempt at the start of vB_Api_User::logininternal()
	*/
	public static function hookLoginInternal($params)
	{
		// Editable int Default vB_LoginState::LOGIN_UNHANDLED
		// if $params['loginState'] is...
		// * vB_LoginState::LOGIN_DEFAULT -- no previous hook has handled this request, and your hook may
		//    attempt to find a relevant user and handle the login. If your hook failed to process this user
		//    leave this alone, unless you must instruct vBulletin to hard-reject the login attempt.
		//    vBulletin will attempt to log this user in via default username & password mechanism.
		// * vB_LoginState::LOGIN_HANDLED  -- a previous hook has successfully logged in this user and your hook
		//    should not process this user. vBulletin will return the result value stored in $params['result'].
		// * vB_LoginState::LOGIN_USERINFO_REPLACED -- A special case of LOGIN_HANDLED, a previous hook has replaced
		//    $params['userinfo'] with its own mapping of $params['username'], but will defer to vBulletin login
		//    handling using that userinfo. Your hook should treat this like LOGIN_HANDLED.
		// * vB_LoginState::LOGIN_REJECTED -- a previous hook has indicated that vBUlletin must reject this login
		//    attempt with its standard strikes system mechanism. Your hook should not process this user unless
		//    there is a very good reason to do so.
		$params['loginstate'];

		// Editable bool|array Default false
		// Set this to a results array along with $params['loginstate'] = vB_LoginState::LOGIN_HANDLED to return
		// this result to the login caller.
		$params['result'];

		// Editable array|null  Valid keys are 'email', 'username', 'userid', 'token', 'scheme'.
		// This may be empty or set to values matched to a vbulletin user with  user.username =
		// vB_String::htmlSpecialCharsUni($params['username']).
		// Most packages will not need to touch this. However, access is provided to allow a hook to only
		// handle "fetching" a relevant vB user from the provided username & password, but otherwise fall
		// back to deafult vbulletin login handling with the updated userInfo.
		// See the example below.
		$params['userInfo'];

		// Readonly string username
		$params['username'];

		// Readonly string password
		$params['password'];

		// Readonly array extra auth info (like MFA) that might be required for authentication
		$params['extraAuthInfo'];

		// Readnonly string|null logintype, e.g. null|''|'cplogin'|'modcplogin'
		$params['logintype']





		// Always check to see if a previous hook has already handled this.
		if ($params['loginstate'] !== vB_LoginState::LOGIN_DEFAULT)
		{
			return;
		}

		// Typical fetch & login flow:
		// Try to authenticate this user with $params['username'] and $params['password']
		$logIntoThisVBUserid = MyPackage::tryLogIn($params['username'], $params['password']);
		if ($logIntoThisVBUserid)
		{
			$session = vB::getRequest()->createSessionForUser($logIntoThisVBUserid);
			$sessionUserInfo = $session->fetch_userinfo();
			$auth = [
				'userid'       => $logIntoThisVBUserid,
				'password'     => 'norememberme',
				'lastvisit'    => $sessionUserInfo['lastvisit'],
				'lastactivity' => $sessionUserInfo['lastactivity']
			];
			$res = vB_User::processNewLogin($auth, $params['logintype']);

			// DO NOT FORGET TO SET THIS TO HANDLED IFF YOU WANT VBULLETIN TO RECOGNIZE THIS.
			$params['loginstate'] = vB_LoginState::LOGIN_HANDLED;
			$params['result'] = ... results
		}

		// Fetch only. For example, you could enable logging in strictly with a secret field, NOT the username or email.
		$validVBUser = MyPackage::tryFetchUserinfo($params['username']);
		if (!empty($validVBUser))
		{
			// validVBUser must provide the following keys:
			// 'email', 'username', 'userid', 'token', 'scheme'.
			// Also note the camel case of the key 'userInfo'
			$params['userInfo'] = $validVBUser;
			// don't forget to set this to LOGIN_USERINFO_REPLACED to inform other hooks.
			$params['loginstate'] = vB_LoginState::LOGIN_USERINFO_REPLACED;
		}
		else
		{
			// Explicitly disallow default VB fallback login if our "secret field lookup" failed.
			$params['loginstate'] = vB_LoginState::LOGIN_REJECTED;

		}
	}


	/*
		This is called after a user is approved in the user moderation page
		of the admincp
	*/
	public static function hookUserModerationApproved($params)
	{
		//the user id of the user
		//this parameter is read only
		$params['userid'];
	}


	/*
		This is called when a user is logged in (front end, or control panel)
	*/
	public static function hookProcessNewLogin($params)
	{
		// The function results array:
		//	sessionhash -- hash identifying the new session
		//	cpsessionhash -- the hash for the cp session (only present if the user is an admin or a mod)
		//	userid -- id of the user newly logged in
		//	password -- remember me token
		//	lastvisit -- the newly logged in user's last visit,
		//	lastactivity -- the newly logged in user's last activity
		$params['result']; // Editable

		// The login type
		$params['logintype'];

		// Parameter will always be blank
		// will be removed in a future version of vb.
		$params['cssprefs'];

		// The userinfo array for the user logged in
		$params['userinfo'];
	}


	/*
		This is called when a user logs out (of the front end only, atm).
	*/
	public static function hookProcessLogout($params)
	{
		// The function results array:
		//	sessionhash -- hash identifying the new session
		//	apiaccesstoken -- the current api access token, if this is a request through MAPI

		$params['result']; // Editable

		// The userinfo array for the user logging out
		$params['userinfo'];
	}


	/*
		Called by every page when the router initializes
	*/
	public static function hookSetRouteWhitelist($params)
	{
		/*
			The whitelisted routes, available even when the forum is turned off.
			When the forum is turned off and a non-whitelisted-route is accessed,
			it will not process and display an error page instead.

			Default whitelist:
			'admincp',
			'auth/login',
		 */
		$params['whitelistRoute']; // Editable

		// e.g. add lorem/ipsum to the whitelist without modifying the others:
		$params['whitelistRoute'][] = 'lorem/ipsum';
	}


	/*
		Called by every page as the router decides on what route to use.
	*/
	public static function hookGetRouteMain($params)
	{
		// The pathInfo passed into the getRoute() function
		$params['pathInfo'];

		// The queryString passed into the getRoute() function
		$params['queryString'];

		// The anchor passed into the getRoute() function
		$params['anchor'];

		// The selected route object
		$params['route']; // Editable
	}


	/*
		Called by every page after the router decides on what route to use.
	*/
	public static function hookGetRoutingControllerActionWhitelist($params)
	{
		/*
			When the forum is turned off and a non-whitelisted-controller+action is accessed,
			the forum will display an error page.

			The whitelist should be in the form of
			{controller} => array({action1}, {action2}, ...)
			all in lowercase (not camelCase!).
			e.g.
			$params['whitelist']["googlelogin.page"] = array(
				'index',
				'json',
				'debug',
			);
		 */
		$params['whitelist']; // Editable
	}


	/*
		Called by the page controller just after the page is generated.
	*/
	public static function hookGetPageMain($params)
	{
		// The pageid of the page being generated
		$params['pageid'];

		// The user action passed to the page controller
		$params['useraction'];

		// The generated page
		$params['page']; // Editable

		// The pagekey for the generated page
		$params['pagekey']; // Editable

		// The page arguments passed to the page controller

		$params['arguments']; // Editable
	}


	/*
		Called by the registration code just before the new user is created.
	*/
	public static function hookRegistrationBeforeSave($params)
	{
		// A copy of the parent controller object
		$params['this'];

		// The registration data that is used to create the new user
		$params['data'];

		// If set to 'true', the parent registration function will be aborted without further processing
		// If set to a standard error array then the array will be returned and displayed to the user
		// The error array format is
		$params['abort'] = ['errors' => [$phrase1, $phrase2]];

		//phrases can either be a string or an array with the phrase name and parameters, for instance
		$params['abort'] = ['errors' => [['error_x', 'some unphrased error message']]];
		//or
		$params['abort'] = ['errors' => ['my_custom_phrase_without_params']];

		$params['abort']; // Editable
	}

	/*
		Called when checking if Third party login buttons should be displayed on the login iframe.
		Use this hook if your product cannot extend the vB_Api_ExternalLogin::showExternalLoginButton()
		function
	*/
	public static function hookShowExternalLoginButton($params)
	{
		// array of {string productid} => {bool show button}.
		$params['buttons'];
		// By default, populated with facebook button:
		/*
		$options = vB::getDatastore()->getValue('options');
		$params['buttons']['facebook'] = (bool)$options['facebookactive'];
		 */
		$params['buttons']['sampleproduct'] = true;
	}

	/*
		Called when checking if Third party connection blocks should be displayed on the registration form.
		Use this hook if your product cannot extend the vB_Api_ExternalLogin::showExternalRegistrationBlock()
		function
	*/
	public static function hookShowExternalRegistrationBlock($params)
	{
		// array of {string productid} => {bool show button}.
		$params['blocks'];
		// By default, populated with facebook button:
		/*
		$options = vB::getDatastore()->getValue('options');
		$params['blocks']['facebook'] = (bool)$options['facebookactive'];
		 */
		$params['blocks']['sampleproduct'] = true;
	}

	/*
		Called right after a user is deleted, but before the `userauth` records are removed.
		Use this hook if your product cannot extend the vB_Library_ExternalLogin::postUserDelete()
		function.
		The extension or this hook should take care of any additional cleanup required by the product
		if it stores any user data outside of `userauth`.
	*/
	public static function hookExternalLoginPostUserDelete($params)
	{
		// integer userid of deleted user.
		$params['userid'];
		// Note that at this point, userauth records are still available iff the deleted user previously
		// linked their accounts.
		$authRecord = vB_Library::instance('YourPackage:ExternalLogin')->getUserAuthRecord(null, null, $params['userid']);

		if (!empty($authRecord))
		{
			vB_Library::instance('YourPackage:ExternalLogin')->doFullCleanup($authRecord);
		}
	}

	/*
		Called when generating the list of template groups.  Allows altering the list or adding to it.
	*/
	public static function hookTemplateGroupPhrase($params)
	{
		//This is a map of template group name to phrase name.  The former is used as the prefix to
		//determine if a template is part of the group, the latter is the phrase used to display the
		//group name
		$params['groups']; //Editable

	}


	/*
		Called by the Nodevote API after the default permission checks for voteNode()
	*/
	public static function hookCanVoteNode($params)
	{
		// Note, if invalid data is passed into voteNode(), or if the current user is not
		// logged in, we will hit an exception before we hit this hook.

		// Readonly data dicerned from the voteNode() parameters
		$params['currentUserid']; // int
		$params['votetype']; // [int votetypeid, string label, int votegroupid]
		$params['votegroup']; // [int votegroupid, string label, string onchange]
		$params['node']; // array - typical "bare" node array without attached contents.

		// Editable, ovewrite to true to allow this ballot, false to disallow.
		// Currently defaults to true.
		$params['can'] = true;
		// If params['can'] is false voteNode() will throw a no_permission exception.


		/*
		// E.g.
		$conditions = [
			'nodeid'      => $params['node']['nodeid'],
			'votegroupid' => $params['votegroup']['votegroupid'],
			'whovoted'    => $currentUserid,
			'votetypeid'  => $params['votetype']['votetypeid'],
		];
		$existing = $this->assertor->getRow('nodevote', $conditions);
		if ($votegroup['label'] == 'mytopicflairs' AND $votetype['label'] == 'cool')
		{
			// e.g. check if user is subscribed or is a specific usergroup member
			$userinfo = vB_Api::instanceInternal('user')->fetchCurrentUserInfo();
			$usergroups = array();
			if ($userinfo['membergroupids'])
			{
				$usergroups = explode(',', $userinfo['membergroupids']);
			}
			$usergroups[] = $userinfo['usergroupid'];
			$somecoolgroup = 1234;
			if (!in_array($somecoolgroup, $usergroups))
			{
				throw new vB_Exception_Api('no_permission');
			}
		}

		$cutoff = 10800; // 3 hours
		if (!empty($existing) && $timenow > $existing['dateline'] + $cutoff)
		{
			throw new vB_Exception_Api('too late to change votes');
		}
		 */
	}

	/*
		Called by the Nodevote API after the default permission checks for unvoteNode()
	*/
	public static function hookCanUnvoteNode($params)
	{
		// Note, if invalid data is passed into unvoteNode(), or if the current user is not
		// logged in, we will hit an exception before we hit this hook.

		// Readonly data dicerned from the unvoteNode() parameters
		$params['currentUserid']; // int
		$params['votetype']; // [int votetypeid, string label, int votegroupid]
		$params['votegroup']; // [int votegroupid, string label, string onchange]
		$params['node']; // array - typical "bare" node array without attached contents.

		// Editable, ovewrite to true to allow this ballot, false to disallow.
		// Currently defaults to true.
		$params['can'] = true;
		// If params['can'] is false unvoteNode() will throw a no_permission exception.
	}

	/*
		Called by the Nodevote library after nodevote aggregate change
	*/
	public static function hookNodevoteAggregateChange($params)
	{
		// Readonly data
		$params['nodeids']; // int[]
		$params['delta'];   // int

		// Editable, unset any votetype(s) that is handled exclusively by this package.
		// Only do this iff multiple packages run conflicting operations for a given votetype.
		$params['votetypeids']; // int[]

		/*
		Notes:
		This hook may be invoked when:
			A node receives a vote
			A node is unvoted
			A user is deleted
			-- Sample action: for any $votetypeids that the extension handles, fetch all nodevoteaggregates and
			   update any impacted sphinx or mysql helper/aggregate indices
			Votetype(s) or votegroup(s) removed
			-- Special note: in this scenario the votetypeids will no longer exist and queries will return null.
			   The handler is expected to "zero out" any downstream indices in such a case.
		$params['votetypeids'] is editable by other extensions. As such do not use them directly in unsafe context
		without sanitizing first.
		 */

		/*
		E.g.

		// somefilterfunction would be a custom function that filters out only the subset
		// of votetypes that this extension handles.
		$votetypesPackageHandles = somefilterfunction($params['votetypeids']);
		if (empty($votetypesPackageHandles))
		{
			return;
		}

		if ($params['delta'] == 0)
		{
			// nodevoteaggregate has been zeroed out for these votetypeids & nodeids.
		}

		if (empty($params['nodeids']))
		{
			// This is meant for ALL nodes.
			foreach ($votetypesPackageHandles AS $votetypeid)
			{
				newscore = somebulkscoringfunction($params['delta'], $votetypeid);
				//run a query like
				UPDATE someprecalculatedscoretable
				SET score = {newscore} WHERE
					votetypeid = {$votetypeicareabout}
			}
		}
		else
		{
			foreach ($votetypesPackageHandles AS $votetypeid)
			{
				$votetypeid = intval($votetypeid);
				foreach ($params['nodeids'] AS $nodeid)
				{
					$nodeid = intval($nodeid);

					newscore = someindividualscoringfunction($params['delta'], $nodeid, $votetypeid);
					//run a query like
					UPDATE someprecalculatedscoretable
					SET score = {newscore} WHERE
						nodeid = {nodeid} AND votetypeid = {$votetypeid}
				}
			}
		}
		 */
	}

	/*
		Called during bbcode init.
	*/
	public static function hookExtendBbcodeTagList($params)
	{
		// Writable data
		// array, see vB_Api_Bbcode::fetchTagList()
		$params['tag_list'];
		// array of bbcodes with option e.g. [somebbcode=option]abcd[/somebbcode]
		$params['tag_list']['option'];
		// array of bbcodes without option e.g. [somebbcode]abcd[/somebbcode]
		$params['tag_list']['no_option'];
		// Note that a bbcode can support both option & noption.

		// E.g. define a new bbcode
		$bbcodeInfo = [
			// this should be an array of class names (can be an array of a single element).
			// Note that any listed class must be a descendant of vB_BbCode that has a
			//   public function renderBbCode($data, $option) : string
			// defined
			'handlers' => ['custombbcode1', 'custombbcode2', ...],
			// bool, do not allow other bbcodes inside of this bbcode.
			'stop_parse' => true,
			// bool, disabling smilies inside of tag. E.g. ":)" will be treated literally instead
			// of being replaced with an image of a smilie
			'disable_smilies' => true,
			// bool, remove tag if empty (no data between tag open and close)
			'strip_empty' => false,
			// int, default 0, remove up to this many linebreaks after the tag.
			'strip_space_after' => 2,
			// bool, default false, set to true to prevent any links autoparsing into URL bbcodes.
			'disable_urlconversion' => false,
		];
		// allow both option & no_option
		$params['tag_list']['option']['somecustombbcode'] = $bbcodeInfo;
		$params['tag_list']['no_option']['somecustombbcode'] = $bbcodeInfo;

		// Extend a bbcode
		$bbcodeInfo = [
			// When EXTENDING a bbcode, we want to 1) set up the defaults
			// if it's not defined yet, and then 2) extend the 'handlers'
			'handlers' => [],
			'stop_parse' => true,
			'disable_smilies' => true,
			'strip_empty' => false,
		];
		// Step 1, set up the defaults with empty handlers. This is done this way
		// as to avoid removing handlers that may have been already set by another
		// package or core code.
		$params['tag_list']['option']['someextendedbbcode'] ??= $bbcodeInfo;
		$params['tag_list']['no_option']['someextendedbbcode'] ??= $bbcodeInfo;
		// Step 2, extend the 'handlers'. Put your own handler earlier for higher
		// precedence, or put it at the end for lower precedence.
		$params['tag_list']['option']['someextendedbbcode']['handlers'][] = 'mybbcodeclass';
		$params['tag_list']['no_option']['someextendedbbcode']['handlers'][] = 'mybbcodeclass';
	}

	/*
		Called during Wysiwyg HTML to BbCode parsing, when links in the text are automatically
		converted into URL bbcodes. If your product requires a class of links to be converted
		into a different bbcode, handle them in this hook.
	 */
	public static function hookConvertUrlToBbcodeCallback($params)
	{
		// Readonly data
		// Array, matches array passed into the callback of preg_replace_callback().
		// For the regexes, see vB_Library_Bbcoe::getRegexForUrlDetection().
		$params['matches'];

		// Writable data
		// bool, a previous product has already handled this replacement. Check this
		// before overriding the following data.
		$params['handled'];
		// string, the replacement string.
		$params['replace'];


		// e.g. replace with custom bbcode instead of url bbcode.
		$matches = $params['matches'];
		// Don't forget to check 'handled'
		if (!$params['handled'] AND strpos($matches[0], 'somedomainthishandles.com') !== false)
		{
			// don't forget to set 'handled' to true
			$params['handled'] = true;
			$params['replace'] = "[custombbcode=someoption]$matches[0][/custombbcode]";
		}
	}

	public static function hookGetBbcodeRenderOptions($params)
	{
		// Readonly
		// String 'EDITOR'|'NORMAL_FRONTEND'. EDITOR means it's for ckeditor. NORMAL_FRONTEND means for regular browser view.
		// Sometimes, you do not want complex HTML in the editor view.
		$params['context'];

		// Writable
		// Array, set your custom bbcode specific render options here.
		$params['renderOptions'];
	}


	/*
		Called in vB_Rss_Feed::fetch_xml(), right before $url is fetched
		via $vurl->get($url); This allows for extensions to inspect the
		feed URL and add any custom cookies or headers that might be
		required for the specific URL.
	 */
	public static function hookRssFetchXmlPreGet($params)
	{
		// Readonly
		// String URL's going to be fetched
		$params['url'];

		// Writable
		// vB_Utility_Url instance
		$params['vurl'];

		// E.g. Set a custom cookie
		if (strpos($params['url'], 'somecustomdomain') !== false)
		{
			$params['vurl']->setOption($vurl::COOKIE, "somecustomcookie=somecustomvalue");
		}
	}

	/*
		Called by RSS Feed widgets while feed data is parsed. This hook is called after each item
		has been mapped from the RSS or Atom feed item/entry to a PHP array formatted for
		vBulletin's RSS Feed widget.
	 */
	public static function hookRssFeedMapItem($params)
	{
		// Readonly
		// String 'atom'|'rss'
		$params['type'];

		// SimpleXmlElement representing a single channel->item (RSS) or entry (Atom)
		$params['in_item'];

		// Writable
		// Array mapped in_item data
		$params['out_item'];
		// $params['out_item'] is expected to have the following keys:
		/*
			'title'       => string,
			'description' => string,
			'url'         => string,
			'timestamp'   => unixtimestamp,
		 */

		// E.g. modify the description:
		if (strpos($url, 'myfeedurl') !== false AND $params['type'] == 'rss')
		{
			// do your package specific logic / lookup / replacements here.
			$params['out_item']['description'] = "This is a custom description that's been set by a product";
		}
	}
}
