Sunday, November 11, 2018

Google Maps API - Resolving maps.googleapis.com

I am using the Google Maps API for several websites that retrieve the Lat/Long of an address.


  • Executing the API Call from PHP was NOT working
  • API Key was setup correctly
  • Curl or WGET from Windows is working correctly
  • Get from the browser is working ok
  • WGET/CURL from the command line on CentOS 7 was NOT working ok.
HEre is the output from when I did WGET for the Google Maps API Call:


Resolving maps.googleapis.com (maps.googleapis.com)... 2607:f8b0:4000:816::200a, 216.58.194.106, 216.58.194.138, ...
Connecting to maps.googleapis.com (maps.googleapis.com)|2607:f8b0:4000:816::200a|:443... failed: Connection timed out.
Connecting to maps.googleapis.com (maps.googleapis.com)|216.58.194.106|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: unspecified [application/json]


I could tell it was using IPv6 and the request was timing out. After much Googling I found that others were having the same issue and the resolution was to not use IPv6 for the call. I disabled IPv6 on the machine in question and then the API call worked just fine.

Thank to the following Unixmen Article: https://www.unixmen.com/disable-ipv6-centos-7/


Moving forward, 
  • I need to make sure that IPV6 is not disabled globally for the entire machine. Would be nice if it wasn't.
  • Add this to some checklist somewhere that when I need to use the Google Maps API, I check for this

Full disclosure, I had this working fine on a different CentOS 7 machine and had forgotten how I fixed it for the first 1 hour of research. Then I remembered that it was an "Iptables or Firewall problem". Nope. Then after another hour of research and comparing output on the 2 machines I realized it was due to IPv6. Then I decided to blog about it because I knew I had blogged about it before, but apparently really only thought of blogging about it. Duh!

I hope I can find this blog the next time I need it. :)


Saturday, January 20, 2018

Using Python to access your Trello Boards



Here is a really basic tutorial on using Python to set up and start accessing your Trello boards.

This works with Python 2.7!

First, let's start with installing the python package used to easily access the Trello API:

pip install py-trello

Next, You will need an APPKEY and a Token.
To get your App Key, go to https://trello.com/app-key  and find your App Key. Copy that.
Next, you will need your Token. Click the "Token" and authorize until you receive the following message:

There is your token!

Next, get your Board ID! When you are in Trello, open your Board. The URL will be something like "https://trello.com/b/{HERE IS YOUR BOARD ID}/{THE NAME OF YOUR BOARD}"

Add those pieces of information to a Python file called "myconfig.py"
# myconfig.py:

APPKEY = 'PASTE YOUR API KEY HERE'
TOKEN = 'PASTE YOUR TOKEN HERE'
BOARD_ID = 'PASTE YOUR BOARD ID HERE'


In a different file (e.g., test.py), enter the following code:

from myconfig import *
from trello import TrelloClient


client = TrelloClient(
 api_key=APPKEY,
 api_secret=TOKEN
)

my_board = client.get_board(BOARD_ID)
print my_board.name


Why are we using myconfig.py? This allows us to share our programs without sharing our API Key, Token, and Board Id.

Now when you run this simple example, you will connect to Trello and the program will print out the name of your board. Pretty simple, but once you get here and have the connection working, then the rest will come much easier.

Monday, April 17, 2017

Drupal - Creating Anchors in your Articles

Note: This post uses CKEditor in Drupal as the WYSIWYG editor.

The web term is called an "anchor". A place in the web page that marks a section of the page that you can jump to.

When editing the Medical Article Summary, place your cursor at the location you want the anchor. Then click the FLAG icon in the editor (see screenshot).



After clicking the FLAG icon (Anchor), a popup will show asking what you want to call it. Use any name you want with no spaces.



I used the name "anchor1".

Now if I want to automatically jump to this section when I go to view the article, I just append the anchor (with the '#’ symbol).

So what was before: https://example.com/article/test
Now becomes:          https://example.com/article/test#anchor1


This is also how Table of Contents are done on a web page, by having a list of items that you can jump to.


You can have multiple anchors within a web page, but they need to be unique within that web page. In this page, I could not have 2 anchors named "anchor1" or else we don’t know where to jump. However I could have "anchor1" in one article and then "anchor1" in a completely different article.

Monday, September 19, 2016

NGINX 1.4.6 upgrade on AWS EC2

I've been working with NGINX a lot more lately and still love it.

The info:
Using AWS EC2 with Ubuntu 14.04 (trusty)
NGINX 1.4.6 as a web server

During a PCI compliance scan, it was found the the server I was working on was running NGINX 1.4.6, a version released on 2014-03-04, nearly 2 and a half years old (at this writing).

This is the default version installed with the AWS packages for Ubuntu anyway, but I'm not sure about other packages.

On the DEV machine I was using, I tried to do the basic update:
sudo apt-get install nginx

The resulting version of NGINX was still 1.4.6, so I figured it was the repo being used.


FIRST STEP: Make sure you backup your config files!!! To some place other than /etc/nginx/, like perhaps your home dir. When I performed my install, all the files in /etc/nginx/sites-available were deleted.


I found this nice article on nginx's website: https://www.nginx.com/resources/admin-guide/installing-nginx-open-source/#prebuilt_ubuntu
I followed the above section on Ubuntu and everything went great.


After the upgrade was complete, I was running 1.11.4. Great! But now I had to configure my website.
The files in /etc/nginx/sites-available  were gone, so I copied my backup to that directory.

Then in /etc/nginx/nginx.conf
  1. set user to www-data
  2. Add the line if you are still using sites-available:
    1. include /etc/nginx/sites-enabled/*;

In the directory /etc/nginx/conf.d, rename default.conf to something else.

Restart NGINX:
sudo service nginx restart




Tuesday, June 7, 2016

Drupal Rules - Sending to Multiple Emails

This one took me awhile, with a lot of interesting testing.

I had a Rule setup to email a user when a node went to a certain Workflow state. Easy enough.

myemail@example.com  works just fine!


To add a second email? Difficult. That's the part where you actually need to read the description in the TO section of the Action:
"The formatting of this string must comply with RFC 2822."

What's RFC 2822? No clue really, until I did some research. Read this if you want to know the whole RFC. https://tools.ietf.org/html/rfc2822#page-15

Based on Page 15 and looking at an email I had received through my GMail, I decided to try using the angle brackets. Here's what I got:

<myemail@example.com>, <jarod@example.com>

I re-ran the rule and received the email I was expecting. Jeepers!


Monday, May 23, 2016

Webmin on CentOS 7

Here's a great resource page for installing Webmin:
If you can't access the Webmin login screen when you are done, (https://[server-ip]:10000) then you may need to review your firewall rules. 
For IPtables, this worked for me: http://www.webmin.com/firewall.html

Thursday, April 21, 2016

Drupal - Field Collection - Appending/Inserting an Entity

Kudos for this solution goes to Drupaler Justin Fraser :
Slightly changed description to fit more of my append/insert issue.


I had an issue where I was trying to append new field collection entity to the 'user' Field Collection and doing so would delete any existing items. The solution was to directly load the user entity and not depend on the global $user as that one doesn't have the field collection information.
$thisUser = entity_load('user', array($user->uid));
$thisUser = $thisUser[$user->uid]; //make thisUser the user object, not an array of user objects

$newFieldCollectionItem = array();
$newFieldCollectionItem['field_name'] = 'field_download';
$newFieldCollectionItem['field_download_filename'][LANGUAGE_NONE][0]['value'] = $src;
$newFieldCollectionItem['field_download_date'][LANGUAGE_NONE][0]['value'] = time();

$newFieldCollection = entity_create('field_collection_item', $newFieldCollectionItem);
$newFieldCollection->setHostEntity('user', $thisUser);
$newFieldCollection->save();

Line 2 $thisUser[$user->uid] is what made it work for me.

The rest of the code had been the exact same, but adding line 2 is what did it for me!!!

Wednesday, February 17, 2016

IP Range for where you are.


Synopsis....

I needed to enable a range of IP's for use in an AWS Security Group so that other developers could access a test website I was working on. Here's how I found the smallest range, rather than opening it the whole world. NOTE: There is a small security issue with this method, it is described below.

All IP's pointing to me have been obfuscated...your eyes aren't going bad. :)

Step 1 - Find your IP

There's lots of ways to do this, but the quick simple way I use is simply going to

https://myip.ms/

And there's your IP!



Step 2 - Find Your IP Range

Now you know your IP. Now click on the WHOIS? icon and it takes you to the next page which shows you everything known about that IP. Who owns it, the IP range that is owned by them, etc.

Here's my general IP range:
X.X.128.0 - X.X.255.255    (32,768 ip)

So I want to figure out the short form of that IP Range.

Scroll down (or search) for CIDR. This is the value you want to use. The whole thing. For example, X.X.128.0/17 (it may be different based on the IP Range from above).



This CIDR is the value I used in the AWS console to denote my range of IP's.

Now the security hole here is that anyone in this set of 32,768 IP's can access my special website (on a specific port) that I setup with this particular rule. I'm ok with this small, if not virtually non-existent hole for my TEST website. Risk vs. Reward. I'll be sure to lock down the production site though.

More information on CIDR and what it means:
https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing


Monday, June 22, 2015

Windows, Nginx, MariaDB...and sometimes SQLServer

I have a couple projects at work that are quite different.

All are using PHP.

Some projects require connecting to MSSql (SQL Server).

Other projects are Drupal based.

I had been using XAMPP for awhile now, but knew I wanted to use Nginx on Windows, so I finally took the leap and got it running.


Here's a great stack to use for Windows, PHP, Nginx, XDebug, and MariaDB:  http://wpn-xm.org/


Now, in my case I needed to interface with a SQL Server database for other projects. The Microsoft Drivers for PHP for SQL Server are located here: https://msdn.microsoft.com/en-us/sqlserver/ff657782.aspx

BUT (at the time of this writing) PHP 32-bit is required for these SQL Server drivers, so make sure you download the correct package from WPN-XM. This was my "gotcha" that took awhile for me to get this running. I was getting "could not find driver" even though the correct versions were available.


I've got some more learning to do, but so far I've got multiple projects running with some accessing MSSQL and others MariaDB, all using Nginx and PHP. The speed difference compared to Apache is remarkably noticeable.

Also for my Drupal projects, I was able to do a simple export from MySQL and then an import into MariaDB. My Drupal sites needed no changes in-order-to run, other than a server config in the nginx.conf file.





Wednesday, May 20, 2015

Python example to Add task in Todoist

A simple script that will prompt you for a task description and date, then add it to the project in Todoist.


Requires:
* https://github.com/Doist/todoist-python  - View the Readme for installing the todoist-python library
* Python 2.7+
* PIP: https://pypi.python.org/pypi/pip


Code:

import todoist

api = todoist.TodoistAPI('HERE_IS_MY_API_KEY')
task_desc = input('Enter the task: ')
task_time = input('When (optional): ')  
item = api.items.add(task_desc, MY_PROJECT_ID, date_string=task_time)
api.commit()



I just created a simple shortcut in my taskbar and it easily adds tasks to the project ID. I used the Inbox project Id from my account so everything goes in there. I can sort it later.

The "When" parameter recognizes the Todoist date/times such as:
  • today
  • tomorrow
  • 2 weeks
  • 1 month
If you press Enter, instead of adding a date, then the task is still added, but with no date.

Tuesday, April 21, 2015

Multifield - Add value using web service

One little checkbox...that's all it took. Something I didn't understand, but now do just a little better.

Here we are:

  1. Using Multifield module
    1. Multifield is defined as Unlimited Number of values
  2. Using Services with REST server to update a node - adding a value in the list of Multifields
  3. Using Postman to PUT to update that node
  4. Content type name: test_multi
  5. Multifield field name: field_jarod_multi
  6. Multifield subfield name: field_jarod_note, Integer
Here's what the Multifield config looks like by default for jarod_multi:
Note the checkbox is on for "Hide blank items". So when editing a node we see this:
That's fine. If I want to add another, I just use the button and we're good.
BUT it doesn't work when adding another through web services. Doing a PUT will give me a return code of 200, but the value is never added.

To do that, uncheck the "Hide blank items" button which gives a view like this when editing a node:
Now in my web services I do a PUT:

The full value for the Key is 
node[field_jarod_multi][und][2][field_jarod_note][und][0][value]

After the PUT, I get the new multifield value added to the node:

Lots of headaches over this one. I just missed the checkbox.  So glad this one is solved.



Wednesday, February 4, 2015

SMTP Auth Support - Drupal module Gotcha!

In using the SMTP module, I found an error that I really did not expect.

Using the Debug mode of SMTP Auth, I found that the email/password combination I was using was working ok. Auth was always successful. When sending the message, I continued to get "553 Relaying disallowed".

The options for "E-MAIL OPTIONS -> E-mail from address" in the SMTP auth config page were all correct. It should be using this from address to send emails. Nope!

I don't know what made me check the next thing, but I went to "System -> Site Information" and changed the From email address to the one I was using in SMTP auth. Abracadabra, email sending worked.

Monday, October 20, 2014

My Drupal Web Stack - new and improved

It's amazing how sometimes breaking through a major obstacle leads to new and improved technologies.

I had recently realized that one of my servers had been compromised. Hacked into. The best solution I had come up with was to rebuild the server. I quickly did a backup of the databases and all the code and moved them off of the server.

When spinning up a new server, I had the chance to finally move to a different tech stack I had been wanting to use.
  1. CentOS 7
  2. Nginx
  3. MariaDB
This meant no more Apache and no more MySQL. The change between MySQL and MariaDB was really only in the name. There was no change in code or scripts for what I was doing. Awesome!

The Nginx implementation was a lot easier than I thought it was going to be too. I had some Nginx experience from a past contract and knew of the performance increase. Some configuration changes and learning how to setup multiple virtual hosts, and that was that.

At this point, the basic installation seems as fast as my previous Apache + Memcache install.

What is left?
Well this was a very quick implementation in-order-to get my websites moved to a different server. As fast as the stack is right now, I still want to install Memcache as well as Varnish.


Here are a couple of great articles that I used to setup my server and get Drupal 7 running with Nginx and MariaDB.



A little bit of learning, but I'm very happy with the LEMP and Drupal 7 configuration.

Thursday, August 7, 2014

Backup and Migrate - Restore 1 Table

Well I never thought about trying this before, and there was no reason why it wouldn't work, but restoring a single table into a Drupal website using the Backup and Migrate module worked like a charm!

Scenario

I'm using the IP Ranges module on several websites across several servers. Most of the time the websites have content that is specific to certain geographic regions or countries, so getting hit a ton by China, Russia, or Brazil isn't desirable.

Also, adding the IP Ranges entries to each website (or even a new one) is tedious.

Enter Backup and Migrate

On one site, I do all of my manual entries using the IP Ranges interface. Then I can do a backup of the IP Ranges table from this site. The backup will contain info for just IP_RANGES and nothing else. I do not want to break my other sites. I have a Settings Profile for IP Ranges and then I do a manual backup.

On another site, do a restore using that backup file. But wait.....read below.

One Gotcha!

The backup contains a "DROP TABLE IF EXISTS" statement for ip_ranges. Now the script should be fairly quick, but dropping the table while the module is enabled can cause issues for the site if someone visits.

What I like to do is replace the statement in the script with "TRUNCATE" giving: TRUNCATE `ip_ranges`;

double check the rest of the script to make sure you haven't accidentally included other stuff.

Then run the script through the rest of the sites!


I'd rather use Firewall rules, but I need to do more research on that. I've broken access to sites before because I've entered the wrong Firewall rule. :P

Wednesday, July 23, 2014

Blocking the Spammers

Stop the spammers and bad referrers.

This is a compilation of info I found on various sites.

Add this to the end of your .htaccess file:

SetEnvIfNoCase Via evil-spam-proxy spammer=yes
SetEnvIfNoCase Referer evil-spam-domain.com spammer=yes
SetEnvIfNoCase Referer evil-spam-keyword spammer=yes
SetEnvIfNoCase Via pinappleproxy spammer=yes
SetEnvIfNoCase Referer semalt.com spammer=yes
SetEnvIfNoCase Referer semalt.semalt.com spammer=yes
SetEnvIfNoCase Referer poker spammer=yes

Order allow,deny
Allow from all
Deny from env=spammer


Drupal Modules I use:
ip_ranges - https://www.drupal.org/project/ip_ranges - Block entire range of IP's.

Additional Sites:
http://myip.ms/info/whois/  OR any other popular Whois database. I use this to see the range of IP's I want to exclude. 

This process is a little long for me. Going through the logs and seeing what IP's are hitting bad URL's or protected URL's. Then find the IP range and enter into IP Ranges.  I'd like to see a nice report that uses a Whois database to show where the requests are coming from.

I'm still looking for a better overall solution. For example, I've got to be able to put the rules somewhere in my Apache config to cover all the sites on my server, rather than configuring each one individually, I just haven't taken the time to do it yet. We'll see. :)

Wednesday, June 11, 2014

Module - Front Page

Do you want different user roles to have different Front pages when they login or go to the Home page?

The Front Page module (https://drupal.org/project/front) works very well for that.  18,109 reported installs at the time of this writing.

If you want token support, 7.x-2.4 and lower will need a patch: https://drupal.org/node/1786128

Front Page handles roles in the reverse order of what you have defined in the Role arrangement and creation page (admin/people/permissions/roles), so Admin role, then Authenticated, and then finally Anonymous role will be processed when implementing a custom front page.

Here's a screenshot of 2 roles, Authenticated and Anonymous.
As an example, for an e-commerce site and with token support, I can redirect a "customer" role to their Orders page.

More advanced users and cases will probably want to use Rules.

Monday, April 22, 2013

Handle potential plural items

Let's say you have a count of items ("widgets") that you want to display.

When you count them, the result could be 0, 1, or more than 1 item.  SO you need to display one of the following:

  • 0 widgets
  • 1 widget
  • 2 widgets

In PHP, you can handle this with the following code:

<?php print $view->total_rows;
 if ($view->total_rows == 1) {
   print " widget"; 
} else {
   print " widgets";
}
 ?> 


Or use Drupals built in function "format_plural" to help out:
<?php print format_plural(max(0, $view->total_rows), '1 widget', '@count widgets'); ?>

Wednesday, March 13, 2013

JSON for Drupal select list and location


Background

I'm using PhoneGap and DrupalGap to create my first Android app called "2 For Deals" which helps you track and locate deals in stores that are "2 For"  some dollar value.  Location of the item, as well as expiration of the deal are tracked.  Have a gander (with screenshots): https://play.google.com/store/apps/details?id=com.twofordeals

Tech Talk

DrupalGap provides a JavaScript library for your PhoneGap app, as well as a Drupal module which provides services for accessing data through the Drupal site/data.  Great stuff if you want to combine the two.

JSON

Here is the JSON I used to create a new node which includes the following fields:

  • field_cost: A Decimal field type using the text field widget
  • field_select:  A "List (text)" field type using the "Select list" widget.  Notice the difference between this and some of the other fields.  No index is used.
  • A simple Location determined by users' GPS.  This is the location for the node, where 1 and only 1 is allowed.  Not sure if this is correct, but I had to provide at a minimum the locations and location fields.
  • Body:  standard body field used in Drupal


{
"node": {
"type": "my-content-type",
"title": "This is my new title ",
"language": "und",
"field_cost": {
"und": {
"0": {
"value": "5"
}
}
},
"field_select": {
"und": {
"value": "key-from-drupal for the select"
}
},
"locations": [{
"locpick": {
"user_latitude": "41.98464",
"user_longitude": "-91.673859"
}
}],
"location": {
"locpick": {
"user_latitude": "41.98464",
"user_longitude": "-91.673859"
}
},
"body": {
"und": {
"0": {
"value": "This is the body of my node"
}
}
}
}
}

For blogging purposes, some of the fields are made more generic. The location is one that exists, but points to the middle of a river.  You won't find me there.  :)

I'm posting this because different pieces caused me different headaches at times.  For example on the mobile app, I wanted to collect as little data as possible about the location, so I needed to find the sweet spot of how to create the location on the server side with as few pieces of data as possible.

TODO:  I will eventually be changing the Location to a node reference.  Which means I will need to find the location(s) closest to the users location, display a list, then submit using that reference.  Fun!!!

I hope this helps someone.  Enjoy!

Wednesday, December 19, 2012

New Server setup with CentOS 6, Drush and Drupal libs


My compilation of docs and tutorials to help me get a server setup with CentOS 6, Drush and various libs needed for Drupal 7.  Once that is complete and working, a short write-up on getting a Drupal 7 site up and running in less than 2 minutes.
  1. Webmin install and setup: http://jarodms-drupal.blogspot.com/2012/07/webmin-on-ec2.html
  2. Config Firewall for Webmin:   http://www.webmin.com/firewall.html
  3. LAMP Setup: http://library.linode.com/lamp-guides/centos-6
  4. Drush in less than a minute: http://danreb.com/content/how-install-drush-centos-linux-drupal-development
  5. Additional libraries for Drupal:
    1. yum install php-gd
    2. yum install php-dom
    3. yum install php-mbstring
Additional Steps:
  1. Optimize php.ini
  2. Depending on server, may need to open firewall for port 80

Install Drupal 7 instance in less than 2 minutes:
  1. Create MySQL Database
  2. Create MySQL User/pass
  3. Grant MySQL privileges for user
  4. Config virtual host for new Drupal site and restart Apache

<VirtualHost *:80>
     DocumentRoot "/var/www/html/drupal7"
     ServerName fill-in
     ServerAlias fill-in
     ErrorLog /var/www/logs/drupal7-error.log
     CustomLog /var/www/logs/drupal7-access.log combined
     <Directory "/var/www/html/drupal7">

RewriteEngine on

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} !=/favicon.ico
RewriteRule ^(.*)$ index.php?q=$1 [L,QSA]

</Directory>
</VirtualHost>



Use Drush to download Drupal and install site:
  1. drush dl 
  2. drush site-install standard --account-name=admin --account-pass=admin --db-url=mysql://SiteMySQLUser:SiteMySQLUserPassword@localhost/SiteMySQLDatabase