Mark

IPS Staff
  • Content count

    31061
  • Joined

  • Last visited


About Mark

  • Rank
    I dropped the "iggy"
  • Birthday March 04

IPS Marketplace

  • Resources Contributor Total file submissions: 5

Profile Information

  • Gender Male
  • Location Colchester, UK

Recent Profile Visitors


128,750 profile views

Mark's Activity

  1. Mark added a post in a topic: Multiple options in traslating tool   

    Feel free to open a bug report about anything like this you spot. We're happy to look into any changes necessary for translations.
  2. Mark added a comment: Required Custom Support Fields   

    Rev 9905. I removed the notion of "required" from custom support fields and added a validation callback.
  3. Mark added a post in a topic: Show yourself   

    ​I'm not that young, just shockingly good looking.
  4. Mark added a post in a topic: What is this setting for when editing a theme?   

    That is when adding or editing a theme setting (not a theme as a whole, but an individual setting within it).
    Theme settings are used to control colours, default views, etc.
  5. Mark added a bug in Bug Tracker   

    Max tags
    Create a content item (the example I had was an IP.Downloads file) with 5 tagsSet the ACP setting for maximum number of tags to 3When you edit the file, you will only see 3 tags, but attempting to save the file will result in an error that only 3 tags are allowed.
    This is confusing. It should show all 5, which will of course prompt the error when the user attempts to submit, but will be obvious what the problem is.
    • 0 replies
    • 27 views
  6. Mark added a article in Development   

    Custom Login Handlers
    IPS Community Suite comes with a number of different methods to allow users to log in to the community, called "login handlers". Generally speaking, there are two types of login handlers:
    "Standard" login handlers which take a username and/or email address and password. For example, the default login handler, LDAP and IPS Connect.Login handlers which use a custom form, which are usually (though not necessarily) OAuth based. For example, Facebook, Twitter and LinkedIn login. 
    Getting Started
    Both types are implemented by creating a PHP file in the system/Login folder containing class that extends \IPS\Login\LoginAbstract and inserting a record into the core_login_handlers database table. If you are creating a 3rd party login handler for distribution, you will need to create a plugin to insert that record, and distribute it with your login class.
    When inserting the record into core_login_handlers, set login_key to the name of the class without the namespace (for example, if your class is \IPS\Login\Example, set login_key to "Example").
    Note that your PHP class will be prefixed with an underscore. This is a technicality in how code hooks are facilitated in the IPS Community Suite.
     
    Standard Login Handlers
    Here is a basic skeleton for a standard login handler:
    namespace IPS\Login; class _Example extends LoginAbstract { /** * @brief Authentication types */ public $authTypes = \IPS\Login::AUTH_TYPE_USERNAME; /** * Authenticate * * @param array $values Values from from * @return \IPS\Member * @throws \IPS\Login\Exception */ public function authenticate( $values ) { /* Init */ $username = $values['auth']; // Depending on the value of $authTypes this may be an email instead $password = $values['password']; /* Find member */ try { $member = \IPS\Member::load( $username ); } catch ( \OutOfRangeException $e ) { throw new \IPS\Login\Exception( \IPS\Member::loggedIn()->language()->addToStack('login_err_no_account', FALSE, array( 'sprintf' => array( \IPS\Member::loggedIn()->language()->addToStack('username') ) ) ), \IPS\Login\Exception::NO_ACCOUNT ); } /* Check password */ if ( $password !== 'the-correct-password' ) // Implement correct check here { throw new \IPS\Login\Exception( 'login_err_bad_password', \IPS\Login\Exception::BAD_PASSWORD, NULL, $member ); } /* Return member */ return $member; }     /**      * ACP Settings Form      *      * @param    string    $url    URL to redirect user to after successful submission      * @return    array    List of settings to save - settings will be stored to core_login_handlers.login_settings DB field      * @code          return array( 'savekey'    => new \IPS\Helpers\Form\[Type]( ... ), ... );      * @endcode      */     public function acpForm()     {         return array();     }           /**      * Can a member change their email/password with this login handler?      *      * @param    string        $type    'username' or 'email' or 'password'      * @param    \IPS\Member    $member    The member      * @return    bool      */     public function canChange( $type, \IPS\Member $member )     {         return TRUE;     } }The $authTypes property defines whether your login handler expects a username or email address or either. It is a bitwise field, and the acceptable values are:
    public $authTypes = \IPS\Login::AUTH_TYPE_USERNAME; // Username public $authTypes = \IPS\Login::AUTH_TYPE_EMAIL; // Email address public $authTypes = \IPS\Login::AUTH_TYPE_USERNAME + \IPS\Login::AUTH_TYPE_EMAIL; // Username or email address If you want to base this off a setting, or do any other setup for your login handler, you can implement an init() method.
    The authenticate() function receives the values from the form (the username/email address and password) and can either return an \IPS\Member object if the login was successful, or throw an \IPS\Login\Exception object if it wasn't. If throwing an \IPS\Login\Exception object, the message is displayed to the user, and the code should be one of the following values:
    throw new \IPS\Login\Exception( "Login Failed.", \IPS\Login\Exception::INTERNAL_ERROR ); // Something went wrong with the login handler which wasn't the user's fault. throw new \IPS\Login\Exception( "Login Failed.", \IPS\Login\Exception::BAD_PASSWORD ); // The password the user provided was incorrect. throw new \IPS\Login\Exception( "Login Failed.", \IPS\Login\Exception::NO_ACCOUNT ); // The username or email address the user provided did not match any account. throw new \IPS\Login\Exception( "Login Failed.", \IPS\Login\Exception::MERGE_SOCIAL_ACCOUNT ); // The username or email address matches an existing account but which has not been used by this login handler before and an account merge is required (see below)If your login handler needs to create an account for a user, and it is appropriate to do that, you can do that in the authenticate() method. For example:
        public function authenticate( $values ) { /* Init */ $username = $values['auth']; // Depending on the value of $authTypes this may be an email instead $password = $values['password']; /* Find member */ try { $member = \IPS\Member::load( $username ); } catch ( \OutOfRangeException $e ) { $member = new \IPS\Member; $member->member_group_id = \IPS\Settings::i()->member_group; $member->name = $username; $member->email = '...'; // You'll need to get the email from your login handler's database // You may want to set additional properties here $member->save(); } /* Check password */ if ( $password !== 'the-correct-password' ) // Implement correct check here { throw new \IPS\Login\Exception( 'login_err_bad_password', \IPS\Login\Exception::BAD_PASSWORD, NULL, $member ); } /* Return member */ return $member; }The acpForm() and canChange() methods are discussed below.
     
    Other Login Handlers
    Here is a basic skeleton for an OAuth-based login handler:
    namespace IPS\Login; class _Example extends LoginAbstract { /** * @brief Icon */ public static $icon = 'lock'; /** * Get Form * * @param \IPS\Http\Url $url The URL for the login page * @param bool $ucp If this is being done from the User CP * @return string */ public function loginForm( $url, $ucp=FALSE ) { $redirectUrl = \IPS\Http\Url::internal( 'applications/core/interface/example/auth.php', 'none' ); $oauthUrl = \IPS\Http\Url::external( "https://www.example.com/oauth" )->setQueryString( array( 'client_id' => 'xxx', 'redirect_uri' => (string) $redirectUrl ) ); return "<a href='{$oauthUrl}'>Login</a>"; } /** * Authenticate * * @param string $url The URL for the login page * @param \IPS\Member $member If we want to integrate this login method with an existing member, provide the member object * @return \IPS\Member * @throws \IPS\Login\Exception */ public function authenticate( $url, $member=NULL ) { /* Get user details from service */ $userData = \IPS\Http\Url::external( "https://www.example.com/userData" )->setQueryString( 'token', \IPS\Request::i()->token )->request()->get()->decodeJson(); /* Get or create member */ if ( $member === NULL ) { /* Try to find member */ $member = \IPS\Member::load( $userData['id'], 'my_custom_id' ); /* If we don't have one, create one */ if ( !$member->member_id ) { /* If a member already exists with this email, prompt them to merge */ $existingEmail = \IPS\Member::load( $userData['email'], 'email' ); if ( $existingEmail->member_id ) { $exception = new \IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::MERGE_SOCIAL_ACCOUNT ); $exception->handler = 'Example'; $exception->member = $existingEmail; $exception->details = \IPS\Request::i()->token; throw $exception; } /* Create member */ $member = new \IPS\Member; $member->member_group_id = \IPS\Settings::i()->member_group; /* Is a user doesn't exist with this username, set it (if it does, the user will automatically be prompted) */ $existingUsername = \IPS\Member::load( $userData['name'], 'name' ); if ( !$existingUsername->member_id ) { $member->name = $userData['name']; } /* Set validating if necessary */ if ( \IPS\Settings::i()->reg_auth_type == 'admin' or \IPS\Settings::i()->reg_auth_type == 'admin_user' ) { $member->members_bitoptions['validating'] = TRUE; } } } /* Set service ID */ $member->my_custom_id = $userData['id']; $member->save(); /* Return */ return $member; } /** * Link Account * * @param \IPS\Member $member The member * @param mixed $details Details as they were passed to the exception thrown in authenticate() * @return void */ public static function link( \IPS\Member $member, $details ) { $userData = \IPS\Http\Url::external( "https://www.example.com/userData" )->setQueryString( 'token', $details )->request()->get()->decodeJson(); $member->my_custom_id = $userData['id']; $member->save(); }     /**      * ACP Settings Form      *      * @param    string    $url    URL to redirect user to after successful submission      * @return    array    List of settings to save - settings will be stored to core_login_handlers.login_settings DB field      * @code          return array( 'savekey'    => new \IPS\Helpers\Form\[Type]( ... ), ... );      * @endcode      */     public function acpForm()     {         return array();     }      /** * Can a member change their email/password with this login handler? * * @param string $type 'email' or 'password' * @param \IPS\Member $member The member * @return bool */ public function canChange( $type, \IPS\Member $member ) { return FALSE; } }The $icon parameter should be the name of a FontAwesome icon which is used on some login screens.
    The loginForm() method is used to display the HTML you need for the form. For an OAuth-based handler, this will usually just return the appropriate login button. You can alternatively return an \IPS\Helpers\Form object.
    The authenticate() method is where the bulk of your login code will go. If your loginForm() method returns an \IPS\Helpers\Form object it will be passed an array of values from that form (just like standard login handlers). If your loginForm() method returns raw HTML, it is your responsibility to ultimately redirect the user back to the same URL that was passed as $url to loginForm with the "loginProcess" set to the key for your login handler. Most OAuth providers do this with a gateway script in the interface directory.
    Your authenticate() method needs to return an \IPS\Member object or throw an \IPS\Login\Exception object, just as described above for standard login handlers.
    The acpForm(), link() and changeSettings() methods are described below.
     
    Creating settings for your login handler
    You will likely need to create settings for your login handler so when an admin sets it up they can provide keys, etc. There are two methods to assist with this: 
    acpForm() can return an array of form fields allowing you to specify these settings, and testSettings() allows you to check the settings are correct. For example, to define a client ID setting you might do something like this:
    /** * ACP Settings Form * * @param string $url URL to redirect user to after successful submission * @return array List of settings to save - settings will be stored to core_login_handlers.login_settings DB field * @code return array( 'savekey' => new \IPS\Helpers\Form\[Type]( ... ), ... ); * @endcode */ public function acpForm() { return array( 'example_client_id' => new \IPS\Helpers\Form\Text( 'example_client_id', ( isset( $this->settings['example_client_id'] ) ) ? $this->settings['example_client_id'] : '', TRUE ) ); } /** * Test Settings * * @return bool * @throws \InvalidArgumentException */ public function testSettings() { if ( $this->settings['example_client_id'] == 'invalid id' ) { throw new \InvalidArgumentException("The Client ID isn't correct."); } return TRUE; }And then you can simply access it's value elsewhere using $this->settings['example_client_id'].
    You can of course use custom validation callbacks for fields if appropriate, but often you will need testSettings() where there are multiple settings which work together.
     
    Merging Accounts
    With some login handlers, particularly those which are OAuth-based, you may need to merge accounts. For example, imagine a user is registered on your community, and then they try to log in using Facebook. In this situation, you don't want to create a new account, but rather prompt the user to link their Facebook account with their existing account. In this case, throw an exception in your authenticate() method:
    $exception = new \IPS\Login\Exception( 'generic_error', \IPS\Login\Exception::MERGE_SOCIAL_ACCOUNT ); $exception->handler = 'Example'; $exception->member = $existingAccount; $exception->details = $token; throw $exception;Set $handler to the key for your login handler, $member to the existing account and $details to any details you need to link the accounts together, such as the access token.
    Then implement a link() method:
    /** * Link Account * * @param \IPS\Member $member The member * @param mixed $details Details as they were passed to the exception thrown in authenticate() * @return void */ public static function link( \IPS\Member $member, $details ) { $userData = \IPS\Http\Url::external( "https://www.example.com/userData" )->setQueryString( 'token', $details )->request()->get()->decodeJson(); $member->my_custom_id = $userData['id']; $member->save(); } Your link() method is called after the user has provided their password and it is safe to link the accounts together. Do whatever is necessary so that on subsequent logins, you can log the user in without intervention. Note that link() is static and cannot use any settings from the login handler.
     
    Checking if email/username is in use
    When a user registers an account on the community, your handler can check if the email address or username is acceptable. This is useful if you want your login handler to provide close integration such as is provided by the LDAP and IPS Connect handlers. The methods are emailIsInUse() and usernameIsInUse() - see the signatures in LoginAbstract for information on how to override these.
     
    Changing Details and additional callbacks
    When a user changes their email, password or username on the community, your handler can be notified of these changes and update their databases. You need to implement the canChange() method to let the User CP controllers know you support this functionality, and then the methods are changeEmail(), changePassword() and changeUsername() - see the signatures in LoginAbstract for information on how to override these.
    Additional callbacks are also available - logoutAccount(), createAccount(), validateAccount(), deleteAccount(), banAccount() and mergeAccounts() - see the signatures in LoginAbstract for information on how to override these.
    • 0 replies
    • 111 views
  7. Mark added a comment: [rc7] No more then 25 PM are shown on mobile   

    @Rikki The infinite scrolling doesn't seem to work, but I also noticed the UI on mobile for messenger is a little confusing... it shows the modal popup which it probably shouldn't, and then the default display is just "no message selected"... can we make that a bit better?
  8. Mark added a post in a topic: Embedded Facebook Videos   

    We have added this feature to the next version  
  9. Mark added a comment: [RC7] Autolink not working correctly   

    I will take a look.
  10. Mark added a comment: [RC7] Editor skin not applied IN_DEV   

    Developer mode is intended for us or third party developers to write applications and plugins. In developer mode, certain things are "uncompiled" so that they can be worked on. For example, HTML templates are stored in individual .phtml files rather than compiled into a class, CSS and JS is unminified, etc. CKEditor, and it's theme, are also in "source" mode. When you install a custom CKEditor theme, it is not in source mode, and so cannot be ran in developer mode.
  11. Mark added a comment: Theme&Code Hooks for IPS4 don't work   

    Changed Status to Pending
  12. Mark added a comment: [RC 7a] Theme hook on Downloads index   

    The CSS selector you have used matches two elements. If you want to match only one, you need a more specific selector.
     
  13. Mark added a comment: [RC7a Commerce]Printed invoice doesn't support utf-8 strings   

    @Rikki: If you look at applications/nexus/modules/front/clients/invoices.php::printout() I've added a comment for you to get it to output HTML rather than a PDF.
  14. Mark added a comment: [GOLD] IPS Commerce - Donation minimum amount   

    Changed Status to Fixed

Contributes To

  1. IPS News    By IPS

    • 507
      entries
    • 13984
      comments
    • 4835667
      views