Version Description
Download this release
Release Info
Developer | antoniosejas |
Plugin | Full Site Editing |
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 |
+
}
|