diff --git a/.gitignore b/.gitignore
new file mode 100644
index 000000000..22d0d82f8
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+vendor
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 000000000..ce1973f8c
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,64 @@
+sudo: false
+
+dist: trusty
+
+language: php
+
+php:
+ - 7.0
+ - 7.1
+ - 7.2
+ - 7.3
+
+notifications:
+ email:
+ on_success: never
+ on_failure: change
+
+branches:
+ only:
+ - master
+ - development
+
+cache:
+ apt: true
+ directories:
+ - vendor
+ - node_modules
+ - composer
+ - $HOME/.composer/cache
+
+matrix:
+ fast_finish: true
+ include:
+ - php: 7.3
+ env: WP_VERSION=latest WP_MULTISITE=0
+ - php: 7.2
+ env: WP_VERSION=latest WP_MULTISITE=0
+ - php: 7.2
+ env: WP_VERSION=latest WP_MULTISITE=1
+ - php: 7.1
+ env: WP_VERSION=latest WP_MULTISITE=0
+ - php: 7.0
+ env: WP_VERSION=latest WP_MULTISITE=1
+
+ allow_failures:
+ # Allow failures for unstable builds.
+ - php: nightly
+
+before_install:
+ # Speed up build time by disabling Xdebug.
+ # https://johnblackbourn.com/reducing-travis-ci-build-times-for-wordpress-projects/
+ # https://twitter.com/kelunik/status/954242454676475904
+ - phpenv config-rm xdebug.ini || echo 'No xdebug config.'
+ # Install PHP CodeSniffer.
+ - composer self-update
+ - composer clearcache
+ - composer install
+ - phpenv rehash
+
+script:
+ # Search for PHP syntax errors.
+ - find -L . -path ./vendor -prune -o -name '*.php' -print0 | xargs -0 -n 1 -P 4 php -l
+ # Run PHPCS.
+ - composer check-cs . -- --runtime-set ignore_warnings_on_exit 1
diff --git a/README.md b/README.md
new file mode 100644
index 000000000..ae733528e
--- /dev/null
+++ b/README.md
@@ -0,0 +1,3 @@
+# Eightshift Libs
+
+TBD
diff --git a/composer.json b/composer.json
new file mode 100644
index 000000000..69f8be61a
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,44 @@
+{
+ "name": "infinum/eightshift-libs",
+ "description": "WordPress libs developed by Eightshift team to use in modern WordPress.",
+ "keywords": [
+ "composer", "installer", "plugin"
+ ],
+ "homepage": "https://eightshift.com/",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Eightshift team",
+ "email": "team@eightshift.com",
+ "homepage": "https://eightshift.com/",
+ "role": "Developer / IT Manager"
+ }
+ ],
+ "support": {
+ "issues": "https://github.com/infinum/eightshift-libs/issues",
+ "source": "https://github.com/infinum/eightshift-libs"
+ },
+ "require": {
+ "php": ">=7.0"
+ },
+ "require-dev": {
+ "infinum/coding-standards-wp": "^0.4.1",
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.5"
+ },
+ "autoload": {
+ "psr-4": {
+ "Eightshift_Libs\\": "src/"
+ }
+ },
+ "minimum-stability": "dev",
+ "prefer-stable": true,
+ "config": {
+ "sort-packages": true,
+ "optimize-autoloader": true,
+ "process-timeout": 2000
+ },
+ "scripts": {
+ "check-cs": "@php ./vendor/bin/phpcs",
+ "fix-cs": "@php ./vendor/bin/phpcbf"
+ }
+}
diff --git a/composer.lock b/composer.lock
new file mode 100644
index 000000000..50de2d975
--- /dev/null
+++ b/composer.lock
@@ -0,0 +1,389 @@
+{
+ "_readme": [
+ "This file locks the dependencies of your project to a known state",
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+ "This file is @generated automatically"
+ ],
+ "content-hash": "fda87bcab613aa12bab5ea6cc1cf4ea0",
+ "packages": [],
+ "packages-dev": [
+ {
+ "name": "dealerdirect/phpcodesniffer-composer-installer",
+ "version": "v0.5.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git",
+ "reference": "e749410375ff6fb7a040a68878c656c2e610b132"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/e749410375ff6fb7a040a68878c656c2e610b132",
+ "reference": "e749410375ff6fb7a040a68878c656c2e610b132",
+ "shasum": ""
+ },
+ "require": {
+ "composer-plugin-api": "^1.0",
+ "php": "^5.3|^7",
+ "squizlabs/php_codesniffer": "^2|^3"
+ },
+ "require-dev": {
+ "composer/composer": "*",
+ "phpcompatibility/php-compatibility": "^9.0",
+ "sensiolabs/security-checker": "^4.1.0"
+ },
+ "type": "composer-plugin",
+ "extra": {
+ "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin"
+ },
+ "autoload": {
+ "psr-4": {
+ "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Franck Nijhof",
+ "email": "franck.nijhof@dealerdirect.com",
+ "homepage": "http://www.frenck.nl",
+ "role": "Developer / IT Manager"
+ }
+ ],
+ "description": "PHP_CodeSniffer Standards Composer Installer Plugin",
+ "homepage": "http://www.dealerdirect.com",
+ "keywords": [
+ "PHPCodeSniffer",
+ "PHP_CodeSniffer",
+ "code quality",
+ "codesniffer",
+ "composer",
+ "installer",
+ "phpcs",
+ "plugin",
+ "qa",
+ "quality",
+ "standard",
+ "standards",
+ "style guide",
+ "stylecheck",
+ "tests"
+ ],
+ "time": "2018-10-26T13:21:45+00:00"
+ },
+ {
+ "name": "infinum/coding-standards-wp",
+ "version": "0.4.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/infinum/coding-standards-wp.git",
+ "reference": "87aa3608eb064308291f8f7f3d3d30a8fd1ea296"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/infinum/coding-standards-wp/zipball/87aa3608eb064308291f8f7f3d3d30a8fd1ea296",
+ "reference": "87aa3608eb064308291f8f7f3d3d30a8fd1ea296",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.6",
+ "phpcompatibility/phpcompatibility-wp": "^2.0",
+ "squizlabs/php_codesniffer": "^3.3.0",
+ "wp-coding-standards/wpcs": "^1.0.0"
+ },
+ "require-dev": {
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.4.3",
+ "phpcompatibility/php-compatibility": "^9.0",
+ "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0",
+ "roave/security-advisories": "dev-master"
+ },
+ "suggest": {
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.4.3 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically."
+ },
+ "type": "phpcodesniffer-standard",
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Contributors",
+ "homepage": "https://github.com/infinum/coding-standards-wp/graphs/contributors"
+ }
+ ],
+ "description": "Infinum WordPress Coding Standards",
+ "homepage": "https://github.com/infinum/coding-standards-wp",
+ "keywords": [
+ "Eightshift",
+ "Infinum",
+ "phpcs",
+ "standards",
+ "wordpress"
+ ],
+ "time": "2018-11-15T12:49:58+00:00"
+ },
+ {
+ "name": "phpcompatibility/php-compatibility",
+ "version": "9.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/PHPCompatibility/PHPCompatibility.git",
+ "reference": "2b63c5d284ab8857f7b1d5c240ddb507a6b2293c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibility/zipball/2b63c5d284ab8857f7b1d5c240ddb507a6b2293c",
+ "reference": "2b63c5d284ab8857f7b1d5c240ddb507a6b2293c",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3",
+ "squizlabs/php_codesniffer": "^2.3 || ^3.0.2"
+ },
+ "conflict": {
+ "squizlabs/php_codesniffer": "2.6.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.5 || ^5.0 || ^6.0 || ^7.0"
+ },
+ "suggest": {
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.4.3 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically.",
+ "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues."
+ },
+ "type": "phpcodesniffer-standard",
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "LGPL-3.0-or-later"
+ ],
+ "authors": [
+ {
+ "name": "Contributors",
+ "homepage": "https://github.com/PHPCompatibility/PHPCompatibility/graphs/contributors"
+ },
+ {
+ "name": "Wim Godden",
+ "homepage": "https://github.com/wimg",
+ "role": "lead"
+ },
+ {
+ "name": "Juliette Reinders Folmer",
+ "homepage": "https://github.com/jrfnl",
+ "role": "lead"
+ }
+ ],
+ "description": "A set of sniffs for PHP_CodeSniffer that checks for PHP cross-version compatibility.",
+ "homepage": "http://techblog.wimgodden.be/tag/codesniffer/",
+ "keywords": [
+ "compatibility",
+ "phpcs",
+ "standards"
+ ],
+ "time": "2018-12-30T23:16:27+00:00"
+ },
+ {
+ "name": "phpcompatibility/phpcompatibility-paragonie",
+ "version": "1.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie.git",
+ "reference": "9160de79fcd683b5c99e9c4133728d91529753ea"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityParagonie/zipball/9160de79fcd683b5c99e9c4133728d91529753ea",
+ "reference": "9160de79fcd683b5c99e9c4133728d91529753ea",
+ "shasum": ""
+ },
+ "require": {
+ "phpcompatibility/php-compatibility": "^9.0"
+ },
+ "require-dev": {
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.4.4"
+ },
+ "suggest": {
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.4.4 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.",
+ "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues."
+ },
+ "type": "phpcodesniffer-standard",
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "LGPL-3.0-or-later"
+ ],
+ "authors": [
+ {
+ "name": "Wim Godden",
+ "role": "lead"
+ },
+ {
+ "name": "Juliette Reinders Folmer",
+ "role": "lead"
+ }
+ ],
+ "description": "A set of rulesets for PHP_CodeSniffer to check for PHP cross-version compatibility issues in projects, while accounting for polyfills provided by the Paragonie polyfill libraries.",
+ "homepage": "http://phpcompatibility.com/",
+ "keywords": [
+ "compatibility",
+ "paragonie",
+ "phpcs",
+ "polyfill",
+ "standards"
+ ],
+ "time": "2018-12-16T19:10:44+00:00"
+ },
+ {
+ "name": "phpcompatibility/phpcompatibility-wp",
+ "version": "2.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/PHPCompatibility/PHPCompatibilityWP.git",
+ "reference": "cb303f0067cd5b366a41d4fb0e254fb40ff02efd"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityWP/zipball/cb303f0067cd5b366a41d4fb0e254fb40ff02efd",
+ "reference": "cb303f0067cd5b366a41d4fb0e254fb40ff02efd",
+ "shasum": ""
+ },
+ "require": {
+ "phpcompatibility/php-compatibility": "^9.0",
+ "phpcompatibility/phpcompatibility-paragonie": "^1.0"
+ },
+ "require-dev": {
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.4.3"
+ },
+ "suggest": {
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.4.3 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.",
+ "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues."
+ },
+ "type": "phpcodesniffer-standard",
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "LGPL-3.0-or-later"
+ ],
+ "authors": [
+ {
+ "name": "Wim Godden",
+ "role": "lead"
+ },
+ {
+ "name": "Juliette Reinders Folmer",
+ "role": "lead"
+ }
+ ],
+ "description": "A ruleset for PHP_CodeSniffer to check for PHP cross-version compatibility issues in projects, while accounting for polyfills provided by WordPress.",
+ "homepage": "http://phpcompatibility.com/",
+ "keywords": [
+ "compatibility",
+ "phpcs",
+ "standards",
+ "wordpress"
+ ],
+ "time": "2018-10-07T18:31:37+00:00"
+ },
+ {
+ "name": "squizlabs/php_codesniffer",
+ "version": "3.4.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/squizlabs/PHP_CodeSniffer.git",
+ "reference": "5b4333b4010625d29580eb4a41f1e53251be6baa"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5b4333b4010625d29580eb4a41f1e53251be6baa",
+ "reference": "5b4333b4010625d29580eb4a41f1e53251be6baa",
+ "shasum": ""
+ },
+ "require": {
+ "ext-simplexml": "*",
+ "ext-tokenizer": "*",
+ "ext-xmlwriter": "*",
+ "php": ">=5.4.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0"
+ },
+ "bin": [
+ "bin/phpcs",
+ "bin/phpcbf"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.x-dev"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Greg Sherwood",
+ "role": "lead"
+ }
+ ],
+ "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.",
+ "homepage": "https://github.com/squizlabs/PHP_CodeSniffer",
+ "keywords": [
+ "phpcs",
+ "standards"
+ ],
+ "time": "2019-03-19T03:22:27+00:00"
+ },
+ {
+ "name": "wp-coding-standards/wpcs",
+ "version": "1.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards.git",
+ "reference": "f328bcafd97377e8e5e5d7b244d5ddbf301a3a5c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/WordPress-Coding-Standards/WordPress-Coding-Standards/zipball/f328bcafd97377e8e5e5d7b244d5ddbf301a3a5c",
+ "reference": "f328bcafd97377e8e5e5d7b244d5ddbf301a3a5c",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3",
+ "squizlabs/php_codesniffer": "^2.9.0 || ^3.0.2"
+ },
+ "require-dev": {
+ "phpcompatibility/php-compatibility": "^9.0"
+ },
+ "suggest": {
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.4.3 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically."
+ },
+ "type": "phpcodesniffer-standard",
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Contributors",
+ "homepage": "https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/graphs/contributors"
+ }
+ ],
+ "description": "PHP_CodeSniffer rules (sniffs) to enforce WordPress coding conventions",
+ "keywords": [
+ "phpcs",
+ "standards",
+ "wordpress"
+ ],
+ "time": "2018-12-18T09:43:51+00:00"
+ }
+ ],
+ "aliases": [],
+ "minimum-stability": "dev",
+ "stability-flags": [],
+ "prefer-stable": true,
+ "prefer-lowest": false,
+ "platform": {
+ "php": ">=7.0"
+ },
+ "platform-dev": []
+}
diff --git a/examples/admin/class-register-post-type.php b/examples/admin/class-register-post-type.php
new file mode 100644
index 000000000..3affd0477
--- /dev/null
+++ b/examples/admin/class-register-post-type.php
@@ -0,0 +1,72 @@
+ esc_html_x( 'FAQ', 'post type upper case singular name', 'eightshift-libs' ),
+ Label_Generator::SINGULAR_NAME_LC => esc_html_x( 'faq', 'post type lower case singular name', 'eightshift-libs' ),
+ Label_Generator::PLURAL_NAME_UC => esc_html_x( 'FAQs', 'post type upper case plural name', 'eightshift-libs' ),
+ Label_Generator::PLURAL_NAME_LC => esc_html_x( 'faqs', 'post type lower case plural name', 'eightshift-libs' ),
+ ];
+
+ return [
+ 'label' => $nouns[ Label_Generator::SINGULAR_NAME_UC ],
+ 'labels' => ( new Label_Generator() )->get_generated_labels( $nouns ),
+ 'public' => true,
+ 'menu_position' => 50,
+ 'menu_icon' => self::MENU_ICON,
+ 'supports' => array( 'title', 'revisions', 'editor' ),
+ 'has_archive' => false,
+ 'show_in_rest' => true,
+ 'publicly_queryable' => true,
+ 'capability_type' => 'page',
+ 'map_meta_cap' => true,
+ 'can_export' => true,
+ ];
+ }
+}
diff --git a/examples/admin/class-register-taxonomy.php b/examples/admin/class-register-taxonomy.php
new file mode 100644
index 000000000..1c8c94924
--- /dev/null
+++ b/examples/admin/class-register-taxonomy.php
@@ -0,0 +1,77 @@
+ esc_html_x( 'FAQ category', 'post type upper case singular name', 'eighshift-libs' ),
+ Label_Generator::SINGULAR_NAME_LC => esc_html_x( 'faq category', 'post type lower case singular name', 'eighshift-libs' ),
+ Label_Generator::PLURAL_NAME_UC => esc_html_x( 'FAQ categories', 'post type upper case plural name', 'eighshift-libs' ),
+ Label_Generator::PLURAL_NAME_LC => esc_html_x( 'faq categories', 'post type lower case plural name', 'eighshift-libs' ),
+ ];
+
+ return [
+ 'label' => $nouns[ Label_Generator::SINGULAR_NAME_UC ],
+ 'labels' => ( new Label_Generator() )->get_generated_labels( $nouns ),
+ 'hierarchical' => true,
+ 'show_ui' => true,
+ 'show_admin_column' => true,
+ 'update_count_callback' => '_update_post_term_count',
+ 'query_var' => true,
+ ];
+ }
+}
diff --git a/examples/blocks/heading/class-heading.php b/examples/blocks/heading/class-heading.php
new file mode 100644
index 000000000..c259d09e8
--- /dev/null
+++ b/examples/blocks/heading/class-heading.php
@@ -0,0 +1,51 @@
+ array(
+ 'type' => parent::TYPE_STRING,
+ ),
+ 'level' => array(
+ 'type' => parent::TYPE_NUMBER,
+ 'default' => '2',
+ ),
+ 'styleAlign' => array(
+ 'type' => parent::TYPE_STRING,
+ 'default' => 'center',
+ ),
+ 'styleSize' => array(
+ 'type' => parent::TYPE_STRING,
+ 'default' => 'huge',
+ ),
+ );
+ }
+}
diff --git a/examples/includes/class-main.php b/examples/includes/class-main.php
new file mode 100644
index 000000000..771cf621b
--- /dev/null
+++ b/examples/includes/class-main.php
@@ -0,0 +1,41 @@
+ Array of fully qualified class names.
+ */
+ protected function get_service_classes() : array {
+ return [
+ Admin\Admin::class,
+ ];
+ }
+}
diff --git a/phpcs.xml.dist b/phpcs.xml.dist
new file mode 100644
index 000000000..4441c7318
--- /dev/null
+++ b/phpcs.xml.dist
@@ -0,0 +1,22 @@
+
+
+ Eightshift Library uses extended WordPress coding standards with some minor corections. TBD
+
+
+
+
+ 0
+
+
+
+ 0
+
+
+
+ 0
+
+
+
+ 0
+
+
diff --git a/phpunit.xml b/phpunit.xml
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/blocks/class-attribute-type-enums.php b/src/blocks/class-attribute-type-enums.php
new file mode 100644
index 000000000..7eb51a564
--- /dev/null
+++ b/src/blocks/class-attribute-type-enums.php
@@ -0,0 +1,60 @@
+ [ $this, 'render' ],
+ 'attributes' => $this->get_attributes(),
+ )
+ );
+ }
+ );
+ }
+
+ /**
+ * Adds default attributes that are dynamically built for all blocks.
+ * These are:
+ * - blockName: Block's full name including namespace (example: infinum/heading)
+ * - rootClass: Block's root (base) BEM CSS class, built in "block/$name" format (example: block-heading)
+ * - jsClass: Block's js selector class, built in "js-block-$name" format (example: js-block-heading)
+ *
+ * @throws \Exception On missing block name.
+ *
+ * @return array
+ *
+ * @since 1.0.0
+ */
+ public function get_default_attributes() : array {
+
+ // Make sure the class (block) extending this class (abstract Base_Block)
+ // has defined its own name.
+ if ( static::NAME === self::NAME ) {
+ throw Missing_Block::name_exception();
+ }
+
+ return [
+ 'blockName' => array(
+ 'type' => parent::TYPE_STRING,
+ 'default' => self::BLOCK_NAMESPACE . '/' . static::NAME,
+ ),
+ 'rootClass' => array(
+ 'type' => parent::TYPE_STRING,
+ 'default' => 'block-' . static::NAME,
+ ),
+ 'jsClass' => array(
+ 'type' => parent::TYPE_STRING,
+ 'default' => 'js-block-' . static::NAME,
+ ),
+ ];
+ }
+
+ /**
+ * Get block attributes assigned inside block class.
+ *
+ * @return array
+ *
+ * @since 1.0.0
+ */
+ public function get_block_attributes() : array {
+ return [];
+ }
+
+ /**
+ * Get all block attributes. Default and block attributes.
+ *
+ * @return array
+ *
+ * @since 1.0.0
+ */
+ public function get_attributes() : array {
+ return array_merge( $this->get_default_attributes(), $this->get_block_attributes() );
+ }
+
+ /**
+ * Renders the block using a template in Infinum\Blocks\Templates namespace/folder.
+ * Template file must have the same name as the class-blockname file, for example:
+ *
+ * Block: class-heading.php
+ * Template: heading.php
+ *
+ * @param array $attributes Array of attributes as defined in block's index.js.
+ * @param string $content Block's content.
+ *
+ * @throws \Exception On missing attributes OR missing template.
+ * @echo string
+ *
+ * @since 1.0.0
+ */
+ public function render( array $attributes, string $content ) : string {
+
+ // Block must have a defined name to find its template.
+ // Make sure the class (block) extending this class (abstract Base_Block)
+ // has defined its own name.
+ if ( static::NAME === self::NAME ) {
+ throw Missing_Block::name_exception();
+ }
+
+ $template_path = 'src/blocks/' . static::NAME . '/view/' . static::NAME . '.php';
+ $template = locate_template( $template_path );
+
+ if ( empty( $template ) ) {
+ throw Missing_Block::view_exception( static::NAME, $template_path );
+ }
+
+ // If everything is ok, return the contents of the template (return, NOT echo).
+ ob_start();
+ include $template;
+ $output = ob_get_clean();
+ unset( $template );
+ return $output;
+ }
+}
diff --git a/src/blocks/interface-block.php b/src/blocks/interface-block.php
new file mode 100644
index 000000000..8fbc6ad91
--- /dev/null
+++ b/src/blocks/interface-block.php
@@ -0,0 +1,76 @@
+register_assets_manifest_data();
+ }
+
+ /**
+ * Register the individual services of this plugin.
+ *
+ * @throws Exception\Invalid_Service If a service is not valid.
+ *
+ * @return void
+ *
+ * @since 1.0.0
+ */
+ public function register_services() : void {
+
+ // Bail early so we don't instantiate services twice.
+ if ( ! empty( $this->services ) ) {
+ return;
+ }
+
+ $classes = $this->get_service_classes();
+
+ $this->services = array_map(
+ [ $this, 'instantiate_service' ],
+ $classes
+ );
+ array_walk(
+ $this->services,
+ function( Service $service ) {
+ $service->register();
+ }
+ );
+ }
+
+ /**
+ * Provide menifest json url location.
+ *
+ * @return string
+ *
+ * @since 1.0.0
+ */
+ protected function get_manifest_url() : string {
+ return get_template_directory() . '/skin/public/manifest.json';
+ }
+
+ /**
+ * Register bundled asset manifest
+ *
+ * @throws Exception\Missing_Manifest Throws error if manifest is missing.
+ *
+ * @return void
+ *
+ * @since 1.0.0
+ */
+ public function register_assets_manifest_data() : void {
+
+ $manifest = $this->get_manifest_url();
+ if ( ! file_exists( $manifest ) ) {
+ $error_message = esc_html__( 'manifest.json is missing. Bundle the theme before using it.', 'developer-portal' );
+ throw Exception\Missing_Manifest::message( $error_message );
+ }
+
+ define( 'INF_ASSETS_MANIFEST', implode( ' ', file( $manifest ) ) );
+ }
+
+ /**
+ * Instantiate a single service.
+ *
+ * @param string $class Service class to instantiate.
+ *
+ * @throws Exception\Invalid_Service If the service is not valid.
+ *
+ * @return Service
+ *
+ * @since 1.0.0
+ */
+ private function instantiate_service( $class ) {
+ if ( ! class_exists( $class ) ) {
+ throw Exception\Invalid_Service::from_service( $class );
+ }
+
+ $service = new $class();
+ if ( ! $service instanceof Service ) {
+ throw Exception\Invalid_Service::from_service( $service );
+ }
+
+ return $service;
+ }
+
+ /**
+ * Get the list of services to register.
+ *
+ * A list of classes which contain hooks.
+ *
+ * @return array Array of fully qualified class names.
+ *
+ * @since 1.0.0
+ */
+ protected function get_service_classes() : array {
+ return [];
+ }
+}
diff --git a/src/custom-post-type/class-base-post-type.php b/src/custom-post-type/class-base-post-type.php
new file mode 100644
index 000000000..e22dbfdf8
--- /dev/null
+++ b/src/custom-post-type/class-base-post-type.php
@@ -0,0 +1,51 @@
+get_post_type_slug(), $this->get_post_type_arguments() );
+ }
+ );
+ }
+
+ /**
+ * Get the slug to use for the custom post type.
+ *
+ * @return string Custom post type slug.
+ *
+ * @since 1.0.0
+ */
+ abstract protected function get_post_type_slug() : string;
+
+ /**
+ * Get the arguments that configure the custom post type.
+ *
+ * @return array Array of arguments.
+ *
+ * @since 1.0.0
+ */
+ abstract protected function get_post_type_arguments() : array;
+}
diff --git a/src/custom-post-type/class-label-generator.php b/src/custom-post-type/class-label-generator.php
new file mode 100644
index 000000000..20ffd69c5
--- /dev/null
+++ b/src/custom-post-type/class-label-generator.php
@@ -0,0 +1,159 @@
+ esc_html_x( '%3$s', 'Post Type General Name', 'developer-portal' ), /* phpcs:disable */
+ /* Translators: %1$s uc singular, %2$s lc singular, %3$s uc plural, %4$s lc plural. */
+ 'singular_name' => esc_html_x( '%1$s', 'Post Type Singular Name', 'developer-portal' ), /* phpcs:disable */
+ /* Translators: %1$s uc singular, %2$s lc singular, %3$s uc plural, %4$s lc plural. */
+ 'menu_name' => esc_html__( '%3$s', 'developer-portal' ), /* phpcs:disable */
+ /* Translators: %1$s uc singular, %2$s lc singular, %3$s uc plural, %4$s lc plural. */
+ 'name_admin_bar' => esc_html__( '%1$s', 'developer-portal' ), /* phpcs:disable */
+ /* Translators: %1$s uc singular, %2$s lc singular, %3$s uc plural, %4$s lc plural. */
+ 'archives' => esc_html__( '%1$s Archives', 'developer-portal' ),
+ /* Translators: %1$s uc singular, %2$s lc singular, %3$s uc plural, %4$s lc plural. */
+ 'attributes' => esc_html__( '%1$s Attributes', 'developer-portal' ),
+ /* Translators: %1$s uc singular, %2$s lc singular, %3$s uc plural, %4$s lc plural. */
+ 'parent_item_colon' => esc_html__( 'Parent %1$s:', 'developer-portal' ),
+ /* Translators: %1$s uc singular, %2$s lc singular, %3$s uc plural, %4$s lc plural. */
+ 'all_items' => esc_html__( 'All %3$s', 'developer-portal' ),
+ /* Translators: %1$s uc singular, %2$s lc singular, %3$s uc plural, %4$s lc plural. */
+ 'add_new_item' => esc_html__( 'Add New %1$s', 'developer-portal' ),
+ /* Translators: %1$s uc singular, %2$s lc singular, %3$s uc plural, %4$s lc plural. */
+ 'add_new' => esc_html__( 'Add New', 'developer-portal' ),
+ /* Translators: %1$s uc singular, %2$s lc singular, %3$s uc plural, %4$s lc plural. */
+ 'new_item' => esc_html__( 'New %1$s', 'developer-portal' ),
+ /* Translators: %1$s uc singular, %2$s lc singular, %3$s uc plural, %4$s lc plural. */
+ 'edit_item' => esc_html__( 'Edit %1$s', 'developer-portal' ),
+ /* Translators: %1$s uc singular, %2$s lc singular, %3$s uc plural, %4$s lc plural. */
+ 'update_item' => esc_html__( 'Update %1$s', 'developer-portal' ),
+ /* Translators: %1$s uc singular, %2$s lc singular, %3$s uc plural, %4$s lc plural. */
+ 'view_item' => esc_html__( 'View %1$s', 'developer-portal' ),
+ /* Translators: %1$s uc singular, %2$s lc singular, %3$s uc plural, %4$s lc plural. */
+ 'view_items' => esc_html__( 'View %3$s', 'developer-portal' ),
+ /* Translators: %1$s uc singular, %2$s lc singular, %3$s uc plural, %4$s lc plural. */
+ 'search_items' => esc_html__( 'Search %1$s', 'developer-portal' ),
+ /* Translators: %1$s uc singular, %2$s lc singular, %3$s uc plural, %4$s lc plural. */
+ 'not_found' => esc_html__( 'Not found', 'developer-portal' ),
+ /* Translators: %1$s uc singular, %2$s lc singular, %3$s uc plural, %4$s lc plural. */
+ 'not_found_in_trash' => esc_html__( 'Not found in Trash', 'developer-portal' ),
+ /* Translators: %1$s uc singular, %2$s lc singular, %3$s uc plural, %4$s lc plural. */
+ 'featured_image' => esc_html__( 'Featured Image', 'developer-portal' ),
+ /* Translators: %1$s uc singular, %2$s lc singular, %3$s uc plural, %4$s lc plural. */
+ 'set_featured_image' => esc_html__( 'Set featured image', 'developer-portal' ),
+ /* Translators: %1$s uc singular, %2$s lc singular, %3$s uc plural, %4$s lc plural. */
+ 'remove_featured_image' => esc_html__( 'Remove featured image', 'developer-portal' ),
+ /* Translators: %1$s uc singular, %2$s lc singular, %3$s uc plural, %4$s lc plural. */
+ 'use_featured_image' => esc_html__( 'Use as featured image', 'developer-portal' ),
+ /* Translators: %1$s uc singular, %2$s lc singular, %3$s uc plural, %4$s lc plural. */
+ 'insert_into_item' => esc_html__( 'Insert into %2$s', 'developer-portal' ),
+ /* Translators: %1$s uc singular, %2$s lc singular, %3$s uc plural, %4$s lc plural. */
+ 'uploaded_to_this_item' => esc_html__( 'Uploaded to this %2$s', 'developer-portal' ),
+ /* Translators: %1$s uc singular, %2$s lc singular, %3$s uc plural, %4$s lc plural. */
+ 'items_list' => esc_html__( '%3$s list', 'developer-portal' ),
+ /* Translators: %1$s uc singular, %2$s lc singular, %3$s uc plural, %4$s lc plural. */
+ 'items_list_navigation' => esc_html__( '%3$s list navigation', 'developer-portal' ),
+ /* Translators: %1$s uc singular, %2$s lc singular, %3$s uc plural, %4$s lc plural. */
+ 'filter_items_list' => esc_html__( 'Filter %4$s list', 'developer-portal' ),
+ ];
+
+ return array_map(
+ function( $label ) use ( $nouns ) {
+ return sprintf(
+ $label,
+ $nouns[ self::SINGULAR_NAME_UC ],
+ $nouns[ self::SINGULAR_NAME_LC ],
+ $nouns[ self::PLURAL_NAME_UC ],
+ $nouns[ self::PLURAL_NAME_LC ]
+ );
+ },
+ $label_templates
+ );
+ }
+}
diff --git a/src/custom-taxonomy/class-base-taxonomy.php b/src/custom-taxonomy/class-base-taxonomy.php
new file mode 100644
index 000000000..0db369850
--- /dev/null
+++ b/src/custom-taxonomy/class-base-taxonomy.php
@@ -0,0 +1,66 @@
+get_taxonomy_slug(),
+ [ $this->get_post_type_slug() ],
+ $this->get_taxonomy_arguments()
+ );
+ }
+ );
+ }
+
+ /**
+ * Get the slug of the custom taxonomy
+ *
+ * @return string Custom taxonomy slug.
+ *
+ * @since 1.0.0
+ */
+ abstract protected function get_taxonomy_slug() : string;
+
+ /**
+ * Get the post type slug to use the taxonomy to
+ *
+ * @return string Custom post type slug.
+ *
+ * @since 1.0.0
+ */
+ abstract protected function get_post_type_slug() : string;
+
+ /**
+ * Get the arguments that configure the custom taxonomy.
+ *
+ * @return array Array of arguments.
+ *
+ * @since 1.0.0
+ */
+ abstract protected function get_taxonomy_arguments() : array;
+}
diff --git a/src/exception/class-failed-to-load-view.php b/src/exception/class-failed-to-load-view.php
new file mode 100644
index 000000000..2a6e3b038
--- /dev/null
+++ b/src/exception/class-failed-to-load-view.php
@@ -0,0 +1,37 @@
+getMessage()
+ );
+
+ return new static( $message, $exception->getCode(), $exception );
+ }
+}
diff --git a/src/exception/class-invalid-callback.php b/src/exception/class-invalid-callback.php
new file mode 100644
index 000000000..4a400ace1
--- /dev/null
+++ b/src/exception/class-invalid-callback.php
@@ -0,0 +1,36 @@
+