TV Shows that Changed My Life: Part 2

2017-JUL-24:

The Sopranos

Show Creator: David Chase

The Sopranos taught me that you could like characters who do horrible things as long as there was psychological justification. David chase created characters who were horrible but entertaining. There wasn’t a single person in that series that I would ever want to know in real life. But watching them onscreen was magic.

Buffy the Vampire Slayer

Show Creator: Joss Whedon

At the time, TV had strict limits on what a Genre could do and be. Joss showed the world that genre rules were made to be broken. Looking back it occurs to me that that sort of experiment in TV could never have been done by any network that wasn’t as desperate for viewers as UPN was at the time.

Animaniacs

Show Creators: Rob Paulsen, Jess Harnell, Tress MacNeille

Live before Pixar… Animation was for kids. Live action was for adults. That changed with Animaniacs. The show did adult jokes beside slapstick, some of which I remember to this day: “You can’t split us up!!! We go together like civil war chess pieces from the franklin mint.” Who can forget the rambling kid telling stories: “Ok, so onetime I was sitting on the porch and I felt a lick and thought it was a dog but it wasn’t it just just some guy who liked to do that. Ok, I love you bye bye.” Comedic Genius.

Reboot of Dragnet (First Season)

Show Creator: Dick Wolf

I remember watching this reboot by Dick Wolf having very low expectations. The writing was fucking amazing. It showed me that something could be uniquely derivative. That you could take something, honor the past and supersede the past with such a great effect.

The Jeffersons

Show Creator: Norman Lear, Don Nicholl, Michael Ross

It can’t be understated how much TV has changed American culture. I lived in the DEEP, DEEP south and the black people I saw (when I saw them) in the 1970’s were primarily poor, or at best lower-middle class, like my family. That’s just the way it was. At ten years old, I didn’t have the ability or language to question that view of the world. It simply was. The Jeffersons presented a view of the world where a black person was a successful businessman and lived in a New York High Rise with a maid …and the white people in the show were the “cracker-honkey-whitebreads”. That was a revolutionary idea to me. After the first few episodes, it was accepted reality. Somewhere a black person is rich and lives in a high rise with a sassy maid and a terminally white awkward neighbor. That was the reality simply because TV presented it to me in a way that made me laugh and enjoy it. And just that quickly the world was changed.

The show’s view of race seems so dated now, it’s easy to forget just how revolutionary it truly was.

Also the chemistry between Martha Gibbs and Sherman Hemsley has really never been duplicated anywhere since. Every show with a sassy maid thereafter was just chasing shadows trying to be a tenth as good as those two comedic actors at the top of their game.

Bewitched

Show Creator: Sol Sacks

Bewitched made me think about what I wanted my life to be as an adult. I wanted to come home to a house with modern furniture, mix a martini (though I had no idea what that was) and sit on the couch and talk about the day with someone. It’s weird how everyone remembers this show for the magic but I remember it for the mundane things that were so interesting to me as an adolescent.

Rockford Files // CHiPs

Show Creator: Stephen J. Cannell, Roy Huggins // Rick Rosner, Paul Playdon

The Rockford Files and Chips taught me that the only place on the planet to live was Southern California. I developed a real love for the setting. I think this was where my move to Southern California was first planted in my mind. The first time I walked on that hard-pack California clay-rock, I thought to myself… it’s just like on CHiPs!!!

Gilmore Girls

Show Creator: Amy Sherman-Palladino

Has there ever been a show that packs so much interesting dialog into 42 minutes? The show was completely built on dialog. Nobody ever did anything. They just talked about doing it. And talked about not doing it. Whether they should do something or not was a topic of endless debate and I continue to marvel at how well the back and forth of dialog created a show literally out of nothing.

Designing Women // Golden Girls

Show Creators: Linda Bloodworth-Thomason // Susan Harris

What can I say about Designing Women and Golden Girls that hasn’t already been said by a thousand other gay men in a thousand other kvetchy tweets, articles and blog entries. Sassy women with rich inner lives talking about their thoughts on sex, politics, and religion. They were wonderfully entertaining and the world will never see their like again.

PART 1

Spiderman Sheets

2017-JUL-23:

I just watch the movie Attack the Block and I gotta tell ya’ the writing was really good. It was a cute movie. It’s a notable in that both John Boyega (Star Wars: The Force Awakens) and Jodie Whittaker (the 13th Doctor on BBC’s Doctor Who) were in it and it was some of their “Early Work”.

I think it’s notable for a few reasons, only one of which I want to write about here: Spiderman Sheets.

There’s a scene where Jodie Whittaker goes into an abandoned apartment where John Boyega’s Character has been living and there’s a dusty disused Child’s bedroom where the bed is covered in Spiderman sheets.

Those few lines of dialogue and the vidual of the Spiderman sheets tell us everything we need to know about the last 10 years of John Boyega’s Life.

Fantastic writing. SHOW, DON’T TELL.

TV Shows that Changed My Life: Part 1

2017-JUL-19:

Mary Tyler Moore

Series Creator: James Brooks & Alan Burns

MTM was the first show I remember really enjoying with my parents. Until that time, I watched kids TV, alone or with my brother. MTM was a show I could enjoy with Mom and Dad sitting on the couch behind me.

Dad always got home early because he taught middle school band. Mom worked later. She was a A/P clerk for Pepesi Cola. Dad would have dinner ready. After dinner, my brother and I would lay in the middle of the floor on shag carpeting and mom and dad would sit on the couch, usually holding hands.

It probably wasn’t the first show we all enjoyed together, but it’s the one I remember. I remember everyone laughing. Whatever arguments or differences we had, everyone laughed for 30 minutes when Mary Richards made a mess of things.

Life was very, very good.

M*A*S*H

Series Creator: Larry Gelbert

MASH was the first show I remember changing my mind about something.

Poppa (my grandfaterh) was in World War II. My uncle went was in the Korean war. If there was a war somewhere, we supported it because not supporting it was not supporting the people fighting it.

Alan Alda taught me shows could present alternate arguments that prevail. You could be against war but still have a passion and love for the people who were fighting it. Alan Alda was a liberal’s liberal. He was so passionate about everything he believed in. I think Hawkeye was my first real role model.

Superfriends

Series Creators: William Hanna and Joseph Barberra

The show that taught me as a young child that writing could be bad and could get it completely wrong. I loved the characters so much and the series was so bad. It taught me I could love something that was utterly horrible because of my affinity for the Characters.

Star Trek (TOS)

Series Creator: Gene Roddenberry

The future belonged to smart people. When you’re a smart person in rural Florida and you attend school, you don’t have a great time being smart. Usually you either dumb yourself down to the level of your peers or you get beat up a lot. I chose the latter. Star Trek made me okay with being a nerd. The opening still gives me chills to this day.

Wonder Woman

Series Creators: William Moulton Marston & Stanley Ralph Ross

In the 70’s and early 80’s, guys were the hero. The story was about the guy. The women were the victim or there to help comfort the victim when the guy saved her. Wonder Woman was having none of your misogyny. She was nobody’s victim and if she was a victim, she saved herself. You can’t put a price tag on the lessons that teaches. Both men and women.

Three’s Company

Series Creators: Don Nicholl, Michael Ross & Bernard West

The first time I ever saw someone who was ok with being perceived as gay. Being gay was something that, if you were accused, you fought against it. Jack was not gay but was ok with being perceived as such. Truly revolutionary to an impressionable mind.

The West Wing

Series Creator: Arron Sorkin

Duh, Sorkin. Dialogue, Characters, world-ending stakes, a love of poetry, art, music… respect for the job people elected to office do. There’s no episode of this show that didn’t teach me something. Every dramatic TV writer on the planet in one way or another will live in the shadow of the “Two Cathedrals” episode. It might be the greatest hour ever teleivised. Cursing at God in latin? There aren’t five actors in Hollywood that could ever hope to pull that off.

Six Feet Under

Series Creator: Alan Ball

I watched the first episode a few weeks after I came out to my my mom. David’s journey was my journey. There’s a scene in the first season where David is on his knees praying not to be gay. Typing that last sentence literally brings tears to my eyes today. I was watching that episode with the few gay friends I had at the time and someone said something to the effect of “Who hasn’t prayed that prayer?” And then I knew that other people had too.

PART 2

Coming out... Again

2017-JUL-15:

So last year I took a position at a company called Legacy.com, moving myself and my husband to Los Angeles. Basically the same kind of Drupal/PHP/ESScript work i’ve been doing but focused primarily on Drupal as a backing store for a REACT-based front end. If you don’t understand what that is, don’t bother. Understanding it is not important to the post.

It’s been a great year. I love the work. I love the site. I love the people I’m working with. My commute is 10 minutes. I’m paid very well.

But I’ve had a dream. For a long time this dream has gestated. A dream I haven’t dared share because it was completely ridiculous.

That dream is to write for the screen.

Yeah, I know. EVERYONE who’s ever moved to LA and bought a copy of Final Draft has decided they’re going to write The Great American Screenplay(tm). Or better yet, a screenplay about writing The Great American Screenplay(tm). I told you it was ridiculous like a paragraph ago.

It. Is. Completely. Ridiculous.

Especially given that I have such a good career that would keep me comfortable for the next 10 years or until I could retire.

But I can’t escape the dream. It chases me down. It reminds me in my sleep every night that I could be more… and that what I am now is less than my realized potential.

In my life I can point to a series of issues about which i’m passionate:

  1. The political climate in America
    It’s no secret a number of Americans were unahppy with the results of the election of 2016. I’ve had to face the fact that people who don’t care about literally anything but themselves are now in charge of our government and are making laws that pretty much only benefit themselves and they’re rich friends.

  2. Increasing violence against minorities (both LGBTQ and ethnic)
    I literally can’t open news apps anymore. If it’s not a headline about The President doing something stupid, embarassing or corrupt (or a combination therof) then it’s some innocent minority getting caught up in a violent event by being in the wrong place at the wrong time. Sometimes it causes me to lose sleep.

  3. Increasing climate distress
    Every year this planet gets a little bit hotter because of the carbon humans keep pumping into the air. If you don’t believe this is true. Please stop reading now. I’m sorry. We’ll need to part ways from here. Climate catastrophe is a bus. It’s coming toward us. You can get on the bus going the other way or you can get run over. Your choice.

  4. Peak TV
    It’s no secret I love TV. Anyone who knows me, knows I grew up in front of a Television and continue to love the various offerings of broadcast, cable and streaming services. Two years ago I cut the cord and no longer have a cable box. I subscribe to multiple streaming services that add up to less than what I was paying for cable but with so much more of the content I love.

  5. Peak Comics
    The last ten years has seen comic book culture going mainstream. There has never been a time in my life where so much comic book culture is available both on the internet and in comic book stores. The stories of our comic books have become America’s mythology. The heros, American gods (no disrespect to Neil Gaiman’s fantastic work). I believe Comic Books and Jazz will America’s longest lasting contributions to the human experience.

These converging tsunamis are driving a desire in me for change. But I have a very keen understanding of my own impotance in afffecting change to both public opinion and/or public policy.

I can add my voice to the chorus of people who are saying the same thing I am on social media, and I do. But you can’t convince someone of something about which they’ve already made up their mind.

How do you change someone’s mind? How do you take a brain that truly believes climate change is made up by the Chinese to sell solar panels, show them data from literally hundreds of scientists and have them come to a rational conclusion other than “You’re trying to sell me something” ?

How do you take someone who calls themselves a “Christian” and convince them that the religious leaders telling them for the last 20 years to shun gay people are not actually encouraging behaviour that Christ would in any way endorse? And worse yet, that attitude contributes to the tacit approval of violence against people who are part of this current society’s “least of these” (to use Christ’s language)?

And not to beat a dead horse, here, but Sunday morning 10 A.M. is still the most racially segregated hour in American life. ‘Nuff said.

How do you convince someone who’s a 4th generation coal miner in West Virginia that they’re the one that needs to change and that coal’s days as a useful material on this planet are numbered?

How do you convince someone in Lousiana who feeds their family with money made working an oil rig that fossil fuels are endangering their children’s children’s ability to breathe clean air?

Cognative bias is the enemy. I go back to that old screenplay trope: Show don’t tell.

After last year’s election, I felt like the most radical thing I could do was to buy an electric car. I bought a Kia Soul EV. They’re not only better for the planet, they’re actually better at being a car. Much of the stupid stuff that normally breaks down with a gas car simply doesn’t exist in the electric drive train. I continue to look for other ways I can minimize the mess I make while I’m here on this planet. You should too.

I’ve also made the decision to write for the screen. I’ve done that by writing an entry for the Warner Brother’s Television Screenwriting Program.

To my blog, I’m adding my submission essay. I poured all of the tangled feelings about my dream into it.

I’ll find out in September whether or not i’m accepted. I just read a few days ago that there were 2428 submissions this year, the most ever in the history of the program. I’m under no illusions i’ll get in my first year of submission.

I’ve been down this road. I know the signs. Your first program never works. Your first stab at creating a feature for a website is never well-received.

You always suck. Every Time. Until you don’t suck anymore.

It takes 10,000 hours to get shit right. The only way past is through.

This summer i’m writing a few comic book adaptations in preparation for writing next year’s entry.

I haven’t told that many people about my dream to be a writer. So I guess this blog post is a sort of “coming out” as a writer. I’m here. I’m (hopefully) clear. Get used to it.

Pulse

2017-JUN-12:

I stayed mostly quiet on the anniversary of the Pulse shooting, mainly because adding my “thoughts and prayers” to the chorus of Facebook and Twitter updates seems pointless and self-serving.

And also because the problem that created the massacre has yet to be resolved. It could happen again. At any moment the same group of religious zealots or maybe a different group of religious zealots could come busting in enforcing their vision of who God is on the rest of the world. Anywhere. Anytime. We all know who they are because they’re always the same people.

I can’t control the actions of other people. I can only control myself.

I made the decision shortly after the election to rededicate my life to creative expression: Taking it in and breathing it out. And since then I’ve been writing. I’m not sure what final form the things I’m writing will take or even if they’ll ever see the light of day. But what I can’t do… what I refuse to do… is nothing. In my eyes, bitching about how bad things are in whatever arena, politics, religion, entertainment, is completely useless and a wasted effort. Your mileage may vary.

If we owe the victims of the violence of this world anything its to embrace and enjoy the life we’re given and to create. Simply create. As Karen Carpenter once sang, “Don’t worry that it’s not good enough for anyone else to hear, just sing.” I plan to sing. And dance. And write. It’s not a reaction to anyone or any event. It’s simply to use my life and my voice to speak that which I know to be true.

I know this is long. Thanks for reading.

Entrance Essay

2017-MAY-29:

The following is my entrance essay for the Warner Brother’s Television WRiter’s Workshop.

I have a vivid memory from childhood of sitting in front of a 17-inch color television on Saturday morning in my footie pajamas, nineteen-eighty-something, watching “SuperFriends” and thinking to myself “I could write better TV than this.” Superman had no motivation other than to look like a hero, which made me hate him. Wonder Woman was wooden, two-dimensional, and completely extraneous to the plot. And don’t get me started on their take on Batman!

I can’t remember a time when I didn’t want to both consume and be consumed by stories. I grew up on Central Florida’s Gulf Coast… “Jesus’s armpit,” as we called it. During the summer, when kids “up north” would go to camp and play outside, it was nine thousand degrees with three hundred percent humidity, so all I wanted to do was stay somewhere cool and out of the sun.

Mom and Dad were both teachers, and mom was pursuing a graduate degree during the summer months, so she often took us to the library with her. I read probably two or three books per week during those grade school summers, and the stories I read became part of my DNA, making me the man I am today. “The Black Cauldron,” “The Chronicles of Narnia,” “Tales of Fourth Grade Nothing,” “Sherlock Holmes,” “Dune,” “Dragon Riders of Pern,” Dean Koontz, Steven King, Judy Blume, Arthur Clarke: I read them all. I loved them all.

But, a boy’s gotta eat. As it turns out, good reading, language, and retention skills make for a decent software engineer. So, for the last 20 years of my life, I’ve written more software than fiction.

While doing some work for the Obama Administration in 2011, I accepted a contract and wrote a technical book for Packt Publishing (available on Amazon). In the book, I wrote a backstory thread, running the length of the book, about a family pizza parlor that needed a website. I worked the technical examples into the backstory. When reflecting on that writing experience later, it seems obvious that I had loved writing the backstory far more than I had the technical examples.

Maybe I’m good enough to make a living with my writing. Maybe not. But I am going to write. I have to write. I can’t NOT write. The only question in my mind is whether I’m a good enough alchemist to transmute a career writing software into one writing television.

Thank you for your consideration.

Tom Stovall

interviews

2016-AUG-24:

In Silicon Valley, employers want you to love what you’re doing. It needs to be your passion, your existence, your “I Ching.” I just don’t think i’m built that way.

Everything in my life that I’ve ever wanted really badly has never ended up happening. The job I really, REALLY want, I never end up getting because when I WANT to impress someone, it never ends up happening.

If I see a job and I’m like yeah I could be pretty good at that. If I’m lucky enough to get a couple of interviews I think “I could be pretty good at that job and I think can do it really well.” That’s the job I usually end up getting an offer for.

I have to go into the interview not caring whether I get it or not. I have to be “on”, comically. I have to find an opening and land a joke and when people start laughing they start relaxing. If I leave them and they’re still relaxed, I can usually get an offer from that.

The other thing is complex “programmer” math. Folks, I graduated college in 1991. HTML didn’t exist, at least not in its present form. JavaScript, python, java, PHP and Ruby on Rails didn’t exist. Practically every convention that we currently use in computing has evolved into being since then. When I went to college for information systems, I learned SQL, RPG, COBOL and C+ [back then there was only one plus]. The only one of those in current common use is C. And it’s object oriented now with two plus signs.

Asking me about complex “programmer” math is an exercise in futility. I currently know a little bit of Ruby, PHP, JavaScript, enough python to read code but not enough to write, and the barest notion of Java in the places where the syntax coincides with C. All of those are self-taught. I learned them because I sat down with a book and forced myself to learn. And then began working on real world problems for clients using the new tool.

In one interview a 19 year old Chinese girl with thick glasses and even thicker accent (a staff programmer at the company at 19!!!!) asked me to write a b-tree and became angry when I wouldn’t even try. I told her that was a problem that smarter people than I had solved and we didn’t need to rehash it now. Honestly I didn’t know if I remembered how to write one. I could look it up and learn it, but why? It had been done 1000 times by good programmers and I wasn’t going to revolutionize the code so why bother?

Yea, I pulled the ultimate white privilege “Change the rules if you can’t win” but I had already decided I didn’t want to work for the company and was being purposefully argumentative.

What I mean to say is this in interviews Silicon Valley tends to focus on theoretical programming concepts none of which usually end up getting implemented in the real world. Real world problem-solving has a tendency to be much more boring than the theoretical stuff. For example, there’s data from this REST endpoint that needs to go on your page mixed in with data that’s already there and you need to do it in an elegant way that doesn’t block user action on another part of the page but also lets the user know that something is going on behind the scenes and the page they’re looking at may change at some point in the near future.

Now the judge of whether or not all of that happened successfully is really four or five different data points not just the code that makes the REST call. First you need to make sure the REST call begins in A way that doesn’t block user interaction with anything else going on on the page. So what that means is waiting a few seconds after the page completely loads and loads all of its images in order to begin the code that starts the REST call. Once the REST call is made you need to wait for the response in a way that again that doesn’t block anything else going on on the page. That usually easy to do with jQuery, but jQuery is falling out of fashion with most JS Rev’s that are embracing ES6. jQuery’s AJAX stack is built on a promise interface so it’s usually easy to put a post action “onComplete”. Now updating the user of the result can be done in 1000 different ways and may need to be done in more than 2 or 3 practical ways depending on the page.

I said all this to say, knowledge in programming does not equal FINESSE. Any programmer may know how to make the call and update the user in a pop up alert but it takes an experienced programmer and/or II integration specialist to show a progress indicator while the call is being made and unobtrusively slip the data into the DOM and not let the user know unless something goes wrong. That’s programming finesse and its much more difficult to teach to people who don’t love code. It also comes mainly from experience and from a personal decision on the level of fit and finish you’re going to consistently deliver. None of which has anything to do with education.

A love of code is different from being able to regurgitate the solutions to commonly solved problems like b-trees. Its considerably different than the “put data A in slot A and data b in slot b” that is taught in most computer schools.

It is also, coincidentally what keeps me coming back to code. I love looking at beautiful code and reading thru it. I love seeing developing patters and where code is smart and takes advantage of reusibility. I love beautiful, concise classes that once written, you cannot live without. Good code that’s written with finesse is a pleasure to behold.

Or maybe this is just a massive rationalization for not remembering how to write a b-tree.

Two things can be true.

Drupal Store Locator - ~~Part 3~~ SAGA

2015-FEB-08:

So now let’s configure the importer to automatically Geocode the addresses as they’re imported.

###Geocoding

Geocoding isn’t the process of obtaining latitude and longitude for a physical address like “55 Second Street, San Francisco, CA 94105”. This can be done a number of ways, but the easiest way to do this on import is to enable the Geocoding API for the Location module in Drupal.

Navigate to Administration » Configuration » Content authoring

#WHITESCREEN

So while writing this article, I did the previous step and got a white screen. After investigation I figured out the location module’s geocoding currently has an outstanding issue preventing it from correctly geocoding. YAY!

Basically, Drupal tries to obtain some data dynamically from a resource on google spreadsheets that is no longer available. Setting aside the wisdom of relying on an external resource like this, we’ll just say that we need to completely move this solution off the Location module and move it to Geocoder/Geofield/Addressfield/GeoPHP.

Sigh. Lesson learned: Don’t make your module dependant on external data sources unless its stated up front. Scrape your shoe in the grass and move on.

So the modules you need to download and install are Geocoder, Geofield, Addressfield, and GeoPHP. Download and enable.

Add an Addressfield to our content object “Retail Partner”. The defaults should be sufficient. Make sure you enable a country-specific address form under “form handlers”.

</a>

After that, we’re going to create a single geo field to hold the geo information. There’s an option on the geofield config to get the geofield’s data by geocoding the addressfield you just created.

</a>

So now there’s just one problem: Google has strict limits on how much you can geocode in any given day. In order to get past those limits, you have to enter a credit card and have google charge your card for overages. Its been my experience that that never amounts to much until you’re geocoding a LOT of points (20k+). I have about 1500 data points to geocode, which importing the list once will put me at or near the usage limits for the day, so I go ahead and get the google account set up to be charged. But that means the geocoding action needs to include my personal API key so google can recognize its me and allow me past the limits.

Currently the shipping version of the Geocoder module does not do that. I found an issue to add the Google API key to geocoding requests and a patch that will be included in a future version of the module. I used git to clone the development version of the module and then patched it and voila! Google geocoding with an API Key.

Now lets expose the the two new fields to the programmable API using the RESTful project:


/**
 * @file
 * Contains RestfulExampleArticlesResource.
 */
class AnkiRestfulPartnerResource extends RestfulEntityBaseNode {

  var $distanceQuery;

  /**
   * Overrides RestfulExampleArticlesResource::publicFieldsInfo().
   */
  public function publicFieldsInfo() {
    $public_fields = parent::publicFieldsInfo();

    $public_fields['nid'] = array(
      'property' => 'nid',
    );
    $public_fields['vid'] = array(
      'property' => 'vid',
    );
    $public_fields['status'] = array(
      'property' => 'status',
    );
    $public_fields['uuid'] = array(
      'property' => 'uuid',
    );
    $public_fields['language'] = array(
      'property' => 'language',
    );
    $public_fields['address'] = array(
      'property' => 'field_addressfield'
    );
    $public_fields['geo'] = array(
      'property' => 'field_geo'
    );
    
    return $public_fields;
  }

  public function getList() {
    $request = $this->getRequest();
    $autocomplete_options = $this->getPluginKey('autocomplete');
    if (!empty($autocomplete_options['enable']) && isset($request['autocomplete']['string'])) {
      // Return autocomplete list.
      return $this->getListForAutocomplete();
    }
    $entity_type = $this->entityType;
    if (array_key_exists("lat", $request) || array_key_exists("zip", $request)) {
      return $this->proximityQuery($request);
    }
    else {
      $result = $this
        ->getQueryForList()
        ->execute();
    }


    if (empty($result[$entity_type])) {
      return array();
    }

    $ids = array_keys($result[$entity_type]);

    // Pre-load all entities if there is no render cache.
    $cache_info = $this->getPluginKey('render_cache');
    if (!$cache_info['render']) {
      entity_load($entity_type, $ids);
    }

    $return = array();

    foreach ($ids as $id) {
      $toReturn = $this->viewEntity($id);
      $toReturn['location'] = array_merge($toReturn['addressfield'], $toReturn['geo']);
      $return[] = $toReturn;
    }

    return $return;
  }

  /**
   * Overrides RestfulEntityBase::getQueryForList().
   *
   * Expose only published nodes.
   */
  public function getQueryForList() {
    $entity_type = $this->getEntityType();
    $entity_info = entity_get_info($entity_type);
    $query = new EntityFieldQuery();
    $query->entityCondition('entity_type', $this->getEntityType());

    if ($this->bundle && $entity_info['entity keys']['bundle']) {
      $query->entityCondition('bundle', $this->getBundle());
    }
    if ($path = $this->getPath()) {
      $ids = explode(',', $path);
      if (!empty($ids)) {
        $query->entityCondition('entity_id', $ids, 'IN');
      }
    }


    $this->queryForListSort($query);
    $this->queryForListFilter($query);
    $this->queryForListPagination($query);
    $this->addExtraInfoToQuery($query);

    $query->propertyCondition('status', NODE_PUBLISHED);

    return $query;
  }

  public function proximityQuery($origin = NULL) {
    if ($origin == NULL) {
      return [];
    }

    $max_age = variable_get("page_cache_maximum_age", 0);

    if ($max_age !== 0) {
      drupal_add_http_header("Expires", gmdate(DATE_RFC1123, strtotime("+ ".$max_age." seconds")));
      drupal_add_http_header("Cache-Control", "public, max-age=".$max_age);
    }

    $toReturn = ["origin" => []];
    if (array_key_exists("zip", $origin)) {
      $origin += (array) $this->geocodeZip($origin['zip']);
      $toReturn['origin']['zip'] = $origin['zip'];
      //drupal_add_http_header("Expires", date(DATE_RFC850, strtotime("+2 weeks")));
    }
    if ($this->validateOrigin($origin) === true) {
      $toReturn['origin'] += [
        "coords" => [
          "latitude" => $origin['lat'],
          "longitude" => $origin['lon'],
        ]
      ];
    } else {
      return [];
    }

    if (array_key_exists("limit", $_REQUEST) && $_REQUEST['limit'] <= 20) {
      $limit = intval($_REQUEST['limit']);
    } else {
      $limit = variable_get("anki_partner_result_query_limit", 20);
    }


    //variable_set("location_default_distance_unit", "miles");
    $distance_unit = variable_get("location_default_distance_unit", "km");

    $query = db_select("field_data_field_geo", "geo");
    $query->join("node", "n", "n.nid = geo.entity_id and n.vid = geo.revision_id and n.type = geo.bundle and n.status = 1");
    $query->fields("geo", array("field_geo_lon", "field_geo_lat", "entity_id", "revision_id"));
    $query->addExpression($this->earth_distance_sql((float) $origin['lon'], (float) $origin['lat']), "distance");
    $query->condition("geo.entity_type", $this->getEntityType());
    $query->condition("geo.bundle", $this->getBundle());
    $query->condition("field_geo_lon", 0, "!=");
    $query->condition("field_geo_lat", 0, "!=");
    $query->orderBy("distance");
    $query->range(0, $limit);
    $results = $query->execute();


    if ($results->rowCount()) {
      while ($result = $results->fetchObject()) {
        $node = $this->viewEntity($result->entity_id);
        $node += [
          'location' => array_merge($node['address'], $node['geo']),
          "raw_distance" => $result->distance,
          'scalar' => round($result->distance / (($distance_unit == 'km') ? 1000.0 : 1609.347), 1),
          'distance_unit' => $distance_unit
        ];
        $toReturn[] = $node;
      }
    }


    return $toReturn;
  }


  /*
   * Returns the SQL fragment needed to add a column called 'distance'
   * to a query that includes the location table
   *
   * @param $longitude   The measurement point
   * @param $latibude    The measurement point
   * @param $tbl_alias   If necessary, the alias name of the location table to work from.  Only required when working with named {location} tables
   */
  function earth_distance_sql($longitude, $latitude, $tbl_alias = '') {
    //TODO: remove hardcoded location module dependency
    if (!function_exists('earth_radius')) {
      require_once(DRUPAL_ROOT."/sites/all/modules/contrib/location/earth.inc");
    }
    // Make a SQL expression that estimates the distance to the given location.
    $long = deg2rad($longitude);
    $lat = deg2rad($latitude);
    $radius = earth_radius($latitude);

    // If the table alias is specified, add on the separator.
    $tbl_alias = empty($tbl_alias) ? $tbl_alias : ($tbl_alias . '.');

    $coslong = cos($long);
    $coslat = cos($lat);
    $sinlong = sin($long);
    $sinlat = sin($lat);
    return "(IFNULL(ACOS($coslat*COS(RADIANS({$tbl_alias}field_geo_lat))*($coslong*COS(RADIANS({$tbl_alias}field_geo_lon)) + $sinlong*SIN(RADIANS({$tbl_alias}field_geo_lon))) + $sinlat*SIN(RADIANS({$tbl_alias}field_geo_lat))), 0.00000)*$radius)";
  }

  static function geocodeZip($zip) {
    module_load_include("module", "gmap", "gmap");
    module_load_include("inc", "location", "geocoding/google");
    return (array) google_geocode_location(["postal_code" => $zip]);
  }

  function validateOrigin($origin) {

    if (( array_key_exists("lon", $origin) === FALSE || array_key_exists("lat", $origin) === FALSE ) )
    {
      if (array_key_exists("zip", $origin)) {
        throw new RestfulNotFoundException("Postal code was given but unable to resolve its geolocation.");
        return false;
      }
      else {
        throw new RestfulNotFoundException("Unable to locate. lat/lon params are required.");
        return false;
      }
    }

    if (((float)$origin['lat'] == 0 || (float)$origin['lon'] == 0))
    {
      throw new RestfulNotFoundException("Longitude/Latitude or Zip/Postal code needs to be a non-zero number (float)");
      return false;
    }

    if ((int)$zip == 0) {
      if (((float)$origin['lat'] == 0 && (float)$origin['lon'] == 0))
      {
        throw new RestfulNotFoundException("Longitude/Latitude or Zip/Postal code needs to be a non-zero number (float)");
        return false;
      }
    }

    return true;
  }

}


Drupal Store Locator - Part 2

2015-JAN-26:

Second post in my series “Drupal Store Locator” which is an update of a chapter from my book “Drupal Mobile Web Development: A Beginner’s Guide”.

Now that we have the storage container, we need a way of importing and exporting raw data into and out of the container. For that I took at look at the 3 REST API modules, Services, REST and RESTful. The one that made the most sense to me was RESTful because it basically allows you to write classes that represent your exposed data and tightly control how the data is imported.

###Services The bellweather module for implementing REST API’s in Drupal is the Services. There are, however, a couple of issues for which services does not adequately work. First and foremost is the API is not structured according to the standard noun/verb pattern that has become the standard for API’s and I found (my opinion here) posting data and getting that data saved to drupal’s database to be cumbersome at best and at worst, impossible.

###REST REST is an abandoned project that follows the API Pattern, e.g. POST /blog/entry +[data] = new entry. The problem is that it has, in fact, been abandoned and will not be updated, making this solution undesirable for that reason alone.

###RESTful Restful is a project by the guys over at Gizra. The way it differs is that the module itself does not expose any content to the REST interface, rather it is an API by which content can be mad available. The developer must explicitly write classes for each piece of exposed content. This may sound scary, but like I always say, “Let go of my ears and lean back, I’ve done this before.”

##Its About Creation So in evaluating REST frameworks, I tried creating content with the REST interface. While I eventually got each module to work, I found the one that was most customizable was RESTful because in writing the class, I could handle extra arguments in the class and run some error checking on the back end if I cared to do so. Also, the added bonus of exposing only the content types I cared to was very attractive. Whereas the other two REST frameworks exposed all content types to REST actions, RESTful only exposes content types for which you write a data class. This made sense to me.

The class I built was as follows:


/*
 *  @file {module}/plugins/restful/node/partner/1.0/AnkiRestfulPartnerResource.class.php
 *
 *
*/


class AnkiRestfulPartnerResource extends RestfulEntityBaseNode {

  /**
   * Overrides RestfulExampleArticlesResource::publicFieldsInfo().
   */

  public function publicFieldsInfo() {
    $public_fields = parent::publicFieldsInfo();

    $public_fields['nid'] = array(
      'property' => 'nid',
    );
    $public_fields['vid'] = array(
      'property' => 'vid',
    );
    $public_fields['status'] = array(
      'property' => 'status',
    );
    $public_fields['uuid'] = array(
      'property' => 'uuid',
    );
    $public_fields['language'] = array(
      'property' => 'language',
    );

    $public_fields['location'] = array(
      'property' => 'field_location',
    );
    return $public_fields;
  }
  
}

The publicFieldsInfo method exposes which fields will be available to the REST interface. Calling the parent on the first line of the method adds the node title to the list.

What I wanted to be able to do was to move the content from environment to environment without having to move the Drupal database so I wrote a script that does the import via REST with a few classes that extended Guzzle. This is a separate project and separate stack from the Drupal repo. This will be used almost entirely from the command line:


/*
 *  @file {import/export project}/src/Drupal/Partner.php
 *
 *
*/

namespace Drupal\REST;

use GuzzleHttp\Message\Request;
use GuzzleHttp\Post\PostFile;
use GuzzleHttp\Exception\RequestException;
use Symfony\Component\DomCrawler\Crawler;
use Symfony\Component\CssSelector\CssSelector;

/**
 * Class Node
 * @package Drupal\REST
 */
class Partner extends Base {

  static $addressFieldTranslation = [
    "state" => "province",
    "zip" => "postal_code",
    "street2" => "additional",
    "address" => "street",
  ];



  public $remoteInfo = [
    "label" => null,
    "location" => [
      "name" => null,
      "street" => null,
      "additional" => null,
      "city" => null,
      "province" => null,
      "postal_code" => null,
      "country" => "us",
      "latitude" => null,
      "longitude" => null,
      "source" => null,
      "is_primary" => 1,
      "province_name" => null,
      "email" => null,
      "fax" => null,
      "phone" => null
    ],

  ];



  function __construct($values) {
    if (empty($values)) {
      return null;
    }
    foreach ($values as $key => $value) {
      $this->setValue($key, $value);
    }
  }

  function save() {
    try {
      $config = Config::get_config(true);
      $client = Config::get_rest_client('restful');
      $resp = $client->post($config['drupalRestEndpoint']."/en/api/v1/partner", [
        "debug" => false,
        "json" => $this->__toArray()
      ]);
      if (in_array($resp->getStatusCode(), [200, 201, 202])) {
        echo $this->getValue("label")." has been saved.".PHP_EOL;
      }
    } catch(Exception $e) {
      if ($e instanceOf RequestException) {
        echo $e->getRequest() . PHP_EOL;
        if ($e->hasResponse()) {
          echo $e->getResponse() . PHP_EOL;
        }
      } else {
        echo $e->getMessage();
      }
    }
  }

  function setValue($key, $value) {
    if (in_array($key, array_keys(self::$addressFieldTranslation))) {
      $this->remoteInfo['location'][self::$addressFieldTranslation[$key]] = $value;
    } elseif(in_array($key, array_keys($this->remoteInfo['location']))) {
      if ($key == "country") {
        $this->remoteInfo['location'][$key] = strtolower($value);
      } else {
        $this->remoteInfo['location'][$key] = $value;
      }
    } else {
      $this->remoteInfo[$key] = $value;
    }
  }

  function getValue($key) {
    if (in_array($key, array_keys(self::$addressFieldTranslation))) {
      return $this->remoteInfo['location'][self::$addressFieldTranslation[$key]];
    } elseif (array_key_exists($key, $this->remoteInfo['location'])) {
      return $this->remoteInfo[$key];
    } elseif (array_key_exists($key, $this->remoteInfo)) {
      return $this->remoteInfo[$key];
    } else {
      return null;
    }
  }


  function __toJSON() {
    return json_encode($this->__toArray());
  }

  function __toArray() {
    $toReturn = [
      "label" => $this->getValue("label"),
      "location" => $this->getValue("location"),
      "language" => "en",
      "status" => 1,
    ];
    $toReturn['location']['name'] = $toReturn['label'];
    return $toReturn;
  }

}

This class takes an array as constructor argument and posts that array to the drupal resource /en/api/v1/partner. We’ll use this class to script the import from a standard CSV of partner locations and then use the script below to do the work of running the import:


#!/usr/bin/env php
<?php

/*
 *  @file {import/export project}/bin/importPartners.php
 *
 *
*/


ini_set("auto_detect_line_endings", true);

//error_reporting(E_ERROR | E_PARSE);

require_once(__DIR__ . "/../config/config.php");

if (!array_key_exists("GRUNT_CONFIG", $_ENV)) {
  $config = \Drupal\REST\Config::get_config();
}


use GuzzleHttp\Post\PostFile;
use GuzzleHttp\Exception\RequestException;
use Drupal\REST\Node;


$script = array_shift($argv);
$argc--;

if ($argc) {
  echo "Importing {$argv[0]} into drupal env ".$config["drupalRestEndpoint"].PHP_EOL;
  $incoming = [];
  try{
    if (file_exists($argv[0])) {
      $toImport = file($argv[0]);
      foreach($toImport as $line) {
        $incoming[] =  explode(",", substr($line, 0, -2));
      }
      $keys = array_shift($incoming);
    }
    
    if (!empty($incoming)) {
      foreach($incoming as $rowNum => $row) {
        $partner = new \Drupal\REST\Partner(array_combine($keys,$row));
        if ($partner instanceOf \Drupal\REST\Partner) {
          $partner->save();
        }
      }
    }

  } catch(Exception $e) {
    if ($e instanceOf RequestException) {
      echo $e->getRequest() . PHP_EOL;
      if ($e->hasResponse()) {
        echo $e->getResponse() . PHP_EOL;
      }
    } else {
      echo $e->getMessage();
    }
    print_r(debug_backtrace());
  }
}

Notice that the first line starts with a #!/usr/bin/env php. This as well as changing the file permissions to allow execution (chmod +x) will allow us to call this script from the command line with a single argument that represents the data CSV.

Part 3 will describe the front end user interface once we’ve got the data imported.

Drupal Store Locator - Part 1

2015-JAN-21:

A year or so ago, I wrote a book called “Drupal Mobile Web Development: A Beginner’s Guide”. In the book I detail how to make a store locator with Drupal and a few mapping modules.

I just did another one for my current employer Anki and the process was a bit different than the book example for a couple of reasons. First and foremost, we wanted the store locator data to eventually be available to our Android and iOS mobile apps. Second, there are some new front-end modules that will give you more options on the front end now than there were when I wrote the book… most notably “Leaflet”, a javascript library for interacting with map data in browsers. I also wanted to create a solution that was easy enough to upgrade once I decided to take the site to Drupal 8. This is the first in a series of articles detailing how I did what I did and why.

First, let’s discuss the data store we will be using to hold the map data. The data I was given to place points on the map was a series of stores in which our flagship product, Anki Drive is sold. For those points, I was given addresses in the US, Canada and the UK. I need a way to store that address information and more importantly, the Longitude and Latitude of each of those addresses because we can’t map their location without long/lat coordinates.

There are a couple of modules that do this. The Location module series stores all data about a single location in a table separate from standard field data. It doesn’t work with the standard Field API and it doesn’t work with the Entity API, which are two pretty big strikes against it. But it does its job really well. There’s an alternative path using the field API: Geolocation and Address field. This is two unrelated modules that use the field API where Location is designed as an inter-operative suite. I tried the geolocation/address field solution (which is in keeping with best practices for Drupal 8), but there geare currently some outstanding issues with each module. The most significant issue being google Geocoding is broken in the geolocation module (or at least the issue was unresolved when I created the Anki map.

Geocoding is the process of taking an address and turning it into a long/lat pair. More about that process in a bit.

Location is the more mature solution and it works with several geocoding providers. We have a google geocoding API account we use for other purposes so this presented itself as the best solution for the moment.

I created a content object that has a location attached to it as well as a title and description. With the location module, the locations are “attached” to nodes and exists as non-entity data. There’s a secondary module that will auto-load location data when entities are loaded (location_entity).

</a>

Expect parts 2 nd 3 within a week or so.

2017

July

June

May

2016

August

2015

February

January

2014

March

February

2013

April

January

2012

July

May

April

March

January

2011

December

November

October

June

May

March

February

2010

December

September

May

April

March

January

2009

October

September

August

July

May

April

March

February

January

2008

December

November

October

September

August

July

June

May

April

March

February

January

2007

December

November

August

July

June

May

April

March

February

January

2006

March

February

January

2005

December

November

October

September

August

July

June

May

2001

December

All Posts