Pastebin launched a little side project called HostCabi.net, check it out ;-)Don't like ads? PRO users don't see any ads ;-)
Guest

core

By: a guest on Dec 14th, 2012  |  syntax: PHP  |  size: 44.94 KB  |  hits: 40  |  expires: Never
download  |  raw  |  embed  |  report abuse  |  print
Text below is selected. Please press Ctrl+C to copy to your clipboard. (⌘+C on Mac)
  1. <?php
  2.  
  3. if( $_SERVER['SCRIPT_FILENAME'] == __FILE__ )
  4.         die( 'Access denied.' );
  5.  
  6. if( !class_exists( 'BasicGoogleMapsPlacemarks' ) )
  7. {
  8.         /**
  9.          * A Wordpress plugin that adds a custom post type for placemarks and builds a Google Map with them
  10.          * @package BasicGoogleMapsPlacemarks
  11.          * @author Ian Dunn <ian@iandunn.name>
  12.          * @link http://wordpress.org/extend/plugins/basic-google-maps-placemarks/
  13.          */
  14.         class BasicGoogleMapsPlacemarks
  15.         {
  16.                 // Declare variables and constants
  17.                 protected $settings, $options, $updatedOptions, $userMessageCount, $mapShortcodeCalled, $mapShortcodeCategories;
  18.                 const VERSION           = '1.9.3-rc2a';
  19.                 const PREFIX            = 'bgmp_';
  20.                 const POST_TYPE         = 'bgmp';
  21.                 const TAXONOMY          = 'bgmp-category';
  22.                 const ZOOM_MIN          = 0;
  23.                 const ZOOM_MAX          = 21;
  24.                 const DEBUG_MODE        = false;
  25.                
  26.                 /**
  27.                  * Constructor
  28.                  * @author Ian Dunn <ian@iandunn.name>
  29.                  */
  30.                 public function __construct()
  31.                 {      
  32.                         add_action( 'init',                                             array( $this, 'init' ), 8 );    // lower priority so that variables defined here will be available to BGMPSettings class and other init callbacks
  33.                         add_action( 'init',                                             array( $this, 'upgrade' ) );
  34.                         add_action( 'init',                                             array( $this, 'createPostType' ) );
  35.                         add_action( 'init',                                             array( $this, 'createCategoryTaxonomy' ) );
  36.                         add_action( 'after_setup_theme',                array( $this, 'addFeaturedImageSupport' ), 11 );                // @todo add note explaining why higher priority
  37.                         add_action( 'admin_init',                               array( $this, 'addMetaBoxes' ) );
  38.                         add_action( 'wp',                                               array( $this, 'loadResources' ), 11 );                          // @todo - should be wp_enqueue_scripts instead?        // @todo add note explaining why higher priority
  39.                         add_action( 'admin_enqueue_scripts',    array( $this, 'loadResources' ), 11 );
  40.                         add_action( 'wp_head',                                  array( $this, 'outputHead' ) );
  41.                         add_action( 'admin_notices',                    array( $this, 'printMessages' ) );
  42.                         add_action( 'save_post',                                array( $this, 'saveCustomFields' ) );
  43.                         add_action( 'wpmu_new_blog',                    array( $this, 'activateNewSite' ) );
  44.                         add_action( 'shutdown',                                 array( $this, 'shutdown' ) );
  45.                        
  46.                         add_filter( 'parse_query',                              array( $this, 'sortAdminView' ) );
  47.                        
  48.                         add_shortcode( 'bgmp-map',                              array( $this, 'mapShortcode') );
  49.                         add_shortcode( 'bgmp-list',                             array( $this, 'listShortcode') );
  50.                        
  51.                         register_activation_hook( dirname(__FILE__) . '/basic-google-maps-placemarks.php', array( $this, 'networkActivate') );
  52.                        
  53.                         require_once( dirname(__FILE__) . '/settings.php' );
  54.                         $this->settings = new BGMPSettings();
  55.                 }
  56.                
  57.                 /**
  58.                  * Performs various initialization functions
  59.                  * @author Ian Dunn <ian@iandunn.name>
  60.                  */
  61.                 public function init()
  62.                 {
  63.                         if( did_action( 'init' ) !== 1 )
  64.                                 return;
  65.                                
  66.                         $defaultOptions                                 = array( 'updates' => array(), 'errors' => array(), 'dbVersion' => '0' );
  67.                         $this->options                                  = array_merge( $defaultOptions, get_option( self::PREFIX . 'options', array() ) );
  68.                        
  69.                         if( !is_array( $this->options ) )
  70.                                 $this->options = $defaultOptions;
  71.                         if( !is_array( $this->options[ 'updates' ] ) )
  72.                                 $this->options[ 'updates' ] = array();
  73.                         if( !is_array( $this->options[ 'errors' ] ) )
  74.                                 $this->options[ 'errors' ] = array();
  75.                                
  76.                         $this->userMessageCount                 = array( 'updates' => count( $this->options[ 'updates' ] ), 'errors' => count( $this->options[ 'errors' ] )     );
  77.                         $this->updatedOptions                   = false;
  78.                         $this->mapShortcodeCalled               = false;
  79.                         $this->mapShortcodeCategories   = null;
  80.                 }
  81.                
  82.                 /**
  83.                  * Getter method for instance of the BGMPSettings class, used for unit testing
  84.                  * @author Ian Dunn <ian@iandunn.name>
  85.                  */
  86.                 public function &getSettings()
  87.                 {
  88.                         return $this->settings;
  89.                 }
  90.                
  91.                 /**
  92.                  * Handles extra activation tasks for MultiSite installations
  93.                  * @author Ian Dunn <ian@iandunn.name>
  94.                  * @param bool $networkWide True if the activation was network-wide
  95.                  */
  96.                 public function networkActivate( $networkWide )
  97.                 {
  98.                         global $wpdb, $wp_version;
  99.                        
  100.                         if( function_exists( 'is_multisite' ) && is_multisite() )
  101.                         {
  102.                                 // Enable image uploads so the 'Set Featured Image' meta box will be available
  103.                                 $mediaButtons = get_site_option( 'mu_media_buttons' );
  104.                                
  105.                                 if( version_compare( $wp_version, '3.3', "<=" ) && ( !array_key_exists( 'image', $mediaButtons ) || !$mediaButtons[ 'image' ] ) )
  106.                                 {
  107.                                         $mediaButtons[ 'image' ] = 1;
  108.                                         update_site_option( 'mu_media_buttons', $mediaButtons );
  109.                                        
  110.                                         /*
  111.                                         @todo enqueueMessage() needs $this->options to be set, but as of v1.8 that doesn't happen until the init hook, which is after activation. It doesn't really matter anymore, though, because mu_media_buttons was removed in 3.3. http://core.trac.wordpress.org/ticket/17578
  112.                                         $this->enqueueMessage( sprintf(
  113.                                                 __( '%s has enabled uploading images network-wide so that placemark icons can be set.', 'bgmp' ),               // @todo - give more specific message, test. enqueue for network admin but not regular admins
  114.                                                 BGMP_NAME
  115.                                         ) );
  116.                                         */
  117.                                 }
  118.                                
  119.                                 // Activate the plugin across the network if requested
  120.                                 if( $networkWide )
  121.                                 {
  122.                                         $blogs = $wpdb->get_col( "SELECT blog_id FROM $wpdb->blogs" );
  123.                                        
  124.                                         foreach( $blogs as $b )
  125.                                         {
  126.                                                 switch_to_blog( $b );
  127.                                                 $this->singleActivate();
  128.                                         }
  129.                                        
  130.                                         restore_current_blog();
  131.                                 }
  132.                                 else
  133.                                         $this->singleActivate();
  134.                         }
  135.                         else
  136.                                 $this->singleActivate();
  137.                 }
  138.                
  139.                 /**
  140.                  * Prepares a single blog to use the plugin
  141.                  * @author Ian Dunn <ian@iandunn.name>
  142.                  */
  143.                 protected function singleActivate()
  144.                 {
  145.                         // Save default settings
  146.                         if( !get_option( self::PREFIX . 'map-width' ) )
  147.                                 add_option( self::PREFIX . 'map-width', 600 );
  148.                         if( !get_option( self::PREFIX . 'map-height' ) )
  149.                                 add_option( self::PREFIX . 'map-height', 400 );
  150.                         if( !get_option( self::PREFIX . 'map-address' ) )
  151.                                 add_option( self::PREFIX . 'map-address', __( 'Seattle', 'bgmp' ) );
  152.                         if( !get_option( self::PREFIX . 'map-latitude' ) )
  153.                                 add_option( self::PREFIX . 'map-latitude', 47.6062095 );
  154.                         if( !get_option( self::PREFIX . 'map-longitude' ) )
  155.                                 add_option( self::PREFIX . 'map-longitude', -122.3320708 );
  156.                         if( !get_option( self::PREFIX . 'map-zoom' ) )
  157.                                 add_option( self::PREFIX . 'map-zoom', 7 );
  158.                         if( !get_option( self::PREFIX . 'map-type' ) )
  159.                                 add_option( self::PREFIX . 'map-type', 'ROADMAP' );
  160.                         if( !get_option( self::PREFIX . 'map-type-control' ) )
  161.                                 add_option( self::PREFIX . 'map-type-control', 'off' );
  162.                         if( !get_option( self::PREFIX . 'map-navigation-control' ) )
  163.                                 add_option( self::PREFIX . 'map-navigation-control', 'DEFAULT' );
  164.                         if( !get_option( self::PREFIX . 'map-info-window-width' ) )
  165.                                 add_option( self::PREFIX . 'map-info-window-width', 500 );
  166.                        
  167.                         if( !get_option( self::PREFIX . 'marker-clustering' ) )
  168.                                 add_option( self::PREFIX . 'marker-clustering', '' );
  169.                         if( !get_option( self::PREFIX . 'cluster-max-zoom' ) )
  170.                                 add_option( self::PREFIX . 'cluster-max-zoom', '7' );
  171.                         if( !get_option( self::PREFIX . 'cluster-grid-size' ) )
  172.                                 add_option( self::PREFIX . 'cluster-grid-size', '40' );
  173.                         if( !get_option( self::PREFIX . 'cluster-style' ) )
  174.                                 add_option( self::PREFIX . 'cluster-style', 'default' );
  175.                        
  176.                         // @todo - this isn't DRY, same values in BGMPSettings::__construct() and upgrade()
  177.                 }
  178.                
  179.                 /**
  180.                  * Runs activation code on a new WPMS site when it's created
  181.                  * @author Ian Dunn <ian@iandunn.name>
  182.                  * @param int $blogID
  183.                  */
  184.                 public function activateNewSite( $blogID )
  185.                 {
  186.                         if( did_action( 'wpmu_new_blog' ) !== 1 )
  187.                                 return;
  188.                        
  189.                         switch_to_blog( $blogID );
  190.                         $this->singleActivate();
  191.                         restore_current_blog();
  192.                 }
  193.                
  194.                 /**
  195.                  * Checks if the plugin was recently updated and upgrades if necessary
  196.                  * @author Ian Dunn <ian@iandunn.name>
  197.                  */
  198.                 public function upgrade()
  199.                 {
  200.                         if( did_action( 'init' ) !== 1 )
  201.                                 return;
  202.                        
  203.                         if( version_compare( $this->options[ 'dbVersion' ], self::VERSION, '==' ) )
  204.                                 return;
  205.                        
  206.                         if( version_compare( $this->options[ 'dbVersion' ], '1.1', '<' ) )
  207.                         {
  208.                                 // Populate new Address field from existing coordinate fields
  209.                                 $posts = get_posts( array( 'numberposts' => -1, 'post_type' => self::POST_TYPE ) );
  210.                                 if( $posts )
  211.                                 {
  212.                                         foreach( $posts as $p )
  213.                                         {
  214.                                                 $address        = get_post_meta( $p->ID, self::PREFIX . 'address', true );
  215.                                                 $latitude       = get_post_meta( $p->ID, self::PREFIX . 'latitude', true );
  216.                                                 $longitude      = get_post_meta( $p->ID, self::PREFIX . 'longitude', true );
  217.                                                
  218.                                                 if( empty($address) && !empty($latitude) && !empty($longitude) )
  219.                                                 {
  220.                                                         $address = $this->reverseGeocode( $latitude, $longitude );
  221.                                                         if( $address )
  222.                                                                 update_post_meta( $p->ID, self::PREFIX . 'address', $address );
  223.                                                 }
  224.                                         }
  225.                                 }
  226.                         }
  227.                        
  228.                         if( version_compare( $this->options[ 'dbVersion' ], '1.6', '<' ) )
  229.                         {
  230.                                 // Add new options
  231.                                 add_option( self::PREFIX . 'map-type',                                  'ROADMAP' );
  232.                                 add_option( self::PREFIX . 'map-type-control',                  'off' );
  233.                                 add_option( self::PREFIX . 'map-navigation-control',    'DEFAULT' );
  234.                                
  235.                                 // @todo - this isn't DRY, those default values appear in activate and settings->construct. should have single array to hold them all
  236.                         }
  237.                        
  238.                         if( version_compare( $this->options[ 'dbVersion' ], '1.9', '<' ) )
  239.                         {
  240.                                 // Add new options
  241.                                 add_option( self::PREFIX . 'marker-clustering', '' );
  242.                                 add_option( self::PREFIX . 'cluster-max-zoom',  '7' );
  243.                                 add_option( self::PREFIX . 'cluster-grid-size', '40' );
  244.                                 add_option( self::PREFIX . 'cluster-style',             'default' );
  245.                                
  246.                                 // @todo - this isn't DRY, those default values appear in activate and settings->construct. should have single array to hold them all
  247.                         }
  248.                        
  249.                         $this->options[ 'dbVersion'] = self::VERSION;
  250.                         $this->updatedOptions = true;
  251.                        
  252.                         // Clear WP Super Cache and W3 Total Cache
  253.                         if( function_exists( 'wp_cache_clear_cache' ) )
  254.                                 wp_cache_clear_cache();
  255.                                
  256.                         if( class_exists( 'W3_Plugin_TotalCacheAdmin' ) )
  257.                         {
  258.                                 $w3TotalCache =& w3_instance('W3_Plugin_TotalCacheAdmin');
  259.                                
  260.                                 if( method_exists( $w3TotalCache, 'flush_all' ) )
  261.                                         $w3TotalCache->flush_all();
  262.                         }
  263.                 }
  264.                
  265.                 /**
  266.                  * Adds featured image support
  267.                  * @author Ian Dunn <ian@iandunn.name>
  268.                  */
  269.                 public function addFeaturedImageSupport()
  270.                 {
  271.                         global $wp_version;
  272.                        
  273.                         if( did_action( 'after_setup_theme' ) !== 1 )
  274.                                 return;
  275.                        
  276.                         // We enabled image media buttons for MultiSite on activation, but the admin may have turned it back off
  277.                         if( version_compare( $wp_version, '3.3', "<=" ) && is_admin() && function_exists( 'is_multisite' ) && is_multisite() )
  278.                         {
  279.                                 // @todo this isn't DRY, similar code in networkActivate()
  280.                                
  281.                                 $mediaButtons = get_site_option( 'mu_media_buttons' );
  282.                
  283.                                 if( !array_key_exists( 'image', $mediaButtons ) || !$mediaButtons[ 'image' ] )
  284.                                 {
  285.                                         $this->enqueueMessage( sprintf(
  286.                                                 __( "%s requires the Images media button setting to be enabled in order to use custom icons on markers, but it's currently turned off. If you'd like to use custom icons you can enable it on the <a href=\"%s\">Network Settings</a> page, in the Upload Settings section.", 'bgmp' ),
  287.                                                 BGMP_NAME,
  288.                                                 network_admin_url() . 'settings.php'
  289.                                         ), 'error' );
  290.                                 }
  291.                         }
  292.                        
  293.                         $supportedTypes = get_theme_support( 'post-thumbnails' );
  294.                        
  295.                         if( $supportedTypes === false )
  296.                                 add_theme_support( 'post-thumbnails', array( self::POST_TYPE ) );                              
  297.                         elseif( is_array( $supportedTypes ) )
  298.                         {
  299.                                 $supportedTypes[0][] = self::POST_TYPE;
  300.                                 add_theme_support( 'post-thumbnails', $supportedTypes[0] );
  301.                         }
  302.                 }
  303.                
  304.                 /**
  305.                  * Gets all of the shortcodes in the current post
  306.                  * @author Ian Dunn <ian@iandunn.name>
  307.                  * @param string $content
  308.                  * @return mixed false | array
  309.                  */
  310.                 protected function getShortcodes( $content )
  311.                 {
  312.                         $matches = array();
  313.                        
  314.                         preg_match_all( '/'. get_shortcode_regex() .'/s', $content, $matches );
  315.                         if( !is_array( $matches ) || !array_key_exists( 2, $matches ) )
  316.                                 return false;
  317.                        
  318.                         return $matches;
  319.                 }
  320.                
  321.                 /**
  322.                  * Validates and cleans the map shortcode arguments
  323.                  * @author Ian Dunn <ian@iandunn.name>
  324.                  * @param array
  325.                  * return array
  326.                  */
  327.                 protected function cleanMapShortcodeArguments( $arguments )
  328.                 {
  329.                         // @todo - not doing this in settings yet, but should. want to make sure it's DRY when you do.
  330.                         // @todo - Any errors generated in there would stack up until admin loads page, then they'll all be displayed, include  ones from geocode() etc. that's not great solution, but is there better way?
  331.                                 // maybe add a check to enqueuemessage() to make sure that messages doesn't already exist. that way there'd only be 1 of them. if do that, make sure to fix the bug where they're getting adding twice before, b/c this would mask that
  332.                                 // maybe call getMapShortcodeArguments() when saving post so they get immediate feedback about any errors in shortcode
  333.                                         // do something similar for list shortcode arguments?
  334.                        
  335.                         global $post;
  336.                        
  337.                         if( !is_array( $arguments ) )
  338.                                 return array();
  339.                                
  340.                         if( isset( $arguments[ 'categories' ] ) )
  341.                         {
  342.                                 if( is_string( $arguments[ 'categories' ] ) )
  343.                                         $arguments[ 'categories' ] = explode( ',', $arguments[ 'categories' ] );
  344.                                
  345.                                 elseif( !is_array( $arguments[ 'categories' ] ) || empty( $arguments[ 'categories' ] ) )
  346.                                         unset( $arguments[ 'categories' ] );
  347.                                        
  348.                                 if( isset( $arguments[ 'categories' ] ) && !empty( $arguments[ 'categories' ] ) )
  349.                                 {
  350.                                         foreach( $arguments[ 'categories' ] as $index => $term )
  351.                                         {
  352.                                                 if( !term_exists( $term, self::TAXONOMY ) )
  353.                                                 {
  354.                                                         unset( $arguments[ 'categories' ][ $index ] );  // Note - This will leave holes in the key sequence, but it doesn't look like that's a problem with the way we're using it.
  355.                                                         $this->enqueueMessage( sprintf(
  356.                                                                 __( '%s shortcode error: %s is not a valid category.', 'bgmp' ),
  357.                                                                 BGMP_NAME,
  358.                                                                 $term
  359.                                                         ), 'error' );
  360.                                                 }
  361.                                         }
  362.                                 }
  363.                         }
  364.                        
  365.                         // Rename width and height keys to match internal ones. Using different ones in shortcode to make it easier for user.
  366.                         if( isset( $arguments[ 'width' ] ) )
  367.                         {
  368.                                 if( is_numeric( $arguments[ 'width' ] ) && $arguments[ 'width' ] > 0 )
  369.                                         $arguments[ 'mapWidth' ] = $arguments[ 'width' ];
  370.                                
  371.                                 else
  372.                                 {
  373.                                         $this->enqueueMessage( sprintf(
  374.                                                 __( '%s shortcode error: %s is not a valid width.', 'bgmp' ),
  375.                                                 BGMP_NAME,
  376.                                                 $arguments[ 'width' ]
  377.                                         ), 'error' );
  378.                                 }
  379.                                
  380.                                 unset( $arguments[ 'width' ] );
  381.                         }
  382.                        
  383.                         if( isset( $arguments[ 'height' ] ) && $arguments[ 'height' ] > 0 )
  384.                         {
  385.                                 if( is_numeric( $arguments[ 'height' ] ) )
  386.                                         $arguments[ 'mapHeight' ] = $arguments[ 'height' ];
  387.                                        
  388.                                 else
  389.                                 {
  390.                                         $this->enqueueMessage( sprintf(
  391.                                                 __( '%s shortcode error: %s is not a valid height.', 'bgmp' ),
  392.                                                 BGMP_NAME,
  393.                                                 $arguments[ 'height' ]
  394.                                         ), 'error' );
  395.                                 }
  396.                                
  397.                                 unset( $arguments[ 'height' ] );
  398.                         }
  399.                        
  400.                         if( isset( $arguments[ 'center' ] ) )
  401.                         {
  402.                                 // Note: Google's API has a daily request limit, which could be a problem when geocoding map shortcode center address each time page loads. Users could get around that by using a caching plugin, though.
  403.                                                                                
  404.                                 $coordinates = $this->geocode( $arguments[ 'center' ] );
  405.                                 if( $coordinates )
  406.                                         $arguments = array_merge( $arguments, $coordinates );
  407.                                
  408.                                 unset( $arguments[ 'center' ] );
  409.                         }
  410.                
  411.                         if( isset( $arguments[ 'zoom' ] ) )
  412.                         {
  413.                                 if( !is_numeric( $arguments[ 'zoom' ] ) || $arguments[ 'zoom' ] < self::ZOOM_MIN || $arguments[ 'zoom' ] > self::ZOOM_MAX )
  414.                                 {
  415.                                         $this->enqueueMessage( sprintf(
  416.                                                 __( '%s shortcode error: %s is not a valid zoom level.', 'bgmp' ),
  417.                                                 BGMP_NAME,
  418.                                                 $arguments[ 'zoom' ]
  419.                                         ), 'error' );
  420.                                        
  421.                                         unset( $arguments[ 'zoom' ] );
  422.                                 }
  423.                         }
  424.                        
  425.                         if( isset( $arguments[ 'type' ] ) )
  426.                         {
  427.                                 $arguments[ 'type' ] = strtoupper( $arguments[ 'type' ] );
  428.                                
  429.                                 if( !array_key_exists( $arguments[ 'type' ], $this->settings->mapTypes ) )
  430.                                 {
  431.                                         $this->enqueueMessage( sprintf(
  432.                                                 __( '%s shortcode error: %s is not a valid map type.', 'bgmp' ),
  433.                                                 BGMP_NAME,
  434.                                                 $arguments[ 'type' ]
  435.                                         ), 'error' );
  436.                                        
  437.                                         unset( $arguments[ 'type' ] );
  438.                                 }
  439.                         }
  440.                        
  441.                         return $arguments;
  442.                 }
  443.                
  444.                 /**
  445.                  * Checks the current posts to see if they contain the map shortcode
  446.                  * @author Ian Dunn <ian@iandunn.name>
  447.                  * @link http://wordpress.org/support/topic/plugin-basic-google-maps-placemarks-can-i-use-the-shortcode-on-any-php-without-assign-it-in-functionphp
  448.                  * @return bool
  449.                  */
  450.                 protected function mapShortcodeCalled()
  451.                 {
  452.                         global $post;
  453.                                
  454.                         $this->mapShortcodeCalled = apply_filters( self::PREFIX .'mapShortcodeCalled', $this->mapShortcodeCalled );             // @todo - deprecated b/c not consistent w/ shortcode naming scheme. need a way to notify people
  455.                         $this->mapShortcodeCalled = apply_filters( self::PREFIX .'map-shortcode-called', $this->mapShortcodeCalled );
  456.                        
  457.                         if( $this->mapShortcodeCalled )
  458.                                 return true;
  459.                        
  460.                         if( !$post )            // note: this needs to run after the above code, so that templates can call do_shortcode(...) from templates that don't have $post, like 404.php. See link in phpDoc for background.
  461.                                 return false;
  462.                        
  463.                         setup_postdata( $post );
  464.                         $shortcodes = $this->getShortcodes( get_the_content() );
  465.                         wp_reset_postdata();
  466.                        
  467.                         for( $i = 0; $i < count( $shortcodes[2] ); $i++ )
  468.                                 if( $shortcodes[ 2 ][ $i ] == 'bgmp-map' )
  469.                                         return true;
  470.                        
  471.                         return false;
  472.                 }
  473.                
  474.                 /**
  475.                  * Load CSS and JavaScript files
  476.                  * @author Ian Dunn <ian@iandunn.name>
  477.                  */
  478.                 public function loadResources()
  479.                 {
  480.                         if( is_admin() )
  481.                         {
  482.                                 if( did_action( 'admin_enqueue_scripts' ) !== 1 )
  483.                                         return;
  484.                         }
  485.                         else
  486.                         {
  487.                                 if( did_action( 'wp' ) !== 1 )
  488.                                         return;
  489.                         }
  490.                                                        
  491.                         $googleMapsLanguage = apply_filters( self::PREFIX . 'map-language', '' );
  492.                         if( $googleMapsLanguage )
  493.                                 $googleMapsLanguage = '&language=' . $googleMapsLanguage;
  494.                        
  495.                         wp_register_script(
  496.                                 'googleMapsAPI',
  497.                                 'http'. ( is_ssl() ? 's' : '' ) .'://maps.google.com/maps/api/js?sensor=false' . $googleMapsLanguage,
  498.                                 array(),
  499.                                 false,
  500.                                 true
  501.                         );
  502.                        
  503.                         wp_register_script(
  504.                                 'markerClusterer',
  505.                                 plugins_url( 'includes/marker-clusterer/markerclusterer_packed.js', __FILE__ ),
  506.                                 array(),
  507.                                 '1.0',
  508.                                 true
  509.                         );
  510.                        
  511.                         wp_register_script(
  512.                                 'bgmp',
  513.                                 plugins_url( 'functions.js', __FILE__ ),
  514.                                 array( 'googleMapsAPI', 'jquery' ),
  515.                                 self::VERSION,
  516.                                 true
  517.                         );
  518.                        
  519.                         wp_register_style(
  520.                                 self::PREFIX .'style',
  521.                                 plugins_url( 'style.css', __FILE__ ),
  522.                                 false,
  523.                                 self::VERSION
  524.                         );
  525.                        
  526.                         $this->mapShortcodeCalled = $this->mapShortcodeCalled();
  527.                        
  528.                         // Load front-end resources
  529.                         if( !is_admin() && $this->mapShortcodeCalled )
  530.                         {
  531.                                 wp_enqueue_script( 'googleMapsAPI' );
  532.                                 if( $this->settings->markerClustering )
  533.                                         wp_enqueue_script( 'markerClusterer' );
  534.                                 wp_enqueue_script( 'bgmp' );
  535.                         }
  536.                        
  537.                         if( $this->mapShortcodeCalled )
  538.                                 wp_enqueue_style( self::PREFIX . 'style' );
  539.                                
  540.                        
  541.                         // Load meta box resources for settings page
  542.                         if( isset( $_GET[ 'page' ] ) && $_GET[ 'page' ] == self::PREFIX . 'settings' )  // @todo better way than $_GET ?
  543.                         {
  544.                                 wp_enqueue_style( self::PREFIX . 'style' );
  545.                                 wp_enqueue_script( 'dashboard' );
  546.                         }
  547.                 }
  548.                
  549.                 /**
  550.                  * Outputs elements in the <head> section of the front-end
  551.                  * @author Ian Dunn <ian@iandunn.name>
  552.                  */
  553.                 public function outputHead()
  554.                 {
  555.                         if( did_action( 'wp_head' ) !== 1 )
  556.                                 return;
  557.                        
  558.                         if( $this->mapShortcodeCalled )
  559.                                 require_once( dirname(__FILE__) . '/views/front-end-head.php' );
  560.                 }
  561.                
  562.                 /**
  563.                  * Registers the custom post type
  564.                  * @author Ian Dunn <ian@iandunn.name>
  565.                  */
  566.                 public function createPostType()
  567.                 {
  568.                         if( did_action( 'init' ) !== 1 )
  569.                                 return;
  570.                        
  571.                         if( !post_type_exists( self::POST_TYPE ) )
  572.                         {
  573.                                 $labels = array
  574.                                 (
  575.                                         'name'                                  => __( 'Placemarks', 'bgmp' ),
  576.                                         'singular_name'                 => __( 'Placemark', 'bgmp' ),
  577.                                         'add_new'                               => __( 'Add New', 'bgmp' ),
  578.                                         'add_new_item'                  => __( 'Add New Placemark', 'bgmp' ),
  579.                                         'edit'                                  => __( 'Edit', 'bgmp' ),
  580.                                         'edit_item'                             => __( 'Edit Placemark', 'bgmp' ),
  581.                                         'new_item'                              => __( 'New Placemark', 'bgmp' ),
  582.                                         'view'                                  => __( 'View Placemark', 'bgmp' ),
  583.                                         'view_item'                             => __( 'View Placemark', 'bgmp' ),
  584.                                         'search_items'                  => __( 'Search Placemarks', 'bgmp' ),
  585.                                         'not_found'                             => __( 'No Placemarks found', 'bgmp' ),
  586.                                         'not_found_in_trash'    => __( 'No Placemarks found in Trash', 'bgmp' ),
  587.                                         'parent'                                => __( 'Parent Placemark', 'bgmp' )
  588.                                 );
  589.                                
  590.                                 $postTypeParams = array(
  591.                                         'labels'                        => $labels,
  592.                                         'singular_label'        => __( 'Placemarks', 'bgmp' ),
  593.                                         'public'                        => true,
  594.                                         'menu_position'         => 20,
  595.                                         'hierarchical'          => false,
  596.                                         'capability_type'       => 'post',
  597.                                         'rewrite'                       => array( 'slug' => 'placemarks', 'with_front' => false ),
  598.                                         'query_var'                     => true,
  599.                                         'supports'                      => array( 'title', 'editor', 'author', 'thumbnail', 'comments', 'revisions' )
  600.                                 );
  601.                                
  602.                                 register_post_type(
  603.                                         self::POST_TYPE,
  604.                                         apply_filters( self::PREFIX . 'post-type-params', $postTypeParams )
  605.                                 );
  606.                         }
  607.                 }
  608.                
  609.                 /**
  610.                  * Registers the category taxonomy
  611.                  * @author Ian Dunn <ian@iandunn.name>
  612.                  */
  613.                 public function createCategoryTaxonomy()
  614.                 {
  615.                         if( did_action( 'init' ) !== 1 )
  616.                                 return;
  617.                                
  618.                         if( !taxonomy_exists( self::TAXONOMY ) )
  619.                         {
  620.                                 $taxonomyParams = array(
  621.                                         'label'                                 => __( 'Category', 'bgmp' ),
  622.                                         'labels'                                => array( 'name' => __( 'Categories', 'bgmp' ), 'singular_name' => __( 'Category', 'bgmp' ) ),
  623.                                         'hierarchical'                  => true,
  624.                                         'rewrite'                               => array( 'slug' => self::TAXONOMY ),
  625.                                         'update_count_callback' => '_update_post_term_count'
  626.                                 );
  627.                                        
  628.                                 register_taxonomy(
  629.                                         self::TAXONOMY,
  630.                                         self::POST_TYPE,
  631.                                         apply_filters( self::PREFIX . 'category-taxonomy-params', $taxonomyParams )
  632.                                 );
  633.                         }
  634.                 }
  635.                
  636.                 /**
  637.                  * Sorts the posts by the title in the admin view posts screen
  638.                  * @author Ian Dunn <ian@iandunn.name>
  639.                  */
  640.                 function sortAdminView( $query )
  641.                 {
  642.                         global $pagenow;
  643.                        
  644.                         if( is_admin() && $pagenow == 'edit.php' && array_key_exists( 'post_type', $_GET ) && $_GET[ 'post_type' ] == self::POST_TYPE )
  645.                         {
  646.                                 $query->query_vars[ 'order' ]   = apply_filters( self::PREFIX . 'admin-sort-order', 'ASC' );
  647.                                 $query->query_vars[ 'orderby' ] = apply_filters( self::PREFIX . 'admin-sort-orderby', 'title' );
  648.                                
  649.                                 // @todo - should just have a filter on $query, or don't even need one at all, since they can filter $query directly?
  650.                         }
  651.                 }
  652.                
  653.                 /**
  654.                  * Adds meta boxes for the custom post type
  655.                  * @author Ian Dunn <ian@iandunn.name>
  656.                  */
  657.                 public function addMetaBoxes()
  658.                 {
  659.                         if( did_action( 'admin_init' ) !== 1 )
  660.                                 return;
  661.                        
  662.                         add_meta_box( self::PREFIX . 'placemark-address', __( 'Placemark Address', 'bgmp' ), array( $this, 'markupAddressFields' ), self::POST_TYPE, 'normal', 'high' );
  663.                         add_meta_box( self::PREFIX . 'placemark-zIndex', __( 'Stacking Order', 'bgmp' ), array( $this, 'markupZIndexField' ), self::POST_TYPE, 'side', 'low' );
  664.                 }
  665.                
  666.                 /**
  667.                  * Outputs the markup for the address fields
  668.                  * @author Ian Dunn <ian@iandunn.name>
  669.                  */
  670.                 public function markupAddressFields()
  671.                 {
  672.                         global $post;
  673.                
  674.                         $address                        = get_post_meta( $post->ID, self::PREFIX . 'address', true );
  675.                         $latitude                       = get_post_meta( $post->ID, self::PREFIX . 'latitude', true );
  676.                         $longitude                      = get_post_meta( $post->ID, self::PREFIX . 'longitude', true );
  677.                         $showGeocodeResults = ( $address && !self::validateCoordinates( $address ) && $latitude && $longitude ) ? true : false;
  678.                         $showGeocodeError       = ( $address && ( !$latitude || !$longitude ) ) ? true : false;
  679.                        
  680.                         require_once( dirname(__FILE__) . '/views/meta-address.php' );
  681.                 }
  682.                
  683.                 /**
  684.                  * Outputs the markup for the stacking order field
  685.                  * @author Ian Dunn <ian@iandunn.name>
  686.                  */
  687.                 public function markupZIndexField()
  688.                 {
  689.                         global $post;
  690.                
  691.                         $zIndex = get_post_meta( $post->ID, self::PREFIX . 'zIndex', true );
  692.                         if( filter_var( $zIndex, FILTER_VALIDATE_INT ) === FALSE )
  693.                                 $zIndex = 0;
  694.                                
  695.                         require_once( dirname(__FILE__) . '/views/meta-z-index.php' );
  696.                 }
  697.                
  698.                 /**
  699.                  * Saves values of the the custom post type's extra fields
  700.                  * @param int $postID
  701.                  * @author Ian Dunn <ian@iandunn.name>
  702.                  */
  703.                 public function saveCustomFields( $postID )
  704.                 {
  705.                         if( did_action( 'save_post' ) !== 1 )
  706.                                 return;
  707.                        
  708.                         global $post;
  709.                         $coordinates = false;
  710.                        
  711.                         // Check preconditions
  712.                         if( isset( $_GET[ 'action' ] ) && ( $_GET[ 'action' ] == 'trash' || $_GET[ 'action' ] == 'untrash' ) )
  713.                                 return;
  714.                        
  715.                         if(     !$post || $post->post_type != self::POST_TYPE || !current_user_can( 'edit_posts' ) )
  716.                                 return;
  717.                                
  718.                         if( ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) || $post->post_status == 'auto-draft' )
  719.                                 return;
  720.                                
  721.                        
  722.                         // Save address
  723.                         update_post_meta( $post->ID, self::PREFIX . 'address', $_POST[ self::PREFIX . 'address' ] );
  724.  
  725.                         if( $_POST[ self::PREFIX . 'address'] )
  726.                                 $coordinates = $this->geocode( $_POST[ self::PREFIX . 'address' ] );
  727.                                
  728.                         if( $coordinates )
  729.                         {
  730.                                 update_post_meta( $post->ID, self::PREFIX . 'latitude', $coordinates[ 'latitude' ] );
  731.                                 update_post_meta( $post->ID, self::PREFIX . 'longitude', $coordinates[ 'longitude' ] );
  732.                         }
  733.                         else
  734.                         {      
  735.                                 update_post_meta( $post->ID, self::PREFIX . 'latitude', '' );
  736.                                 update_post_meta( $post->ID, self::PREFIX . 'longitude', '' );
  737.                         }
  738.                        
  739.                         // Save z-index
  740.                         if( filter_var( $_POST[ self::PREFIX . 'zIndex'], FILTER_VALIDATE_INT ) === FALSE )
  741.                         {
  742.                                 update_post_meta( $post->ID, self::PREFIX . 'zIndex', 0 );
  743.                                 $this->enqueueMessage( __( 'The stacking order has to be an integer.', 'bgmp' ), 'error' );
  744.                         }      
  745.                         else
  746.                                 update_post_meta( $post->ID, self::PREFIX . 'zIndex', $_POST[ self::PREFIX . 'zIndex'] );
  747.                 }
  748.                
  749.                 /**
  750.                  * Geocodes an address
  751.                  * @param string $address
  752.                  * @author Ian Dunn <ian@iandunn.name>
  753.                  * @return mixed
  754.                  */
  755.                 public function geocode( $address )
  756.                 {
  757.                         // @todo - this should be static, or better yet, broken out into an Address class
  758.                        
  759.                         // Bypass geocoding if already have valid coordinates
  760.                         $coordinates = self::validateCoordinates( $address );
  761.                         if( is_array( $coordinates ) )
  762.                                 return $coordinates;
  763.                        
  764.                         // Geocode address and handle errors
  765.                         $geocodeResponse = wp_remote_get( 'http://maps.googleapis.com/maps/api/geocode/json?address='. str_replace( ' ', '+', $address ) .'&sensor=false' );
  766.                         // @todo - esc_url() on address?
  767.                        
  768.                         if( is_wp_error( $geocodeResponse ) )
  769.                         {
  770.                                 $this->enqueueMessage( sprintf(
  771.                                          __( '%s geocode error: %s', 'bgmp' ),
  772.                                          BGMP_NAME,
  773.                                          implode( '<br />', $geocodeResponse->get_error_messages() )
  774.                                  ), 'error' );
  775.                                  
  776.                                 return false;
  777.                         }
  778.                        
  779.                         // Check response code
  780.                         if( !isset( $geocodeResponse[ 'response' ][ 'code' ] ) || !isset( $geocodeResponse[ 'response' ][ 'message' ] ) )
  781.                         {
  782.                                 $this->enqueueMessage( sprintf(
  783.                                         __( '%s geocode error: Response code not present', 'bgmp' ),
  784.                                         BGMP_NAME
  785.                                 ), 'error' );
  786.                                  
  787.                                 return false;
  788.                         }
  789.                         elseif( $geocodeResponse[ 'response' ][ 'code' ] != 200 )
  790.                         {
  791.                                 /*
  792.                                         @todo - strip content inside <style> tag. regex inappropriate, but DOMDocument doesn't exist on dev server, but does on most?. would have to wrap this in an if( class_exists() )...
  793.                                        
  794.                                         $responseHTML = new DOMDocument();
  795.                                         $responseHTML->loadHTML( $geocodeResponse[ 'body' ] );
  796.                                         // nordmalize it b/c doesn't have <body> tag inside?
  797.                                         $this->describe( $responseHTML->saveHTML() );
  798.                                 */
  799.                                
  800.                                 $this->enqueueMessage( sprintf(
  801.                                         __( '<p>%s geocode error: %d %s</p> <p>Response: %s</p>', 'bgmp' ),
  802.                                         BGMP_NAME,
  803.                                         $geocodeResponse[ 'response' ][ 'code' ],
  804.                                         $geocodeResponse[ 'response' ][ 'message' ],
  805.                                         strip_tags( $geocodeResponse[ 'body' ] )
  806.                                 ), 'error' );
  807.                                
  808.                                 return false;
  809.                         }
  810.                        
  811.                         // Decode response and handle errors
  812.                         $coordinates = json_decode( $geocodeResponse['body'] );
  813.                        
  814.                         if( function_exists( 'json_last_error' ) && json_last_error() != JSON_ERROR_NONE )
  815.                         {
  816.                                 // @todo - Once PHP 5.3+ is more widely adopted, remove the function_exists() check here and just bump the PHP requirement to 5.3
  817.                                
  818.                                 $this->enqueueMessage( sprintf( __( '%s geocode error: Response was not formatted in JSON.', 'bgmp' ), BGMP_NAME ), 'error' );
  819.                                 return false;
  820.                         }
  821.                        
  822.                         if( isset( $coordinates->status ) && $coordinates->status == 'REQUEST_DENIED' )
  823.                         {
  824.                                 $this->enqueueMessage( sprintf( __( '%s geocode error: Request Denied.', 'bgmp' ), BGMP_NAME ), 'error' );
  825.                                 return false;
  826.                         }
  827.                                
  828.                         if( !isset( $coordinates->results ) || empty( $coordinates->results ) )
  829.                         {
  830.                                 $this->enqueueMessage( __( "That address couldn't be geocoded, please make sure that it's correct.", 'bgmp' ), "error" );
  831.                                 return false;
  832.                         }
  833.                        
  834.                         return array( 'latitude' => $coordinates->results[ 0 ]->geometry->location->lat, 'longitude' => $coordinates->results[ 0 ]->geometry->location->lng );
  835.                 }
  836.                
  837.                 /**
  838.                  * Checks if a given string represents a valid set of geographic coordinates
  839.                  * Expects latitude/longitude notation, not minutes/seconds
  840.                  *
  841.                  * @author Ian Dunn <ian@iandunn.name>
  842.                  * @param string $coordinates
  843.                  * @return mixed false if any of the tests fails | an array with 'latitude' and 'longitude' keys/value pairs if all of the tests succeed
  844.                  */
  845.                 public static function validateCoordinates( $coordinates )
  846.                 {
  847.                         // @todo - some languages swap the roles of the commas and decimal point. this assumes english.
  848.                        
  849.                         $coordinates = str_replace( ' ', '', $coordinates );
  850.                                
  851.                         if( !$coordinates )
  852.                                 return false;
  853.                                
  854.                         if( substr_count( $coordinates, ',' ) != 1 )
  855.                                 return false;
  856.                        
  857.                         $coordinates = explode( ',', $coordinates );
  858.                         $latitude = $coordinates[ 0 ];
  859.                         $longitude = $coordinates[ 1 ];
  860.                        
  861.                         if( !is_numeric( $latitude ) || $latitude < -90 || $latitude > 90 )
  862.                                 return false;
  863.                                
  864.                         if( !is_numeric( $longitude ) || $longitude < -180 || $longitude > 180 )
  865.                                 return false;
  866.                        
  867.                         return array( 'latitude' => $latitude, 'longitude' => $longitude );
  868.                 }
  869.                
  870.                 /**
  871.                  * Reverse-geocodes a set of coordinates
  872.                  * Google's API has a daily request limit, but this is only called during upgrades from 1.0, so that shouldn't ever be a problem.
  873.                  *
  874.                  * @param string $latitude
  875.                  * @param string $longitude
  876.                  * @author Ian Dunn <ian@iandunn.name>
  877.                  */
  878.                 protected function reverseGeocode( $latitude, $longitude )
  879.                 {
  880.                         $geocodeResponse = wp_remote_get( 'http://maps.googleapis.com/maps/api/geocode/json?latlng='. $latitude .','. $longitude .'&sensor=false' );
  881.                         $address = json_decode( $geocodeResponse['body'] );
  882.                        
  883.                         if( is_wp_error( $geocodeResponse ) || empty( $address->results ) )
  884.                                 return false;
  885.                         else
  886.                                 return $address->results[ 0 ]->formatted_address;
  887.                 }
  888.                        
  889.                 /**
  890.                  * Defines the [bgmp-map] shortcode
  891.                  * @author Ian Dunn <ian@iandunn.name>
  892.                  * @param array $attributes Array of parameters automatically passed in by WordPress
  893.                  * return string The output of the shortcode
  894.                  */
  895.                 public function mapShortcode( $attributes )
  896.                 {
  897.                         if( !wp_script_is( 'googleMapsAPI', 'queue' ) || !wp_script_is( 'bgmp', 'queue' ) || !wp_style_is( self::PREFIX .'style', 'queue' ) )
  898.                         {
  899.                                 $error = sprintf(
  900.                                         __( '<p class="error">%s error: JavaScript and/or CSS files aren\'t loaded. If you\'re using do_shortcode() you need to add a filter to your theme first. See <a href="%s">the FAQ</a> for details.</p>', 'bgmp' ),
  901.                                         BGMP_NAME,
  902.                                         'http://wordpress.org/extend/plugins/basic-google-maps-placemarks/faq/'
  903.                                 );
  904.                                
  905.                                 // @todo maybe change this to use views/message.php
  906.                                
  907.                                 return $error;
  908.                         }
  909.                        
  910.                         if( isset( $attributes[ 'categories' ] ) )
  911.                                 $attributes[ 'categories' ]     = apply_filters( self::PREFIX . 'mapShortcodeCategories', $attributes[ 'categories' ] );                // @todo - deprecated b/c 1.9 output bgmpdata in post; can now just set args in do_shortcode() . also  not consistent w/ shortcode naming scheme and have filter for all arguments now. need a way to notify people
  912.                        
  913.                         $attributes = apply_filters( self::PREFIX . 'map-shortcode-arguments', $attributes );                                   // @todo - deprecated b/c 1.9 output bgmpdata in post...
  914.                         $attributes = $this->cleanMapShortcodeArguments( $attributes );
  915.                        
  916.                         ob_start();
  917.                         require_once( dirname( __FILE__ ) . '/views/shortcode-bgmp-map.php' );
  918.                         $output = ob_get_clean();
  919.                        
  920.                         return $output;
  921.                 }              
  922.                
  923.                 /**
  924.                  * Defines the [bgmp-list] shortcode
  925.                  * @author Ian Dunn <ian@iandunn.name>
  926.                  * @param array $attributes Array of parameters automatically passed in by Wordpress
  927.                  * return string The output of the shortcode
  928.                  */
  929.                 public function listShortcode( $attributes )
  930.                 {
  931.                         $params = array(
  932.                                 'numberposts'   => -1,
  933.                                 'post_type'             => self::POST_TYPE,
  934.                                 'post_status'   => 'publish',
  935.                                 'orderby'               => 'title',
  936.                                 'order'                 => 'ASC'
  937.                         );
  938.                        
  939.                         if( isset( $attributes[ 'categories' ] ) && !empty( $attributes[ 'categories' ] ) )
  940.                         {
  941.                                 // @todo - check each cat to make sure it exists? if not, print error to admin panel.
  942.                                         // non-existant cats don't break the query or anything, so the only purpose for this would be to give feedback to the admin.
  943.                                
  944.                                 $params[ 'tax_query' ] = array(
  945.                                         array(
  946.                                                 'taxonomy'      => self::TAXONOMY,
  947.                                                 'field'         => 'slug',
  948.                                                 'terms'         => explode( ',', $attributes[ 'categories' ] )
  949.                                         )
  950.                                 );
  951.                         }
  952.                        
  953.                         $posts = get_posts( apply_filters( self::PREFIX . 'list-shortcode-params', $params ) );
  954.                         $posts = apply_filters( self::PREFIX . 'list-shortcode-posts', $posts );
  955.                        
  956.                         if( $posts )
  957.                         {
  958.                                 $output = '<ul id="'. self::PREFIX .'list">';
  959.                                
  960.                                 foreach( $posts as $p )
  961.                                 {
  962.                                         // @todo - redo this w/ setup_postdata() and template tags instead of accessing properties directly
  963.                                         // @todo - make this an external view file - can't easily b/c filter on individual marker output. maybe create template loop view for it
  964.                                        
  965.                                         $address = get_post_meta( $p->ID, self::PREFIX . 'address', true );
  966.                                                
  967.                                         $markerHTML = sprintf('
  968.                                                 <li class="'. self::PREFIX .'list-item">
  969.                                                         <h3 class="'. self::PREFIX .'list-placemark-title">%s</h3>
  970.                                                         <div class="'. self::PREFIX .'list-description">%s</div>
  971.                                                         <p class="'. self::PREFIX .'list-link"><a href="%s">%s</a></p>
  972.                                                 </li>',
  973.                                                 $p->post_title,
  974.                                                 wpautop( $p->post_content ),
  975.                                                 'http://google.com/maps?q='. $address,
  976.                                                 $address
  977.                                         );
  978.                                        
  979.                                         $output .= apply_filters( self::PREFIX . 'list-marker-output', $markerHTML, $p->ID );
  980.                                 }
  981.                                
  982.                                 $output .= '</ul>';
  983.                                
  984.                                 return $output;
  985.                         }
  986.                        
  987.                         else
  988.                                 return __( 'No Placemarks found', 'bgmp' );
  989.                 }
  990.                
  991.                 /**
  992.                  * Gets map options
  993.                  * @author Ian Dunn <ian@iandunn.name>
  994.                  * @param array $attributes
  995.                  * @return string JSON-encoded array
  996.                  */
  997.                 public function getMapOptions( $attributes )
  998.                 {
  999.                         $clusterStyles = array(
  1000.                                 'people' => array(
  1001.                                         array(
  1002.                                                 'url'           => plugins_url( 'includes/marker-clusterer/images/people35.png', __FILE__ ),
  1003.                                                 'height'        => 35,
  1004.                                                 'width'         => 35,
  1005.                                                 'anchor'        => array( 16, 0 ),
  1006.                                                 'textColor'     => '#ff00ff',
  1007.                                                 'textSize'      => 10
  1008.                                         ),
  1009.                                        
  1010.                                         array(
  1011.                                                 'url'           => plugins_url( 'includes/marker-clusterer/images/people45.png', __FILE__ ),
  1012.                                                 'height'        => 45,
  1013.                                                 'width'         => 45,
  1014.                                                 'anchor'        => array( 24, 0 ),
  1015.                                                 'textColor'     => '#ff0000',
  1016.                                                 'textSize'      => 11
  1017.                                         ),
  1018.                                        
  1019.                                         array(
  1020.                                                 'url'           => plugins_url( 'includes/marker-clusterer/images/people55.png', __FILE__ ),
  1021.                                                 'height'        => 55,
  1022.                                                 'width'         => 55,
  1023.                                                 'anchor'        => array( 32, 0 ),
  1024.                                                 'textColor'     => '#ffffff',
  1025.                                                 'textSize'      => 12
  1026.                                         )
  1027.                                 ),
  1028.                                
  1029.                                 'conversation' => array(
  1030.                                         array(
  1031.                                                 'url'           => plugins_url( 'includes/marker-clusterer/images/conv30.png', __FILE__ ),
  1032.                                                 'height'        => 27,
  1033.                                                 'width'         => 30,
  1034.                                                 'anchor'        => array( 3, 0 ),
  1035.                                                 'textColor'     => '#ff00ff',
  1036.                                                 'textSize'      => 10
  1037.                                         ),
  1038.                                        
  1039.                                         array(
  1040.                                                 'url'           => plugins_url( 'includes/marker-clusterer/images/conv40.png', __FILE__ ),
  1041.                                                 'height'        => 36,
  1042.                                                 'width'         => 40,
  1043.                                                 'anchor'        => array( 6, 0 ),
  1044.                                                 'textColor'     => '#ff0000',
  1045.                                                 'textSize'      => 11
  1046.                                         ),
  1047.                                        
  1048.                                         array(
  1049.                                                 'url'           => plugins_url( 'includes/marker-clusterer/images/conv50.png', __FILE__ ),
  1050.                                                 'height'        => 50,
  1051.                                                 'width'         => 45,
  1052.                                                 'anchor'        => array( 8, 0 ),
  1053.                                                 'textSize'      => 12
  1054.                                         )
  1055.                                 ),
  1056.                                
  1057.                                 'hearts' => array(
  1058.                                         array(
  1059.                                                 'url'           => plugins_url( 'includes/marker-clusterer/images/heart30.png', __FILE__ ),
  1060.                                                 'height'        => 26,
  1061.                                                 'width'         => 30,
  1062.                                                 'anchor'        => array( 4, 0 ),
  1063.                                                 'textColor'     => '#ff00ff',
  1064.                                                 'textSize'      => 10
  1065.                                         ),
  1066.                                        
  1067.                                         array(
  1068.                                                 'url'           => plugins_url( 'includes/marker-clusterer/images/heart40.png', __FILE__ ),
  1069.                                                 'height'        => 35,
  1070.                                                 'width'         => 40,
  1071.                                                 'anchor'        => array( 8, 0 ),
  1072.                                                 'textColor'     => '#ff0000',
  1073.                                                 'textSize'      => 11
  1074.                                         ),
  1075.                                        
  1076.                                         array(
  1077.                                                 'url'           => plugins_url( 'includes/marker-clusterer/images/heart50.png', __FILE__ ),
  1078.                                                 'height'        => 50,
  1079.                                                 'width'         => 44,
  1080.                                                 'anchor'        => array( 12, 0 ),
  1081.                                                 'textSize'      => 12
  1082.                                         )
  1083.                                 )
  1084.                         );
  1085.                                        
  1086.                         $options = array(
  1087.                                 'mapWidth'                              => $this->settings->mapWidth,                                   // @todo move these into 'map' subarray? but then have to worry about backwards compat
  1088.                                 'mapHeight'                             => $this->settings->mapHeight,
  1089.                                 'latitude'                              => $this->settings->mapLatitude,
  1090.                                 'longitude'                             => $this->settings->mapLongitude,
  1091.                                 'zoom'                                  => $this->settings->mapZoom,
  1092.                                 'type'                                  => $this->settings->mapType,
  1093.                                 'typeControl'                   => $this->settings->mapTypeControl,
  1094.                                 'navigationControl'             => $this->settings->mapNavigationControl,
  1095.                                 'infoWindowMaxWidth'    => $this->settings->mapInfoWindowMaxWidth,
  1096.                                 'streetViewControl'             => true,
  1097.                                
  1098.                                 'clustering'                    => array(
  1099.                                         'enabled'                       => $this->settings->markerClustering,
  1100.                                         'maxZoom'                       => $this->settings->clusterMaxZoom,
  1101.                                         'gridSize'                      => $this->settings->clusterGridSize,
  1102.                                         'style'                         => $this->settings->clusterStyle,
  1103.                                         'styles'                        => $clusterStyles
  1104.                                 )
  1105.                         );
  1106.                        
  1107.                         $options = shortcode_atts( $options, $attributes );
  1108.                        
  1109.                         return apply_filters( self::PREFIX . 'map-options', $options );
  1110.                 }
  1111.                
  1112.                 /**
  1113.                  * Gets the published placemarks from the database, formats and outputs them.
  1114.                  * @author Ian Dunn <ian@iandunn.name>
  1115.                  * @param array $attributes
  1116.                  * @return string JSON-encoded array
  1117.                  */
  1118.                 public function getMapPlacemarks( $attributes )
  1119.                 {
  1120.                         global $post;
  1121.                         $placemarks = array();
  1122.                        
  1123.                         $query = array(
  1124.                                 'numberposts'   => -1,
  1125.                                 'post_type'             => self::POST_TYPE,
  1126.                                 'post_status'   => 'publish'
  1127.                         );
  1128.                        
  1129.                         if( isset( $attributes[ 'categories' ] ) && !empty( $attributes[ 'categories' ] ) )
  1130.                         {
  1131.                                 $query[ 'tax_query' ] = array(
  1132.                                         array(
  1133.                                                 'taxonomy'      => self::TAXONOMY,
  1134.                                                 'field'         => 'slug',
  1135.                                                 'terms'         => $attributes[ 'categories' ]
  1136.                                         )
  1137.                                 );
  1138.                         }
  1139.                        
  1140.                         $query = apply_filters( self::PREFIX . 'get-placemarks-query', $query );                // @todo - filter name deprecated
  1141.                        
  1142.                         $publishedPlacemarks = get_posts( apply_filters( self::PREFIX . 'get-map-placemarks-query', $query ) );
  1143.                        
  1144.                         if( $publishedPlacemarks )
  1145.                         {
  1146.                                 foreach( $publishedPlacemarks as $post )
  1147.                                 {
  1148.                                         setup_postdata( $post );
  1149.                                         $postID = get_the_ID();
  1150.                                        
  1151.                                         $categories = get_the_terms( $postID, self::TAXONOMY );
  1152.                                         if( !is_array( $categories ) )
  1153.                                                 $categories = array();
  1154.                                                
  1155.                                        
  1156.                                         $icon = wp_get_attachment_image_src( get_post_thumbnail_id( $postID ) );
  1157.                                         $defaultIcon = apply_filters( self::PREFIX .'default-icon', plugins_url( 'images/default-marker.png', __FILE__ ), $postID );                                   
  1158.                                         $address = get_post_meta($post->ID, self::PREFIX . 'address', true);
  1159.  
  1160.                                         $placemarks[] = array(
  1161.                                                 'id'                    => $postID,
  1162.                                                 'title'                 => get_the_title(),
  1163.                                                 'latitude'              => get_post_meta( $postID, self::PREFIX . 'latitude', true ),
  1164.                                                 'longitude'             => get_post_meta( $postID, self::PREFIX . 'longitude', true ),
  1165.                                                 'address'               => $address,
  1166.                                                 'details'               => wpautop( get_the_content() ),
  1167.                                                 'categories'    => $categories,
  1168.                                                 'icon'                  => is_array( $icon ) ? $icon[0] : $defaultIcon,
  1169.                                                 'zIndex'                => get_post_meta( $postID, self::PREFIX . 'zIndex', true )
  1170.                                         );
  1171.                                 }
  1172.                                 wp_reset_postdata();
  1173.                         }
  1174.                        
  1175.                         $placemarks = apply_filters( self::PREFIX . 'get-placemarks-return', $placemarks );     // @todo - filter name deprecated
  1176.                         return apply_filters( self::PREFIX . 'get-map-placemarks-return', $placemarks );
  1177.                 }
  1178.                
  1179.                 /**
  1180.                  * Displays updates and errors
  1181.                  * NOTE: In order to allow HTML in the output, any unsafe variables passed to enqueueMessage() need to be escaped before they're passed in, instead of escaping here.
  1182.                  *
  1183.                  * @author Ian Dunn <ian@iandunn.name>
  1184.                  */
  1185.                 public function printMessages()
  1186.                 {
  1187.                         if( did_action( 'admin_notices' ) !== 1 )
  1188.                                 return;
  1189.                        
  1190.                         foreach( array( 'updates', 'errors' ) as $type )
  1191.                         {
  1192.                                 if( $this->options[ $type ] && ( self::DEBUG_MODE || $this->userMessageCount[ $type ] ) )
  1193.                                 {
  1194.                                         $message = '';
  1195.                                         $class = $type == 'updates' ? 'updated' : 'error';
  1196.                                        
  1197.                                         foreach( $this->options[ $type ] as $messageData )
  1198.                                                 if( $messageData[ 'mode' ] == 'user' || self::DEBUG_MODE )
  1199.                                                         $message .= '<p>'. $messageData[ 'message' ] .'</p>';
  1200.                                        
  1201.                                         require( dirname(__FILE__) . '/views/message.php' );
  1202.                                        
  1203.                                         $this->options[ $type ] = array();
  1204.                                         $this->updatedOptions = true;
  1205.                                         $this->userMessageCount[ $type ] = 0;
  1206.                                 }
  1207.                         }
  1208.                 }
  1209.                
  1210.                 /**
  1211.                  * Queues up a message to be displayed to the user
  1212.                  * NOTE: In order to allow HTML in the output, any unsafe variables in $message need to be escaped before they're passed in, instead of escaping here.
  1213.                  *               
  1214.                  * @author Ian Dunn <ian@iandunn.name>
  1215.                  * @param string $message The text to show the user
  1216.                  * @param string $type 'update' for a success or notification message, or 'error' for an error message
  1217.                  * @param string $mode 'user' if it's intended for the user, or 'debug' if it's intended for the developer
  1218.                  */
  1219.                 protected function enqueueMessage( $message, $type = 'update', $mode = 'user' )
  1220.                 {
  1221.                         if( !is_string( $message ) || empty( $message ) )
  1222.                                 return false;
  1223.                                
  1224.                         if( !isset( $this->options[ $type .'s' ] ) )
  1225.                                 return false;
  1226.                                
  1227.                         array_push( $this->options[ $type .'s' ], array(
  1228.                                 'message'       => $message,
  1229.                                 'type'          => $type,
  1230.                                 'mode'          => $mode
  1231.                         ) );
  1232.                        
  1233.                         if( $mode == 'user' )
  1234.                                 $this->userMessageCount[ $type . 's' ]++;
  1235.                        
  1236.                         $this->updatedOptions = true;
  1237.                        
  1238.                         return true;
  1239.                 }
  1240.                
  1241.                 /**
  1242.                  * Prints the output in various ways for debugging.
  1243.                  * @author Ian Dunn <ian@iandunn.name>
  1244.                  * @param mixed $data
  1245.                  * @param string $output 'message' will be sent to an admin notice; 'die' will be output inside wp_die(); 'transient' will create a transient in the database; 'return' will be returned;
  1246.                  * @param string $message Optionally message to output before description
  1247.                  * @return mixed
  1248.                  */
  1249.                 protected function describe( $data, $output = 'die', $message = '' )
  1250.                 {
  1251.                         $type = gettype( $data );
  1252.  
  1253.                         // Build description
  1254.                         switch( $type )
  1255.                         {
  1256.                                 case 'array':
  1257.                                 case 'object':
  1258.                                         $length = count( $data );
  1259.                                         $data = print_r( $data, true );
  1260.                                 break;
  1261.                                
  1262.                                 case 'string';
  1263.                                         $length = strlen( $data );
  1264.                                 break;
  1265.                                
  1266.                                 default:
  1267.                                         $length = count( $data );
  1268.                                        
  1269.                                         ob_start();
  1270.                                         var_dump( $data );
  1271.                                         $data = ob_get_contents();
  1272.                                         ob_end_clean();
  1273.                                        
  1274.                                         $data = print_r( $data, true );
  1275.                                 break;
  1276.                         }
  1277.                        
  1278.                         $description = sprintf('
  1279.                                 <p>
  1280.                                         %s
  1281.                                         %s: %s<br />
  1282.                                         %s: %s<br />
  1283.                                         %s: <br /><blockquote><pre>%s</pre></blockquote>
  1284.                                 </p>',
  1285.                                 ( $message ? 'Message: '. $message .'<br />' : '' ),
  1286.                                 __( 'Type', 'bgmp' ),
  1287.                                 $type,
  1288.                                 __( 'Length', 'bgmp' ),
  1289.                                 $length,
  1290.                                 __( 'Content', 'bgmp' ),
  1291.                                 htmlspecialchars( $data )
  1292.                         );
  1293.                        
  1294.                         // Output description
  1295.                         switch( $output )
  1296.                         {
  1297.                                 case 'notice':
  1298.                                         $this->enqueueMessage( $description, 'error' );
  1299.                                 break;
  1300.                                
  1301.                                 case 'die':
  1302.                                         wp_die( $description );
  1303.                                 break;
  1304.                                
  1305.                                 case 'output':
  1306.                                         return $description;
  1307.                                 break;
  1308.                                
  1309.                                 case 'transient':
  1310.                                         $uniqueKey = $message ? str_replace( array( ' ', '-', '/', '\\', '.' ), '_', $message ) : mt_rand();    // removes characters that are invalid in MySQL column names
  1311.                                         set_transient( self::PREFIX . 'describe_' . $uniqueKey, $description, 60 * 5 );
  1312.                                 break;
  1313.                                
  1314.                                 case 'echo':
  1315.                                 default:
  1316.                                         echo $description;              // @todo - want to esc_html on message, but not entire description. can't do to $message above because don't want to escape for other switch cases
  1317.                                 break;
  1318.                         }
  1319.                 }
  1320.                
  1321.                 /**
  1322.                  * Writes options to the database
  1323.                  * @author Ian Dunn <ian@iandunn.name>
  1324.                  */
  1325.                 public function shutdown()
  1326.                 {
  1327.                         if( did_action( 'shutdown' ) !== 1 )
  1328.                                 return;
  1329.                        
  1330.                         if( $this->updatedOptions )
  1331.                                 update_option( self::PREFIX . 'options', $this->options );
  1332.                 }
  1333.         } // end BasicGoogleMapsPlacemarks
  1334. }
  1335.  
  1336. ?>