{"id":23123030,"url":"https://github.com/devgeniem/dustpress","last_synced_at":"2025-04-09T13:11:13.062Z","repository":{"id":45876270,"uuid":"38493649","full_name":"devgeniem/dustpress","owner":"devgeniem","description":"A WordPress theme framework for writing templates with Dust.js templating engine and separate data models.","archived":false,"fork":false,"pushed_at":"2023-03-08T13:03:59.000Z","size":1012,"stargazers_count":53,"open_issues_count":31,"forks_count":24,"subscribers_count":31,"default_branch":"master","last_synced_at":"2025-03-30T15:02:55.833Z","etag":null,"topics":["dustjs","php-template-engine","wordpress","wordpress-theme-development","wordpress-theme-framework"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/devgeniem.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2015-07-03T13:19:00.000Z","updated_at":"2024-05-29T18:35:27.000Z","dependencies_parsed_at":"2024-06-18T15:23:49.475Z","dependency_job_id":null,"html_url":"https://github.com/devgeniem/dustpress","commit_stats":{"total_commits":585,"total_committers":29,"mean_commits":20.17241379310345,"dds":"0.46495726495726497","last_synced_commit":"5be360b6dae915d2483f83aad78c4641f817316a"},"previous_names":[],"tags_count":207,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devgeniem%2Fdustpress","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devgeniem%2Fdustpress/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devgeniem%2Fdustpress/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devgeniem%2Fdustpress/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/devgeniem","download_url":"https://codeload.github.com/devgeniem/dustpress/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247947842,"owners_count":21023065,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dustjs","php-template-engine","wordpress","wordpress-theme-development","wordpress-theme-framework"],"created_at":"2024-12-17T07:32:05.954Z","updated_at":"2025-04-09T13:11:13.038Z","avatar_url":"https://github.com/devgeniem.png","language":"PHP","readme":"![geniem-github-banner](https://cloud.githubusercontent.com/assets/5691777/14319886/9ae46166-fc1b-11e5-9630-d60aa3dc4f9e.png)\n\n# DustPress\n- Contributors: [devgeniem](https://github.com/devgeniem) / [Nomafin](https://github.com/Nomafin), [villesiltala](https://github.com/villesiltala)\n- URL: http://www.dustpress.com\n- Tags: dustpress, wordpress, dustjs, dust.js\n- Requires at least: 4.2.0\n- Tested up to: 4.9.0\n- License: GPLv2 or later\n- License URI: http://www.gnu.org/licenses/gpl-2.0.html\n\n## Table of contents\n- [DustPress](#dustpress)\n  - [Table of contents](#table-of-contents)\n  - [Description](#description)\n  - [Installation](#installation)\n    - [Composer](#composer)\n    - [Manually](#manually)\n  - [External resources](#external-resources)\n  - [Usage](#usage)\n  - [File naming and locations](#file-naming-and-locations)\n    - [Data models](#data-models)\n    - [Views](#views)\n  - [Data models](#data-models-1)\n    - [Autoconstructing and modular usage](#autoconstructing-and-modular-usage)\n    - [Binding the data](#binding-the-data)\n      - [Submodels](#submodels)\n      - [Binding the data](#binding-the-data-1)\n      - [Reserved model names](#reserved-model-names)\n        - [WP](#wp)\n  - [Caching](#caching)\n    - [Method caching](#method-caching)\n    - [Partial and end-result caching](#partial-and-end-result-caching)\n      - [Partial caching](#partial-caching)\n      - [End-result caching](#end-result-caching)\n    - [Menu caching](#menu-caching)\n      - [Configuration constants](#configuration-constants)\n  - [Dust templates](#dust-templates)\n  - [DustPress Helpers](#dustpress-helpers)\n    - [contains](#contains)\n    - [content](#content)\n    - [image](#image)\n    - [menu](#menu)\n    - [pagination](#pagination)\n      - [Page link formatting](#page-link-formatting)\n        - [Formatting example:](#formatting-example)\n    - [password](#password)\n    - [permalink](#permalink)\n    - [s](#s)\n    - [sep](#sep)\n    - [set and unset](#set-and-unset)\n    - [strtodate](#strtodate)\n    - [title](#title)\n    - [wpfooter](#wpfooter)\n    - [wphead](#wphead)\n  - [Escaping filters](#escaping-filters)\n    - [Add custom filters](#add-custom-filters)\n  - [Other functionality](#other-functionality)\n    - [do_not_render](#do_not_render)\n    - [json output](#json-output)\n    - [Custom routes](#custom-routes)\n- [Additional Classes](#additional-classes)\n  - [\\DustPress\\Query](#dustpressquery)\n    - [Querying single posts](#querying-single-posts)\n      - [get_post()](#get_post)\n      - [get_acf_post()](#get_acf_post)\n    - [Querying multiple posts](#querying-multiple-posts)\n      - [get_posts()](#get_posts)\n        - [Example usage](#example-usage)\n      - [get_acf_posts()](#get_acf_posts)\n- [Plugins](#plugins)\n  - [Debugger](#debugger)\n  - [DustPress.js](#dustpressjs)\n  - [Comments helper](#comments-helper)\n- [Overriding default templates](#overriding-default-templates)\n\n## Description\n\nA WordPress theme framework for writing template files with Dust.js templating engine and separate data models.\n\n## Installation\n\nWe recommend that you install DustPress with Composer, but it is also possible to do it manually.\n\n### Composer\nInstall with composer:\n\n```\n$ composer require devgeniem/dustpress\n```\n\nOR add it into your `composer.json`:\n\n```json\n{\n  \"require\": {\n    \"devgeniem/dustpress\": \"*\"\n  }\n}\n```\n\nDustPress supports Composer's autoload feature. If you have it enabled, you don't have to do anything else to use DustPress. If not, you need to require `dustpress.php` in your `functions.php`.\n\n### Manually\n\n- Clone this repository somewhere in your project and require the `dustpress.php` file in your `functions.php`.\n\n## External resources\n\nThere are several other repositories that contain DustPress material as well:\n\n- [DustPress Starter Theme](https://github.com/devgeniem/dustpress-starter-theme) - a basic starter theme to use as a boilerplate\n- [DustPress Debugger](https://github.com/devgeniem/dustpress-debugger) - a handy plugin to see the data you have sent to the view\n- [DustPress.js](https://github.com/devgeniem/dustpress-js) - a plugin that provides a JavaScript library to unleash the magical powers of DustPress on the front-end as well\n- [DustPress Comments Helper](https://github.com/devgeniem/dustpress-comments) - a DustPress helper to easily implement commenting on your theme\n\n## Usage\n\nYou need to call `dustpress();` in your `functions.php` to enable DustPress. It must naturally be done after requiring the library itself if you haven't used Composer's autoload feature.\n\nWithin your theme there must be two directories called `models` and `partials` to use DustPress. Their purpose will be explained later in this file.\n\nThe basics of using DustPress are very simple. Unlike traditional WordPress theme development, DustPress relies on MVVM, or Model View ViewModel architecture in which fetching data and displaying it to the user are separated into different modules.\n\n## File naming and locations\n\n### Data models\n\nEven though implementing an almost completely new development pattern to WordPress theme developers, DustPress still uses some of the WordPress core functions. The naming of the data models and view partials follow the naming conventions of traditional WordPress themes. The model for a single post should be named `single.php` etc.\n\nIn WordPress, your custom page templates could be named pretty much anything as long as you declare the name of the template in the comment section in the beginning of the file. This is the case in DustPress too, but the class name that you write for the model should follow a certain pattern. For example if you have a `Frontpage` template with a filename `page-frontpage.php`, your class should be named PageFrontpage. The class names are case sensitive. The same goes with custom content type singles, where a single `person` file should be named `single-person.php` and the class accordingly `SinglePerson`.\n\nYou still have to declare a name for the templates in the starting comment as you would have done in a traditional WordPress theme as well. This allows user to choose the template file to use with the page and points the DustPress core\nto load the correct model when loading the page.\n\nThe models must be located in the `models` directory. They could, however, be arranged in any kind of subdirectory tree, so feel free to keep them in whatever structure you like. Note that WordPress also needs to find your template file in order it to work.\n\n### Views\n\nThe Dust templates are the views of our design pattern. DustPress uses Geniem's fork of [DustPHP](https://github.com/devgeniem/dust-php) library for parsing the Dust templates.\n\nAll the data gathered and returned by the public functions of your models are automatically passed to the view. DustPress looks for the Dust templates in the `partials` directory under the root of the theme. Like models, they could be arranged in any kind of subdirectory hierarchy, so feel free to use whatever suits your needs.\n\nBy default the Dust template files follow the naming of the models. `single.php` should be paired with `single.dust`. This naming convention can be overwritten in your model by calling the `set_template()` function. In any of the public functions of the model write `$this-\u003eset_template(\"partial_name\")` and it will be used instead of the default template. The `.dust` file extension is not needed.\n\n## Data models\n\nThe data models of DustPress consist of a class named the same as the file but in CamelCase instead of hyphens. `page-frontpage.php` should have a class named `PageFrontpage` that extends the `\\DustPress\\Model` class:\n\n```\n\u003c?php\n/*\nTemplate name: Frontpage\n*/\n\nclass PageFrontpage extends \\DustPress\\Model {\n  //\n}\n?\u003e\n```\n\n### Autoconstructing and modular usage\n\nAs described above DustPress automatically locates your main model following the WordPress theme naming conventions and structure. The main model is loaded and constructed automatically. Lots of good stuff happen behind the scenes in the `__construct` method of the `Model` class. _Do not overwrite it without calling `parent::__construct();` in the beginning of your own constructor._\n\nAlongside the autoloading you can use DustPress models in any modular use case you can come up with. One example would be building a custom API in a file called `api.php` containing a `Model` extending class called `API` _(no need to follow the naming convention since the class is not autoconstructed)_ running all kinds of public functions rendering multiple custom templates. Yes, with DustPress you can do Dust rendering anywhere within your WordPress project! [(see the example)]() [(power up your API with DustPressJS)]()\n\n### Binding the data\n\nDustPress has its own global data object that is passed to the view when everything is done and it is time to render the page. Binding data to the object is done via the `return` statements in publicly accessible functions. While autoloading the main model and its submodels, all public functions will automatically be run. If you have data you want to load inside a function and do not want to include it into the global data object, set the visibility of a function to `private` or `protected`.\n\n```\npublic function last_posts() {\n    $args = [ 'posts_per_page' =\u003e 3 ];\n    return get_posts( $args );\n}\n```\n\nDustPress data object holds a variety of other objects that are user defined models. For example if you have a frontpage with a header, a content block, a sidebar and a footer, the data object would look like this:\n\n```\nobject(stdClass)#1 (5) {\n  [\"PageFrontpage\"]=\u003e\n  object(stdClass)#2 (0) {\n  }\n  [\"Header\"]=\u003e\n  object(stdClass)#2 (0) {\n  }\n  [\"Sidebar\"]=\u003e\n  object(stdClass)#2 (0) {\n  }\n  [\"Footer\"]=\u003e\n  object(stdClass)#2 (0) {\n  }\n}\n```\n\n#### Submodels\n\nRecurring elements like headers or footers should be created as submodels that can be included in any page. Submodels have their own models which are located in their own files inside the `models` directory. They are attached to the main model with the aforementioned `bind_sub()` method. The frontpage model could look like this:\n\n```\n\u003c?php\n/*\nTemplate name: Frontpage\n*/\n\nclass PageFrontpage extends \\DustPress\\Model {\n\n  public function init() {\n    $this-\u003ebind_sub(\"Header\");\n    $this-\u003ebind_sub(\"Sidebar\");\n    $this-\u003ebind_sub(\"Footer\");\n  }\n}\n?\u003e\n```\n\nThis code fetches all three models and binds their data to the global data hierarchy under the corresponding object. Notice that we have created a public function `init` which is automatically run by DustPress and therefore the submodels will be included. No `init` block will be created in the data tree since we do not return anything in our function.\n\nSubmodel bindings can be run anywhere in the model for example inside an `if` statement. Submodels work recursively, hence submodels can bind more submodels.\n\n`bind_sub()` can also take a second parameter, an array of arguments to be passed to the submodel. It is then accessible in the submodel globally by calling `$this-\u003eget_args()`.\n\n#### Binding the data\n\nThe actual passing of the data to inside the methods happens via user defined functions. DustPress runs through all public methods in the model and puts their return data to the global data object under current model's branch of the tree. It goes in a object named after the method.\n\n```\npublic function SomeData() {\n  return \"This is data.\";\n}\n```\n\nIf this code is located in our PageFrontpage class, the result in the data object would be as follows:\n\n```\nobject(stdClass)#1 (5) {\n  [\"PageFrontpage\"]=\u003e\n    array(1) {\n      [\"SomeData\"]=\u003e\n      string(13) \"This is data.\"\n    }\n  }\n  [\"Header\"]=\u003e\n  object(stdClass)#2 (0) {\n  }\n  [\"Sidebar\"]=\u003e\n  object(stdClass)#2 (0) {\n  }\n  [\"Footer\"]=\u003e\n  object(stdClass)#2 (0) {\n  }\n}\n```\n\nThere is also a function called `bind` within the model class. If you want to bind multiple data blocks inside one method, you can use it like so:\n\n```\npublic function SomeMethod() {\n  $data = \"This is another piece of data.\";\n\n  $this-\u003ebind( $data, \"SomethingElse\" );\n}\n```\n\nThe result would be as follows:\n\n```\nobject(stdClass)#1 (5) {\n  [\"PageFrontpage\"]=\u003e\n    array(1) {\n      [\"SomethingElse\"]=\u003e\n      string(13) \"This is another piece of data.\"\n    }\n  }\n  [\"Header\"]=\u003e\n  object(stdClass)#2 (0) {\n  }\n  [\"Sidebar\"]=\u003e\n  object(stdClass)#2 (0) {\n  }\n  [\"Footer\"]=\u003e\n  object(stdClass)#2 (0) {\n  }\n}\n```\n\n`bind` can also take a third parameter to create a new primary data block:\n\n```\npublic function SomeMethod() {\n  $data = \"This is yet another piece of data.\";\n\n  $this-\u003ebind( $data, \"Method\", \"PrimaryBlock\" );\n}\n```\n\nThe result would be as follows:\n\n```\nobject(stdClass)#1 (5) {\n  [\"PageFrontpage\"]=\u003e\n  object(stdClass)#2 (0) {\n  }\n  [\"PrimaryBlock\"] =\u003e\n  array(1) {\n    [\"SomethingElse\"]=\u003e\n    string(13) \"This is yet another piece of data.\"\n  }\n  [\"Header\"]=\u003e\n  object(stdClass)#2 (0) {\n  }\n  [\"Sidebar\"]=\u003e\n  object(stdClass)#2 (0) {\n  }\n  [\"Footer\"]=\u003e\n  object(stdClass)#2 (0) {\n  }\n}\n```\n\n#### Reserved model names\n\n##### WP\n\nWP is reserved for the essential WordPress data that is accessible in any template all the time. It is stored in the root of the data object with the key `WP` and it contains all the fields that WordPress' native `get_bloginfo()`\nwould return.\n\nIt also contains information about the current user in WP-\u003euser and a true/false boolean if the user is logged in in WP-\u003eloggedin.\n\nContents of the `wp_head()` and `wp_footer()` functions are available for use in helpers {@wphead /} and {@wpfooter /} respectively. They should be inserted in the corresponding places in your template file.\n\n```\n{@wphead /}\n```\n\n## Caching\n\n### Method caching\n\nDustPress has a native support of WordPress transient cache. It works on a method basis, so you can give different methods different TTLs and they get cached automatically.\n\nBy default the method caching is disabled. It can be enabled via a filter in your functions.php as follows:\n\n```\nadd_filter( \"dustpress/settings/cache\", \"__return_true\" );\n```\n\nHowever, that setting itself doesn't do anything. You also have to define TTLs for the methods you want to cache. TTL is an abbreviation for Time To Live. It defines the time that a method's cache is alive before it needs to be renewed, i.e. when it's code gets run again.\n\nThe TTLs are set in an associative array in a public property of the model called `$ttl` as follows:\n\n```\nclass Model extends \\DustPress\\Model {\n  public $ttl = [\n    \"Method\" =\u003e 60\n  ];\n\n  public function Method() {\n    // something\n\n    return $data;\n  }\n}\n```\n\nIn the example, the data `Method` returns gets cached for 60 seconds. Caching works on [DustPress.js](https://github.com/devgeniem/dustpress-js) requests as well.\n\n### Partial and end-result caching\n\nDustPress also uses WordPress object cache for its partials and even the end-result HTML caching. Unlike method caching, these features are enabled by default and you have to disable them if you don't want to use them.\n\n#### Partial caching\n\nPartial caching caches the compiled version of the Dust templates so that they don't have to be compiled from scratch every time the page loads as it is relatively heavy operation in a large scale.\n\nThe partials are cached forever, which means that whenever you change them, you probably want to run `wp_cache_flush()` or the corresponding WP CLI command so that they get updated.\n\nYou may want to turn the caching off totally while you are in your development environment. That happens, unexpectedly, via a filter as follows:\n\n```\nadd_filter( \"dustpress/cache/partials\", \"__return_false\" );\n```\n\nYou can also turn the partial caching off only for certain partials:\n\n```\nadd_filter( \"dustpress/cache/partials/partial_name\", \"__return_false\" );\n```\n\n#### End-result caching\n\nDustPress also caches the resulting HTML that gets served to the end-user. It generates the cache keys with both the data and the partial used to render the HTML, so that cache updates every time the data changes. It is used only to save the time that DustPHP would use to render the template with the data. With more complicated templates the operation can take time, so it is recommended to keep the cache on at least in production.\n\nYou can turn the end-result caching off in the same way as you would the partial caching:\n\n```\nadd_filter( \"dustpress/cache/rendered\", \"__return_false\" );\n```\n\n**Note!** When enabled, [DustPress Debugger](https://github.com/devgeniem/dustpress-debugger) turns both partial and end-result caching off.\n\n### Menu caching\n\nThe menu helper uses [WP Object Cache](https://codex.wordpress.org/Class_Reference/WP_Object_Cache) to cache loaded menu items for each menu. The menu id (more specifically the term id of the menu) is used to build the cache key. No cache group is used. The menu helper also handles deleting the cache automatically when the menu is updated. To enable persistent caching you must install some third party plugin for it, for example [Redis Object Cache for WordPress](https://github.com/devgeniem/wp-redis-object-cache-dropin/).\n\n#### Configuration constants\n\nYou can define the following PHP constants to control menu helper caching:\n\n- `DUSTPRESS_MENU_HELPER_CACHE_EXPIRE : int`: The menu items cache expiration time in seconds. The default value used is 15 minutes (900) if the constant is not set.\n- `DUSTPRESS_MENU_HELPER_CACHE_DISABLE : bool`: Set to `true` if you want to disable caching. The cache is enabled by default.\n\n## Dust templates\n\nDustPHP templates are 100% compatible with Dust.js templates. See the official [Dust.js website](http://www.dustjs.com/) for documentation or the [LinkedIn Dust Tutorial](https://github.com/linkedin/dustjs/wiki/Dust-Tutorial).\n\nAll templates should have a context block with the name of the current model, so that the variables are usable in the template. As for our previous example model, very simplified template could look like this:\n\n```\n{\u003e\"shared/header\" /}\n\n{#PageFrontpage}\n  \u003ch1\u003e{WP.name}\u003c/h1\u003e\n  \u003ch2\u003e{WP.description}\u003c/h2\u003e\n\n  \u003cp\u003e{SomeString}\u003c/p\u003e\n\n  {SomeHTML|s}\n\n  {\"\u003eshared/sidebar\" /}\n{/PageFrontpage}\n\n{\u003e\"shared/footer\" /}\n```\n\nThis template includes header.dust, sidebar.dust and footer.dust templates from `partials/shared/` subdirectory. At the end of the `PageFrontpage` block we echo HTML from the `SomeHTML` variable and use the `s` filter to get it _unescaped_.  See the [Dust Tutorial](https://github.com/linkedin/dustjs/wiki/Dust-Tutorial#Sections_and_Context) for more information about sections and contexts.\n\n## DustPress Helpers\n\nHelpers extend the Dust.js templating language with more complex functionality than just data inserting (see: [Context Helpers](http://www.dustjs.com/guides/context-helpers/), [Dust Helpers](http://www.dustjs.com/guides/dust-helpers/)). With DustPress you can use all Dust.js Helpers within your Dust templates. We have also taken it a bit further and included some nice bits for you to use. As mentioned above there are helpers for echoing header and footer data into your templates but here is a complete list of helpers included with DustPress:\n\n### contains\n\n`contains` is a conditional helper. It can be used to determine if an array contains wanted item, so it works like PHP's `in_array`. It can also have an else condition.\n\nExample:\n```\n{@contains key=haystack value=\"needle\"}\n  Found it! :)\n{:else}\n  Didn't find it. :(\n{/contains}\n```\n\n### content\n\n`content` helper has two functions. If it is run without parameters, it emulates the use of WordPress' native `the_content()` function and displays the current post's content.\n\nIt can also be given a parameter `data` (`{@content data=string /}`). It then behaves like you would run `apply_filters( 'the_content', $string )`.\n\nExample:\n```\n{@content data=fields.some_content.value /}\n```\n\n### image\n\nThe `image` helper returns a markup for `img` tags with proper `srcset` and `sizes` attributes for responsive use. The full readme for the helper can be found [here](docs/helpers/image.md).\n\n### menu\n\n`menu` helper does what it name suggests: it creates a menu. It has several parameters that are explained below:\n\n- `menu_id` / `menu_name`: Defines the menu to show. Either is mandatory.\n- `depth`: Only show submenus to the level this parameter defines. Defaults to infinite (or PHP_INT_MAX, really).\n- `parent`: If parent parameter is defined, the menu features only the subpages of a post with the parameter's value as its ID. Defaults to 0 - all items will be shown.\n- `override`: With this parameter you can override what menu item (post ID) will be shown as active (WordPress' default current-menu-item class). Defaults to current post's ID.\n- `ul_classes`: Classes that are given to the `ul` element separated with spaces. Per default this is empty.\n- `ul_id`: ID that is given to the `ul` element. Per default this is empty.\n- `show_submenu`: A boolean value if submenus are shown or not. Defaults to true.\n- `menu_partial`: Use another partial instead of the default `menu.dust`. You can use a custom partial by creating your own `menu.dust` inside your theme, and DustPress will use that instead of the one from its core.\n- `menuitem_partial`: Use another partial instead of the default `menuitem.dust`. You can use a custom partial by creating your own `menuitem.dust` inside your theme, and DustPress will use that instead of the one from its core.\n- `data`: Custom data object to be passed to the menu template. Can be used under `{data}` in `menu.dust` or `menuitem.dust`.\n\nExample:\n```\n{@menu menu_name=\"main-menu\" ul_id=\"main-menu\" ul_classes=\"menu primary-menu\" show_submenu=false /}\n```\n\n### pagination\n\n`pagination` helper prints out a basic pagination for your template. It takes the data from your model as parameters and calculates the number of pages based on the `per_page` value. It then prints out an `ul` element containing the page links with the corresponding page as a query parameter. The helper accepts the following parameters:\n\n- `page`: The current active page. Defaults to `1`.\n- `per_page`: The number of items a single page should have.\n- `items`: The amount of items in your data set. For example this could be the post count.\n- `page_var`: The query parameter for the pagination links. The default is `paged`.\n- `hash`: The hash link to be added at the end of the pagination links.\n- `neighbours`: How many page numbers to display to either side of the current page. Defaults to 3.\n- `strings`: An array of strings to translate and manipulate the texts in the navigational links. Pass the following array keys with translated values:\n  - `previous`\n  - `next`\n  - `start`\n  - `end`\n\nExample:\n\n```\n{@pagination page=current_page per_page=10 items=item_count page_var=\"paged\" hash=\"posts-section-id\" /}\n```\n#### Page link formatting\n\nThe helper defaults to the following page link format: `{page_link}{last_page}{hash}`. Without any customizing to WordPress url handling the links are outputted as `https://domain.com/something/?paged=2`.  To alter the link formatting filter the `page_link` value with the `dustpress/pagination/page_link` filter and replace the `pagination.dust` with your custom template in you theme.\n\n##### Formatting example:\n\n```php\n/**\n * Change the pagination link format from '?paged=' to 'page/'.\n * This works only on pages with no other GET parameters.\n */\ncustom_pagination_link( $page_link ) {\n    return str_replace( '?paged=', 'page/', $page_link );\n}\nadd_filter( 'dustpress/pagination/page_link', 'custom_pagination_link' );\n```\n\n### password\n\n`password` implements WordPress' native password protection functionality. It takes one parameter, `id`, that tells it what post's password to require. It defaults to current post's id.\n\nPut your password protected content inside the `password` context and it will be replaced with a password form until the correct password has been given.\n\nExample:\n```\n{@password}\n  \u003cp\u003eThis content is password protected.\u003c/p\u003e\n{/password}\n```\n\nUnlike native WordPress, where the form's layout can be changed via filter, you can override it with your own Dust template. The default template can be found in DustPress core under `partials/` directory. Copy it somewhere under your theme's `partials/` directory and make the modifications you want to.\n\n### permalink\n\n`permalink` helper emulates WordPress' native `get_permalink()` function. It takes one parameter, `id`, that tells it what post's permalink to give. It defaults to current post's id.\n\nExample:\n```\n{@permalink id=featured_post.ID /}\n```\n\n### s\n\n`s` helper emulates WordPress' native `__` or `_x` functions and it is used in internationalization. It takes one to three parameters: `s` that is the string to be translated and the only required parameter. `td` is the text domain and `x` is the context to provide with the translation.\n\nNote that the use of `s` helper does not bring the string available to for example WPML's string scanning function.\n\nExample:\n```\n{@s s=\"Home page\" td=\"my-page\" /}\n```\n\nYou can use the [translation parser script](https://github.com/devgeniem/dustpress/wiki/Translation-parser) to find all strings defined with {@s} and to write them to a file in a format that can be scanned with POedit.\n\n### sep\n\n`sep` helper is an extension to Dust's native `sep` helper. It behaves the same, but it can also be given two extra parameters: `start` and `end`. They work as the offsets of the function. By default `start` is 0 and `end` is 1.\n\nExample:\n```\nThe participants are {#names}{@last}and {/last}{.}{@sep start=0 end=2}, {/sep}{/names}\n```\n\nThe result could be:\n```\nThe participants are Bob, Samantha, Michael and Alice.\n```\n\n### set and unset\n\n`set` helper can be used to set and alter the data tree in the Dust template. You can create your own variables and assign them values either hardcoded or from the data. You can also perform several mathematic operations on them.\n\nParameters\n- `key`: This parameter is required. It is the name of the variable you want to create or alter.\n- `value`: If you want to create a new variable or overwrite a value of another one, give the value you want to give it here.\n- `add`: Used to perform mathematical _add_ operation to the value of the variable.\n- `subtract`: Used to perform mathematical _subtract_ operation to the value of the variable.\n- `multiply`: Used to perform mathematical _multiply_ operation to the value of the variable.\n- `divide`: Used to perform mathematical _divide_ operation to the value of the variable.\n- `mod`: Used to perform mathematical _modulo_ operation to the value of the variable.\n\nExamples:\n```\n{@set key=\"my_variable\" value=\"some_value\" /}\n{@set key=\"another_variable\" value=path.to.data /}\n{@set key=\"saved_index\" value=$idx /}\n{@set key=\"counter\" add=1 /}\n```\n\n`unset` is used to unset a variable. Usage:\n```\n{@unset key=\"my_variable\" /}\n```\n\n### strtodate\n\n`strtodate` formats the date it is given to a format it is given. It takes three parameters: `value`, `format` and `now`. The function emulates the behaviour of a PHP code: `date( $format, strtotime( $value, $now ) )`.\n\nIf no format parameter is given, the default date format for the site will be used ( `get_option( 'date_format' )`).\n\nExample:\n```\n{@strtodate value=post_date format=\"d.m.Y H:i:s\" /}\n```\n\n### title\n\n`Title` works as a proxy for WordPress' native `the_title()` function.\n\nExample:\n```\n{@title /}\n```\n\n### wpfooter\n\n`wpfooter` works as a proxy for WordPress' native `wpfooter()` function.\n\nExample:\n```\n{@wpfooter /}\n```\n\n### wphead\n\n`wphead` works as a proxy for WordPress' native `wphead()` function.\n\nExample:\n```\n{@wphead /}\n```\n\n## Escaping filters\n- `kses`\n  - Uses WordPress function `wp_kses_post()`\n- `attr`\n  - Uses WordPress function `esc_attr()`\n- `html`\n  - Uses WordPress function `esc_html()`\n- `url`\n  - Uses WordPress function `esc_url()`\n\nExample usage:\n```dust\n\u003ca href=\"{permalink|url}\"\u003eLink text\u003c/a\u003e\n```\n### Add custom filters\n- You can add custom filters via `dustpress/filters` filter.\n\n## Other functionality\n\n### do_not_render\n\nIf you do not want the DustPress to render the page automatically but would rather do it yourself, you can call `$this-\u003edo_not_render()` anywhere in your model or submodels. In that case DustPress populates the data object, but leaves the\nrendering for the developer.\n\nDustPress render function is declared public and is thus usable anywhere. It takes an array of arguments as its parameter. Only mandatory argument is `partial` that contains the name, filename or path to the wanted partial.\n\nWith only the partial defined, DustPress passes its global data object to the template. That can be changed by giving it another parameter `data` that would then be passed to the template.\n\nThere is also a parameter `type` that defines the format the data would be rendered in. By default it is `html`, but `json` is also a possibility. You can write your own render format functions as well. That feature will be documented later, sorry for that.\n\nThe last but not the least of the parameters is `echo` that takes a boolean value. By default it is set to true, so the render function echoes the output straight to browser. If it is false, it is returned as a string. Here is an example usage of the render function:\n\n_in some function_\n```\n$output = dustpress()-\u003erender( [\n  \"partial\"   =\u003e 'my_custom_template',\n  \"data\"    =\u003e [\n      'some_number' =\u003e 1,\n      'some_string' =\u003e 'ABC',\n  ],\n  \"type\"    =\u003e \"html\",\n  \"echo\"    =\u003e false\n]);\n\necho $output;\n```\n\n_my_custom_template.dust_\n```\n\u003cul\u003e\n    \u003cli\u003eMy number: {some_number}\u003c/li\u003e\n    \u003cli\u003eMy string: {some_string}\u003c/li\u003e\n\u003c/ul\u003e\n```\n_the echoed output_\n```\n\u003cul\u003e\n    \u003cli\u003eMy number: 1\u003c/li\u003e\n    \u003cli\u003eMy string: ABC\u003c/li\u003e\n\u003cul\u003e\n```\n\n### json output\n\nDustPress can output its data as JSON instead of the rendered version if the developer enables the functionality. It is done by adding one or both of following filters into your `functions.php`:\n\n```\nadd_filter( 'dustpress/settings/json_url', '__return_true' );\n\nadd_filter( 'dustpress/settings/json_headers', '__return_true' );\n```\n\nThe former enables JSON output when query parameter `?JSON` is added to the url, and the latter when HTTP header `Accept: application/json` is present on the request.\n\n### Custom routes\n\nWith DustPress, you can define custom routes easily to be used outside the WordPress post context. If you have a custom page you need to show, but don't want to create an admin page for it, custom routes are the way to go.\n\n```\ndustpress()-\u003eregister_custom_route( 'custom/route', 'MyModel' ); \n```\n\nThe above code registers `custom/route` url to be mapped with `MyModel`. DustPress automatically passes all parameters from the url to the model as arguments, so for example url `custom/route/custom_parameter/1` still uses the `MyModel` model and the parameters are available with `$this-\u003eget_args()` for the developer.\n\nThe routes should be defined before `init` hook is run, so just register them directly within the `functions.php` scope.\n\n**Note!** Remember to flush WordPress rewrites after registering them in your `functions.php`. You can do it either in the code, with the WP CLI or just visiting the Permalinks options page in the admin.\n\n# Additional Classes\n\n## \\DustPress\\Query\n\n`\\DustPress\\Query` is a class that contains a few helper functions for common WordPress tasks. The main functionality of this class is customized post querying with the ability to bind basic WordPress metadata to the queried post objects. With a single function call you can get all the meta needed in your Dust template. It also supports customized data fetching for **[Advanced Custom Fields](https://www.advancedcustomfields.com/) (ACF)** field group data in your post objects.\n\n### Querying single posts\n\n#### get_post()\n\nWith `\\DustPress\\Query` you can query single WordPress posts with two different functions. The `get_post()` function accepts the following parameters:\n* id: The id of the post.\n* args: Arguments in an array.\n\nThe argument key `meta_keys` is used to query postmeta. It defaults to  `'null'` and no postmeta is loaded by default. Set the value to `'all'` to fetch all the postmeta rows. You can query single meta keys by passing an array of strings corresponding to keys in postmeta.\n\nThe argument key `'single'` is described in WordPress [documentation](https://codex.wordpress.org/Function_Reference/get_metadata) for `get_metadata()` and it defines the return type of the found meta rows. Postmeta is appended to the queried post object with the key `meta`. Found metadata is returned in an associative array with found meta values mapped by the meta keys. The metadata is  the key `meta` under the value returned by the function.\n\nThe required return type can be defined similarly to WordPress' `get_post` function. Set the `output` argument key with one of `'OBJECT'`, `'ARRAY_A'`, or `'ARRAY_N'`, which correspond to a [WP_Post](https://developer.wordpress.org/reference/classes/wp_post/) object, an associative array, or a numeric array, respectively. If no matching post with the passed id is found, `false` is returned.\n\n```\n$args = [\n  'meta_keys' =\u003e [\n    'my_meta_key_1',\n    'my_meta_key_2'\n  ],\n  'single' =\u003e true\n];\n\\DustPress\\Query::get_post( get_the_ID(), $args );\n```\n\nThe returned [WordPress post object](https://codex.wordpress.org/Class_Reference/WP_Post) is extended with the following keys:\n\n- `permalink` - The post permalink.\n- `image_id` - If a featured image is set for the post, this is the id of it.\n\n#### get_acf_post()\n\nThis function extends the `get_post()` function with automatic loading of ACF field group data. Fields are loaded with the ACF function [`get_fields`](https://www.advancedcustomfields.com/resources/get_fields/) and are returned to the post object under the key `fields`. This function accepts the same arguments as the `get_post()` function and also the argument key `whole_fields`. With this argument set to `true`, this function returns the field group data as seen in the field group edit screen.\n\nThis function has a recursive operation. If the argument with the key `max_recursion_level` is set with an integer value, ACF fields with relational post object data are loaded recursively with full meta and field group data. The level indicates how many levels of related articles are loaded as full post objects with ACF fields included. This recursion also works within the first level of an ACF repeater field.\n\n*Use the recursion with caution. Levels greater than `1` might cause an increase in page load times.*\n\n```\n$args = [\n  'meta_keys' =\u003e null,\n  'single'    =\u003e true\n];\n\\DustPress\\Query::get_acf_post( get_the_ID(), $args );\n```\n\n### Querying multiple posts\n\n#### get_posts()\n\nThis function will query multiple posts based on given arguments. Post objects are queried with the WordPress `WP_Query` class. This function accepts the following arguments:\n\n* All the arguments described in the WordPress codex for the `get_posts`function: https://codex.wordpress.org/Function_Reference/get_posts\n* meta_keys: This functionality is [described](#get_post) in the `get_post()` function and it it applyed for each found post.\n* query_object: If set to `false`, the function returns the posts in an array. If set to `true`, the function returns an object which specifications are described below. Defaults to `false`.\n\nIf no matching posts are found, `false` is returned. The return type of this function varies based on the given arguments. If `query_object` argument is set to true **or** the accepted `WP_Query` argument `no_found_rows` is set to `false`, the function returns a clean object with attributes copied from the queried `WP_Query` class instance with unnecessary attributes removed. If these conditions are not met, the function returns the found posts in an array.\n\nIn the returned array [WordPress post objects](https://codex.wordpress.org/Class_Reference/WP_Post) are extended with the following keys:\n\n- `permalink` - The post permalink.\n- `image_id` - If a featured image is set for the post, this is the id of it.\n\n##### Example usage\n\n```\npublic function Query() {\n\t// Args might be set if the function is in a submodel or\n\t// they can come from a DustPress.js AJAX request.\n    $args = $this-\u003eget_args();\n\n    $per_page   = (int) get_option( 'posts_per_page' );\n    $page       = isset( $args['page'] ) ? $args['page'] : 1;\n    $offset     = ( $page - 1 ) * $per_page;\n\n    $query_args = [\n        'post_type'                 =\u003e 'post',\n        'posts_per_page'            =\u003e $per_page,\n        'offset'                    =\u003e $offset,\n        'update_post_meta_cache'    =\u003e false,\n        'update_post_term_cache'    =\u003e false,\n        'no_found_rows'             =\u003e false,\n        'query_object'              =\u003e false,\n    ];\n\n    // This returns a WP_Query like object.\n    // Queried posts are accessible in dust under the 'Query' key.\n    return \\DustPress\\Query::get_posts( $args );\n}\n```\n\n#### get_acf_posts()\n\nThis function extends the `\\DustPress\\Query\\get_posts` method with the ability to load ACF field group data with the post objects. Arguments [described](#get_posts) for the `get_posts` method are also accepted here with the addition of the key `whole_fields` which is used similarly to the the `\\DustPress\\Query\\get_acf_post` function.\n\nThis function does not support the recursive related post fetching. ACF fields with relational post object data need to be loaded separately.\n\nApart from the `\\DustPress\\Query\\get_posts`, the `image_id` key is removed in the found post objects and WordPress post objects](https://codex.wordpress.org/Class_Reference/WP_Post) are extended with the following keys:\n\n- `permalink` - The post permalink.\n- `image` - If a featured image is set for the post, this is the data returned by the `acf_get_attachment` function.\n\n# Plugins\n\n## Debugger\n\nDustPress also features a debugger that displays the data loaded by your current model in a pretty JSON viewer.\n\nGet the debugger from [Geniem GitHub](https://github.com/devgeniem/dustpress-debugger) or install it with Composer:\n```\ncomposer require devgeniem/dustpress-debugger\n```\n\n## DustPress.js\n\nWe have also created a handy DustPress.js library for using the DustPress Model methods on the front end.\n\nGet the DustPress.js from [Geniem Github](https://github.com/devgeniem/dustpress-js) or install it with Composer:\n```\ncomposer require devgeniem/dustpress-js\n```\n\n## Comments helper\n\nIf you need to make a commenting feature on your site, you can use our very easy-to-use Comments helper. It is available as a separate plugin.\n\nGet the Comments helper from [Geniem Github](https://github.com/devgeniem/dustpress-comments) or install it with Composer:\n```\ncomposer require devgeniem/dustpress-comments\n```\n\n# Overriding default templates\nDustPress offers a way to override WordPress' default templates that otherwise are not easily editable.\n\nAt the moment it's possible to modify:\n\n- wp-activate.php ([docs](https://github.com/devgeniem/dustpress/blob/master/docs/customize-wp/user-activate.md))\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdevgeniem%2Fdustpress","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdevgeniem%2Fdustpress","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdevgeniem%2Fdustpress/lists"}