Weekly Web Harvest for 2019-11-03

  • snarfed/granary: ? The social web translator.
    The social web translator. Fetches and converts data between social networks, HTML and JSON with microformats2, ActivityStreams, Atom, JSON Feed, and more.
  • Paul Ford on Twitter: “Oh my god enough about generations it’s just astrology for opinion columnists.” / Twitter
  • How to Build a Low-tech Website? | LOW?TECH MAGAZINE
    Page size has decreased more than sixfold, number of requests has decreased fivefold, and download speed has increased tenfold. Note that we did not design the website for speed, but for low energy use.
    …..
    In contrast, this website runs on an off-the-grid solar power system with its own energy storage, and will go off-line during longer periods of cloudy weather. Less than 100% reliability is essential for the sustainability of an off-the-grid solar system, because above a certain threshold the fossil fuel energy used for producing and replacing the batteries is higher than the fossil fuel energy saved by the solar panels.

    …..
    When is the best time to visit?
    The accessibility of this website depends on the weather in Barcelona, Spain, where the solar-powered web server is located. To help visitors “plan” their visits to Low-tech Magazine, we provide them with several clues.

  • Low-tech Lab
  • List: My Maximalist Lifehacks – McSweeney’s Internet Tendency
    I always say “yes” — even when no one is asking a question, or speaking, or physically near me.

    When someone suggests anything, I shout, “Think bigger! — “Let’s eat Greek food tonight.” — “Think bigger!” — “Mexican?” — “Think bigger!” — “Russian food?” — “Bigger!” — “Let’s feed asteroids to 1 billion poor people?” — “YES.”

  • Fast and Free Music Separation with Deezer’s Machine Learning Library – Waxy.org
    Cleanly isolating vocals from drums, bass, piano, and other musical accompaniment is the dream of every mashup artist, karaoke fan, and producer. Commercial solutions exist, but can be expensive and unreliable. Techniques like phase cancellation have very mixed results.

    The engineering team behind streaming music service Deezer just open-sourced Spleeter, their audio separation library built on Python and TensorFlow that uses machine learning to quickly and freely separate music into stems. (Read more in today’s announcement.)

Weekly Web Harvest for 2019-10-27

Weekly Web Harvest for 2019-10-20

Weekly Web Harvest for 2019-10-06

Weekly Web Harvest for 2019-09-29

  • Documenting the Now
    Documenting the Now responds to the public’s use of social media for chronicling historically significant events as well as demand from scholars, students, and archivists, among others, seeking a user-friendly means of collecting and preserving this type of digital content. Documenting the Now has a strong commitment to prioritizing ethical practices when working with social media content, especially in terms of collection and long-term preservation.
  • Josh Hader’s Fastball Is Baseball’s Most Mysterious Pitch | FiveThirtyEight
    Hader also owns a below-average total spin rate, as calculated by Statcast’s TrackMan Doppler radar component. The average spin rate for a four-seam fastball this year is 2,284 revolutions per minute, while Hader’s is a rate of 2,154 rpms. Moreover, fastballs — even mid-90 mph iterations — are generally pitches that produce some of the lowest swing-and-miss rates in baseball.

    *****I think about this level of analysis and numbers every time someone says something about AI or big data. A simple game. Intensely scrutinized. Simple rules. Observed from all angles. Recorded. Tons of money. Yet . . .

  • The Day I Thought I Misled the President of the United States: A Visualization Tragicomedy
    “If anything on this graphic causes confusion, ignore the entire product.” That’s perfect. So perfect, in fact, that one of my followers on Twitter, Chen Mingi, suggested I should use it as a title, maybe for an article or even for the next book I’ll write after How Charts Lie.

ACF Repeater Field to Multiple Posts via Advanced Forms

Screenshot of the advanced form interface example.

Origin Story

We have someone who wants to allow people to enter various numbers of items in a particular pattern. We want to keep this as easy as possible and they wanted to avoid having to repeatedly hit submit.

First Pass – Gravity Forms Repeater Field

I initially thought we could do this in Gravity Forms using the beta repeater field option. I used example #2 this time. It lets you reference another form as a repeater field inside the parent field. This example uses the form with ID 1 as the parent and ID 2 as the included repeater.

This worked great except it has issues with file uploads which canceled it out for our uses. This will work nicely for repeaters that aren’t dealing with those field types. And this pattern can be tossed in a plugin without modification with the idea that the first form you make will be the holder and the second form will be the repeater.

// Adjust your form ID
add_filter( 'gform_form_post_get_meta_1', 'add_fields_from_another_form' );
function add_fields_from_another_form( $form ) {
 
    $repeater = GF_Fields::create( array(
        'type'   => 'repeater',
        'id'     => 1000,
        'formId' => $form['id'],
        'label'  => 'My Repeater',
    ) );
 
    $another_form = GFAPI::get_form( 2 );
    foreach ( $another_form['fields'] as $field ) {
        $field->id = $field->id + 1000;
        $field->formId = $form['id'];
            if ( is_array( $field->inputs ) ) {
            foreach ( $field->inputs as &$input ) {
                $input['id'] = (string) ( $input['id'] + 1000 );
            }
        }  
    }
 
    $repeater->fields = $another_form['fields'];
    $form['fields'][] = $repeater;
 
    return $form;
}
// Remove the field before the form is saved. Adjust your form ID
add_filter( 'gform_form_update_meta_1', 'remove_my_field', 10, 3 );
function remove_my_field( $form_meta, $form_id, $meta_name ) {
 
    if ( $meta_name == 'display_meta' ) {
        // Remove the Repeater field: ID 1000
        $form_meta['fields'] = wp_list_filter( $form_meta['fields'], array( 'id' => 1000 ), 'NOT' );
    }
 
    return $form_meta;
}

Second Pass – ACF with Advanced Forms

After seeing that fail, I debated writing something to customize the file upload in Gravity Forms but figured I’d try ACF first. ACF has repeater fields and I recalled using acf_form() in a project a while back but wasn’t sure how it’d handle repeaters. Prior to doing it from scratch, I opted to google a bit for acf forms and repeaters. I found Advanced Forms which is a really easy way to stick ACF on the front of the site as a form.

Advanced Forms was really easy. Turn it on. Associate an ACF group with an Advanced Form and insert on a page via a shortcode. Repeater fields work out of the box. Image uploads are no problem. There is a pro version which offers additional functionality as well.

My goal was to have each repeater field entry create a new post. I wasn’t sure that the pro version would support that and figured I could give it a shot with the free version. They have good documentation.

The Trigger

Our first goal was to figure out how to trigger an event on submission of the form. The documentation makes that easy under processing form submissions. Their example is below.

function handle_form_submission( $form, $fields, $args ) {
    
    $email = af_get_field( 'email' );
    
}
add_action( 'af/form/submission', 'handle_form_submission', 10, 3 );

The Data

Because I have suffered so many times, I have finally wised up and want to see the data before I make any assumptions about what it looks like. That is where the logging function comes to play and the following bit shows how the two things interact to dump the data I want to the debugging log (wp-content/debug.log).

function handle_form_submission( $form, $fields, $args ) {
    $data = $fields;    
    
}

add_action( 'af/form/submission', 'handle_form_submission', 10, 3 );

//happy little logging function
if ( ! function_exists('write_log')) {
   function write_log ( $log )  {
      if ( is_array( $log ) || is_object( $log ) ) {
         error_log( print_r( $log, true ) );
      } else {
         error_log( $log );
      }
   }
}

That gives me data that looks like this for the demo setup. This is two repeater items and it looks scarier than it it.

Array
(
    [5d95e912a38fb] => Array
        (
            [title] => uno
            [image] => Array
                (
                    [ID] => 19
                    [id] => 19
                    [title] => 15353034428_cd94d1abc2_k
                    [filename] => 15353034428_cd94d1abc2_k.jpg
                    [filesize] => 871665
                    [url] => http://192.168.33.10/wordpress/one-two-repeater/wp-content/uploads/sites/61/2019/10/15353034428_cd94d1abc2_k.jpg
                    [link] => http://192.168.33.10/wordpress/one-two-repeater/15353034428_cd94d1abc2_k/
                    [alt] => 
                    [author] => 1
                    [description] => 
                     => 
                    [name] => 15353034428_cd94d1abc2_k
                    [status] => inherit
                    [uploaded_to] => 20
                    [date] => 2019-10-02 18:38:48
                    [modified] => 2019-10-02 18:39:06
                    [menu_order] => 0
                    [mime_type] => image/jpeg
                    [type] => image
                    [subtype] => jpeg
                    [icon] => http://192.168.33.10/wordpress/one-two-repeater/wp-includes/images/media/default.png
                    [width] => 1365
                    [height] => 2048
                    [sizes] => Array
                        (
                            [thumbnail] => http://192.168.33.10/wordpress/one-two-repeater/wp-content/uploads/sites/61/2019/10/15353034428_cd94d1abc2_k-150x150.jpg
                            [thumbnail-width] => 150
                            [thumbnail-height] => 150
                            [medium] => http://192.168.33.10/wordpress/one-two-repeater/wp-content/uploads/sites/61/2019/10/15353034428_cd94d1abc2_k-200x300.jpg
                            [medium-width] => 200
                            [medium-height] => 300
                            [medium_large] => http://192.168.33.10/wordpress/one-two-repeater/wp-content/uploads/sites/61/2019/10/15353034428_cd94d1abc2_k-768x1152.jpg
                            [medium_large-width] => 640
                            [medium_large-height] => 960
                            [large] => http://192.168.33.10/wordpress/one-two-repeater/wp-content/uploads/sites/61/2019/10/15353034428_cd94d1abc2_k-683x1024.jpg
                            [large-width] => 640
                            [large-height] => 960
                            [post-thumbnail] => http://192.168.33.10/wordpress/one-two-repeater/wp-content/uploads/sites/61/2019/10/15353034428_cd94d1abc2_k.jpg
                            [post-thumbnail-width] => 1365
                            [post-thumbnail-height] => 2048
                        )

                )

            [pattern_type] => Array
                (
                    [0] => 3
                    [1] => 4
                )

        )

    [5d95e91ca38fc] => Array
        (
            [title] => Dos
            [image] => Array
                (
                    [ID] => 38
                    [id] => 38
                    [title] => implication12
                    [filename] => implication12.jpg
                    [filesize] => 54948
                    [url] => http://192.168.33.10/wordpress/one-two-repeater/wp-content/uploads/sites/61/2019/10/implication12.jpg
                    [link] => http://192.168.33.10/wordpress/one-two-repeater/implication12/
                    [alt] => 
                    [author] => 1
                    [description] => 
                     => 
                    [name] => implication12
                    [status] => inherit
                    [uploaded_to] => 0
                    [date] => 2019-10-03 12:27:26
                    [modified] => 2019-10-03 12:27:26
                    [menu_order] => 0
                    [mime_type] => image/jpeg
                    [type] => image
                    [subtype] => jpeg
                    [icon] => http://192.168.33.10/wordpress/one-two-repeater/wp-includes/images/media/default.png
                    [width] => 700
                    [height] => 526
                    [sizes] => Array
                        (
                            [thumbnail] => http://192.168.33.10/wordpress/one-two-repeater/wp-content/uploads/sites/61/2019/10/implication12-150x150.jpg
                            [thumbnail-width] => 150
                            [thumbnail-height] => 150
                            [medium] => http://192.168.33.10/wordpress/one-two-repeater/wp-content/uploads/sites/61/2019/10/implication12-300x225.jpg
                            [medium-width] => 300
                            [medium-height] => 225
                            [medium_large] => http://192.168.33.10/wordpress/one-two-repeater/wp-content/uploads/sites/61/2019/10/implication12.jpg
                            [medium_large-width] => 640
                            [medium_large-height] => 481
                            [large] => http://192.168.33.10/wordpress/one-two-repeater/wp-content/uploads/sites/61/2019/10/implication12.jpg
                            [large-width] => 640
                            [large-height] => 481
                            [post-thumbnail] => http://192.168.33.10/wordpress/one-two-repeater/wp-content/uploads/sites/61/2019/10/implication12.jpg
                            [post-thumbnail-width] => 700
                            [post-thumbnail-height] => 526
                        )

                )

            [pattern_type] => Array
                (
                    [0] => 4
                    [1] => 2
                )

        )

)

Processing the Data

We can move into the important part of the data with $fields[0][‘value’]. Once we are in the data, we can now loop through however many repeater entries we have.

The following code loops through the entries, gets the data, and then goes the extra step of using it to create a post for each repeater entry.

function handle_form_submission( $form, $fields, $args ) {
    $data = $fields[0]['value'];
    foreach ($data as $key => $item) {
//GET DATA
    	        $title = $item['title'];
		$image = $item['image']['ID'];
		$cats = $item['pattern_type'];
//MAKE POSTS
		$args = array(
			'post_title' => wp_strip_all_tags($title),
			'post_category' =>  $cats,
			'post_status'   => 'publish',
		);
		$post = wp_insert_post($args);
		set_post_thumbnail($post, $image);
    }
    
}

Google Form to Script to Make Folder, Document, Share it etc.

My click bait titles are so powerful!

This isn’t anything special but it does show a couple of Google Script patterns that we reuse all the time. Folders are made in other folders! Documents are made in those folders. Things are shared. Ownership is assigned! So many things.

The following script takes a form submission and then . . .

  • makes a folder in a particular folder, shares it with particular people, sets the ownership
  • makes a document within that sub-folder, shares it with particular people, sets the ownership
  • adds them as a viewer on a different folder
  • deals with some issues VCU has with students having both @vcu.edu and @mymail.vcu.edu email addresses but without them behaving as if they are the same in Google

Details are in the comments. The trigger is on form submission.

function onFormSubmission(){
   var sheet = SpreadsheetApp.getActiveSheet();//get the sheet where we are writing the form data
   var rows = sheet.getDataRange();//get the entries
   var lastRow = rows.getLastRow();//get the most recent entry  
   var email = sheet.getRange(lastRow,5).getValue();//get the email column 
   var atSymbol = email.search('@');//find the @ symbol in the email
   var cleanEmail = email.substring(0,atSymbol) //chop off the first portion of the email
   var lastName = sheet.getRange(lastRow,3).getValue();//get their last name from the SS
   addStudentToResourceFolder(cleanEmail);
   makeStudentFolder(cleanEmail, lastName);
}


function addStudentToResourceFolder(emailClean){
  var id = 'YOUR_FOLDER_ID';//base resource folder id
  var folder = DriveApp.getFolderById(id);//get that folder
  folder.addViewers([emailClean + '@vcu.edu', emailClean + '@mymail.vcu.edu']);//add student email versions as viewer
}

function makeStudentFolder(emailClean, lastName){
  var holderId = '18KRAbz6ag3v5IiCc31Acs7pkz076Vds1';//get the top level holder folder 
  var draftId = createFolderBasic(holderId, lastName+'_Essay Drafts');//create new sub folder
  var draftFolder = DriveApp.getFolderById(draftId);//get that sub folder
  draftFolder.setOwner('SOMEONE@vcu.edu');//set the owner to someone who is not me
  draftFolder.addEditors([emailClean + '@vcu.edu', emailClean + '@mymail.vcu.edu']);//add student as editor
  var document = DocumentApp.create(lastName +' essay draft'); //create document
  DriveApp.getFileById(document.getId()).addEditors([emailClean + '@vcu.edu', emailClean + '@mymail.vcu.edu']); //add editors
  DriveApp.getFolderById(draftId).addFile( DriveApp.getFileById(document.getId()) );//put doc in folder
   DriveApp.getFileById(document.getId()).setOwner('SOMEONE@vcu.edu');//set the owner of teh doc
}

function createFolderBasic(folderID, folderName) {
  var folder = DriveApp.getFolderById(folderID);
  var newFolder = folder.createFolder(folderName);
  return newFolder.getId();
};