Peter Wilson
○
@pwcc
○
peterwilson.cc
Burn it down: A case study in CMS replatforming
A presentation at LoopConf in February 2018 in Salt Lake City, UT, USA by Peter Wilson
Peter Wilson
○
@pwcc
○
peterwilson.cc
Burn it down: A case study in CMS replatforming
brisbanetimes.com.au August 27, 2017, 8:07pm
brisbanetimes.com.au August 27, 2017, 8:07pm August 27, 2017, 8:08pm
“ It was just another day in the newsroom
“ It was just another day writing content
humanmade.com/fairfax-media
*/ $must_use_plugins = [
'ffx-options/plugin.php'
,
// Filter get_option calls.
/* ... Vendor plugins snipped ... */
'ffx-helpers/plugin.php'
,
// Available during bootstrap.
'ffx-feature-flags/plugin.php' , // Available during bootstrap.
'ffx-api-content/plugin.php' ,
'ffx-api-media/plugin.php' ,
'ffx-roles-capabilities/plugin.php' ,
'ffx-article-editor/plugin.php' ,
'ffx-live-articles/plugin.php' ,
'ffx-notifications/plugin.php' ,
'ffx-shortcake/plugin.php' ,
'ffx-brightcove/plugin.php' ,
'ffx-profiles/plugin.php' ,
'ffx-unpublish/plugin.php' ,
'ffx-wire-feed/plugin.php' ,
'ffx-image-editor/plugin.php' ,
'ffx-taxonomies/plugin.php' ,
'ffx-dashboard/plugin.php' ,
'ffx-publishing/plugin.php' ,
'ffx-tooltips/plugin.php' , ];
Reimagining the tech-stack
The stack
The stack
The stack
The stack
The stack
The stack
The stack Media API Content API C loudinary
Extending WordPress for enterprise
Publishing and Workflow
Media Library
Taxonomies
Tagging articles
at scale
SELECT count ( * ) FROM wp_term_taxonomy WHERE taxonomy= 'ffx_tag'
2 2 3 0 3
Content API tags endpoint ! Tags manager
GET 50 tags Content API
GET 50 tags wp_insert_term( /* Roxie Hart / ); wp_insert_term( / Billy Flynn / ); wp_insert_term( / Velma Kelly / ); wp_insert_term( / x 50 */ ); Content API
Managing terms
makes a lot of DB queries
wp_insert_term(
'Roxie Hart' ,
'ffx_tags' , [
'slug'
=>
'roxie-hart'
,
'parent' => '105' , ] ); add_term_meta( 23170 , '_ffx_id' , 'h4rt' ); add_term_meta( 23170 , '_ffx_term_meta' , [ /* */ ] );
SELECT tt.term_id, tt.term_taxonomy_id FROM wp_terms AS t
INNER JOIN wp_term_taxonomy as tt
ON tt.term_id = t.term_id WHERE t.slug = 'roxie-hart '
AND tt.parent = '105'
AND tt.taxonomy = 'ffx_tag' ORDER BY t.term_id ASC LIMIT 1
SELECT tt.term_id, tt.term_taxonomy_id FROM wp_terms AS t
INNER JOIN wp_term_taxonomy as tt
ON tt.term_id = t.term_id
AND tt.parent = '105'
AND tt.taxonomy = 'ffx_tag' ORDER BY t.term_id ASC LIMIT 1
SELECT tt.term_id, tt.term_taxonomy_id FROM wp_terms AS t
INNER JOIN wp_term_taxonomy as tt
ON tt.term_id = t.term_id
AND tt.parent = '105'
AND tt.taxonomy = 'ffx_tag' ORDER BY t.term_id ASC LIMIT 1
' R o x i e H a r t '
SELECT
t.
*
, tt.
*
FROM
wp_terms
AS
t
INNER JOIN wp_term_taxonomy AS tt
ON t.term_id = tt.term_id WHERE tt.taxonomy IN ( 'ffx_tag' )
AND t.name IN ( 'Roxie Hart' )
AND tt.parent = '105' ORDER BY t.name ASC
SELECT term_id FROM wp_terms as t WHERE t.slug = 'roxie-hart' ORDER BY t.term_id ASC LIMIT 1
INSERT
INTO
wp_terms
(name
,slug
,term_group
)
VALUES
(
'Roxie Hart'
,
'roxie-hart'
,
0
)
Production, Chicago
. SELECT tt.term_taxonomy_id FROM wp_term_taxonomy AS tt
INNER JOIN wp_terms AS t
ON tt.term_id = t.term_id WHERE tt.taxonomy = 'ffx_tag'
AND t.term_id = 23170
INSERT
INTO
wp_term_taxonomy
( term_id
, taxonomy
,
`description`, `parent`, `count`
) VALUES ( 23170 , 'ffx_tag' , '' , 105 , 0 )
SELECT t.term_id, tt.term_taxonomy_id FROM wp_terms t
INNER JOIN wp_term_taxonomy tt
ON ( tt.term_id = t.term_id ) WHERE t.slug = 'roxie-hart'
AND tt.parent = 105
AND tt.taxonomy = 'ffx_tag'
AND t.term_id < 23170
AND tt.term_taxonomy_id != 23170
And that’s not all
SELECT term_id, meta_key, meta_value FROM wp_term
SELECT meta_id FROM wp_termmeta WHERE meta_key =
INSERT INTO
wp_termmeta
(term_id
, meta_key
,
DELETE FROM
wp_options
WHERE
option_name
=
'ffx_tag_children'
SELECT option_value FROM wp_options WHERE option_name = 'ffx_tag_children' LIMIT 1
all terms
in the taxonomy
to work out hierarchy
SELECT
t.term_id, tt.parent, tt.count, tt.taxonomy
FROM
wp_terms
AS
t
INNER
SELECT term_id, meta_key, meta_value FROM wp_termmeta WHERE term_id IN ( 95 ,
INSERT INTO
wp_options
(option_name
, option_value
, autoload
)
Production, Chicago
1 ; $page <= $total_pages ; $page ++ ) {
// Get all the tags we need to process on the current page.
$tags = get_tags_from_api( $page );
// Schedule a single event to create or update terms.
wp_schedule_single_event(
time () + ( 10 * $page * MINUTE_IN_SECONDS ),
'ffx_import_some_terms_action' ,
$tags
); }
Complex sites need scheduled tasks & asynchronous processing. Meet Cavalcade: A #WordPress jobs processing solution humanmade.com /cavalcade Human Made @ humanmadeltd 5:31 AM - 30 May 2017
NASA
NASA
We were so focused on the database , we forgot about HTTP
wp-cron.php
wp-cron.php Ta g s A P I
wp-cron.php Ta g s A P I Saved as cron job
wp-cron.php Ta g s A P I Saved as cron job
That’s what we forgot HTTP requests time out Prairie Kittin, flic.kr/p/a4Ujpv
update_option( 'cron' , [ /* ... */ ] );
update_option( 'cron' , [ /* ... */ ] );
update_option( 'cron' , [ /* ... */ ] ); wp_insert_term(
'Roxie Hart' ,
'ffx_tags' , [
'slug'
=>
'roxie-hart'
,
'parent' => '105' , ] ); add_term_meta( 23170 , '_ffx_id' , 'h4rt' ); add_term_meta( 23170 , '_ffx_term_meta' , [ /* */ ] );
update_option( 'cron' , [ /* ... */ ] ); wp_insert_term(
'Roxie Hart' ,
'ffx_tags' , [
'slug'
=>
'roxie-hart'
,
'parent' => '105' , ] ); add_term_meta( 23170 , '_ffx_id' , 'h4rt' ); add_term_meta( 23170 , '_ffx_term_meta' , [ /* */ ] ); wp_insert_term(
'Roxie Hart' ,
'ffx_tags' , [
'slug'
=>
'roxie-hart'
,
'parent' => '105' , ] ); add_term_meta( 23170 , '_ffx_id' , 'h4rt' ); add_term_meta( 23170 , '_ffx_term_meta' , [ /* */ ] );
update_option( 'cron' , [ /* ... */ ] ); wp_insert_term(
'Roxie Hart' ,
'ffx_tags' , [
'slug'
=>
'roxie-hart'
,
'parent' => '105' , ] ); add_term_meta( 23170 , '_ffx_id' , 'h4rt' ); add_term_meta( 23170 , '_ffx_term_meta' , [ /* */ ] ); wp_insert_term(
'Roxie Hart' ,
'ffx_tags' , [
'slug'
=>
'roxie-hart'
,
'parent' => '105' , ] ); add_term_meta( 23170 , '_ffx_id' , 'h4rt' ); add_term_meta( 23170 , '_ffx_term_meta' , [ /* */ ] ); wp_insert_term(
'Roxie Hart' ,
'ffx_tags' , [
'slug'
=>
'roxie-hart'
,
'parent' => '105' , ] ); add_term_meta( 23170 , '_ffx_id' , 'h4rt' ); add_term_meta( 23170 , '_ffx_term_meta' , [ /* */ ] ); wp_insert_term(
'Roxie Hart' ,
'ffx_tags' , [
'slug'
=>
'roxie-hart'
,
'parent' => '105' , ] ); add_term_meta( 23170 , '_ffx_id' , 'h4rt' ); add_term_meta( 23170 , '_ffx_term_meta' , [ /* */ ] ); wp_insert_term(
'Roxie Hart' ,
'ffx_tags' , [
'slug'
=>
'roxie-hart'
,
'parent' => '105' , ] ); add_term_meta( 23170 , '_ffx_id' , 'h4rt' ); add_term_meta( 23170 , '_ffx_term_meta' , [ /* */ ] ); wp_insert_term(
'Roxie Hart' ,
'ffx_tags' , [
'slug'
=>
'roxie-hart'
,
'parent' => '105' , ] ); add_term_meta( 23170 , '_ffx_id' , 'h4rt' ); add_term_meta( 23170 , '_ffx_term_meta' , [ /* */ ] ); wp_insert_term(
'Roxie Hart' ,
'ffx_tags' , [
'slug'
=>
'roxie-hart'
,
'parent' => '105' , ] ); add_term_meta( 23170 , '_ffx_id' , 'h4rt' ); add_term_meta( 23170 , '_ffx_term_meta' , [ /* */ ] ); wp_insert_term(
'Roxie Hart' ,
'ffx_tags' , [
'slug'
=>
'roxie-hart'
,
'parent' => '105' , ] ); add_term_meta( 23170 , '_ffx_id' , 'h4rt' ); add_term_meta( 23170 , '_ffx_term_meta' , [ /* */ ] ); wp_insert_term(
'Roxie Hart' ,
'ffx_tags' , [
'slug'
=>
'roxie-hart'
,
'parent' => '105' , ] ); add_term_meta( 23170 , '_ffx_id' , 'h4rt' ); add_term_meta( 23170 , '_ffx_term_meta' , [ /* */ ] ); wp_insert_term(
'Roxie Hart' ,
'ffx_tags' , [
'slug'
=>
'roxie-hart'
,
'parent' => '105' , ] ); add_term_meta( 23170 , '_ffx_id' , 'h4rt' ); add_term_meta( 23170 , '_ffx_term_meta' , [ /* */ ] );
We reverted the original commit.
Complex sites need scheduled tasks & asynchronous processing. Meet Cavalcade: A #WordPress jobs processing solution humanmade.com /cavalcade Human Made @ humanmadeltd 5:31 AM - 30 May 2017
/**
1 ; $page <= $total_pages ; $page ++ ) {
// Get all the tags we need to process on the current page.
$tags = get_tags_from_api( $page );
// Schedule a single event to create or update terms.
wp_schedule_single_event(
time () + ( 10 * $page * MINUTE_IN_SECONDS ),
'ffx_import_some_terms_action' ,
$tags
); }
1 ; $page <= $total_pages ; $page ++ ) {
// Get all the tags we need to process on the current page.
$tags = get_tags_from_api( $page );
// Schedule a single event to create or update terms.
wp_schedule_single_event( time() , // Schedule all pages NOW!
'ffx_import_some_terms_action' ,
$tags
); }
1 ; $page <= $total_pages ; $page ++ ) {
// Get all the tags we need to process on the current page.
$tags = get_tags_from_api( $page );
// Schedule a single event to create or update terms.
wp_schedule_single_event( time() , // Schedule all pages NOW!
'ffx_import_some_terms_action' ,
$tags
); }
1 ; $page <= $total_pages ; $page ++ ) {
// Set the arguments needed to process the current page.
$args = [ 'page' => $page , 'qty' => 50 ];
// Schedule a single event to create or update terms.
wp_schedule_single_event(
time (), // Schedule all pages NOW!
'ffx_import_tags_cron' ,
$args
); }
1 ; $page <= $total_pages ; $page ++ ) {
// Set the arguments needed to process the current page.
$args = [ 'page' => $page , 'qty' => 50 ];
// Schedule a single event to create or update terms.
wp_schedule_single_event(
time (), // Schedule all pages NOW!
// Schedule a single event to create or update terms.
wp_schedule_single_event(
time (), // Schedule all pages NOW!
'ffx_import_tags_cron' ,
$args
); } // Delete the scheduling lock. delete_option( TAGS_SCHEDULING_LOCK_OPTION ); update_option( TAGS_SCHEDULED_LOCK_OPTION , time () );
[11] Worker shutting down...
[11] Worker out:
[11] Worker err:
[11] Worker ret: 0
[12] Worker status: Array
(
[command] => wp cavalcade run 12
[pid] => 57
[running] =>
[signaled] =>
[stopped] =>
[exitcode] => 0
[termsig] => 0
[stopsig] => 0
)
2018-01-27T01:09:22.458320545Z
[12] Worker shutting down...
[12] Worker out:
[12] Worker err:
[12] Worker ret: 0
[14] Running wp cavalcade run 14 (ffx/taxonomies/term_importer/import_tags_cron a:1:{i:0;a:5:{s:8:"tag_type";s:4:"tags";s:10:"total_tags";i:23032;s:4:"size";i:50;s:5:"pages";i:461;s:4:"page";i:1;}})
[14] Started worker
[15] Running wp cavalcade run 15 (ffx/taxonomies/term_importer/import_tags_cron a:1:{i:0;a:5:{s:8:"tag_type";s:4:"tags";s:10:"total_tags";i:23032;s:4:"size";i:50;s:5:"pages";i:461;s:4:"page";i:2;}})
[15] Started worker
[16] Running wp cavalcade run 16 (ffx/taxonomies/term_importer/import_tags_cron a:1:{i:0;a:5:{s:8:"tag_type";s:4:"tags";s:10:"total_tags";i:23032;s:4:"size";i:50;s:5:"pages";i:461;s:4:"page";i:3;}})
[16] Started worker
[ ] Out of workers
[ ] Out of workers
[ ] Out of workers
[ ] Out of workers
[14] Worker status: Array
(
GET 50 tags wp_insert_term( /* Roxie Hart / ); wp_insert_term( / Billy Flynn / ); wp_insert_term( / Velma Kelly / ); wp_insert_term( / x 50 */ ); Content API
wp_insert_term( /* Roxie Hart / ); wp_insert_term( / Billy Flynn / ); wp_insert_term( / Velma Kelly / ); wp_insert_term( / x 50 */ ); GET 50 tags Content API "
wp_insert_term( /* Roxie Hart / ); wp_insert_term( / Billy Flynn / ); wp_insert_term( / Velma Kelly / ); wp_insert_term( / x 50 / ); " GET 50 tags Content API " GET 50 tags Content API " GET 50 tags Content API " GET 50 tags Content API wp_insert_term( / Roxie Hart / ); wp_insert_term( / Billy Flynn / ); wp_insert_term( / Velma Kelly / ); wp_insert_term( / x 50 / ); wp_insert_term( / Roxie Hart / ); wp_insert_term( / Billy Flynn / ); wp_insert_term( / Velma Kelly / ); wp_insert_term( / x 50 / ); wp_insert_term( / Roxie Hart / ); wp_insert_term( / Billy Flynn / ); wp_insert_term( / Velma Kelly / ); wp_insert_term( / x 50 */ );
wp_insert_term( /* Roxie Hart / ); wp_insert_term( / Billy Flynn / ); wp_insert_term( / Velma Kelly / ); wp_insert_term( / x 50 / ); " GET 50 tags Content API " GET 50 tags Content API " GET 50 tags Content API " GET 50 tags Content API wp_insert_term( / Roxie Hart / ); wp_insert_term( / Billy Flynn / ); wp_insert_term( / Velma Kelly / ); wp_insert_term( / x 50 / ); wp_insert_term( / Roxie Hart / ); wp_insert_term( / Billy Flynn / ); wp_insert_term( / Velma Kelly / ); wp_insert_term( / x 50 / ); wp_insert_term( / Roxie Hart / ); wp_insert_term( / Billy Flynn / ); wp_insert_term( / Velma Kelly / ); wp_insert_term( / x 50 */ );
DELETE FROM
wp_options
WHERE
option_name
=
'ffx_tag_children'
SELECT option_value FROM wp_options WHERE option_name = 'ffx_tag_children' LIMIT 1
all terms
in the taxonomy
to work out hierarchy
SELECT
t.term_id, tt.parent, tt.count, tt.taxonomy
FROM
wp_terms
AS
t
INNER
SELECT term_id, meta_key, meta_value FROM wp_termmeta WHERE term_id IN ( 95 ,
335KB at 23K tags)
INSERT INTO
wp_options
(option_name
, option_value
, autoload
)
cache
335KB at 23K tags )
wp_options
WHERE option_name
= 'ffx_tag_children' # Get term hierarchy option (for reasons) SELECT option_value FROM wp_options WHERE option_name = 'ffx_tag_children' # Select all terms in the taxonomy to work out hierarchy SELECT t.term_id, tt.parent, tt.count, tt.taxonomy FROM wp_terms AS t INNER # Warm the term meta cache for all (a bug) SELECT term_id, meta_key, meta_value FROM wp_termmeta WHERE term_id IN ( 95 # Update the term hierarchy cache ( 335KB at 23K tags) INSERT INTO wp_options
(option_name
, option_value
, autoload
)function import_tags( $taxonomy , $tags ) {
/*
wp_suspend_cache_addition( true );
foreach ( $tags [ $taxonomy ] as $tag ) { wp_insert_term( /* $tag / ); update_term_meta( / tag meta 1 / ); update_term_meta( / tag meta 2 */ ); } }
function import_tags( $taxonomy , $tags ) {
/*
wp_suspend_cache_addition( true );
foreach ( $tags [ $taxonomy ] as $tag ) { wp_insert_term( /* $tag / ); update_term_meta( / tag meta 1 / ); update_term_meta( / tag meta 2 */ ); } }
Read Cache Read Cache Read Cache Read Cache Write cache Write cache Write cache Write cache
Improving the media library
Limited crops four by default
Global crops happens on upload, calculated
defined by user, not by system Unpredictable file names deh-logo.jpg
Same database images and articles share a db table
The stack
POST http://cms-authoring-local/wp/wp-admin/async-upload.php - action : upload-attachment
add_action( 'admin_init' , 'ajax_upload_attachment' , 0 ); /**
by async-upload.php
.
admin_init
.
*/
function
ajax_upload_attachment() {// Only intercept the upload-attachment action.
if ( ! isset ( $_POST [ 'action' ] ) ||
'upload-attachment' !== $_POST [ 'action' ] ) {
return ;
add_action( 'admin_init' , 'ajax_upload_attachment' , 0 ); /**
by async-upload.php
.
admin_init
.
*/
function
ajax_upload_attachment() {// Only intercept the upload-attachment action.
if ( ! isset ( $_POST [ 'action' ] ) ||
'upload-attachment' !== $_POST [ 'action' ] ) {
return ;
by async-upload.php
.
admin_init
.
*/
function
ajax_upload_attachment() {// Only intercept the upload-attachment action.
if ( ! isset ( $_POST [ 'action' ] ) ||
'upload-attachment' !== $_POST [ 'action' ] ) {
return ; }
// Duplicate core functionality.
wp_die(); }
by async-upload.php
.
admin_init
.
*/
function
ajax_upload_attachment() {// Only intercept the upload-attachment action.
if ( ! isset ( $_POST [ 'action' ] ) ||
'upload-attachment' !== $_POST [ 'action' ] ) {
return ; }
// Duplicate core functionality.
}
{
'altText'
:
''
,
'description'
:
''
,
'caption'
:
''
,
'credit'
:
''
,
'keywords'
:
''
,
'sha1'
:
'7e51b009bf0e9097a1fd6ba339a78b6181cbde0c'
,
'source'
:
''
,
'source_system_name' : 'wordpress' ,
'fileDataURI'
:
'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAAUGBgcJBwoLCwoNDg0ODRMSEBASEx0V...'
}
return ; }
// Duplicate core functionality.
$schema = Image_Schema\get_image_schema( $image , true );
wp_remote_request(
'https://api-media/v0/images' ,
[
'method'
=>
'POST'
,
'headers' => [
'content-type' => 'application/json' ,
],
'body' => $schema ,
'timeout' => 10 ,
]
); wp_delete_post( $image_id , true );
wp_remote_request(
'https://api-media/v0/images' ,
[
'method'
=>
'POST'
,
'headers' => [
'content-type' => 'application/json' ,
],
'body' => $schema ,
'timeout' => 10 ,
]
); wp_delete_post( $image_id , true );
wp_die(); }
{
"success" : true ,
"data" : [ { "id" : 4424 /* ... / }, { "id" : 4333 / ... / }, { "id" : 4332 / ... / }, { "id" : 4330 / ... / }, { "id" : 4327 / ... / }, { "id" : 4323 / ... / }, { "id" : 4321 / ... / }, { "id" : 4317 / ... / }, { "id" : 4315 / ... / }, { "id" : 4312 / ... */ } ] } /wp-admin/admin-ajax.php?action= query-attachments
{
"id"
:
4424
,
"filename"
:
"deh-logo.jpg"
,
"url"
:
"https://pwcc.cc/wp-content/uploads/2018/02/deh-logo.jpg"
,
"alt"
:
""
,
"description"
:
""
,
"caption"
:
""
,
"name"
:
"deh-logo"
,
"dateFormatted" : "February 8, 2018" ,
"mime"
:
"image/jpeg"
,
"type"
:
"image"
,
"subtype"
:
"jpeg"
,
"sizes" : {
"thumbnail" : {},
"medium" : {},
"large" : {},
"full" : {}
{
"altText"
:
"Plaster casts from Dear Evan Hansen."
,
"caption"
:
"The props department creates a new plaster cast for Dear Evan Hansen each night. It"
,
"dateCreated" : "2018-02-07T23:07:57.191Z" ,
"credit"
:
"Internet"
,
"description" : "Plaster casts Evan wears in Dear Evan Hansen." ,
"id"
:
"78b576d29756c0f06ca5a1c450f4cf84bb69e8de"
,
"keywords"
:
""
,
"source"
:
"Internet"
}
/**
static.ffx.io /bd0d64a85c59655b815776ae46c3d14be7a6098e
static.ffx.io/images/ t_resize_wp_admin /t_quality_best,f_auto/ …
…/images/ $width_357,$height_201 /t_quality_best,f_auto/ …
…/ $multiply_3,$zoom_0.35,$ratio_1.8,$width_357,$x_600,$y_180 / …
add_image_size(
'square1x1'
,
200
,
200
,
true
);
add_image_size(
'landscape3x2'
,
300
,
200
,
true
);
add_image_size(
'landscape16x9'
,
357
,
201
,
true
);
add_image_size(
'portrait2x3'
,
200
,
300
,
true
);
A complete waste of time
add_filter(
'intermediate_image_sizes_advanced' ,
/* sizes generated */
'__return_empty_array'
/* [] - none */ );
get_attachment( 'al4n4b3ck' );
get_attachment( 'al4n4b3ck' ); is_int ( 'al4n4b3ck' );
get_attachment( 'al4n4b3ck' ); is_int ( 'al4n4b3ck' );
false
/**
wp_get_attachment_metadata()
@param
string $attachment_id Attachment ID. Default ''.
*
@param
bool $unfiltered True: filters are not run.
Default false.
@return mixed Attachment meta field.
False on failure.
/ function get_attachment_metadata( / ... */ ) { }
var_dump ( wp_get_attachment_metadata() ); Array ( [ width ] => 2400 , [ height ] => 1559 , [ file ] => '2018/02/you-will-be-found.jpg' , [ sizes ] => Array ( [ square1x1 ] => Array ( width, height, file ), [ landscape3x2 ] => Array ( width, height, file ), [ landscape16x9 ] => Array ( width, height, file ), [ portrait2x3 ] => Array ( width, height, file ), [ etc ] => Array (width, height, file),
) )
var_dump ( API_Media\get_attachment_metadata() ); Array ( [ width ] => 2400 , [ height ] => 1559 , [ file ] => 'bd0d64a85c59655b815776ae46c3d14be7a6098e' , [ url ] => 'http://static.ffx.io/bd0d64a85c59655b81…98e' , [ sizes ] => Array ( [ square1x1 ] => Array ( width, height, file, url ), [ landscape3x2 ] => Array ( width, height, file, url ), [ landscape16x9 ] => Array ( width, height, file, url ), [ portrait2x3 ] => Array ( width, height, file, url ), [ etc ] => Array (width, height, file, url),
) )
API_Media\get_attachment_metadata( 'bd0d64...98e' ); API_Media\get_attachment( 'bd0d64...98e' ); API_Media\get_attachment_url( 'bd0d64...98e' );
{
"id"
:
"bd0d64a85c59655b815776ae46c3d14be7a6098e"
,
"filename"
:
"bd0d64a85c59655b815776ae46c3d14be7a6098e"
,
"url"
:
"https://static.ffx.io/bd0d64a85c59655b815776ae46c3d14be7a6098e.jpg"
,
"alt"
:
"The Dear Evan Hansen cast perform You Will Be Found"
,
"description"
:
"Dear Evan Hansen cast perform You Will Be Found, the emotional first act climax of Dear Evan Hansen"
,
"caption"
:
"Dear Evan Hansen cast perform You Will Be Found, the emotional first act climax of Dear Evan Hansen"
,
"name"
:
"bd0d64a85c59655b815776ae46c3d14be7a6098e"
,
"dateFormatted" : "February 8, 2018 09:02am" ,
"mime"
:
"image/jpeg"
,
"type"
:
"image"
,
"subtype"
:
"jpeg"
,
"sizes" : {
"square1x1" : {},
"landscape3x2" : {},
"landscape16x9" : {},
"thumbnail" : {}
Not numeric That’s new These names have changed { "id" : "bd0d64a85c59655b815776ae46c3d14be7a6098e" , "filename" : "bd0d64a85c59655b815776ae46c3d14be7a6098e" , "url" : "https://static.ffx.io/bd0d64a85c59655b815776ae46c3d14be7a6098e.jpg" "alt" : "The Dear Evan Hansen cast perform You Will Be Found" , "description" : "Dear Evan Hansen cast perform You Will Be Found, the emotional first act climax of Dear Evan Hansen" "caption" : "Dear Evan Hansen cast perform You Will Be Found, the emotional first act climax of Dear Evan Hansen" "name" : "bd0d64a85c59655b815776ae46c3d14be7a6098e" , "dateFormatted" : "February 8, 2018 09:02am" , "mime" : "image/jpeg" , "type" : "image" , "subtype" : "jpeg" , "sizes" : { “thumbnail" : {}, "landscape3x2" : {}, "landscape16x9" : {}, "thumbnail" : {}
wp.media.view ○ Attachment ○ AttachmentCompat ○ AttachmentFilters ○ Attachments ○ AttachmentsBrowser ○ AudioDetails ○ Button ○ ButtonGroup ○ Cropper ○ DateFilter ○ EditImage ○ EditorUploader ○ Embed ○ EmbedImage ○ EmbedLink ○ EmbedUrl ○ FocusManager ○ Frame ○ Iframe ○ ImageDetails ○ Label ○ MediaDetails ○ MediaFrame ○ Menu ○ MenuItem ○ Modal ○ PriorityList ○ Router ○ RouterItem ○ Search ○ Selection ○ Settings ○ Sidebar ○ SiteIconCropper ○ SiteIconPreview ○ Spinner ○ Toolbar ○ UploaderInline ○ UploaderStatus ○ UploaderStatusError ○ UploaderWindow ○ VideoDetails
Inherited from WordPress ○ 42 views ○ 6 models ○ 19 collections
Inherited from WordPress ○ 42 views ○ 6 models ○ 19 collections
Underscore
Backbone $ jQuery
require ( './query' );
require ( './query' );
require ( './query' );
require ( './query' );
require ( './query' );
/**
wp.media.query
We're overriding this because we need to use our custom
function ( props = {} ) {
return new Attachments ( null , {
props : _ . extend (
_ . defaults ( props, { orderby : 'date' , order : 'DESC' } ), { query : true } ) } ); };
/**
wp.media.query
We're overriding this because we need to use our custom
function ( props = {} ) {
return new Attachments ( null , {
props : _ . extend (
_ . defaults ( props, { orderby : 'date' , order : 'DESC' } ), { query : true } ) } ); };
/**
template : function () {
const prefix = ( this . model . get ( 'type' ) === 'image' ) ? 'ffx-' : '' ;
wp
.
template
(
${ prefix } attachment
);
return template . apply ( this , arguments ); } } );
/**
template : function () {
const prefix = ( this . model . get ( 'type' ) === 'image' ) ? 'ffx-' : '' ;
wp
.
template
(
${ prefix } attachment
);
return template . apply ( this , arguments ); } } );
A heavy touch server side A lighter touch client side
/**
@class
Backbone . Model . extend ( {
defaults : {
id : '' ,
url : '' ,
attachment_id : '' ,
autoCrop : true ,
fileName : '' ,
aspect : '' ,
originalWidth : 0 ,
cropWidth : 0 ,
// Attributes below should not saved.
imageUrl : '' ,
[ img id= "f23d41a8ebf536a52eca254e1bb44d996cf21d25"
altText= "..." caption= "..." credit= "..."
source= "..." description= "..."
aspect= "1.5" cropWidth= "300" autoCrop= "false"
offsetX= "-374.3478" offsetY= "-85.9782"
zoom= "0.3062" ][/ img ]
Publishing & workflow
// We don't need this, we have
// the Publish Box of the Future™! remove_meta_box(
'submitdiv' ,
'post' ,
'side' );
// T he Publish Box of the Future™! add_meta_box(
'ffx-submitdiv' , __( 'Save & Publish' , 'ffx' ),
NAMESPACE . ' \ the_loading_icon' , [ 'post' ],
'side' ,
'high' );
AJAX everything avoid full page refreshes
register_rest_field(
'post' ,
'ffx_writeoff' , [
'get_callback'
=>
NAMESPACE
.
'
\
get_writeoff'
,
'update_callback' => NAMESPACE . ' \ set_writeoff' ,
'schema'
=> [
'description' => __( 'Article writeoff' , 'ffx' ),
'type'
=>
'string'
,
],
]
);
register_rest_field(
'post' ,
'ffx_writeoff' , [
'get_callback'
=>
NAMESPACE
.
'
\
get_writeoff'
,
'update_callback' => NAMESPACE . ' \ set_writeoff' ,
'schema'
=> [
'description' => __( 'Article writeoff' , 'ffx' ),
'type'
=>
'string'
,
],
]
);
Sixty-one custom properties
register_rest_field(
'post' ,
'ffx_private' , [
'get_callback'
=>
NAMESPACE
.
'
\
get_privacy'
,
'update_callback' => NAMESPACE . ' \ set_privacy' ,
'schema'
=> [
'description' => __( 'Fairfax Visibility' , 'ffx' ),
'type'
=>
'boolean'
,
],
]
);
Inherited from WordPress
Underscore
Backbone $ jQuery
Inherited from WordPress
Underscore
Backbone $ jQuery % WordPress REST API client library
The Post model ○ author ○ categories ○ comment_status ○ content ○ date ○ date_gmt ○ excerpt ○ featured_media ○ ffx-legal-status ○ ffx-post-state ○ ffx-sources ○ ffx-tags ○ ffx_advertisements ○ ffx_advertiser_logo ○ ffx_advertiser_name ○ ffx_article_tool ○ ffx_authors ○ ffx_bespoke_url ○ ffx_brief ○ ffx_collab_authors ○ ffx_collab_editor ○ ffx_collab_watchers ○ ffx_comments ○ ffx_comments_open ○ ffx_commercial_content_ty pe ○ ffx_correction ○ ffx_correction_text ○ ffx_format ○ ffx_identifier ○ ffx_in_numbers ○ ffx_in_numbers_title ○ ffx_index_headline ○ ffx_intro ○ ffx_label ○ ffx_last_updated ○ ffx_major_update ○ ffx_misc_update ○ ffx_off_time ○ ffx_primary_tag ○ ffx_private ○ ffx_seo_description ○ ffx_seo_news_keywords ○ ffx_seo_noindex ○ ffx_seo_title ○ ffx_slack_channel ○ ffx_sponsor ○ ffx_sports_score ○ ffx_syndication ○ ffx_talking_points ○ ffx_talking_points_live_title ○ ffx_talking_points_title ○ ffx_url ○ ffx_url_content ○ ffx_url_slug ○ ffx_why_it_matters ○ ffx_writeoff ○ format ○ id ○ meta ○ password ○ ping_status ○ slug ○ status ○ sticky ○ template ○ title
wp.api.models.Post.prototype.args.ffx_private
{
required:
false
,
description:
"Fairfax Visibility."
,
type:
"boolean"
}
Post meta of the past
wp_insert_post ○ update post content % create new revision ○ update taxonomies ○ update meta data
Update via REST API ○ update post content & create new revision ○ update taxonomies ○ update meta data
add_filter( 'rest_pre_dispatch' , 'move_revision_callback' , 10 , 3 ); /** Move the 'wp_save_post_revision' callback for REST requests. */ function move_revision_callback( $result , $unused , $request ) {
/* SNIP: Check for post update request */
// Move default wp_save_post_revision
callback.
remove_action( 'post_updated' , 'wp_save_post_revision' , 10 ); add_action( 'rest_request_after_callbacks' , 'wp_save_post_revision' );
return $result ; // Support other filters. }
add_filter( 'rest_pre_dispatch' , 'move_revision_callback' , 10 , 3 ); /** Move the 'wp_save_post_revision' callback for REST requests. */ function move_revision_callback( $result , $unused , $request ) {
/* SNIP: Check for post update request */
// Move default wp_save_post_revision
callback.
remove_action( 'post_updated' , 'wp_save_post_revision' , 10 ); add_action( 'rest_request_after_callbacks' , 'wp_save_post_revision' );
return $result ; // Support other filters. }
add_filter( 'rest_request_after_callbacks' , 'reset_revision_cb' ); /** Reset 'wp_save_post_revision' after REST requests. */ function reset_revision_cb( $response ) {
// Move default wp_save_post_revision
callback.
add_action( 'post_updated' , 'wp_save_post_revision' , 10 );
return $response ; // Support other filters. }
The stack
The stack
Why WordPress?
[ img id= "9550846e3098113e9fe16878fcbc26e23580e8ea" altText= "..." caption= "..." … ][/ img ] "body" : "<x-placeholder id='2c62…d2'></x-placeholder>" , "bodyPlaceholders" : {
"2c62…d2" : {
"type" : "image" ,
"data" : {
"id"
:
"9550846e3098113e9fe16878fcbc26e23580e8ea"
,
"caption"
:
"Lin-Manuel Miranda in Hamilton the Musical"
,
"credit"
:
"Production, Hamilton the Musical"
,
"aspect"
:
1.5
,
"cropWidth"
:
300
,
[ img id= "9550846e3098113e9fe16878fcbc26e23580e8ea" altText= "..." caption= "..." … ][/ img ] "body" : "<x-placeholder id='2c62…d2'></x-placeholder>" , "bodyPlaceholders" : {
"2c62…d2" : {
"type" : "image" ,
"data" : {
"id"
:
"9550846e3098113e9fe16878fcbc26e23580e8ea"
,
"caption"
:
"Lin-Manuel Miranda in Hamilton the Musical"
,
"credit"
:
"Production, Hamilton the Musical"
,
"aspect"
:
1.5
,
"cropWidth"
:
300
,
[ img id= "9550846e3098113e9fe16878fcbc26e23580e8ea" altText= "..." caption= "..." … ][/ img ] "body" : "<x-placeholder id='2c62…d2'></x-placeholder>" , "bodyPlaceholders" : {
"2c62…d2" : {
"type" : "image" ,
"data" : {
"id"
:
"9550846e3098113e9fe16878fcbc26e23580e8ea"
,
"caption"
:
"Lin-Manuel Miranda in Hamilton the Musical"
,
"credit"
:
"Production, Hamilton the Musical"
,
"aspect"
:
1.5
,
"cropWidth"
:
300
,
Project retro
Committers 31
Commits 11,737
“ My job is code review and fooling myself that today I really will pick up a ticket. Me
My guilty secret
My guilty secret I like code review
Code review
is productivity
February 11, 2018 theage.com.au
February 12, 2018 theage.com.au
Slides and white paper
○
pwcc.cc/go/loop2018 Thank you