WP Manifestindependent plugin directory

Install

Download source zip No release zip yet

This is the repository archive. It installs, but the folder name will carry the branch suffix and updates will not flow.

Install straight from GitHub with WP-CLI:

wp plugin install https://github.com/digitalunited/components/archive/refs/heads/master.zip

On Packagist as digitalunited/components:

composer require digitalunited/components
No built-in updater. New versions won't appear under Dashboard → Updates. Install new releases yourself, or let Git Updater track the repository.

Installation

  1. Install via composer
  2. Activate plugin
  3. Configure grunt

Usage

A component folder should be placed in the theme-directory following this structure:

└── components
    ├── SomeComponent
    │   ├── assets
    |   |   └── some-jpgs-or-whatever.jpg
    │   ├── component.php
    │   ├── one-or-more-less-files.less
    │   ├── one-or-more-coffee-files.coffee
    │   ├── view.php
    │   └── _somePartialView.view.php
    └── SomeOtherComponent
        ├── component.php
        ├── one-or-more-less-files.less
        ├── one-or-more-coffee-files.coffee
        └── view.php

Every component will have their short-code registered and vc_mapping set up.

component.php

A VCComponent have the following structure:

namespace Component;

class Text extends \DigitalUnited\Components\VcComponent
{
    // This is a VC-mapping array
    // https://wpbakery.atlassian.net/wiki/pages/viewpage.action?pageId=524332

    protected function getComponentConfig()
    {
        return [
            'name' => __('Text', 'components'),
            'description' => __('Standard textmodul', 'components'),
            'params' => [
                [
                    "type" => "textfield",
                    "holder" => "div",
                    "class" => "",
                    "heading" => __( "Headline", "components" ),
                    "param_name" => "headline",
                    "value" => "",
                    "description" => ""
                ],
                [
                    "type" => "textarea_html",
                    "holder" => "div",
                    "class" => "",
                    "heading" => __( "Content", "components" ),
                    "param_name" => "content",
                    "value" => "",
                    "description" => ""
                ],
            ]
        ];
    }

    // If you want to you can have diferent views for deferent cases.
    // If you do you can override the following method.
    //
    // If view is not specified they will be rendered in the following
    // order: [view].view.php, view.php
    //
    // default is __DIR__.'/view.php'
    protected function getViewFileName() {
        return parent::getViewFileName();
    }

    // Before the parameters of the components is sent to rendering
    // you may modify their values here
    protected function sanetizeDataForRendering($data)
    {
        return $data;
    }

    // If you want to change what kind of element is rendered
    // You could override this method
    protected function getWrapperElementType()
    {
        return 'div';
    }

    // Add classes to the wrapping element. Should be an array
    // If a param named view exists it will be added automaticly
    protected function getExtraWrapperClasses()
    {
        return $this->param('headline') ? ['has-headline'] : ['no-headline'];
    }

    // Add attribute to the wrapper. if 'class' is specified it will be merged in
    // with classes from getExtraWrapperClasses
    protected function getWrapperAttributes()
    {
        return ['href' => 'myhref foobar', 'role' => 'button'];
    }

    // May be used to implement logic such as post-type registering or whatever
    public function main()
    {
    }
}
?>

A Standard component have the following structure:

namespace Component;

class Sidebar extends \DigitalUnited\Components\Component
{
    // Return key value pair with the accepted parameters for this
    // view file
    protected function getDefaultParams() {
        return [
            'param1' => 'default value1',
            'param2' => '',
            'view' => 'default',
        ];
    }

    //Same as a VcComponent
    protected function getViewFileName() { ... }
    protected function sanetizeDataForRendering($data) { ... }
    public function main() { ... }
}
?>

View

In the views, all values returned from "sanetizeDataForRendering" will be accessible.

eg. ['foo' => 'bar'] will be available like

<?= $foo // outputs 'bar' ?>

You may also use the component class, referenced as $component. eg:

<?= $component->myFancyPublicFunction() ?>

You may use separate view files depending on the $view-param, if "view" param is specified, $view.view.php will be rendered. Default: view.php

It is possible to split a view file into partials:

<?= $component->renderPartial('_listItem') // renders _listItem.view.php ?>

Performance

When a slow registration of a component is detected the plugin will show an admin notice. Slow registration could be caused by by 2 reasons:

  • Dynamic population of params
  • Execution of tasks in the components main method.

If the problem is due to Dynamic population of params there is an easy solution. Just add the following code as an early abort in the function that generates the dynamic params:

if (!$this->shouldGenerateParams()) {
    return [];
}

It the code above doesn't help the problem probably is that code is executed in the components main function.

Less and coffe, assets

Could be handled with with Grunt/gulp or whatever. See https://github.com/digitalunited/roots for example

Releases

Newest 25 of 27 recorded releases.

Tag
Published
v0.2.6 latest
Feb 7, 2024 2 years ago
Sep 4, 2018 8 years ago
Aug 28, 2018 8 years ago
Oct 26, 2017 8 years ago
Sep 26, 2017 9 years ago
Sep 22, 2017 9 years ago
Jun 7, 2017 9 years ago
Mar 16, 2017 9 years ago
Oct 20, 2016 10 years ago
Sep 28, 2016 10 years ago
May 2, 2016 10 years ago
Apr 27, 2016 10 years ago
Apr 27, 2016 10 years ago
Jan 25, 2016 10 years ago
Jan 20, 2016 10 years ago
Dec 17, 2015 10 years ago
Dec 15, 2015 10 years ago
May 21, 2015 11 years ago
May 8, 2015 11 years ago
Apr 21, 2015 11 years ago
Apr 13, 2015 11 years ago
Apr 8, 2015 11 years ago
Feb 10, 2015 11 years ago
Feb 5, 2015 11 years ago
Feb 4, 2015 11 years ago
All releases on GitHub →

These releases are tags only. The author does not attach a packaged zip, so there are no download counts to report.

D+ grade

Security

Components v0.2.6 · audited by WP Registry

High-severity findings.

1 high 1 medium
Audited release
v0.2.6
Findings
2
Worst severity
high
Content hash
842dbb0beaeda5df7a066c3a…

Findings

  • high Stored XSS via unsanitized shortcode view/theme attributes in the component wrapper

    xss_stored

    Every registered component shortcode is rendered through Component::addWrapper(). getWrapperClasses() copies param('view') or param('theme') into the wrapper class list after only str_replace('.', '-'). param() reads raw $this->params (the shortcode atts) and does not go through shortcode_atts, so view/theme work even when the component never declared those keys. The wrapper is emitted as <{el} class='{$classes}' ...> with single-quoted class and no esc_attr/sanitize_html_class. WordPress kses does not rewrite shortcode attributes for users without unfiltered_html, so an Author (or a Contributor whose post is previewed/published) can break out of the class attribute with a payload such as view="' onclick=alert(1) x='" and have that event handler execute in visitors' browsers. Prerequisite: vc_map exists and the theme has at least one components/*/component.php (the plugin is a no-op otherwise). getWrapperAttributes() values are likewise interpolated unescaped, and Link::renderTag() builds <a> tags the same way if a theme calls it.

    src/Components/Component.php:32-74

    Recommendation

    Run view/theme through sanitize_html_class (or drop them from the class list). Escape every wrapper attribute with esc_attr, element names against an allowlist, and Link::renderTag() href/title/target with esc_url/esc_attr. Do not echo shortcode atts into HTML without escaping.

  • medium Shortcode view/theme attributes used as include paths (path traversal to *.view.php)

    file_access

    getViewPath() concatenates param('view') and param('theme') onto the component directory and includes the first existing {path}.view.php. There is no basename(), realpath() confinement, or .. rejection, and param() uses raw shortcode atts, so [ComponentX view="../../other-plugin/payload"] includes that payload.view.php if it exists. renderPartial($viewName) does the same concatenation with no existence check. The forced .view.php suffix blocks arbitrary-file RCE against wp-config.php / uploaded media, so blast radius is other *.view.php files plus whatever a theme exposes. A second sink in TemplateEngine::render() extract($params) before include $file will overwrite $file when a component's default/VC params include a key named file, turning that field into an unsuffixed include of an attacker-supplied path. Not reachable by Subscriber; Author/Contributor who can place the shortcode.

    src/Components/Component.php:125-145

    Recommendation

    Resolve the view file with realpath() and require that it stays under the component directory. Reject .. and absolute paths. In TemplateEngine::render(), extract with EXTR_SKIP (or never extract a key named file) and keep the include path in a local that cannot be overwritten. Prefer a whitelist of view names.

WP Registry hashes the installable build and reports on that exact bytes-for-bytes copy. Embargoed findings are withheld until they are disclosed, so a clean verdict means nothing public is outstanding. WP Manifest does not audit code itself.