Source for file geo-mashup.php
Documentation is available at geo-mashup.php 
Plugin URI: http://code.google.com/p/wordpress-geo-mashup/   
Description: Save location for posts and pages, or even users and comments. Display these locations on Google maps. Make WordPress into your GeoCMS.  
Author URI: http://www.cyberhobo.net/  
Minimum WordPress Version Required: 3.0  
/*  Copyright 2011  Dylan Kuhn  (email : cyberhobo@cyberhobo.net)  
    This program is free software; you can redistribute it and/or modify  
    it under the terms of the GNU General Public License, version 2 or later, as  
    published by the Free Software Foundation.  
    This program is distributed in the hope that it will be useful,  
    but WITHOUT ANY WARRANTY; without even the implied warranty of  
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  
    GNU General Public License for more details.  
    You should have received a copy of the GNU General Public License  
    along with this program; if not, write to the Free Software  
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA  
 * The main Geo Mashup plugin file loaded by WordPress.  
 * The Geo Mashup static class.  
 * Used primarily for namespace, with methods called using the scope operator,  
 * like echo GeoMashup::map();  
     * Whether to add the click-to-load map script.  
    private static $add_loader_script =  false;  
     * Initializations that can be done before init().  
    public static function load() {  
        load_plugin_textdomain( 'GeoMashup', '', path_join( GEO_MASHUP_DIRECTORY, 'lang' ) );  
     *    Test to see if the current request is for the Geo Mashup options page.  
     * @return bool Whether this is a an options page request.  
        // We may need this before $pagenow is set, but maybe this method won't always work?  
        return ( is_admin() and isset ($_GET['page']) and GEO_MASHUP_PLUGIN_NAME ===  $_GET['page'] );  
     * init {@link http://codex.wordpress.org/Plugin_API/Action_Reference#Advanced_Actions action},  
    public static function init() {  
     * Load relevant dependencies.  
    private static function load_dependencies() {  
        include_once( GEO_MASHUP_DIR_PATH .  '/geo-mashup-options.php' );  
        include_once( GEO_MASHUP_DIR_PATH .  '/geo-mashup-db.php' );  
        include_once( GEO_MASHUP_DIR_PATH .  '/geo-mashup-ui-managers.php' );  
            include_once( GEO_MASHUP_DIR_PATH .  '/shortcodes.php');  
    private static function load_hooks() {  
        add_action( 'init', array( __CLASS__ , 'init' ) );  
        add_action( 'plugins_loaded', array( __CLASS__ , 'dependent_init' ), - 1 );  
        add_action( 'wp_ajax_geo_mashup_query', array( __CLASS__ , 'geo_query') );  
        add_action( 'wp_ajax_nopriv_geo_mashup_query', array( __CLASS__ , 'geo_query') );  
        add_action( 'wp_ajax_geo_mashup_kml_attachments', array( __CLASS__ , 'ajax_kml_attachments') );  
        add_action( 'wp_ajax_nopriv_geo_mashup_kml_attachments', array( __CLASS__ , 'ajax_kml_attachments') );  
        add_action( 'wp_ajax_geo_mashup_suggest_custom_keys', array( 'GeoMashupDB', 'post_meta_key_suggest' ) );  
            register_activation_hook( __FILE__ , array( __CLASS__ , 'activation_hook' ) );  
            // To add Geo Mashup settings page  
            add_action('admin_menu', array(__CLASS__ , 'admin_menu'));  
            // To make important announcements  
            add_action( 'admin_notices', array( __CLASS__ , 'admin_notices' ) );  
            // To add plugin listing links  
            add_filter( 'plugin_action_links', array( __CLASS__ , 'plugin_action_links' ), 10, 2 );  
            add_filter( 'plugin_row_meta', array( __CLASS__ , 'plugin_row_meta' ), 10, 2 );  
            // This is a non-admin request  
            if ($geo_mashup_options->get('overall','add_category_links') ==  'true') {  
                // To add map links to a category list - flaky, requires non-empty category description  
                add_filter('list_cats', array(__CLASS__ , 'list_cats'), 10, 2);  
            // To output location meta tags in the page head  
            add_action('wp_head', array(__CLASS__ , 'wp_head'));  
            // To add footer output (like scripts)  
            add_action( 'wp_footer', array( __CLASS__ , 'wp_footer' ) );  
            // To allow shortcodes in the text widget  
            if ( ! has_filter( 'widget_text', 'do_shortcode' ) ) {  
                add_filter( 'widget_text', 'do_shortcode', 11 );  
            // To add the GeoRSS namespace to feeds (not available for RSS 0.92)  
            add_action('rss2_ns', array(__CLASS__ , 'rss_ns'));  
            add_action('atom_ns', array(__CLASS__ , 'rss_ns'));  
            // To add GeoRSS location to feeds  
            add_action('rss2_item', array(__CLASS__ , 'rss_item'));  
            add_action('atom_entry', array(__CLASS__ , 'rss_item'));  
            // To add custom renderings  
            add_filter( 'query_vars', array( __CLASS__ , 'query_vars' ) );  
            add_action( 'template_redirect', array( __CLASS__ , 'template_redirect' ) );  
     * Define Geo Mashup constants.  
    private static function load_constants() {  
        define('GEO_MASHUP_PLUGIN_NAME', plugin_basename(__FILE__ ));  
        define('GEO_MASHUP_DIRECTORY', dirname( GEO_MASHUP_PLUGIN_NAME ) );  
        define('GEO_MASHUP_URL_PATH', trim( plugin_dir_url( __FILE__  ), '/' ) );  
        define('GEO_MASHUP_MAX_ZOOM', 20);  
        // Make numeric versions: -.02 for alpha, -.01 for beta  
        define('GEO_MASHUP_VERSION', '1.3.99.5');  
        define('GEO_MASHUP_DB_VERSION', '1.3');  
    private static function load_scripts() {  
        if ( self::is_options_page() )  
            wp_enqueue_script( 'jquery-ui-tabs' );  
    private static function load_styles() {  
        if ( self::is_options_page() ) {  
            self::register_style( 'jquery-smoothness', 'css/jquery-ui.1.7.smoothness.css', array(), GEO_MASHUP_VERSION, 'screen' );  
            wp_enqueue_style( 'jquery-smoothness' );  
            wp_enqueue_script( 'suggest' );  
     * WordPress hook to perform activation tasks.  
        if ( '' ==  $geo_mashup_options->get( 'overall', 'version' ) and '' !=  $geo_mashup_options->get( 'overall', 'google_key' ) ) {  
            // Upgrading from a pre-1.4 version - don't set the default provider to Google v3  
            $geo_mashup_options->set_valid_options(  
                        'version' =>  GEO_MASHUP_VERSION  
            $geo_mashup_options->save();  
     * WordPress action to supply an init action for plugins that would like to use Geo Mashup APIs.  
     * @uses do_action() geo_mashup_init Fired when Geo Mashup is loaded and ready.  
        do_action( 'geo_mashup_init' );  
     * Register the Geo Mashup script appropriate for the request.  
     * @param string $handle Global tag for the script.  
     * @param string $src Path to the script from the root directory of Geo Mashup.  
     * @param array $deps Array of dependency handles.  
     * @param string $ver Script version.  
     * @param bool $in_footer Whether the script can be loaded in the footer.  
    public static function register_script( $handle, $src, $deps =  array(), $ver =  false, $in_footer =  false ) {  
        // Use the .dev version if SCRIPT_DEBUG is set or there is no minified version  
        if ( ( defined( 'SCRIPT_DEBUG' ) and SCRIPT_DEBUG ) or !is_readable( path_join( GEO_MASHUP_DIR_PATH, $src ) ) )  
        wp_register_script( $handle, plugins_url( $src, __FILE__  ), $deps, $ver, $in_footer );  
     * Register the Geo Mashup style appropriate for the request.  
     * @param string $handle Global tag for the style.  
     * @param string $src Path to the stylesheet from the root directory of Geo Mashup.  
     * @param array $deps Array of dependency handles.  
     * @param string $ver Script version.  
     * @param bool $media Stylesheet media target.  
    public static function register_style( $handle, $src, $deps =  array(), $ver =  false, $media =  'all' ) {  
        // Use the .dev version if SCRIPT_DEBUG is set or there is no minified version  
        if ( ( defined( 'SCRIPT_DEBUG' ) and SCRIPT_DEBUG ) or !is_readable( path_join( GEO_MASHUP_DIR_PATH, $src ) ) )  
        wp_register_style( $handle, plugins_url( $src, __FILE__  ), $deps, $ver, $media );  
     * WordPress action to add things like scripts to the footer.  
        if ( self::$add_loader_script ) {  
            self::register_script( 'geo-mashup-loader', 'js/loader.js', array(), GEO_MASHUP_VERSION, true );  
            wp_print_scripts( 'geo-mashup-loader' );  
     * WordPress filter to add Geo Mashup query variables.  
    public static function query_vars( $public_query_vars ) {  
        $public_query_vars[] =  'geo_mashup_content';  
        return $public_query_vars;  
     *    Locate a Geo Mashup template.  
     * Geo Mashup looks for templates given a certain base name. Given a base  
     * name of 'info-window', it will return the first of:  
     *     'geo-mashup-info-window.php' in the active theme directory  
     *     'info-window.php' in the geo-mashup-custom plugin directory  
     *     'default-templates/info-window.php' in the geo-mashup plugin directory  
     * @param string $template_base The base name of the template.  
     * @return string The file path of the template found.  
        if ( empty( $template ) and isset ( $geo_mashup_custom ) and $geo_mashup_custom->file_url( $template_base .  '.php' ) ) {  
            $template =  path_join( $geo_mashup_custom->dir_path, $template_base .  '.php' );  
        if ( empty( $template ) or !is_readable( $template ) ) {  
            $template =  path_join( GEO_MASHUP_DIR_PATH, "default-templates/$template_base.php" );  
        if ( empty( $template ) or !is_readable( $template ) ) {  
            // If all else fails, just use the default info window template  
            $template =  path_join( GEO_MASHUP_DIR_PATH, 'default-templates/info-window.php' );  
     * WordPress action to deliver templated Geo Mashup content.  
        $geo_mashup_content =  get_query_var( 'geo_mashup_content' );  
        if ( ! empty( $geo_mashup_content ) ) {  
            // The parameter's purpose is to get us here, we can remove it now  
            unset ( $_GET['geo_mashup_content'] ); 
            // Call the function corresponding to the content request  
            // This provides some security, as only implemented methods will be executed  
            $method =  str_replace( '-', '_', $geo_mashup_content );  
     * Process an AJAX geo query.  
        require_once( 'geo-query.php' );  
     * Process an iframe map request.  
    private static function render_map() {  
        require_once( 'render-map.php' );  
     * WordPress action to perform an ajax edit operation and echo results.  
        check_ajax_referer( 'geo-mashup-edit', 'geo_mashup_nonce' );  
        unset ( $_GET['_wpnonce'] ); 
        $status =  array( 'request' =>  'ajax-edit', 'code' =>  200 );  
        if ( isset ( $_POST['geo_mashup_object_id'] ) ) {  
            $status['object_id'] =  $_POST['geo_mashup_object_id'];  
            $status['message'] =  __( 'No object id posted.', 'GeoMashup' );  
            $status['object_id'] =  '?';  
        /** @todo add an option for a user capability check here? */  
        if ( 200 ==  $status['code'] and ! empty( $_POST['geo_mashup_ui_manager'] ) ) {  
            $result =  $ui_manager->save_posted_object_location( $status['object_id'] );  
            if ( is_wp_error( $result ) ) {  
                $status['message'] =  $result->get_error_message();  
        if ( 200 ==  $status['code'] ) {  
            if ( ! empty( $_REQUEST['geo_mashup_update_location'] ) ) {  
                $status['message'] =  __( 'Location updated.', 'GeoMashup' );  
            } else if ( ! empty( $_REQUEST['geo_mashup_delete_location'] ) ) {  
                $status['message'] =  __( 'Location deleted.', 'GeoMashup' );  
            } else if ( ! empty( $_REQUEST['geo_mashup_add_location'] ) ) {  
                $status['message'] =  __( 'Location added.', 'GeoMashup' );  
     * Toggle limiting of query_posts to located posts only,  
     * with Geo Mashup query extensions.  
     * When enabled, only posts with locations will be returned from  
     * WordPress query_posts() and related functions. Also adds Geo Mashup  
     * public query variables.  
     * Caution - what if a future Geo Mashup incorporates multiple locations per object?  
     * @param bool $yes_or_no Whether to activate the join or not.  
     * Helper to turn a string of key-value pairs into an associative array.  
     * @param string $glue1 Pair separator.  
     * @param string $glue2 Key/value separator.  
     * @param string $str String to explode.  
     * @return array The associative array.  
        foreach($array2 as  $val) {  
     * Helper to turn an associative array into a string of key-value pairs.  
     * @param string $inner_glue Key/value separator.  
     * @param string $outer_glue Pair separator.  
     * @param array $array Array to implode.  
     * @param mixed $skip_empty Whether to include empty values in output.  
     * @param mixed $urlencoded Whetern to URL encode the output.  
     * @return string The imploded string.  
    public static function implode_assoc($inner_glue, $outer_glue, $array, $skip_empty= false, $urlencoded= false) {  
        foreach($array as $key=> $item) {  
            if (!$skip_empty || isset ($item)) {  
                    $output[] =  preg_replace('/\s/', ' ', $key. $inner_glue. $item);  
        return implode($outer_glue, $output);  
     * Guess the best language code for the current context.  
     * Takes some plugins and common practices into account.  
     * @return string Language code.  
        if ( isset ( $_GET['lang'] ) ) {  
            // A language override technique is to use this querystring parameter  
            $language_code =  $_GET['lang'];  
            // qTranslate integration  
            $language_code =  qtrans_getLanguage();  
        } else if ( defined( 'ICL_LANGUAGE_CODE' ) ) {  
            $language_code =  ICL_LANGUAGE_CODE;  
            $language_code =  get_locale();  
     * Get an array of URLs of KML or KMZ attachments for a post.  
     * @return array Array of URL strings.  
        if ( empty( $post_id ) ) {  
            'post_type' =>  'attachment',  
            'post_mime_type' =>  array(  
                'application/vnd.google-earth.kml+xml',  
                'application/vnd.google-earth.kmz',  
                'application/octet-stream'  
            'post_parent' =>  $post_id  
        $attachments =  get_posts($args);  
            foreach ($attachments as $attachment) {  
                $url =  wp_get_attachment_url( $attachment->ID );   
                // Backwards compatibility: include KML attachments with the incorrect octet-stream mime type  
                if ( 'application/octet-stream' !=  $attachment->post_mime_type or 'kml' ==  substr( $url, - 3 ) ) {  
     * Echo a JSONP array of URLs of KML or KMZ attachments for posts.  
        if ( !empty( $_REQUEST['post_ids'] ) ) {  
            foreach( $post_ids as $post_id ) {  
                $urls =  array_merge( $urls, self::get_kml_attachment_urls( $post_id ) );  
        if ( isset ( $_REQUEST['callback'] ) )  
            $json =  $_REQUEST['callback'] .  '(' .  $json .  ')';  
 * WordPress action to add relevant geo meta tags to the document head.  
 * wp_head {@link http://codex.wordpress.org/Plugin_API/Action_Reference#TemplateActions action},  
            $title =  esc_html(convert_chars(strip_tags(get_bloginfo('name')). " - ". $wp_query->post->post_title));  
            echo  '<meta name="ICBM" content="' .  esc_attr( $loc->lat .  ', ' .  $loc->lng ) .  '" />' .  "\n"; 
            echo  '<meta name="DC.title" content="' .  esc_attr( $title ) .  '" />' .  "\n"; 
            echo  '<meta name="geo.position" content="' .   esc_attr( $loc->lat .  ';' .  $loc->lng ) .  '" />' .  "\n"; 
 * Query object locations and return JSON.  
 * Offers customization per object location via a filter, geo_mashup_locations_json_object.  
     * @uses GeoMashupDB::get_object_locations()  
     * @uses apply_filters() the_title Filter post titles.  
     * @uses apply_filters() geo_mashup_locations_json_object Filter each location associative array before conversion to JSON.  
     * @param string|array$query_args Query variables for GeoMashupDB::get_object_locations().  
     * @param string $format (optional) 'JSON' (default) or ARRAY_A  
     * @return string Queried object locations JSON ( { "object" : [...] } ).  
        $default_args =  array( 'object_name' =>  'post' );  
        $query_args =  wp_parse_args( $query_args, $default_args );  
            foreach ($objects as $object) {  
                if ( 'post' ==  $query_args['object_name'] ) {  
                    $object->label =  sanitize_text_field( apply_filters( 'the_title', $object->label, $object->object_id ) );  
                    // Only know about post categories now, but could abstract to objects  
                    if ( !defined( 'GEO_MASHUP_DISABLE_CATEGORIES' ) )  
                        $category_ids =  wp_get_object_terms( $object->object_id, 'category', array( 'fields' =>  'ids' ) );  
                    $author =  get_userdata( $object->post_author );  
                    if ( empty( $author ) ) {  
                        $author_name =  $author->display_name;  
                    'object_name' =>  $query_args['object_name'],  
                    'object_id' =>  $object->object_id,  
                    // We should be able to use real UTF-8 characters in titles  
                    // Helps with the spelling-out of entities in tooltips  
                    'author_name' =>  $author_name,  
                    'categories' =>  $category_ids  
                // Allow companion plugins to add data  
                $json_object =  apply_filters( 'geo_mashup_locations_json_object', $json_object, $object );  
                $json_objects[] =  $json_object;  
        if ( ARRAY_A ==  $format )   
            return array( 'objects' =>  $json_objects );  
            return json_encode( array( 'objects' =>  $json_objects ) );  
     * Convert depricated attribute names.  
     * @param array $atts Attributes to modify.  
    private static function convert_map_attributes( &$atts ) {  
        $attribute_conversions =  array(   
            'auto_open_info_window' =>  'auto_info_open',  
            'open_post_id' =>  'open_object_id'  
        foreach ( $attribute_conversions as $old_key =>  $new_key ) {  
            if ( isset ( $atts[$old_key] ) ) {  
                if ( ! isset ( $atts[$new_key] ) ) {  
                    $atts[$new_key] =  $atts[$old_key];  
                unset ( $atts[$old_key] ); 
     * Build the data for a javascript map.  
     * Parameters are used both to retrieve data and as options to  
     * eventually pass to the javascript.  
     * @uses GeoMashup::get_locations_json()  
     * @global array $geo_mashup_options   
     * @global object $geo_mashup_custom   
     * @param array $query Query parameters  
     * @return array Map data ready to be rendered.  
            'map_api' =>  $geo_mashup_options->get( 'overall', 'map_api' )  
        $query =  wp_parse_args( $query, $defaults );  
        $object_id = isset ( $query['object_id'] ) ?  $query['object_id'] :  0;  
        unset ( $query['object_id'] ); 
        $map_data =  $query +  array(  
            'ajaxurl' =>  admin_url( 'admin-ajax.php' ),  
            'siteurl' =>  home_url(), // qTranslate doesn't work with get_option( 'home' )  
            'url_path' =>  GEO_MASHUP_URL_PATH,  
            'template_url_path' =>  get_stylesheet_directory_uri()  
        if ( isset ( $geo_mashup_custom ) ) {  
            $map_data['custom_url_path'] =  $geo_mashup_custom->url_path;  
        $map_content =  ( isset ( $query['map_content'] ) ) ?  $query['map_content'] :  null;  
        $object_name =  ( isset ( $query['object_name'] ) ) ?  $query['object_name'] :  'post';  
        if ( $map_content ==  'single') {  
            $options =  $geo_mashup_options->get( 'single_map' );  
            if ( !empty( $location ) ) {  
                $map_data['object_data'] =  array( 'objects' =>  array( $location ) );  
                $map_data['center_lat'] =  $location['lat'];  
                $map_data['center_lng'] =  $location['lng'];  
            if ( 'post' ==  $object_name ) {  
                $kml_urls =  self::get_kml_attachment_urls( $object_id );  
                if (count($kml_urls)> 0) {  
                    $map_data['load_kml'] =  array_pop( $kml_urls );  
            // Map content is not single  
            $map_data['context_object_id'] =  $object_id;  
            if ( $map_content ==  'contextual' ) {  
                $options =  $geo_mashup_options->get( 'context_map' );  
                // If desired we could make these real options  
                $options['auto_info_open'] =  'false';  
                $options =  $geo_mashup_options->get( 'global_map' );  
                // Category options done during render  
                unset ( $options['category_color'] ); 
                unset ( $options['category_line_zoom'] ); 
                if ( empty( $query['show_future'] ) )  
                    $query['show_future'] =  $options['show_future'];  
                    $options['map_content'] =  'global';  
            if ( isset ( $options['add_google_bar'] ) and 'true' ==  $options['add_google_bar'] ) {  
                $options['adsense_code'] =  $geo_mashup_options->get( 'overall', 'adsense_code' );  
            // We have a lot map control parameters that don't effect the locations query,  
            // but only the relevant ones are used  
            $map_data['object_data'] =  self::get_locations_json( $query, ARRAY_A );  
            // Incorporate parameters from the query and options  
     * Returns HTML for a Google map. Must use with echo in a template: echo GeoMashup::map();.  
     * @link http://code.google.com/p/wordpress-geo-mashup/wiki/TagReference#Map tag parameter documentation  
     * @uses $_SERVER['QUERY_STRING'] The first global map on a page uses query string parameters like tag parameters.  
     * @staticvar $map_number Used to index maps per request.  
     * @param string|array$atts Template tag parameters.  
     * @return string The HTML for the requested map.  
    public static function map( $atts =  null ) {  
        $atts =  wp_parse_args( $atts );  
        $static = (bool) ( !empty( $atts['static'] ) and 'true' ==  $atts['static'] );  
        unset ( $atts['static'] ); 
        if ( empty( $atts['lang'] ) ) {  
                // qTranslate integration  
                $atts['lang'] =  qtrans_getLanguage();  
            } else if ( defined( 'ICL_LANGUAGE_CODE' ) ) {  
                $atts['lang'] =  ICL_LANGUAGE_CODE;  
        $click_to_load_options =  array( 'click_to_load', 'click_to_load_text' );  
        self::convert_map_attributes( $atts );  
        // Default query is for posts  
        $object_name =  ( isset ( $atts['object_name'] ) ) ?  $atts['object_name'] :  'post';  
        // Find the ID and location of the container object if it exists  
        if ( 'post' ==  $object_name and $wp_query->in_the_loop ) {  
            $context_object_id =  $wp_query->post->ID;  
        } else if ( 'comment' ==  $object_name and $in_comment_loop ) {  
            $context_object_id =  get_comment_ID();  
        } else if ( 'user' ==  $object_name and $wp_query->post ) {  
            $context_object_id =  $wp_query->post->post_author;  
        if ( empty( $atts['object_id'] ) and ! empty( $context_object_id ) ) {  
            $atts['object_id'] =  $context_object_id;  
        // Map content type isn't required, so resolve it  
        $map_content = isset ( $atts['map_content'] ) ?  $atts['map_content'] :  null;  
        if ( empty ( $map_content ) ) {  
            if ( empty( $context_object_id ) ) {  
                $map_content =  'contextual';  
            } else if ( empty( $context_location ) ) {  
                // Not located, go global  
                $atts['map_content'] =  'contextual';  
                $atts +=  $geo_mashup_options->get( 'context_map', $click_to_load_options );  
                if ( 'comment' ==  $object_name ) {  
                    $context_objects =  $wp_query->comments;  
                    $context_objects =  $wp_query->posts;  
                    return '<!-- ' .  __( 'Geo Mashup found no objects to map in this context', 'GeoMashup' ) .  '-->';  
                foreach ( $context_objects as $context_object ) {  
                    if ( 'post' ==  $object_name ) {  
                        $object_ids[] =  $context_object->ID;  
                    } else if ( 'user' ==  $object_name ) {  
                        $object_ids[] =  $context_object->post_author;  
                    } else if ( 'comment' ==  $object_name ) {  
                        $object_ids[] =  $context_object->comment_ID;  
                $atts['object_ids'] =  implode( ',', $object_ids );  
                $atts['map_content'] =  'single';  
                $atts +=  $geo_mashup_options->get( 'single_map', $click_to_load_options );  
                if ( empty( $atts['object_id'] ) ) {  
                    return '<!-- ' .  __( 'Geo Mashup found no current object to map', 'GeoMashup' ) .  '-->';  
                if ( empty( $single_location ) ) {  
                    return '<!-- ' .  __( 'Geo Mashup omitted a map for an object with no location', 'GeoMashup' ) .  '-->';  
                if ( isset ( $_GET['template'] ) and 'full-post' ==  $_GET['template'] ) {  
                    // Global maps tags in response to a full-post query can infinitely nest, prevent this  
                    return '<!-- ' .  __( 'Geo Mashup map omitted to avoid nesting maps', 'GeoMashup' ) .  '-->';  
                $atts['map_content'] =  'global';  
                if ( isset ($_SERVER['QUERY_STRING']) and 1 ==  $map_number ) {  
                    // The first global map on a page will make use of query string arguments  
                    $atts =  wp_parse_args( $_SERVER['QUERY_STRING'], $atts );  
                $atts +=  $geo_mashup_options->get( 'global_map', $click_to_load_options );  
                // Don't query more than max_posts  
                $max_posts =  $geo_mashup_options->get( 'global', 'max_posts' );  
                if ( empty( $atts['limit'] ) and !empty( $max_posts ) )  
                    $atts['limit'] =  $max_posts;  
                return '<div class="gm-map"><p>Unrecognized value for map_content: "'. $map_content. '".</p></div>';  
        $click_to_load =  $atts['click_to_load'];  
        unset ( $atts['click_to_load'] ); 
        $click_to_load_text =  $atts['click_to_load_text'];  
        unset ( $atts['click_to_load_text'] ); 
        if ( !isset ( $atts['name'] ) )  
            $atts['name'] =  'gm-map-' .  $map_number;  
        $map_data =  self::build_map_data( $atts );  
        if ( empty( $map_data['object_data']['objects'] ) and !isset ( $map_data['load_empty_map'] ) )  
            return '<!-- ' .  __( 'Geo Mashup omitted a map with no located objects found.', 'GeoMashup' ) .  '-->';  
        unset ( $map_data['load_empty_map'] ); 
            // Static maps have a limit of 50 markers: http://code.google.com/apis/maps/documentation/staticmaps/#Markers  
            $atts['limit'] =  empty( $atts['limit'] ) ?  50 :  $atts['limit'];  
            if ( !empty( $map_data['object_data']['objects'] ) ) {  
                $image_width =  str_replace( '%', '', $map_data['width'] );  
                $image_height =  str_replace( '%', '', $map_data['height'] );  
                $map_image =  '<img src="http://maps.google.com/maps/api/staticmap?size='. $image_width. 'x'. $image_height;  
                if ( count( $map_data['object_data']['objects'] ) ==  1) {  
                    $map_image .=  '&center=' .  $map_data['object_data']['objects'][0]['lat'] .  ',' .   
                        $map_data['object_data']['objects'][0]['lng'];  
                $map_image .=  '&sensor=false&zoom=' .  $map_data['zoom'] .  '&markers=size:small|color:red';  
                foreach( $map_data['object_data']['objects'] as $location ) {  
                    // TODO: Try to use the correct color for the category? Draw category lines?  
                    $map_image .=  '|' .  $location['lat'] .  ',' .  $location['lng'];  
                $map_image .=  '" alt="geo_mashup_map"';  
                if ($click_to_load ==  'true') {  
                    $map_image .=  '" title="'. $click_to_load_text. '"';  
        set_transient( 'gmm' .  $atts_md5, $map_data, 20 );  
        set_transient( 'gmp' .  $atts_md5, $atts, 60* 60* 24 );  
        $iframe_src =   home_url( '?geo_mashup_content=render-map&map_data_key=' .  $atts_md5 );  
        if ( !empty( $atts['lang'] ) )  
            $iframe_src .=  '&lang=' .  $atts['lang'];  
        if ($click_to_load ==  'true') {  
                $content .=  "<a href=\"{$iframe_src}\">$click_to_load_text</a>";  
                self::$add_loader_script =  true;  
                $style =  "height:{$map_data['height']}px;width:{$map_data['width']}px;background-color:#ddd;".   
                    "background-image:url(". GEO_MASHUP_URL_PATH. "/images/wp-gm-pale.png);".   
                    "background-repeat:no-repeat;background-position:center;cursor:pointer;";  
                $content =  "<div class=\"gm-map\" style=\"$style\" " .   
                    "onclick=\"GeoMashupLoader.addMapFrame(this,'$iframe_src','{$map_data['height']}','{$map_data['width']}','{$map_data['name']}')\">";  
                    // TODO: test whether click to load really works with a static map  
                    $content .=  $map_image .  '</div>';  
                    $content .=  "<p style=\"text-align:center;\">$click_to_load_text</p></div>";  
            $content =  "<div class=\"gm-map\">$map_image</div>";  
            $content =   "<div class=\"gm-map\"><iframe name=\"{$map_data['name']}\" src=\"{$iframe_src}\" " .   
                "height=\"{$map_data['height']}\" width=\"{$map_data['width']}\" marginheight=\"0\" marginwidth=\"0\" ".   
                "scrolling=\"no\" frameborder=\"0\"></iframe></div>";  
     * Full post template tag.  
     * Returns a placeholder where a related map should display the full post content  
     * of the currently selected marker.  
     * @link http://code.google.com/p/wordpress-geo-mashup/wiki/TagReference#Full_Post  
     * @param string|array$args Template tag arguments.  
     * @return string Placeholder HTML.  
    public static function full_post($args =  null) {  
        $args =  wp_parse_args($args);  
        if ( !empty( $args['for_map'] ) ) {  
            $for_map =  $args['for_map'];  
        // It's nice if click-to-load works in the full post display  
        self::$add_loader_script =  true;  
        return '<div id="' .  $for_map .  '-post"></div>';  
     * Category name template tag.  
     * If there is a map_cat parameter, return the name of that category.  
     * @link http://code.google.com/p/wordpress-geo-mashup/wiki/TagReference#Category_Name  
     * @param string|array$option_args Template tag arguments.  
     * @return string Category name.  
            $option_args =  wp_parse_args($option_args);  
        if (is_page() && isset ($_SERVER['QUERY_STRING'])) {  
            $option_args =  $option_args +  self::explode_assoc('=','&',$_SERVER['QUERY_STRING']);  
        if (isset ($option_args['map_cat'])) {  
            $category_name =  get_cat_name($option_args['map_cat']);  
     * Category legend template tag.  
     * Returns a placeholder where a related map should display a legend for the  
     * categories of the displayed content.  
     * @link http://code.google.com/p/wordpress-geo-mashup/wiki/TagReference#Category_Legend  
     * @param string|array$args Template tag arguments.  
     * @return string Placeholder HTML.  
        $args =  wp_parse_args($args);  
        if ( !empty( $args['for_map'] ) ) {  
            $for_map =  $args['for_map'];  
        return '<div id="' .  $for_map .  '-legend"></div>';  
     * If the option is set, add a map link to category lists.  
     * list_cats {@link http://codex.wordpress.org/Plugin_API/Filter_Reference#Category_Filters filter}  
    public static function list_cats($content, $category =  null) {  
        if ( $category and 'category' ==  $category->taxonomy ) {  
            // Add map link only if there are geo-located posts to see  
                // This feature doesn't work unless there is a category description  
                if ( empty( $category->description ) ) {  
                    return $content .  $geo_mashup_options->get('overall', 'category_link_separator') .    
                        __( 'You must add a description to this category to use this Geo Mashup feature.', 'GeoMashup' );  
                $url =  get_page_link($geo_mashup_options->get('overall', 'mashup_page'));  
                $link =  '<a href="'. $url. 'map_cat='. $category->cat_ID. '&zoom='. $geo_mashup_options->get('overall', 'category_zoom').   
                    '" title="'. $geo_mashup_options->get('overall', 'category_link_text'). '">';  
                return $content. '</a>'. $geo_mashup_options->get('overall', 'category_link_separator'). $link.   
                    $geo_mashup_options->get('overall', 'category_link_text');  
     * WordPress action to add the Geo Mashup Options settings admin page.  
     * admin_menu {@link http://codex.wordpress.org/Plugin_API/Action_Reference#Advanced_Actions action}  
            add_options_page(__('Geo Mashup Options','GeoMashup'), __('Geo Mashup','GeoMashup'), 'manage_options', __FILE__ , array( __CLASS__ , 'options_page'));  
     * WordPress action to display important messages in the admin.  
     * admin_notices {@link http://codex.wordpress.org/Plugin_API/Action_Reference#Advanced_Actions action}  
        if ( ! self::is_options_page() ) {  
            // We're not looking at the settings, but it may be important to do so  
            $google_key =  $geo_mashup_options->get( 'overall', 'google_key' );  
            if ( empty( $google_key ) and 'google' ==  $geo_mashup_options->get( 'overall', 'map_api' ) and current_user_can( 'manage_options' ) ) {  
                $message =  __( 'Geo Mashup requires a Google API key in the <a href="%s">settings</a> before it will work.', 'GeoMashup' );  
                $message =  sprintf( $message, admin_url( 'options-general.php?page=' .  GEO_MASHUP_PLUGIN_NAME ) );  
                $message =  __( 'Geo Mashup needs to upgrade its database, visit the <a href="%s">settings</a> to do it now.', 'GeoMashup' );  
                $message =  sprintf( $message, admin_url( 'options-general.php?page=' .  GEO_MASHUP_PLUGIN_NAME ) );  
        if ( ! empty( $message ) ) {  
            echo  '<div class="error fade"><p>' .  $message .  '</p></div>'; 
     * WordPress action to add custom action links to the plugin listing.  
     * plugin_action_links {@link http://codex.wordpress.org/Plugin_API/Filter_Reference#Advanced_WordPress_Filters filter},  
        if ( GEO_MASHUP_PLUGIN_NAME ==  $file ) {  
            $settings_link =  '<a href="' .  admin_url( 'options-general.php?page=' .  GEO_MASHUP_PLUGIN_NAME ) . '">' .   
                __( 'Settings' ) .  '</a>';  
     * WordPress action to add custom meta links to the plugin listing.  
     * plugin_row_meta {@link http://codex.wordpress.org/Plugin_API/Filter_Reference#Advanced_WordPress_Filters filter},  
        if ( GEO_MASHUP_PLUGIN_NAME ==  $file ) {  
            $links[] =  '<a href="http://code.google.com/p/wordpress-geo-mashup/wiki/Documentation">' .   
                __( 'Documentation', 'GeoMashup' ) .  '</a>';  
            $links[] =  '<a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=11045324">' .   
                __( 'Donate', 'GeoMashup' ) .  '</a>';  
     * WordPress action to produce the Geo Mashup Options admin page.  
     * Called by the WordPress admin.  
        include_once( path_join( GEO_MASHUP_DIR_PATH, 'options.php' ) );  
     * Get the location of the current loop object, if any.  
     * @param string $output ARRAY_A | ARRAY_N | OBJECT  
     * @return object|boolLocation object or false if none.  
        global $in_comment_loop, $in_user_loop, $user;  
        if ( $in_comment_loop ) {  
            $object_name =  'comment';  
            $object_id =  get_comment_ID();  
        } else if ( $in_user_loop ) {  
        } else if ( in_the_loop() ) {  
            $object_id =  get_the_ID();  
            $object_name =  $object_id =  '';  
        if ( $object_name &&  $object_id ) {  
     * A template tag to insert location information.  
     * @param string|array$args Template tag arguments.  
     * @return string The information requested, empty string if none.  
        $args =  wp_parse_args( $args, $defaults );  
        if ( $object_name &&  $object_id ) {  
            $location =  self::current_location( ARRAY_A );  
        if ( !empty( $location ) ) {  
            foreach( $fields as $field ) {  
                if ( isset ( $location[$field] ) ) {  
                    if ( 'country_name' ==  $field ) {   
                    } else if ( 'admin_name' ==  $field ) {  
            if ( empty( $format ) ) {  
                $info =  implode( $separator, $values );  
     * A template tag to insert a link to a post on the mashup.  
     * @see show_on_map_link()  
    public static function post_link($option_args =  '') {  
        return self::show_on_map_link($option_args);  
     * A template tag to return an URL for the current location on the  
     * @return string The URL, empty if no current location is found.  
        $defaults =  array( 'zoom' =>  '' );  
        $args =  wp_parse_args( $args, $defaults );  
        $location =  self::current_location();  
            $url =  get_page_link($geo_mashup_options->get('overall', 'mashup_page'));  
            if ( $geo_mashup_options->get( 'global_map', 'auto_info_open' ) ==  'true' ) {  
                $open =  '&open_object_id=' .  $location->object_id;  
            if ( !empty( $args['zoom'] ) ) {  
                $zoom =  '&zoom=' .  urlencode( $args['zoom'] );  
            $url .=  htmlentities("center_lat={$location->lat}¢er_lng={ $location->lng}$open$zoom");  
     * A template tag to insert a link to the current location post on the  
     * @return string The link HTML, empty if no current location is found.  
        $defaults =  array( 'text' =>  __( 'Show on map', 'GeoMashup' ),  
        $options =  wp_parse_args($args, $defaults);  
        $url =  self::show_on_map_link_url( $args );  
            if ($options['show_icon'] &&  strcmp( $options['show_icon'], 'false' ) !=  0) {  
                $icon =  '<img src="'. GEO_MASHUP_URL_PATH.   
                    '/images/geotag_16.png" alt="'. __('Geotag Icon','GeoMashup'). '"/>';  
            $link =  '<a class="gm-link" href="'. $url. '">'.   
                $icon. ' '. $options['text']. '</a>';  
            if ($options['display']) {  
     * Visible posts list template tag.  
     * Returns a placeholder where a related map should display a list  
     * of the currently visible posts.  
     * @link http://code.google.com/p/wordpress-geo-mashup/wiki/TagReference#Visible_Posts_List  
     * @param string|array$args Template tag arguments.  
     * @return string Placeholder HTML.  
        $args =  wp_parse_args($args);  
        if ( !empty( $args['for_map'] ) ) {  
            $for_map =  $args['for_map'];  
        if ( !empty( $args['heading_text'] ) ) {  
            $heading_div =  '<div id="' .  $for_map .  '-visible-list-header" style="display:none;">';  
            if ( !empty( $args['heading_tags'] ) ) {  
                $heading_tags =  $args['heading_tags'];  
            $list_html .=  balanceTags( $heading_div .  $heading_tags .  $args['heading_text'], true );  
        $list_html .=  '<div id="' .  $for_map .  '-visible-list"></div>';  
     * List located posts template tag.  
     * Returns an HTML list of all located posts.  
     * @link http://code.google.com/p/wordpress-geo-mashup/wiki/TagReference#List_Located_Posts  
     * @param string|array$args Template tag arguments.  
     * @return string List HTML.  
        $option_args =  wp_parse_args( $option_args );  
        $option_args['object_name'] =  'post';  
        $list_html =  '<ul class="gm-index-posts">';  
            foreach ($locs as $loc) {  
                $list_html .=  '<li><a href="'. get_permalink($loc->object_id). '">'.   
                    $loc->label. "</a></li>\n";  
     * List located posts by area template tag.  
     * Returns an HTML list of all located posts by country and state. May try to look up  
     * this information when absent.  
     * @link http://code.google.com/p/wordpress-geo-mashup/wiki/TagReference#List_Located_Posts_By_Area  
     * @param string|array$args Template tag arguments.  
     * @return string List HTML.  
        $args =  wp_parse_args( $args );  
        $list_html =  '<div class="gm-area-list">';  
        $country_count =  count( $countries );  
        foreach ( $countries as $country ) {  
            if ( $country_count >  1 ) {  
                $country_name =  $country_name ?  $country_name :  $country->country_code;  
                $country_heading =  '<h3>' .  $country_name .  '</h3>';  
                array( 'country_code' =>  $country->country_code, 'object_name' =>  'post' ) );  
            if ( empty( $states ) ) {  
                $states =  array( (object)  array( 'admin_code' =>  null ) );  
            foreach ($states as $state ) {   
                    'country_code' =>  $country->country_code,  
                    'admin_code' =>  $state->admin_code,  
                if ( count( $post_locations ) >  0 ) {  
                    if ( ! empty( $country_heading ) ) {  
                        $list_html .=  $country_heading;  
                    if ( null !=  $states[0]->admin_code ) {  
                        $state_name =  $state_name ?  $state_name :  $state->admin_code;  
                        $list_html .=  '<h4>' .  $state_name .  '</h4>';  
                    $list_html .=  '<ul class="gm-index-posts">';  
                    foreach ( $post_locations as $post_location ) {   
                        $list_html .=  '<li><a href="' .    
                            get_permalink( $post_location->object_id ) .   
                        if ( isset ( $args['include_address'] ) &&  $args['include_address'] ==  'true' ) {  
                            $list_html .=  '<p>' .  $post_location->address .  '</p>';  
     * Post coordinates template tag.  
     * Get the coordinates of the current post.  
     * @link http://code.google.com/p/wordpress-geo-mashup/wiki/TagReference#Post_Coordinates  
     * @deprecated 1.3 Use GeoMashup::current_location()  
     * @param string|array$places Maximum number of decimal places to use.  
     * @return array Array containing 'lat' and 'lng' keys.  
        if ( !empty( $location ) ) {  
            $lat_dec_pos =  strpos($lat,'.');  
            if ($lat_dec_pos !==  false) {  
                $lat =  substr($lat, 0, $lat_dec_pos+ $places+ 1);  
            $lng_dec_pos =  strpos($lng,'.');  
            if ($lng_dec_pos !==  false) {  
                $lng =  substr($lng, 0, $lng_dec_pos+ $places+ 1);  
            $coordinates['lat'] =  $lat;  
            $coordinates['lng'] =  $lng;  
     * WordPress action to emit GeoRSS namespace.  
     * rss_ns {@link http://codex.wordpress.org/Plugin_API/Action_Reference#Feed_Actions action}  
    public static function rss_ns() {  
        echo  'xmlns:georss="http://www.georss.org/georss" '; 
     * WordPress action to emit GeoRSS tags.  
     * rss_item {@link http://codex.wordpress.org/Plugin_API/Action_Reference#Feed_Actions action}  
        // Using Simple GeoRSS for now  
        if ( !empty( $location ) ) {  
            echo  '<georss:point>' .  esc_html( $location->lat .  ' ' .  $location->lng ) .  '</georss:point>'; 
     * Tabbed category index template tag.  
     * Returns a placeholder where a related map should display a list  
     * of map objects by category, organized into HTML suited for presentation  
     * @link http://code.google.com/p/wordpress-geo-mashup/wiki/TagReference#Visible_Posts_List  
     * @param string|array$args Template tag arguments.  
     * @return string Placeholder HTML.  
        $args =  wp_parse_args($args);  
        if ( !empty( $args['for_map'] ) ) {  
            $for_map =  $args['for_map'];  
        return '<div id="' .  $for_map .  '-tabbed-index"></div>';  
 
 
        
       |