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
