diff --git a/.gitignore b/.gitignore index c180ae4..a3b12fd 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,8 @@ vendor/ *.tar.gz hugo-content hugo-images +hugo-groups +geo-api-token.txt # STDOUT files html_text.txt html_tags.txt \ No newline at end of file diff --git a/command.php b/command.php index ee0592d..6e0bc8c 100644 --- a/command.php +++ b/command.php @@ -16,6 +16,7 @@ require_once( HH_HUGO_COMMAND_DIR . '/inc/process-wp-post-content.php' ); require_once( HH_HUGO_COMMAND_DIR . '/inc/migrate-images.php' ); require_once( HH_HUGO_COMMAND_DIR . '/inc/migrate-post.php' ); +require_once( HH_HUGO_COMMAND_DIR . '/inc/migrate-group.php' ); if ( ! class_exists( 'WP_CLI' ) ) { @@ -210,7 +211,7 @@ function markdown( $args ) { function delete_content_dirs( $args, $assoc_args ) { WP_CLI::confirm( 'Are you sure you want to delete the hugo-content and hugo-images directories?' ); - foreach( array( 'hugo-content', 'hugo-images' ) as $dir ) { + foreach( array( 'hugo-content', 'hugo-images', 'hugo-groups' ) as $dir ) { $path = trailingslashit( HH_HUGO_COMMAND_DIR ) . $dir; if ( is_dir( $path ) ) { exec( 'rm -rf ' . escapeshellarg( $path ) ); @@ -228,6 +229,9 @@ function delete_content_dirs( $args, $assoc_args ) { * * : WordPress post ID or slug, or comma-separated list of IDs/slugs * + * [--parent=] + * : Override hard-coded groups parent page ID + * * [--dry-run] * : Simulate without touching any Markdown files or images * @@ -237,12 +241,19 @@ function delete_content_dirs( $args, $assoc_args ) { */ function migrate_group( $args, $assoc_args ) { $dry_run = isset( $assoc_args['dry-run'] ); + $parent_id = is_numeric( $assoc_args['parent'] ) ? intval( $assoc_args['parent'] ) : $this->groups_parent_page_id; - // Convert to array - $groups = explode( ',', $args[0] ); + // Convert to array if needed + if ( is_string( $args[0] ) ) { + $groups = explode( ',', $args[0] ); + } else { + $groups = array( $args[0] ); + } foreach ( $groups as $group ) { - $migrated = new HH_Hugo\Migrate_Group( $group, $dry_run ); + // $group could be post ID, post_name, or WP_Post object + $migrated = new HH_Hugo\Migrate_Group( $group, $parent_id, $dry_run ); + $result = $migrated->result(); // $result will be in format like array( 'success' => 'message' ) if ( 'success' === array_keys( $result )[0] ) { @@ -276,7 +287,6 @@ function migrate_all_groups( $args, $assoc_args ) { $parent_id = is_numeric( $assoc_args['parent'] ) ? intval( $assoc_args['parent'] ) : $this->groups_parent_page_id; $this->query = new WP_Query( array( - 'fields' => 'ids', 'posts_per_page' => 500, 'post_type' => 'page', 'post_status' => 'publish', diff --git a/inc/migrate-group.php b/inc/migrate-group.php new file mode 100644 index 0000000..e4e3bda --- /dev/null +++ b/inc/migrate-group.php @@ -0,0 +1,207 @@ +dest_dir = trailingslashit( HH_HUGO_COMMAND_DIR ) . $content_dir; + + // just return the class for unit testing + if ( empty( $group ) && defined( 'HH_HUGO_UNIT_TESTS_RUNNING' ) ) { + return; + } + + $is_wp_post = is_object( $group ) && 'WP_Post' === get_class( $group ); + $this->page = $is_wp_post ? $group : $this->get_page( $group, $parent_id ); + + $this->data = $this->setup_data( $this->page->ID ); + $this->filename = sanitize_title( $this->page->post_title ) . '.yml'; + + if ( ! $dry_run ) { + $written = $this->write_file( $this->filename, $this->data ); + if ( ! $written ) { + $this->result = array( 'error' => 'Error writing file: ' . $this->filename ); + return; + } + } + $this->result = array( 'success' => 'Migrated ' . $this->filename ); + } + + /** + * Instantiate and migrate + * + * @param string|int $group WordPress post ID or slug + * @param int $parent_id ID of WordPress parent page + * @return WP_Post + */ + public function get_page( $group, $parent_id ) { + $args = array( + 'numberposts' => 1, + 'post_type' => 'page', + 'post_status' => 'publish', + 'post_parent' => $parent_id, + ); + + if ( is_numeric( $group ) ) { + $args['page_id'] = intval( $group ); + } else { + $args['name'] = $group; + } + + $pages = get_posts( $args ); + if ( empty( $pages ) ) { + $this->result = array( 'error' => 'Could not find group page for: ' . $group ); + return null; + } + + return $pages[0]; + } + + /** + * Setup data for conversion to YAML + * + * @param int $page Page ID to migrate + * @return array + */ + public function setup_data( $page_id ) { + $title = get_the_title( $page_id ); + $data = array( + 'label' => $title, + ); + + if ( $coordinates = $this->get_coordinates( $title ) ) { + $data['coordinates'] = $coordinates; + } + + if ( $external_url = $this->get_external_url( $page_id ) ) { + $data['externalUrl'] = $external_url; + } + + return $data; + } + + /** + * Turn city / post title into search query + * + * @param string $city + * @return string + */ + public function city_search( $city ) { + $city = preg_replace( '/[^\w]+/', '+', strtolower( $city ) ); + return trim( $city, '+' ); + } + + /** + * Get lat,lon for city name + * + * @param string $city + * @return null|array Lat,Lon array or null if failure + */ + public function get_coordinates( $city ) { + $token_path = trailingslashit( HH_HUGO_COMMAND_DIR ) . 'geo-api-token.txt'; + if ( ! file_exists( $token_path ) ) { + return null; + } + $token = file_get_contents( $token_path ); + if ( empty( $token ) ) { + return null; + } + + $request_url = sprintf( $this->geo_api_url, $this->city_search( $city ), $token ); + $response = wp_remote_get( $request_url ); + + if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { + return null; + } + + $body = json_decode( wp_remote_retrieve_body( $response ), true ); + + if ( empty( $body['features'][0]['center'] ) ) { + return null; + } + + // Carmen GeoJSON provides [lon, lat] but Leaflet.js wants [lat, lon] + return array_reverse( $body['features'][0]['center'] ); + } + + /** + * Get external URL from options stored in Simple 301 Redirects plugin + * + * @param int $page_id + * @return string|null URL or null if not found + */ + public function get_external_url( $page_id ) { + $redirects = get_option( '301_redirects', array() ); + + // search in redirects with and without leading slash + $uri = get_page_uri( $page_id ); + if ( ! empty( $redirects[ $uri ] ) ) { + return $redirects[ $uri ]; + } + + if ( ! empty( $redirects[ '/' . $uri ] ) ) { + return $redirects[ '/' . $uri ]; + } + + return null; + } + + /** + * Emit yaml with a little bit of processing for Hugo's sake + * + * @param array $data Group data + * @return string YAML data + */ + public function get_yaml( $data ) { + $yaml = yaml_emit( $data, YAML_UTF8_ENCODING ); + return trim( preg_replace( array( '/^-+\n/', '/\.+$/'), '', $yaml ) ); + } + + /** + * Write group data file + * + * @param string $filename + * @param array $data + * @return bool + */ + public function write_file( $filename, $data ) { + if ( ! is_dir( $this->dest_dir ) ) { + mkdir( $this->dest_dir, 0755, true ); + } + + return file_put_contents( + trailingslashit( $this->dest_dir ) . $filename, + $this->get_yaml( $data ) + ); + } + + /** + * Getter for migration results + * + * @return array + */ + public function result() { + return $this->result; + } +} diff --git a/tests/test-migrate-group.php b/tests/test-migrate-group.php new file mode 100644 index 0000000..047198d --- /dev/null +++ b/tests/test-migrate-group.php @@ -0,0 +1,68 @@ +migrator = new HH_Hugo\Migrate_Group( null ); + + $this->parent_post = wp_insert_post( array( + 'post_title' => 'Local Groups', + 'post_name' => 'chapters', + 'post_type' => 'page', + 'post_status' => 'publish', + ) ); + + $this->group_post = wp_insert_post( array( + 'post_title' => 'Atlanta', + 'post_name' => 'atlanta', + 'post_type' => 'page', + 'post_status' => 'publish', + 'post_parent' => $this->parent_post, + ) ); + + update_option( '301_redirects', array( '/chapters/atlanta' => 'https://www.meetup.com/HacksHackersATL/' ) ); + } + + function tearDown() { + wp_delete_post( $this->parent_post, true ); + wp_delete_post( $this->group_post, true ); + } + + /** + * Get contents of test data file + * + * @param string $type 'test' or 'expect' + * @param string $filename + * @return string Contents of file + */ + private function _get_test_data( $type, $filename ) { + return file_get_contents( HH_HUGO_COMMAND_DIR . '/tests/data/' . $type . '/' . $filename ); + } + + public function test_setup_data() { + $expect = array( + 'label' => 'Atlanta', + 'coordinates' => array( 33.749100, -84.390200 ), + 'externalUrl' => 'https://www.meetup.com/HacksHackersATL/' + ); + $this->assertEquals( $expect, $this->migrator->setup_data( $this->group_post ) ); + } + + public function test_get_yaml() { + $data = $this->migrator->setup_data( $this->group_post ); + $actual = $this->migrator->get_yaml( $data ); + $expected = "label: Atlanta\ncoordinates:\n- 33.749100\n- -84.390200\nexternalUrl: https://www.meetup.com/HacksHackersATL/"; + $this->assertEquals( $expected, $actual ); + } + + public function test_city_search() { + $tests = array( + array( 'Atlanta', 'atlanta' ), + array( 'Mexico City', 'mexico+city' ), + array( 'Raleigh/Durham', 'raleigh+durham' ), + array( 'Amman, Jordan', 'amman+jordan' ), + array( 'Research Triangle (Chapel Hill/Durham/Raleigh, N.C.)', 'research+triangle+chapel+hill+durham+raleigh+n+c' ), + ); + foreach ( $tests as $test ) { + $this->assertEquals( $test[1], $this->migrator->city_search( $test[0] ) ); + } + } +}