GeoMashup
[ class tree: GeoMashup ] [ index: GeoMashup ] [ all elements ]

Source for file geo-mashup-db.php

Documentation is available at geo-mashup-db.php

  1. <?php 
  2. /**
  3.  * Geo Mashup Data Access
  4.  *
  5.  * @package GeoMashup
  6.  */
  7.  
  8. // init action
  9. add_action'init'array'GeoMashupDB''init' ) );
  10.  
  11. /**
  12.  * Static class to provide a namespace for Geo Mashup data functions.
  13.  *
  14.  * @since 1.2
  15.  * @package GeoMashup
  16.  */
  17. class GeoMashupDB {
  18.     /**
  19.      * Current installed database version.
  20.      * 
  21.      * @since 1.4
  22.      * @var string 
  23.      */
  24.     private static $installed_version null;
  25.     /**
  26.      * Flag for objects that have geodata fields copied to.
  27.      * Key $meta_type-$object_id, value true.
  28.      * 
  29.      * @since 1.4
  30.      * @var array 
  31.      */
  32.     private static $copied_to_geodata array();
  33.     /**
  34.      * Meta keys used to store geodata
  35.      *
  36.      * @since 1.4
  37.      * @var array 
  38.      */
  39.     private static $geodata_keys array'geo_latitude''geo_longitude''geo_address' );
  40.     /**
  41.      * The last geocode error, or empty if no error.
  42.      * @var WP_Error 
  43.      */
  44.     public static $geocode_error array();
  45.  
  46.     /**
  47.      * WordPress action to set up data-related WordPress hooks.
  48.      *
  49.      * @since 1.4
  50.      * @usedby do_action() init
  51.      */
  52.     public static function init({
  53.         global $geo_mashup_options;
  54.  
  55.         // Some caching plugins don't implement this
  56.         if function_exists'wp_cache_add_global_groups' ) )
  57.             wp_cache_add_global_groupsarray'geo_mashup_object_locations''geo_mashup_locations' ) );
  58.  
  59.         // Avoid orphans
  60.         add_action'delete_post'array'GeoMashupDB''delete_post' ) );
  61.         add_action'delete_comment'array'GeoMashupDB''delete_comment' ) );
  62.         add_action'delete_user'array'GeoMashupDB''delete_user' ) );
  63.  
  64.         if 'true' == $geo_mashup_options->get'overall''copy_geodata' ) )
  65.             self::add_geodata_sync_hooks();
  66.     }
  67.  
  68.     /**
  69.      * Add hooks to synchronize Geo Mashup ojbect locations with WordPress geodata.
  70.      *
  71.      * @since 1.4
  72.      */
  73.     public static function add_geodata_sync_hooks({
  74.         add_filter'update_post_metadata'array'GeoMashupDB''filter_update_post_metadata' )10);
  75.         add_action'added_post_meta'array'GeoMashupDB''action_added_post_meta' )10);
  76.         add_action'updated_post_meta'array'GeoMashupDB''action_added_post_meta' )10);
  77.         add_filter'update_user_metadata'array'GeoMashupDB''filter_update_user_metadata' )10);
  78.         add_action'added_user_meta'array'GeoMashupDB''action_added_user_meta' )10);
  79.         add_action'updated_user_meta'array'GeoMashupDB''action_added_user_meta' )10);
  80.         add_filter'update_comment_metadata'array'GeoMashupDB''filter_update_comment_metadata' )10);
  81.         add_action'added_comment_meta'array'GeoMashupDB''action_added_comment_meta' )10);
  82.         add_action'updated_comment_meta'array'GeoMashupDB''action_added_comment_meta' )10);
  83.         // AJAX calls use a slightly different hook - triksy!
  84.         add_action'added_postmeta'array'GeoMashupDB''action_added_post_meta' )10);
  85.         add_action'updated_postmeta'array'GeoMashupDB''action_added_post_meta' )10);
  86.  
  87.         add_action'geo_mashup_added_object_location'array'GeoMashupDB''copy_to_geodata' )10);
  88.         add_action'geo_mashup_updated_object_location'array'GeoMashupDB''copy_to_geodata' )10);
  89.     }
  90.     
  91.     /**
  92.      * Remove hooks to synchronize Geo Mashup ojbect locations with WordPress geodata.
  93.      *
  94.      * @since 1.4
  95.      */
  96.     public static function remove_geodata_sync_hooks({
  97.         remove_filter'update_post_metadata'array'GeoMashupDB''filter_update_post_metadata' )10);
  98.         remove_action'added_post_meta'array'GeoMashupDB''action_added_post_meta' )10);
  99.         remove_action'updated_post_meta'array'GeoMashupDB''action_added_post_meta' )10);
  100.         remove_filter'update_user_metadata'array'GeoMashupDB''filter_update_user_metadata' )10);
  101.         remove_action'added_user_meta'array'GeoMashupDB''action_added_user_meta' )10);
  102.         remove_action'updated_user_meta'array'GeoMashupDB''action_added_user_meta' )10);
  103.         remove_filter'update_comment_metadata'array'GeoMashupDB''filter_update_comment_metadata' )10);
  104.         remove_action'added_comment_meta'array'GeoMashupDB''action_added_comment_meta' )10);
  105.         remove_action'updated_comment_meta'array'GeoMashupDB''action_added_comment_meta' )10);
  106.         // AJAX calls use a slightly different hook - triksy!
  107.         remove_action'added_postmeta'array'GeoMashupDB''action_added_post_meta' )10);
  108.         remove_action'updated_postmeta'array'GeoMashupDB''action_added_post_meta' )10);
  109.  
  110.         remove_action'geo_mashup_added_object_location'array'GeoMashupDB''copy_to_geodata' )10);
  111.         remove_action'geo_mashup_updated_object_location'array'GeoMashupDB''copy_to_geodata' )10);
  112.     }
  113.  
  114.     /**
  115.      * WordPress action to update Geo Mashup post location when geodata custom fields are updated.
  116.      *
  117.      * @since 1.4
  118.      */
  119.     public static function action_added_post_meta$meta_id$post_id$meta_key$meta_value {
  120.         self::copy_from_geodata'post'$meta_id$post_id$meta_key$meta_value );
  121.     }
  122.  
  123.     /**
  124.      * WordPress action to update Geo Mashup user location when geodata custom fields are updated.
  125.      *
  126.      * @since 1.4
  127.      */
  128.     public static function action_added_user_meta$meta_id$user_id$meta_key$meta_value {
  129.         self::copy_from_geodata'user'$meta_id$user_id$meta_key$meta_value );
  130.     }
  131.  
  132.     /**
  133.      * WordPress action to update Geo Mashup comment location when geodata custom fields are updated.
  134.      *
  135.      * @since 1.4
  136.      */
  137.     public static function action_added_comment_meta$meta_id$comment_id$meta_key$meta_value {
  138.         self::copy_from_geodata'comment'$meta_id$comment_id$meta_key$meta_value );
  139.     }
  140.  
  141.     /**
  142.      * WordPress filter to prevent updates to geodata fields we've already updated.
  143.      * 
  144.      * @since 1.4
  145.      */
  146.     public static function filter_update_post_metadata$ok$object_id$meta_key$meta_value$prev_value {
  147.         if !in_array$meta_keyself::$geodata_keys ) )
  148.             return $ok;
  149.         if issetself::$copied_to_geodata['post-' $object_id) )
  150.             return false;
  151.         else
  152.             return $ok;
  153.     }
  154.  
  155.     /**
  156.      * WordPress filter to prevent updates to geodata fields we've already updated.
  157.      *
  158.      * @since 1.4
  159.      */
  160.     public static function filter_update_user_metadata$ok$object_id$meta_key$meta_value$prev_value {
  161.         if !in_array$meta_keyself::$geodata_keys ) )
  162.             return $ok;
  163.         if issetself::$copied_to_geodata['user-' $object_id) )
  164.  
  165.             return false;
  166.         else
  167.             return $ok;
  168.     }
  169.  
  170.     /**
  171.      * WordPress filter to prevent updates to geodata fields we've already updated.
  172.      *
  173.      * @since 1.4
  174.      */
  175.     public static function filter_update_comment_metadata$ok$object_id$meta_key$meta_value$prev_value {
  176.         if !in_array$meta_keyself::$geodata_keys ) )
  177.             return $ok;
  178.         if issetself::$copied_to_geodata['comment-' $object_id) )
  179.             return false;
  180.         else
  181.             return $ok;
  182.     }
  183.  
  184.     /**
  185.      * Create a Geo Mashup object location from WordPress geodata.
  186.      *
  187.      * @since 1.4
  188.      * @see http://codex.wordpress.org/Geodata
  189.      */
  190.     private static function copy_from_geodata$meta_type$meta_id$object_id$meta_key$meta_value {
  191.         global $geo_mashup_options$wpdb;
  192.  
  193.         // Do nothing if meta_key is not a known location field
  194.         $location_keys array'geo_latitude''geo_longitude''geo_lat_lng' );
  195.         $import_custom_key $geo_mashup_options->get'overall''import_custom_field' );
  196.         $location_keys[$import_custom_key;
  197.         if in_array$meta_key$location_keys ) ) 
  198.             return;
  199.  
  200.         $existing_location self::get_object_location$meta_type$object_id );
  201.  
  202.         $restore_insert_id $wpdb->insert_id// Work around a little WP bug in add_meta #15465
  203.         $location array();
  204.  
  205.         // Do nothing unless both latitude and longitude exist for the object
  206.         if 'geo_lat_lng' == $meta_key {
  207.  
  208.             $lat_lng preg_split'/\s*[, ]\s*/'trim$meta_value ) );
  209.             if != count$lat_lng ) ) {
  210.                 return
  211.             }
  212.             $location['lat'$lat_lng[0];
  213.             $location['lng'$lat_lng[1];
  214.  
  215.         else if 'geo_latitude' == $meta_key {
  216.  
  217.             $location['lat'$meta_value;
  218.             $lng get_metadata$meta_type$object_id'geo_longitude'true );
  219.             if empty$lng ) )
  220.                 return;
  221.             $location['lng'$lng;
  222.  
  223.         else if 'geo_longitude' == $meta_key {
  224.  
  225.             $location['lng'$meta_value;
  226.             $lat get_metadata$meta_type$object_id'geo_latitude'true );
  227.             if empty$lat ) )
  228.                 return;
  229.             $location['lat'$lat;
  230.  
  231.         else if $import_custom_key == $meta_key {
  232.  
  233.             $lat_lng preg_split'/\s*[, ]\s*/'trim$meta_value ) );
  234.             if count$lat_lng == and is_numeric$lat_lng[0and is_numeric$lat_lng[1) ) {
  235.                 $location['lat'$lat_lng[0];
  236.                 $location['lng'$lat_lng[1];
  237.             else if !empty$meta_value ) ) {
  238.                 $location self::blank_locationARRAY_A );
  239.                 self::geocode$meta_value$location );
  240.             }
  241.         }
  242.  
  243.         // Do nothing if the location already exists
  244.         if !empty$existing_location ) ) {
  245.             $epsilon 0.00001;
  246.             if abs$location['lat'$existing_location->lat $epsilon and abs$location['lng'$existing_location->lng $epsilon )
  247.                 return;
  248.         }
  249.  
  250.         // Save the location, attempt reverse geocoding
  251.         self::remove_geodata_sync_hooks();
  252.         self::set_object_location$meta_type$object_id$locationnull );
  253.         self::add_geodata_sync_hooks();
  254.         $wpdb->insert_id $restore_insert_id;
  255.     }
  256.  
  257.     /**
  258.      * Update object geodata if needed.
  259.      * 
  260.      * @since 1.4
  261.      * 
  262.      * @param string $meta_type 'post','user','comment'
  263.      * @param int $object_id 
  264.      * @param array $location The location to copy from.
  265.      */
  266.     public static function copy_to_geodata$meta_type$object_id$geo_date$location_id {
  267.  
  268.         $geo_latitude get_metadata$meta_type$object_id'geo_latitude'true );
  269.         $geo_longitude get_metadata$meta_type$object_id'geo_longitude'true );
  270.         $existing_location self::get_location$location_id );
  271.  
  272.         // Do nothing if the geodata already exists
  273.         if $geo_latitude and $geo_longitude {
  274.             $epsilon 0.00001;
  275.             if abs$geo_latitude $existing_location->lat $epsilon and abs$geo_longitude $existing_location->lng $epsilon )
  276.                 return;
  277.         }
  278.         
  279.         self::remove_geodata_sync_hooks();
  280.         update_metadata$meta_type$object_id'geo_latitude'$existing_location->lat );
  281.         update_metadata$meta_type$object_id'geo_longitude'$existing_location->lng );
  282.         update_metadata$meta_type$object_id'geo_address'$existing_location->address );
  283.         self::$copied_to_geodata[$meta_type '-' $object_idtrue;
  284.         self::add_geodata_sync_hooks();
  285.     }
  286.  
  287.     /**
  288.      * Set the installed database version.
  289.      * 
  290.      * @since 1.4
  291.      * 
  292.      * @param string $new_version 
  293.      */
  294.     private static function set_installed_version$new_version {
  295.         self::$installed_version $new_version;
  296.         update_option'geo_mashup_db_version'$new_version );
  297.     }
  298.  
  299.     /**
  300.      * Get the installed database version.
  301.      *
  302.      * @since 1.2
  303.      * 
  304.      * @param string $new_version If provided, overwrites any currently installed version.
  305.      * @return string The installed database version.
  306.      */
  307.     public static function installed_version({
  308.  
  309.         if is_nullself::$installed_version ) ) {
  310.             self::$installed_version get_option'geo_mashup_db_version' );
  311.         }
  312.         return self::$installed_version;
  313.     }
  314.  
  315.     /**
  316.      * Get or set storage information for an object name.
  317.      *
  318.      * Potentially you could add storage information for a new kind of object:
  319.      * <code>
  320.      * GeoMashupDB::object_storage( 'foo', array(
  321.      *     'table' => $wpdb->prefix . 'foos',
  322.      *     'id_column' => 'foo_id',
  323.      *     'label_column' => 'foo_display_name',
  324.      *     'sort' => 'foo_order ASC' )
  325.      * );
  326.      * </code>
  327.      * Would add the necessary information for a custom table of foo objects.
  328.      *
  329.      * @since 1.3
  330.      * 
  331.      * @param string $object_name A type of object to be stored, default is 'post', 'user', and 'comment'.
  332.      * @param array $new_storage If provided, adds or replaces the storage information for the object name.
  333.      * @return array|boolThe storage information array, or false if not found.
  334.      */
  335.     public static function object_storage$object_name$new_storage null {
  336.         global $wpdb;
  337.         static $objects null;
  338.         
  339.         if is_null$objects ) ) {
  340.             $objects array
  341.                 'post' => array
  342.                     'table' => $wpdb->posts
  343.                     'id_column' => 'ID'
  344.                     'label_column' => 'post_title'
  345.                     'date_column' => 'post_date',
  346.                     'sort' => 'post_status ASC, geo_date DESC' ),
  347.                 'user' => array
  348.                     'table' => $wpdb->users
  349.                     'id_column' => 'ID'
  350.                     'label_column' => 'display_name',
  351.                     'date_column' => 'user_registered',
  352.                      'sort' => 'display_name ASC' ),
  353.                 'comment' => array
  354.                     'table' => $wpdb->comments
  355.                     'id_column' => 'comment_ID'
  356.                     'label_column' => 'comment_author',
  357.                     'date_column' => 'comment_date',
  358.                      'sort' => 'comment_date DESC'    
  359.             );
  360.         }
  361.  
  362.         if !empty$new_storage ) ) {
  363.             $objects[$object_name$new_storage;
  364.         
  365.         return isset$objects[$object_name) ) $objects[$object_namefalse;
  366.     }
  367.  
  368.     /**
  369.      * Toggle joining of WordPress queries with Geo Mashup tables.
  370.      * 
  371.      * Use the public wrapper GeoMashup::join_post_queries()
  372.      * 
  373.      * @since 1.3
  374.      *
  375.      * @param bool $new_value If provided, replaces the current active state.
  376.      * @return bool The current state.
  377.      */
  378.     public static function join_post_queries$new_value null{
  379.         static $active null;
  380.  
  381.         if is_bool$new_value ) ) {
  382.             if $new_value {
  383.  
  384.                 add_filter'query_vars'array'GeoMashupDB''query_vars' ) );
  385.                 add_filter'posts_fields'array'GeoMashupDB''posts_fields' ) );
  386.                 add_filter'posts_join'array'GeoMashupDB''posts_join' ) );
  387.                 add_filter'posts_where'array'GeoMashupDB''posts_where' ) );
  388.                 add_filter'posts_orderby'array'GeoMashupDB''posts_orderby' ) );
  389.                 add_action'parse_query'array'GeoMashupDB''parse_query' ) );
  390.  
  391.             else if is_null$active ) ) {
  392.  
  393.                 remove_filter'query_vars'array'GeoMashupDB''query_vars' ) );
  394.                 remove_filter'posts_fields'array'GeoMashupDB''posts_fields' ) );
  395.                 remove_filter'posts_join'array'GeoMashupDB''posts_join' ) );
  396.                 remove_filter'posts_where'array'GeoMashupDB''posts_where' ) );
  397.                 remove_filter'posts_orderby'array'GeoMashupDB''posts_orderby' ) );
  398.                 remove_action'parse_query'array'GeoMashupDB''parse_query' ) );
  399.             }
  400.  
  401.             $active $new_value;
  402.         }
  403.         return $active;
  404.     }
  405.  
  406.     /**
  407.      * WordPress filter to add Geo Mashup public query variables.
  408.      *
  409.      * query_vars {@link http://codex.wordpress.org/Plugin_API/Filter_Reference filter}
  410.      * called by Wordpress.
  411.      *
  412.      * @since 1.3
  413.      */
  414.     public static function query_vars$public_query_vars {
  415.         $public_query_vars['geo_mashup_date';
  416.         $public_query_vars['geo_mashup_saved_name';
  417.         $public_query_vars['geo_mashup_country_code';
  418.         $public_query_vars['geo_mashup_postal_code';
  419.         $public_query_vars['geo_mashup_admin_code';
  420.         $public_query_vars['geo_mashup_locality';
  421.         return $public_query_vars;
  422.     }
  423.  
  424.     /**
  425.      * Set or get custom orderby field.
  426.      *
  427.      * @since 1.3
  428.      * 
  429.      * @param string $new_value Replace any current value.
  430.      * @return string Current orderby field.
  431.      */
  432.     private static function query_orderby$new_value null {
  433.         static $orderby null;
  434.  
  435.         if !is_null$new_value ) ) {
  436.             $orderby $new_value;
  437.         }
  438.         return $orderby;
  439.     }
  440.  
  441.     /**
  442.      * WordPress action to capture custom orderby field before it is removed.
  443.      *
  444.      * parse_query {@link http://codex.wordpress.org/Plugin_API/Action_Reference action}
  445.      * called by WordPress.
  446.      * 
  447.      * @since 1.3
  448.      * @access private
  449.      * @static
  450.      */
  451.     public static function parse_query$query {
  452.         global $wpdb;
  453.  
  454.         if empty$query->query_vars['orderby') ) 
  455.             return;
  456.  
  457.         // Check for geo mashup fields in the orderby before they are removed as invalid
  458.         switch $query->query_vars['orderby'{
  459.             case 'geo_mashup_date':
  460.                 self::query_orderby$wpdb->prefix 'geo_mashup_location_relationships.geo_date' );
  461.                 break;
  462.  
  463.             case 'geo_mashup_locality':
  464.                 self::query_orderby$wpdb->prefix 'geo_mashup_locations.locality_name' );
  465.                 break;
  466.  
  467.             case 'geo_mashup_saved_name':
  468.                 self::query_orderby$wpdb->prefix 'geo_mashup_locations.saved_name' );
  469.                 break;
  470.  
  471.             case 'geo_mashup_country_code':
  472.                 self::query_orderby$wpdb->prefix 'geo_mashup_locations.country_code' );
  473.                 break;
  474.  
  475.             case 'geo_mashup_admin_code':
  476.                 self::query_orderby$wpdb->prefix 'geo_mashup_locations.admin_code' );
  477.                 break;
  478.  
  479.             case 'geo_mashup_postal_code':
  480.                 self::query_orderby$wpdb->prefix 'geo_mashup_locations.postal_code' );
  481.                 break;
  482.         }
  483.     }
  484.  
  485.     /**
  486.      * WordPress filter to add Geo Mashup fields to WordPress post queries.
  487.      *
  488.      * posts_fields {@link http://codex.wordpress.org/Plugin_API/Filter_Reference filter}
  489.      * called by WordPress.
  490.      * 
  491.      * @since 1.3
  492.      */
  493.     public static function posts_fields$fields {
  494.         global $wpdb;
  495.  
  496.         $fields .= ',' $wpdb->prefix 'geo_mashup_location_relationships.geo_date' .
  497.             ',' $wpdb->prefix 'geo_mashup_locations.*';
  498.  
  499.         return $fields;
  500.     }
  501.  
  502.     /**
  503.      * WordPress filter to join Geo Mashup tables to WordPress post queries.
  504.      * 
  505.      * posts_join {@link http://codex.wordpress.org/Plugin_API/Filter_Reference filter}
  506.      * called by WordPress.
  507.      * 
  508.      * @since 1.3
  509.      */
  510.     public static function posts_join$join {
  511.         global $wpdb;
  512.  
  513.         $gmlr $wpdb->prefix 'geo_mashup_location_relationships';
  514.         $gml $wpdb->prefix 'geo_mashup_locations';
  515.         $join .= " INNER JOIN $gmlr ON ($gmlr.object_name = 'post' AND $gmlr.object_id = $wpdb->posts.ID).
  516.             " INNER JOIN $gml ON ($gml.id = $gmlr.location_id) ";
  517.  
  518.         return $join;
  519.     }
  520.  
  521.     /**
  522.      * WordPress filter to incorporate geo mashup query vars in WordPress post queries.
  523.      *
  524.      * posts_where {@link http://codex.wordpress.org/Plugin_API/Filter_Reference filter}
  525.      * called by WordPress.
  526.      * 
  527.      * @since 1.3
  528.      */
  529.     public static function posts_where$where {
  530.         global $wpdb;
  531.  
  532.         $gmlr $wpdb->prefix 'geo_mashup_location_relationships';
  533.         $gml $wpdb->prefix 'geo_mashup_locations';
  534.         $geo_date get_query_var'geo_mashup_date' );
  535.         if $geo_date {
  536.             $where .= $wpdb->prepare" AND $gmlr.geo_date = %s "$geo_date );
  537.         }
  538.         $saved_name get_query_var'geo_mashup_saved_name' );
  539.         if $saved_name {
  540.             $where .= $wpdb->prepare" AND $gml.saved_name = %s "$saved_name );
  541.         }
  542.         $locality get_query_var'geo_mashup_locality' );
  543.         if $locality {
  544.             $where .= $wpdb->prepare" AND $gml.locality_name = %s "$locality );
  545.         }
  546.         $country_code get_query_var'geo_mashup_country_code' );
  547.         if $country_code {
  548.             $where .= $wpdb->prepare" AND $gml.country_code = %s "$country_code );
  549.         }
  550.         $admin_code get_query_var'geo_mashup_admin_code' );
  551.         if $admin_code {
  552.             $where .= $wpdb->prepare" AND $gml.admin_code = %s "$admin_code );
  553.         }
  554.         $postal_code get_query_var'geo_mashup_postal_code' );
  555.         if $postal_code {
  556.             $where .= $wpdb->prepare" AND $gml.postal_code = %s "$postal_code );
  557.         }
  558.  
  559.         return $where;
  560.     }
  561.  
  562.     /**
  563.      * WordPress filter to replace a WordPress post query orderby with a requested Geo Mashup field.
  564.      *
  565.      * posts_orderby {@link http://codex.wordpress.org/Plugin_API/Filter_Reference filter}
  566.      * called by WordPress.
  567.      * 
  568.      * @since 1.3
  569.      */
  570.     public static function posts_orderby$orderby {
  571.         global $wpdb;
  572.  
  573.         if self::query_orderby() ) {
  574.  
  575.             // Now our "invalid" value has been replaced by post_date, we change it back
  576.             $orderby str_replace"$wpdb->posts.post_date"self::query_orderby()$orderby );
  577.  
  578.             // Reset for subsequent queries
  579.             self::query_orderbyfalse );
  580.         }
  581.         return $orderby;
  582.     }
  583.  
  584.     /**
  585.      * Append to the activation log.
  586.      * 
  587.      * Add a message and optionally write the activation log.
  588.      * Needs to be written before the end of the request or it will not be saved.
  589.      *
  590.      * @since 1.4
  591.      *
  592.      * @param string $message The message to append.
  593.      * @param boolean $write Whether to save the log.
  594.      * @return string The current log.
  595.      */
  596.     public static function activation_log$message null$write false {
  597.         static $log null;
  598.  
  599.         if is_null$log ) ) {
  600.             $log get_option'geo_mashup_activation_log' );
  601.         }
  602.         if is_null$message ) ) {
  603.             $log .= "\n" $message;
  604.         }
  605.         if $write {
  606.             update_option'geo_mashup_activation_log'$log );
  607.         }
  608.         return $log;
  609.     }
  610.  
  611.     /**
  612.      * Install or update Geo Mashup tables.
  613.      *
  614.      * @uses GeoMashupDB::activation_log()
  615.      * @since 1.2
  616.      */
  617.     public static function install({
  618.         global $wpdb$geo_mashup_options;
  619.  
  620.         self::activation_logdate'r' ' ' __'Activating Geo Mashup''GeoMashup' ) );
  621.         $location_table_name $wpdb->prefix 'geo_mashup_locations';
  622.         $relationships_table_name $wpdb->prefix 'geo_mashup_location_relationships';
  623.         $administrative_names_table_name $wpdb->prefix 'geo_mashup_administrative_names';
  624.         if self::installed_version(!= GEO_MASHUP_DB_VERSION {
  625.             $sql "
  626.                 CREATE TABLE $location_table_name (
  627.                     id MEDIUMINT( 9 ) NOT NULL AUTO_INCREMENT,
  628.                     lat FLOAT( 11,7 ) NOT NULL,
  629.                     lng FLOAT( 11,7 ) NOT NULL,
  630.                     address TINYTEXT NULL,
  631.                     saved_name VARCHAR( 100 ) NULL,
  632.                     geoname TINYTEXT NULL, 
  633.                     postal_code TINYTEXT NULL,
  634.                     country_code VARCHAR( 2 ) NULL,
  635.                     admin_code VARCHAR( 20 ) NULL,
  636.                     sub_admin_code VARCHAR( 80 ) NULL,
  637.                     locality_name TINYTEXT NULL,
  638.                     PRIMARY KEY  ( id ),
  639.                     UNIQUE KEY saved_name ( saved_name ),
  640.                     UNIQUE KEY latlng ( lat, lng ),
  641.                     KEY lat ( lat ),
  642.                     KEY lng ( lng )
  643.                 );
  644.                 CREATE TABLE $relationships_table_name (
  645.                     object_name VARCHAR( 80 ) NOT NULL,
  646.                     object_id BIGINT( 20 ) NOT NULL,
  647.                     location_id MEDIUMINT( 9 ) NOT NULL,
  648.                     geo_date DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00',
  649.                     PRIMARY KEY  ( object_name, object_id, location_id ),
  650.                     KEY object_name ( object_name, object_id ),
  651.                     KEY object_date_key ( object_name, geo_date )
  652.                 );
  653.                 CREATE TABLE $administrative_names_table_name (
  654.                     country_code VARCHAR( 2 ) NOT NULL,
  655.                     admin_code VARCHAR( 20 ) NOT NULL,
  656.                     isolanguage VARCHAR( 7 ) NOT NULL,
  657.                     geoname_id MEDIUMINT( 9 ) NULL,
  658.                     name VARCHAR( 200 ) NOT NULL,
  659.                     PRIMARY KEY admin_id ( country_code, admin_code, isolanguage )
  660.                 );";
  661.             require_onceABSPATH 'wp-admin/includes/upgrade.php' );
  662.             // Capture error messages - some are ok
  663.             $old_show_errors $wpdb->show_errorstrue );
  664.             ob_start();
  665.             dbDelta$sql );
  666.             $errors ob_get_contents();
  667.             ob_end_clean();
  668.             $have_create_errors preg_match'/^(CREATE|INSERT|UPDATE)/m'$errors );
  669.             $have_bad_alter_errors preg_match'/^ALTER TABLE.*ADD (?!KEY|PRIMARY KEY|UNIQUE KEY)/m'$errors )
  670.             $have_bad_errors $have_create_errors || $have_bad_alter_errors;
  671.             if $errors && $have_bad_errors {
  672.                 // Any errors other than duplicate or multiple primary key could be trouble
  673.                 self::activation_log$errorstrue );
  674.                 die$errors );
  675.             else {
  676.                 if self::convert_prior_locations) ) {
  677.                     self::set_installed_versionGEO_MASHUP_DB_VERSION );
  678.                 }
  679.             }
  680.             $wpdb->show_errors$old_show_errors );
  681.         }
  682.         if self::installed_version(== GEO_MASHUP_DB_VERSION {
  683.             if 'true' == $geo_mashup_options->get'overall''copy_geodata' ) )
  684.                 self::duplicate_geodata();
  685.             self::activation_log__'Geo Mashup database is up to date.''GeoMashup' )true );
  686.             return true;
  687.         else {
  688.             self::activation_log__'Geo Mashup database upgrade failed.''GeoMashup' )true );
  689.             return false;
  690.         }
  691.     }
  692.  
  693.     /**
  694.      * Try to get a language-sensitive place administrative name.
  695.      *
  696.      * First look in the names cached in the database, then query geonames.org for it.
  697.      * If a name can't be found for the requested language, a default name is returned,
  698.      * usually in the local language. If nothing can be found, returns NULL.
  699.      *
  700.      * @since 1.4
  701.      *
  702.      * @param string $country_code Two-character ISO country code.
  703.      * @param string $admin_code Code for the administrative area within the country, or NULL to get the country name.
  704.      * @param string $language Language code, defaults to the WordPress locale language.
  705.      * @return string|nullPlace name in the appropriate language, or if not available in the default language.
  706.      */
  707.     public function get_administrative_name$country_code$admin_code null$language '' {
  708.         $language self::primary_language_code$language );
  709.         $name GeoMashupDB::get_cached_administrative_name$country_code$admin_code$language );
  710.         if empty$name ) ) {
  711.             // Look it up with Geonames
  712.             if !class_exists'GeoMashupHttpGeocoder' ) )
  713.                 include_oncepath_joinGEO_MASHUP_DIR_PATH'geo-mashup-geocoders.php' ) );
  714.             $geocoder new GeoMashupGeonamesGeocoder();
  715.             $name $geocoder->get_administrative_name$country_code$admin_code );
  716.             if is_wp_error$name ) )
  717.                 $name null;
  718.         }
  719.         return $name;
  720.     }
  721.  
  722.     /** 
  723.      * Look in the database for a cached administrative name.
  724.      *
  725.      * @since 1.2
  726.      *
  727.      * @param string $country_code Two-character ISO country code.
  728.      * @param string $admin_code Code for the administrative area within the country, or NULL to get the country name.
  729.      * @param string $language Language code, defaults to the WordPress locale language.
  730.      * @return string|nullPlace name or NULL.
  731.      */
  732.     private static function get_cached_administrative_name$country_code$admin_code ''$language '' {
  733.         global $wpdb;
  734.  
  735.         $language self::primary_language_code$language );
  736.         $select_string "SELECT name
  737.             FROM {$wpdb->prefix}geo_mashup_administrative_names
  738.             WHERE 
  739.             $wpdb->prepare'isolanguage = %s AND country_code = %s AND admin_code = %s'$language$country_code$admin_code )
  740.  
  741.         return $wpdb->get_var$select_string );
  742.     }
  743.  
  744.     /** 
  745.      * Trim a locale or browser accepted languages string down to the 2 or 3 character
  746.      * primary language code.
  747.      *
  748.      * @since 1.2
  749.      *
  750.      * @param string $language Local or language code string, NULL for blog locale.
  751.      * @return string Two (rarely three?) character language code.
  752.      */
  753.     public static function primary_language_code$language null {
  754.         if empty$language ) ) {
  755.             $language get_locale);
  756.         }
  757.         if strlen$language {
  758.             if ctype_alpha$language[2) ) {
  759.                 $language substr$language0);
  760.             else {
  761.                 $language substr$language0);
  762.             }
  763.         }
  764.         return $language;
  765.     }
  766.  
  767.     /**
  768.      * Try to fill in coordinates and other fields of a location from a textual
  769.      * location search.
  770.      *
  771.      * Multiple geocoding services may be used. Google services are only used
  772.      * if the default map provider is Google.
  773.      * 
  774.      * @since 1.3
  775.      *
  776.      * @param mixed $query The search string.
  777.      * @param array $location The location array to geocode, modified.
  778.      * @param string $language 
  779.      * @return bool Whether a lookup succeeded.
  780.      */
  781.     public static function geocode$query&$location$language '' {
  782.         global $geo_mashup_options;
  783.  
  784.         if empty$location ) ) {
  785.             $location self::blank_location();
  786.         else if !is_array$location and !is_object$location ) ) {
  787.             return false;
  788.         }
  789.  
  790.         $status false;
  791.         if !class_exists'GeoMashupHttpGeocoder' ) )
  792.             include_oncepath_joinGEO_MASHUP_DIR_PATH'geo-mashup-geocoders.php' ) );
  793.  
  794.         // Try GeoCoding services (google, nominatim, geonames) until one gives an answer
  795.         $results array();
  796.         if 'google' == substr$geo_mashup_options->get'overall''map_api' )0) ) {
  797.             // Only try the google service if a google API is selected as the default
  798.             $google_geocoder new GeoMashupGoogleGeocoderarray'language' => $language ) );
  799.             $results $google_geocoder->geocode$query );
  800.         }
  801.         if is_wp_error$results or empty$results ) ) {
  802.             self::$geocode_error $results;
  803.             $nominatim_geocoder new GeoMashupNominatimGeocoderarray'language' => $language ) );
  804.             $results $nominatim_geocoder->geocode$query );
  805.         }
  806.         if is_wp_error$results or empty$results ) ) {
  807.             self::$geocode_error $results;
  808.             $geonames_geocoder new GeoMashupGeonamesGeocoderarray'language' => $language ) );
  809.             $results $geonames_geocoder->geocode$query );
  810.         }
  811.         if is_wp_error$results or empty$results ) ) {
  812.             self::$geocode_error $results;
  813.         else {
  814.             self::fill_empty_location_fields$location$results[0);
  815.             $status true;
  816.         }
  817.         return $status;
  818.     }
  819.  
  820.     /**
  821.      * Check a location for empty fields.
  822.      *
  823.      * @since 1.4
  824.      *
  825.      * @param array $location The location to check.
  826.      * @param array $fields The fields to check.
  827.      * @return bool Whether any of the specified fields are empty.
  828.      */
  829.     public static function are_any_location_fields_empty$location$fields null {
  830.         if is_array$location ) ) {
  831.             $location = (array)$location;
  832.         }
  833.         if is_null$fields ) ) {
  834.             $fields array_keys$location );
  835.         }
  836.         foreach$fields as $field {
  837.             if empty$location[$field) ) {
  838.                 return true;
  839.             }
  840.         }
  841.         return false;
  842.     }
  843.  
  844.     /**
  845.      * Copy empty fields in one location array from another.
  846.      * 
  847.      * @since 1.4
  848.      * 
  849.      * @param array $primary Location to copy to, modified.
  850.      * @param array $secondary Location to copy from.
  851.      */
  852.     private static function fill_empty_location_fields&$primary$secondary {
  853.         $secondary = (array)$secondary;
  854.         foreach$primary as $field => $value {
  855.             if empty$value and !empty$secondary[$field) ) {
  856.                 if is_object$primary ) )
  857.                     $primary->$field $secondary[$field];
  858.                 else
  859.                     $primary[$field$secondary[$field];
  860.             }
  861.         }
  862.     }
  863.  
  864.     /**
  865.      * Add missing location fields, and update country and admin codes with
  866.      * authoritative Geonames values.
  867.      *
  868.      * @since 1.3
  869.      *
  870.      * @param array $location The location to geocode, modified.
  871.      * @param string $language Optional ISO language code.
  872.      * @return bool Success.
  873.      */
  874.     private static function reverse_geocode_location&$location$language '' {
  875.         global $geo_mashup_options;
  876.  
  877.         // Coordinates are required
  878.         if self::are_any_location_fields_empty$locationarray'lat''lng' ) ) ) 
  879.             return false;
  880.  
  881.         // Don't bother unless there are missing geocodable fields
  882.         $geocodable_fields array'country_code''admin_code''address''locality_name''postal_code' );
  883.         $have_empties self::are_any_location_fields_empty$location$geocodable_fields );
  884.         if $have_empties 
  885.             return false;
  886.  
  887.         $status false;
  888.  
  889.         if !class_exists'GeoMashupHttpGeocoder' ) )
  890.             include_oncepath_joinGEO_MASHUP_DIR_PATH'geo-mashup-geocoders.php' ) );
  891.  
  892.         $geonames_geocoder new GeoMashupGeonamesGeocoder();
  893.         $geonames_results $geonames_geocoder->reverse_geocode$location['lat']$location['lng');
  894.         if is_wp_error$geonames_results or empty$geonames_results ) ) {
  895.             self::$geocode_error $geonames_results;
  896.         else {
  897.             if !empty$geonames_results[0]->admin_code ) )
  898.                 $location['admin_code'$geonames_results[0]->admin_code;
  899.             if !empty$geonames_results[0]->country_code ) )
  900.                 $location['country_code'$geonames_results[0]->country_code;
  901.             self::fill_empty_location_fields$location(array)($geonames_results[0]) );
  902.             $status true;
  903.         }
  904.  
  905.         $have_empties self::are_any_location_fields_empty$location$geocodable_fields );
  906.         if $have_empties {
  907.             // Choose a geocoding service based on the default API in use
  908.             if 'google' == substr$geo_mashup_options->get'overall''map_api' )0) ) {
  909.                 $next_geocoder new GeoMashupGoogleGeocoder();
  910.             else if 'openlayers' == $geo_mashup_options->get'overall''map_api' ) ) {
  911.                 $next_geocoder new GeoMashupNominatimGeocoder();
  912.             }
  913.             $next_results $next_geocoder->reverse_geocode$location['lat']$location['lng');
  914.             if is_wp_error$next_results or empty$next_results ) )
  915.                 self::$geocode_error $next_results;
  916.             else
  917.                 self::fill_empty_location_fields$location(array)($next_results[0]) );
  918.             $status true;
  919.         }
  920.         return $status;
  921.     }
  922.  
  923.     /**
  924.      * Try to reverse-geocode all locations with relevant missing data.
  925.      *
  926.      * Used by the options page. Tries to comply with the PHP maximum execution
  927.      * time, and delay requests if Google sends a 604.
  928.      * 
  929.      * @since 1.3
  930.      * @return string An HTML log of the actions performed.
  931.      */
  932.     public static function bulk_reverse_geocode({
  933.         global $wpdb;
  934.  
  935.         $log date'r' '<br/>';
  936.         $select_string 'SELECT * ' .
  937.             "FROM {$wpdb->prefix}geo_mashup_locations "
  938.             "WHERE country_code IS NULL ".
  939.             "OR admin_code IS NULL " .
  940.             "OR address IS NULL " .
  941.             "OR locality_name IS NULL " .
  942.             "OR postal_code IS NULL ";
  943.         $locations $wpdb->get_results$select_stringARRAY_A );
  944.         if empty$locations ) ) {
  945.             $log .= __'No locations found with missing address fields.''GeoMashup' '<br/>';
  946.             return $log;
  947.         }
  948.         $log .= __'Locations to geocode: ''GeoMashup' count$locations '<br />';
  949.         $time_limit ini_get'max_execution_time' );
  950.         if empty$time_limit || !is_numeric$time_limit ) ) {
  951.             $time_limit 270;
  952.         else {
  953.             $time_limit -= $time_limit 10 );
  954.         }
  955.         $delay 100000// one tenth of a second
  956.         $log .= __'Time limit: ''GeoMashup' $time_limit '<br />';
  957.         $start_time time();
  958.         foreach $locations as $location {
  959.             $set_id self::set_location$locationtrue );
  960.             if is_wp_error$set_id ) ) {
  961.                 $log .= 'error: ' $set_id->get_error_message(' ';
  962.             else {
  963.                 $log .= 'id: ' $location['id'' '
  964.             }
  965.             if !emptyself::$geocode_error ) ) {
  966.                 $delay += 100000;
  967.                 $log .= __'Lookup error:''GeoMashup' ' ' self::$geocode_error->get_error_message(.
  968.                         ' ' __'Increasing delay to''GeoMashup' ' ' $delay 1000000 .
  969.                         '<br/>';
  970.             else if isset$location['address') ) {
  971.                 $log .= __'address''GeoMashup' ': ' $location['address'];
  972.                 if isset$location['postal_code') ) 
  973.                     $log .= ' ' .  __'postal code''GeoMashup' ': ' $location['postal_code'];
  974.                 $log .= '<br />';
  975.             else {
  976.                 $log .= '(' .$location['lat'', ' $location['lng'') ' 
  977.                         __'No address info found.''GeoMashup' .  '<br/>';
  978.             }
  979.             if time($start_time $time_limit {
  980.                 $log .= __'Time limit exceeded, retry to continue.''GeoMashup' '<br />';
  981.                 break;
  982.             }
  983.             usleep$delay );
  984.         }
  985.         return $log;
  986.     }
  987.             
  988.     /**
  989.      * Store an administrative name in the database to prevent future web service lookups.
  990.      * 
  991.      * @since 1.2
  992.      *
  993.      * @param string $country_code 
  994.      * @param string $admin_code 
  995.      * @param string $isolanguage 
  996.      * @param string $name 
  997.      * @param string $geoname_id 
  998.      * @return int Rows affected.
  999.      */
  1000.     public static function cache_administrative_name$country_code$admin_code$isolanguage$name$geoname_id null {
  1001.         global $wpdb;
  1002.  
  1003.         $table_name $wpdb->prefix 'geo_mashup_administrative_names';
  1004.         $cached_name self::get_cached_administrative_name$country_code$admin_code$isolanguage );
  1005.         $rows 0;
  1006.         if empty$cached_name ) ) {
  1007.             $rows $wpdb->insert$table_namecompact'country_code''admin_code''isolanguage''name''geoname_id' ) );
  1008.         else if $cached_name != $name {
  1009.             $rows $wpdb->update$table_namecompact'name' )compact'country_code''admin_code''name' ) );
  1010.         }
  1011.         return $rows;
  1012.     }
  1013.  
  1014.     /**
  1015.      * Copy missing geo data to and from the standard location (http://codex.wordpress.org/Geodata)
  1016.      * for posts, users, and comments.
  1017.      *
  1018.      * @since 1.4
  1019.      * @return bool True if no more orphan locations can be found.
  1020.      */
  1021.     public static function duplicate_geodata({
  1022.         self::duplicate_geodata_type'post' );
  1023.         self::duplicate_geodata_type'user' );
  1024.         self::duplicate_geodata_type'comment' );
  1025.         self::activation_log__'Geodata duplication done.''GeoMashup' )true );
  1026.     }
  1027.  
  1028.     /**
  1029.      * Copy missing geo data to and from the standard location (http://codex.wordpress.org/Geodata)
  1030.      * for a specific object type.
  1031.      *
  1032.      * @since 1.4
  1033.      *
  1034.      * @global object $wpdb 
  1035.      * @param string $meta_type One of the WP meta types, 'post', 'user', 'comment'
  1036.      * @return bool True if no more orphan locations can be found.
  1037.      */
  1038.     private static function duplicate_geodata_type$meta_type {
  1039.         global $wpdb;
  1040.         $object_storage self::object_storage$meta_type );
  1041.         $meta_type $wpdb->escape$meta_type );
  1042.         $meta_type_id $meta_type '_id';
  1043.         $meta_table $meta_type 'meta';
  1044.         // Copy from meta table to geo mashup
  1045.         // NOT EXISTS doesn't work in MySQL 4, use left joins instead
  1046.         $meta_select "SELECT pmlat.{$meta_type_id} as object_id, pmlat.meta_value as lat, pmlng.meta_value as lng, pmaddr.meta_value as address, o.{$object_storage['date_column']} as object_date
  1047.             FROM {$object_storage['table']} o
  1048.             INNER JOIN {$wpdb->$meta_table} pmlat ON pmlat.{$meta_type_id} = o.{$object_storage['id_column']} AND pmlat.meta_key = 'geo_latitude'
  1049.             INNER JOIN {$wpdb->$meta_table} pmlng ON pmlng.{$meta_type_id} = o.{$object_storage['id_column']} AND pmlng.meta_key = 'geo_longitude'
  1050.             LEFT JOIN {$wpdb->$meta_table} pmaddr ON pmaddr.{$meta_type_id} = o.{$object_storage['id_column']} AND pmaddr.meta_key = 'geo_address'
  1051.             LEFT JOIN {$wpdb->prefix}geo_mashup_location_relationships gmlr ON gmlr.object_id = o.{$object_storage['id_column']} AND gmlr.object_name = '{$meta_type}'
  1052.             WHERE pmlat.meta_key = 'geo_latitude' 
  1053.             AND gmlr.object_id IS NULL";
  1054.  
  1055.         $wpdb->query$meta_select );
  1056.  
  1057.         if ($wpdb->last_error{
  1058.             self::activation_log$wpdb->last_error );
  1059.             return false;
  1060.         }
  1061.  
  1062.         $unconverted_metadata $wpdb->last_result;
  1063.         if $unconverted_metadata {
  1064.             $msg sprintf__'Copying missing %s geodata from WordPress''GeoMashup' )$meta_type );
  1065.             self::activation_log$msg );
  1066.             $start_time time();
  1067.             foreach $unconverted_metadata as $objectmeta {
  1068.                 $object_id $objectmeta->object_id;
  1069.                 $location array'lat' => trim$objectmeta->lat )'lng' => trim$objectmeta->lng )'address' => trim$objectmeta->address ) );
  1070.                 $do_lookups ( ( time($start_time 10 true false;
  1071.                 $set_id self::set_object_location$meta_type$object_id$location$do_lookups$objectmeta->object_date );
  1072.                 if $set_id {
  1073.                     self::activation_log'OK: ' $meta_type ' id ' $object_id );
  1074.                 else {
  1075.                     $msg sprintf__'Failed to duplicate WordPress location (%s). You can edit %s with id %s ' .
  1076.                         'to update the location, and try again.''GeoMashup' ),
  1077.                         $objectmeta->lat ',' $objectmeta->lng$meta_type$object_id );
  1078.                     self::activation_log$msgtrue );
  1079.                 }
  1080.             }
  1081.         }
  1082.  
  1083.         // Copy from Geo Mashup to missing object meta
  1084.         // NOT EXISTS doesn't work in MySQL 4, use left joins instead
  1085.         $geomashup_select "SELECT gmlr.object_id, gml.lat, gml.lng, gml.address
  1086.             FROM {$wpdb->prefix}geo_mashup_locations gml
  1087.             INNER JOIN {$wpdb->prefix}geo_mashup_location_relationships gmlr ON gmlr.location_id = gml.id
  1088.             LEFT JOIN {$wpdb->$meta_table} pmlat ON pmlat.{$meta_type_id} = gmlr.object_id AND pmlat.meta_key = 'geo_latitude'
  1089.             WHERE gmlr.object_name = '{$meta_type}'
  1090.             AND pmlat.{$meta_type_id} IS NULL";
  1091.  
  1092.         $wpdb->query$geomashup_select );
  1093.  
  1094.         if ($wpdb->last_error{
  1095.             self::activation_log$wpdb->last_errortrue );
  1096.             return false;
  1097.         }
  1098.  
  1099.         $unconverted_geomashup_objects $wpdb->last_result;
  1100.         if $unconverted_geomashup_objects {
  1101.             $msg sprintf__'Copying missing %s geodata from Geo Mashup''GeoMashup' )$meta_type );
  1102.             self::activation_logdate'r' ' ' $msg );
  1103.             $start_time time();
  1104.             foreach $unconverted_geomashup_objects as $location {
  1105.                 $lat_success update_metadata$meta_type$location->object_id'geo_latitude'$location->lat );
  1106.                 $lng_success update_metadata$meta_type$location->object_id'geo_longitude'$location->lng );
  1107.                 if empty$location->address ) ) {
  1108.                     update_metadata$meta_type$location->object_id'geo_address'$location->address );
  1109.                 }
  1110.                 if $lat_success and $lng_success {
  1111.                     self::activation_log'OK: ' $meta_type ' id ' $location->object_id );
  1112.                 else {
  1113.                     $msg sprintf__'Failed to duplicate Geo Mashup location for %s (%s).''GeoMashup' )$meta_type$location->object_id );
  1114.                     self::activation_log$msg );
  1115.                 }
  1116.             }
  1117.         }
  1118.  
  1119.         $wpdb->query$meta_select );
  1120.  
  1121.         return empty$wpdb->last_result ) );
  1122.     }
  1123.  
  1124.     /**
  1125.      * Convert Geo plugin locations to Geo Mashup format.
  1126.      *
  1127.      * @since 1.2
  1128.      * @return bool True if no more unconverted locations can be found.
  1129.      */
  1130.     private static function convert_prior_locations{
  1131.         global $wpdb;
  1132.  
  1133.         // NOT EXISTS doesn't work in MySQL 4, use left joins instead
  1134.         $unconverted_select "SELECT pm.post_id, pm.meta_value
  1135.             FROM {$wpdb->postmeta} pm
  1136.             LEFT JOIN {$wpdb->postmeta} lpm ON lpm.post_id = pm.post_id 
  1137.             AND lpm.meta_key = '_geo_converted'
  1138.             LEFT JOIN {$wpdb->prefix}geo_mashup_location_relationships gmlr ON gmlr.object_id = pm.post_id
  1139.             AND gmlr.object_name = 'post'
  1140.             WHERE pm.meta_key = '_geo_location' 
  1141.             AND length( pm.meta_value ) > 1
  1142.             AND lpm.post_id IS NULL 
  1143.             AND gmlr.object_id IS NULL";
  1144.  
  1145.         $wpdb->query$unconverted_select );
  1146.  
  1147.         if ($wpdb->last_error{
  1148.             self::activation_log$wpdb->last_errortrue );
  1149.             return false;
  1150.         }
  1151.  
  1152.         $unconverted_metadata $wpdb->last_result;
  1153.         if $unconverted_metadata {
  1154.             $msg __'Converting old locations''GeoMashup' );
  1155.             self::activation_logdate'r' ' ' $msg );
  1156.             $start_time time();
  1157.             foreach $unconverted_metadata as $postmeta {
  1158.                 $post_id $postmeta->post_id;
  1159.                 list$lat$lng split','$postmeta->meta_value );
  1160.                 $location array'lat' => trim$lat )'lng' => trim$lng ) );
  1161.                 $do_lookups ( ( time($start_time 10 true false;
  1162.                 $set_id self::set_object_location'post'$post_id$location$do_lookups );
  1163.                 if $set_id {
  1164.                     add_post_meta$post_id'_geo_converted'$wpdb->prefix 'geo_mashup_locations.id = ' $set_id );
  1165.                     self::activation_log'OK: post_id ' $post_id );
  1166.                 else {
  1167.                     $msg sprintf__'Failed to convert location (%s). You can %sedit the post%s ' .
  1168.                         'to update the location, and try again.''GeoMashup' ),
  1169.                         $postmeta->meta_value'<a href="post.php?action=edit&post=' $post_id '">''</a>');
  1170.                     self::activation_log$msg );
  1171.                 }
  1172.             }
  1173.         }
  1174.  
  1175.         $geo_locations get_option'geo_locations' );
  1176.         if is_array$geo_locations ) ) {
  1177.             $msg __'Converting saved locations''GeoMashup' );
  1178.             self::activation_log$msg );
  1179.             foreach $geo_locations as $saved_name => $coordinates {
  1180.                 list$lat$lng$converted split','$coordinates );
  1181.                 $location array'lat' => trim$lat )'lng' => trim$lng )'saved_name' => $saved_name );
  1182.                 $do_lookups ( ( time($start_time 15 true false;
  1183.                 $set_id self::set_location$location$do_lookups );
  1184.                 if is_wp_error$set_id ) ) {
  1185.                     $geo_locations[$saved_name.= ',' $wpdb->prefix 'geo_mashup_locations.id=' $set_id;
  1186.                     $msg __'OK: ''GeoMashup' $saved_name '<br/>';
  1187.                     self::activation_log$msg );
  1188.                 else {
  1189.                     $msg $saved_name ' - ' 
  1190.                         sprintf__"Failed to convert saved location (%s). " .
  1191.                             "You'll have to save it again, sorry."'GeoMashup' ),
  1192.                         $coordinates );
  1193.                     self::activation_log$set_id->get_error_message() );
  1194.                     self::activation_log$msg );
  1195.                 }
  1196.             }
  1197.             update_option'geo_locations'$geo_locations );
  1198.         }
  1199.  
  1200.         $geo_date_update "UPDATE {$wpdb->prefix}geo_mashup_location_relationships gmlr, $wpdb->posts p .
  1201.             "SET gmlr.geo_date = p.post_date " .
  1202.             "WHERE gmlr.object_name='post' " .
  1203.             "AND gmlr.object_id = p.ID " .
  1204.             "AND gmlr.geo_date = '0000-00-00 00:00:00'";
  1205.  
  1206.         $geo_date_count $wpdb->query$geo_date_update );
  1207.  
  1208.         if $geo_date_count === false {
  1209.             $msg __'Failed to initialize geo dates from post dates: ''GeoMashup' );
  1210.             $msg .= $wpdb->last_error;
  1211.         else {
  1212.             $msg sprintf__'Initialized %d geo dates from corresponding post dates.''GeoMashup' )$geo_date_count );
  1213.         }
  1214.  
  1215.         self::activation_log$msgtrue );
  1216.  
  1217.         $wpdb->query$unconverted_select );
  1218.  
  1219.         return empty$wpdb->last_result ) );
  1220.     }
  1221.  
  1222.     /**
  1223.      * Get a blank location.
  1224.      *
  1225.      * Used to return object fields too - use blank_object_location for that if desired.
  1226.      *
  1227.      * @since 1.2
  1228.      * 
  1229.      * @param string $format OBJECT or ARRAY_A
  1230.      * @return array|object Empty  location.
  1231.      */
  1232.     public static function blank_location$format OBJECT {
  1233.         global $wpdb;
  1234.         static $blank_location null;
  1235.         if is_null$blank_location ) ) {
  1236.             $wpdb->query("SELECT * FROM {$wpdb->prefix}geo_mashup_locations WHERE 1=2);
  1237.             $col_info $wpdb->get_col_info();
  1238.             $blank_location array();
  1239.             foreach$col_info as $col_name {
  1240.                 $blank_location[$col_namenull;
  1241.             }
  1242.         }
  1243.         if $format == OBJECT {
  1244.             return (object) $blank_location;
  1245.         else {
  1246.             return $blank_location;
  1247.         }
  1248.     }
  1249.  
  1250.     /**
  1251.      * Get a blank object location.
  1252.      *
  1253.      * @since 1.4
  1254.      *
  1255.      * @param string $format OBJECT or ARRAY_A
  1256.      * @return array|object Empty  object location.
  1257.      */
  1258.     public static function blank_object_location$format OBJECT {
  1259.         global $wpdb;
  1260.         static $blank_object_location null;
  1261.         if is_null$blank_object_location ) ) {
  1262.             $wpdb->query("SELECT * FROM {$wpdb->prefix}geo_mashup_locations gml
  1263.                     JOIN {$wpdb->prefix}geo_mashup_location_relationships gmlr ON gmlr.location_id = gml.id
  1264.                     WHERE 1=2);
  1265.             $col_info $wpdb->get_col_info();
  1266.             $blank_object_location array();
  1267.             foreach$col_info as $col_name {
  1268.                 $blank_object_location[$col_namenull;
  1269.             }
  1270.         }
  1271.         if $format == OBJECT {
  1272.             return (object) $blank_object_location;
  1273.         else {
  1274.             return $blank_object_location;
  1275.         }
  1276.     }
  1277.  
  1278.     /**
  1279.      * Get distinct values of one or more object location fields.
  1280.      *
  1281.      * Can be used to get a list of countries with locations, for example.
  1282.      *
  1283.      * @since 1.2
  1284.      * 
  1285.      * @param string $names Comma separated table field names.
  1286.      * @param array $where Associtive array of conditional field names and values.
  1287.      * @return object WP_DB query results.
  1288.      */
  1289.     public static function get_distinct_located_values$names$where null {
  1290.         global $wpdb;
  1291.  
  1292.         if is_string$names ) ) {
  1293.             $names preg_split'/\s*,\s*/'$names );
  1294.         }
  1295.         $wheres array);
  1296.         foreach$names as $name {
  1297.             $wheres[$wpdb->escape$name ' IS NOT NULL';
  1298.         }
  1299.         $names implode','$names );
  1300.  
  1301.         if is_object$where ) ) {
  1302.             $where = (array) $where;
  1303.         }
  1304.  
  1305.         $select_string 'SELECT DISTINCT ' $wpdb->escape$names "
  1306.             FROM {$wpdb->prefix}geo_mashup_locations gml
  1307.             JOIN {$wpdb->prefix}geo_mashup_location_relationships gmlr ON gmlr.location_id = gml.id";
  1308.  
  1309.         if is_array$where && !empty$where ) ) {
  1310.             foreach $where as $name => $value {
  1311.                 $wheres[$wpdb->escape$name ' = \'' $wpdb->escape$value .'\'';
  1312.             }
  1313.             $select_string .= ' WHERE ' implode' AND '$wheres );
  1314.         }
  1315.         $select_string .= ' ORDER BY ' $wpdb->escape$names );
  1316.  
  1317.         return $wpdb->get_results$select_string );
  1318.     }
  1319.  
  1320.     /**
  1321.      * Get the location of a post.
  1322.      *
  1323.      * @since 1.2
  1324.      * @uses GeoMashupDB::get_object_location()
  1325.      * 
  1326.      * @param id $post_id 
  1327.      * @return object Post location.
  1328.      */
  1329.     public static function get_post_location$post_id {
  1330.         return self::get_object_location'post'$post_id );
  1331.     }
  1332.  
  1333.     /**
  1334.      * Format a query result as an object or array.
  1335.      * 
  1336.      * @since 1.3
  1337.      *
  1338.      * @param object $obj To be formatted.
  1339.      * @param constant $output Format.
  1340.      * @return object|arrayResult.
  1341.      */
  1342.     private static function translate_object$obj$output OBJECT {
  1343.         if !is_object$obj ) ) {
  1344.             return $obj;
  1345.         }
  1346.  
  1347.         if $output == OBJECT {
  1348.         elseif $output == ARRAY_A {
  1349.             $obj get_object_vars($obj);
  1350.         elseif $output == ARRAY_N {
  1351.             $obj array_values(get_object_vars($obj));
  1352.         }
  1353.         return $obj;
  1354.     }
  1355.  
  1356.     /**
  1357.      * Get the location of an object.
  1358.      * 
  1359.      * @since 1.3
  1360.      *
  1361.      * @param string $object_name 'post', 'user', a GeoMashupDB::object_storage() index.
  1362.      * @param id $object_id Object
  1363.      * @param string $output (optional) one of ARRAY_A | ARRAY_N | OBJECT constants.  Return an
  1364.      *          associative array (column => value, ...), a numerically indexed array (0 => value, ...)
  1365.      *          or an object ( ->column = value ), respectively.
  1366.      * @return object|arrayResult or null if not found.
  1367.      */
  1368.     public static function get_object_location$object_name$object_id$output OBJECT {
  1369.         global $wpdb;
  1370.  
  1371.         $cache_id $object_name '-' $object_id;
  1372.  
  1373.         $object_location wp_cache_get$cache_id'geo_mashup_object_locations' );
  1374.         if !$object_location {
  1375.             $select_string "SELECT * 
  1376.                 FROM {$wpdb->prefix}geo_mashup_locations gml
  1377.                 JOIN {$wpdb->prefix}geo_mashup_location_relationships gmlr ON gmlr.location_id = gml.id .
  1378.                 $wpdb->prepare'WHERE gmlr.object_name = %s AND gmlr.object_id = %d'$object_name$object_id );
  1379.  
  1380.             $object_location $wpdb->get_row$select_string );
  1381.             wp_cache_add$cache_id$object_location'geo_mashup_object_locations' );
  1382.         }
  1383.         return self::translate_object$object_location$output );
  1384.     }
  1385.     
  1386.     /**
  1387.      * Get a location by ID.
  1388.      * 
  1389.      * @since 1.4
  1390.      * 
  1391.      * @param int $location_id 
  1392.      * @param string $output (optional) one of ARRAY_A | ARRAY_N | OBJECT constants.  Return an
  1393.      *          associative array (column => value, ...), a numerically indexed array (0 => value, ...)
  1394.      *          or an object ( ->column = value ), respectively.
  1395.      * @return object|arrayResult or null if not found.
  1396.      */
  1397.     public static function get_location$location_id$output OBJECT {
  1398.         global $wpdb;
  1399.  
  1400.         $location wp_cache_get$location_id'geo_mashup_locations' );
  1401.         if !$location {
  1402.             $location $wpdb->get_row$wpdb->prepare"SELECT * FROM {$wpdb->prefix}geo_mashup_locations WHERE id = %d"$location_id ) );
  1403.             wp_cache_add$location_id$location'geo_mashup_locations' );
  1404.         }
  1405.         return self::translate_object$location$output );
  1406.     }
  1407.  
  1408.     /**
  1409.      * Get locations of posts.
  1410.      * 
  1411.      * @since 1.2
  1412.      * @uses GeoMashupDB::get_object_locations()
  1413.      * 
  1414.      * @param string $query_args Same as GeoMashupDB::get_object_locations()
  1415.      * @return array Array of matching rows.
  1416.      */
  1417.     public static function get_post_locations$query_args '' {
  1418.         return self::get_object_locations$query_args );
  1419.     }
  1420.  
  1421.     /**
  1422.      * Get locations of objects.
  1423.      *
  1424.      * <code>
  1425.      * $results = GeoMashupDB::get_object_locations( array(
  1426.      *     'object_name' => 'user',
  1427.      *     'map_cat' => '3,4,8',
  1428.      *     'minlat' => 30,
  1429.      *     'maxlat' => 40,
  1430.      *     'minlon' => -106,
  1431.      *     'maxlat' => -103 )
  1432.      * );
  1433.      * </code>
  1434.      * 
  1435.      * @since 1.3
  1436.      *
  1437.      * @param string $query_args Override default args.
  1438.      * @return array Array of matching rows.
  1439.      */
  1440.     public static function get_object_locations$query_args '' {
  1441.         global $wpdb;
  1442.  
  1443.         $default_args array
  1444.             'minlat' => null
  1445.             'maxlat' => null
  1446.             'minlon' => null
  1447.             'maxlon' => null,
  1448.             'radius_km' => null,
  1449.             'radius_mi' => null,
  1450.             'map_cat' => null,
  1451.             'map_post_type' => 'any',
  1452.             'object_name' => 'post',
  1453.             'show_future' => 'false'
  1454.             'suppress_filters' => false,
  1455.              'limit' => );
  1456.         $query_args wp_parse_args$query_args$default_args );
  1457.         
  1458.         // Construct the query 
  1459.         $object_name $query_args['object_name'];
  1460.         $object_store self::object_storage$object_name );
  1461.         if empty$object_store ) ) {
  1462.             return null;
  1463.         }
  1464.         $field_string "gmlr.object_id, o.{$object_store['label_column']} as label, gml.*";
  1465.         $table_string "{$wpdb->prefix}geo_mashup_locations gml 
  1466.             "INNER JOIN {$wpdb->prefix}geo_mashup_location_relationships gmlr .
  1467.             $wpdb->prepare'ON gmlr.object_name = %s AND gmlr.location_id = gml.id '$object_name .
  1468.             "INNER JOIN {$object_store['table']} o ON o.{$object_store['id_column']} = gmlr.object_id";
  1469.         $wheres array);
  1470.         $groupby '';
  1471.         $having '';
  1472.  
  1473.         if 'post' == $object_name {
  1474.             $field_string .= ', o.post_author';
  1475.             if $query_args['show_future'== 'true' {
  1476.                 $wheres['post_status in ( \'publish\',\'future\' )';
  1477.             else if $query_args['show_future'== 'only' {
  1478.                 $wheres['post_status = \'future\'';
  1479.             else {
  1480.                 $wheres['post_status = \'publish\'';
  1481.             }
  1482.         }
  1483.  
  1484.         // Check for a radius query
  1485.         if empty$query_args['radius_mi') ) {
  1486.             $query_args['radius_km'1.609344 floatval$query_args['radius_mi');
  1487.         }
  1488.         if empty$query_args['radius_km'and is_numeric$query_args['near_lat'and is_numeric$query_args['near_lng') ) {
  1489.             // Earth radius = 6371 km, 3959 mi
  1490.             $near_lat floatval$query_args['near_lat');
  1491.             $near_lng floatval$query_args['near_lng');
  1492.             $radius_km floatval$query_args['radius_km');
  1493.             $field_string .= ", 6371 * 2 * ASIN( SQRT( POWER( SIN( RADIANS( $near_lat - gml.lat ) / 2 ), 2 ) + COS( RADIANS( $near_lat ) ) * COS( RADIANS( gml.lat ) ) * POWER( SIN( RADIANS( $near_lng - gml.lng ) / 2 ), 2 ) ) ) as distance_km";
  1494.             $having "HAVING distance_km < $radius_km";
  1495.             // approx 111 km per degree latitude
  1496.             $query_args['min_lat'$near_lat $radius_km 111 );
  1497.             $query_args['max_lat'$near_lat $radius_km 111 );
  1498.             $query_args['min_lon'$near_lng $radius_km abscosdeg2rad$near_lat ) ) ) 111 ) );
  1499.             $query_args['max_lon'$near_lng $radius_km abscosdeg2rad$near_lat ) ) ) 111 ) );
  1500.         }
  1501.  
  1502.         // Ignore nonsense bounds
  1503.         if $query_args['minlat'&& $query_args['maxlat'&& $query_args['minlat'$query_args['maxlat'{
  1504.             $query_args['minlat'$query_args['maxlat'null;
  1505.         }
  1506.         if $query_args['minlon'&& $query_args['maxlon'&& $query_args['minlon'$query_args['maxlon'{
  1507.             $query_args['minlon'$query_args['maxlon'null;
  1508.         }
  1509.  
  1510.         // Build bounding where clause
  1511.         if is_numeric$query_args['minlat') ) $wheres["lat > {$query_args['minlat']}";
  1512.         if is_numeric$query_args['minlon') ) $wheres["lng > {$query_args['minlon']}";
  1513.         if is_numeric$query_args['maxlat') ) $wheres["lat < {$query_args['maxlat']}";
  1514.         if is_numeric$query_args['maxlon') ) $wheres["lng < {$query_args['maxlon']}";
  1515.  
  1516.         // Handle inclusion and exclusion of categories
  1517.         if empty$query_args['map_cat') ) {
  1518.  
  1519.             $cats preg_split'/[,\s]+/'$query_args['map_cat');
  1520.  
  1521.             $escaped_include_ids array();
  1522.             $escaped_include_slugs array();
  1523.             $escaped_exclude_ids array();
  1524.  
  1525.             foreach$cats as $cat {
  1526.  
  1527.                 if is_numeric$cat ) ) {
  1528.  
  1529.                     if $cat {
  1530.                         $escaped_exclude_ids[abs$cat );
  1531.                         $escaped_exclude_ids array_merge$escaped_exclude_idsget_term_children$cat'category' ) );
  1532.                     else {
  1533.                         $escaped_include_ids[intval$cat );
  1534.                         $escaped_include_ids array_merge$escaped_include_idsget_term_children$cat'category' ) );
  1535.                     }
  1536.  
  1537.                 else {
  1538.  
  1539.                     // Slugs might begin with a dash, so we only include them
  1540.                     $term get_term_by'slug'$cat'category' );
  1541.                     if $term {
  1542.                         $escaped_include_ids[$term->term_id;
  1543.                         $escaped_include_ids array_merge$escaped_include_idsget_term_children$term->term_id'category' ) );
  1544.                     }
  1545.                 }
  1546.             
  1547.  
  1548.             $table_string .= " JOIN $wpdb->term_relationships tr ON tr.object_id = gmlr.object_id ";
  1549.  
  1550.             if empty$escaped_include_ids ) ) {
  1551.                 $term_tax_ids $wpdb->get_col(
  1552.                         "SELECT term_taxonomy_id FROM $wpdb->term_taxonomy .
  1553.                         "WHERE taxonomy = 'category' AND term_id IN (" .
  1554.                         implode','$escaped_include_ids ')'
  1555.                 );
  1556.                 $wheres['tr.term_taxonomy_id IN (' implode','$term_tax_ids ')';
  1557.             }
  1558.  
  1559.             if empty$escaped_exclude_ids ) ) {
  1560.                 $term_tax_ids $wpdb->get_col(
  1561.                         "SELECT term_taxonomy_id FROM $wpdb->term_taxonomy .
  1562.                         "WHERE taxonomy = 'category' AND term_id IN (" .
  1563.                         implode','$escaped_exclude_ids ')'
  1564.                 );
  1565.                 $wheres["o.ID NOT IN ( " .
  1566.                         "SELECT object_id " .
  1567.                         "FROM $wpdb->term_relationships .
  1568.                         "WHERE term_taxonomy_id IN ( " .
  1569.                         implode','$term_tax_ids ') )';
  1570.             }
  1571.  
  1572.             $groupby 'GROUP BY gmlr.object_id';
  1573.         // end if map_cat exists 
  1574.  
  1575.         if 'post' == $object_name {
  1576.             // Handle inclusion and exclusion of post types
  1577.             if 'any' == $query_args['map_post_type'{
  1578.                 $exclude_post_types '';
  1579.                 $in_search_post_types get_post_typesarray('exclude_from_search' => false) );
  1580.  
  1581.                 if empty$in_search_post_types ) )
  1582.                     $exclude_post_types .= $wpdb->prepare("o.post_type IN ('" join("', '"$in_search_post_types "')");
  1583.  
  1584.                 $wheres[$exclude_post_types;
  1585.             else {
  1586.                 $post_types preg_split'/[,\s]+/'$query_args['map_post_type');
  1587.                 $wheres["o.post_type IN ('" join("', '"$post_types"')";
  1588.             }
  1589.         
  1590.  
  1591.         if empty$query_args['object_id') ) {
  1592.             $wheres['gmlr.object_id = ' $wpdb->escape$query_args['object_id');
  1593.         else if empty$query_args['object_ids') ) {
  1594.             $wheres['gmlr.object_id in ( ' $wpdb->escape$query_args['object_ids'.' )';
  1595.         }
  1596.  
  1597.         $no_where_fields array'object_name''object_id''geo_date' );
  1598.         foreach self::blank_object_locationARRAY_A as $field => $blank {
  1599.             if !in_array$field$no_where_fields&& isset$query_args[$field) ) {
  1600.                 $wheres[$wpdb->prepare"gml.$field = %s"$query_args[$field);
  1601.             }
  1602.         }
  1603.  
  1604.         $where empty$wheres ) ) '' :  'WHERE ' implode' AND '$wheres )
  1605.         $sort isset$query_args['sort') ) $query_args['sort'$object_store['sort'];
  1606.         $sort empty$sort ) ) '' 'ORDER BY ' $wpdb->escape$sort );
  1607.         $limit is_numeric$query_args['limit'&& $query_args['limit']>" LIMIT 0,{$query_args['limit']}'';
  1608.  
  1609.         if $query_args['suppress_filters'{
  1610.             $field_string    apply_filters'geo_mashup_locations_fields'$field_string );
  1611.             $table_string apply_filters'geo_mashup_locations_join'$table_string );
  1612.             $where apply_filters'geo_mashup_locations_where'$where );
  1613.             $sort apply_filters'geo_mashup_locations_orderby'$sort );
  1614.             $groupby apply_filters'geo_mashup_locations_groupby'$groupby );
  1615.             $limit apply_filters'geo_mashup_locations_limits'$limit );
  1616.         }
  1617.         
  1618.         $query_string "SELECT $field_string FROM $table_string $where $groupby $having $sort $limit";
  1619.  
  1620.         $wpdb->query$query_string );
  1621.         
  1622.         return $wpdb->last_result;
  1623.     }
  1624.  
  1625.     /**
  1626.      * Save an object location in the database.
  1627.      *
  1628.      * Object data is saved in the geo_mashup_location_relationships table, and
  1629.      * location data is saved in geo_mashup_locations.
  1630.      * 
  1631.      * @since 1.3
  1632.      * @uses do_action() Calls 'geo_mashup_added_object_location' with the object name,
  1633.      *        object id, geo date, and location array
  1634.      * @uses do_action() Calls 'geo_mashup_updated_object_location' with the object name,
  1635.      *        object id, geo date, and location array
  1636.      *
  1637.      * @param string $object_name 'post', 'user', a GeoMashupDB::object_storage() index.
  1638.      * @param id $object_id ID of the object to save the location for.
  1639.      * @param id|array$location If an ID, the location is not modified. If an array of valid location fields,
  1640.      *          the location is added or updated. If empty, the object location is deleted.
  1641.      * @param bool $do_lookups Whether to try looking up missing location information, which can take extra time.
  1642.      *          Default is to use the saved option.
  1643.      * @param string $geo_date Optional geo date to associate with the object.
  1644.      * @return id|WP_ErrorThe location ID now assiociated with the object.
  1645.      */
  1646.     public static function set_object_location$object_name$object_id$location$do_lookups null$geo_date '' {
  1647.         global $wpdb;
  1648.  
  1649.         if is_numeric$location ) ) {
  1650.             $location_id $location;
  1651.         
  1652.  
  1653.         if !isset$location_id ) ) {
  1654.             $location_id self::set_location$location$do_lookups );
  1655.             if is_wp_error$location_id ) ) {
  1656.                 return $location_id;
  1657.             }
  1658.         }
  1659.  
  1660.         if !is_numeric$location_id ) ) {
  1661.             self::delete_object_location$object_name$object_id );
  1662.             return 0;
  1663.         }
  1664.  
  1665.         if empty$geo_date ) ) {
  1666.             $geo_date date'Y-m-d H:i:s' );
  1667.         else {
  1668.             $geo_date date'Y-m-d H:i:s'strtotime$geo_date ) );
  1669.         }
  1670.  
  1671.         $relationship_table "{$wpdb->prefix}geo_mashup_location_relationships"
  1672.         $select_string "SELECT * FROM $relationship_table .
  1673.             $wpdb->prepare'WHERE object_name = %s AND object_id = %d'$object_name$object_id );
  1674.  
  1675.         $db_location $wpdb->get_row$select_stringARRAY_A );
  1676.  
  1677.         $set_id null;
  1678.         if empty$db_location ) ) {
  1679.             if $wpdb->insert$relationship_tablecompact'object_name''object_id''location_id''geo_date' ) ) ) {
  1680.                 $set_id $location_id;
  1681.             else 
  1682.                 return new WP_Error'db_insert_error'$wpdb->last_error );
  1683.             }
  1684.             do_action'geo_mashup_added_object_location'$object_name$object_id$geo_date$location_id );
  1685.         else {
  1686.             $wpdb->update$relationship_tablecompact'location_id''geo_date' )compact'object_name''object_id' ) );
  1687.             if $wpdb->last_error 
  1688.                 return new WP_Error'db_update_error'$wpdb->last_error );
  1689.             $set_id $location_id;
  1690.             do_action'geo_mashup_updated_object_location'$object_name$object_id$geo_date$location_id );
  1691.         }
  1692.         return $set_id;
  1693.     }
  1694.  
  1695.     /**
  1696.      * Save a location.
  1697.      *
  1698.      * This can create a new location or update an existing one. If a location exists within 5 decimal
  1699.      * places of the passed in coordinates, it will be updated. If the saved_name of a different location
  1700.      * is given, it will be removed from the other location and saved with this one. Blank fields will not
  1701.      * replace existing data.
  1702.      * 
  1703.      * @since 1.2
  1704.      * @uses do_action() Calls 'geo_mashup_added_location' with the location array added
  1705.      * @uses do_action() Calls 'geo_mashup_updated_location' with the location array updated
  1706.      *
  1707.      * @param array $location Location to save, may be modified to match actual saved data.
  1708.      * @param bool $do_lookups Whether to try to look up address information before saving,
  1709.      *          default is to use the saved option.
  1710.      * @return id|WP_ErrorThe location ID saved, or a WordPress error.
  1711.      */
  1712.     public static function set_location&$location$do_lookups null {
  1713.         global $wpdb$geo_mashup_options;
  1714.  
  1715.         if is_null$do_lookups ) ) {
  1716.             $do_lookups $geo_mashup_options->get'overall''enable_reverse_geocoding' == 'true' );
  1717.         }
  1718.  
  1719.         if is_object$location ) ) {
  1720.             $location = (array) $location;
  1721.         }
  1722.  
  1723.         // Check for existing location ID
  1724.         $location_table $wpdb->prefix 'geo_mashup_locations';
  1725.         $select_string "SELECT id, saved_name FROM $location_table ";
  1726.  
  1727.         // If local has a different floating point format, change it temporarily
  1728.         $changed_locale false;
  1729.         if (string) 1.1 != '1.1' {
  1730.             $original_locale setlocaleconstant'LC_NUMERIC' )null );
  1731.             setlocaleconstant'LC_NUMERIC' )'en_US' );
  1732.         }
  1733.  
  1734.         if isset$location['id'&& is_numeric$location['id') ) {
  1735.  
  1736.             $select_string .= $wpdb->prepare'WHERE id = %d'$location['id');
  1737.  
  1738.         else if isset$location['lat'and is_numeric$location['lat'and isset$location['lng'and is_numeric$location['lng') ) {
  1739.  
  1740.             // The database might round these, but let's be explicit and stymy evildoers too
  1741.             $location['lat'round$location['lat']);
  1742.             $location['lng'round$location['lng']);
  1743.  
  1744.             // MySql appears to only distinguish 5 decimal places, ~8 feet, in the index
  1745.             $delta 0.00001;
  1746.             $select_string .= $wpdb->prepare'WHERE lat BETWEEN %f AND %f AND lng BETWEEN %f AND %f'
  1747.                 $location['lat'$delta$location['lat'$delta$location['lng'$delta$location['lng'$delta );
  1748.  
  1749.         else {
  1750.             if $changed_locale )
  1751.                 setlocaleconstant'LC_NUMERIC' )$original_locale );
  1752.             return new WP_Error'invalid_location'__'A location must have an ID or coordinates to be saved.''GeoMashup' ) );
  1753.         }
  1754.  
  1755.         $db_location $wpdb->get_row$select_stringARRAY_A );
  1756.  
  1757.         $found_saved_name '';
  1758.         if empty$db_location ) ) {
  1759.             // Use the existing ID
  1760.             $location['id'$db_location['id']
  1761.             $found_saved_name $db_location['saved_name'];
  1762.         }
  1763.  
  1764.         // Reverse geocode
  1765.         if $do_lookups {
  1766.             self::reverse_geocode_location$location );
  1767.         }
  1768.  
  1769.         // Don't set blank entries
  1770.         foreach $location as $name => $value {
  1771.             if !is_numeric$value && empty$value ) ) {
  1772.                 unset$location[$name);
  1773.             }
  1774.         }
  1775.  
  1776.         // Replace any existing saved name
  1777.         if empty$location['saved_name'and $found_saved_name != $location['saved_name'{
  1778.             $wpdb->query$wpdb->prepare"UPDATE $location_table SET saved_name = NULL WHERE saved_name = %s"$location['saved_name') );
  1779.         }
  1780.  
  1781.         $set_id null;
  1782.  
  1783.         if empty$location['id') ) {
  1784.  
  1785.             // Create a new location
  1786.             if $wpdb->insert$location_table$location ) ) {
  1787.                 $set_id $wpdb->insert_id;
  1788.             else {
  1789.                 if $changed_locale )
  1790.                     setlocaleconstant'LC_NUMERIC' )$original_locale );
  1791.                 return new WP_Error'db_insert_error'$wpdb->last_error );
  1792.             }
  1793.             do_action'geo_mashup_added_location'$location );
  1794.  
  1795.         else {
  1796.  
  1797.             // Update existing location, except for coordinates
  1798.             $tmp_lat $location['lat']
  1799.             $tmp_lng $location['lng']
  1800.             unset$location['lat');
  1801.             unset$location['lng');
  1802.             if !empty $location ) ) {
  1803.                 $wpdb->update$location_table$locationarray'id' => $db_location['id') );
  1804.                 if $wpdb->last_error {
  1805.                     if $changed_locale )
  1806.                         setlocaleconstant'LC_NUMERIC' )$original_locale );
  1807.                     return new WP_Error'db_update_error'$wpdb->last_error );
  1808.                 }
  1809.             }
  1810.             $set_id $db_location['id'];
  1811.             do_action'geo_mashup_updated_location'$location );
  1812.             $location['lat'$tmp_lat;
  1813.             $location['lng'$tmp_lng;
  1814.  
  1815.         }
  1816.         if $changed_locale )
  1817.             setlocaleconstant'LC_NUMERIC' )$original_locale );
  1818.         return $set_id;
  1819.     }
  1820.  
  1821.     /**
  1822.      * Delete an object location or locations.
  1823.      * 
  1824.      * This removes the association of an object with a location, but does NOT
  1825.      * delete the location.
  1826.      * 
  1827.      * @since 1.3
  1828.      * @uses do_action() Calls 'geo_mashup_deleted_object_location' with the object location
  1829.      *          object that was deleted.
  1830.      *
  1831.      * @param string $object_name 'post', 'user', a GeoMashupDB::object_storage() index.
  1832.      * @param id|array$object_ids Object ID or array of IDs to remove the locations of.
  1833.      * @return int|WP_ErrorRows affected or WordPress error.
  1834.      */
  1835.     public static function delete_object_location$object_name$object_ids {
  1836.         global $wpdb;
  1837.  
  1838.         $object_ids is_array$object_ids $object_ids array$object_ids ) );
  1839.         $rows_affected 0;
  1840.         foreach$object_ids as $object_id {
  1841.             $object_location self::get_object_location$object_name$object_id );
  1842.             if $object_location {
  1843.                 $delete_string "DELETE FROM {$wpdb->prefix}geo_mashup_location_relationships .
  1844.                     $wpdb->prepare'WHERE object_name = %s AND object_id = %d'$object_name$object_id );
  1845.                 $rows_affected += $wpdb->query$delete_string );
  1846.                 if $wpdb->last_error )
  1847.                     return new WP_Error'delete_object_location_error'$wpdb->last_error );
  1848.                 do_action'geo_mashup_deleted_object_location'$object_location );
  1849.             }
  1850.         }
  1851.  
  1852.         return $rows_affected;
  1853.     }
  1854.  
  1855.     /**
  1856.      * Delete a location or locations.
  1857.      *
  1858.      * @since 1.2
  1859.      * @uses do_action() Calls 'geo_mashup_deleted_location' with the location object deleted.
  1860.      * 
  1861.      * @param id|array$ids Location ID or array of IDs to delete.
  1862.      * @return int|WP_ErrorRows affected or Wordpress error.
  1863.      */
  1864.     public static function delete_location$ids {
  1865.         global $wpdb;
  1866.         $ids is_array$ids $ids array$ids ) );
  1867.         $rows_affected 0;
  1868.         foreach$ids as $id {
  1869.             $location self::get_location$id );
  1870.             if $location {
  1871.                 $rows_affected += $wpdb->query$wpdb->prepare"DELETE FROM {$wpdb->prefix}geo_mashup_locations WHERE id = %d"$id ) );
  1872.                 if $wpdb->last_error )
  1873.                     return new WP_Error'delete_location_error'$wpdb->last_error );
  1874.                 do_action'geo_mashup_deleted_location'$location );
  1875.             }
  1876.         }
  1877.         return $rows_affected;
  1878.     }
  1879.  
  1880.     /**
  1881.      * Get locations with saved names.
  1882.      *
  1883.      * @since 1.2
  1884.      *
  1885.      * @return array|WP_ErrorArray of location rows or WP_Error.
  1886.      */
  1887.     public static function get_saved_locations({
  1888.         global $wpdb;
  1889.  
  1890.         $location_table $wpdb->prefix 'geo_mashup_locations';
  1891.         $wpdb->query"SELECT * FROM $location_table WHERE saved_name IS NOT NULL ORDER BY saved_name ASC);
  1892.         if $wpdb->last_error 
  1893.             return new WP_Error'saved_locations_error'$wpdb->last_error );
  1894.  
  1895.         return $wpdb->last_result;
  1896.     }
  1897.  
  1898.     /**
  1899.      * Get the number of located posts in a category.
  1900.      * 
  1901.      * @since 1.2
  1902.      *
  1903.      * @param id $category_id 
  1904.      * @return int 
  1905.      */
  1906.     public static function category_located_post_count$category_id {
  1907.         global $wpdb;
  1908.  
  1909.         $select_string "SELECT count(*) FROM {$wpdb->posts} p 
  1910.             INNER JOIN {$wpdb->term_relationships} tr ON tr.object_id = p.ID 
  1911.             INNER JOIN {$wpdb->term_taxonomy} tt ON tt.term_taxonomy_id = tr.term_taxonomy_id
  1912.             INNER JOIN {$wpdb->prefix}geo_mashup_location_relationships gmlr ON gmlr.object_id = p.ID AND gmlr.object_name = 'post'
  1913.             WHERE tt.term_id = $wpdb->escape$category_id ."
  1914.             AND p.post_status='publish'";
  1915.         return $wpdb->get_var$select_string );
  1916.     }
  1917.  
  1918.     /**
  1919.      * Get categories that contain located objects.
  1920.      *
  1921.      * Not sufficient - probably want parent categories.
  1922.      *
  1923.      * @return array Located category id, name, slug, description, and parent id
  1924.      */
  1925.     private static function get_located_categories({
  1926.         global $wpdb;
  1927.  
  1928.         $select_string "SELECT DISTINCT t.term_id, t.name, t.slug, tt.description, tt.parent
  1929.             FROM {$wpdb->prefix}geo_mashup_location_relationships gmlr
  1930.             INNER JOIN {$wpdb->term_relationships} tr ON tr.object_id = gmlr.object_id
  1931.             INNER JOIN {$wpdb->term_taxonomy} tt ON tt.term_taxonomy_id = tr.term_taxonomy_id
  1932.             INNER JOIN {$wpdb->terms} t ON t.term_id = tt.term_id
  1933.             WHERE tt.taxonomy='category'
  1934.             ORDER BY t.slug ASC";
  1935.         return $wpdb->get_results$select_string );
  1936.     }
  1937.  
  1938.     /**
  1939.      * Get multiple comments.
  1940.      *
  1941.      * What is the WordPress way? Expect deprecation.
  1942.      * 
  1943.      * @return array Comments.
  1944.      */
  1945.     public static function get_comment_in$args {
  1946.         global $wpdb;
  1947.  
  1948.         $args wp_parse_args$args );
  1949.         if is_array$args['comment__in') ) {
  1950.             $comment_ids implode','$args['comment__in');
  1951.         else {
  1952.             $comment_ids isset$args['comment__in') ) $args['comment__in''0';
  1953.         }
  1954.         $select_string "SELECT * FROM $wpdb->comments WHERE comment_ID IN (.
  1955.             $wpdb->prepare$comment_ids ') ORDER BY comment_date_gmt DESC';
  1956.         return $wpdb->get_results$select_string );
  1957.     }
  1958.  
  1959.     /**
  1960.      * Get multiple users.
  1961.      *
  1962.      * What is the WordPress way? Expect deprecation.
  1963.      * 
  1964.      * @return array Users.
  1965.      */
  1966.     public static function get_user_in$args {
  1967.         global $wpdb;
  1968.  
  1969.         $args wp_parse_args$args );
  1970.         if is_array$args['user__in') ) {
  1971.             $user_ids implode','$args['user__in');
  1972.         else {
  1973.             $user_ids isset$args['user__in') ) $args['user__in''0';
  1974.         }
  1975.         $select_string "SELECT * FROM $wpdb->users WHERE ID IN (.
  1976.             $wpdb->prepare$user_ids ') ORDER BY display_name ASC';
  1977.         return $wpdb->get_results$select_string );
  1978.     }
  1979.  
  1980.     /**
  1981.      * When a post is deleted, remove location relationships for it.
  1982.      *
  1983.      * delete_post {@link http://codex.wordpress.org/Plugin_API/Action_Reference action}
  1984.      * called by WordPress.
  1985.      *
  1986.      * @since 1.2
  1987.      */
  1988.     public static function delete_post$id {
  1989.         return self::delete_object_location'post'$id );
  1990.     }
  1991.  
  1992.     /**
  1993.      * When a comment is deleted, remove location relationships for it.
  1994.      *
  1995.      * delete_comment {@link http://codex.wordpress.org/Plugin_API/Action_Reference action}
  1996.      * called by WordPress.
  1997.      *
  1998.      * @since 1.2
  1999.      */
  2000.     public static function delete_comment$id {
  2001.         return self::delete_object_location'comment'$id );
  2002.     }
  2003.  
  2004.     /**
  2005.      * When a user is deleted, remove location relationships for it.
  2006.      *
  2007.      * delete_user {@link http://codex.wordpress.org/Plugin_API/Action_Reference action}
  2008.      * called by WordPress.
  2009.      *
  2010.      * @since 1.2
  2011.      */
  2012.     public static function delete_user$id {
  2013.         return self::delete_object_location'user'$id );
  2014.     }
  2015.  
  2016.     /**
  2017.      * Geo Mashup action to echo post meta keys that match a jQuery suggest query.
  2018.      *
  2019.      * @since 1.4
  2020.      */
  2021.     public static function post_meta_key_suggest({
  2022.         global $wpdb;
  2023.         if isset$_GET['q') ) {
  2024.             $limit = (int) apply_filters'postmeta_form_limit'30 );
  2025.             $like $wpdb->escape$_GET['q');
  2026.             $keys $wpdb->get_col"
  2027.                 SELECT meta_key
  2028.                 FROM $wpdb->postmeta
  2029.                 GROUP BY meta_key
  2030.                 HAVING meta_key LIKE '$like%'
  2031.                 ORDER BY meta_key
  2032.                 LIMIT $limit);
  2033.             foreach$keys as $key {
  2034.                 echo "$key\n";
  2035.             }
  2036.         }
  2037.         exit;
  2038.     }
  2039. }

Documentation generated on Sat, 09 Jul 2011 21:54:39 -0700 by phpDocumentor 1.4.3