Version Description
Download this release
Release Info
| Developer | antoniosejas |
| Plugin | |
| Version | 3.36079 |
| Comparing to | |
| See all releases | |
Code changes from version 3.35918 to 3.36079
- build_meta.txt +3 -3
- full-site-editing-plugin.php +10 -2
- jetpack-timeline/blocks/src/style.scss +1 -0
- jetpack-timeline/dist/jetpack-timeline.asset.php +1 -1
- jetpack-timeline/dist/jetpack-timeline.css +1 -1
- jetpack-timeline/dist/jetpack-timeline.css.map +1 -1
- jetpack-timeline/dist/jetpack-timeline.rtl.css +1 -1
- phpunit.xml.dist +3 -0
- readme.txt +1 -1
- tutorials/class-wpcom-tutorials.php +253 -0
- tutorials/test/tutorials-test.php +154 -0
- tutorials/tutorials.php +75 -0
build_meta.txt
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
-
commit_hash=
|
| 2 |
-
commit_url=https://github.com/Automattic/wp-calypso/commit/
|
| 3 |
-
build_number=3.
|
| 1 |
+
commit_hash=71848faa7a136cbb435e3a6e360ab9e003d97f5a
|
| 2 |
+
commit_url=https://github.com/Automattic/wp-calypso/commit/71848faa7a136cbb435e3a6e360ab9e003d97f5a
|
| 3 |
+
build_number=3.36079
|
full-site-editing-plugin.php
CHANGED
|
@@ -2,7 +2,7 @@
|
|
| 2 |
/**
|
| 3 |
* Plugin Name: WordPress.com Editing Toolkit
|
| 4 |
* Description: Enhances your page creation workflow within the Block Editor.
|
| 5 |
-
* Version: 3.
|
| 6 |
* Author: Automattic
|
| 7 |
* Author URI: https://automattic.com/wordpress-plugins/
|
| 8 |
* License: GPLv2 or later
|
|
@@ -42,7 +42,7 @@ namespace A8C\FSE;
|
|
| 42 |
*
|
| 43 |
* @var string
|
| 44 |
*/
|
| 45 |
-
define( 'A8C_ETK_PLUGIN_VERSION', '3.
|
| 46 |
|
| 47 |
// Always include these helper files for dotcom FSE.
|
| 48 |
require_once __DIR__ . '/dotcom-fse/helpers.php';
|
|
@@ -428,3 +428,11 @@ function load_block_description_links() {
|
|
| 428 |
require_once __DIR__ . '/wpcom-block-description-links/class-wpcom-block-description-links.php';
|
| 429 |
}
|
| 430 |
add_action( 'plugins_loaded', __NAMESPACE__ . '\load_block_description_links' );
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
/**
|
| 3 |
* Plugin Name: WordPress.com Editing Toolkit
|
| 4 |
* Description: Enhances your page creation workflow within the Block Editor.
|
| 5 |
+
* Version: 3.36079
|
| 6 |
* Author: Automattic
|
| 7 |
* Author URI: https://automattic.com/wordpress-plugins/
|
| 8 |
* License: GPLv2 or later
|
| 42 |
*
|
| 43 |
* @var string
|
| 44 |
*/
|
| 45 |
+
define( 'A8C_ETK_PLUGIN_VERSION', '3.36079' );
|
| 46 |
|
| 47 |
// Always include these helper files for dotcom FSE.
|
| 48 |
require_once __DIR__ . '/dotcom-fse/helpers.php';
|
| 428 |
require_once __DIR__ . '/wpcom-block-description-links/class-wpcom-block-description-links.php';
|
| 429 |
}
|
| 430 |
add_action( 'plugins_loaded', __NAMESPACE__ . '\load_block_description_links' );
|
| 431 |
+
|
| 432 |
+
/**
|
| 433 |
+
* Load Tutorials API
|
| 434 |
+
*/
|
| 435 |
+
function load_tutorials() {
|
| 436 |
+
require_once __DIR__ . '/tutorials/tutorials.php';
|
| 437 |
+
}
|
| 438 |
+
add_action( 'plugins_loaded', __NAMESPACE__ . '\load_tutorials' );
|
jetpack-timeline/blocks/src/style.scss
CHANGED
|
@@ -78,6 +78,7 @@ $timeline-border-width: 4px;
|
|
| 78 |
// Bubbles.
|
| 79 |
.wp-block-jetpack-timeline-item {
|
| 80 |
width: calc( 50% - #{ $timeline-width } + #{ $timeline-border-width * 0.5 } );
|
|
|
|
| 81 |
}
|
| 82 |
|
| 83 |
// Left aligned.
|
| 78 |
// Bubbles.
|
| 79 |
.wp-block-jetpack-timeline-item {
|
| 80 |
width: calc( 50% - #{ $timeline-width } + #{ $timeline-border-width * 0.5 } );
|
| 81 |
+
box-sizing: border-box;
|
| 82 |
}
|
| 83 |
|
| 84 |
// Left aligned.
|
jetpack-timeline/dist/jetpack-timeline.asset.php
CHANGED
|
@@ -1 +1 @@
|
|
| 1 |
-
<?php return array('dependencies' => array('wp-block-editor', 'wp-blocks', 'wp-components', 'wp-data', 'wp-element', 'wp-i18n', 'wp-polyfill', 'wp-primitives'), 'version' => '
|
| 1 |
+
<?php return array('dependencies' => array('wp-block-editor', 'wp-blocks', 'wp-components', 'wp-data', 'wp-element', 'wp-i18n', 'wp-polyfill', 'wp-primitives'), 'version' => 'f0d32f10451f52017160');
|
jetpack-timeline/dist/jetpack-timeline.css
CHANGED
|
@@ -1,2 +1,2 @@
|
|
| 1 |
[data-type="jetpack/timeline"] .block-list-appender{display:none}[data-type="jetpack/timeline"].is-selected .block-list-appender,[data-type="jetpack/timeline"].has-child-selected .block-list-appender{display:block}.components-button.timeline-item-appender{background:#1e1e1e;border-radius:2px;color:#fff;min-width:24px;height:24px;margin-right:12px;padding-left:0;padding-right:8px;animation:none !important}.components-button.timeline-item-appender:hover{color:#fff}
|
| 2 |
-
.wp-block-jetpack-timeline.wp-block-jetpack-timeline{padding:0}.wp-block-jetpack-timeline li.wp-block-jetpack-timeline-item{list-style-type:none;position:relative;padding:1em 2em;margin-bottom:36px;margin-left:36px}.wp-block-jetpack-timeline li.wp-block-jetpack-timeline-item .timeline-item__bubble{display:block;width:36px;height:4px;background-color:currentColor;position:absolute;top:50%;transform:translateY(-2px);left:-36px}.wp-block-jetpack-timeline li.wp-block-jetpack-timeline-item .timeline-item::after{content:"";display:block;background:currentColor;position:absolute;left:-36px;top:-36px;bottom:-36px;width:4px}.wp-block-jetpack-timeline [data-type="jetpack/timeline-item"]:first-child .timeline-item::after,.wp-block-jetpack-timeline>li.wp-block-jetpack-timeline-item:first-child .timeline-item::after{top:50%}.wp-block-jetpack-timeline [data-type="jetpack/timeline-item"]:nth-last-child(2) .timeline-item::after,.wp-block-jetpack-timeline>li.wp-block-jetpack-timeline-item:last-child .timeline-item::after{bottom:50%}@media only screen and (min-width: 640px){ul.wp-block-jetpack-timeline.is-alternating{display:flex;flex-direction:column}ul.wp-block-jetpack-timeline.is-alternating .wp-block-jetpack-timeline-item{width:calc( 50% - 36px + 2px )}ul.wp-block-jetpack-timeline.is-alternating .wp-block-jetpack-timeline-item.is-left.is-left.is-left,ul.wp-block-jetpack-timeline.is-alternating [data-type="jetpack/timeline-item"]:nth-child(odd) .wp-block-jetpack-timeline-item:not(.is-right),ul.wp-block-jetpack-timeline.is-alternating>.wp-block-jetpack-timeline-item:nth-child(odd):not(.is-right){margin-left:0;margin-right:auto}ul.wp-block-jetpack-timeline.is-alternating .wp-block-jetpack-timeline-item.is-left.is-left.is-left .timeline-item__bubble,ul.wp-block-jetpack-timeline.is-alternating [data-type="jetpack/timeline-item"]:nth-child(odd) .wp-block-jetpack-timeline-item:not(.is-right) .timeline-item__bubble,ul.wp-block-jetpack-timeline.is-alternating>.wp-block-jetpack-timeline-item:nth-child(odd):not(.is-right) .timeline-item__bubble{left:auto;right:-36px}ul.wp-block-jetpack-timeline.is-alternating .wp-block-jetpack-timeline-item.is-left.is-left.is-left .timeline-item::after,ul.wp-block-jetpack-timeline.is-alternating [data-type="jetpack/timeline-item"]:nth-child(odd) .wp-block-jetpack-timeline-item:not(.is-right) .timeline-item::after,ul.wp-block-jetpack-timeline.is-alternating>.wp-block-jetpack-timeline-item:nth-child(odd):not(.is-right) .timeline-item::after{left:auto;right:-36px}ul.wp-block-jetpack-timeline.is-alternating .wp-block-jetpack-timeline-item.is-right.is-right.is-right,ul.wp-block-jetpack-timeline.is-alternating [data-type="jetpack/timeline-item"]:nth-child(even) .wp-block-jetpack-timeline-item:not(.is-left),ul.wp-block-jetpack-timeline.is-alternating .wp-block-jetpack-timeline-item:nth-child(even):not(.is-left){margin-left:auto;margin-right:0}}
|
| 1 |
[data-type="jetpack/timeline"] .block-list-appender{display:none}[data-type="jetpack/timeline"].is-selected .block-list-appender,[data-type="jetpack/timeline"].has-child-selected .block-list-appender{display:block}.components-button.timeline-item-appender{background:#1e1e1e;border-radius:2px;color:#fff;min-width:24px;height:24px;margin-right:12px;padding-left:0;padding-right:8px;animation:none !important}.components-button.timeline-item-appender:hover{color:#fff}
|
| 2 |
+
.wp-block-jetpack-timeline.wp-block-jetpack-timeline{padding:0}.wp-block-jetpack-timeline li.wp-block-jetpack-timeline-item{list-style-type:none;position:relative;padding:1em 2em;margin-bottom:36px;margin-left:36px}.wp-block-jetpack-timeline li.wp-block-jetpack-timeline-item .timeline-item__bubble{display:block;width:36px;height:4px;background-color:currentColor;position:absolute;top:50%;transform:translateY(-2px);left:-36px}.wp-block-jetpack-timeline li.wp-block-jetpack-timeline-item .timeline-item::after{content:"";display:block;background:currentColor;position:absolute;left:-36px;top:-36px;bottom:-36px;width:4px}.wp-block-jetpack-timeline [data-type="jetpack/timeline-item"]:first-child .timeline-item::after,.wp-block-jetpack-timeline>li.wp-block-jetpack-timeline-item:first-child .timeline-item::after{top:50%}.wp-block-jetpack-timeline [data-type="jetpack/timeline-item"]:nth-last-child(2) .timeline-item::after,.wp-block-jetpack-timeline>li.wp-block-jetpack-timeline-item:last-child .timeline-item::after{bottom:50%}@media only screen and (min-width: 640px){ul.wp-block-jetpack-timeline.is-alternating{display:flex;flex-direction:column}ul.wp-block-jetpack-timeline.is-alternating .wp-block-jetpack-timeline-item{width:calc( 50% - 36px + 2px );box-sizing:border-box}ul.wp-block-jetpack-timeline.is-alternating .wp-block-jetpack-timeline-item.is-left.is-left.is-left,ul.wp-block-jetpack-timeline.is-alternating [data-type="jetpack/timeline-item"]:nth-child(odd) .wp-block-jetpack-timeline-item:not(.is-right),ul.wp-block-jetpack-timeline.is-alternating>.wp-block-jetpack-timeline-item:nth-child(odd):not(.is-right){margin-left:0;margin-right:auto}ul.wp-block-jetpack-timeline.is-alternating .wp-block-jetpack-timeline-item.is-left.is-left.is-left .timeline-item__bubble,ul.wp-block-jetpack-timeline.is-alternating [data-type="jetpack/timeline-item"]:nth-child(odd) .wp-block-jetpack-timeline-item:not(.is-right) .timeline-item__bubble,ul.wp-block-jetpack-timeline.is-alternating>.wp-block-jetpack-timeline-item:nth-child(odd):not(.is-right) .timeline-item__bubble{left:auto;right:-36px}ul.wp-block-jetpack-timeline.is-alternating .wp-block-jetpack-timeline-item.is-left.is-left.is-left .timeline-item::after,ul.wp-block-jetpack-timeline.is-alternating [data-type="jetpack/timeline-item"]:nth-child(odd) .wp-block-jetpack-timeline-item:not(.is-right) .timeline-item::after,ul.wp-block-jetpack-timeline.is-alternating>.wp-block-jetpack-timeline-item:nth-child(odd):not(.is-right) .timeline-item::after{left:auto;right:-36px}ul.wp-block-jetpack-timeline.is-alternating .wp-block-jetpack-timeline-item.is-right.is-right.is-right,ul.wp-block-jetpack-timeline.is-alternating [data-type="jetpack/timeline-item"]:nth-child(even) .wp-block-jetpack-timeline-item:not(.is-left),ul.wp-block-jetpack-timeline.is-alternating .wp-block-jetpack-timeline-item:nth-child(even):not(.is-left){margin-left:auto;margin-right:0}}
|
jetpack-timeline/dist/jetpack-timeline.css.map
CHANGED
|
@@ -1 +1 @@
|
|
| 1 |
-
{"version":3,"file":"jetpack-timeline.css","mappings":"AAIC,oDACC,aAGD,uIAEC,cAMF,0CAWC,yBAVA,mBACA,kBACA,WAEA,YACA,kBAFA,eAGA,eACA,iBAGA,CAEA,gDACC,WCjBD,qDACC,UAGD,6DACC,qBAKA,kBAlBe,CAqBf,iBANA,gBADA,iBAde,CAwBf,oFAIC,8BAHA,cAEA,UAzBqB,CA8BrB,WAHA,kBACA,QACA,2BALA,UAMA,CAID,kFAGC,wBAIA,aANA,WACA,cAGA,WADA,kBAEA,UAEA,SA1CqB,CA+CvB,8LAEC,QAGD,mMAEC,WAWF,yCACC,4CACC,aACA,sBAGA,
|
| 1 |
+
{"version":3,"file":"jetpack-timeline.css","mappings":"AAIC,oDACC,aAGD,uIAEC,cAMF,0CAWC,yBAVA,mBACA,kBACA,WAEA,YACA,kBAFA,eAGA,eACA,iBAGA,CAEA,gDACC,WCjBD,qDACC,UAGD,6DACC,qBAKA,kBAlBe,CAqBf,iBANA,gBADA,iBAde,CAwBf,oFAIC,8BAHA,cAEA,UAzBqB,CA8BrB,WAHA,kBACA,QACA,2BALA,UAMA,CAID,kFAGC,wBAIA,aANA,WACA,cAGA,WADA,kBAEA,UAEA,SA1CqB,CA+CvB,8LAEC,QAGD,mMAEC,WAWF,yCACC,4CACC,aACA,sBAGA,4EAEC,sBADA,sBACA,CAID,4VAGC,cACA,kBASA,4zBACC,UACA,YAKF,2VAGC,iBACA","sources":["webpack://EditingToolkit/./editing-toolkit-plugin/jetpack-timeline/blocks/src/editor.scss","webpack://EditingToolkit/./editing-toolkit-plugin/jetpack-timeline/blocks/src/style.scss"],"sourcesContent":["// Editor-only styles.\n[data-type='jetpack/timeline'] {\n\n\t// Always show the Timeline appender, even when a child block is selected.\n\t.block-list-appender {\n\t\tdisplay: none;\n\t}\n\n\t&.is-selected .block-list-appender,\n\t&.has-child-selected .block-list-appender {\n\t\tdisplay: block;\n\t}\n}\n\n// We replicate the appender look here.\n// @todo: once https://github.com/WordPress/gutenberg/issues/16659 we should replace the button with the actual appender.\n.components-button.timeline-item-appender {\n\tbackground: #1e1e1e;\n\tborder-radius: 2px;\n\tcolor: #fff;\n\tmin-width: 24px;\n\theight: 24px;\n\tmargin-right: 12px;\n\tpadding-left: 0;\n\tpadding-right: 8px;\n\n\t// Temporarily make this important. It can be retired once this custom appender is replaced with the real one.\n\tanimation: none !important;\n\n\t&:hover {\n\t\tcolor: #fff;\n\t}\n}\n","/**\n * Timeline Block styles.\n */\n\n// Variables.\n$timeline-width: 36px;\n$timeline-gutter: 24px;\n$timeline-border-width: 4px;\n\n// Styles shared between editor and frontend.\n.wp-block-jetpack-timeline {\n\n\t// This padding needs extra specificity.\n\t&.wp-block-jetpack-timeline {\n\t\tpadding: 0;\n\t}\n\n\tli.wp-block-jetpack-timeline-item {\n\t\tlist-style-type: none;\n\t\tposition: relative;\n\t\tpadding: 1em 2em;\n\n\t\t// Make the spacing between items consistent so we can connect the timeline\n\t\tmargin-bottom: $timeline-width;\n\n\t\t// Make room for the timeline.\n\t\tmargin-left: $timeline-width;\n\n\t\t// Draw the line connecting to the timeline.\n\t\t.timeline-item__bubble {\n\t\t\tdisplay: block;\n\t\t\twidth: $timeline-width;\n\t\t\theight: $timeline-border-width;\n\t\t\tbackground-color: currentColor;\n\t\t\tposition: absolute;\n\t\t\ttop: 50%;\n\t\t\ttransform: translateY( -( $timeline-border-width * 0.5 ) );\n\t\t\tleft: -$timeline-width;\n\t\t}\n\n\t\t// Draw the vertical timeline line.\n\t\t.timeline-item::after {\n\t\t\tcontent: '';\n\t\t\tdisplay: block;\n\t\t\tbackground: currentColor;\n\t\t\tposition: absolute;\n\t\t\tleft: -$timeline-width;\n\t\t\ttop: -$timeline-width;\n\t\t\tbottom: -$timeline-width;\n\t\t\twidth: $timeline-border-width;\n\t\t}\n\t}\n\n\t// Add special timeline starting point and end point.\n\t[data-type='jetpack/timeline-item']:first-child .timeline-item::after, // Editor\n\t& > li.wp-block-jetpack-timeline-item:first-child .timeline-item::after { // Frontend\n\t\ttop: 50%;\n\t}\n\n\t[data-type='jetpack/timeline-item']:nth-last-child( 2 ) .timeline-item::after, // Editor, is the 2nd last child here\n\t& > li.wp-block-jetpack-timeline-item:last-child .timeline-item::after { // Frontend\n\t\tbottom: 50%;\n\t}\n\n}\n\n\n/**\n * Alternating Bubbles\n */\n\n\n@media only screen and ( min-width: 640px ) {\n\tul.wp-block-jetpack-timeline.is-alternating {\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\n\t\t// Bubbles.\n\t\t.wp-block-jetpack-timeline-item {\n\t\t\twidth: calc( 50% - #{ $timeline-width } + #{ $timeline-border-width * 0.5 } );\n\t\t\tbox-sizing: border-box;\n\t\t}\n\n\t\t// Left aligned.\n\t\t.wp-block-jetpack-timeline-item.is-left.is-left.is-left, // Both, explicitly set. Needs specificity.\n\t\t[data-type='jetpack/timeline-item']:nth-child( odd ) .wp-block-jetpack-timeline-item:not( .is-right ), // Editor\n\t\t& > .wp-block-jetpack-timeline-item:nth-child( odd ):not( .is-right ) { // Frontend\n\t\t\tmargin-left: 0;\n\t\t\tmargin-right: auto;\n\n\t\t\t// Adjust the the line connecting to the timeline.\n\t\t\t.timeline-item__bubble {\n\t\t\t\tleft: auto;\n\t\t\t\tright: -$timeline-width;\n\t\t\t}\n\n\t\t\t// Adjust the vertical line.\n\t\t\t.timeline-item::after {\n\t\t\t\tleft: auto;\n\t\t\t\tright: -$timeline-width;\n\t\t\t}\n\t\t}\n\n\t\t// Right aligned.\n\t\t.wp-block-jetpack-timeline-item.is-right.is-right.is-right, // Both, explicitly set. Needs specificity.\n\t\t[data-type='jetpack/timeline-item']:nth-child( even ) .wp-block-jetpack-timeline-item:not( .is-left ), // Editor\n\t\t.wp-block-jetpack-timeline-item:nth-child( even ):not( .is-left ) { // Frontend\n\t\t\tmargin-left: auto;\n\t\t\tmargin-right: 0;\n\t\t}\n\t}\n}\n"],"names":[],"sourceRoot":""}
|
jetpack-timeline/dist/jetpack-timeline.rtl.css
CHANGED
|
@@ -1 +1 @@
|
|
| 1 |
-
[data-type="jetpack/timeline"] .block-list-appender{display:none}[data-type="jetpack/timeline"].has-child-selected .block-list-appender,[data-type="jetpack/timeline"].is-selected .block-list-appender{display:block}.components-button.timeline-item-appender{animation:none!important;background:#1e1e1e;border-radius:2px;color:#fff;height:24px;margin-left:12px;min-width:24px;padding-left:8px;padding-right:0}.components-button.timeline-item-appender:hover{color:#fff}.wp-block-jetpack-timeline.wp-block-jetpack-timeline{padding:0}.wp-block-jetpack-timeline li.wp-block-jetpack-timeline-item{list-style-type:none;margin-bottom:36px;margin-right:36px;padding:1em 2em;position:relative}.wp-block-jetpack-timeline li.wp-block-jetpack-timeline-item .timeline-item__bubble{background-color:currentColor;display:block;height:4px;position:absolute;right:-36px;top:50%;transform:translateY(-2px);width:36px}.wp-block-jetpack-timeline li.wp-block-jetpack-timeline-item .timeline-item:after{background:currentColor;bottom:-36px;content:"";display:block;position:absolute;right:-36px;top:-36px;width:4px}.wp-block-jetpack-timeline>li.wp-block-jetpack-timeline-item:first-child .timeline-item:after,.wp-block-jetpack-timeline [data-type="jetpack/timeline-item"]:first-child .timeline-item:after{top:50%}.wp-block-jetpack-timeline>li.wp-block-jetpack-timeline-item:last-child .timeline-item:after,.wp-block-jetpack-timeline [data-type="jetpack/timeline-item"]:nth-last-child(2) .timeline-item:after{bottom:50%}@media only screen and (min-width:640px){ul.wp-block-jetpack-timeline.is-alternating{display:flex;flex-direction:column}ul.wp-block-jetpack-timeline.is-alternating .wp-block-jetpack-timeline-item{width:calc(50% - 34px)}ul.wp-block-jetpack-timeline.is-alternating .wp-block-jetpack-timeline-item.is-left.is-left.is-left,ul.wp-block-jetpack-timeline.is-alternating>.wp-block-jetpack-timeline-item:nth-child(odd):not(.is-right),ul.wp-block-jetpack-timeline.is-alternating [data-type="jetpack/timeline-item"]:nth-child(odd) .wp-block-jetpack-timeline-item:not(.is-right){margin-left:auto;margin-right:0}ul.wp-block-jetpack-timeline.is-alternating .wp-block-jetpack-timeline-item.is-left.is-left.is-left .timeline-item:after,ul.wp-block-jetpack-timeline.is-alternating .wp-block-jetpack-timeline-item.is-left.is-left.is-left .timeline-item__bubble,ul.wp-block-jetpack-timeline.is-alternating>.wp-block-jetpack-timeline-item:nth-child(odd):not(.is-right) .timeline-item:after,ul.wp-block-jetpack-timeline.is-alternating>.wp-block-jetpack-timeline-item:nth-child(odd):not(.is-right) .timeline-item__bubble,ul.wp-block-jetpack-timeline.is-alternating [data-type="jetpack/timeline-item"]:nth-child(odd) .wp-block-jetpack-timeline-item:not(.is-right) .timeline-item:after,ul.wp-block-jetpack-timeline.is-alternating [data-type="jetpack/timeline-item"]:nth-child(odd) .wp-block-jetpack-timeline-item:not(.is-right) .timeline-item__bubble{left:-36px;right:auto}ul.wp-block-jetpack-timeline.is-alternating .wp-block-jetpack-timeline-item.is-right.is-right.is-right,ul.wp-block-jetpack-timeline.is-alternating .wp-block-jetpack-timeline-item:nth-child(2n):not(.is-left),ul.wp-block-jetpack-timeline.is-alternating [data-type="jetpack/timeline-item"]:nth-child(2n) .wp-block-jetpack-timeline-item:not(.is-left){margin-left:0;margin-right:auto}}
|
| 1 |
+
[data-type="jetpack/timeline"] .block-list-appender{display:none}[data-type="jetpack/timeline"].has-child-selected .block-list-appender,[data-type="jetpack/timeline"].is-selected .block-list-appender{display:block}.components-button.timeline-item-appender{animation:none!important;background:#1e1e1e;border-radius:2px;color:#fff;height:24px;margin-left:12px;min-width:24px;padding-left:8px;padding-right:0}.components-button.timeline-item-appender:hover{color:#fff}.wp-block-jetpack-timeline.wp-block-jetpack-timeline{padding:0}.wp-block-jetpack-timeline li.wp-block-jetpack-timeline-item{list-style-type:none;margin-bottom:36px;margin-right:36px;padding:1em 2em;position:relative}.wp-block-jetpack-timeline li.wp-block-jetpack-timeline-item .timeline-item__bubble{background-color:currentColor;display:block;height:4px;position:absolute;right:-36px;top:50%;transform:translateY(-2px);width:36px}.wp-block-jetpack-timeline li.wp-block-jetpack-timeline-item .timeline-item:after{background:currentColor;bottom:-36px;content:"";display:block;position:absolute;right:-36px;top:-36px;width:4px}.wp-block-jetpack-timeline>li.wp-block-jetpack-timeline-item:first-child .timeline-item:after,.wp-block-jetpack-timeline [data-type="jetpack/timeline-item"]:first-child .timeline-item:after{top:50%}.wp-block-jetpack-timeline>li.wp-block-jetpack-timeline-item:last-child .timeline-item:after,.wp-block-jetpack-timeline [data-type="jetpack/timeline-item"]:nth-last-child(2) .timeline-item:after{bottom:50%}@media only screen and (min-width:640px){ul.wp-block-jetpack-timeline.is-alternating{display:flex;flex-direction:column}ul.wp-block-jetpack-timeline.is-alternating .wp-block-jetpack-timeline-item{box-sizing:border-box;width:calc(50% - 34px)}ul.wp-block-jetpack-timeline.is-alternating .wp-block-jetpack-timeline-item.is-left.is-left.is-left,ul.wp-block-jetpack-timeline.is-alternating>.wp-block-jetpack-timeline-item:nth-child(odd):not(.is-right),ul.wp-block-jetpack-timeline.is-alternating [data-type="jetpack/timeline-item"]:nth-child(odd) .wp-block-jetpack-timeline-item:not(.is-right){margin-left:auto;margin-right:0}ul.wp-block-jetpack-timeline.is-alternating .wp-block-jetpack-timeline-item.is-left.is-left.is-left .timeline-item:after,ul.wp-block-jetpack-timeline.is-alternating .wp-block-jetpack-timeline-item.is-left.is-left.is-left .timeline-item__bubble,ul.wp-block-jetpack-timeline.is-alternating>.wp-block-jetpack-timeline-item:nth-child(odd):not(.is-right) .timeline-item:after,ul.wp-block-jetpack-timeline.is-alternating>.wp-block-jetpack-timeline-item:nth-child(odd):not(.is-right) .timeline-item__bubble,ul.wp-block-jetpack-timeline.is-alternating [data-type="jetpack/timeline-item"]:nth-child(odd) .wp-block-jetpack-timeline-item:not(.is-right) .timeline-item:after,ul.wp-block-jetpack-timeline.is-alternating [data-type="jetpack/timeline-item"]:nth-child(odd) .wp-block-jetpack-timeline-item:not(.is-right) .timeline-item__bubble{left:-36px;right:auto}ul.wp-block-jetpack-timeline.is-alternating .wp-block-jetpack-timeline-item.is-right.is-right.is-right,ul.wp-block-jetpack-timeline.is-alternating .wp-block-jetpack-timeline-item:nth-child(2n):not(.is-left),ul.wp-block-jetpack-timeline.is-alternating [data-type="jetpack/timeline-item"]:nth-child(2n) .wp-block-jetpack-timeline-item:not(.is-left){margin-left:0;margin-right:auto}}
|
phpunit.xml.dist
CHANGED
|
@@ -22,5 +22,8 @@
|
|
| 22 |
<testsuite name="block-patterns">
|
| 23 |
<directory suffix="-test.php">./block-patterns/test/</directory>
|
| 24 |
</testsuite>
|
|
|
|
|
|
|
|
|
|
| 25 |
</testsuites>
|
| 26 |
</phpunit>
|
| 22 |
<testsuite name="block-patterns">
|
| 23 |
<directory suffix="-test.php">./block-patterns/test/</directory>
|
| 24 |
</testsuite>
|
| 25 |
+
<testsuite name="tutorials">
|
| 26 |
+
<directory suffix="-test.php">./tutorials/test/</directory>
|
| 27 |
+
</testsuite>
|
| 28 |
</testsuites>
|
| 29 |
</phpunit>
|
readme.txt
CHANGED
|
@@ -3,7 +3,7 @@ Contributors: automattic
|
|
| 3 |
Tags: block, blocks, editor, gutenberg, page
|
| 4 |
Requires at least: 5.5
|
| 5 |
Tested up to: 6.0
|
| 6 |
-
Stable tag: 3.
|
| 7 |
Requires PHP: 5.6.20
|
| 8 |
License: GPLv2 or later
|
| 9 |
License URI: https://www.gnu.org/licenses/gpl-2.0.html
|
| 3 |
Tags: block, blocks, editor, gutenberg, page
|
| 4 |
Requires at least: 5.5
|
| 5 |
Tested up to: 6.0
|
| 6 |
+
Stable tag: 3.36079
|
| 7 |
Requires PHP: 5.6.20
|
| 8 |
License: GPLv2 or later
|
| 9 |
License URI: https://www.gnu.org/licenses/gpl-2.0.html
|
tutorials/class-wpcom-tutorials.php
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
/**
|
| 3 |
+
* Tutorials (We would have called them Guided Tours but it's been done).
|
| 4 |
+
*
|
| 5 |
+
* Each Tutorial contains a flat list of Tasks. Each Task may contain several Steps in
|
| 6 |
+
* a `WpcomTourKit` component on the frontend, but we are only concerned with the top
|
| 7 |
+
* two levels of Tutorial and its contained Tasks, tracking Task completion status
|
| 8 |
+
* on the backend. This API will be used to generate
|
| 9 |
+
*
|
| 10 |
+
* @package A8C\FSE
|
| 11 |
+
*/
|
| 12 |
+
|
| 13 |
+
/**
|
| 14 |
+
* WPCom_Tutorials class
|
| 15 |
+
*/
|
| 16 |
+
class WPCom_Tutorials {
|
| 17 |
+
|
| 18 |
+
/**
|
| 19 |
+
* Storage for our singleton instance.
|
| 20 |
+
*
|
| 21 |
+
* @var null|WPCom_Tutorials
|
| 22 |
+
*/
|
| 23 |
+
private static $instance = null;
|
| 24 |
+
|
| 25 |
+
/**
|
| 26 |
+
* Internal storage for registered Tutorials.
|
| 27 |
+
*
|
| 28 |
+
* @var array
|
| 29 |
+
*/
|
| 30 |
+
private $registry = array();
|
| 31 |
+
|
| 32 |
+
/**
|
| 33 |
+
* Gets our singleton instance, initiating it the first time.
|
| 34 |
+
*
|
| 35 |
+
* @return WPCom_Tutorials instance.
|
| 36 |
+
*/
|
| 37 |
+
public static function get_instance() {
|
| 38 |
+
if ( is_null( self::$instance ) ) {
|
| 39 |
+
self::$instance = new self();
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
return self::$instance;
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
/**
|
| 46 |
+
* Registers a Tutorial.
|
| 47 |
+
*
|
| 48 |
+
* @param string $id A unique ID for the Tutorial.
|
| 49 |
+
* @param array $options `title` and `tasks` required.
|
| 50 |
+
*
|
| 51 |
+
* @return bool True if successfully registered, false if it failed.
|
| 52 |
+
*/
|
| 53 |
+
public function register( $id, $options ) {
|
| 54 |
+
if ( ! $this->validate_options( $options ) ) {
|
| 55 |
+
return false;
|
| 56 |
+
}
|
| 57 |
+
$options['id'] = $id;
|
| 58 |
+
$this->registry[ $id ] = $options;
|
| 59 |
+
return true;
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
/**
|
| 63 |
+
* Unregisters a registered Tutorial
|
| 64 |
+
*
|
| 65 |
+
* @param string $id Tutorial ID.
|
| 66 |
+
* @return bool True if successfully unregistered, false if not found.
|
| 67 |
+
*/
|
| 68 |
+
public function unregister( $id ) {
|
| 69 |
+
if ( ! isset( $this->registry[ $id ] ) ) {
|
| 70 |
+
return false;
|
| 71 |
+
}
|
| 72 |
+
unset( $this->registry[ $id ] );
|
| 73 |
+
return true;
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
/**
|
| 77 |
+
* Validates the options passed in `WPCom_Tutorials::register`.
|
| 78 |
+
*
|
| 79 |
+
* @param array $options Associative array passed with registration.
|
| 80 |
+
* @return bool Validation success.
|
| 81 |
+
*/
|
| 82 |
+
private function validate_options( $options ) {
|
| 83 |
+
if ( ! isset( $options['title'] ) || ! is_string( $options['title'] ) ) {
|
| 84 |
+
return false;
|
| 85 |
+
}
|
| 86 |
+
if ( ! isset( $options['tasks'] ) || ! is_array( $options['tasks'] ) ) {
|
| 87 |
+
return false;
|
| 88 |
+
}
|
| 89 |
+
foreach ( $options['tasks'] as $task ) {
|
| 90 |
+
if ( ! $this->validate_task( $task ) ) {
|
| 91 |
+
return false;
|
| 92 |
+
}
|
| 93 |
+
}
|
| 94 |
+
return true;
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
/**
|
| 98 |
+
* Validates a single task for expected structure.
|
| 99 |
+
*
|
| 100 |
+
* @param array $task A single task (id and title).
|
| 101 |
+
* @return bool Validation success.
|
| 102 |
+
*/
|
| 103 |
+
private function validate_task( $task ) {
|
| 104 |
+
return isset( $task['id'] )
|
| 105 |
+
&& is_string( $task['id'] )
|
| 106 |
+
&& isset( $task['title'] )
|
| 107 |
+
&& is_string( $task['title'] );
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
/**
|
| 111 |
+
* Retrieves a Tutorial from the registry.
|
| 112 |
+
*
|
| 113 |
+
* @param string $id The ID of the Tutorial to fetch.
|
| 114 |
+
* @return null|array Tutorial associative array if found, null if not.
|
| 115 |
+
*/
|
| 116 |
+
public function get_tutorial( $id ) {
|
| 117 |
+
if ( ! isset( $this->registry[ $id ] ) ) {
|
| 118 |
+
return null;
|
| 119 |
+
}
|
| 120 |
+
$tutorial = $this->registry[ $id ];
|
| 121 |
+
|
| 122 |
+
// Populating status at runtime so that any invalidations happen within the Options API.
|
| 123 |
+
$tutorial['tasks'] = $this->get_tasks( $tutorial );
|
| 124 |
+
return $tutorial;
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
/**
|
| 128 |
+
* Retrives all registered Tutorial IDs
|
| 129 |
+
*
|
| 130 |
+
* @return array list of registered Tutorial IDs.
|
| 131 |
+
*/
|
| 132 |
+
public function get_registered_ids() {
|
| 133 |
+
return array_keys( $this->registry );
|
| 134 |
+
}
|
| 135 |
+
|
| 136 |
+
/**
|
| 137 |
+
* Gets registered tasks for a Tutorial, with stored completion status.
|
| 138 |
+
*
|
| 139 |
+
* @param array $tutorial Tutorial object.
|
| 140 |
+
* @return array Tasks for Tutorial, with task completion status.
|
| 141 |
+
*/
|
| 142 |
+
private function get_tasks( $tutorial ) {
|
| 143 |
+
$tasks_with_statuses = array();
|
| 144 |
+
$statuses = $this->get_statuses( $tutorial['id'] );
|
| 145 |
+
|
| 146 |
+
foreach ( $tutorial['tasks'] as $task ) {
|
| 147 |
+
// Default status is pending.
|
| 148 |
+
$task['status'] = 'pending';
|
| 149 |
+
if ( isset( $statuses[ $task['id'] ] ) ) {
|
| 150 |
+
$task['status'] = $statuses[ $task['id'] ];
|
| 151 |
+
}
|
| 152 |
+
$tasks_with_statuses[] = $task;
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
return $tasks_with_statuses;
|
| 156 |
+
}
|
| 157 |
+
|
| 158 |
+
/**
|
| 159 |
+
* Gets the completion statuses for a specific Tutorial.
|
| 160 |
+
*
|
| 161 |
+
* @param string $id Tutorial ID.
|
| 162 |
+
* @return array Completion statuses for Tutorial ID.
|
| 163 |
+
*/
|
| 164 |
+
public function get_statuses( $id ) {
|
| 165 |
+
$statuses = $this->load_persisted_statuses();
|
| 166 |
+
return isset( $statuses[ $id ] ) ? $statuses[ $id ] : array();
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
/**
|
| 170 |
+
* Loads all Tutorial statuses from the database.
|
| 171 |
+
*
|
| 172 |
+
* @return array All Tutorial completion statuses.
|
| 173 |
+
*/
|
| 174 |
+
private function load_persisted_statuses() {
|
| 175 |
+
if ( function_exists( 'get_user_attribute' ) ) {
|
| 176 |
+
return get_user_attribute( get_current_user_id(), 'wpcom_tutorials_status' );
|
| 177 |
+
}
|
| 178 |
+
return (array) get_user_meta( get_current_user_id(), 'wpcom_tutorials_status', true );
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
+
/**
|
| 182 |
+
* Marks a task with a status.
|
| 183 |
+
*
|
| 184 |
+
* @param string $tutorial_id Tutorial ID.
|
| 185 |
+
* @param string $task_id Task ID.
|
| 186 |
+
* @param string $status Status for the task. Allowed: complete|skipped|pending.
|
| 187 |
+
* @return bool True if status is updated, false otherwise.
|
| 188 |
+
*/
|
| 189 |
+
public function mark_task( $tutorial_id, $task_id, $status ) {
|
| 190 |
+
if ( ! $this->validate_status( $status ) ) {
|
| 191 |
+
return false;
|
| 192 |
+
}
|
| 193 |
+
if ( ! $this->task_exists( $tutorial_id, $task_id ) ) {
|
| 194 |
+
return false;
|
| 195 |
+
}
|
| 196 |
+
|
| 197 |
+
$statuses = $this->load_persisted_statuses();
|
| 198 |
+
if ( ! isset( $statuses[ $tutorial_id ] ) ) {
|
| 199 |
+
$statuses[ $tutorial_id ] = array();
|
| 200 |
+
}
|
| 201 |
+
// pending is the natural state with no stored status.
|
| 202 |
+
if ( 'pending' === $status ) {
|
| 203 |
+
if ( isset( $statuses[ $tutorial_id ][ $task_id ] ) ) {
|
| 204 |
+
unset( $statuses[ $tutorial_id ][ $task_id ] );
|
| 205 |
+
}
|
| 206 |
+
} else {
|
| 207 |
+
$statuses[ $tutorial_id ][ $task_id ] = $status;
|
| 208 |
+
}
|
| 209 |
+
|
| 210 |
+
return $this->persist_statuses( $statuses );
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
+
/**
|
| 214 |
+
* Checks if a task exists for a registered tutorial.
|
| 215 |
+
*
|
| 216 |
+
* @param string $tutorial_id A tutorial ID.
|
| 217 |
+
* @param string $task_id A task ID.
|
| 218 |
+
* @return bool True if it exists, otherwise false.
|
| 219 |
+
*/
|
| 220 |
+
private function task_exists( $tutorial_id, $task_id ) {
|
| 221 |
+
$tutorial = $this->get_tutorial( $tutorial_id );
|
| 222 |
+
foreach ( $tutorial['tasks'] as $task ) {
|
| 223 |
+
if ( $task_id === $task['id'] ) {
|
| 224 |
+
return true;
|
| 225 |
+
}
|
| 226 |
+
}
|
| 227 |
+
return false;
|
| 228 |
+
}
|
| 229 |
+
|
| 230 |
+
/**
|
| 231 |
+
* Persist statuses to the DB, using the platform-appropriate tech
|
| 232 |
+
*
|
| 233 |
+
* @param array $statuses All statuses to persist.
|
| 234 |
+
* @return bool True if persisting succeeded, otherwise false.
|
| 235 |
+
*/
|
| 236 |
+
public function persist_statuses( $statuses ) {
|
| 237 |
+
if ( function_exists( 'update_user_attribute' ) ) {
|
| 238 |
+
return update_user_attribute( get_current_user_id(), 'wpcom_tutorials_status', $statuses );
|
| 239 |
+
}
|
| 240 |
+
return update_user_meta( get_current_user_id(), 'wpcom_tutorials_status', $statuses );
|
| 241 |
+
}
|
| 242 |
+
|
| 243 |
+
/**
|
| 244 |
+
* Checks if the status is valid.
|
| 245 |
+
*
|
| 246 |
+
* @param string $status A status to check. Allowed values: complete|pending|skipped.
|
| 247 |
+
* @return bool If status if valid, true, otherwise false.
|
| 248 |
+
*/
|
| 249 |
+
private function validate_status( $status ) {
|
| 250 |
+
$allowed = array( 'complete', 'pending', 'skipped' );
|
| 251 |
+
return in_array( $status, $allowed, true );
|
| 252 |
+
}
|
| 253 |
+
}
|
tutorials/test/tutorials-test.php
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
// phpcs:ignoreFile
|
| 3 |
+
|
| 4 |
+
use PHPUnit\Framework\TestCase;
|
| 5 |
+
|
| 6 |
+
require_once dirname( __FILE__, 2 ) . '/tutorials.php';
|
| 7 |
+
|
| 8 |
+
class WPCom_Tutorials_Test extends TestCase {
|
| 9 |
+
|
| 10 |
+
public function tearDown() {
|
| 11 |
+
// cleanse after each test
|
| 12 |
+
foreach ( wpcom_get_registered_tutorial_ids() as $id ) {
|
| 13 |
+
wpcom_tutorials()->unregister( $id );
|
| 14 |
+
}
|
| 15 |
+
// delete status storage
|
| 16 |
+
wpcom_tutorials()->persist_statuses( [] );
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
public function test_registered_tutorial_is_returned() {
|
| 20 |
+
wpcom_register_tutorial(
|
| 21 |
+
'foo',
|
| 22 |
+
array(
|
| 23 |
+
'title' => 'Foo',
|
| 24 |
+
'tasks' => $this->get_tasks(),
|
| 25 |
+
)
|
| 26 |
+
);
|
| 27 |
+
|
| 28 |
+
$return_tutorial = wpcom_get_tutorial( 'foo' );
|
| 29 |
+
|
| 30 |
+
$expected_tutorial = array(
|
| 31 |
+
'id' => 'foo',
|
| 32 |
+
'title' => 'Foo',
|
| 33 |
+
'tasks' => $this->get_tasks_with_status(),
|
| 34 |
+
);
|
| 35 |
+
|
| 36 |
+
$this->assertEquals( $return_tutorial, $expected_tutorial );
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
public function test_all_registered_ids_are_returned() {
|
| 40 |
+
$this->assertEquals( wpcom_get_registered_tutorial_ids(), array() );
|
| 41 |
+
|
| 42 |
+
wpcom_register_tutorial(
|
| 43 |
+
'foo',
|
| 44 |
+
array(
|
| 45 |
+
'title' => 'Foo',
|
| 46 |
+
'tasks' => $this->get_tasks(),
|
| 47 |
+
)
|
| 48 |
+
);
|
| 49 |
+
wpcom_register_tutorial(
|
| 50 |
+
'bar',
|
| 51 |
+
array(
|
| 52 |
+
'title' => 'Bar',
|
| 53 |
+
'tasks' => $this->get_tasks(),
|
| 54 |
+
)
|
| 55 |
+
);
|
| 56 |
+
wpcom_register_tutorial(
|
| 57 |
+
'baz',
|
| 58 |
+
array(
|
| 59 |
+
'title' => 'Baz',
|
| 60 |
+
'tasks' => $this->get_tasks(),
|
| 61 |
+
)
|
| 62 |
+
);
|
| 63 |
+
|
| 64 |
+
$this->assertEquals( wpcom_get_registered_tutorial_ids(), array( 'foo', 'bar', 'baz' ) );
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
public function test_tutorial_with_malformed_options_fails() {
|
| 68 |
+
// empty option object
|
| 69 |
+
$foo1 = wpcom_register_tutorial( 'foo', array() );
|
| 70 |
+
$this->assertEmpty( wpcom_get_registered_tutorial_ids() );
|
| 71 |
+
$this->assertFalse( $foo1 );
|
| 72 |
+
|
| 73 |
+
// no title
|
| 74 |
+
$foo2 = wpcom_register_tutorial( 'foo', array( 'tasks' => $this->get_tasks() ) );
|
| 75 |
+
$this->assertEmpty( wpcom_get_registered_tutorial_ids() );
|
| 76 |
+
$this->assertFalse( $foo2 );
|
| 77 |
+
|
| 78 |
+
// no steps
|
| 79 |
+
$foo3 = wpcom_register_tutorial( 'foo', array( 'title' => 'Foo' ) );
|
| 80 |
+
$this->assertEmpty( wpcom_get_registered_tutorial_ids() );
|
| 81 |
+
$this->assertFalse( $foo3 );
|
| 82 |
+
|
| 83 |
+
// empty steps
|
| 84 |
+
$foo4 = wpcom_register_tutorial(
|
| 85 |
+
'foo',
|
| 86 |
+
array(
|
| 87 |
+
'title' => 'Foo',
|
| 88 |
+
'steps' => array(),
|
| 89 |
+
)
|
| 90 |
+
);
|
| 91 |
+
$this->assertEmpty( wpcom_get_registered_tutorial_ids() );
|
| 92 |
+
$this->assertFalse( $foo4 );
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
public function test_persisted_task_statuses() {
|
| 96 |
+
wpcom_register_tutorial(
|
| 97 |
+
'foo',
|
| 98 |
+
array(
|
| 99 |
+
'title' => 'Foo',
|
| 100 |
+
'tasks' => $this->get_tasks(),
|
| 101 |
+
)
|
| 102 |
+
);
|
| 103 |
+
wpcom_register_tutorial(
|
| 104 |
+
'bar',
|
| 105 |
+
array(
|
| 106 |
+
'title' => 'Bar',
|
| 107 |
+
'tasks' => $this->get_tasks(),
|
| 108 |
+
)
|
| 109 |
+
);
|
| 110 |
+
|
| 111 |
+
wp_set_current_user( 1 );
|
| 112 |
+
|
| 113 |
+
$bar_task_1 = $this->get_task_by_id( wpcom_get_tutorial( 'foo' )['tasks'], 'bar' );
|
| 114 |
+
$this->assertEquals( $bar_task_1['status'], 'pending' );
|
| 115 |
+
|
| 116 |
+
wpcom_tutorial_mark_task( 'foo', 'bar', 'skipped' );
|
| 117 |
+
$bar_task_2 = $this->get_task_by_id( wpcom_get_tutorial( 'foo' )['tasks'], 'bar' );
|
| 118 |
+
$this->assertEquals( $bar_task_2['status'], 'skipped' );
|
| 119 |
+
|
| 120 |
+
wpcom_tutorial_mark_task( 'foo', 'bar', 'pending' );
|
| 121 |
+
$bar_task_3 = $this->get_task_by_id( wpcom_get_tutorial( 'foo' )['tasks'], 'bar' );
|
| 122 |
+
$this->assertEquals( $bar_task_3['status'], 'pending' );
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
wpcom_tutorial_mark_task( 'bar', 'bar', 'complete' );
|
| 126 |
+
$baz_task = $this->get_task_by_id( wpcom_get_tutorial( 'bar' )['tasks'], 'bar' );
|
| 127 |
+
$this->assertEquals( $baz_task['status'], 'complete' );
|
| 128 |
+
|
| 129 |
+
wp_set_current_user( 0 );
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
private function get_task_by_id( $tasks, $id ) {
|
| 133 |
+
$filtered = wp_list_filter( $tasks, [ 'id' => $id ] );
|
| 134 |
+
return empty( $filtered ) ? null : $filtered[0];
|
| 135 |
+
}
|
| 136 |
+
|
| 137 |
+
// boilerplate
|
| 138 |
+
private function get_tasks() {
|
| 139 |
+
return array(
|
| 140 |
+
array(
|
| 141 |
+
'id' => 'bar',
|
| 142 |
+
'title' => 'Bar',
|
| 143 |
+
),
|
| 144 |
+
);
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
+
private function get_tasks_with_status( $status = 'pending' ) {
|
| 148 |
+
$tasks = $this->get_tasks();
|
| 149 |
+
foreach ( $tasks as $i => $task ) {
|
| 150 |
+
$tasks[ $i ]['status'] = $status;
|
| 151 |
+
}
|
| 152 |
+
return $tasks;
|
| 153 |
+
}
|
| 154 |
+
}
|
tutorials/tutorials.php
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
/**
|
| 3 |
+
* Tutorials API
|
| 4 |
+
*
|
| 5 |
+
* @package A8C\FSE
|
| 6 |
+
*/
|
| 7 |
+
|
| 8 |
+
// Load the class we use. We also need this comment here to mollify PHPCS.
|
| 9 |
+
require_once __DIR__ . '/class-wpcom-tutorials.php';
|
| 10 |
+
|
| 11 |
+
/**
|
| 12 |
+
* Helper function to return a WPCom_Tutorials object
|
| 13 |
+
*
|
| 14 |
+
* @return WPCom_Tutorials instance object.
|
| 15 |
+
*/
|
| 16 |
+
function wpcom_tutorials() {
|
| 17 |
+
return WPCom_Tutorials::get_instance();
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
// phpcs:disable Squiz.Commenting.FunctionComment.ParamCommentFullStop
|
| 21 |
+
// ^ it doesn't like the params for $options, despite this being Core style.
|
| 22 |
+
/**
|
| 23 |
+
* Registers a Tutorial.
|
| 24 |
+
*
|
| 25 |
+
* @param string $tutorial_id A unique ID for the Tutorial.
|
| 26 |
+
* @param array $options {
|
| 27 |
+
* Options for the Tutorial with the following shape.
|
| 28 |
+
*
|
| 29 |
+
* @type string $title The displayed title for this Tutorial
|
| 30 |
+
* @type array $tasks {
|
| 31 |
+
* An array of $tasks, each of which has:
|
| 32 |
+
* {
|
| 33 |
+
* @type string $id A unique identifier for the Task
|
| 34 |
+
* @type string $title What is displayed as the Task
|
| 35 |
+
* }
|
| 36 |
+
* }
|
| 37 |
+
* }
|
| 38 |
+
*
|
| 39 |
+
* @return bool True if successfully registered, false if it failed.
|
| 40 |
+
*/
|
| 41 |
+
function wpcom_register_tutorial( $tutorial_id, $options ) {
|
| 42 |
+
return wpcom_tutorials()->register( $tutorial_id, $options );
|
| 43 |
+
}
|
| 44 |
+
// phpcs:enable
|
| 45 |
+
|
| 46 |
+
/**
|
| 47 |
+
* Retrieves a Tutorial from the registry.
|
| 48 |
+
*
|
| 49 |
+
* @param string $tutorial_id The ID of the Tutorial to fetch.
|
| 50 |
+
* @return null|array Tutorial associative array if found, null if not.
|
| 51 |
+
*/
|
| 52 |
+
function wpcom_get_tutorial( $tutorial_id ) {
|
| 53 |
+
return wpcom_tutorials()->get_tutorial( $tutorial_id );
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
/**
|
| 57 |
+
* Marks a task with a status.
|
| 58 |
+
*
|
| 59 |
+
* @param string $tutorial_id Tutorial ID.
|
| 60 |
+
* @param string $task_id Task ID.
|
| 61 |
+
* @param string $status Status for the task. Allowed: complete|skipped|pending.
|
| 62 |
+
* @return bool True if status is updated, false otherwise.
|
| 63 |
+
*/
|
| 64 |
+
function wpcom_tutorial_mark_task( $tutorial_id, $task_id, $status ) {
|
| 65 |
+
return wpcom_tutorials()->mark_task( $tutorial_id, $task_id, $status );
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
/**
|
| 69 |
+
* Retrives all registered Tutorial IDs
|
| 70 |
+
*
|
| 71 |
+
* @return array list of registered Tutorial IDs.
|
| 72 |
+
*/
|
| 73 |
+
function wpcom_get_registered_tutorial_ids() {
|
| 74 |
+
return wpcom_tutorials()->get_registered_ids();
|
| 75 |
+
}
|
