Jump to content

Rikki

Members
  • Posts

    24,413
  • Joined

  • Last visited

  • Days Won

    84

 Content Type 

Downloads

Release Notes

IPS4 Guides

IPS4 Developer Documentation

Invision Community Blog

Development Blog

Deprecation Tracker

Providers Directory

Forums

Events

Store

Gallery

Everything posted by Rikki

  1. Before you begin developing plugins or applications for the IPS Community Suite, you need to enable Developer Mode. Developer mode causes the software to load required files directly from the filesystem, rather than cached versions or from the database. Warning Developer Mode will cause the software to run much slower than usual and may introduce security vulnerabilities. You should only enable Developer Mode if you are a PHP developer intending to develop Applications and Plugins and should only do so on a local installation that is not web-accessible Enabling Developer Mode Follow these steps to enable Developer Mode on your installation: Download the Developer Tools, making sure you download the correct version for the version of IPS Community Suite you are using. Developer Tools for pre-release versions may be available, so you may need to download an older version from the "Previous Versions" section. Extract the developer tools and move them to where IPS Community Suite is installed, merging with the existing files. There is a root "dev" folder, and "dev" folders for each application. If you do not have every IPS Community Suite application installed, you should delete the folders you don't need from the Developer Tools folder before copying. The presence of Developer Tools for uninstalled applications may cause errors. If you do not already have a constants.php file in the root folder of your installation, create one. Add the following line to your constants.php file: <?php define( 'IN_DEV', TRUE ); For more information on how to use the tools which become available when Developer Mode is enabled, and for more information on developing for the IPS Community Suite, see the developer documentation. Important notes The developer tools includes the files necessary for all IPS applications. If you are enabling developer mode on an install where third-party applications and plugins are present you will also need to obtain and apply the developer tools (i.e. the "dev" folder) for those from the author. Note that when you upgrade your installation, you will need to download the updated Developer Tools.
  2. What is an active record? IPS4 makes extensive use of the active record pattern, where each active record object represents a row in your database, and the object provides methods for interacting with that row (including adding new rows). For example, since the \IPS\Member model returns an active record, we can fetch and interact with a member row in the database like so: $member = \IPS\Member::load(1); $member->name = "Dave"; $member->save(); IPS4's base active record class is \IPS\Patterns\ActiveRecord. Most of the model classes you'll work with (including \IPS\Node\Model, \IPS\Content\Item, \IPS\Content\Comment and \IPS\Content\Review) extend the active record pattern, meaning they all offer a consistent interface for working with your data. It's important you make use of the active record methods provided instead of accessing the database directly, as when you add additional features to your content item, the methods will perform more complicated tasks. Configuring ActiveRecord classes When writing a class that extends \IPS\Patterns\ActiveRecord in the inheritance chain (even if you're not extending it yourself directly), you need to specify some properties that allow the class to locate records in the database. They are: public static $databaseTable = 'string'; Required. Specifies the database table that this ActiveRecord maps to. public static $databasePrefix = 'string'; Optional. Specifies the field prefix this table uses. For example, if your table named its fields item_id, item_name, item_description and so on, then $databasePrefix would be set as item_. This allows for automatic mapping of properties to database columns. public static $databaseColumnId = 'string'; Optional (default: 'id'). Specifies the primary key field of this database table. Loading records static \IPS\Patterns\ActiveRecord load( int $id [, string $idField=NULL [, mixed $extraWhereClause=NULL ]] ) Example: $row = \IPS\YourClass::load(1); This method retrieves a row with the ID 1 from the database and returns it as an active record instance of your class. Results are cached, so you can call load for the same item multiple times, and it will only perform one query, returning the same object by reference on subsequent calls. $id (Integer; required) The primary key ID of the record to load. $idField (String; optional) The database column that the $id parameter pertains to (NULL will use static::$databaseColumnId) $extraWhereClause (Mixed; optional) Additional where clause(s) (see \IPS\Db::build for details) Throws InvalidArgumentException if $idField does not exist in the table, or OutOfRangeException if a record with the given ID is not found. Note: this method does not check user permissions on the item being loaded. When writing a class that extends one of the content models (\IPS\Node\Model, \IPS\Content\Item, \IPS\Content\Comment or \IPS\Content\Review, for example), you should instead use loadAndCheckPerms when loading data for the front end. This is a method with the same signature as the load method above, but loadAndCheckPerms will throw an OutOfRangeException if the user does not have permission to view the record. static \IPS\Patterns\ActiveRecord constructFromData( array $data [, boolean $updateMultitonStoreIfExists=TRUE] ) Example: $row = \IPS\YourClass::constructFromData( \IPS\Db::i()->select(...)->first() ); If you have retrieved a database row by manually querying (for example, to fetch the most recent record), and you need to build an ActiveRecord object from the data, this method allows you to do so without having to call load (which would cause another database query). $data (Array; required) Database row returned from \IPS\Db, as an array. $updateMultitonStoreIfExists (Boolean; optional) If true, will update the current cached object if one exists for this ID Updating data on an ActiveRecord You can get or set the data for a record simply by setting properties on the object, which map to your database column names. If you configured the $databasePrefix property in your class, then you should not include the prefix when you reference properties. Example (assuming our database table contained columns named title and description): $item = \IPS\YourClass::load( 1 ); $item->title = "My record title"; echo $item->description; Note that to actually update the database, the save method should be called after you've made all of your changes (see below). If your class needs to perform processing on properties before getting or setting them, you can define getters or setters for each property by adding a method named get_<property> or set_<property>. Within these methods, you can access the raw values using the $this->_data array. Example that uppercases the title property when set: // YourClass.php public function set_title( $title ) { $this->_data['title'] = strtoupper( $title ); } // OtherClass.php $item = \IPS\YourClass::load( 1 ); $item->title = 'my title'; echo $item->title; //--> 'MY TITLE' Saving & deleting records void save() Example: $item = \IPS\YourClass::load( 1 ); $item->title = 'New Title'; $item->save(); After changing data in an ActiveRecord, save must be called in order to update the database. void delete() Example: $item = \IPS\YourClass::load( 1 ); $item->delete(); Deletes a row from the database. Cloning records You can clone records simply by using a clone expression. Internally, \IPS\Patterns\ActiveRecord ensures that primary keys are adjusted as needed. Note that you still need to call save after cloning in order to create the record in the database. $item = \IPS\YourClass::load( 1 ); $copy = clone $item; $copy->save(); echo $copy->id; //--> 2 Using Bitwise flags The ActiveRecord class implements a bitwise feature, allowing you to store multiple boolean values in a single field, without needing to add new fields (and therefore database columns) to your model. Under the hood, the bitwise field is stored as an integer. You define your bitwise flags as keys with a numeric value; this numeric value doubles for each new value you add (so the order would go 1, 2, 4, 8, 16, 32, and so on). You define your bitwise flags as a static property on your model, like so: public static $bitOptions = array( 'model_bitoptions' => array( 'model_bitoptions' => array( 'property_1' => 1, // Some option for this model 'property_2' => 2, // Another option for this model 'property_3' => 4 // A third option for this model ) ) ); In this example, our model's database table would have a column named model_bitoptions - and we use this name in the $bitOptions array to identify it. We're storing three options, but you can define more by following the pattern of doubling the value each time. It's very good practice to comment each option here to explain what it does. Your ActiveRecord model will automatically provide an \IPS\Patterns\Bitwise object for this column, which implements \ArrayAccess, allowing you to get and set values as it it were an array: /* Getting a value */ if ( $object->model_bitoptions[‘property_1’] ) { // ... } /* Setting a value - remember it can be TRUE or FALSE only! */ $object->model_bitoptions[‘property_2’] = FALSE; $object->save(); /* Getting database rows */ $rowsWithPropery1AsTrue = \IPS\Db::i()->select( ‘*’, ‘table’, \IPS \Db::i()->bitwiseWhere( \IPS\YourClass::$bitOptions['model_bitoptions'], 'property_1' ));
  3. Rikki

    Error Handling

    Errors are a natural part of any web application, and you will need to be prepared to show errors when appropriate. Invision Community software has several powerful built in features for error handling that you should familiarize yourself with in order to best handle unexpected (or even expected, but invalid) situations. The Invision Community software throws Exceptions when appropriate, and attempts to throw the most valid exception class possible (including custom exception classes that extend default PHP exception classes). This means that many times when you are calling built in methods within the suite, you will need to wrap those calls in try/catch statements. For instance, when making an HTTP request, the request could fail for any number of reasons, and this is normal - it is important to wrap the request in a try/catch block to ensure that any issues are handled properly. Failure to do so will typically result in a generic "Something went wrong" error being shown to the end user which both is unhelpful (what went wrong?) and often inappropriate (do we need to show an error to the end user?). try { // Do something here throw new \UnexpectedValueException; } catch( \UnexpectedValueException $e ) { // Do something else instead } It is important to catch the individual exceptions when possible and either show relevant error messages, log the information as needed, or perform another action if possible (in some cases, exceptions may be expected and can even be ignored). You should avoid catching the generic \Exception class unless absolutely necessary - it is better to catch 3 separate more specific Exception subclasses instead. When you need to show an error to the end user, there is an error() method in \IPS\Output to facilitate this need. /** * Display Error Screen * * @param string $message language key for error message * @param mixed $code Error code * @param int $httpStatusCode HTTP Status Code * @param string $adminMessage language key for error message to show to admins * @param array $httpHeaders Additional HTTP Headers * @param string $extra Additional information (such backtrace or API error) which will be shown to admins * @param int|string|NULL $faultyAppOrHookId The 3rd party application or the hook id, which caused this error, NULL if it was a core application */ public function error( $message, $code, $httpStatusCode=500, $adminMessage=NULL, $httpHeaders=array(), $extra=NULL, $faultyAppOrHookId=NULL ) The first parameter should be the language string key of the error message to be shown. A full error message to display is also acceptable, in the event the message is coming from a third party or you need to replace variables within the language string. The second parameter should be a unique error code that you generate (using your application key in the code is helpful to ensure it remains unique). The third parameter is the HTTP status code to return to the end user. Error messages should always have a 4xx or 5xx HTTP status code, and you should always use the most appropriate status code possible. For instance, if the user requested a page that is invalid, you would likely return a 404 status code, while a request for a page that the user does not have permission to view should likely return a 403 status code. The $adminMessage parameter allows you to show a different message to administrators vs regular end users. This is useful to guide administrators into resolving issues themselves. For example, if the reason an error is being thrown is because the feature is disabled, you may tell the end user "You do not have permission to access this page", but you may want to instead tell the administrator "This page cannot be shown because the setting XYZ is disabled in the Administrator Control Panel". The $httpHeaders array allows you to send custom headers with the HTTP response, if needed. For example, if the reason a user reached an error page is because they are rate limited, you may want to send a Retry-After header to indicate to the user agent when they can try again. The $extra parameter allows you to include additional information (such as a backtrace) which will be shown to administrators, and the $faultyAppOrHookId is typically omitted when you are manually calling the error() method. Be aware that you should call the error() method even if the current request is an AJAX request. The method will automatically handle this situation and return the error information through a JSON response which will be handled automatically (in most cases at least) by the core AJAX javascript libraries. Often times, when an unexpected error occurs you should also log some additional data to the system logs to help diagnose the problem later. Please review our logging documentation for more information about logging data to the system logs. Error Codes The first number (the severity) is all that matters. Beyond that, you're free to use whatever you want, though I would recommend using something which is vaguely in line with what we do for consistency. Out format is: ABC/D A is a number 1-5 indicating the 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 is a single character indicating the application (or "S" for code outside any application): array( 'core' => 'C', 'forums' => 'F', 'blog' => 'B', 'gallery' => 'G', 'downloads' => 'D', 'cms' => 'T', 'nexus' => 'X', 'chat' => 'H', 'calendar' => 'L' ) C is a 3-digit number indicating the class in which the error occurred. We just started at 100 for each application and every time we need a new one, add a number on. It's just to make it unique - we don't reverse-lookup the number anywhere. D is then an identifier error within the class. The first error code in the class is given 1, if we need more than 9, the next one gets "A" then "B", etc. So, as a random example, the code "2X196/1" is a level-2 severity error in Commerce. It has the code "196/1" - "196" is \IPS\nexus\modules\front\checkout\checkout and "1" means it was the first error which was added. Some tips: Please don't just copy them blindly from existing code. Use descriptive error messages and make use of the ability to show a different error message to admins where appropriate. If it's a severity 4 error, you probably want to show an admin message. The HTTP status code you use is important so make sure you set that properly. Don't use HTTP 500 for an error code with severity 1 or 2 or a HTTP 4xx error for a code with severity 4 or 5. HTTP 404 and 403 will usually be severity 2, HTTP 429 and 503 will usually be severity 1.
  4. Rikki

    Logging

    Logging issues unexpected issues that occur in your third part application or plugin is the #1 way you can help diagnose problems that have occurred after the fact. Often times, proper logging can completely eliminate the need to spend valuable time reproducing an obscure issue and allow you to cut to the chase and look into the problem that actually occurred in the first place. The primary means of logging issues in the Community Suite is through the \IPS\Log class. Generalized logging When you need to log an unexpected situation, you should use the \IPS\Log::log() method. The first parameter should either be an Exception instance, or a string to log. The second parameter should be a randomly-chosen "category" for the log. try { throw new \UnexpectedValueException( 'My message', 200 ); } catch( \Exception $e ) { \IPS\Log::log( $e, 'my_app_issue' ); \\ No handle the issue gracefully, i.e. by showing an error message } Here, the details about the exception will be logged in a category 'my_app_issue', which you can name whatever you want. By using unique error codes, you can more easily identify where the system log entry came from when a user reports the issue to the administrator. Debug logging In addition to generalized logging, you can log debug messages as well, useful when working to diagnose issues in your application and primarily meant for development purposes. The function definition is the same as the log() method, except you call debug() instead. try { throw new \UnexpectedValueException( 'My message', 200 ); } catch( \Exception $e ) { \IPS\Log::debug( $e, 'my_app_issue' ); \\ No handle the issue gracefully, i.e. by showing an error message } In order to enable debug logging, you will need to set a constant in your constants.php file to enable it. define( 'DEBUG_LOG', true ); You can then view the system logs in the AdminCP by using the live search to look for "System logs".
  5. Rikki

    Caching

    The Invision Community software provides two separate, but related, methods of caching data: the data store, and caching abstraction layers. It is essential that both types of caching be considered volatile, and that the data can be regenerated on the fly as needed. In other words, while using a caching layer to speed up the application is a good idea in many cases, your code must handle situations where the data is unavailable in the cache gracefully. The data store The primary method of caching data throughout the Invision Community software is the data store. The data store is accessed through \IPS\Data\Store::i()->$key where $key is any string key that you desire to use (although you should take care to prevent key collisions by choosing a sufficiently unique key name). To set data in the data store you need only set the variable like so \IPS\Data\Store::i()->my_key_name = "Some data"; That's it - the data will be saved in the storage location configured by the administrator (which may be on disk, or in the database). To later retrieve the value that was set, you just call the data store like so echo \IPS\Data\Store::i()->my_key_name; You can also check if the key has been set, and unset it if desired. if( isset( \IPS\Data\Store::i()->my_key_name ) ) { unset( \IPS\Data\Store::i()->my_key_name ); } The data store library will take care of fetching, saving and deleting the values automatically when used in this manner. The data store can be cleared at any time, so it is important that you ensure your values exist before relying on them. For instance, instead of simply echoing the value as shown above, you should do something similar to the following instead if( !isset( \IPS\Data\Store::i()->my_key_name ) ) { \IPS\Data\Store::i()->my_key_name = 'my value'; } echo \IPS\Data\Store::i()->my_key_name; Be aware that running the support tool, upgrading, and other features of the software will clear data store entries entirely. The caching layer In addition to the data store feature built into Invision Community software, true caching to a caching layer is also possible. By default, the following caching layers are supported: Redis It is possible for third parties to add support for other caching layers as well, however userland code does not need to know what caching layer is in use (if any). Firstly, it is worth noting that any values stored in the data store are automatically cached to the configured alternate caching layer if present. This is handled transparently, and you should still simply call to \IPS\Data\Store::i()->xxxxx regardless. If your application would benefit from storing additional data in a caching engine that does not need to be stored in the data store, you may access the caching layer directly as well. For example, the Invision Community software has the capability to cache entire pages for guests for a configured amount of time, and this is not stored in the data store but is stored in the alternate caching layer if present (in effect, this means that your site can serve entire pages to guests through Memcache or a similar engine without ever even needing to establish a database connection). The caching layer will automatically fallback to a special None class if no caching layer is configured, so you do not need to worry about whether it is set up or not. You simply call to it as you would the data store, like so if( isset( \IPS\Data\Cache::i()->my_cache_key ) ) { echo \IPS\Data\Cache::i()->my_cache_key; } else { $variable = 'some data'; \IPS\Data\Cache::i()->my_cache_key = $variable; echo $variable; } It is important to remember that caching systems typically automatically delete older or least frequently accessed data as they run out of storage room, so (like the data store) all data stored in a caching layer must be considered volatile and cannot be relied on to be present. Other caching notes Beyond these explicit caching classes that you can make use of, there are a couple of other notes regarding caching that are worth mentioning. Inline caching It is not uncommon when a page request is executed that a method may be called multiple times, and if that method performs resource-intensive work, this can slow your application down. It is good practice, when possible, to cache data that has been fetched or computed once and to return that data. In most cases, it is as simple as the following (assume this is pseudo-code inside a class): /** * @brief Property to temporarily store computed data */ protected $temporaryCache = NULL; /** * This method does something computationaly expensive * * @return string */ public function resourceIntensiveMethod() { if( $this->temporaryCache !== NULL ) { return $this->temporaryCache; } // Do some work $variable = '....'; $this->temporaryCache = $variable; return $this->temporaryCache; } The premise of this pseudo-code is that when our slow method is called, we check first to see if we've already done the work, and if so we return it. If not, we perform the work we need to do, but then store the result in a variable so that next time we can return it quicker. The values will not persist to another process or page load, but if this method took 2 seconds to execute and was called 5 times, the page would take at least 10 seconds to load. By caching the data after the first call, we can drop the page loading time down to the 2 seconds this method takes to execute the first time. Keep in mind that there is a trade off with all caching. For example, PHP has a memory_limit and storing too much data in class properties in this manner could cause your application to more easily reach the configured memory limit, resulting in a PHP error. You will need to decide while working on your application what data is worth caching and what data isn't. Tools like XDebug can help you identify bottlenecks in your code to focus on. HTML/Resource caching Another way to take advantage of caching is by instructing browsers to cache the response that is sent to them. Before proceeding, it is worth mentioning that browser-based caching cannot be strictly relied upon. Some user agents may ignore caching headers sent to them, proxy servers can be set up which may ignore caching headers (or may cache resources unexpectedly), and so on. Additionally, you generally do not want browsers to cache most dynamically generated pages because they will be different on each page load (on one page load you may be a guest, and then you may log in and be viewing the page as a logged in user on the next visit for example). To that end, you should be careful and not rely on browser-based caching. That said, if you are sending a static resource to the browser you may wish to instruct the browser to cache the file to reduce overall strain on the server for later requests. To send caching headers with a response you must send the appropriate HTTP response headers in the 4th parameter passed to \IPS\Output::i()->sendOutput(). A utility method is available to help you reliably set the headers. \IPS\Output::i()->sendOutput( "The output to cache, e.g. a file", 200, 'text/html', \IPS\Output::getCacheHeaders( time(), 360 ) ); The first parameter to getCacheHeaders() is the time the file was last modified, and the second is how long to cache the response for. This is used, for example, when an attachment is downloaded so that if the user attempts to redownload the attachment within a short period of time their browser may retrieve the already downloaded file from the browser cache without having to request it again from the server. Caches inside ActiveRecord classes If you have any data which need to be rebuilt after an item was created, editor or deleted, you can use the $cache property inside the AR class which will prune the necessary caches automatically!
  6. Rikki

    Routing & URLs

    URL routing in IPS4 Most URLs in IPS4 are 'friendly URLs', also known as FURLs - that is, they are plain English and easy to read for users and search engines. The FURL is mapped by IPS4 to a traditional URL with parameters, based on the mappings provided by the application. For example, the FURL community.com/messenger/compose might be mapped to community.com/index.php?app=core&module=messaging&controller=messenger&do=compose. How controllers are loaded Controllers are automatically run when the URL matches. The parameters of the URL are used by IPS4 to locate the correct controller: app=core Indicates the controller is in the core app module=messaging Indicates the controller is in the messaging module, in the core app controller=messenger Indicates we're loading the messenger controller, in the messaging module, in the core app do=compose The do param is optional, and allows a particular method within the controller to be called to handle the request. If omitted, by default the manage method of the controller will be called. So, for the URL community.com/index.php?app=core&module=messaging&controller=messenger&do=compose, the controller loaded would be located at /applications/core/modules/front/messaging/messenger.php, and the compose method inside the controller will be called to handle the request. Because of this structure, it's very easy to identify exactly which method in which controller is handling a request for any given URL. Defining friendly URLs When developing an application, your FURL configuration is created in a file named furl.json in the /data directory of your app. This JSON structure defines the friendly URL, the real URL it maps to, and some other data the FURL might use. A simple FURL example ... "messenger_compose": { "friendly": "messenger/compose", "real": "app=core&module=messaging&controller=messenger&do=compose" }, ... The key (messenger_compose in this case) is what identifies this FURL throughout IPS4. When you build links in your templates or controllers, you'll use this key to indicate which FURL is to be used to generate and parse the URL. The friendly value is the URL links will use and users will see. Don't use leading or trailing slashes. In this example, the URL would end up being community.com/messenger/compose. The real value is the actual URL that IPS4 will use to locate the correct app/module/controller to handle the request. FURLs using parameters Although many FURLs are static and simple, as above, many others will be dynamic and include parameters that identify a particular record that should be displayed - for example, viewing a topic in a forum. To facilitate this, FURL definitions support parameters. ... "profile": { "friendly": "profile/{#id}-{?}", "real": "app=core&module=members&controller=profile" }, ... Parameters within the FURL are enclosed in {curly braces}. The first character can either be # to match numbers, or @ to match strings. This is followed by the parameter name, and this should match the parameter name that will passed into the real URL. For example, here we use {#id}, which matches a number and will result in &id=123 being passed into our real URL. The {?} tag can be used for the SEO title; that is, friendly text that identifies the individual record, but which isn't part of the real URL. An example of this would be a topic title - it's included in the URL to help users and search engines, but only the topic ID is used to locate the topic record in the real URL. You can support multiple SEO titles within a URL by using a zero-indexed number in the tag, for example {?0}, {?1} and {?2}. Note: You shouldn't specify the FURL parameters you collect in your real URL; they are automatically added to the real URL by the Dispatcher. In the example above, the URL a user would see and visit would be something like community.com/profile/123-someuser. FURL specificity The order in which you specify your FURL rules is significant. When the Dispatcher is trying to match a URL, it moves sequentially through the FURL rules, and stops at the first rule that matches. It follows therefore that your most specific rules should come before the less-specific rules in furl.json. For example, assume we have two rules relating to a user's profile. One is the profile homepage, the other is the profile edit screen. If we ordered the rules like this: "profile": { "friendly": "profile/{#id}-{?}", "real": "app=core&module=members&controller=profile" }, "edit_profile": { "friendly": "profile/{#id}-{?}/edit", "real": "app=core&module=members&controller=profile&do=edit" }, ...then edit_profile will never match. The profile/1-someuser URL would match first, and be called by the Dispatcher. Instead, ordering them like this: "edit_profile": { "friendly": "profile/{#id}-{?}/edit", "real": "app=core&module=members&controller=profile&do=edit" }, "profile": { "friendly": "profile/{#id}-{?}", "real": "app=core&module=members&controller=profile" }, ...means edit_profile will be skipped unless it's an exact match, allowing the more general profile FURL to handle other profile requests.
  7. Rikki

    Autoloading classes

    Classes in IPS Community Suite 4 are "autoloaded". This means you never have to include or require an IPS4 source file. For reference, the autoload method is \IPS\IPS::autoloader() which is located in the init.php file of the root directory. Locating Classes Classes must be located in the correct location and be named properly so the autoloader can find them. There are generally three locations: Framework classes Classname structure: \IPS\Namespace\Class Location on disk: system/Namespace/Class.php Application classes Classname structure: \IPS\app\Namespace\Class (note that the application key is lowercase, but the parts after are PascalCase) Location on disk: applications/app/sources/Namespace/Class.php Application extensions and modules Classname structure: \IPS\app\modules\front\module\controller (note all the parts are lowercase) Location on disk: applications/app/modules/front/module/controller.php For Framework classes and Application classes, the final file must always be in within a folder (not loose in the system directory). If only one-level deep, the system will look for a file in a directory of the same name. For example \IPS\Member is located in system/Member/Member.php while \IPS\Member\Group is located in system/Member/Group.php Monkey Patching When declared, classes always start with an underscore. For example, throughout the IPS Community Suite, you call \IPS\Member, however, if you look in the source file you will see it declared like this: namespace IPS; class _Member { ... This is a technicality of a feature called monkey patching which allows third party developers to overload any class without any consideration throughout the code and in a way where hooks do not conflict with one another. In this example, the system will execute, right after autoloading the Member.php file, code like this: namespace IPS; class Member extends \IPS\_Member { } If a third party developer wants to overload \IPS\Member, the system will inject this in between, so you end up with a structure like so: \IPS\Member extends hook1 hook1 extends \IPS\_Member Or if two hooks wanted to overload \IPS\Member: \IPS\Member extends hook1 hook1 extends hook2 hook2 extends \IPS\_Member This means that the framework, and any third party code only ever has to call \IPS\Member, and the system will not only automatically autoload the source, but create a chain of any hooks wanting to overload the class. If the mechanics of this are confusing (it is an unusual practice) - it is not essential to thoroughly understand. You only need to know that classes must be prefixed with an underscore when declared, but that underscore is never used when actually calling the class. It also means that rather than calling "self::" within the class, you should call "static::" so you are calling the overloaded class, not the original. Third Party Libraries If you're using composer, please place the vendor directory into the sources directory! If you want to use a third-party PHP library without using composer and it's autoloader, you will of course need to be included manually and cannot be monkey patched. If the library follows the PSR-0 standard for naming, you can add it to \IPS\IPS::$PSR0Namespaces like so, and the autoloader will then autoload it: \IPS\IPS::$PSR0Namespaces['Libary'] = \IPS\Application::getRootPath() . '/applications/app/sources/vendor/Library'; Otherwise, you will need to include the source files manually.
  8. The Developer Center for each application and plugin is designed to be a tool that facilitates creating and updating applications and plugins for the Invision Community software. It can help you automatically track things such as settings, database changes, tasks, and more. While most of the functionality available through the developer center can be bypassed by adjusting your files directly on disk, we strongly encourage you to make use of the developer center whenever possible to minimize development time as well as potential for bugs in your third party code. Because the needs for an application versus a plugin are different, the Developer Center for each is different. Applications The Developer Center for each application allows you to manage several aspects of the application and facilitates creating upgrade routines and managing versions easily. We will run through each tab in the Developer Center to outline what can be done. Modules - Front The front modules tab allows you to create new front modules, which correspond to folders underneath applications/(your-application)/modules/front/ . Once you have created a front module, you can then create one or more controllers underneath it, and you can define which module is the default front module (i.e. if you visit index.php?app=(your-application) which module should be loaded), as well as which controller is the default within each module. You can also edit and delete modules and controllers here. Modules - Admin Just like with the front modules, you can manage your admin modules from the Developer Center. The behavior of this tab is very similar to the front modules tab, with one key (and important) difference: when you create controllers, you can simultaneously create menu entries, ACP restrictions, and you can use one of three templates for the controller itself (a normal controller, a table helper controller, or a node controller). If the controller will be a node controller (i.e. a page to allow you to manage categories which you intend to use the Node functionality for), then you should use the node controller template. Similarly, if you intend to output a table of data (like the member list, or the system logs) then you should use the table helper controller template. Settings Very simply, the settings tab allows you to add new settings and to define their default values. These settings will then be tracked in a settings.json file within your application's data/ folder, and when administrators install your application the setting records will be installed as well. You will still need to create one or more controllers to present the settings to the administrator so that the administrator can adjust the values, as appropriate. Tasks You can use the tasks tab to add any tasks that are required by your application. You will define a 'key' which will be used as the filename, and how often the task should run, and then a skeleton file will be created in your applications/(your-application)/tasks/ folder for you automatically, ready for you to complete. Your tasks will also be tracked in a tasks.json file (within your application's data/ folder) and automatically inserted into the software when your application is installed. Versions With a brand new application you will see two records automatically on the versions tab: install, which represents installation of your application, and working, which represents the current unreleased version (you do not define a version number until you are ready to release your application, even for subsequent update releases). Typically, you will not need to define any database changes here. When you make changes under the Database Schema tab (see below), the SQL changes are automatically tracked for both installations and for the latest release. However, if you are updating your application from 3.x for 4.x you may need to manually define queries to run during an upgrade, or if you are adjusting database tables outside of your application you will need to manually define those changes here. You can also add and remove upgrade routines and manual queries for older versions from this page if needed. If you are creating an upgrade release which requires executing more code than a simple database query, you can click the </> code icon for the "working" version which will create an upgrade.php file in your application's setup/upg_working/ directory. You can then open this file and follow the instructions in the file to create upgrade steps that perform more work than simply executing database queries. You can read more about versioning for applications in the Versions/Upgrading article. Widgets If your application defines any sidebar widgets, you can define them from the Developer Center of your application. When you add a widget for your application, you will define a widget key and specify which widget class your widget extends (which provides certain automatic functionality, such as caching the widget based on permissions if necessary). You can specify if your widget is available in the sidebar and/or within the Pages application for use on pages, you can specify whether your widget should display within your application by default out of the box, choose which style of configuration form to use (menu or popup modal), and determine if the widget can be used more than once on the same page. Upon saving the form, a skeleton template will be written to applications/(your-application)/widgets/ which you will need to adjust as appropriate and the widget will be tracked in /data/widgets.json for installations and upgrades of your application. Hooks Applications, like plugins, can define hooks which are essentially a way to override how default code within the Suite behaves. You can read more about code hooks and theme hooks in the respective articles. Extensions Extensions allow you to perform actions at certain points during normal code execution without having to "hook" into the code execution directly. There are many different types of extensions within the Suite. To create a new extension for your application, under the Extensions tab you expand the application that holds the extension you want to create (typically core or nexus), and then click on the + icon next to that extension type. A skeleton file will be written to your application's extensions/(app)/(extension)/ directory for you. If your application wishes to define its own extensions that other applications can generate, underneath your application's data/ folder you will need to create a folder defaults/ and underneath this folder create a folder named extensions. Within this folder, you create your extension templates which end with a .txt extension, instead of a .php extension. You can see examples within the developer tools for the core application. It is then your responsibility to call the extension during code execution as needed. It is relatively uncommon that a third party application would need to leverage this capability. Database Schema Whenever you need to add new database tables or adjust existing database tables for your application you should always do this under the Database Schema tab within your application's Developer Center. Not only will your changes be ran on your database automatically for you immediately, they will be tracked and automatically executed during both installations and upgrades of your application. You can add, edit and delete database tables, columns, indexes and default inserts for these tables from this area of the Developer Center through a simple to use GUI. Admin CP Restrictions This tab of the Developer Center allows you to define Admin CP Restrictions, which you can then check in your code to determine if the currently logged in administrator has permission to perform the action they are attempting to perform. There is no technical requirement to use Admin CP Restrictions, however it is strongly encouraged that you do so when your application provides various different functionality. For example, if your application can allow administrators to add content, and to view reporting statistics, a website may wish to allow one group of administrators permission to do everything and another group of administrators only permission to view the statistics. You can accomplish this easily by defining these restrictions in the Developer Center, and then checking the restrictions in your code. Restrictions are tied to a module, so you will first need to create an admin module (and you can automatically create a basic restriction during this process). If your module handles viewing, adding, editing and deleting within one module (as an example), however, you may wish to create additional restrictions from the Admin CP Restrictions tab and then adjusting your controllers to check the appropriate restriction when the administrator attempts to perform that specific action. Note, also, that you will want to create language strings for each restriction group and each restriction that is listed on this page, as the language strings will be used in the Admin CP Restrictions area of the AdminCP. If you created a restriction "myappview", then the language key you will want to set will be "r__myappview" for the group, and "r__myappview_manage" (by default, unless you delete this restriction) for the restriction itself. Admin CP Menu The Admin CP Menu tab allows you to manage your links in the AdminCP Menu and relates to the data/acpmenu.json file for your application. You will create new AdminCP Menu entries automatically when adding admin modules (if you choose to do so), however you can also edit this file manually to customize the menu further if needed. If you wish to locate a menu entry (or multiple entries) underneath the statistics tab, you will need to use the top level tab "stats". Plugins The only Developer Center tab for plugins that does not exist for applications is the 'Information' tab, which allows you to adjust the information about your plugin (the author, the plugin name, and so on). The remainder of the tabs are identical to those for applications, and serve the same purpose. You can learn more about plugins and how to use them in our Plugins developer documentation.
  9. Rikki

    Containers

    In most situations, your Nodes will be a container for content items - this is the typical structure used by most apps. Node models and their content item models provide a number of methods for working with each other in this kind of relationship. There are two parts to this relationship - the functionality that becomes available to the container (i.e. the node), and the functionality that becomes available to the content items within that node. This guide will deal with the first; see Content Items in Containers for information on functionality available to your content items. Configuring your node model In your node model, you need to specify the name of the content item model you're using. public static $contentItemClass = 'string'; For example, in the Forum node model, we'd specify the content item model like so: public static $contentItemClass = 'IPS\forums\Topic'; Configuring your content item model In your content item model, you need to specify the name of the node model you're using. public static $containerNodeClass = 'string'; For example, in the Topic node model, we'd specify the node model like so: public static $containerNodeClass = 'IPS\forums\Forum'; In addition, you need to add a key named container to your $databaseColumnMap property, the value of which should be the column name in the database table that holds the ID to the node that is parent to each content item. protected static $databaseColumnMap = array( 'container' => 'parent_id', //other column maps... ); Getters/setters available to the node model After establishing the two-way binding, these additional getters and setters become available to your model class. They are all defined in the base node model class, and by default, all of the get__* methods return NULL, while all of the set__* methods do nothing. You can override them in your own node class if you need to keep track of the values.
  10. By default, IPS4 has a maximum body width of 1340 pixels, and is fluid at sizes smaller than this. However, we ship the software with a customizable theme setting allowing this to be easily overridden, should you want to change the behavior. Using the Easy Mode theme editor If your theme was made using the Easy Mode editor, you can adjust the body width on the Settings tab when designing your theme. First, click the magic wand icon to launch the Easy Mode editor: And then the Settings tab. There's two settings you need to adjust; turn on Enable Fluid Width, and then change the percentage value in the Fluid Width Size setting. To have it take up all available space, set this to 100%. Using the standard theme editor If your theme was created using the standard theme editor in the AdminCP, you adjust the theme settings also in the AdminCP. Click the edit icon next to the theme you want to adjust: And on the Custom tab on the edit screen, you will see the Enable Fluid Width and Fluid Width Size settings.
  11. By default, we use the 'comments' icon from FontAwesome to represent forums on the read/unread badges. IPS4 also includes an option to upload an image that will be used instead of the icon. But what if you want to use a different FontAwesome icon for each forum? The good news is this is possible using some custom CSS. Each forum row in the index template includes a data attribute with the forum ID, meaning we can write a style to specifically target each individual forum and overwrite the icon it uses. Note: Although this method isn't terribly complex, it does require editing custom CSS and working with unicode characters. Determining the icon unicode value The method we're going to use involves replacing the icon using a CSS rule, rather than changing the icon classname in the HTML. The reason we take this approach is it won't make upgrading your templates difficult later - our custom CSS persist through IPS4 versions easily. What this means however is that we need to identify the unicode value that FontAwesome assigns to the icons we want to use. To do so, head over to the FontAwesome icon list on this page. Locate the icon you'd like to use, and click it. On the information page, you'll see the unicode value for the icon. Make a note of this code. For example: Do this for each icon you'll want to use. Adding the CSS We're going to add our CSS to our custom.css file so that it persists through upgrades. In the AdminCP, go to Customizations -> Themes, and click the code edit icon next to the theme you want to change. On the CSS tab, open the custom.css file: The rule we need to use looks like this: [data-forumid="..."] .fa-comments:before { content: "\f123"; } You'll need to adjust this code to include the forum ID for the forum you want to change. You can find the forum ID by hovering on the link to the forum, and noting the number you see in the URL: You'll also need to replace the f123 unicode value with the one for the icon you want to use that you noted earlier. Example Let's say we have forum ID's 1 and 2, and we want to use FontAwesome's bicycle and car icons, respectively. We note the unicode values for those icons, which are f206 and f1b9. The CSS we'd add looks like this: [data-forumid="1"] .fa-comments:before { content: "\f206"; } [data-forumid="2"] .fa-comments:before { content: "\f1b9"; } Once we save it, we can see the result:
  12. For more advanced sites built with Pages, you may want to change the output of a custom HTML or PHP block depending on which page the user is viewing. For example, if you have a custom menu, you may want to highlight the active item. We can implement this in Pages by checking the underlying page URL parameters. Although you access a page with a friendly URL (FURL) like http://<yourcommunity>/section/page, behind the scenes this is mapped to a raw URL, such as http://<yourcommunity>/index.php?app=cms&module=pages&controller=page&path=/section/page. Notice the path parameter allows us to identify which page we're accessing. When we access the \IPS\Request::i() object, we can compare against this parameter, like so: {{if strpos( \IPS\Request::i()->path, 'section/page' ) !== FALSE}} <!-- We know the user is on /section/page --> {{elseif strpos( \IPS\Request::i()->path, 'othersection/otherpage' ) !== FALSE}} <!-- We know the user is on /othersection/otherpage --> {{endif}} Note that for reliability, we're using PHP's strpos function to check for the presence of the page URL in the path parameter, rather than a simple comparison. Example Let's assume we've created a Manual HTML block, we're adding HTML to show a menu, and we want to highlight the correct item based on the page. Here's what our block contents might look like: <ul class='ipsList_inline cMyMenu'> <li {{if strpos( \IPS\Request::i()->path, 'help/home' ) !== FALSE}}class='active'{{endif}}> <a href='/help/home'>Home</a> </li> <li {{if strpos( \IPS\Request::i()->path, 'help/faq' ) !== FALSE}}class='active'{{endif}}> <a href='/help/faq'>FAQ</a> </li> <li {{if strpos( \IPS\Request::i()->path, 'help/tutorials' ) !== FALSE}}class='active'{{endif}}> <a href='/help/tutorials'>Tutorials</a> </li> </ul> If we had many items to show, it would get tedious to list them all like this. We could instead do it as a loop: // Using a PHP variable to store an array of pages => page names that we'll loop over {{$myPages = array('help/home' => "Home", 'help/faq' => "FAQ", 'help/tutorials' => "Tutorials", 'help/qna/listing' => "Questions", 'help/qna/recent' => "Recent Questions", 'help/contact' => "Contact Us");}} <ul class='ipsList_inline cMyMenu'> {{foreach $myPages as $url => $title}} <li {{if strpos( \IPS\Request::i()->path, $url ) !== FALSE}}class='active'{{endif}}> <a href='{$url}'>{$title}</a> </li> {{endforeach}} </ul> Now to add new items to our custom menu, we just have to add them to the array.
  13. We now have a complete, working database for our recipe section! Feel free to take it further - experiment with the other options we haven't used, or if you have any familiarity with HTML coding, try creating your own templates. There's a couple of other handy features in Pages that it's worth pointing out. Database Filters When we created the Cuisine field, we enabled the Allow Filtering option. In Pages, database filters are available as a sidebar block that you can drag and drop wherever you wish to show them. On the front-end listing screen, open the sidebar manager and drag the Database Filters block to the sidebar: Block Manager Exit the sidebar manager, and you'll now see the Cuisine field available for filtering: Database Filters Category Listing It's often useful to have the list of categories shown even when you're already in a category. There's also a Pages block that adds this feature for you. Simply drag the Database Category Menu from the sidebar manager to your sidebar to show it. Here's both of the sidebar blocks we've discussed: Category Listing
  14. Custom fields are what you use to make a database that is specific to your needs. IPS4 supports a wide range of field types so that you can collect data in the appropriate formats, from text and numbers, to upload fields and YouTube embeds. We're going to create a few different fields to gather information about each recipe that we can then display in our database. They are: List of ingredients Prep time Cook time Cuisine A link to a YouTube video recipe We don't need fields for the recipe title, or the instructions. Databases already provide a title field, and we'll also use the built-in content field for the recipe instructions. We'll see how to set up the custom fields, as well as how to customize their appearance. Ingredients field 1. To create a new field, hover on the Pages tab in the AdminCP, and then click the Fields link under the Recipes database in the menu. The screen will show you the default built-in fields for your database, including Title and Content. 2. Click the Create New button; you'll see a form where you configure your field. 3. For Title, enter "Ingredients". This is the field title users will see when adding/viewing recipes on your site. 4. For Description, enter "One per line". The description is only shown on the add/edit record form, so it's a good place to enter special instructions for filling it in. 5. For the type, we'll choose Editor in this case, because it will allow the user to format the list if they wish. The other fields on the form will change depending on which field type you choose, since each has specific options. 6. We don't require a maximum length, but we do want to require this field to be completed, so leave these settings at their default values. 7. On the Display tab, we'll configure how the field content is shown to users. First, enter ingredients as the field key. This key is how IPS4 identifies this field in templates. We won't actually be using it in this tutorial, but it's good practice to set it anyway so you can use it later if needed. 8. We don't want our list of ingredients to show on the listing screen, so disable that option. 9. We do, however, want it to show on the record display screen, since this is the main screen for viewing a recipe. A badge isn't ideal for showing content from an Editor, so change the Display View Format to the simple Label: Value option as shown: Display View Format 10. We'll show the list of ingredients above the main record content, so make sure Above the item content is selected for the Display option. 11. Finally, there's no need to allow this field to be updated when viewing a record; instead, users will go to the full edit form. You can leave the Editable when viewing a record option disabled. 12. Save the form to create the field. Other fields Other fields Other fields are created in the same way, so instead of going through the whole process again, here's the configuration options you need for each field. Where a configuration option isn't listed, leave it at its default value. Note: Some field types, including the Select Box type that Cuisine uses (see below), support a fixed set of options from which the user can choose. You'll set these up as key/value pairs in the AdminCP, but the user will only see the values when they select them. The key is just used internally by IPS4 and in custom templates to identify the value, and is best kept as a single lowercase word. Prep time Name: Prep Time Type: Text Required: No Template key: prep-time Listing view format: Label: Value Display view format: Label: Value Cook time Name: Cook Time Type: Text Required: No Template key: cook-time Listing view format: Label: Value Display view format: Label: Value Cuisine Name: Cuisine Type: Select Box Content: (key/value) indian / Indian italian / Italian american / American mexican / Mexican Allow filtering: Yes Required: Yes Template key: cuisine Listing view format: Label: Value Show to right of listing area: Yes Display view format: Label: Value Display: Above the item content Video recipe Name: Video tutorial Type: YouTube Embed Required: No Template key: video-tutorial Show in listing template: No Show in display template: Yes In a record, display as: Embedded Player That's it! If you now head over to the front-end page for your database, click into a category, and click the Add New Recipe button, you'll see your fields on the form, ready to collect information! Feel free to add some sample records to try it out. You'll also see the Record Image field that the database provides automatically (discussed earlier). When you upload an image here, you'll see it is automatically included in the record view for you. Here's an example recipe in our database: Listing View Record View
  15. The next thing we're going to focus on is creating our categories. If you've created categories in the other IPS4 applications, it's a very similar process. How to set up categories When you hover on the Pages tab in the AdminCP, you'll see your database is now listed as a menu item, and a link for Categories is available underneath. Click this link to view the current categories. You'll see a default "Records" category. Click Create New to add a new one. We'll name this category Main Courses as an example. There's no need to manually enter a manual Friendly URL 'slug' (the slug is the part of the URL that identifies this category, e.g. <yourcommunity>/recipes/main-courses/record) - one will be created automatically based on the category name you enter. Category Creation We do want to show the record listing for this category, since it'll show recipes. We can leave Star Ratings disabled, and we'll leave Set Custom Permissions disabled too. However, if you were creating a category and wanted to use permissions different to those you set up for the database, you could enable that option and do so here. Finally, we'll leave the default database templates selected. If you had a situation where you wanted to use different templates for individual categories, these settings allow you to do so. We will ignore the other two tabs on this form for now; we aren't using the functionality they control. Refreshing our front-end, you'll now see your new category: Category List You can now go back and either delete the Records category, or edit it and rename it and adjust the settings to match. You can also create other categories, for example: Appetizers Desserts Snacks
  16. How to add We now have our page ready, and the database created (albeit completely default and without any customization yet!). The next thing we'll do is add the database to the page. Since we created page as a Page Builder page, we can do this just by dragging and dropping. To open the page builder, first go to the page listing in the AdminCP, at Pages -> Pages. Click the magic wand icon to the right of your recipes page: Pages Listing This will take you to your community, with the sidebar manager ready to use. Open the Pages category, and drag the Database widget into the middle widget area on the page: Block Manager You can then click the Edit button on the widget to select the Recipes database: Select Database That's it! Click Finish Editing on the sidebar manager, and you'll now see your (empty) database on the page. Recipe Front End
  17. What is a database? The Databases feature in Pages gives you the tools you need to create your own custom data-driven sections of your community. You define the settings and fields in the database, and Pages gives you support for categories, comments, reviews, ratings and more - just like full IPS Community Suite applications. Creating the database Databases are set up in the AdminCP, by navigating to Pages -> Databases. You'll see a listing of the current databases in your community. Click the Create New button to add a new one. Details tab On the first tab, we'll set up some basic details about our database. For the name, enter Recipes. This name is how users will see it presented throughout the community, such as on the search form. Enter a description if you wish; this will be shown on the homepage of the database when users visit the page. The next two settings determine how this database will handle categories and records. You can choose whether to use categories at all (the alternative being that records don't exist in categories, and are simply listed in the database), and if you do use categories, whether the user will see a category listing as the homepage or a blog-like article layout. For our use, we are going to use categories, and we want to show the category listing, so select the appropriate options: Details tab Next, we choose the templates this database will use. In our advanced tutorial we create custom templates to change how the database looks, but for this database, we're going to stick with the defaults so that we don't have to edit code. Finally, there's the database key. This is how IPS4 identifies your database internally. You can leave this blank; it will automatically create a key for you. Language tab The fields on the language tab allow you to customize the words that IPS4 will use to refer to the records in your database (since 'records' is generic and non-descriptive in most cases). Because different languages have different grammar rules, there's five variations you need to specify. For English, we'll use these: Language tab Options tab The options tab is where more fine-tuned configuration of the database happens. We don't want wiki-style editing here (which is where any member can edit any record), and we don't need to store revisions for our use. We will enable searching, though. We'll also allow both comments and reviews. Therefore, the first half of the form looks like this: Options tab Following these fields, we can set up the ordering of records in the database. Some built-in sort options are available in the dropdown list, but when you've added custom fields to the database later, they would also be available for sorting here. However, for our use, sorting by the built-in Publish Date and Descending is perfect - it means new records will show at the top, and be ordered in date-descending order. The final section of this tab deals with the record image. This is a special built-in field that allows users to upload an image to represent each record. We'll use this functionality since it suits our purposes. The default settings will work for us: Record image settings Forums tab The forums tab sets up the Pages/Forums integration for records, meaning a topic is automatically posted when new records are added. We won't be making use of this functionality, so you leave it disabled. Page tab The final tab handles the page for your database. We created a page manually in the first step, so we don't need to do so again here, but this form is an alternative way of doing so. Leave this option set to I will add this database into an existing page. After saving the form, you'll set up the permissions for the database, so configure those to your liking. Note that if a group doesn't have permission to access the page we created in Step 1, they won't be able to access this database even if you grant permission here. Also note that in Pages databases, individual categories can override these permissions if desired, allowing you to set up defaults for the database here, but have categories where access is restricted to only certain groups (staff, or premium members, for example). Adding the database to a page
  18. The first thing we're going to do is create a page, into which we'll insert our database later. A page is the foundation of almost everything in Pages; it gives your content a place to be accessed by users. Sometimes, a page may just have static text using a WYSIWYG block. Other times (like in our case), they may be a container for a database. To manage your pages, navigate to Pages -> Pages in the AdminCP. You'll see a listing of your current pages and folders. Click Add Page to create a new one. Add Page There's two ways of creating pages: using the drag and drop Page Builder to add content, or by manually coding a page using HTML. We're going to use the Page Builder, so ensure that option is selected, then click Next. On the next screen, you'll set up some basic configuration for your page. The name is how the page will appear in the browser window, in your menu navigation etc. Since we're creating a recipe area, enter something like Recipes. The filename is how the page is accessed in a URL. Enter recipes here - this means we'll access our page at the URL <yourcommunity>/recipes. For the layout option, choose single column. Pages includes a few default layout options, and for other kinds of pages, you might want a different layout. For databases, a single column tends to work best so that it has enough space to display properly. We're going to ignore the second and third tabs (Page Includes and Title & Meta Tags) on this form for now because they aren't important to what we're doing. On the final tab, Menu, you can easily add this new page to your navigation menu. Add Menu Item Click Save when you're ready. The next screen will ask you to configure the permissions for this page, in much the same way as you'd configure permissions for a forum or gallery. Set them up to your liking, and then save the form. That's it - our page is created, and right now it is empty. We'll come back to it later and make it display something, but for now we'll move onto the database itself. Creating the database
  19. As mentioned in the Introduction to Pages guide, unlike our other applications Pages is a blank slate. Its purpose is to give you tools to build your own areas of your community. As a result, it can seem a little intimidating to new users, and especially non-coders, because it's up to you to build what you need with the tools provided. This tutorial aims to help you get started by walking through the creation of a simple recipe section. The concepts we cover here can be applied to all kinds of sections you might want to build. Who is this tutorial for? Unlike our advanced tutorial, this one doesn't assume you have knowledge of coding. Instead, we'll be using the visual tools available in Pages, the built-in formatting options available for data, and sticking with the templates that IPS4 ships with. Our aim is to give you an understanding of how to build data-driven sections of your site from scratch. Creating a page
  20. Database URL Structure Databases exist inside a page you've created with Pages. Individual categories and records in the database are accessed via the URL of the page. For example, if you had a page with the URL <yourcommunity>/mypage and this page contained your database, you might have a record that's accessed via the URL <yourcommunity>/mypage/category/record, where category is the category name and record is the record name. Your URLs would dynamically update themselves if you renamed your page or moved the database to a different page. To facilitate this approach, databases can only exist in one page at a time. They can't be duplicated on other pages (although you can create blocks showing data from the database and use them on other pages). Fields More advanced uses of databases require custom data to achieve their goals, and fields can be set up to gather this data. Fields are created in the AdminCP, and when a user adds a new record, the fields are shown on the form. IPS4 supports a wide range of field types, allowing you to capture data of a specific type easily. Here's the supported types: Address Provides a special auto-completing address field, powered by Google Checkbox A single on/off checkbox Checkbox set A group of several on/off checkboxes Code Provides a way to syntax-highlight code Date A date field, with a date picker Editor Provides a rich text editor for WYSIWYG editing Database relationship An advanced field type that allows records from different databases to be associated Member Provides an auto-complete member search box Number A number input box (on older browsers, reverts to a simple text box but with number validation) Password A password input field Radio A group of radio boxes (meaning only one of the options can be selected) Select box A drop-down menu containing the provided options (can allow multiple selections if desired) Soundcloud A Soundcloud embed field Spotify A Spotify embed field Telephone A telephone number input field (on older browsers, reverts to a simple text box) Text (default) A one-line text input field Text Area A multiple-line text input field Upload An IPS4 upload field URL A URL input field (on older browsers, reverts to a simple text box with URL format validation) Yes/No A toggle field that can accept yes or no values YouTube A YouTube embed field Many of these field types have additional options specific to them. For example, select boxes have an option to allow multiple values to be selected, whereas the upload has options to allow multiple files, and a way to restrict file types. Field Formatting Fields can have automatic formatting applied to them. For non-coders, a range of badge colors is available to choose from, and you have some control over the location that the field shows in the listing or record display. For coders, however, you have full control over the HTML output for each field, including having use of IPS4's template logic. This means you have the ability to use the data stored by IPS4 for each field in some very interesting ways - for example, you might take the value of an address field and use it to output an embedded Google Maps map, or even create some fields that you don't output, but instead use to control the layout of your record templates. There are a huge number of possibilities. Permissions There's multiple levels of permissions at play with databases: Page-level Since pages have their own permission settings, if the user doesn't have permission to see the page, they won't be able to see the database either. Database-level Permissions can be set at a database-level, which forms the default permissions for categories in the database too. Category-level A category can optionally override the database-level permissions and provide its own. This is useful for hidden categories - perhaps staff only, or a category only for premium members. Managing Databases Databases are managed by going to Pages -> Databases in the AdminCP. You'll also find that databases are listed in the Pages menu in the AdminCP for quicker access. From this screen, you'll see some simple information about each of your databases, as well as menu items to manage each part: Managing Database Records can be added either via the AdminCP (click the icon) or via the front-end page that displays the database. This means users don't need AdminCP access to add/edit records. Creating Databases To create a database, click the Create New button on the screen above. There's a number of configuration options available to you. Details The basic settings for this database. At the bottom of this tab, you can choose the templates you want to use for this database. If you haven't created them yet, you always do this later. Language On the Language tab, you set up the words that will be used to refer to records in this database (rather than the generic 'records' terminology). For example, if you are creating a database for guides, these language choices will mean IPS4 will refer to your database in context, such as "Search Guides", "There are 5 guides in this database" and "Create a new guide". Options This tab more finely controls how your database will work, including comments, reviews, tagging, and the 'wiki-style' editing we covered earlier. Sorting options are also available here, allowing you to choose the order of records, and more importantly, the field on which they are sorted. For example, if you had a database containing records about dinosaurs, you may want to sort the records by Era (a custom field you will have created). You can return to this tag after creating your fields later to configure it. Forum This tab configures the aforementioned Forums integration for the database (individual categories can override these settings too). Page Since a database requires a page in which it displays, you can easily create one here as part of the database creation process. Alternatively, you can add it to one of your existing pages later. Adding to a Page If you don't create a page as part of the database creation process (above), you can do so manually by using a special database tag in your page content. On the Details tab of the database form, you specified a database key. This is how this database is included in pages. If the key is my-database, you'd insert it into a page by placing this: {database="my-database"} As mentioned above, a database can only exist on one page at a time; trying to use this tag on multiple pages won't work correctly.
  21. Templates, CSS and JS files are the means by which coders can take the default output of the various parts of Pages, and customize them to build unique parts of their site. Note: only those familiar with HTML, CSS and some PHP should consider modifying their templates. Since they involve editing code, it's easy to break the output of your site unintentionally! Types of template in Pages Templates are the key to customizing areas of your Pages website so that they're unique to your site and to your particular use of them. There's three primary types of template you can customize: Database templates Databases templates let you change the output of just about every part of your databases. There's four main types of template: Category Index The templates that render the category index of a database, and subcategories when browsing the record listing. Listing Templates that render the record listing Display Templates that render record view itself, including comments & reviews Form Templates for the add/edit form, allowing you to customize this form per-database Page templates By default, a page will use the suite wrapper, which includes the header, navigation, user bar etc. leaving your page content to control the actual content area. However, you can instead choose to use a custom page wrapper, allowing you to control the entire output of the page. Wrappers are created as page templates, and selected when you create your page. Block templates When you create a plugin block showing a feed of data, IPS4 uses a default template. You can however customize this template or create a new one, allowing each block to have a unique appearance. What can blocks contain? Blocks can contain the full range of IPS4 template syntax, which affords great flexibility. The data available to each template will depend on its type (for example, a database listing template will receive data from the database category, but a block template will receive data that matches the block's filters), but all templates can access the full underlying IPS4 PHP framework. Creating & Editing templates Database and page templates are managed in the Pages template editor, accessed by navigating to Pages -> Templates in the AdminCP and clicking the New button. Default block templates are also managed in the template editor as above, but can also be customized per-block within the block configuration themselves, by going to Pages -> Blocks in the AdminCP and then editing the block in question. CSS & JS files To fully customize sections created with Pages, it's often desirable to add custom CSS and Javascript. Pages offers a built-in way to write and use these resources and then assign them to a page for use. CSS and JS resources within Pages are managed by navigating to Pages -> Templates in the AdminCP, and then clicking the CSS or JS tabs in the editor. New resources can be added by clicking the New button. To assign them to a page, simply select them on the Includes tab when creating or editing a page in the AdminCP.
  22. What is a database? Databases are one of the most powerful and flexible features available in the Pages application. With some configuration and customization, they enable you to add complex, data-driven areas to your community, using some of the basic underlying functionality that full IPS4 applications have. Databases, as the name implies, are designed to hold data and display that to the user. This might be as simple as a table of records each containing a title and a body, from which you could make a very simple blog-like section, or it might be as complex as a completely custom interface backed by a large number of custom data fields specific to your needs - and the possibilities for this are endless. Features Searching Databases are searchable by default (although you can turn this off if desired). Each database is treated as a distinct area of your community, so on the search form, each database is listed as a first-class area to search, much like the Forums app for example. Core suite features Pages provides a range of core application features to databases that make even the simplest database feature-rich and well-integrated with your community from the outset. Commenting and writing reviews for records is available (although this can be disabled per-database). Users can also follow categories and records to be notified of new content wherever they are in the community. Social features such as reputation and sharing to other social networks is also built-in and available for records. Tagging and full moderation of records is also supported by default, and integrated across the suite as you'd expect. Wiki-style editing In terms of adding/editing records, databases in Pages behave much like you'd expect from our other applications; that is, when a user with permission creates a record, they 'own' it. However, databases have an option for wiki-style editing, whereby any user can edit records after they are created. This approach is great for community-curated content. Revisions Databases also support revisions for records. This means each time a record changes, the previous version is saved as a revision that can be accessed again later - you can also revert to an earlier revision if desired. Forum Integration Finally, databases has special integration with our Forums app. When posting a new record to a Pages database, IPS4 can optionally cross-post the record as a forum topic, to a category of your choosing. But it goes further - you can even use the forum topic as the comments for the record, rather than the standard commenting interface that records have. What does a database consist of? There's a few key components in a database to be aware of when creating one: The database itself Naturally, you need to create the database itself. This is where you configure options that affect that database as a whole, such as sorting, permissions, and so on. Categories If your databases uses categories (you can optionally choose not to), they add another level of structure and permissions. Fields We'll cover fields in more depth shortly, but you can create custom fields for all kinds of data that you might need for your database. IPS4 supports a wide range of field types, from simple text boxes up to YouTube embeds, upload fields and intra-database relationships. Templates Templates allow you to customize the output of the database. Default templates are supplied with IPS4, and if you aren't a coder, using these defaults allow you to get a database up and running quickly. For coders, however, customizing templates is the best way to build complex data-driven applications. Templates, CSS & JS
  23. Rikki

    Blocks

    What's a block? In Pages, a block is simply a section of reusable content. These can come from the applications in your community, be custom ones based on other built-in blocks, or be entirely custom. If you're comfortable with code, custom blocks allow you to achieve some complex results since the full range of IPS4 template logic is available to them. If you aren't comfortable with code, you can still create blocks based on built-in blocks using a simple wizard. Blocks can be inserted into your own custom pages, but also into any page generated by IPS4 (if the sidebar appears on the page, this can be done with drag and drop from the sidebar manager, or you can manually show the block by including its tag in IPS4's templates). What can blocks be used for? There's a huge range of uses for blocks. Custom blocks based on built-in blocks are a great way of highlighting worthwhile content in your community. For example, you could create a block showing a feed of the most popular topics in a particular forum you specify, and use the block to cross-promote that forum in related areas of your community. Custom blocks are larger in scope by their nature. They can range from simple HTML that you want to use in multiple places to reduce repetition, all the way to custom PHP that can be used to dynamically generate content. Common uses for custom blocks include providing a convenient way to insert Javascript in your page, building page headers with dynamic elements based on the page in which they're used, and including site notices in your community pages in an easy-to-edit way. Managing blocks Blocks are primarily managed in the AdminCP by going to Pages -> Blocks. Blocks can be organized into categories, although this has no impact on users - it's simply for ease of management via the AdminCP. Creating blocks Blocks are created from the same screen. When you click the Create New Block, you're prompted to choose between Plugin and Custom. Plugin A plugin block is one based on an existing data source from the IPS Community Suite, such as members, forum topics, upcoming calendar events and so forth. Although the data source provides the data, the block configuration you set up allows you to define filters to customize what subset of the data is shown. For example, you could create a feed of topics, from a particular forum, with more than 10 replies. Plugin blocks use a default template, but you can choose to override or customize this template with another. Custom A custom block can be created using a rich text editor, HTML (including IPS4's template logic), or pure PHP. Using blocks Using blocks On pages built using the Page Builder, blocks can be dragged and dropped into position by using the sidebar manager (the available areas on the page to receive the block will depend on the layout you chose when the page was created). On pages built using custom HTML (or if you want to insert the block in an IPS4 template), a special tag is used to insert the block: {block="block_id"} Block caching Custom blocks can optionally be cached, meaning the resources required to render the block are reduced. If your block is dynamic in any way (such as rendering differently based on the user viewing it, or the page it is being shown on), you should ensure that caching is not used. For plugin-based blocks, IPS4 handles the caching behind the scenes for you. Databases Part I
  24. Rikki

    Pages

    The foundation of Pages (the application) is the page (the thing). Tip To alleviate confusion in these tutorials, the application "Pages" will always be referred to with a capital letter; pages within the application or community will be lowercase. What is a page? A page is a container for content. Depending on your needs, a page can simply contain simple content (whether that's plain text, or rich content with images, embeds, and the other things you're used from our other applications), or more complex content like blocks and databases (see later steps to find out more about those). If you are comfortable with code, you can also use all of our standard template logic in a page, allowing for some highly custom results. For those who aren't comfortable with coding, there's an easy page builder, allowing you to drag and drop components into your page. A page has a URL, and can be automatically added to your menu navigation when you create it, if you wish. A page can also have custom permissions, allowing you to restrict who can or cannot access the page. This is great if you want to build special sections of your site, perhaps for staff or premium members only. Prior to Invision Community 4.3, Pages were not searchable (although external search engines such as Google will index them). However, if you have a database on a page, the content of the database will be searchable. Creating Pages Pages are created via the AdminCP, by navigating to Pages -> Pages. A directory listing of your current pages will be shown. Folders are supported here as you'd expect; the URL of the page will reflect this structure. For example, a page called index in a folder called guides will have the URL <your community URL>/guides/index. When you click the Add Page button, you are asked whether you want to use the Page Builder or Manual HTML. Page Type Page Builder After creating the page in the AdminCP, you'll be able to go to the front-end to add content to your page, using drag and drop from the sidebar manager. This option is best for those not familiar with HTML. Manual HTML When you choose this option, you'll be provided with a code editor to create your page. Within this code editor you're free to use HTML, as well as the full range of template logic supported by IPS4. With this method, you insert other items (blocks, databases etc.) into the page by using special tags. A sidebar on the editor show you the available tags. Managing content in pages with the drag and drop editor If you've created a page using the Page Builder options, after saving the configuration in the AdminCP, you can head over to the page on the front-end to manage its content (click the View Page option from the menu on the page listing screen to easily navigate to it). By default, the page will be empty. Click the sidebar toggle to open the sidebar and see the available widgets. All of the usual widgets are available to you from across the suite, but under the Pages category are a handful of special reusable widgets: Block Manager Of these, WYSIWYG Editor is the one you'd be most likely to use when setting up your pages. It gives you a standard IPS4 rich text editor to add content to the page. Simply drag it into a location on your page, then click the Edit button to access the editor. We won't cover the other blocks here since they are specific to other kinds of functionality within Pages. Managing content in pages using Manual HTML When you create a page using manual HTML, you can choose how much of the standard IPS4 wrapper you start with. By default, the suite wrapper is included in the output. This encompasses the header, menu navigation etc., meaning the content of your page is inserted inside this wrapper. With this option disabled, the suite wrapper isn't used - you'll be responsible for providing this (you can however choose a custom page wrapper you've created). If you use the suite wrapper, you can also choose whether the standard sidebar is included in the output. The content you enter into the code editor forms the main page content, and the sidebar will be managed as usual with drag and drop on the front-end. Adding to Navigation When you create a page, you can easily add it to your site's main navigation menu under the Menu tab on the page edit screen. Alternatively, you can add it to the menu manually via the normal menu management process. Setting as Default Often you will wish to set the pages application as your default application, so that you can show a page you created as your default homepage. For this, along with how to create a basic homepage, please refer to the following guide. Blocks
  25. Introduction Pages is our application that is designed to allow you to build custom areas of your community. These could range from custom reusable blocks to use in your sidebar, all the way up to complex applications built using our databases feature. Whereas our other applications provide a regimented (although customizable) front-end that's ready to use right after installation, Pages is a little different. Think of it more as a toolbox - it provides features that you use to build your own front-ends. Therefore, you need to put in some work once you start using it before you have anything to make live. This guide will cover the core concepts and features in Pages to help you get started. Who is Pages for? Pages has features for everyone! It has easy-to-use features like drag'n'drop page building, but under the hood, the entire IPS4 framework is available for power users to take advantage of. The wider your knowledge of HTML & PHP, the more advanced solutions you'll be able to develop, although no experience of HTML & PHP is required to get started with Pages! Pages
×
×
  • Create New...