Mark

IPS Staff
  • Content count

    29020
  • Joined

  • Last visited


About Mark

  • Rank
    Meet Jay
  • Birthday March 04

IPS Marketplace

  • Resources Contributor Total file submissions: 5

Profile Information

  • Gender Male
  • Location Colchester, UK

Recent Profile Visitors


126,361 profile views

Mark's Activity

  1. Mark added a post in a topic: Commerce Payment Flow   

    Neither. Full payment goes to the site owner (which could be through any payment gateway).
    The seller then, at a later date, requests a payout of the money owed to them (which could be through PayPal or bank transfer with Stripe).
  2. Mark added a post in a topic: Error Codes Conventions   

    ABXXX/Z
    A = Severity
    Severity Description Examples 1 User does something that is not allowed. Can happen in normal use. User did not fill in a required form element. 2 Action cannot be performed. Will not happen in normal clicking around, but may happen if a URL is shared. User does not have permission to access requested page; Page doesn't exist. 3 Action cannot be performed. Will not happen in normal use. Secure key doesn't match; User submitted a value for a select box that wasn't in the select box. 4 Configuration error that may happen if the admin hasn't set things up properly. Uploads directory isn't writable; Facebook application data was rejected. 5 Any error that should  never  happen. No login modules exist; Module doesn't have a defined default section.   B = A single character representing the application.   XXX = A 3-digit number which represents the class. Starting with 100, it's just in the order we created them. We have a script which keeps track of them.   Z = A number or letter representing the error within that class. Starting at 1, when we get to 9, we then go A-Z.
  3. Mark added a bug in IPS4 Bug Tracker   

    Gallery Upgrader
    Gallery's Upgrader doesn't go back further than the current latest 3.x version (if you tried to upgrade from IP.Board 3.2 and whatever the IP.Gallery version was at that time, for example, it wouldn't work).
    • 0 replies
    • 0 views
  4. Mark added a bug in IPS4 Bug Tracker   

    Pages Upgrader
    Page's Upgrader doesn't go back further than the current latest 3.x version (if you tried to upgrade from IP.Board 3.2 and whatever the IP.Content version was at that time, for example, it wouldn't work).
    • 0 replies
    • 0 views
  5. Mark added a bug in IPS4 Bug Tracker   

    Commerce commission email unsubscribe link
    I received an email advising me that I'd earned commission from the marketplace. The footer said "Prefer to stop receiving these emails? Unsubscribe here." but with no link. 
    • 0 replies
    • 0 views
  6. Mark added a bug in IPS4 Bug Tracker   

    "With selected" doesn't go away
    I split two posts out of a topic. Now whenever I visit that topic I still have the action bar which says "With 2 selected".
    • 0 replies
    • 0 views
  7. Mark added a article in Development   

    Versions and Upgrading your application
    This article covers how to define versions for your application and use the upgrader to run queries to upgrade between versions.
     
    Coming from an IP.Board 3 app?
    If you have an application which was originally developed for IP.Board 3.x, you will need to do the following steps. It doesn't matter if you installed a fresh 4.0 install or upgraded from 3.x.
    Create a new application as normal in the AdminCP If you installed a fresh 4.x rather than upgraded, manually create the database tables in your SQL database. Under the Database Schema tab in the Developer Center for your application,  import  the database tables. It is important that you use the "Import From Database" tab as the other options will assume these are new tables. Under the Versions tab, add all of your previous versions and the queries/code needed to upgrade between those steps - this is discussed in detail below. There is an option to upload your old versions.xml file to import them quickly, but you will need to specify the upgrade for each version queries/code manually.  
    How to define versions
    In the Developer Center for your application, there is a Versions tab which shows all the versions there has been for your application. It is important that you always have the latest, unreleased version of your application in here. For example, for a brand new application, you will have version "1.0.0" under the versions tab. When you build and release your application (version 1.0.0), you should straight afterwards add the next version you will be working on (1.0.1 for example) to the Versions list. This is because while you're working on your application, the system will automatically add any changes you make to the database schema to the upgrade routine for the latest version you have defined.
     
    Database Schema
    The Database Schema tab is where you define all of the tables that your application uses. When your application is installed for the first time, all of the tables you have defined or created. As you make changes to the database schema, the system will automatically add the appropriate queries to make those changes to the upgrade routine for the latest version.
    For example: say the current latest version of your application under the Versions tab is 1.0.0 and you have a table under the Database Schema tab. You release that version, and then add version 1.0.1 to the Versions tab. Later, you add a column to the table under the Database Schema tab. When you release version 1.0.1, the system will automatically:
    For new installs: Just create the table as it is defined, including the added column For upgrades: Add the column You do  not  need to manually add a statement to add the column to your upgrade routine.
     
    Custom Version Queries and Code
    Though the Database Schema system is good at automatically handling tables owned by your application, you may find you need to run other queries (for example UPDATE queries or queries to add columns to tables not owned by your application). This is done under the Versions tab. Simply click the "+" icon for the version that the query need to be ran for (for example, if the query needs to be ran when upgrading to 1.0.1, you would click the "+" button for version 1.0.1).
    You will notice there is a special "install" version which can be used to specify queries which should be ran on install. Since on a fresh install, those are the only queries that are ran, it may be necessary to add a query both to the "install" special version, and to the version you're working with. For example, if you're working on version 1.0.1 and decide you want to add a column to the core_groups table that you didn't add in version 1.0.1, you would need to specify the query in  both  the 1.0.1 version (for people upgrading from 1.0.0) and the special "install" version (for new installs).
    If you need to run code which cannot be expressed as a single query, you can also define custom code. To do this, click the "</>" button for the version that needs custom code. This will write a file to the applications/<your_app>/setup/<version>/ directory with a skeleton to get you started. Simply open that file and follow the instructions within it to add your code. You can also do this for the special "install" version.
     
    Uninstall Code
    The system will automatically delete any tables defined by your application's Database Schema when uninstalling. If you need to run code in addition to this, you can do this with the Uninstall extension. In the Developer Center for your application, under the Extensions tab, click the "+" button for core > Uninstall and create an extension (the name of it isn't important). This will write a file to the  applications/<your_app>/extensions/core/Uninstall/  directory with a skeleton to get you started. Simply open that file and follow the instructions within it to add your code.
    • 0 replies
    • 0 views
  8. Mark added a bug in IPS4 Bug Tracker   

    Commerce Upgrader
    Commerce's Upgrader doesn't go back further than the current latest 3.x version.
    • 0 replies
    • 0 views
  9. Mark added a post in a topic: IP. Chat 4.0?   

    It will come. It's not ready yet though  
  10. Mark added a article in Development   

    Pluralized Language Strings
    Some language strings in IPS Community Suite have a format like this:
    {# [1:post][?:posts]} This is to accommodate how words can change depending on the number of things they are referring to. In this example, the string may display "1 post" or "2 post s " depending on the number passed to it. Although in English there is usually only 2 forms (singular and plural), the syntax is designed to be flexible so that any language can be accommodated.
     
    Basic Format
    The basic format is:
    {#[x:value]} The meaning of each section is:
    The curly braces mark the boundaries and are required. The # at the start is where the number will go and is also required and must be at the front. Each set of square brackets represents a possible value. It contains the value, followed by a colon, followed by the word(s) to display if that is the value. At the start of the square brackets, the value (represented here by "x") can be a number, or "?" for all other values not specified.  
    Hiding or moving the number
    If you do not want to display the number, you can add an ! before the #. For example:
    %s {!#[1:likes][?:like]} this Will display "%s likes this" if the value passed is 1 or "%s like this" for any other value.
    If you do this, you can display the number elsewhere by placing another # where you want the number to display. For example:
    {!#[1:week][?:# weeks]} Will display "week" if the value passed is 1, or " x  weeks" for any other value.
     
    Including additional content
    You can include additional characters where desired within the syntax. For example:
    {# anonymous [1:member][?:members]} Will display "1 anonymous member" is the value passed is 1 or " x  anonymous members" for any other value.
     
    Wildcards
    Rather than provide a specific number or "?" for all other values, you can also use wildcards. * will match anything which ends with the following number and % will match anything that begins with it. Though these are never used in English, they are necessary in some languages.
    For example:
    {#[*1:posts][*2:posts][?:posts]} Though all display the same value, the first block will match any number ending with 1 (for example: 1, 21, 171), the second block any number ending with 2, and the final block matching any number.
    Or:
    {#[%1:posts][%2:posts][?:posts]} Though all display the same value, the first block will match any number beginning with 1 (for example: 1, 12, 108), the second block any number beginning with 2, and the final block matching any number.
     
    Multiple Numbers
    You can create strings which accept multiple numbers, and prefix any #s with an index.
    For example:
    {0# [1:post][*2:posts]} and {1# [1:view][*2:views]} Will expect two values. If passed 1 and 2, it will return "1 post and 2 views".
    • 0 replies
    • 0 views
  11. Mark added a post in a topic: External tools for advanced users   

    Deleting the stored files should cause them to be rebuilt. There was an issue with how this is handled for theme-related files which will be fixed in the next beta  
    And yeah, isn't it awesome? Just include init.php and you're away  
  12. Mark added a article in Development   

    Translatable Text Fields
    Translatable text fields can be used to allow the user to provide a different value for all of the different languages they have on their community (if they have only one, they will appear to be a regular text field). They are commonly used for when an administrator has to provide the name for something which may need to be different depending on the language. They are not designed to be used outside of the ACP .
    To use translatable fields in your code, you use the \IPS\Helpers\Form\Translatable class within the  Form Helper . U sing a Translatable field is slightly more complicated than most other form types .
     
    Creating the element
    When creating the element you must provide an $options parameter specifying an application which "owns" the language string and a key. If you are displaying a "create" form for something which hasn't been created yet, you can pass NULL as the key.
    Unlike other fields, $defaultValue must be NULL. The system will automatically fill in the current value for the key.
    For example,  the code to create your element will look something like:
    $form->add( new \IPS\Helpers\Form\Translatable( 'my_translatable_field', NULL, TRUE, array( 'app' => 'app' 'key' => 'my_language_string' ) ) );  
    Handling Submissions
    You must save the returned value manually like so: \IPS\Lang::saveCustom( 'app', 'my_language_string', $values['my_translatable_field'] );  
    Text area and editors
     
    You can make the field a texture rather than single-line textbook by setting the "textArea" element in $options to TRUE. To use a full WYSIWG editor, you must make  all the normal considerations for an editor field . Then set the "editor" element in $options to what you would normally set as $options in the \IPS\Helpers\Form\Editor object.
    • 0 replies
    • 0 views
  13. Mark added a bug in IPS4 Bug Tracker   

    Content databases auto save
    When I create a new database record, I get the autosaved content of the record I was last editing. I'm guessing it's mistakenly using the same autoSaveKey for edits as for creates.
    • 0 replies
    • 0 views
  14. Mark added a article in Development   

    File Uploads
    To allow file uploads in your code, you use the \IPS\Helpers\Form\Upload class within the  Form Helper .
    The administrator has the ability to control how to store different types of file - due to this, using an Upload field is slightly more complicated than most other form types.
    The FileStorage Extension
    You are required to create an FileStorage extension within your application which is mostly used to provide callbacks to locate files uploaded by your field.
    To get started, create an FileStorage extension file through the developer center for your application. A skeleton file will be created in the applications/app/extensions/core/ FileStorage folder with example code. You will need to provide code for all the methods.
    For example, if you are storing each file in a row in a database table, the code might look something like this:
    <?php namespace IPS\forums\extensions\core\FileStorage; class _Key { /** * Count stored files * * @return int */ public function count() { return \IPS\Settings::i()->setting_key ? 1 : 0; } /** * Move stored files * * @param int $offset This will be sent starting with 0, increasing to get all files stored by this extension * @param int $storageConfiguration New storage configuration ID * @param int|NULL $oldConfiguration Old storage configuration ID * @throws \Underflowexception When file record doesn't exist. Indicating there are no more files to move * @return void */ public function move( $offset, $storageConfiguration, $oldConfiguration=NULL ) { $thing = \IPS\Db::i()->select( '*', 'my_table', 'image IS NOT NULL', 'id', array( $offset, 1 ) )->first(); \IPS\Db::i()->update( 'my_table', array( 'image' => (string) \IPS\File::get( $oldConfiguration ?: 'app_Key', $thing['image'] )->move( $storageConfiguration ) ), array( 'id=?', $thing['id'] ) ); } /** * Check if a file is valid * * @param \IPS\Http\Url $file The file to check * @return bool */ public function isValidFile( $file ) { try { \IPS\Db::i()->select( 'id', 'my_table', array( 'image=?', $file ) )->first(); return TRUE; } catch ( \UnderflowException $e ) { return FALSE; } } /** * Delete all stored files * * @return void */ public function delete() { foreach( \IPS\Db::i()->select( '*', 'my_table', "image IS NOT NULL" ) as $forum ) { try { \IPS\File::get( 'app_Key', $forum['image'] )->delete(); } catch( \Exception $e ){} } } } However the appropriate code to use will depend on the nature of how the content created by your files are stored.
     
    Creating the element
    When creating the element you must provide an $options parameter specifying the extension you just created.  For example, the code to create your element will look something like:
    $form->add( new \IPS\Helpers\Form\Upload( 'my_upload_field', NULL, TRUE, array( 'storageExtension' => 'app_Key' ) ) ); Additional options are available to allow multiple file uploads, to restrict the allowed extensions, the maximum file size and more. See the source code for all the available options.
     
    Handling Submissions
    The value returned will be an object of \IPS\File (or an array of \IPS\File objects if the field allows multiple file uploads). You do not need to do anything with the file itself, as it has already been stored according to the administrators preference. You do however, have to save the URL to it (which you can get by casting the \IPS\File object to a string) as that is what you will need to get and manipulate the file later, and use within the extension you created earlier. For example, your code might look like:
    $form = new \IPS\Helpers\Form; $form->add( new \IPS\Helpers\Form\Upload( 'my_upload_field', NULL, TRUE, array( 'storageExtension' => 'app_Key' ) ) ); if ( $values = $form->values() ) { \IPS\Db::i()->insert( 'my_table', array( 'image' => (string) $values['my_upload_field'] ) ); }  
    Manipulating the file later
    To get the \IPS\File object back, you simply call:
    $file = \IPS\File::get( 'app_Key', $url ); The first parameter being your extension, and the second being the URL you obtained when saving the form. You can then use this object to get the contents of the file, delete it, etc. See the phpDocs in \IPS\File for more information on what you can do with files.
    If it is an image file, you can also create an \IPS\Image object to resize, add a watermark, etc. To do this you call:
    $image = \IPS\Image::create( $file->contents() );  See the phpDocs in \IPS\Image for more information on what you can do with images. 
    • 0 replies
    • 0 views
  15. Mark added a article in Development   

    Editor
    To use the WYSIWG editor in your code, you use the \IPS\Helpers\Form\Editor class within the Form Helper .
    Editors automatically have the ability to support attachments, and the administrator can customise the editor to make certain features available in some areas and not others - due to these concerns, using an Editor is slightly more complicated than most other form types.
     
    The EditorLocations Extension
    You are required to create an EditorLocations extension within your application which is mostly used to provide callbacks to locate attachments uploaded to that editor.
    To get started, create an EditorLocations extension file through the developer center for your application. A skeleton file will be created in the applications/app/extensions/core/EditorLocations folder with example code. You will need to provide code for the attachmentPermissionCheck() and attachmentLookup() methods.
    For example, if you are using this editor for an ACP setting, the value of which will be displayed on a certain page which is visible to all members who have access to the application, the code might look something like this:
    <?php namespace IPS\app\extensions\core\EditorLocations; class _Key { /** * Permission check for attachments * * @param \IPS\Member $member The member * @param int|null $id1 Primary ID * @param int|null $id2 Secondary ID * @param string|null $id3 Arbitrary data * @return bool */ public function attachmentPermissionCheck( $member, $id1, $id2, $id3 ) { return $member->canAccessModule( \IPS\Application\Module::get( 'app', 'module' ) ); } /** * Attachment lookup * * @param int|null $id1 Primary ID * @param int|null $id2 Secondary ID * @param string|null $id3 Arbitrary data * @return \IPS\Http\Url|\IPS\Content|\IPS\Node\Model * @throws \LogicException */ public function attachmentLookup( $id1, $id2, $id3 ) { return \IPS\Http\Url::internal( 'app=app&module=module&controller=controller', 'front' ); } } Or if you are using this editor for the description for  Content Items that members create, the code might look something like:
    <?php namespace IPS\app\extensions\core\EditorLocations; class _Key { /** * Permission check for attachments * * @param \IPS\Member $member The member * @param int|null $id1 Primary ID * @param int|null $id2 Secondary ID * @param string|null $id3 Arbitrary data * @return bool */ public function attachmentPermissionCheck( $member, $id1, $id2, $id3 ) { try { return \IPS\app\Thing::load( $id1 )->canView( $member ); } catch ( \OutOfRangeException $e ) { return FALSE; } } /** * Attachment lookup * * @param int|null $id1 Primary ID * @param int|null $id2 Secondary ID * @param string|null $id3 Arbitrary data * @return \IPS\Http\Url|\IPS\Content|\IPS\Node\Model * @throws \LogicException */ public function attachmentLookup( $id1, $id2, $id3 ) { return \IPS\app\Thing::load( $id1 )->url(); } } However the appropriate code to use will depend on the nature of how the content created by your editor will be used.
    Note that you do not have to (and shouldn't) create a separate extension for every single editor. It is common practice for example, to use one extension for every setting field within your application. The $id parameters allow you to know specifically what piece of content is referenced, as explained below.
    You must also create a language string which identfies your editor with the key "editor__ app _ Key ". This is used to display to the admin when they are configuring which buttons show up in which areas. For example, in the core application, the key "editor__core_Contact" is defined as "Contact Form".
     
    Creating the element
    When creating the element you must provide an $options parameter specifying the extension you just created, along with some additional information:
    autoSaveKey  is a string which identifies this editor's purpose. For example, if the editor is for replying to a topic with ID 5, you could use "topic-reply-5". Make sure you pass the same key every time, but a different key for different editors. attachIds are discussed below. Can contain up to 3 elements, and the first 2 must be numeric, but the last can be a string For example, the code to create your element will look something like:
    $form->add( new \IPS\Helpers\Form\Editor( 'my_editor', NULL, TRUE, array( 'app' => 'app', 'key' => 'Key', 'autoSaveKey' => 'my-editor-field', 'attachIds' => array( ... ) ) );  
    Claiming Attachments
    Generally speaking, there are two types of content: things which always exist (like settings) and content which is created and deleted. Attachments are handled differently for each:
    Things which always exist
    Pass an identifier to the attachIds parameter. For example, you might do:
    'attachIds' => array( 1 ) And then in your extension you will look at $id1 and know that 1 is for this instance of the editor. Then you would use different numbers for other editors using the same extension.
    Even if there is only one editor using this extension, you must provide a value for attachIds. The system will then automatically handle claiming attachments.
    Things which are created and deleted
    When displaying the editor on the "create" screen you will pass NULL for attachIds (because of course at this point you don't know what ID you will save it with). Then, in the code for your form which handles creating the content, after you have created the content and therefore have an ID for it, you call this code:
    \IPS\File::claimAttachments( $autoSaveKey, $id1, $id2, $id3 ); $autoSaveKey is the same value used for the autoSaveKey - each of the $id fields are optional but you must provide at least one. They are what will be passed to the method in your extension.
    When displaying the editor on the "edit" screen, you pass the ID values to attachIds and do not call claimAttachments.
    For example:
    $editing = NULL; if ( \IPS\Request::i()->id ) { try { $editing = \IPS\app\Thing::load( \IPS\Request::i()->id ); } catch ( \OutOfRangeException $e ) { \IPS\Output::i()->error( ... ); } } $form = new \IPS\Helpers\Form; $form->add( new \IPS\Helpers\Form\Editor( 'my_editor', NULL, TRUE, array( 'app' => 'app', 'key' => 'Key', 'autoSaveKey' => $editing ? 'creating-thing' : "editing-thing-{$editing->id}", 'attachIds' => $editing ? array( $editing->id ) : NULL ) ) ); if ( $values = $form->values() ) { if ( !$editing ) { $item = new \IPS\app\Thing; $item->content = $values['my_editor']; $item->save(); \IPS\File::claimAttachments( 'creating-thing', $item->id ); } else { $editing->content = $values['my_editor']; $editing->save(); } }  
    ​Displaying the value
    The value is automatically parsed including replacing profanity and other settings configured by the administrator, and sanitised of any security concerns. You can safely store the display value and display it without any further parsing.
    When sending any variable to a template, the system will automatically escape it to prevent XSS vulnerabilities. Since editor content is allowed to contain HTML, and has been sanitised, you can override this escaping by doing:
    {$val|raw}  
    • 0 replies
    • 0 views

About Me

Contributes To

  1. News and Announcements    By IPS

    • 500
      entries
    • 13837
      comments
    • 4816548
      views

    Most recent entry

    IP.Board 3.3.x, 3.4.x Security Update