632636fb by Jeff Balicki

home content

1 parent e4dc7cb8
Showing 104 changed files with 5032 additions and 0 deletions
<svg fillRule="evenodd" clipRule="evenodd" strokeLinecap="round" strokeLinejoin="round" strokeMiterlimit="1.5"
width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path
id="Block_Lab_Icon"
d="M2.255,4.02c0.032,-0.042 0.071,-0.08 0.119,-0.111l4.937,-2.85c0.08,-0.042 0.169,-0.063 0.263,-0.058c0.054,0.007 0.077,0.002 0.184,0.058l4.938,2.85l0,0l4.937,2.851l0.026,0.016l0.026,0.019l0.023,0.02l0.023,0.021l0.021,0.023l0.019,0.025l0.017,0.026l0.016,0.027l0.014,0.028l0.011,0.029l0.01,0.03l0.008,0.03l0.005,0.031l0.003,0.031l0.001,0.031l0,0l0,5.701c-0.012,0.235 -0.035,0.265 -0.223,0.387l-9.837,5.679c-0.13,0.099 -0.3,0.12 -0.485,0.022l0,0l-4.937,-2.85l0,0c-0.025,-0.016 -0.046,-0.031 -0.065,-0.044c-0.118,-0.083 -0.143,-0.132 -0.154,-0.271c-0.009,-0.048 -0.011,-0.095 -0.004,-0.141l0,-11.334c0.005,-0.104 0.043,-0.2 0.104,-0.276Zm4.833,13.754l0,-10.369l-4.043,-2.334l0,10.368l4.043,2.335Zm5.831,-8.551l3.596,-2.076l-3.596,-2.076l0,4.152Z"
fill="#82878c"
/>
</svg>
<?php
/**
* Block Lab
*
* @package Block_Lab_Custom_Pro
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*
* Plugin Name: Block Lab Custom Pro
* Plugin URI:
* Description: The easy way to build custom blocks for Gutenberg.
* Version: 200
* Author:
* Author URI:
* License: GPL2
* License URI:
* Text Domain: block-lab
* Domain Path: languages
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
// Setup the plugin auto loader.
require_once 'php/autoloader.php';
/**
* Admin notice for incompatible versions of PHP.
*/
function block_lab_php_version_error() {
printf( '<div class="error"><p>%s</p></div>', esc_html( block_lab_php_version_text() ) );
}
/**
* String describing the minimum PHP version.
*
* "Namespace" is a PHP 5.3 introduced feature. This is a hard requirement
* for the plugin structure.
*
* "Traits" is a PHP 5.4 introduced feature. Remove "Traits" support from
* php/autoloader if you want to support a lower PHP version.
* Remember to update the checked version below if you do.
*
* @return string
*/
function block_lab_php_version_text() {
return __( 'Block Lab plugin error: Your version of PHP is too old to run this plugin. You must be running PHP 5.4 or higher.', 'block-lab' );
}
// If the PHP version is too low, show warning and return.
if ( version_compare( phpversion(), '5.4', '<' ) ) {
if ( defined( 'WP_CLI' ) ) {
WP_CLI::warning( block_lab_php_version_text() );
} else {
add_action( 'admin_notices', 'block_lab_php_version_error' );
}
return;
}
/**
* Admin notice for incompatible versions of WordPress or missing Gutenberg Plugin.
*/
function block_lab_wp_version_error() {
printf( '<div class="error"><p>%s</p></div>', esc_html( block_lab_wp_version_text() ) );
}
/**
* String describing the minimum WP version or Gutenberg Plugin requirement.
*
* "Blocks" are a feature of WordPress 5.0+ or require the Gutenberg plugin.
*
* @return string
*/
function block_lab_wp_version_text() {
return __( 'Block Lab plugin error: Your version of WordPress is too old. You must be running WordPress 5.0 to use Block Lab.', 'block-lab' );
}
// If the WordPress version is too low, show warning and return.
if ( ! function_exists( 'register_block_type' ) ) {
if ( defined( 'WP_CLI' ) ) {
WP_CLI::warning( block_lab_wp_version_text() );
} else {
add_action( 'admin_notices', 'block_lab_wp_version_error' );
}
}
/**
* Get the plugin object.
*
* @return \Block_Lab\Plugin
*/
function block_lab() {
static $instance;
if ( null === $instance ) {
$instance = new \Block_Lab\Plugin();
}
return $instance;
}
/**
* Setup the plugin instance.
*/
block_lab()
->set_basename( plugin_basename( __FILE__ ) )
->set_directory( plugin_dir_path( __FILE__ ) )
->set_file( __FILE__ )
->set_slug( 'block-lab' )
->set_url( plugin_dir_url( __FILE__ ) )
->set_version( __FILE__ )
->init();
// Sometimes we need to do some things after the plugin is loaded, so call the Plugin_Interface::plugin_loaded().
add_action( 'plugins_loaded', [ block_lab(), 'plugin_loaded' ] );
// Require helpers at 11, so if GCB is active, its helpers will be required first and prevent a PHP error.
add_action( 'plugins_loaded', [ block_lab(), 'require_helpers' ], 11 );
.post-type-block_lab .tablenav.top,
.post-type-block_lab .search-box,
.post-type-block_lab .inline-edit-date,
.post-type-block_lab .inline-edit-group {
display: none;
}
.post-type-block_lab .column-template code {
background: none;
font-size: 12px;
padding-left: 0;
}
.post-type-block_lab .fixed .column-icon {
width: 10%;
}
.post-type-block_lab .fixed td.column-icon {
color: #72777c;
}
.post-type-block_lab .fixed td.column-icon .icon {
background: #ffffff;
border: 1px solid #aaa;
border-radius: 4px;
padding: 4px;
width: 24px;
height: 24px;
display: inline-block;
}
.post-type-block_lab .fixed .column-fields {
width: 10%;
}
.bl-notice-conflict {
display: flex;
padding-top: 0.2rem;
padding-bottom: 0.2rem;
}
.bl-notice-conflict .bl-conflict-copy {
margin-right: auto;
}
.bl-notice-conflict .bl-link-deactivate {
margin: auto 0.2rem;
}
#adminmenu ul > li > a[href="edit.php?post_type=block_lab&page=block-lab-pro"] {
color: #00b9eb;
}
\ No newline at end of file
.bl-notice-migration {
display: flex;
}
.bl-notice-migration .bl-migration-copy {
margin-right: auto;
}
.bl-notice-migration__learn-more {
display: block;
}
.bl-notice-migration .bl-notice-option {
margin: auto 0.2rem;
}
.bl-notice-migration.bl-hidden {
display: none;
}
#bl-notice-not-now {
margin-left: 16px;
}
:root {
--color-brand: #5C34E8;
--color-pink: #FF227E;
--color-orange: #FF7C53;
--color-heading: #000;
--color-body: rgb(51, 51, 51);
--color-white: #fff;
--color-black: #000;
--color-gray: #edf2f7;
--color-green: #48bb78;
--color-purple: #422E62;
--color-purple-100: #FAF5FF;
--color-purple-200: #E9D8FD;
--color-purple-300: #D6BCFA;
--color-purple-400: #B794F4;
--color-purple-500: #9F7AEA;
--color-purple-600: #805AD5;
--color-purple-700: #6B46C1;
--color-purple-800: #553C9A;
--color-purple-900: #44337A;
--color-gray-100: #F7FAFC;
--color-gray-200: #EDF2F7;
--color-gray-300: #E2E8F0;
--color-gray-400: #CBD5E0;
--color-gray-500: #A0AEC0;
--color-gray-600: #718096;
--color-gray-700: #4A5568;
--color-gray-800: #2D3748;
--color-gray-900: #1A202C;
--box-shadow: 10px 10px 20px 0px rgba(121,110,255,0.1);
--box-shadow-small: 0 1px 3px 0 rgba(0,0,0,.1), 0 1px 2px 0 rgba(0,0,0,.06);
}
#wpcontent {
padding-left: 0;
}
.bl-migration__content {
font-family: system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";
line-height: 1.5;
}
.bl-migration__content {
color: var(--color-body);
font-size: 16px;
}
.bl-migration__content .get-genesis-pro {
border-top: solid 1px var(--color-gray-300);
padding-top: 2rem;
font-size: 18px;
}
.bl-migration__content p {
margin-bottom: 1em;
font-size: 16px;
}
.bl-migration__content h1,
.bl-migration__content h2,
.bl-migration__content h3,
.bl-migration__content h4,
.bl-migration__content h5,
.bl-migration__content h6 {
font-weight: 500;
margin-bottom: 1em;
}
.bl-migration__content h1 {
font-size: 1.65rem;
}
.bl-migration__content a {
color: var(--color-brand);
text-decoration: underline;
}
.bl-migration__content {
display: flex;
flex-grow: 1;
flex-direction: column;
background-color: var(--color-gray);
}
.bl-migration__content ul {
list-style-type: disc;
padding-left: 2rem;
margin-bottom: 2rem;
}
.bl-migration__content-wrapper {
padding: 1rem;
overflow: auto;
flex-grow: 1;
}
.bl-migration__content-container {
box-shadow: var(--box-shadow);
padding: 2.5rem;
background-color: var(--color-white);
}
.bl-migration__content .container {
max-width: 1200px;
margin-left: auto;
margin-right: auto;
}
.bl-migration__content .btn {
appearance: none;
border: none;
display: flex;
color: var(--color-purple-100);
cursor: pointer;
box-shadow: var(--box-shadow-small);
padding-left: 1.25rem;
padding-right: 1.25rem;
font-size: .875rem;
height: 2.25rem;
line-height: 1;
font-weight: 500;
align-items: center;
border-radius: .25rem;
background-color: var(--color-purple-700);
text-decoration: none;
}
.bl-migration__content .btn:hover {
cursor: pointer;
background-color: var(--color-purple-800);
}
.bl-migration__content .btn[disabled],
.genesis-pro-form button[disabled] {
color: var(--color-gray-500);
background-color: var(--color-gray-200);
box-shadow: none;
cursor: not-allowed;
}
.bl-migration__content .get-genesis-pro .btn {
display: inline-flex;
margin-right: 1rem;
}
.bl-migration__content .btn-secondary {
color: var(--color-purple-600);
border-color: var(--color-purple-200);
background-color: var(--color-purple-100);
box-shadow: none;
border-width: 1px;
border-style: solid;
}
.bl-migration__content .btn-secondary:hover {
color: var(--color-purple-700);
background-color: var(--color-purple-200);
}
.help-text {
opacity: 0.7;
font-style: italic;
margin-top: .5rem;
font-size: .875rem;
}
.dev-notice {
display: flex;
padding-left: .5rem;
padding-right: .5rem;
height: 3rem;
justify-content: flex-start;
align-items: center;
border-width: 1px;
border-radius: .25rem;
border-color: var(--color-brand);
background-color: var(--color-purple-100);
}
.dev-notice svg {
color: var(--color-purple-600);
fill: currentColor;
height: 1.25rem;
width: 1.25rem;
margin-left: 0.25rem;
}
.dev-notice>span {
color: var(--color-purple-700);
font-size: .875rem;
margin-left: .5rem;
line-height: 1;
font-weight: 500;
}
.dev-notice .btn {
color: var(--color-purple-700);
background-color: var(--color-purple-200);
height: 2rem;
padding-right: 0.75rem;
padding-left: 0.75rem;
margin-left: auto;
}
.dev-notice .btn:hover,
.genesis-pro-form button:hover {
color: var(--color-purple-800);
background-color: var(--color-purple-300);
}
.step {
background-color: var(--color-gray-100);
display: flex;
justify-content: flex-start;
padding-top: 1.1rem;
padding-bottom: 1rem;
padding-left: 1rem;
padding-right: 1rem;
border-top-width: 1px;
border-top-style: solid;
border-top-color: var(--color-gray-400);
max-height: 2.4rem;
overflow: hidden;
transition-duration: 200ms;
}
.step:last-child {
border-bottom: 1px solid var(--color-gray-400);
}
.step-icon {
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: center;
height: 2.25rem;
width: 2.25rem;
border-radius: 99999px;
border-color: var(--color-gray-300);
border-width: 2px;
border-style: solid;
color: var(--color-gray-500)
}
.step-icon svg {
width: 1.5rem;
height: 1.5rem;
fill: currentColor;
}
.step-icon span {
font-size: 1.25rem;
font-weight: 600;
}
.step h3 {
color: var(--color-gray-500);
margin-top: 6px;
}
.step--active {
overflow: hidden;
transition: max-height 1s ease-in-out;
max-height: 200rem;
background-color: var(--color-white);
padding-top: 2rem;
padding-bottom: 2rem;
}
.step--active h3 {
color: var(--color-body);
}
.step--active .step-icon {
border-color: var(--color-green);
color: var(--color-green);
}
.step--complete .step-icon {
background-color: var(--color-green);
border-color: var(--color-green);
color: var(--color-white);
}
.step--complete h3 {
color: var(--color-body);
}
.step-content {
flex-grow: 1;
margin-left: 2rem;
}
.step-footer {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 2rem;
border-top: solid 1px var(--color-gray-300);
padding-top: 1rem;
}
.step-footer form {
display: flex;
align-items: center;
margin-left: auto;
margin-bottom: 0;
margin-right: 1rem;
}
.step-footer button:only-child {
margin-left: auto;
}
.step-footer input[type="checkbox"] {
margin: 0;
}
label {
font-size: .875rem;
margin-left: .25rem;
font-weight: 500;
color: var(--color-gray-700);
}
.genesis-pro-form {
display: flex;
align-items: center;
}
.genesis-pro-form form {
display: flex;
margin-bottom: 0;
}
.genesis-pro-form input {
appearance: none;
width: 20rem;
box-shadow: var(--box-shadow-small);
padding-left: 1rem;
padding-right: 1rem;
font-size: .875rem;
height: 2.5rem;
border-style: solid;
border-width: 1px;
border-radius: 0.25rem 0 0 0.25rem;
border-color: var(--color-gray-400)
}
.genesis-pro-form button {
color: var(--color-purple-600);
box-shadow: var(--box-shadow-small);
padding-left: 1rem;
padding-right: 1rem;
font-size: .875rem;
height: 2.5rem;
margin-left: -1px;
font-weight: 500;
border-style: solid;
border-width: 1px;
border-bottom-right-radius: .25rem;
border-top-right-radius: .25rem;
border-color: var(--color-purple-300);
background-color: var(--color-purple-200);
cursor: pointer;
}
.genesis-pro-form p {
margin-left: 1rem;
margin-right: 1rem;
margin-bottom: 0;
margin-top: 0;
font-weight: 500;
}
.pro-submission-message {
font-style: italic;
color: var(--color-purple-600);
margin-top: 0.5rem;
}
.pro-box {
background-color: var(--color-gray-800);
color: var(--color-gray-200);
padding: 2.5rem 2.5rem 1rem 2.5rem;
margin-top: 2.5rem;
border-radius: .25rem;
box-shadow: var(--box-shadow);
margin-bottom: 2rem;;
}
.pro-box h3 {
color: var(--color-gray-200);
}
.pro-box-tiles {
display: flex;
margin-top: 1rem;
margin-left: -.5rem;
margin-right: -.5rem;
}
.pro-box-tile {
background-color: var(--color-gray-900);
padding: 2rem;
border-radius: .25rem;
margin-left: .5rem;
margin-right: .5rem;
width: 0;
flex-grow: 1;
}
.pro-box-tile__icon {
display: flex;
align-items: center;
justify-content: center;
border-radius: .25rem;
background-color: var(--color-purple-700);
height: 3rem;
width: 3rem;
margin-bottom: .75rem;
}
.pro-box-tile__icon svg {
width: 2rem;
height: 2rem;
color: var(--color-purple-300);
fill: currentColor;
}
.bl-migration__error {
background-color: var(--color-purple-100);
padding: 0.1rem 1rem;
margin-bottom: 1rem;
}
.bl-migration__error p:first-child {
color: var(--color-purple-700);
font-weight: 500;
}
.message-future {
font-size: 12px;
}
/*
* Copied from Gutenberg.
* https://github.com/WordPress/gutenberg/blob/eb856ef0892d6b9d15f39483ac2f45673d68f2d4/packages/components/src/spinner/style.scss
*/
.components-spinner {
display: inline-block;
background-color: #7e8993;
width: 18px;
height: 18px;
opacity: 0.7;
margin: 5px 11px 0;
border-radius: 100%;
position: relative;
}
.components-spinner::before {
content: "";
position: absolute;
background-color: #fff;
top: 3px;
left: 3px;
width: 4px;
height: 4px;
border-radius: 100%;
transform-origin: 6px 6px;
animation: components-spinner__animation 1s infinite linear;
}
@keyframes components-spinner__animation {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@media (max-width: 1000px) {
.step .pro-box-tiles {
flex-direction: column;
}
.step .pro-box-tile {
width: auto;
}
}
.block-lab-notice {
border-left: 4px solid #7D5DEC;
padding: 10px 20px;
background: #fff;
box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.1);
margin: 5px 0 15px;
}
.block-lab-notice h2 {
margin: 0.5em 0 1em;
}
.block-lab-notice p {
font-size: 1em;
}
.block-lab-notice .intro {
font-size: 1.2em;
line-height: 1.5em;
}
.block-lab-notice p.ps {
font-size: smaller;
}
.block-lab-notice .button {
display: inline-block;
position: relative;
background-color: #7D5DEC;
padding: 10px 12px;
margin: 1em 0;
height: auto;
border-radius: 4px;
border: 2px solid #7D5DEC;
color: #fff;
font-size: 13px;
text-decoration: none;
line-height: 13px;
cursor: pointer;
box-shadow: none;
}
.block-lab-notice .button:active,
.block-lab-notice .button:focus,
.block-lab-notice .button:visited {
color: #fff;
}
.block-lab-notice .button:hover {
color: #fff;
background-color: #6444d3;
border-color: #6444d3;
}
.block-lab-notice .button:nth-of-type( 2 ) {
margin-left: 10px;
}
.block-lab-notice .button--white {
background-color: #fff;
border-color: #fff;
color: #7D5DEC;
}
.block-lab-notice .button--white:active,
.block-lab-notice .button--white:focus,
.block-lab-notice .button--white:visited {
color: #7D5DEC;
}
.block-lab-notice .button--white:hover {
color: #7D5DEC;
background-color: #f4f4f4;
border-color: #f4f4f4;
}
.block-lab-notice .button_cta {
font-weight: 600;
padding: 16px 15px 15px;
box-shadow: 10px 10px 20px 0 rgba( 0, 0, 0, 0.2 );
}
.block-lab-welcome {
background-color: #7D5DEC;
background-image: url('https://getblocklab.com/wp-content/uploads/2019/02/Block-Lab-Pro-Hero-Background-1.svg');
background-size: cover;
background-position: center;
color: #fff;
padding: 32px 32px 6px;
border: none;
box-shadow: 10px 10px 20px 0px rgba( 121, 110, 255, 0.05 );
border-radius: 3px;
}
.block-lab-welcome h2 {
color: #fff;
line-height: 1em;
font-size: 2em;
font-weight: 700;
margin-bottom: 0.5em;
margin-top: 0;
font-style: italic;
}
.block-lab-welcome p {
font-size: 1.1em;
}
.block-lab-welcome .intro {
font-size: 1.3em;
}
.block-lab-notice p.ps {
opacity: 0.5;
}
.block-lab-welcome .notice-dismiss:before {
color: #372182;
}
.block-lab-welcome .notice-dismiss:hover:before {
color: #f4f4f4;
}
.block-lab-edit-block a.trash {
color: #444;
}
.block-lab-edit-block a.trash:hover {
color: #dc3232;
}
.block-lab-add-fields {
position: relative;
}
.block-lab-add-fields h2 {
font-size: 1.3em !important;
padding: 0 !important;
margin: 0.5em 0 1em !important;
font-weight: 600 !important;
}
.block-lab-publish {
margin: 1em 0 0;
}
.block-lab-publish h2 {
font-size: 1.3em !important;
padding: 0 !important;
margin: 0.5em 0 1em !important;
}
@keyframes block-lab-edit-block-point {
from {
transform: rotate(230deg) translateX(0px) translateY(0px);
}
to {
transform: rotate(220deg) translateX(10px) translateY(-40px);
}
}
@keyframes block-lab-add-fields-point {
from {
transform: rotate(30deg) translateX(0px) translateY(0px);
}
to {
transform: rotate(40deg) translateX(40px) translateY(20px);
}
}
.block-lab-settings .nav-tab-wrapper .dashicons-before:before {
padding: 2px 3px 0 0;
}
\ No newline at end of file
.block-lab-pro {
padding: 20px 20px 0 0;
margin: 0 auto;
max-width: 1280px;
}
.block-lab-pro .container {
margin-top: 20px;
display:grid;
grid-template-columns: repeat( 6, 1fr );
grid-gap: 20px;
}
.block-lab-pro .tile {
box-shadow: 10px 10px 20px 0px rgba( 121, 110, 255, 0.05 );
border-radius: 3px;
background-color: #fff;
}
.block-lab-pro .tile p {
font-size: 16px;
}
.block-lab-pro .tile_header {
padding: 40px 40px 0;
font-weight: 600;
display: flex;
justify-content: space-between;
align-items: center;
}
.block-lab-pro .tile_header span {
font-size: 16px;
line-height: 16px;
}
.block-lab-pro .tile_body {
padding: 0 40px 20px;
}
.block-lab-pro .tile_body h4 {
font-size: 24px;
line-height: 24px;
}
.block-lab-pro .tile_footer {
border-top: 2px solid #F7F9FC;
padding: 30px 40px 30px;
display: flex;
justify-content: space-between;
align-items: center;
}
.block-lab-pro .tile_footer.tile_footer_email {
background-color: #F7F9FC;
}
.block-lab-pro .tile_icon {
width: 180px;
display: block;
margin: auto;
max-width: 100%;
}
.block-lab-pro .tile_icon_wrapper {
width: 180px;
height: 180px;
display: block;
background-size: contain;
background-position: center;
background-repeat: no-repeat;
margin: auto;
}
.block-lab-pro .button {
display: inline-block;
position: relative;
background-color: #5c34e8;
padding: 10px 12px;
height: auto;
border-radius: 4px;
border: 2px solid #5c34e8;
color: #fff;
font-size: 13px;
text-decoration: none;
line-height: 13px;
cursor: pointer;
box-shadow: none;
}
.block-lab-pro .button:active,
.block-lab-pro .button:focus,
.block-lab-pro .button:visited {
color: #fff;
background-color: #5c34e8;
}
.block-lab-pro .button:hover {
color: #fff;
background-color: #5c34e8;
border-color: #5c34e8;
opacity: 0.9;
}
.block-lab-pro .button:nth-of-type( 2 ) {
margin-left: 10px;
}
.block-lab-pro .button--secondary {
background-color: #ff237e;
border-color: #ff237e;
color: #fff;
}
.block-lab-pro .button--secondary:active,
.block-lab-pro .button--secondary:focus,
.block-lab-pro .button--secondary:visited {
color: #fff;
background-color: #ff237e;
}
.block-lab-pro .button--secondary:hover {
background-color: #ff237e;
border-color: #ff237e;
opacity: 0.9;
}
.block-lab-pro .button--secondary-stroke {
border-color: #fff;
background-color: transparent;
}
.block-lab-pro .button--secondary-stroke:active,
.block-lab-pro .button--secondary-stroke:focus,
.block-lab-pro .button--secondary-stroke:visited {
color: #fff;
}
.block-lab-pro .button--secondary-stroke:hover {
color: #5c34e8;
background-color: #fff;
border-color: #fff;
}
.block-lab-pro .button_close {
background-color: rgba( 0, 0, 0, 0.1 );
border-radius: 50%;
line-height: 1.6em;
display: flex;
justify-content: center;
border: none;
color: red;
font-weight: 700;
}
.block-lab-pro .section_heading {
grid-column-start: 1;
grid-column-end: 7;
}
.block-lab-pro .dashboard_welcome {
grid-column-start: 1;
grid-column-end: 7;
background-color: #fff;
background-image: url('http://getblocklab.com/wp-content/uploads/2019/08/block_lab_hero_bg.svg');
background-size: cover;
background-position: top;
color: #000;
padding: 32px;
}
.block-lab-pro .dashboard_welcome .notice p {
color: #444;
font-size: 13px;
margin: 0.5em 0;
padding: 2px;
}
.block-lab-pro .dashboard_welcome h1 {
color: #000;
font-weight: 700;
font-size: 52px;
line-height: 52px;
margin-top: 10px;
text-shadow: 0 0 10px rgba(255,255,255,0.6);
}
.block-lab-pro .dashboard_welcome h1 .pro-pill {
font-size: .6em;
line-height: 1.4;
color: #fff;
background-color: #5c34e8;
display: inline-block;
border-radius: 8px;
padding: 0 11px;
top: -5px;
position: relative;
box-shadow: 0 0 10px rgba(255,255,255,0.6);
text-shadow: none;
}
.block-lab-pro .dashboard_welcome .tile_body {
display: flex;
justify-content: left;
align-items: center;
}
.block-lab-pro .dashboard_welcome p.description {
font-size: 18px;
margin-bottom: 30px;
max-width: 480px;
color: #000;
padding-top: 12px;
text-shadow: 0 0 4px #fff;
font-style: normal;
}
/* Tile sizing */
.block-lab-pro .tile_2 {
grid-column: span 2;
}
.block-lab-pro .tile_3 {
grid-column: span 3;
}
.block-lab-pro .tile_4 {
grid-column: span 4;
}
.block-lab-pro .tile_5 {
grid-column: span 5;
}
.block-lab-pro .tile_6 {
grid-column: span 6;
}
.block-lab-pro .ul_pills {
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
}
.block-lab-pro .ul_pills li {
display: block;
padding: 3px 6px;
margin-right: 16px;
margin-bottom: 16px;
border-radius: 3px;
box-shadow: 0 0 0 4px rgba( 121, 110, 255, 0.1 );
font-weight: 600;
}
.block-lab-pro .align_center {
text-align: center;
}
/* Mailchimp Styling */
#mc_embed_signup {
width: 100%;
}
#mc_embed_signup #mc_embed_signup_scroll {
display: flex;
}
.block-lab-pro input[type=email].input {
background-color: #fff;
border: 1px solid rgba( 0, 0, 0, 0.1 );
border-radius: 3px;
padding: 9px 10px;
width: 100%;
}
.block-lab-pro label.input_label {
display: none;
}
.mc-field-group {
flex-grow: 1;
padding-right: 10px
}
.block-lab-pro .cta_license_form_wrapper {
display: flex;
align-items: center;
}
.block-lab-pro .button_cta {
padding: 16px 15px 15px;
box-shadow: 10px 10px 20px 0 rgba( 0, 0, 0, 0.2 );
}
.block-lab-pro .license_key_form {
display: flex;
background-color: rgba( 0, 0, 0, 0.06 );
border-radius: 3px;
padding: 4px;
}
.block-lab-pro .license_key_text {
font-size: 14px !important;
font-style: italic !important;
margin-bottom: 14px !important;
margin-right: 10px;
margin-left: 10px;
}
.block-lab-pro input[type="text"].input_text {
border-radius: 3px;
padding: 11px 10px 10px;
width: 100%;
box-shadow: none;
border: none;
background-color: rgba( 255,255,255,1 );
margin-right: 4px;
}
@media ( max-width: 1200px ) {
.block-lab-pro .cta_license_form_wrapper {
display: block;
}
}
@media ( max-width: 720px ) {
.block-lab-pro .container {
display: block;
}
.block-lab-pro .tile {
margin-bottom: 20px;
}
.block-lab-pro .dashboard_welcome {
display: block;
}
.block-lab-pro .tile_body {
padding-top: 40px;
}
}
<?php return array('dependencies' => array(), 'version' => '734085aee55b820c6613aebadcd335c2');
\ No newline at end of file
/**
* Colors
*
* The variables below are taken from Gutenberg's styling in _colors.scss.
*
* @see https://github.com/WordPress/gutenberg/blob/master/assets/stylesheets/_colors.scss
*/
// Hugo's new WordPress shades of gray, from http://codepen.io/hugobaeta/pen/grJjVp.
$black: #000;
$dark-gray-900: #191e23;
$dark-gray-800: #23282d;
$dark-gray-700: #32373c;
$dark-gray-600: #40464d;
$dark-gray-500: #555d66; // Use this most of the time for dark items.
$dark-gray-400: #606a73;
$dark-gray-300: #6c7781; // Lightest gray that can be used for AA text contrast.
$dark-gray-200: #7e8993;
$dark-gray-150: #8d96a0; // Lightest gray that can be used for AA non-text contrast.
$dark-gray-100: #8f98a1;
$light-gray-900: #a2aab2;
$light-gray-800: #b5bcc2;
$light-gray-700: #ccd0d4;
$light-gray-600: #d7dade;
$light-gray-500: #e2e4e7; // Good for "grayed" items and borders.
$light-gray-400: #e8eaeb; // Good for "readonly" input fields and special text selection.
$light-gray-300: #edeff0;
$light-gray-200: #f3f4f5;
$light-gray-100: #f8f9f9;
$white: #fff;
// Dark opacities, for use with light themes.
$dark-opacity-900: rgba(#000510, 0.9);
$dark-opacity-800: rgba(#00000a, 0.85);
$dark-opacity-700: rgba(#06060b, 0.8);
$dark-opacity-600: rgba(#000913, 0.75);
$dark-opacity-500: rgba(#0a1829, 0.7);
$dark-opacity-400: rgba(#0a1829, 0.65);
$dark-opacity-300: rgba(#0e1c2e, 0.62);
$dark-opacity-200: rgba(#162435, 0.55);
$dark-opacity-100: rgba(#223443, 0.5);
$dark-opacity-light-900: rgba(#304455, 0.45);
$dark-opacity-light-800: rgba(#425863, 0.4);
$dark-opacity-light-700: rgba(#667886, 0.35);
$dark-opacity-light-600: rgba(#7b86a2, 0.3);
$dark-opacity-light-500: rgba(#9197a2, 0.25);
$dark-opacity-light-400: rgba(#95959c, 0.2);
$dark-opacity-light-300: rgba(#829493, 0.15);
$dark-opacity-light-200: rgba(#8b8b96, 0.1);
$dark-opacity-light-100: rgba(#747474, 0.05);
$dark-opacity-background-fill: rgba($dark-gray-700, 0.7); // Similar to $dark-opacity-light-200, but more opaque.
// Light opacities, for use with dark themes.
$light-opacity-900: rgba($white, 1);
$light-opacity-800: rgba($white, 0.9);
$light-opacity-700: rgba($white, 0.85);
$light-opacity-600: rgba($white, 0.8);
$light-opacity-500: rgba($white, 0.75);
$light-opacity-400: rgba($white, 0.7);
$light-opacity-300: rgba($white, 0.65);
$light-opacity-200: rgba($white, 0.6);
$light-opacity-100: rgba($white, 0.55);
$light-opacity-light-900: rgba($white, 0.5);
$light-opacity-light-800: rgba($white, 0.45);
$light-opacity-light-700: rgba($white, 0.4);
$light-opacity-light-600: rgba($white, 0.35);
$light-opacity-light-500: rgba($white, 0.3);
$light-opacity-light-400: rgba($white, 0.25);
$light-opacity-light-300: rgba($white, 0.2);
$light-opacity-light-200: rgba($white, 0.15);
$light-opacity-light-100: rgba($white, 0.1);
$light-opacity-background-fill: rgba($light-gray-300, 0.8); // Similar to $light-opacity-light-200, but more opaque.
// Additional colors.
// Some are from https://make.wordpress.org/design/handbook/foundations/colors/.
$blue-wordpress-700: #00669b;
$blue-dark-900: #0071a1;
$blue-medium-900: #006589;
$blue-medium-800: #00739c;
$blue-medium-700: #007fac;
$blue-medium-600: #008dbe;
$blue-medium-500: #00a0d2;
$blue-medium-400: #33b3db;
$blue-medium-300: #66c6e4;
$blue-medium-200: #bfe7f3;
$blue-medium-100: #e5f5fa;
$blue-medium-highlight: #b3e7fe;
$blue-medium-focus: #007cba;
// Alert colors.
$alert-yellow: #f0b849;
$alert-red: #d94f4f;
$alert-green: #4ab866;
\ No newline at end of file
/* global XMLHttpRequest, FormData, ajaxurl */
document.addEventListener( 'DOMContentLoaded', function() {
const hiddenClass = 'bl-hidden';
// In the main migration notice, on clicking 'Not Now',
// make an AJAX request to store the user meta to not display the notice again.
// Also, remove this notice and display another.
document.querySelector( '#bl-notice-not-now' ).addEventListener( 'click', function() {
const request = new XMLHttpRequest();
const data = new FormData();
data.append( 'action', 'bl_dismiss_migration_notice' );
data.append( 'bl-migration-nonce-name', document.querySelector( '#bl-migration-nonce-name' ).value );
request.open( 'POST', ajaxurl, true );
request.send( data );
// Remove this notice.
const notice = document.querySelector( '#bl-migration-notice' );
notice.parentNode.removeChild( notice );
// Display the 'Not Now' notice.
document.querySelector( '#bl-not-now-notice' ).classList.remove( hiddenClass );
} );
// In the 'Not Now' notice, on clicking 'OK', hide the notice.
document.querySelector( '#bl-notice-ok' ).addEventListener( 'click', function() {
document.querySelector( '#bl-not-now-notice' ).classList.add( hiddenClass );
} );
} );
<?php return array('dependencies' => array('react', 'wp-a11y', 'wp-api-fetch', 'wp-components', 'wp-element', 'wp-polyfill'), 'version' => 'd880838e7a15ba0cee2603309a597941');
\ No newline at end of file
<?php return array('dependencies' => array('lodash', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-data', 'wp-deprecated', 'wp-dom', 'wp-editor', 'wp-element', 'wp-hooks', 'wp-html-entities', 'wp-keycodes'), 'version' => '71acd0fd9d80bf1688a4e97931170ca5');
\ No newline at end of file
This diff could not be displayed because it is too large.
/* global blockLabMigration */
// @ts-check
/**
* External dependencies
*/
import * as React from 'react';
/**
* WordPress dependencies
*/
import { useState } from '@wordpress/element';
/**
* Internal dependencies
*/
import { Intro } from './';
import { BackUpSite, GetGenesisPro, InstallActivateGcb, MigrateBlocks, UpdateHooks } from './steps';
import { FIRST_STEP_NUMBER } from '../constants';
/**
* The migration admin page.
*
* @return {React.ReactElement} The component for the admin page.
*/
const App = () => {
const [ currentStepIndex, setStepIndex ] = useState( FIRST_STEP_NUMBER );
/**
* Sets the step index to the previous step.
*/
const goToPrevious = () => {
setStepIndex( currentStepIndex - 1 );
};
/**
* Sets the step index to the next step.
*/
const goToNext = () => {
setStepIndex( currentStepIndex + 1 );
};
const steps = [
BackUpSite,
UpdateHooks,
InstallActivateGcb,
MigrateBlocks,
];
// Conditionally add the step to get Genesis Pro.
// @ts-ignore
if ( blockLabMigration.isPro ) {
steps.unshift( GetGenesisPro );
}
return (
<div className="bl-migration__content-wrapper">
<div className="container bl-migration__content-container">
<Intro />
{
steps.map( ( MigrationStep, index ) => {
const stepIndex = FIRST_STEP_NUMBER + index;
const isStepActive = currentStepIndex === stepIndex;
const isStepComplete = currentStepIndex > stepIndex;
return (
<MigrationStep
key={ `bl-migration-step-${ stepIndex }` }
{ ...{ currentStepIndex, goToNext, goToPrevious, isStepActive, isStepComplete, stepIndex } }
/>
);
} )
}
</div>
</div>
);
};
export default App;
// @ts-check
/**
* External dependencies
*/
import * as React from 'react';
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { useState } from '@wordpress/element';
/**
* @typedef ButtonNextProps
* @property {React.EventHandler<React.MouseEvent<HTMLButtonElement, MouseEvent>>} onClick The click handler.
* @property {string} [checkboxLabel] The label of the checkbox, if there should be one.
* @property {number} stepIndex The index of this button's step.
*/
/**
* The next button.
*
* @param {ButtonNextProps} props The component props.
* @return {React.ReactElement} The component for the step content.
*/
const ButtonNext = ( { onClick, checkboxLabel, stepIndex } ) => {
const [ isCheckboxChecked, setCheckboxChecked ] = useState( false );
// If there's no label for the 'confirmation' checkbox, return a simple button.
if ( ! checkboxLabel ) {
return <button className="btn" onClick={ onClick }>{ __( 'Next Step', 'block-lab' ) }</button>;
}
const inputId = `bl-migration-check-${ stepIndex }`;
return (
<>
<form>
<input
id={ inputId }
type="checkbox"
onClick={ () => {
setCheckboxChecked( ! isCheckboxChecked );
} }
/>
<label htmlFor={ inputId } className="ml-2 font-medium">{ checkboxLabel }</label>
</form>
<button
className="btn"
onClick={ onClick }
disabled={ ! isCheckboxChecked }
>
{ __( 'Next Step', 'block-lab' ) }
</button>
</>
);
};
export default ButtonNext;
// @ts-check
/**
* External dependencies
*/
import * as React from 'react';
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
/**
* @typedef ButtonPreviousProps
* @property {React.EventHandler<React.MouseEvent<HTMLButtonElement, MouseEvent>>} onClick The click handler.
*/
/**
* The previous button.
*
* @param {ButtonPreviousProps} props The component props.
* @return {React.ReactElement} The component for the step content.
*/
const ButtonPrevious = ( { onClick } ) => {
return (
<button className="btn btn-secondary" onClick={ onClick }>
{ __( 'Previous', 'block-lab' ) }
</button>
);
};
export default ButtonPrevious;
export { default as App } from './app';
export { default as ButtonNext } from './button-next';
export { default as ButtonPrevious } from './button-previous';
export { default as Intro } from './intro';
export { default as Step } from './step';
export { default as StepContent } from './step-content';
export { default as StepFooter } from './step-footer';
export { default as StepIcon } from './step-icon';
// @ts-check
/**
* External dependencies
*/
import * as React from 'react';
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
/**
* The introduction to the migration.
*
* @return {React.ReactElement} The introduction to the migration.
*/
const Intro = () => {
const developerNoticeUrl = 'https://getblocklab.com/migrating-to-genesis-custom-blocks/';
const announcementUrl = 'https://getblocklab.com/the-block-lab-team-are-joining-wp-engine/';
return (
<>
<div>
<h1>{ __( 'Migrating to Genesis Custom Blocks', 'block-lab' ) }</h1>
<p>{ __( 'In April, the Block Lab team joined the Genesis team at WP Engine. With our full-time focus, we’re very excited about the future of custom block tooling in WordPress. You can read more about that moment in this', 'block-lab' ) } <a target="_blank" rel="noopener noreferrer" className="text-purple-600 underline hover:text-purple-700" href={ announcementUrl }>{ __( 'announcement post', 'block-lab' ) }.</a></p>
<p>
{ __( 'As part of this move, we have been working on a new plugin that is based on what we developed at Block Lab.', 'block-lab' ) }
&nbsp;
{ __( 'Genesis Custom Blocks is now the home of all our custom block efforts and what we have planned is very very cool!', 'block-lab' ) }
&nbsp;
{ __( 'Version 1.0 of this plugin is now released and has full feature parity with Block Lab.', 'block-lab' ) }
</p>
<p>{ __( 'To continue receiving the best of what our team is building, we encourage you to migrate over. Our migration tool makes this nice and easy, and for the majority of use cases, completely automated.', 'block-lab' ) }</p>
<div className="dev-notice">
<svg fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clipRule="evenodd"></path>
</svg>
<span>{ __( 'Need to let the developer for this site know about this? Send them this link.', 'block-lab' ) }</span>
<a href={ developerNoticeUrl } target="_blank" rel="noopener noreferrer" className="btn">
<span>{ __( 'Developer Notice', 'block-lab' ) }</span>
<svg fill="currentColor" viewBox="0 0 20 20">
<path d="M11 3a1 1 0 100 2h3.586l-6.293 6.293a1 1 0 101.414 1.414L15 6.414V9a1 1 0 102 0V4a1 1 0 00-1-1h-5z"></path>
<path d="M5 5a2 2 0 00-2 2v8a2 2 0 002 2h8a2 2 0 002-2v-3a1 1 0 10-2 0v3H5V7h3a1 1 0 000-2H5z"></path>
</svg>
</a>
</div>
</div>
<h2>{ __( "Let's Migrate", 'block-lab' ) }</h2>
</>
);
};
export default Intro;
// @ts-check
/**
* External dependencies
*/
import * as React from 'react';
/**
* @typedef StepContentProps
* @property {React.ReactNode} children The component's children.
* @property {string} heading The step heading.
* @property {boolean} isStepActive Whether this step is active.
*/
/**
* The content of the step.
*
* @param {StepContentProps} props The component props.
* @return {React.ReactElement} The component for the step content.
*/
const StepContent = ( { children, heading, isStepActive } ) => {
return (
<div className="step-content">
<h3>{ heading }</h3>
{ isStepActive && children }
</div>
);
};
export default StepContent;
// @ts-check
/**
* External dependencies
*/
import * as React from 'react';
/**
* @typedef StepFooterProps
* @property {React.ReactNode} children The component's children.
*/
/**
* The footer of the step.
*
* @param {StepFooterProps} props The component props.
* @return {React.ReactElement} The component for the step content.
*/
const StepFooter = ( { children } ) => {
return (
<div className="step-footer">
{ children }
</div>
);
};
export default StepFooter;
// @ts-check
/**
* External dependencies
*/
import * as React from 'react';
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
/**
* @typedef StepIconProps
* @property {number} index The index of this icon's step.
* @property {boolean} isComplete Whether this icon's step is active.
*/
/**
* The icon of the step number.
*
* @param {StepIconProps} props The component props.
* @return {React.ReactElement} props The icon component.
*/
const StepIcon = ( { index, isComplete } ) => {
const titleId = `bl-migration-icon-${ index }`;
const svg = (
<svg fill="currentColor" viewBox="0 0 20 20" aria-labelledby={ titleId }>
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd"></path>
<title id={ titleId }>{ __( 'Step completed', 'block-lab' ) }</title>
</svg>
);
return (
<div className="step-icon">
{ isComplete ? svg : index }
</div>
);
};
export default StepIcon;
// @ts-check
/**
* External dependencies
*/
import classNames from 'classnames';
import * as React from 'react';
/**
* @typedef StepProps
* @property {boolean} isActive Whether this step is active.
* @property {boolean} isComplete Whether this step is complete.
* @property {React.ReactNode} children The children of the component.
*/
/**
* Migration step.
*
* @param {StepProps} props The component props.
*/
const Step = ( { isActive, isComplete, children } ) => {
return (
<div
className={ classNames( 'step', {
'step--active': isActive,
'step--complete': isComplete,
} ) }
>
{ children }
</div>
);
};
export default Step;
// @ts-check
/**
* External dependencies
*/
import * as React from 'react';
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
*/
import { ButtonNext, ButtonPrevious, Step, StepContent, StepFooter, StepIcon } from '../';
import { FIRST_STEP_NUMBER } from '../../constants';
/**
* @typedef {Object} BackUpSiteProps The component props.
* @property {boolean} isStepActive Whether this step is active.
* @property {boolean} isStepComplete Whether this step is complete.
* @property {number} stepIndex The step index of this step.
* @property {React.EventHandler<React.MouseEvent<HTMLButtonElement, MouseEvent>>} goToNext Goes to the next step.
* @property {React.EventHandler<React.MouseEvent<HTMLButtonElement, MouseEvent>>} goToPrevious Goes to the previous step.
*/
/**
* The step that prompts to back up the site.
*
* @param {BackUpSiteProps} Props The component props.
* @return {React.ReactElement} The component to prompt to back up the site.
*/
const BackUpSite = ( { isStepActive, isStepComplete, goToNext, goToPrevious, stepIndex } ) => {
const isFirstStep = FIRST_STEP_NUMBER === stepIndex;
return (
<Step isActive={ isStepActive } isComplete={ isStepComplete }>
<StepIcon
index={ stepIndex }
isComplete={ isStepComplete }
/>
<StepContent
heading={ __( 'Back Up Your Site', 'block-lab' ) }
isStepActive={ isStepActive }
>
<p>{ __( 'Migrating from Block Lab to Genesis Custom Blocks is a one-way action. It can’t be undone. Please back up your site before you begin, just in case you need to roll it back.', 'block-lab' ) }</p>
<StepFooter>
{ ! isFirstStep && <ButtonPrevious onClick={ goToPrevious } /> }
<ButtonNext
checkboxLabel={ __( 'I have backed up my site.', 'block-lab' ) }
onClick={ goToNext }
stepIndex={ stepIndex }
/>
</StepFooter>
</StepContent>
</Step>
);
};
export default BackUpSite;
export { default as BackUpSite } from './back-up-site';
export { default as GetGenesisPro } from './get-genesis-pro';
export { default as InstallActivateGcb } from './install-activate-gcb';
export { default as MigrateBlocks } from './migrate-blocks';
export { default as UpdateHooks } from './update-hooks';
// @ts-check
/**
* External dependencies
*/
import * as React from 'react';
/**
* WordPress dependencies
*/
import { speak } from '@wordpress/a11y';
import apiFetch from '@wordpress/api-fetch';
import { Spinner } from '@wordpress/components';
import { useState } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
*/
import { ButtonNext, Step, StepContent, StepFooter, StepIcon } from '../';
/**
* @typedef {Object} InstallActivateGcbProps The component props.
* @property {React.EventHandler<React.MouseEvent<HTMLButtonElement, MouseEvent>>} goToNext Goes to the next step.
* @property {boolean} isStepActive Whether this step is active.
* @property {boolean} isStepComplete Whether this step is complete.
* @property {number} stepIndex The step index of this step.
*/
/**
* Installs and activates GCB.
*
* @param {InstallActivateGcbProps} Props The component props.
* @return {React.ReactElement} The component to activate Genesis Custom Blocks.
*/
const InstallActivateGcb = ( { goToNext, isStepActive, isStepComplete, stepIndex } ) => {
const [ isInProgress, setIsInProgress ] = useState( false );
const [ isError, setIsError ] = useState( false );
const [ errorMessage, setErrorMessage ] = useState( '' );
const [ isSuccess, setIsSuccess ] = useState( false );
/**
* Installs and activates Genesis Custom Blocks.
*/
const installAndActivateGcb = async () => {
speak( __( 'The installation is now in progress', 'block-lab' ) );
setIsInProgress( true );
setIsError( false );
setErrorMessage( '' );
await apiFetch( {
path: '/block-lab/install-activate-gcb',
method: 'POST',
} ).then( () => {
speak( __( 'Success! Genesis Custom Blocks is installed and activated.', 'block-lab' ) );
setIsSuccess( true );
} ).catch( ( result ) => {
speak( __( 'The installation and activation failed with the following error:', 'block-lab' ) );
if ( result.hasOwnProperty( 'message' ) ) {
speak( result.message );
setErrorMessage( result.message );
}
setIsSuccess( false );
setIsError( true );
} );
setIsInProgress( false );
};
return (
<Step isActive={ isStepActive } isComplete={ isStepComplete }>
<StepIcon
index={ stepIndex }
isComplete={ isStepComplete }
/>
<StepContent
heading={ __( 'Install And Activate Genesis Custom Blocks', 'block-lab' ) }
isStepActive={ isStepActive }
>
{ isInProgress && (
<>
<Spinner />
<p>{ __( 'Installing and activating Genesis Custom Blocks…', 'block-lab' ) }</p>
</>
) }
{ !! errorMessage && (
<div className="bl-migration__error">
<p>{ __( 'The following error ocurred:', 'block-lab' ) }</p>
<p>{ errorMessage }</p>
</div>
) }
{ ! isInProgress && ! isSuccess && (
<button
className="btn"
onClick={ installAndActivateGcb }
>
{ isError ? __( 'Try Again', 'block-lab' ) : __( 'Install and activate', 'block-lab' ) }
</button>
) }
{ isSuccess && (
<>
<p>{ __( 'Success! Genesis Custom Blocks is installed and activated.', 'block-lab' ) }</p>
<StepFooter>
<ButtonNext
onClick={ goToNext }
stepIndex={ stepIndex }
/>
</StepFooter>
</>
) }
</StepContent>
</Step>
);
};
export default InstallActivateGcb;
// @ts-check
/* global blockLabMigration */
/**
* External dependencies
*/
import * as React from 'react';
/**
* WordPress dependencies
*/
import { speak } from '@wordpress/a11y';
import apiFetch from '@wordpress/api-fetch';
import { Spinner } from '@wordpress/components';
import { useState } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
*/
import { Step, StepContent, StepFooter, StepIcon } from '../';
/**
* @typedef {Object} MigrateBlocksProps The component props.
* @property {Function} goToNext Goes to the next step.
* @property {boolean} isStepActive Whether this step is active.
* @property {boolean} isStepComplete Whether this step is complete.
* @property {number} stepIndex The step index of this step.
*/
/**
* The step that migrates the blocks.
*
* @param {MigrateBlocksProps} Props The component props.
* @return {React.ReactElement} The component to prompt to migrate the post content.
*/
const MigrateBlocks = ( { isStepActive, isStepComplete, stepIndex } ) => {
const [ currentBlockMigrationStep, setCurrentBlockMigrationStep ] = useState( 0 );
const [ isInProgress, setIsInProgress ] = useState( false );
const [ isError, setIsError ] = useState( false );
const [ errorMessage, setErrorMessage ] = useState( '' );
const [ isSuccess, setIsSuccess ] = useState( false );
const migrationLabels = [
__( 'Migrating your blocks…', 'block-lab' ),
__( 'Migrating your post content…', 'block-lab' ),
];
/**
* Migrates the custom post type, then chains post content migration to the callback.
*/
const migrateCpt = async () => {
await apiFetch( {
path: '/block-lab/migrate-post-type',
method: 'POST',
} ).then( async () => {
setCurrentBlockMigrationStep( 1 );
await migratePostContent();
} ).catch( ( result ) => {
if ( result.hasOwnProperty( 'message' ) ) {
setErrorMessage( result.message );
}
speak( __( 'The migration failed in the CPT migration', 'block-lab' ) );
setIsError( true );
setIsInProgress( false );
} );
};
/**
* Migrates the post content.
*/
const migratePostContent = async () => {
// Used for a 504 Gateway Timeout Error, but could also be for other errors.
const timeoutErrorCode = 'invalid_json';
await apiFetch( {
path: '/block-lab/migrate-post-content',
method: 'POST',
} ).then( () => {
speak( __( 'The migration was successful!', 'block-lab' ) );
setIsSuccess( true );
} ).catch( async ( result ) => {
if ( result.hasOwnProperty( 'code' ) && timeoutErrorCode === result.code ) {
await migratePostContent();
return;
} else if ( result.hasOwnProperty( 'message' ) ) {
setErrorMessage( result.message );
}
speak( __( 'The migration failed in the post content migration', 'block-lab' ) );
setIsError( true );
} );
};
/**
* Handles all of the migration for this step.
*/
const migrate = async () => {
speak( __( 'The migration is now in progress', 'block-lab' ) );
setErrorMessage( '' );
setIsInProgress( true );
// The post content migration is chained to the callback in then().
await migrateCpt();
setIsInProgress( false );
};
return (
<Step isActive={ isStepActive } isComplete={ isStepComplete }>
<StepIcon
index={ stepIndex }
isComplete={ isStepComplete }
/>
<StepContent
heading={ __( 'Migrate Your Blocks', 'block-lab' ) }
isStepActive={ isStepActive }
>
{ ! isSuccess && <p>{ __( "Okay! Everything is ready. Let's do this. While the migration is underway, don't leave this page.", 'block-lab' ) }</p> }
{ !! errorMessage && (
<div className="bl-migration__error">
<p>{ __( 'The following error ocurred:', 'block-lab' ) }</p>
<p>{ errorMessage }</p>
</div>
) }
{ isInProgress && (
<>
<Spinner />
<p>{ migrationLabels[ currentBlockMigrationStep ] }</p>
</>
) }
{ ! isInProgress && ! isSuccess && (
<button
className="btn"
onClick={ migrate }
>
{ isError ? __( 'Try Again', 'block-lab' ) : __( 'Migrate Now', 'block-lab' ) }
</button>
) }
{ isSuccess && (
<>
<p>
<span role="img" aria-label={ __( 'party emoji', 'block-lab' ) }>🎉</span>
&nbsp;
{ __( 'The migration completed successfully! Time to say goodbye to Block Lab (it’s been fun!) and step into the FUTURE', 'block-lab' ) }
&nbsp;
<span className="message-future">{ __( 'FUTURE', 'block-lab' ) }</span>
&nbsp;
<sub>{ __( 'FUTURE', 'block-lab' ) }</sub>.
</p>
<StepFooter>
{ /* @ts-ignore */ }
<a href={ blockLabMigration.gcbUrl } className="btn">
{ __( 'Go To Genesis Custom Blocks', 'block-lab' ) }
</a>
</StepFooter>
</>
) }
</StepContent>
</Step>
);
};
export default MigrateBlocks;
/**
* External dependencies
*/
import { render } from '@testing-library/react';
import user from '@testing-library/user-event';
/**
* Internal dependencies
*/
import { BackUpSite } from '../';
test( 'back up site migration step', async () => {
const props = {
goToNext: jest.fn(),
isStepActive: true,
isStepComplete: false,
stepIndex: 1,
};
const { getByLabelText, getByText } = render( <BackUpSite { ...props } /> );
getByText( /back up your site/ );
getByText( props.stepIndex.toString() );
// Because the 'confirm' checkbox isn't checked, the 'next' button should be disabled.
user.click( getByText( 'Next Step' ) );
expect( props.goToNext ).not.toHaveBeenCalled();
// Now that the 'confirm' checkbox is checked, the 'next' button should work.
user.click( getByLabelText( 'I have backed up my site.' ) );
user.click( getByText( 'Next Step' ) );
expect( props.goToNext ).toHaveBeenCalled();
} );
/**
* External dependencies
*/
import { fireEvent, render, waitFor } from '@testing-library/react';
import user from '@testing-library/user-event';
/**
* WordPress dependencies
*/
import apiFetch from '@wordpress/api-fetch';
/**
* Internal dependencies
*/
import { GetGenesisPro } from '../';
jest.mock( '@wordpress/api-fetch' );
window.blockLabMigration = {};
test( 'get Genesis Pro migration step', async () => {
apiFetch.mockImplementation( () => new Promise( ( resolve ) => resolve( { success: true } ) ) );
const props = {
goToNext: jest.fn(),
goToPrevious: jest.fn(),
isStepActive: true,
isStepComplete: false,
stepIndex: 1,
};
const { getByText, getByRole } = render( <GetGenesisPro { ...props } /> );
// Because the checkbox isn't checked, the 'next' button should be disabled.
user.click( getByText( 'Next Step' ) );
expect( props.goToNext ).not.toHaveBeenCalled();
fireEvent.change(
getByRole( 'textbox' ),
{ target: { value: '1234567' } }
);
await waitFor( () =>
user.click( getByText( 'Save' ) )
);
getByText( 'Thanks! Your key is valid, and has been saved.' );
user.click( getByText( 'Next Step' ) );
expect( props.goToNext ).toHaveBeenCalled();
} );
/**
* External dependencies
*/
import { render } from '@testing-library/react';
/**
* Internal dependencies
*/
import { InstallActivateGcb } from '../';
global.blockLabMigration = {
activateUrl: 'https://example.com',
};
test( 'activate gcb migration step', async () => {
const props = {
isStepActive: true,
isStepComplete: false,
stepIndex: 5,
};
const { getByText } = render( <InstallActivateGcb { ...props } /> );
expect( getByText( props.stepIndex.toString() ) ).toBeInTheDocument();
} );
/**
* External dependencies
*/
import { render, waitFor } from '@testing-library/react';
import user from '@testing-library/user-event';
/**
* WordPress dependencies
*/
import apiFetch from '@wordpress/api-fetch';
/**
* Internal dependencies
*/
import { MigrateBlocks } from '../';
jest.mock( '@wordpress/api-fetch' );
global.blockLabMigration = {
gcbUrl: 'https://example.com',
};
test( 'migrate blocks step', async () => {
apiFetch.mockImplementation( () => new Promise( ( resolve ) => resolve( { success: true } ) ) );
const props = {
currentStepIndex: 4,
goToNext: jest.fn(),
isStepActive: true,
isStepComplete: false,
stepIndex: 4,
};
const { getByText } = render( <MigrateBlocks { ...props } /> );
getByText( /migrate your blocks/i );
getByText( props.stepIndex.toString() );
await waitFor( () =>
user.click( getByText( 'Migrate Now' ) )
);
expect( getByText( 'The migration was successful!' ) ).toBeInTheDocument();
} );
/**
* External dependencies
*/
import { render, screen } from '@testing-library/react';
import user from '@testing-library/user-event';
/**
* Internal dependencies
*/
import { UpdateHooks } from '../';
describe( 'update hooks migration step', () => {
it( 'displays step content when this step is active', async () => {
const props = {
goToNext: jest.fn(),
goToPrevious: jest.fn(),
isStepActive: true,
isStepComplete: false,
stepIndex: 2,
};
const { getByText } = render( <UpdateHooks { ...props } /> );
getByText( 'Update Hooks & API' );
getByText( props.stepIndex.toString() );
// It should always be possible to click the 'previous' button.
user.click( getByText( 'Previous' ) );
expect( props.goToPrevious ).toHaveBeenCalled();
} );
it( 'does not display content when this step is not active', async () => {
const props = {
goToNext: jest.fn(),
goToPrevious: jest.fn(),
isStepActive: false,
isStepComplete: false,
stepIndex: 2,
};
const { getByText } = render( <UpdateHooks { ...props } /> );
// The heading should still display.
getByText( 'Update Hooks & API' );
getByText( props.stepIndex.toString() );
// The content of the step should now display, as it's not active.
expect( screen.queryByText( 'Previous' ) ).not.toBeInTheDocument();
} );
} );
// @ts-check
/**
* External dependencies
*/
import * as React from 'react';
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
*/
import { ButtonNext, ButtonPrevious, Step, StepContent, StepFooter, StepIcon } from '../';
/**
* @typedef {Object} UpdateHooksProps The component props.
* @property {boolean} isStepActive Whether this step is active.
* @property {boolean} isStepComplete Whether this step is complete.
* @property {number} stepIndex The step index of this step.
* @property {React.EventHandler<React.MouseEvent<HTMLButtonElement, MouseEvent>>} goToNext Goes to the next step.
* @property {React.EventHandler<React.MouseEvent<HTMLButtonElement, MouseEvent>>} goToPrevious Goes to the next step.
*/
/**
* The step that prompts to update hooks.
*
* @param {UpdateHooksProps} Props The component props.
* @return {React.ReactElement} The component to prompt to back up the site.
*/
const UpdateHooks = ( { isStepActive, isStepComplete, stepIndex, goToNext, goToPrevious } ) => {
const hooksDetailsUrl = 'https://developer.wpengine.com/genesis-custom-blocks/block-lab-hook-compatibility/';
const phpApiDetailsUrl = 'https://developer.wpengine.com/genesis-custom-blocks/block-lab-php-api-compatibility/';
return (
<Step isActive={ isStepActive } isComplete={ isStepComplete }>
<StepIcon
index={ stepIndex }
isComplete={ isStepComplete }
/>
<StepContent
heading={ __( 'Update Hooks & API', 'block-lab' ) }
isStepActive={ isStepActive }
>
<p>{ __( 'In most cases, you won’t have to worry about this step. However, there are some instances that will require manual edits to your custom block related files. These are:', 'block-lab' ) }</p>
<ul className="list-disc list-inside mt-2">
<li>
<b>{ __( 'Hooks', 'block-lab' ) }</b> - { __( 'The Block Lab hook names have changed. If you’ve extended Block Lab with custom functionality using these, you’ll need to make some small changes.', 'block-lab' ) }
&nbsp;
<a
href={ hooksDetailsUrl }
target="_blank"
rel="noopener noreferrer"
aria-label={ __( 'More details on the hooks', 'genesis-custom-blocks' ) }
>
{ __( 'More details here.', 'block-lab' ) }
</a>
</li>
<li>
<b>{ __( 'API', 'block-lab' ) }</b> - { __( 'If you use Block Lab’s PHP API or JSON API to register and configure your custom blocks, you’ll need to make some small changes.', 'block-lab' ) }
&nbsp;
<a
href={ phpApiDetailsUrl }
target="_blank"
rel="noopener noreferrer"
aria-label={ __( 'More details on the PHP API', 'genesis-custom-blocks' ) }
>
{ __( 'More details here.', 'block-lab' ) }
</a>
</li>
</ul>
<StepFooter>
<ButtonPrevious onClick={ goToPrevious } />
<ButtonNext
checkboxLabel={ __( "I'm all okay on the hooks and API front.", 'block-lab' ) }
onClick={ goToNext }
stepIndex={ stepIndex }
/>
</StepFooter>
</StepContent>
</Step>
);
};
export default UpdateHooks;
/**
* External dependencies
*/
import { render } from '@testing-library/react';
/**
* Internal dependencies
*/
import App from '../app';
global.blockLabMigration = {
isPro: true,
};
test( 'migration app', async () => {
const { getByText } = render( <App /> );
expect( getByText( 'Migrating to Genesis Custom Blocks' ) ).toBeInTheDocument();
expect( getByText( 'Need to let the developer for this site know about this? Send them this link.' ) ).toBeInTheDocument();
} );
/**
* WordPress dependencies
*/
import domReady from '@wordpress/dom-ready';
import { render } from '@wordpress/element';
/**
* Internal dependencies
*/
import { App } from './components';
// Renders the app in the container.
domReady( () => {
render(
<App />,
document.querySelector( '.bl-migration__content' )
);
} );
<?php
/**
* WP Admin resources.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
namespace Block_Lab\Admin;
use Block_Lab\Component_Abstract;
use Block_Lab\Admin\Migration\Api;
use Block_Lab\Admin\Migration\Subscription_Api;
use Block_Lab\Admin\Migration\Notice;
use Block_Lab\Admin\Migration\Submenu;
/**
* Class Admin
*/
class Admin extends Component_Abstract {
/**
* JSON import.
*
* @var Import
*/
public $import;
/**
* Plugin license.
*
* @var License
*/
public $license;
/**
* User onboarding.
*
* @var Onboarding
*/
public $onboarding;
/**
* Plugin settings.
*
* @var Settings
*/
public $settings;
/**
* Plugin upgrade.
*
* @var Upgrade
*/
public $upgrade;
/**
* The migration API.
*
* @var Api
*/
private $api;
/**
* THe subscription API for the migration.
*
* @var Subscription_Api
*/
private $subscription_api;
/**
* The migration notice.
*
* @var Notice
*/
private $notice;
/**
* The migration submenu under the Block Lab menu item.
*
* @var Submenu
*/
private $submenu;
/**
* Initialise the Admin component.
*/
public function init() {
$this->settings = new Settings();
block_lab()->register_component( $this->settings );
$this->license = new License();
block_lab()->register_component( $this->license );
$this->onboarding = new Onboarding();
block_lab()->register_component( $this->onboarding );
$this->api = new Api();
block_lab()->register_component( $this->api );
$this->subscription_api = new Subscription_Api();
block_lab()->register_component( $this->subscription_api );
$this->notice = new Notice();
block_lab()->register_component( $this->notice );
$this->submenu = new Submenu();
block_lab()->register_component( $this->submenu );
$show_pro_nag = apply_filters( 'block_lab_show_pro_nag', false );
if ( $show_pro_nag && ! block_lab()->is_pro() ) {
$this->upgrade = new Upgrade();
block_lab()->register_component( $this->upgrade );
} else {
$this->maybe_settings_redirect();
}
if ( defined( 'WP_LOAD_IMPORTERS' ) && WP_LOAD_IMPORTERS ) {
$this->import = new Import();
block_lab()->register_component( $this->import );
}
}
/**
* Register any hooks that this component needs.
*/
public function register_hooks() {
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] );
}
/**
* Enqueue scripts and styles used globally in the WP Admin.
*
* @return void
*/
public function enqueue_scripts() {
wp_enqueue_style(
'block-lab',
$this->plugin->get_url( 'css/admin.css' ),
[],
$this->plugin->get_version()
);
}
/**
* Redirect to the Settings screen if the license is being saved.
*/
public function maybe_settings_redirect() {
$page = filter_input( INPUT_GET, 'page', FILTER_SANITIZE_STRING );
if ( 'block-lab-pro' === $page ) {
wp_safe_redirect(
add_query_arg(
[
'post_type' => 'block_lab',
'page' => 'block-lab-settings',
'tab' => 'license',
],
admin_url( 'edit.php' )
)
);
die();
}
}
}
<?php
/**
* Block Lab Importer.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
namespace Block_Lab\Admin;
use Block_Lab\Component_Abstract;
/**
* Class Import
*/
class Import extends Component_Abstract {
/**
* Importer slug.
*
* @var string
*/
public $slug = 'block-lab';
/**
* Register any hooks that this component needs.
*/
public function register_hooks() {
add_action( 'admin_init', [ $this, 'register_importer' ] );
}
/**
* Register the importer for the Tools > Import admin screen
*/
public function register_importer() {
register_importer(
$this->slug,
__( 'Block Lab', 'block-lab' ),
__( 'Import custom blocks created with Block Lab.', 'block-lab' ),
[ $this, 'render_page' ]
);
}
/**
* Render the import page. Manages the three separate stages of the JSON import process.
*/
public function render_page() {
$step = filter_input( INPUT_GET, 'step', FILTER_SANITIZE_NUMBER_INT );
ob_start();
$this->render_page_header();
switch ( $step ) {
case 0:
default:
$this->render_welcome();
break;
case 1:
check_admin_referer( 'import-upload' );
$upload_dir = wp_get_upload_dir();
if ( ! isset( $upload_dir['basedir'] ) ) {
$this->render_import_error(
__( 'Sorry, there was an error uploading the file.', 'block-lab' ),
__( 'Upload base directory not set.', 'block-lab' )
);
}
$cache_dir = $upload_dir['basedir'] . '/block-lab';
$file = wp_import_handle_upload();
if ( $this->validate_upload( $file ) ) {
if ( ! file_exists( $cache_dir ) ) {
mkdir( $cache_dir, 0777, true );
}
// This is on the local filesystem, so file_get_contents() is ok to use here.
file_put_contents( $cache_dir . '/import.json', file_get_contents( $file['file'] ) ); // phpcs:ignore WordPress.WP.AlternativeFunctions
$json = file_get_contents( $file['file'] ); // phpcs:ignore WordPress.WP.AlternativeFunctions
$blocks = json_decode( $json, true );
$this->render_choose_blocks( $blocks );
}
break;
case 2:
$cache_dir = wp_get_upload_dir()['basedir'] . '/block-lab';
$file = [ 'file' => $cache_dir . '/import.json' ];
if ( $this->validate_upload( $file ) ) {
// This is on the local filesystem, so file_get_contents() is ok to use here.
$json = file_get_contents( $file['file'] ); // phpcs:ignore WordPress.WP.AlternativeFunctions
$blocks = json_decode( $json, true );
$import_blocks = [];
foreach ( $blocks as $block_namespace => $block ) {
if ( 'on' === filter_input( INPUT_GET, $block_namespace, FILTER_SANITIZE_STRING ) ) {
$import_blocks[ $block_namespace ] = $block;
}
}
$this->import_blocks( $import_blocks );
}
break;
}
$html = ob_get_clean();
echo '<div class="wrap block-lab-import">' . $html . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* Render the Import page header.
*/
public function render_page_header() {
?>
<h2><?php esc_html_e( 'Import Block Lab Content Blocks', 'block-lab' ); ?></h2>
<?php
}
/**
* Render the welcome message.
*/
public function render_welcome() {
?>
<p><?php esc_html_e( 'Welcome! This importer processes Block Lab JSON files, adding custom blocks to this site.', 'block-lab' ); ?></p>
<p><?php esc_html_e( 'Choose a JSON (.json) file to upload, then click Upload file and import.', 'block-lab' ); ?></p>
<p>
<?php
echo wp_kses(
sprintf(
/* translators: %1$s: an opening anchor tag, %2$s: a closing anchor tag */
__( 'This JSON file should come from the export link or bulk action in the %1$sContent Blocks screen%2$s, not from the main Export tool.', 'block-lab' ),
sprintf(
'<a href="%1$s">',
esc_url(
admin_url(
add_query_arg(
[ 'post_type' => block_lab()->get_post_type_slug() ],
'edit.php'
)
)
)
),
'</a>'
),
[ 'a' => [ 'href' => [] ] ]
);
?>
</p>
<?php
wp_import_upload_form(
add_query_arg(
[
'import' => $this->slug,
'step' => 1,
]
)
);
}
/**
* Render the currently importing block title.
*
* @param string $title The title of the block.
*/
public function render_import_success( $title ) {
echo wp_kses_post(
sprintf(
'<p>%s</p>',
sprintf(
// translators: placeholder refers to title of custom block.
__( 'Successfully imported %1$s.', 'block-lab' ),
'<strong>' . esc_html( $title ) . '</strong>'
)
)
);
}
/**
* Render the currently importing block title.
*
* @param string $title The title of the block.
* @param string $error The error being reported.
*/
public function render_import_error( $title, $error ) {
echo wp_kses_post(
sprintf( '<p><strong>%s</strong></p><p>%s</p>', $title, $error )
);
}
/**
* Render the successful import message.
*/
public function render_done() {
?>
<p><?php esc_html_e( 'All done!', 'block-lab' ); ?></p>
<?php
}
/**
* Render the interface for choosing blocks to update.
*
* @param array $blocks An array of block names to choose from.
*/
public function render_choose_blocks( $blocks ) {
?>
<p><?php esc_html_e( 'Please select the blocks to import:', 'block-lab' ); ?></p>
<form>
<?php
foreach ( $blocks as $block_namespace => $block ) {
$action = __( 'Import', 'block-lab' );
if ( $this->block_exists( $block_namespace ) ) {
$action = __( 'Replace', 'block-lab' );
}
?>
<p>
<input type="checkbox" name="<?php echo esc_attr( $block_namespace ); ?>" id="<?php echo esc_attr( $block_namespace ); ?>" checked>
<label for="<?php echo esc_attr( $block_namespace ); ?>">
<?php echo esc_html( $action ); ?> <strong><?php echo esc_attr( $block['title'] ); ?></strong>
</label>
</p>
<?php
}
wp_nonce_field();
?>
<input type="hidden" name="import" value="block-lab">
<input type="hidden" name="step" value="2">
<p class="submit"><input type="submit" value="<?php esc_attr_e( 'Import Selected', 'block-lab' ); ?>" class="button button-primary"></p>
</form>
<?php
}
/**
* Handles the JSON upload and initial parsing of the file.
*
* @param array $file The file.
* @return bool False if error uploading or invalid file, true otherwise.
*/
public function validate_upload( $file ) {
if ( isset( $file['error'] ) ) {
$this->render_import_error(
__( 'Sorry, there was an error uploading the file.', 'block-lab' ),
$file['error']
);
return false;
} elseif ( ! file_exists( $file['file'] ) ) {
$this->render_import_error(
__( 'Sorry, there was an error uploading the file.', 'block-lab' ),
sprintf(
// translators: placeholder refers to a file directory.
__( 'The export file could not be found at %1$s. It is likely that this was caused by a permissions problem.', 'block-lab' ),
'<code>' . esc_html( $file['file'] ) . '</code>'
)
);
return false;
}
// This is on the local filesystem, so file_get_contents() is ok to use here.
$json = file_get_contents( $file['file'] ); // @codingStandardsIgnoreLine
$data = json_decode( $json, true );
if ( ! is_array( $data ) ) {
$this->render_import_error(
__( 'Sorry, there was an error processing the file.', 'block-lab' ),
__( 'Invalid JSON.', 'block-lab' )
);
return false;
}
return true;
}
/**
* Import data into new Block Lab posts.
*
* @param array $blocks An array of Block Lab content blocks.
*/
public function import_blocks( $blocks ) {
foreach ( $blocks as $block_namespace => $block ) {
if ( ! isset( $block['title'] ) || ! isset( $block['name'] ) ) {
continue;
}
$post_id = false;
if ( $this->block_exists( $block_namespace ) ) {
$post = get_page_by_path( $block['name'], OBJECT, block_lab()->get_post_type_slug() );
if ( $post ) {
$post_id = $post->ID;
}
}
$json = wp_json_encode( [ $block_namespace => $block ], JSON_UNESCAPED_UNICODE );
$post_data = [
'post_title' => $block['title'],
'post_name' => $block['name'],
'post_content' => wp_slash( $json ),
'post_status' => 'publish',
'post_type' => block_lab()->get_post_type_slug(),
];
if ( $post_id ) {
$post_data['ID'] = $post_id;
}
$post = wp_insert_post( $post_data );
if ( is_wp_error( $post ) ) {
$this->render_import_error(
sprintf(
// translators: placeholder refers to title of custom block.
__( 'Error importing %s.', 'block-lab' ),
$block['title']
),
$post->get_error_message()
);
} else {
$this->render_import_success( $block['title'] );
}
}
$this->render_done();
}
/**
* Check if block already exists.
*
* @param string $block_namespace The JSON key for the block. e.g. block-lab/foo.
*
* @return bool
*/
private function block_exists( $block_namespace ) {
$registered_blocks = get_dynamic_block_names();
if ( in_array( $block_namespace, $registered_blocks, true ) ) {
return true;
}
return false;
}
}
<?php
/**
* Enable and validate Pro version licensing.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
namespace Block_Lab\Admin;
use Block_Lab\Component_Abstract;
/**
* Class License
*/
class License extends Component_Abstract {
/**
* Option name of the license key.
*
* @var string
*/
const LICENSE_KEY_OPTION_NAME = 'block_lab_license_key';
/**
* URL of the Block Lab store.
*
* @var string
*/
public $store_url;
/**
* Product slug of the Pro version on the Block Lab store.
*
* @var string
*/
public $product_slug;
/**
* The name of the license key transient.
*
* @var string
*/
const TRANSIENT_NAME = 'block_lab_license';
/**
* The transient 'license' value for when the request to validate the Pro license failed.
*
* This is for when the actual POST request fails,
* not for when it returns that the license is invalid.
*
* @var string
*/
const REQUEST_FAILED = 'request_failed';
/**
* Initialise the Pro component.
*/
public function init() {
$this->store_url = 'https://getblocklab.com';
$this->product_slug = 'block-lab-pro';
}
/**
* Register any hooks that this component needs.
*/
public function register_hooks() {
add_filter( 'pre_update_option_block_lab_license_key', [ $this, 'save_license_key' ] );
}
/**
* Check that the license key is valid before saving.
*
* @param string $key The license key that was submitted.
*
* @return string
*/
public function save_license_key( $key ) {
$this->activate_license( $key );
$license = get_transient( self::TRANSIENT_NAME );
if ( ! $this->is_valid() ) {
$key = '';
if ( isset( $license['license'] ) && self::REQUEST_FAILED === $license['license'] ) {
block_lab()->admin->settings->prepare_notice( $this->license_request_failed_message() );
} else {
block_lab()->admin->settings->prepare_notice( $this->license_invalid_message() );
}
} else {
block_lab()->admin->settings->prepare_notice( $this->license_success_message() );
}
return $key;
}
/**
* Check if the license if valid.
*
* @return bool
*/
public function is_valid() {
$license = $this->get_license();
if ( isset( $license['license'] ) && 'valid' === $license['license'] ) {
if ( isset( $license['expires'] ) && time() < strtotime( $license['expires'] ) ) {
return true;
}
}
return false;
}
/**
* Retrieve the license data.
*
* @return mixed
*/
public function get_license() {
$license = get_transient( self::TRANSIENT_NAME );
if ( ! $license ) {
$key = get_option( self::LICENSE_KEY_OPTION_NAME );
if ( ! empty( $key ) ) {
$this->activate_license( $key );
$license = get_transient( self::TRANSIENT_NAME );
}
}
return $license;
}
/**
* Try to activate the license.
*
* @param string $key The license key to activate.
*/
public function activate_license( $key ) {
// Data to send in our API request.
$api_params = [
'edd_action' => 'activate_license',
'license' => $key,
'item_name' => rawurlencode( $this->product_slug ),
'url' => home_url(),
];
// Call the Block Lab store's API.
$response = wp_remote_post(
$this->store_url,
[
'timeout' => 10,
'sslverify' => true,
'body' => $api_params,
]
);
if ( is_wp_error( $response ) ) {
$license = [ 'license' => self::REQUEST_FAILED ];
} else {
$license = json_decode( wp_remote_retrieve_body( $response ), true );
}
$expiration = DAY_IN_SECONDS;
set_transient( self::TRANSIENT_NAME, $license, $expiration );
}
/**
* Admin notice for correct license details.
*
* @return string
*/
public function license_success_message() {
$message = __( 'Your Block Lab license was successfully activated!', 'block-lab' );
return sprintf( '<div class="notice notice-success"><p>%s</p></div>', esc_html( $message ) );
}
/**
* Admin notice for the license request failing.
*
* This is for when the validation request fails entirely, like with a 404.
* Not for when it returns that the license is invalid.
*
* @return string
*/
public function license_request_failed_message() {
$message = sprintf(
/* translators: %s is an HTML link to contact support */
__( 'There was a problem activating the license, but it may not be invalid. If the problem persists, please %s.', 'block-lab' ),
sprintf(
'<a href="%1$s">%2$s</a>',
'mailto:hi@getblocklab.com?subject=There was a problem activating my Block Lab Pro license',
esc_html__( 'contact support', 'block-lab' )
)
);
return sprintf( '<div class="notice notice-error"><p>%s</p></div>', wp_kses_post( $message ) );
}
/**
* Admin notice for incorrect license details.
*
* @return string
*/
public function license_invalid_message() {
$message = __( 'There was a problem activating your Block Lab license.', 'block-lab' );
return sprintf( '<div class="notice notice-error"><p>%s</p></div>', esc_html( $message ) );
}
}
<?php
/**
* Block Lab Settings.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
namespace Block_Lab\Admin;
use Block_Lab\Component_Abstract;
/**
* Class Settings
*/
class Settings extends Component_Abstract {
/**
* Page slug.
*
* @var string
*/
public $slug = 'block-lab-settings';
/**
* Register any hooks that this component needs.
*/
public function register_hooks() {
add_action( 'admin_menu', [ $this, 'add_submenu_pages' ] );
add_action( 'admin_init', [ $this, 'register_settings' ] );
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] );
add_action( 'admin_notices', [ $this, 'show_notices' ] );
}
/**
* Enqueue scripts and styles used by the Settings screen.
*
* @return void
*/
public function enqueue_scripts() {
$page = filter_input( INPUT_GET, 'page', FILTER_SANITIZE_STRING );
// Enqueue scripts and styles on the edit screen of the Block post type.
if ( $this->slug === $page ) {
wp_enqueue_style(
$this->slug,
$this->plugin->get_url( 'css/admin.settings.css' ),
[],
$this->plugin->get_version()
);
}
}
/**
* Add submenu pages to the Block Lab menu.
*/
public function add_submenu_pages() {
add_submenu_page(
'edit.php?post_type=' . block_lab()->get_post_type_slug(),
__( 'Block Lab Settings', 'block-lab' ),
__( 'Settings', 'block-lab' ),
'manage_options',
$this->slug,
[ $this, 'render_page' ]
);
}
/**
* Register Block Lab settings.
*/
public function register_settings() {
register_setting( 'block-lab-license-key', 'block_lab_license_key' );
}
/**
* Render the Settings page.
*/
public function render_page() {
?>
<div class="wrap block-lab-settings">
<?php
$this->render_page_header();
include block_lab()->get_path() . 'php/views/license.php';
?>
</div>
<?php
}
/**
* Render the Settings page header.
*/
public function render_page_header() {
?>
<h2><?php echo esc_html( get_admin_page_title() ); ?></h2>
<h2 class="nav-tab-wrapper">
<a href="<?php echo esc_url( add_query_arg( 'tab', 'license' ) ); ?>" title="<?php esc_attr_e( 'License', 'block-lab' ); ?>" class="nav-tab nav-tab-active dashicons-before dashicons-nametag">
<?php esc_html_e( 'License', 'block-lab' ); ?>
</a>
<a href="https://getblocklab.com/docs/" target="_blank" class="nav-tab dashicons-before dashicons-info">
<?php esc_html_e( 'Documentation', 'block-lab' ); ?>
</a>
<a href="https://wordpress.org/support/plugin/block-lab/" target="_blank" class="nav-tab dashicons-before dashicons-sos">
<?php esc_html_e( 'Help', 'block-lab' ); ?>
</a>
</h2>
<?php
}
/**
* Prepare notices to be displayed after saving the settings.
*
* @param string $notice The notice text to display.
*/
public function prepare_notice( $notice ) {
$notices = get_option( 'block_lab_notices', [] );
$notices[] = $notice;
update_option( 'block_lab_notices', $notices );
}
/**
* Show any admin notices after saving the settings.
*/
public function show_notices() {
$notices = get_option( 'block_lab_notices', [] );
if ( empty( $notices ) || ! is_array( $notices ) ) {
return;
}
foreach ( $notices as $notice ) {
echo wp_kses_post( $notice );
}
delete_option( 'block_lab_notices' );
}
}
<?php
/**
* Block Lab Upgrade Page.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
namespace Block_Lab\Admin;
use Block_Lab\Component_Abstract;
/**
* Class Upgrade
*/
class Upgrade extends Component_Abstract {
/**
* Page slug.
*
* @var string
*/
public $slug = 'block-lab-pro';
/**
* Register any hooks that this component needs.
*/
public function register_hooks() {
add_action( 'admin_menu', [ $this, 'add_submenu_pages' ] );
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] );
}
/**
* Enqueue scripts and styles used by the Upgrade screen.
*
* @return void
*/
public function enqueue_scripts() {
$page = filter_input( INPUT_GET, 'page', FILTER_SANITIZE_STRING );
// Enqueue scripts and styles on the edit screen of the Block post type.
if ( $this->slug === $page ) {
wp_enqueue_style(
$this->slug,
$this->plugin->get_url( 'css/admin.upgrade.css' ),
[],
$this->plugin->get_version()
);
}
}
/**
* Add submenu pages to the Block Lab menu.
*/
public function add_submenu_pages() {
add_submenu_page(
'edit.php?post_type=block_lab',
__( 'Block Lab Pro', 'block-lab' ),
__( 'Go Pro', 'block-lab' ),
'manage_options',
$this->slug,
[ $this, 'render_page' ]
);
}
/**
* Render the Upgrade page.
*/
public function render_page() {
?>
<div class="wrap block-lab-pro">
<h2 class="screen-reader-text"><?php echo esc_html( get_admin_page_title() ); ?></h2>
<?php include block_lab()->get_path() . 'php/views/upgrade.php'; ?>
</div>
<?php
}
}
<?php
/**
* Migration REST API endpoints.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
namespace Block_Lab\Admin\Migration;
use Plugin_Upgrader;
use WP_Ajax_Upgrader_Skin;
use WP_Error;
use WP_REST_Response;
use Block_Lab\Component_Abstract;
/**
* Class Post_Type
*/
class Api extends Component_Abstract {
/**
* Adds the actions.
*/
public function register_hooks() {
add_action( 'rest_api_init', [ $this, 'register_route_install_gcb' ] );
add_action( 'rest_api_init', [ $this, 'register_route_migrate_post_content' ] );
add_action( 'rest_api_init', [ $this, 'register_route_migrate_post_type' ] );
}
/**
* Registers a route to install and activate the plugin Genesis Custom Blocks.
*/
public function register_route_install_gcb() {
register_rest_route(
block_lab()->get_slug(),
'install-activate-gcb',
[
'methods' => 'POST',
'callback' => [ $this, 'get_install_gcb_response' ],
'permission_callback' => function() {
return current_user_can( 'install_plugins' ) && current_user_can( 'activate_plugins' );
},
]
);
}
/**
* Installs and activates Genesis Custom Blocks, and returns the result.
*
* @param array $data Data sent in the POST request.
* @return WP_REST_Response|WP_Error Response to the request.
*/
public function get_install_gcb_response( $data ) {
unset( $data );
$installation_result = $this->install_plugin();
if ( is_wp_error( $installation_result ) ) {
return $installation_result;
}
$activation_result = $this->activate_plugin();
if ( is_wp_error( $activation_result ) ) {
return $activation_result;
}
return rest_ensure_response( [ 'message' => __( 'Plugin installed and activated', 'block-lab' ) ] );
}
/**
* Installs the new plugin.
*
* Mainly copied from Gutenberg, with slight changes.
* The main change being that it returns true
* if the plugin is already downloaded, not a WP_Error.
*
* @see https://github.com/WordPress/gutenberg/blob/fef0445bf47adc6c8d8b69e19616feb8b6de8c2e/lib/class-wp-rest-plugins-controller.php#L271-L369
* @return true|WP_Error True on success, WP_Error on failure.
*/
private function install_plugin() {
global $wp_filesystem;
require_once ABSPATH . 'wp-admin/includes/file.php';
require_once ABSPATH . 'wp-admin/includes/plugin.php';
require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
require_once ABSPATH . 'wp-admin/includes/class-wp-ajax-upgrader-skin.php';
// Check if the plugin is already installed.
if ( array_key_exists( $this->get_new_plugin_file(), get_plugins() ) ) {
return true;
}
// Verify filesystem is accessible first.
$filesystem_available = $this->is_filesystem_available();
if ( is_wp_error( $filesystem_available ) ) {
return $filesystem_available;
}
$download_link = $this->get_download_link();
if ( is_wp_error( $download_link ) ) {
return $download_link;
}
$skin = new WP_Ajax_Upgrader_Skin();
$upgrader = new Plugin_Upgrader( $skin );
$result = $upgrader->install( $download_link );
if ( is_wp_error( $result ) ) {
$result->add_data( [ 'status' => 500 ] );
return $result;
}
// This should be the same as $result above.
if ( is_wp_error( $skin->result ) ) {
$skin->result->add_data( [ 'status' => 500 ] );
return $skin->result;
}
if ( $skin->get_errors()->has_errors() ) {
$error = $skin->get_errors();
$error->add_data( [ 'status' => 500 ] );
return $error;
}
if ( is_null( $result ) ) {
// Pass through the error from WP_Filesystem if one was raised.
if ( $wp_filesystem instanceof WP_Filesystem_Base && isset( $wp_filesystem->errors ) && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
return new WP_Error( 'unable_to_connect_to_filesystem', $wp_filesystem->errors->get_error_message(), [ 'status' => 500 ] );
}
return new WP_Error( 'unable_to_connect_to_filesystem', __( 'Unable to connect to the filesystem. Please confirm your credentials.', 'block-lab' ), [ 'status' => 500 ] );
}
$file = $upgrader->plugin_info();
if ( ! $file ) {
return new WP_Error( 'unable_to_determine_installed_plugin', __( 'Unable to determine what plugin was installed.', 'block-lab' ), [ 'status' => 500 ] );
}
return true;
}
/**
* Determines if the filesystem is available.
*
* Only the 'Direct' filesystem transport, and SSH/FTP when credentials are stored are supported at present.
* Copied from Gutenberg.
*
* @see https://github.com/WordPress/gutenberg/blob/8d64aa3092d5d9e841895bf2d495565c9a770238/lib/class-wp-rest-plugins-controller.php#L799-L815
*
* @return true|WP_Error True if filesystem is available, WP_Error otherwise.
*/
private function is_filesystem_available() {
if ( 'direct' === get_filesystem_method() ) {
return true;
}
ob_start();
$filesystem_credentials_are_stored = request_filesystem_credentials( self_admin_url() );
ob_end_clean();
if ( $filesystem_credentials_are_stored ) {
return true;
}
return new WP_Error( 'fs_unavailable', __( 'The filesystem is currently unavailable for managing plugins.', 'block-lab' ), [ 'status' => 500 ] );
}
/**
* Gets the GCB Pro download link.
*
* @return string|WP_Error The download link, or a WP_Error.
*/
public function get_download_link() {
if ( ! empty( get_transient( Subscription_Api::TRANSIENT_NAME_GCB_PRO_DOWNLOAD_LINK ) ) ) {
return get_transient( Subscription_Api::TRANSIENT_NAME_GCB_PRO_DOWNLOAD_LINK );
} else {
$api = plugins_api(
'plugin_information',
[
'slug' => 'genesis-custom-blocks',
'fields' => [
'sections' => false,
],
]
);
if ( is_wp_error( $api ) ) {
if ( false !== strpos( $api->get_error_message(), 'Plugin not found.' ) ) {
$api->add_data( [ 'status' => 404 ] );
} else {
$api->add_data( [ 'status' => 500 ] );
}
return $api;
}
if ( empty( $api->download_link ) ) {
return new WP_Error(
'no_download_link',
__( 'There was no download_link in the API', 'block-lab' )
);
}
return $api->download_link;
}
}
/**
* Activates the new plugin.
*
* Mainly copied from Gutenberg's WP_REST_Plugins_Controller::handle_plugin_status().
*
* @see https://github.com/WordPress/gutenberg/blob/fef0445bf47adc6c8d8b69e19616feb8b6de8c2e/lib/class-wp-rest-plugins-controller.php#L679-L709
*
* @return true|WP_Error True on success, WP_Error on failure.
*/
private function activate_plugin() {
$activation_result = activate_plugin( $this->get_new_plugin_file(), '', false, true );
if ( is_wp_error( $activation_result ) ) {
$activation_result->add_data( [ 'status' => 500 ] );
return $activation_result;
}
return true;
}
/**
* Registers a route to migrate the post content to the new namespace.
*/
public function register_route_migrate_post_content() {
register_rest_route(
block_lab()->get_slug(),
'migrate-post-content',
[
'methods' => 'POST',
'callback' => [ $this, 'get_migrate_post_content_response' ],
'permission_callback' => function() {
return current_user_can( Submenu::MIGRATION_CAPABILITY );
},
]
);
}
/**
* Gets the REST API response for the post content migration.
*
* @return WP_REST_Response The response to the request.
*/
public function get_migrate_post_content_response() {
return rest_ensure_response( ( new Post_Content( 'block-lab', 'genesis-custom-blocks' ) )->migrate_all() );
}
/**
* Registers a route to migrate the post type.
*/
public function register_route_migrate_post_type() {
register_rest_route(
block_lab()->get_slug(),
'migrate-post-type',
[
'methods' => 'POST',
'callback' => [ $this, 'get_migrate_post_type_response' ],
'permission_callback' => function() {
return current_user_can( Submenu::MIGRATION_CAPABILITY );
},
]
);
}
/**
* Gets the REST API response for the post type migration.
*
* @return WP_REST_Response The response to the request.
*/
public function get_migrate_post_type_response() {
return rest_ensure_response( ( new Post_Type( 'block_lab', 'block-lab', 'block_lab', 'genesis_custom_block', 'genesis-custom-blocks', 'genesis_custom_blocks' ) )->migrate_all() );
}
/**
* Gets the directory and file of the new plugin to install.
*
* @return string The plugin file.
*/
public function get_new_plugin_file() {
if ( ! empty( get_transient( Subscription_Api::TRANSIENT_NAME_GCB_PRO_DOWNLOAD_LINK ) ) ) {
return 'genesis-custom-blocks-pro/genesis-custom-blocks-pro.php';
}
return 'genesis-custom-blocks/genesis-custom-blocks.php';
}
}
<?php
/**
* Displays a migration notice.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
namespace Block_Lab\Admin\Migration;
use Block_Lab\Component_Abstract;
/**
* Class Notice
*/
class Notice extends Component_Abstract {
/**
* The AJAX action to dismiss the migration notice.
*
* @var string
*/
const NOTICE_AJAX_ACTION = 'bl_dismiss_migration_notice';
/**
* The action of the migration notice nonce.
*
* @var string
*/
const NOTICE_NONCE_ACTION = 'bl-migration-nonce';
/**
* The name of the migration notice nonce.
*
* @var string
*/
const NOTICE_NONCE_NAME = 'bl-migration-nonce-name';
/**
* The slug of the stylesheet for the migration notice.
*
* @var string
*/
const NOTICE_STYLE_SLUG = 'block-lab-migration-notice-style';
/**
* The slug of the script for the migration notice.
*
* @var string
*/
const NOTICE_SCRIPT_SLUG = 'block-lab-migration-notice-script';
/**
* The user meta key to store whether a user has dismissed the migration notice.
*
* @var string
*/
const NOTICE_USER_META_KEY = 'block_lab_show_migration_notice';
/**
* The user meta value stored if a user has dismissed the migration notice.
*
* @var string
*/
const NOTICE_DISMISSED_META_VALUE = 'dismissed';
/**
* The capability required to see the notice.
*
* @var string
*/
const NOTICE_CAPABILITY = 'install_plugins';
/**
* Adds an action for the notice.
*/
public function register_hooks() {
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
add_action( 'wp_ajax_' . self::NOTICE_AJAX_ACTION, [ $this, 'ajax_handler_migration_notice' ] );
}
/**
* Outputs the migration notice if this is on the right page and the user has the right permission.
*/
public function render_migration_notice() {
if ( ! $this->should_display_migration_notice() ) {
return;
}
// @todo: verify that this doc page exists, or change it to one that does exist.
$learn_more_link = 'https://getblocklab.com/docs/genesis-custom-blocks';
$migration_url = add_query_arg(
[
'post_type' => block_lab()->get_post_type_slug(),
'page' => 'block-lab-migration',
],
admin_url( 'edit.php' )
);
?>
<div id="bl-migration-notice" class="notice notice-info bl-notice-migration">
<?php wp_nonce_field( self::NOTICE_NONCE_ACTION, self::NOTICE_NONCE_NAME, false ); ?>
<div class="bl-migration-copy">
<p>
<?php
printf(
/* translators: %1$s: the plugin name */
esc_html__( 'The Block Lab team have moved. For future updates and improvements, migrate now to the new home of custom blocks: %1$s.', 'block-lab' ),
sprintf(
'<strong>%1$s</strong>',
esc_html__( 'Genesis Custom Blocks', 'block-lab' )
)
);
?>
<a target="_blank" rel="noopener noreferrer" class="bl-notice-migration__learn-more" href="<?php echo esc_url( $learn_more_link ); ?>">
<?php esc_html_e( 'Learn more', 'block-lab' ); ?>
</a>
</p>
</div>
<button id="bl-notice-not-now" href="#" class="bl-notice-option button button-secondary">
<?php esc_html_e( 'Not Now', 'block-lab' ); ?>
</button>
<a href="<?php echo esc_url( $migration_url ); ?>" class="bl-notice-option button button-primary">
<?php esc_html_e( 'Migrate', 'block-lab' ); ?>
</a>
</div>
<div id="bl-not-now-notice" class="notice notice-info bl-notice-migration bl-hidden">
<div class="bl-migration-copy">
<p><?php esc_html_e( "When you're ready, our migration tool is available in the main menu, under Block Lab > Migrate.", 'block-lab' ); ?></p>
</div>
<a href="#" id="bl-notice-ok" class="bl-notice-option">
<?php esc_html_e( 'Okay', 'block-lab' ); ?>
</a>
</div>
<?php
}
/**
* Enqueues the migration notice assets.
*/
public function enqueue_assets() {
if ( ! $this->should_display_migration_notice() ) {
return;
}
wp_enqueue_style(
self::NOTICE_STYLE_SLUG,
$this->plugin->get_url( 'css/admin.migration-notice.css' ),
[],
$this->plugin->get_version()
);
wp_enqueue_script(
self::NOTICE_SCRIPT_SLUG,
$this->plugin->get_url( 'js/admin.migration-notice.js' ),
[],
$this->plugin->get_version(),
true
);
}
/**
* Gets whether the migration notice should display.
*
* This should display on Block Lab > Content Blocks,
* /wp-admin/plugins.php, the Dashboard, and Block Lab > Settings.
*
* @return bool Whether the migration notice should display.
*/
public function should_display_migration_notice() {
if ( ! current_user_can( self::NOTICE_CAPABILITY ) ) {
return false;
}
// If the user has dismissed the notice, it shouldn't appear again.
if ( self::NOTICE_DISMISSED_META_VALUE === get_user_meta( get_current_user_id(), self::NOTICE_USER_META_KEY, true ) ) {
return false;
}
$screen = get_current_screen();
return (
( isset( $screen->base, $screen->post_type ) && 'edit' === $screen->base && 'block_lab' === $screen->post_type )
||
( isset( $screen->base ) && in_array( $screen->base, [ 'plugins', 'dashboard', 'block_lab_page_block-lab-settings' ], true ) )
);
}
/**
* Handles an AJAX request to not display the notice.
*
* This stores in the user meta the fact that the notice was dismissed,
* so it's not displayed again.
*/
public function ajax_handler_migration_notice() {
check_ajax_referer( self::NOTICE_NONCE_ACTION, self::NOTICE_NONCE_NAME );
if ( ! current_user_can( self::NOTICE_CAPABILITY ) ) {
wp_send_json_error();
}
update_user_meta( get_current_user_id(), self::NOTICE_USER_META_KEY, self::NOTICE_DISMISSED_META_VALUE );
wp_send_json_success();
}
}
<?php
/**
* Post_Content.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
namespace Block_Lab\Admin\Migration;
use WP_Error;
/**
* Class Post_Content
*/
class Post_Content {
/**
* The previous namespace of the block.
*
* @var string
*/
private $previous_block_namespace;
/**
* The new namespace of the block.
*
* @var string
*/
private $new_block_namespace;
/**
* Post_Content constructor.
*
* @param string $previous_block_namespace Previous namespace of the blocks.
* @param string $new_block_namespace New namespace of the blocks.
*/
public function __construct( $previous_block_namespace, $new_block_namespace ) {
$this->previous_block_namespace = $previous_block_namespace;
$this->new_block_namespace = $new_block_namespace;
}
/**
* Migrates all of the block namespaces in all of the posts that have Block Lab blocks.
*
* @return array|WP_Error The result of the migration, or a WP_Error if it failed.
*/
public function migrate_all() {
$success_count = 0;
$error_count = 0;
$errors = new WP_Error();
$max_allowed_errors = 20;
$posts = $this->query_for_posts();
while ( ! empty( $posts ) && $error_count < $max_allowed_errors ) {
foreach ( $posts as $post ) {
if ( isset( $post->ID ) ) {
$migrated_post = $this->migrate_single( $post->ID );
if ( is_wp_error( $migrated_post ) ) {
$error_count++;
$errors->add( $migrated_post->get_error_code(), $migrated_post->get_error_message() );
} else {
$success_count++;
}
}
}
$posts = $this->query_for_posts();
}
$is_success = $error_count < $max_allowed_errors;
if ( ! $is_success ) {
return $errors;
}
$results = [
'successCount' => $success_count,
'errorCount' => $error_count,
];
if ( $errors->has_errors() ) {
$results['errorMessage'] = $errors->get_error_message();
}
return $results;
}
/**
* Migrates the block namespaces in post_content.
*
* Blocks are stored in the post_content of a post with a namespace,
* like '<!-- wp:block-lab/test-image {"example-image":8} /-->'.
* In that case, 'block-lab' needs to be changed to the new namespace.
* But nothing else in the block should be changed.
* The block pattern is mainly taken from Core.
*
* @see https://github.com/WordPress/wordpress-develop/blob/78d1ab2ed40093a5bd2a75b01ceea37811739f55/src/wp-includes/class-wp-block-parser.php#L413
*
* @param int $post_id The ID of the post to convert.
* @return int|WP_Error The post ID that was changed, or a WP_Error on failure.
*/
public function migrate_single( $post_id ) {
$post = get_post( $post_id );
if ( ! isset( $post->ID ) ) {
return new WP_Error(
'invalid_post_id',
__( 'Invalid post ID', 'block-lab' )
);
}
$new_post_content = preg_replace(
'#(<!--\s+wp:)(' . sanitize_key( $this->previous_block_namespace ) . ')(/[a-z][a-z0-9_-]*)#s',
'$1' . sanitize_key( $this->new_block_namespace ) . '$3',
$post->post_content
);
return wp_update_post(
[
'ID' => $post->ID,
'post_content' => wp_slash( $new_post_content ),
],
true
);
}
/**
* Gets posts that have Block Lab blocks in their post_content.
*
* Queries for posts that have wp:block-lab/ in the post content,
* meaning they probably have a Block Lab block.
* Excludes revision posts, as this could overwrite the entire history.
* This will allow users to go back to the content before it was migrated.
*
* @return array The posts that were found.
*/
private function query_for_posts() {
global $wpdb;
$query_limit = 10;
return $wpdb->get_results(
$wpdb->prepare(
"SELECT * FROM {$wpdb->posts} WHERE post_type != %s AND post_content LIKE %s LIMIT %d",
'revision',
'%' . $wpdb->esc_like( 'wp:' . $this->previous_block_namespace . '/' ) . '%',
absint( $query_limit )
)
);
}
}
<?php
/**
* Post_Type.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
namespace Block_Lab\Admin\Migration;
use WP_Error;
use WP_Post;
use WP_Query;
/**
* Class Post_Type
*/
class Post_Type {
/**
* The previous slug of the custom post type (in Block Lab).
*
* @var string
*/
private $previous_post_type_slug;
/**
* The previous namespace of the block.
*
* @var string
*/
private $previous_block_namespace;
/**
* The previous default block icon.
*
* @var string
*/
private $previous_default_icon;
/**
* The new namespace of the block.
*
* @var string
*/
private $new_block_namespace;
/**
* The new slug of the custom post type (not in Block Lab).
*
* @var string
*/
private $new_post_type_slug;
/**
* The new default block icon.
*
* @var string
*/
private $new_default_icon;
/**
* Post_Type constructor.
*
* @param string $previous_post_type_slug Previous slug of the custom post type.
* @param string $previous_block_namespace Previous block namespace.
* @param string $previous_default_icon Previous default block icon.
* @param string $new_post_type_slug New slug of the custom post type.
* @param string $new_block_namespace New namespace of the block.
* @param string $new_default_icon New default block icon.
*/
public function __construct( $previous_post_type_slug, $previous_block_namespace, $previous_default_icon, $new_post_type_slug, $new_block_namespace, $new_default_icon ) {
$this->previous_post_type_slug = $previous_post_type_slug;
$this->previous_block_namespace = $previous_block_namespace;
$this->previous_default_icon = $previous_default_icon;
$this->new_post_type_slug = $new_post_type_slug;
$this->new_block_namespace = $new_block_namespace;
$this->new_default_icon = $new_default_icon;
}
/**
* Migrates all of the custom post type posts to the new post_type slug and block namespace.
*
* These each store a config for a custom block,
* they aren't blocks that users entered into the block editor.
*
* @return array|WP_Error An array on success, a WP_Error on failure.
*/
public function migrate_all() {
$posts = $this->query_for_posts();
$success_count = 0;
$error_count = 0;
$max_allowed_errors = 20;
$errors = new WP_Error();
while ( ! empty( $posts ) && $error_count < $max_allowed_errors ) {
foreach ( $posts as $post ) {
$migration_result = $this->migrate_single( $post );
if ( is_wp_error( $migration_result ) ) {
$error_count++;
$errors->add( $migration_result->get_error_code(), $migration_result->get_error_message() );
} else {
$success_count++;
}
}
$posts = $this->query_for_posts();
}
$is_success = ! empty( $success_count ) || ( empty( $success_count ) && empty( $error_count ) );
if ( ! $is_success ) {
return $errors;
}
return [
'successCount' => $success_count,
'errorCount' => $error_count,
];
}
/**
* Migrates the custom post type post to the new post_type slug and block namespace.
*
* Inspired by the work of Weston Ruter: https://github.com/ampproject/amp-wp/blob/4880f0f58daaf07685854be8574ff25d76ff583e/includes/validation/class-amp-validated-url-post-type.php#L165-L170
* The post_content of the CPT has a configuration for a block like:
* '{"block-lab\/test-image":{"name":"test-image","title":"Test Image","excluded":[],"icon":"block_lab","category":{"slug":"common","title":"Common Blocks","icon":null},"keywords":[""],"fields":{"image":{"name":"image","label":"Image","control":"image","type":"integer","order":0,"location":"editor","width":"50","help":"Here is some help text"}}}}'
* The beginning of this has the 'block-lab' namespace, which this changes to the new namespace.
*
* @param WP_Post $post The post to convert.
* @return true|WP_Error True on success, WP_Error on failure.
*/
public function migrate_single( WP_Post $post ) {
global $wpdb;
$block = json_decode( $post->post_content, true );
if ( JSON_ERROR_NONE !== json_last_error() || empty( $block ) ) {
return new WP_Error(
'block_invalid_json',
__( 'The block looks to be invalid JSON', 'block-lab' )
);
}
$block_keys = array_keys( $block );
$old_block_name = reset( $block_keys );
if ( empty( $block[ $old_block_name ] ) ) {
return new WP_Error(
'invalid_block_name',
__( 'The block name looks to be invalid', 'block-lab' )
);
}
$block_contents = $block[ $old_block_name ];
if ( isset( $block_contents['icon'] ) && $this->previous_default_icon === $block_contents['icon'] ) {
$block_contents['icon'] = $this->new_default_icon;
}
if ( empty( $block_contents['icon'] ) ) {
$block_contents['icon'] = $this->new_default_icon;
}
$new_block_name = preg_replace( '#^' . $this->previous_block_namespace . '(?=/)#', $this->new_block_namespace, $old_block_name );
$new_block = [ $new_block_name => $block_contents ];
$rows_updated = $wpdb->update(
$wpdb->posts,
[
'post_type' => sanitize_key( $this->new_post_type_slug ),
'post_content' => wp_json_encode( $new_block ),
],
[
'ID' => $post->ID,
]
);
clean_post_cache( $post->ID );
if ( empty( $rows_updated ) ) {
return new WP_Error(
'post_not_updated',
__( 'The post was not updated', 'block-lab' )
);
}
return true;
}
/**
* Gets the posts of the previous post_type.
*
* This query won't find posts that were already migrated, as the migration changes the 'post_type'.
* So this doesn't need an 'offset' parameter.
*
* @return WP_Post[] The posts that were found.
*/
private function query_for_posts() {
$query = new WP_Query(
[
'post_type' => $this->previous_post_type_slug,
'posts_per_page' => 10,
'post_status' => 'any',
]
);
return $query->posts;
}
}
<?php
/**
* Migration submenu.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
namespace Block_Lab\Admin\Migration;
use Block_Lab\Component_Abstract;
use Block_Lab\Admin\License;
/**
* Class Post_Type
*/
class Submenu extends Component_Abstract {
/**
* The menu slug for the migration menu.
*
* @var string
*/
const MENU_SLUG = 'block-lab-migration';
/**
* The user capability to migrate posts and post content.
*
* @var string
*/
const MIGRATION_CAPABILITY = 'edit_others_posts';
/**
* The query var to deactivate this plugin and activate the new one.
*
* @var string
*/
const QUERY_VAR_DEACTIVATE_AND_GCB_PAGE = 'bl_deactivate_and_gcb';
/**
* The query var to deactivate this plugin and activate the new one.
*
* @var string
*/
const NONCE_ACTION_DEACTIVATE = 'deactivate_bl_and_activate_new';
/**
* Adds the actions.
*/
public function register_hooks() {
add_action( 'admin_menu', [ $this, 'add_submenu_page' ], 9 );
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] );
add_action( 'admin_bar_init', [ $this, 'maybe_activate_plugin' ] );
}
/**
* Adds the submenu page for migration.
*/
public function add_submenu_page() {
if ( $this->user_can_view_migration_page() ) {
add_submenu_page(
'edit.php?post_type=block_lab',
__( 'Migrate to Genesis Custom Blocks', 'block-lab' ),
__( 'Migrate', 'block-lab' ),
'manage_options',
self::MENU_SLUG,
[ $this, 'render_page' ]
);
}
}
/**
* Adds the scripts for the submenu.
*/
public function enqueue_scripts() {
$page = filter_input( INPUT_GET, 'page', FILTER_SANITIZE_STRING );
// Only enqueue if on the migration page.
if ( self::MENU_SLUG === $page && $this->user_can_view_migration_page() ) {
wp_enqueue_style(
self::MENU_SLUG,
block_lab()->get_url( 'css/admin.migration.css' ),
[],
block_lab()->get_version()
);
$script_config = require block_lab()->get_path( 'js/admin.migration.asset.php' );
wp_enqueue_script(
self::MENU_SLUG,
block_lab()->get_url( 'js/admin.migration.js' ),
$script_config['dependencies'],
$script_config['version'],
true
);
$gcb_url = add_query_arg(
[
self::QUERY_VAR_DEACTIVATE_AND_GCB_PAGE => true,
'_wpnonce' => wp_create_nonce( self::NONCE_ACTION_DEACTIVATE ),
],
admin_url()
);
$is_pro = block_lab()->is_pro();
$genesis_pro_subscription_key = get_option( Subscription_Api::OPTION_NAME_GENESIS_PRO_SUBSCRIPTION_KEY );
$script_data = [
'isPro' => $is_pro,
'gcbUrl' => $gcb_url,
];
if ( $genesis_pro_subscription_key ) {
$script_data['genesisProKey'] = $genesis_pro_subscription_key;
}
wp_add_inline_script(
self::MENU_SLUG,
'const blockLabMigration = ' . wp_json_encode( $script_data ) . ';',
'before'
);
}
}
/**
* Gets whether the current user can view the migration page.
*
* @return bool Whether the user can view the migration page.
*/
public function user_can_view_migration_page() {
return current_user_can( 'install_plugins' ) && current_user_can( self::MIGRATION_CAPABILITY );
}
/**
* Renders the submenu page.
*/
public function render_page() {
echo '<div class="bl-migration__content"></div>';
}
/**
* Conditionally deactivates this plugin and goes to the Genesis Custom Blocks page.
*
* The logic to deactivate the plugin was mainly copied from Core.
* https://github.com/WordPress/wordpress-develop/blob/61803a37a41eca95efe964c7e02c768de6df75fa/src/wp-admin/plugins.php#L196-L221
*/
public function maybe_activate_plugin() {
$previous_plugin_file = 'block-lab/block-lab.php';
if ( empty( $_GET[ self::QUERY_VAR_DEACTIVATE_AND_GCB_PAGE ] ) ) {
return;
}
if ( ! current_user_can( 'deactivate_plugin', $previous_plugin_file ) ) {
wp_die( esc_html__( 'Sorry, you are not allowed to deactivate this plugin.', 'block-lab' ) );
}
check_admin_referer( self::NONCE_ACTION_DEACTIVATE );
if ( ! is_network_admin() && is_plugin_active_for_network( $previous_plugin_file ) ) {
wp_die( esc_html__( 'Sorry, you are not allowed to deactivate this network-active plugin.', 'block-lab' ) );
}
deactivate_plugins( $previous_plugin_file, false, is_network_admin() );
if ( ! is_network_admin() ) {
update_option( 'recently_activated', [ $previous_plugin_file => time() ] + (array) get_option( 'recently_activated' ) );
} else {
update_site_option( 'recently_activated', [ $previous_plugin_file => time() ] + (array) get_site_option( 'recently_activated' ) );
}
// Go to the Genesis Custom Blocks page.
wp_safe_redirect(
add_query_arg(
'post_type',
'genesis_custom_block',
admin_url( 'edit.php' )
)
);
}
}
<?php
/**
* Verifies the Genesis Pro subscription, and saves the link to download GCB Pro.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
namespace Block_Lab\Admin\Migration;
use WP_Error;
use WP_REST_Request;
use WP_REST_Response;
use Block_Lab\Component_Abstract;
/**
* Class Subscription_Api
*/
class Subscription_Api extends Component_Abstract {
/**
* Option name where the subscription key is stored for Genesis Pro plugins.
*
* @var string
*/
const OPTION_NAME_GENESIS_PRO_SUBSCRIPTION_KEY = 'genesis_pro_subscription_key';
/**
* Transient name where the subscription endpoint response is stored.
*
* @var string
*/
const TRANSIENT_NAME_GCB_PRO_DOWNLOAD_LINK = 'genesis_custom_blocks_pro_download_link';
/**
* Adds the component action.
*/
public function register_hooks() {
add_action( 'rest_api_init', [ $this, 'register_route_update_subscription_key' ] );
}
/**
* Registers a route to update the subscription key.
*/
public function register_route_update_subscription_key() {
register_rest_route(
block_lab()->get_slug(),
'update-subscription-key',
[
'methods' => 'POST',
'callback' => [ $this, 'get_update_subscription_key_response' ],
'permission_callback' => function() {
return current_user_can( 'manage_options' );
},
'accept_json' => true,
]
);
}
/**
* Gets the REST API response to the request to update the subscription key.
*
* @param WP_REST_Request $data Data sent in the POST request.
* @return WP_REST_Response|WP_Error A WP_REST_Response on success, WP_Error on failure.
*/
public function get_update_subscription_key_response( $data ) {
$key = $data->get_param( 'subscriptionKey' );
if ( empty( $key ) ) {
$this->delete_subscription_data();
return new WP_Error( 'empty_subscription_key', __( 'Empty subscription key', 'block-lab' ) );
}
$sanitized_key = $this->sanitize_subscription_key( $key );
$subscription_response = $this->get_subscription_response( $sanitized_key );
if ( $subscription_response->is_valid() && ! empty( $subscription_response->get_product_info()->download_link ) ) {
$was_option_update_successful = update_option( self::OPTION_NAME_GENESIS_PRO_SUBSCRIPTION_KEY, $sanitized_key );
if ( ! $was_option_update_successful ) {
$existing_option = get_option( self::OPTION_NAME_GENESIS_PRO_SUBSCRIPTION_KEY );
// update_option() will return false when trying to save the same option that's already saved.
// In that case, there's no need for an error, but any other failure should be an error.
if ( $sanitized_key !== $existing_option ) {
$this->delete_subscription_data();
return new WP_Error( 'option_not_updated', __( 'The option was not updated', 'block-lab' ) );
}
}
set_transient(
self::TRANSIENT_NAME_GCB_PRO_DOWNLOAD_LINK,
esc_url_raw( $subscription_response->get_product_info()->download_link )
);
return rest_ensure_response( [ 'success' => true ] );
} else {
$this->delete_subscription_data();
return new WP_Error(
$subscription_response->get_error_code(),
$this->get_subscription_invalid_message( $subscription_response->get_error_code() )
);
}
}
/**
* Deletes the stored Genesis Pro key and the GCB Pro download link.
*/
public function delete_subscription_data() {
delete_option( self::OPTION_NAME_GENESIS_PRO_SUBSCRIPTION_KEY );
delete_transient( self::TRANSIENT_NAME_GCB_PRO_DOWNLOAD_LINK );
}
/**
* Gets a new subscription response.
*
* @param string $key The subscription key to check.
* @return Subscription_Response The subscription response.
*/
public function get_subscription_response( $key ) {
return new Subscription_Response( $key );
}
/**
* Admin message for incorrect subscription details.
*
* @param string $error_code The error code from the endpoint.
* @return string The error message to display.
*/
public function get_subscription_invalid_message( $error_code ) {
switch ( $error_code ) {
case 'key-unknown':
return esc_html__( 'The subscription key you entered appears to be invalid or is not associated with this product. Please verify the key you have saved here matches the key in your WP Engine Account Portal.', 'block-lab' );
case 'key-invalid':
return esc_html__( 'The subscription key you entered is invalid. Get your subscription key in the WP Engine Account Portal.', 'block-lab' );
case 'key-deleted':
return esc_html__( 'Your subscription key was regenerated in the WP Engine Account Portal but was not updated in this settings page. Update your subscription key here to receive updates.', 'block-lab' );
case 'subscription-expired':
return esc_html__( 'Your Genesis Pro subscription has expired. Please renew it.', 'block-lab' );
case 'subscription-notfound':
return esc_html__( 'A valid subscription for your subscription key was not found. Please contact support.', 'block-lab' );
case 'product-unknown':
return esc_html__( 'The product you requested information for is unknown. Please contact support.', 'block-lab' );
default:
return esc_html__( 'There was an unknown error connecting to the update service. Please ensure the key you have saved here matches the key in your WP Engine Account Portal. This issue could be temporary. Please contact support if this error persists.', 'block-lab' );
}
}
/**
* Gets the sanitized subscription key.
*
* @param string $subscription_key The subscription key.
* @return string The sanitized key.
*/
public function sanitize_subscription_key( $subscription_key ) {
return preg_replace( '/[^A-Za-z0-9_-]/', '', $subscription_key );
}
}
<?php
/**
* The Genesis Pro subscription response.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
namespace Block_Lab\Admin\Migration;
use stdClass;
/**
* Class Subscription_Response
*/
class Subscription_Response {
/**
* Endpoint to validate the Genesis Pro subscription key.
*
* @var string
*/
const ENDPOINT = 'https://wp-product-info.wpesvc.net/v1/plugins/genesis-custom-blocks-pro/subscriptions/';
/**
* The code expected in a success response.
*
* @var string
*/
const SUCCESS_CODE = 200;
/**
* Whether the subscription key is valid.
*
* @var bool
*/
private $is_valid = false;
/**
* The error code, if any.
*
* @var string|null
*/
private $error_code;
/**
* The product info.
*
* @var stdClass|null
*/
private $product_info;
/**
* Constructs the class.
*
* @param string $subscription_key The subscription key to check.
*/
public function __construct( $subscription_key ) {
$this->evaluate( $subscription_key );
}
/**
* Evaluates the response, storing the response body and a possible error message.
*
* @param string $subscription_key The subscription key to check.
*/
public function evaluate( $subscription_key ) {
$response = wp_remote_get(
self::ENDPOINT . $subscription_key,
[
'timeout' => defined( 'DOING_CRON' ) && DOING_CRON ? 30 : 3,
'user-agent' => 'WordPress/' . get_bloginfo( 'version' ) . '; ' . get_bloginfo( 'url' ),
'body' => [
'version' => block_lab()->get_version(),
],
]
);
if ( is_wp_error( $response ) || self::SUCCESS_CODE !== wp_remote_retrieve_response_code( $response ) ) {
if ( is_wp_error( $response ) ) {
$this->error_code = $response->get_error_code();
} else {
$response_body = json_decode( wp_remote_retrieve_body( $response ), false );
$this->error_code = ! empty( $response_body->error_code ) ? $response_body->error_code : 'unknown';
}
return;
}
$this->is_valid = true;
$this->product_info = new stdClass();
$response_body = json_decode( wp_remote_retrieve_body( $response ) );
if ( ! is_object( $response_body ) ) {
$response_body = new stdClass();
}
$this->product_info = $response_body;
}
/**
* Gets whether the subscription key is valid.
*
* @return bool
*/
public function is_valid() {
return $this->is_valid;
}
/**
* Gets the error code, if any.
*
* @return string|null
*/
public function get_error_code() {
return $this->error_code;
}
/**
* Gets the product info, or null if there isn't any.
*
* @return stdClass|null
*/
public function get_product_info() {
return $this->product_info;
}
}
<?php
/**
* Plugin Autoloader
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
/**
* Register Autoloader
*/
spl_autoload_register(
function ( $class ) {
// Assume we're using namespaces (because that's how the plugin is structured).
$namespace = explode( '\\', $class );
$root = array_shift( $namespace );
// If a class ends with "Trait" then prefix the filename with 'trait-', else use 'class-'.
$class_trait = preg_match( '/Trait$/', $class ) ? 'trait-' : 'class-';
// If we're not in the plugin's namespace then just return.
if ( 'Block_Lab' !== $root ) {
return;
}
// Class name is the last part of the FQN.
$class_name = array_pop( $namespace );
// Remove "Trait" from the class name.
if ( 'trait-' === $class_trait ) {
$class_name = str_replace( 'Trait', '', $class_name );
}
$filename = $class_trait . $class_name . '.php';
// For file naming, the namespace is everything but the class name and the root namespace.
$namespace = trim( implode( DIRECTORY_SEPARATOR, $namespace ) );
// Because WordPress file naming conventions are odd.
$filename = strtolower( str_replace( '_', '-', $filename ) );
$namespace = strtolower( str_replace( '_', '-', $namespace ) );
// Get the path to our files.
$directory = dirname( __FILE__ );
if ( ! empty( $namespace ) ) {
$directory .= DIRECTORY_SEPARATOR . $namespace;
}
$file = $directory . DIRECTORY_SEPARATOR . $filename;
if ( file_exists( $file ) ) {
require_once $file;
}
}
);
<?php
/**
* Block.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
namespace Block_Lab\Blocks;
/**
* Class Block
*/
class Block {
/**
* Block name (slug).
*
* @var string
*/
public $name = '';
/**
* Block title.
*
* @var string
*/
public $title = '';
/**
* Exclude the block in these post types.
*
* @var array
*/
public $excluded = [];
/**
* Icon.
*
* @var string
*/
public $icon = '';
/**
* Category. An array containing the keys slug, title, and icon.
*
* @var array
*/
public $category = [
'slug' => '',
'title' => '',
'icon' => '',
];
/**
* Block keywords.
*
* @var string[]
*/
public $keywords = [];
/**
* Block fields.
*
* @var Field[]
*/
public $fields = [];
/**
* Block constructor.
*
* @param int|bool $post_id Post ID.
*
* @return void
*/
public function __construct( $post_id = false ) {
if ( ! $post_id ) {
return;
}
$post = get_post( $post_id );
if ( ! $post instanceof \WP_Post ) {
return;
}
$this->name = $post->post_name;
$this->from_json( $post->post_content );
}
/**
* Construct the Block from a JSON blob.
*
* @param string $json JSON blob.
*
* @return void
*/
public function from_json( $json ) {
$json = json_decode( $json, true );
if ( ! isset( $json[ 'block-lab/' . $this->name ] ) ) {
return;
}
$config = $json[ 'block-lab/' . $this->name ];
$this->from_array( $config );
}
/**
* Construct the Block from a config array.
*
* @param array $config An array containing field parameters.
*
* @return void
*/
public function from_array( $config ) {
if ( isset( $config['name'] ) ) {
$this->name = $config['name'];
}
if ( isset( $config['title'] ) ) {
$this->title = $config['title'];
}
if ( isset( $config['excluded'] ) ) {
$this->excluded = $config['excluded'];
}
if ( isset( $config['icon'] ) ) {
$this->icon = $config['icon'];
}
if ( isset( $config['category'] ) ) {
$this->category = $config['category'];
if ( ! is_array( $this->category ) ) {
$this->category = $this->get_category_array_from_slug( $this->category );
}
}
if ( isset( $config['keywords'] ) ) {
$this->keywords = $config['keywords'];
}
if ( isset( $config['fields'] ) ) {
foreach ( $config['fields'] as $key => $field ) {
$this->fields[ $key ] = new Field( $field );
}
}
}
/**
* Get the Block as a JSON blob.
*
* @return string
*/
public function to_json() {
$config['name'] = $this->name;
$config['title'] = $this->title;
$config['excluded'] = $this->excluded;
$config['icon'] = $this->icon;
$config['category'] = $this->category;
$config['keywords'] = $this->keywords;
$config['fields'] = [];
foreach ( $this->fields as $key => $field ) {
$config['fields'][ $key ] = $field->to_array();
}
return wp_json_encode( [ 'block-lab/' . $this->name => $config ], JSON_UNESCAPED_UNICODE );
}
/**
* This is a backwards compatibility fix.
*
* Block categories used to be saved as strings, but were always included in
* the default list of categories, so we can find them.
*
* It's not possible to use get_block_categories() here, as Block's are
* sometimes instantiated before that function is available.
*
* @param string $slug The category slug to find.
*
* @return array
*/
public function get_category_array_from_slug( $slug ) {
return [
'slug' => $slug,
'title' => ucwords( $slug, '-' ),
'icon' => null,
];
}
}
<?php
/**
* Block Field.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
namespace Block_Lab\Blocks;
/**
* Class Field
*/
class Field {
/**
* Field name (slug).
*
* @var string
*/
public $name = '';
/**
* Field label.
*
* @var string
*/
public $label = '';
/**
* Field control type.
*
* @var string
*/
public $control = 'text';
/**
* Field variable type.
*
* @var string
*/
public $type = 'string';
/**
* Field order.
*
* @var int
*/
public $order = 0;
/**
* Field settings.
*
* @var array
*/
public $settings = [];
/**
* Field constructor.
*
* @param array $config An associative array with keys corresponding to the Field's properties.
*/
public function __construct( $config = [] ) {
$this->from_array( $config );
}
/**
* Get field properties as an array, ready to be stored as JSON.
*
* @return array
*/
public function to_array() {
$config = [
'name' => $this->name,
'label' => $this->label,
'control' => $this->control,
'type' => $this->type,
'order' => $this->order,
];
$config = array_merge(
$config,
$this->settings
);
// Handle the sub-fields setting used by the Repeater.
if ( isset( $this->settings['sub_fields'] ) ) {
/**
* Recursively loop through sub-fields.
*
* @var string $key The name of the sub-field's parent.
* @var Field $field The sub-field.
*/
foreach ( $this->settings['sub_fields'] as $key => $field ) {
$config['sub_fields'][ $key ] = $field->to_array();
}
}
return $config;
}
/**
* Set field properties from an array, after being stored as JSON.
*
* @param array $config An array containing field parameters.
*/
public function from_array( $config ) {
if ( isset( $config['name'] ) ) {
$this->name = $config['name'];
}
if ( isset( $config['label'] ) ) {
$this->label = $config['label'];
}
if ( isset( $config['control'] ) ) {
$this->control = $config['control'];
}
if ( isset( $config['type'] ) ) {
$this->type = $config['type'];
}
if ( isset( $config['order'] ) ) {
$this->order = $config['order'];
}
if ( isset( $config['settings'] ) ) {
$this->settings = $config['settings'];
}
if ( ! isset( $config['type'] ) ) {
$control_class_name = 'Block_Lab\\Blocks\\Controls\\';
$control_class_name .= ucwords( $this->control, '_' );
if ( class_exists( $control_class_name ) ) {
/**
* An instance of the control, to retrieve the correct type.
*
* @var Control_Abstract $control_class
*/
$control_class = new $control_class_name();
$this->type = $control_class->type;
}
}
// Add any other non-default keys to the settings array.
$field_defaults = [ 'name', 'label', 'control', 'type', 'order', 'settings' ];
$field_settings = array_diff( array_keys( $config ), $field_defaults );
foreach ( $field_settings as $settings_key ) {
$this->settings[ $settings_key ] = $config[ $settings_key ];
}
// Handle the sub-fields setting used by the Repeater.
if ( isset( $this->settings['sub_fields'] ) ) {
/**
* Recursively loop through sub-fields.
*/
foreach ( $this->settings['sub_fields'] as $key => $field ) {
$this->settings['sub_fields'][ $key ] = new Field( $field );
}
}
}
/**
* Return the value with the correct variable type.
*
* @param mixed $value The value to typecast.
* @return mixed
*/
public function cast_value( $value ) {
switch ( $this->type ) {
case 'string':
$value = strval( $value );
break;
case 'textarea':
$value = strval( $value );
if ( isset( $this->settings['new_lines'] ) ) {
if ( 'autop' === $this->settings['new_lines'] ) {
$value = wpautop( $value );
}
if ( 'autobr' === $this->settings['new_lines'] ) {
$value = nl2br( $value );
}
}
break;
case 'boolean':
if ( 1 === $value ) {
$value = true;
}
break;
case 'integer':
$value = intval( $value );
break;
case 'array':
if ( ! $value ) {
$value = [];
} else {
$value = (array) $value;
}
break;
}
return $value;
}
/**
* Gets the field value as a string.
*
* @param mixed $value The field value.
*
* @return string $value The value to echo.
*/
public function cast_value_to_string( $value ) {
if ( is_array( $value ) ) {
return implode( ', ', $value );
}
if ( true === $value ) {
return __( 'Yes', 'block-lab' );
}
if ( false === $value ) {
return __( 'No', 'block-lab' );
}
return strval( $value );
}
}
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.