├── languages ├── wp-github-sync-de_DE.mo ├── wp-github-sync-zh_CN.mo ├── wp-github-sync-zh_CN.po └── wp-github-sync.pot ├── tests ├── data │ ├── post_commits_fail.json │ ├── post_trees_fail.json │ ├── get_refs_heads_master_fail.json │ ├── patch_refs_heads_master_fail.json │ ├── get_blobs_2d73165945b0ccbe4932f1363457986b0ed49f19_fail.json │ ├── get_blobs_8d9b2e6fd93761211dc03abd71f4a9189d680fd0_fail.json │ ├── get_blobs_9fa5c7537f8582b71028ff34b8c20dfd0f3b2a25_fail.json │ ├── get_commits_db2510854e6aeab68ead26b48328b19f4bdf926e_fail.json │ ├── get_trees_9108868e3800bec6763e51beb0d33e15036c3626_fail.json │ ├── get_blobs_9fa5c7537f8582b71028ff34b8c20dfd0f3b2a25_succeed.json │ ├── get_refs_heads_master_succeed.json │ ├── patch_refs_heads_master_succeed.json │ ├── get_blobs_2d73165945b0ccbe4932f1363457986b0ed49f19_succeed.json │ ├── post_trees_succeed.json │ ├── post_commits_succeed.json │ ├── get_commits_db2510854e6aeab68ead26b48328b19f4bdf926e_succeed.json │ ├── get_trees_9108868e3800bec6763e51beb0d33e15036c3626_succeed.json │ ├── get_blobs_8d9b2e6fd93761211dc03abd71f4a9189d680fd0_succeed.json │ ├── payload-valid.json │ ├── payload-synced-commit.json │ ├── payload-invalid-branch.json │ └── payload-invalid-repo.json ├── unit │ ├── test-cli.php │ ├── test-main.php │ ├── test-api.php │ ├── test-response.php │ ├── test-semaphore.php │ ├── test-helpers.php │ ├── test-request.php │ ├── test-payload.php │ ├── test-cache.php │ ├── test-post.php │ ├── test-blob.php │ ├── test-commit.php │ ├── client │ │ ├── test-persist.php │ │ └── test-base.php │ └── test-tree.php └── include │ ├── bootstrap.php │ └── testcase.php ├── .gitignore ├── .svnignore ├── .scrutinizer.yml ├── views ├── setting-field.php ├── user-setting-field.php └── options.php ├── .editorconfig ├── .travis.yml ├── phpunit.xml.dist ├── lib ├── semaphore.php ├── api.php ├── response.php ├── request.php ├── client │ ├── persist.php │ ├── fetch.php │ └── base.php ├── cli.php ├── blob.php ├── import.php ├── cache.php ├── payload.php ├── commit.php ├── tree.php ├── controller.php ├── export.php └── admin.php ├── composer.json ├── CONTRIBUTING.md ├── helpers.php ├── bin ├── wp2md ├── deploy └── install-wp-tests.sh └── CHANGELOG.md /languages/wp-github-sync-de_DE.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mAAdhaTTah/wordpress-github-sync/HEAD/languages/wp-github-sync-de_DE.mo -------------------------------------------------------------------------------- /languages/wp-github-sync-zh_CN.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mAAdhaTTah/wordpress-github-sync/HEAD/languages/wp-github-sync-zh_CN.mo -------------------------------------------------------------------------------- /tests/data/post_commits_fail.json: -------------------------------------------------------------------------------- 1 | { 2 | "message": "Not Found", 3 | "documentation_url": "https://developer.github.com/v3" 4 | } 5 | -------------------------------------------------------------------------------- /tests/data/post_trees_fail.json: -------------------------------------------------------------------------------- 1 | { 2 | "message": "Not Found", 3 | "documentation_url": "https://developer.github.com/v3" 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | 3 | # Ignore IDE settings 4 | .idea 5 | build 6 | phpunit.xml 7 | 8 | # Ignore temp files 9 | \#*\# 10 | *~ -------------------------------------------------------------------------------- /tests/data/get_refs_heads_master_fail.json: -------------------------------------------------------------------------------- 1 | { 2 | "message": "Not Found", 3 | "documentation_url": "https://developer.github.com/v3" 4 | } 5 | -------------------------------------------------------------------------------- /tests/data/patch_refs_heads_master_fail.json: -------------------------------------------------------------------------------- 1 | { 2 | "message": "Not Found", 3 | "documentation_url": "https://developer.github.com/v3" 4 | } 5 | -------------------------------------------------------------------------------- /.svnignore: -------------------------------------------------------------------------------- 1 | bin 2 | tests 3 | .editorconfig 4 | .gitignore 5 | .scrutinizer.yml 6 | .svnignore 7 | .travis.yml 8 | *.json 9 | *.lock 10 | *.md 11 | *.xml 12 | -------------------------------------------------------------------------------- /tests/data/get_blobs_2d73165945b0ccbe4932f1363457986b0ed49f19_fail.json: -------------------------------------------------------------------------------- 1 | { 2 | "message": "Not Found", 3 | "documentation_url": "https://developer.github.com/v3" 4 | } 5 | -------------------------------------------------------------------------------- /tests/data/get_blobs_8d9b2e6fd93761211dc03abd71f4a9189d680fd0_fail.json: -------------------------------------------------------------------------------- 1 | { 2 | "message": "Not Found", 3 | "documentation_url": "https://developer.github.com/v3" 4 | } 5 | -------------------------------------------------------------------------------- /tests/data/get_blobs_9fa5c7537f8582b71028ff34b8c20dfd0f3b2a25_fail.json: -------------------------------------------------------------------------------- 1 | { 2 | "message": "Not Found", 3 | "documentation_url": "https://developer.github.com/v3" 4 | } 5 | -------------------------------------------------------------------------------- /tests/data/get_commits_db2510854e6aeab68ead26b48328b19f4bdf926e_fail.json: -------------------------------------------------------------------------------- 1 | { 2 | "message": "Not Found", 3 | "documentation_url": "https://developer.github.com/v3" 4 | } 5 | -------------------------------------------------------------------------------- /tests/data/get_trees_9108868e3800bec6763e51beb0d33e15036c3626_fail.json: -------------------------------------------------------------------------------- 1 | { 2 | "message": "Invalid object requested. SHA must identify a commit or a tree.", 3 | "documentation_url": "https://developer.github.com/v3/git/trees/#get-a-tree" 4 | } 5 | -------------------------------------------------------------------------------- /tests/unit/test-cli.php: -------------------------------------------------------------------------------- 1 | markTestIncomplete(); 11 | } 12 | } 13 | 14 | -------------------------------------------------------------------------------- /tests/unit/test-main.php: -------------------------------------------------------------------------------- 1 | markTestIncomplete(); 11 | } 12 | } 13 | 14 | -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | tools: 2 | php_sim: true 3 | php_pdepend: true 4 | php_analyzer: true 5 | php_code_sniffer: 6 | config: 7 | standard: WordPress 8 | external_code_coverage: 9 | timeout: 1200 10 | runs: 1 11 | filter: 12 | excluded_paths: 13 | - 'tests/*' 14 | -------------------------------------------------------------------------------- /tests/data/get_blobs_9fa5c7537f8582b71028ff34b8c20dfd0f3b2a25_succeed.json: -------------------------------------------------------------------------------- 1 | { 2 | "sha": "9fa5c7537f8582b71028ff34b8c20dfd0f3b2a25", 3 | "size": 13, 4 | "url": "https://api.github.com/repos/wpghstest/wpghs-test/git/blobs/9fa5c7537f8582b71028ff34b8c20dfd0f3b2a25", 5 | "content": "IyB3cGdocy10ZXN0Cg==\n", 6 | "encoding": "base64" 7 | } 8 | -------------------------------------------------------------------------------- /tests/data/get_refs_heads_master_succeed.json: -------------------------------------------------------------------------------- 1 | { 2 | "ref": "refs/heads/master", 3 | "url": "https://api.github.com/repos/wpghstest/wpghs-test/git/refs/heads/master", 4 | "object": { 5 | "sha": "db2510854e6aeab68ead26b48328b19f4bdf926e", 6 | "type": "commit", 7 | "url": "https://api.github.com/repos/wpghstest/wpghs-test/git/commits/db2510854e6aeab68ead26b48328b19f4bdf926e" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/data/patch_refs_heads_master_succeed.json: -------------------------------------------------------------------------------- 1 | { 2 | "ref": "refs/heads/master", 3 | "url": "https://api.github.com/repos/wpghstest/wpghs-test/git/refs/heads/master", 4 | "object": { 5 | "sha": "ff2b3d42e86fa7e38c7b7886f490ae54b431f524", 6 | "type": "commit", 7 | "url": "https://api.github.com/repos/wpghstest/wpghs-test/git/commits/ff2b3d42e86fa7e38c7b7886f490ae54b431f524" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /views/setting-field.php: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 |

11 | -------------------------------------------------------------------------------- /views/user-setting-field.php: -------------------------------------------------------------------------------- 1 | 2 | 9 |

10 | -------------------------------------------------------------------------------- /tests/unit/test-api.php: -------------------------------------------------------------------------------- 1 | api = new WordPress_GitHub_Sync_Api( $this->app ); 11 | } 12 | 13 | public function test_should_load_fetch() { 14 | $this->assertInstanceOf( 'WordPress_GitHub_Sync_Fetch_Client', $this->api->fetch() ); 15 | } 16 | 17 | public function test_should_load_persist() { 18 | $this->assertInstanceOf( 'WordPress_GitHub_Sync_Persist_Client', $this->api->persist() ); 19 | } 20 | } 21 | 22 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs 2 | # editorconfig.org 3 | 4 | # WordPress Coding Standards 5 | # http://make.wordpress.org/core/handbook/coding-standards/ 6 | 7 | root = true 8 | 9 | [*] 10 | charset = utf-8 11 | end_of_line = lf 12 | insert_final_newline = true 13 | trim_trailing_whitespace = true 14 | indent_style = tab 15 | 16 | [*.md] 17 | trim_trailing_whitespace = false 18 | indent_style = space 19 | indent_size = 4 20 | 21 | [*.json] 22 | indent_style = space 23 | indent_size = 2 24 | 25 | [*.txt,wp-config-sample.php] 26 | end_of_line = crlf 27 | -------------------------------------------------------------------------------- /tests/data/get_blobs_2d73165945b0ccbe4932f1363457986b0ed49f19_succeed.json: -------------------------------------------------------------------------------- 1 | { 2 | "sha": "2d73165945b0ccbe4932f1363457986b0ed49f19", 3 | "size": 283, 4 | "url": "https://api.github.com/repos/wpghstest/wpghs-test/git/blobs/2d73165945b0ccbe4932f1363457986b0ed49f19", 5 | "content": "LS0tCklEOiAxCnBvc3RfdGl0bGU6IEhlbGxvIHdvcmxkIQphdXRob3I6IHRl\nc3QKcG9zdF9kYXRlOiAyMDE1LTExLTAyIDAwOjM0OjUxCnBvc3RfZXhjZXJw\ndDogIiIKbGF5b3V0OiBwb3N0CnBlcm1hbGluazogPgogIGh0dHA6Ly9qYW1l\nc2RpZ2lvaWEubmdyb2suY29tLzIwMTUvMTEvMDIvaGVsbG8td29ybGQvCnB1\nYmxpc2hlZDogdHJ1ZQotLS0KV2VsY29tZSB0byBXb3JkUHJlc3MuIFRoaXMg\naXMgeW91ciBmaXJzdCBwb3N0LiBFZGl0IG9yIGRlbGV0ZSBpdCwgdGhlbiBz\ndGFydCB3cml0aW5nIQ==\n", 6 | "encoding": "base64" 7 | } 8 | -------------------------------------------------------------------------------- /tests/include/bootstrap.php: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | tests/unit/ 12 | 13 | 14 | 15 | 16 | /tmp/wordpress* 17 | ./vendor/ 18 | ./tests/ 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /tests/unit/test-response.php: -------------------------------------------------------------------------------- 1 | response = new WordPress_GitHub_Sync_Response( $this->app ); 11 | } 12 | 13 | public function test_should_log_success() { 14 | $result = 'Success message.'; 15 | $this->assertTrue( $this->response->success( $result ) ); 16 | $this->assertTrue( file_exists( self::TMP_LOG ) ); 17 | $this->assertContains( 'UTC] ' . $result, file_get_contents( self::TMP_LOG ) ); 18 | } 19 | 20 | public function test_should_log_failure() { 21 | $result = new WP_Error( 'failure', 'Failure message.'); 22 | $this->assertFalse( $this->response->error( $result ) ); 23 | $this->assertTrue( file_exists( self::TMP_LOG ) ); 24 | $this->assertContains( 'UTC] ' . $result->get_error_message(), file_get_contents( self::TMP_LOG ) ); 25 | } 26 | 27 | public function tearDown() { 28 | parent::tearDown(); 29 | 30 | unlink( self::TMP_LOG ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/data/post_trees_succeed.json: -------------------------------------------------------------------------------- 1 | { 2 | "sha": "c0d2cb90b51826096c826b61a0e74d2c973d7ad8", 3 | "url": "https://api.github.com/repos/wpghstest/wpghs-test/git/trees/c0d2cb90b51826096c826b61a0e74d2c973d7ad8", 4 | "tree": [ 5 | { 6 | "path": "README.md", 7 | "mode": "100644", 8 | "type": "blob", 9 | "sha": "9fa5c7537f8582b71028ff34b8c20dfd0f3b2a25", 10 | "size": 13, 11 | "url": "https://api.github.com/repos/wpghstest/wpghs-test/git/blobs/9fa5c7537f8582b71028ff34b8c20dfd0f3b2a25" 12 | }, 13 | { 14 | "path": "_pages", 15 | "mode": "040000", 16 | "type": "tree", 17 | "sha": "a746b810e70d76d961116501dcb689f481b76c2c", 18 | "url": "https://api.github.com/repos/wpghstest/wpghs-test/git/trees/a746b810e70d76d961116501dcb689f481b76c2c" 19 | }, 20 | { 21 | "path": "_posts", 22 | "mode": "040000", 23 | "type": "tree", 24 | "sha": "168aff0ee6be2ffa50b34e55859c38a6b511aaad", 25 | "url": "https://api.github.com/repos/wpghstest/wpghs-test/git/trees/168aff0ee6be2ffa50b34e55859c38a6b511aaad" 26 | } 27 | ], 28 | "truncated": false 29 | } 30 | -------------------------------------------------------------------------------- /tests/data/post_commits_succeed.json: -------------------------------------------------------------------------------- 1 | { 2 | "sha": "ff2b3d42e86fa7e38c7b7886f490ae54b431f524", 3 | "url": "https://api.github.com/repos/wpghstest/wpghs-test/git/commits/ff2b3d42e86fa7e38c7b7886f490ae54b431f524", 4 | "html_url": "https://github.com/wpghstest/wpghs-test/commit/ff2b3d42e86fa7e38c7b7886f490ae54b431f524", 5 | "author": { 6 | "name": "James DiGioia", 7 | "email": "jamesorodig@gmail.com", 8 | "date": "2015-11-04T04:45:10Z" 9 | }, 10 | "committer": { 11 | "name": "James DiGioia", 12 | "email": "jamesorodig@gmail.com", 13 | "date": "2015-11-04T04:45:10Z" 14 | }, 15 | "tree": { 16 | "sha": "c0d2cb90b51826096c826b61a0e74d2c973d7ad8", 17 | "url": "https://api.github.com/repos/wpghstest/wpghs-test/git/trees/c0d2cb90b51826096c826b61a0e74d2c973d7ad8" 18 | }, 19 | "message": "New commit via paw.", 20 | "parents": [ 21 | { 22 | "sha": "7497c0574b9430ff5e5521b4572b7452ea36a056", 23 | "url": "https://api.github.com/repos/wpghstest/wpghs-test/git/commits/7497c0574b9430ff5e5521b4572b7452ea36a056", 24 | "html_url": "https://github.com/wpghstest/wpghs-test/commit/7497c0574b9430ff5e5521b4572b7452ea36a056" 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /tests/data/get_commits_db2510854e6aeab68ead26b48328b19f4bdf926e_succeed.json: -------------------------------------------------------------------------------- 1 | { 2 | "sha": "7497c0574b9430ff5e5521b4572b7452ea36a056", 3 | "url": "https://api.github.com/repos/wpghstest/wpghs-test/git/commits/7497c0574b9430ff5e5521b4572b7452ea36a056", 4 | "html_url": "https://github.com/wpghstest/wpghs-test/commit/7497c0574b9430ff5e5521b4572b7452ea36a056", 5 | "author": { 6 | "name": "test", 7 | "email": "test@test.com", 8 | "date": "2015-11-02T00:36:54Z" 9 | }, 10 | "committer": { 11 | "name": "test", 12 | "email": "test@test.com", 13 | "date": "2015-11-02T00:36:54Z" 14 | }, 15 | "tree": { 16 | "sha": "9108868e3800bec6763e51beb0d33e15036c3626", 17 | "url": "https://api.github.com/repos/wpghstest/wpghs-test/git/trees/9108868e3800bec6763e51beb0d33e15036c3626" 18 | }, 19 | "message": "Initial full site export - wpghs", 20 | "parents": [ 21 | { 22 | "sha": "db2510854e6aeab68ead26b48328b19f4bdf926e", 23 | "url": "https://api.github.com/repos/wpghstest/wpghs-test/git/commits/db2510854e6aeab68ead26b48328b19f4bdf926e", 24 | "html_url": "https://github.com/wpghstest/wpghs-test/commit/db2510854e6aeab68ead26b48328b19f4bdf926e" 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /tests/unit/test-semaphore.php: -------------------------------------------------------------------------------- 1 | semaphore = new WordPress_GitHub_Sync_Semaphore( $this->app ); 12 | } 13 | 14 | public function test_should_default_to_open() { 15 | $this->assertTrue( $this->semaphore->is_open() ); 16 | } 17 | 18 | public function test_should_unlock() { 19 | set_transient( WordPress_GitHub_Sync_Semaphore::KEY, WordPress_GitHub_Sync_Semaphore::VALUE_LOCKED ); 20 | 21 | $this->semaphore->unlock(); 22 | 23 | $this->assertTrue( $this->semaphore->is_open() ); 24 | } 25 | 26 | public function test_should_lock() { 27 | set_transient( WordPress_GitHub_Sync_Semaphore::KEY, WordPress_GitHub_Sync_Semaphore::VALUE_UNLOCKED ); 28 | 29 | $this->semaphore->lock(); 30 | 31 | $this->assertFalse( $this->semaphore->is_open() ); 32 | } 33 | 34 | public function test_should_expire() { 35 | $this->semaphore->lock(); 36 | 37 | sleep( MINUTE_IN_SECONDS ); 38 | // A little extra to make sure it's expired. 39 | sleep( 5 ); 40 | 41 | $this->assertTrue( $this->semaphore->is_open() ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /views/options.php: -------------------------------------------------------------------------------- 1 | 8 |
9 |

GitHub Sync', 'wp-github-sync' ); ?>

10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 29 |
?action=wpghs_sync_request
22 | 23 | 24 | | 25 | 26 | 27 | 28 |
30 | 31 |
32 |
33 | -------------------------------------------------------------------------------- /tests/unit/test-helpers.php: -------------------------------------------------------------------------------- 1 | factory->post->create_and_get(); 14 | $this->post = new WordPress_GitHub_Sync_Post( $post->ID, $this->api ); 15 | $this->fetch 16 | ->shouldReceive( 'repository' ) 17 | ->andReturn( 'owner/repo' ); 18 | } 19 | 20 | public function test_should_return_global_post_view_url() { 21 | $this->assertEquals( $this->post->github_view_url(), get_the_github_view_url() ); 22 | } 23 | 24 | public function test_should_return_global_post_edit_url() { 25 | $this->assertEquals( $this->post->github_edit_url(), get_the_github_edit_url() ); 26 | } 27 | 28 | public function test_should_return_global_post_view_link() { 29 | $this->assertEquals( 30 | sprintf( '%s', get_the_github_view_url(), 'View this post on GitHub.' ), 31 | get_the_github_view_link() 32 | ); 33 | } 34 | 35 | public function test_should_return_global_post_edit_link() { 36 | $this->assertEquals( 37 | sprintf( '%s', get_the_github_edit_url(), 'Edit this post on GitHub.' ), 38 | get_the_github_edit_link() 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/semaphore.php: -------------------------------------------------------------------------------- 1 | app = $app; 40 | } 41 | 42 | /** 43 | * Lazy-load fetch client. 44 | * 45 | * @return WordPress_GitHub_Sync_Fetch_Client 46 | */ 47 | public function fetch() { 48 | if ( ! $this->fetch ) { 49 | $this->fetch = new WordPress_GitHub_Sync_Fetch_Client( $this->app ); 50 | } 51 | 52 | return $this->fetch; 53 | } 54 | 55 | /** 56 | * Lazy-load persist client. 57 | * 58 | * @return WordPress_GitHub_Sync_Persist_Client 59 | */ 60 | public function persist() { 61 | if ( ! $this->persist ) { 62 | $this->persist = new WordPress_GitHub_Sync_Persist_Client( $this->app ); 63 | } 64 | 65 | return $this->persist; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /tests/unit/test-request.php: -------------------------------------------------------------------------------- 1 | secret = '1234567890qwertyuiopasdfghjklzxcvbnm'; 22 | update_option( 'wpghs_secret', $this->secret ); 23 | 24 | $this->request = new WordPress_GitHub_Sync_Request_Stub( $this->app ); 25 | $this->request->set_data_dir( $this->data_dir ); 26 | } 27 | 28 | public function test_should_return_error_if_header_invalid() { 29 | $this->set_auth_header( hash_hmac( 'sha1', file_get_contents( $this->data_dir . 'payload-valid.json' ), 'wrong-secret' ) ); 30 | 31 | $this->assertFalse( $this->request->is_secret_valid() ); 32 | } 33 | 34 | public function test_should_return_true_if_header_valid() { 35 | $this->set_auth_header( hash_hmac( 'sha1', file_get_contents( $this->data_dir . 'payload-valid.json' ), $this->secret ) ); 36 | 37 | $this->assertTrue( $this->request->is_secret_valid() ); 38 | $this->assertEquals( 'ad4e0a9e2597de40106d5f52e2041d8ebaeb0087', $this->request->payload()->get_commit_id() ); 39 | } 40 | 41 | protected function set_auth_header( $hash ) { 42 | $_SERVER['HTTP_X_HUB_SIGNATURE'] = 'sha1=' . $hash; 43 | } 44 | } 45 | 46 | class WordPress_GitHub_Sync_Request_Stub extends WordPress_GitHub_Sync_Request { 47 | /** 48 | * @var string 49 | */ 50 | protected $data_dir; 51 | 52 | public function set_data_dir( $data_dir ) { 53 | $this->data_dir = $data_dir; 54 | } 55 | protected function read_raw_data() { 56 | return file_get_contents( $this->data_dir . 'payload-valid.json' ); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /tests/data/get_trees_9108868e3800bec6763e51beb0d33e15036c3626_succeed.json: -------------------------------------------------------------------------------- 1 | { 2 | "sha": "9108868e3800bec6763e51beb0d33e15036c3626", 3 | "url": "https://api.github.com/repos/wpghstest/wpghs-test/git/trees/9108868e3800bec6763e51beb0d33e15036c3626", 4 | "tree": [ 5 | { 6 | "path": "README.md", 7 | "mode": "100644", 8 | "type": "blob", 9 | "sha": "9fa5c7537f8582b71028ff34b8c20dfd0f3b2a25", 10 | "size": 13, 11 | "url": "https://api.github.com/repos/wpghstest/wpghs-test/git/blobs/9fa5c7537f8582b71028ff34b8c20dfd0f3b2a25" 12 | }, 13 | { 14 | "path": "_pages", 15 | "mode": "040000", 16 | "type": "tree", 17 | "sha": "a746b810e70d76d961116501dcb689f481b76c2c", 18 | "url": "https://api.github.com/repos/wpghstest/wpghs-test/git/trees/a746b810e70d76d961116501dcb689f481b76c2c" 19 | }, 20 | { 21 | "path": "_pages/sample-page.md", 22 | "mode": "100644", 23 | "type": "blob", 24 | "sha": "8d9b2e6fd93761211dc03abd71f4a9189d680fd0", 25 | "size": 1155, 26 | "url": "https://api.github.com/repos/wpghstest/wpghs-test/git/blobs/8d9b2e6fd93761211dc03abd71f4a9189d680fd0" 27 | }, 28 | { 29 | "path": "_posts", 30 | "mode": "040000", 31 | "type": "tree", 32 | "sha": "c5021d2560d1c86837aecfd55f333ad6a03b405f", 33 | "url": "https://api.github.com/repos/wpghstest/wpghs-test/git/trees/c5021d2560d1c86837aecfd55f333ad6a03b405f" 34 | }, 35 | { 36 | "path": "_posts/2015-11-02-hello-world.md", 37 | "mode": "100644", 38 | "type": "blob", 39 | "sha": "2d73165945b0ccbe4932f1363457986b0ed49f19", 40 | "size": 283, 41 | "url": "https://api.github.com/repos/wpghstest/wpghs-test/git/blobs/2d73165945b0ccbe4932f1363457986b0ed49f19" 42 | } 43 | ], 44 | "truncated": false 45 | } 46 | -------------------------------------------------------------------------------- /tests/data/get_blobs_8d9b2e6fd93761211dc03abd71f4a9189d680fd0_succeed.json: -------------------------------------------------------------------------------- 1 | { 2 | "sha": "8d9b2e6fd93761211dc03abd71f4a9189d680fd0", 3 | "size": 1155, 4 | "url": "https://api.github.com/repos/wpghstest/wpghs-test/git/blobs/8d9b2e6fd93761211dc03abd71f4a9189d680fd0", 5 | "content": "LS0tCklEOiAyCnBvc3RfdGl0bGU6IFNhbXBsZSBQYWdlCmF1dGhvcjogdGVz\ndApwb3N0X2RhdGU6IDIwMTUtMTEtMDIgMDA6MzQ6NTEKcG9zdF9leGNlcnB0\nOiAiIgpsYXlvdXQ6IHBhZ2UKcGVybWFsaW5rOiA+CiAgaHR0cDovL2phbWVz\nZGlnaW9pYS5uZ3Jvay5jb20vc2FtcGxlLXBhZ2UvCnB1Ymxpc2hlZDogdHJ1\nZQotLS0KVGhpcyBpcyBhbiBleGFtcGxlIHBhZ2UuIEl0J3MgZGlmZmVyZW50\nIGZyb20gYSBibG9nIHBvc3QgYmVjYXVzZSBpdCB3aWxsIHN0YXkgaW4gb25l\nIHBsYWNlIGFuZCB3aWxsIHNob3cgdXAgaW4geW91ciBzaXRlIG5hdmlnYXRp\nb24gKGluIG1vc3QgdGhlbWVzKS4gTW9zdCBwZW9wbGUgc3RhcnQgd2l0aCBh\nbiBBYm91dCBwYWdlIHRoYXQgaW50cm9kdWNlcyB0aGVtIHRvIHBvdGVudGlh\nbCBzaXRlIHZpc2l0b3JzLiBJdCBtaWdodCBzYXkgc29tZXRoaW5nIGxpa2Ug\ndGhpczoKCjxibG9ja3F1b3RlPkhpIHRoZXJlISBJJ20gYSBiaWtlIG1lc3Nl\nbmdlciBieSBkYXksIGFzcGlyaW5nIGFjdG9yIGJ5IG5pZ2h0LCBhbmQgdGhp\ncyBpcyBteSB3ZWJzaXRlLiBJIGxpdmUgaW4gTG9zIEFuZ2VsZXMsIGhhdmUg\nYSBncmVhdCBkb2cgbmFtZWQgSmFjaywgYW5kIEkgbGlrZSBwaSYjMjQxO2Eg\nY29sYWRhcy4gKEFuZCBnZXR0aW4nIGNhdWdodCBpbiB0aGUgcmFpbi4pPC9i\nbG9ja3F1b3RlPgoKLi4ub3Igc29tZXRoaW5nIGxpa2UgdGhpczoKCjxibG9j\na3F1b3RlPlRoZSBYWVogRG9vaGlja2V5IENvbXBhbnkgd2FzIGZvdW5kZWQg\naW4gMTk3MSwgYW5kIGhhcyBiZWVuIHByb3ZpZGluZyBxdWFsaXR5IGRvb2hp\nY2tleXMgdG8gdGhlIHB1YmxpYyBldmVyIHNpbmNlLiBMb2NhdGVkIGluIEdv\ndGhhbSBDaXR5LCBYWVogZW1wbG95cyBvdmVyIDIsMDAwIHBlb3BsZSBhbmQg\nZG9lcyBhbGwga2luZHMgb2YgYXdlc29tZSB0aGluZ3MgZm9yIHRoZSBHb3Ro\nYW0gY29tbXVuaXR5LjwvYmxvY2txdW90ZT4KCkFzIGEgbmV3IFdvcmRQcmVz\ncyB1c2VyLCB5b3Ugc2hvdWxkIGdvIHRvIDxhIGhyZWY9Imh0dHA6Ly9qYW1l\nc2RpZ2lvaWEubmdyb2suY29tL3dwL3dwLWFkbWluLyI+eW91ciBkYXNoYm9h\ncmQ8L2E+IHRvIGRlbGV0ZSB0aGlzIHBhZ2UgYW5kIGNyZWF0ZSBuZXcgcGFn\nZXMgZm9yIHlvdXIgY29udGVudC4gSGF2ZSBmdW4h\n", 6 | "encoding": "base64" 7 | } 8 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "maadhattah/wp-github-sync", 3 | "description": "A WordPress plugin to sync content with a GitHub repository (or Jekyll site)", 4 | "type": "wordpress-plugin", 5 | "minimum-stability": "stable", 6 | "license": "GPL", 7 | "authors": [ 8 | { 9 | "name": "Ben Balter", 10 | "email": "ben@balter.com" 11 | }, 12 | { 13 | "name": "James DiGioia", 14 | "email": "jamesorodig@gmail.com" 15 | } 16 | ], 17 | "repositories": [ 18 | { 19 | "type": "vcs", 20 | "url": "https://github.com/mAAdhaTTah/cyps" 21 | } 22 | ], 23 | "autoload": { 24 | "classmap": ["lib/"], 25 | "files": ["helpers.php"] 26 | }, 27 | "autoload-dev": { 28 | "classmap": ["tests/"] 29 | }, 30 | "require": { 31 | "php": ">=5.2", 32 | "mustangostang/spyc": "^0.6.1", 33 | "xrstf/composer-php52": "~1.0" 34 | }, 35 | "require-dev": { 36 | "jdgrimes/wp-http-testcase": "1.3.1", 37 | "mockery/mockery": "0.9.3", 38 | "phpunit/phpunit": "^4.8", 39 | "websharks/wp-i18n-tools": "dev-master", 40 | "wp-coding-standards/wpcs": "0.7.1", 41 | "squizlabs/php_codesniffer": "~2.3" 42 | }, 43 | "scripts": { 44 | "sniff": "phpcs --runtime-set installed_paths vendor/wp-coding-standards/wpcs -p ./ --standard=WordPress --report=full --extensions=php --ignore=*/tests/*,*/vendor/*", 45 | "clean": "phpcbf --runtime-set installed_paths vendor/wp-coding-standards/wpcs -p ./ --standard=WordPress --report=full --extensions=php --ignore=*/tests/*,*/vendor/*", 46 | "test": "phpunit", 47 | "genpot": "vendor/websharks/wp-i18n-tools/makepot.php wp-plugin . $(pwd)/languages/wp-github-sync.pot", 48 | "post-install-cmd": [ 49 | "xrstf\\Composer52\\Generator::onPostInstallCmd" 50 | ], 51 | "post-update-cmd": [ 52 | "xrstf\\Composer52\\Generator::onPostInstallCmd" 53 | ], 54 | "post-autoload-dump": [ 55 | "xrstf\\Composer52\\Generator::onPostInstallCmd" 56 | ] 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tests/unit/test-payload.php: -------------------------------------------------------------------------------- 1 | fetch 11 | ->shouldReceive( 'repository' ) 12 | ->once() 13 | ->andReturn( 'owner/repo' ) 14 | ->byDefault(); 15 | } 16 | 17 | public function test_should_not_import_invalid_repository() { 18 | $payload = new WordPress_GitHub_Sync_Payload( 19 | $this->app, 20 | file_get_contents( $this->data_dir . 'payload-invalid-repo.json' ) 21 | ); 22 | 23 | $this->assertFalse( $payload->should_import() ); 24 | } 25 | 26 | public function test_should_not_import_invalid_branch() { 27 | $payload = new WordPress_GitHub_Sync_Payload( 28 | $this->app, 29 | file_get_contents( $this->data_dir . 'payload-invalid-branch.json' ) 30 | ); 31 | 32 | $this->assertFalse( $payload->should_import() ); 33 | } 34 | 35 | public function test_should_not_import_synced_commit() { 36 | $payload = new WordPress_GitHub_Sync_Payload( 37 | $this->app, 38 | file_get_contents( $this->data_dir . 'payload-synced-commit.json' ) 39 | ); 40 | 41 | $this->assertFalse( $payload->should_import() ); 42 | } 43 | 44 | public function test_should_be_valid_payload() { 45 | $payload = new WordPress_GitHub_Sync_Payload( 46 | $this->app, 47 | file_get_contents( $this->data_dir . 'payload-valid.json' ) 48 | ); 49 | 50 | $this->assertTrue( $payload->should_import() ); 51 | $this->assertEquals( 'ad4e0a9e2597de40106d5f52e2041d8ebaeb0087', $payload->get_commit_id() ); 52 | $this->assertEquals( 'username@github.com', $payload->get_author_email() ); 53 | $this->assertCount( 1, $commits = $payload->get_commits() ); 54 | $this->assertEquals( 'ad4e0a9e2597de40106d5f52e2041d8ebaeb0087', $commits[0]->id ); 55 | } 56 | 57 | public function test_should_show_error() { 58 | $this->fetch 59 | ->shouldReceive( 'repository' ) 60 | ->never(); 61 | 62 | $payload = new WordPress_GitHub_Sync_Payload( 63 | $this->app, 64 | "This isn't valid JSON" 65 | ); 66 | 67 | $this->assertTrue( $payload->has_error() ); 68 | $this->assertEquals( 'Syntax error, malformed JSON', $payload->get_error() ); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /lib/response.php: -------------------------------------------------------------------------------- 1 | app = $app; 26 | } 27 | 28 | /** 29 | * Writes to the log and returns the error response. 30 | * 31 | * @param WP_Error $error Error to respond with. 32 | * 33 | * @return false 34 | */ 35 | public function error( WP_Error $error ) { 36 | global $wp_version; 37 | 38 | $this->log( $error ); 39 | 40 | if ( defined( 'DOING_AJAX' ) && DOING_AJAX && defined( 'WPGHS_AJAX' ) && WPGHS_AJAX ) { 41 | /** 42 | * WordPress 4.1.0 introduced allowing WP_Error objects to be 43 | * passed directly into `wp_send_json_error`. This shims in 44 | * compatibility for older versions. We're currently supporting 3.9+. 45 | */ 46 | if ( version_compare( $wp_version, '4.1.0', '<' ) ) { 47 | $result = array(); 48 | 49 | foreach ( $error->errors as $code => $messages ) { 50 | foreach ( $messages as $message ) { 51 | $result[] = array( 'code' => $code, 'message' => $message ); 52 | } 53 | } 54 | 55 | $error = $result; 56 | } 57 | 58 | wp_send_json_error( $error ); 59 | } 60 | 61 | return false; 62 | } 63 | 64 | /** 65 | * Writes to the log and returns the success response. 66 | * 67 | * @param string $success Success message to respond with. 68 | * 69 | * @return true 70 | */ 71 | public function success( $success ) { 72 | $this->log( $success ); 73 | 74 | if ( defined( 'DOING_AJAX' ) && DOING_AJAX && defined( 'WPGHS_AJAX' ) && WPGHS_AJAX ) { 75 | wp_send_json_success( $success ); 76 | } 77 | 78 | return true; 79 | } 80 | 81 | /** 82 | * Writes a log message. 83 | * 84 | * Can extract a message from WP_Error object. 85 | * 86 | * @param string|WP_Error $msg Message to log. 87 | */ 88 | protected function log( $msg ) { 89 | if ( is_wp_error( $msg ) ) { 90 | $msg = $msg->get_error_message(); 91 | } 92 | 93 | WordPress_GitHub_Sync::write_log( $msg ); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /lib/request.php: -------------------------------------------------------------------------------- 1 | app = $app; 33 | } 34 | 35 | /** 36 | * Validates the header's secret. 37 | * 38 | * @return true|WP_Error 39 | */ 40 | public function is_secret_valid() { 41 | $headers = $this->headers(); 42 | 43 | $this->raw_data = $this->read_raw_data(); 44 | 45 | // Validate request secret. 46 | $hash = hash_hmac( 'sha1', $this->raw_data, $this->secret() ); 47 | if ( 'sha1=' . $hash !== $headers['X-Hub-Signature'] ) { 48 | return false; 49 | } 50 | 51 | return true; 52 | } 53 | 54 | /** 55 | * Returns a payload object for the given request. 56 | * 57 | * @return WordPress_GitHub_Sync_Payload 58 | */ 59 | public function payload() { 60 | return new WordPress_GitHub_Sync_Payload( $this->app, $this->raw_data ); 61 | } 62 | 63 | /** 64 | * Cross-server header support. 65 | * 66 | * Returns an array of the request's headers. 67 | * 68 | * @return array 69 | */ 70 | protected function headers() { 71 | if ( function_exists( 'getallheaders' ) ) { 72 | return getallheaders(); 73 | } 74 | /** 75 | * Nginx and pre 5.4 workaround. 76 | * @see http://www.php.net/manual/en/function.getallheaders.php 77 | */ 78 | $headers = array(); 79 | foreach ( $_SERVER as $name => $value ) { 80 | if ( 'HTTP_' === substr( $name, 0, 5 ) ) { 81 | $headers[ str_replace( ' ', '-', ucwords( strtolower( str_replace( '_', ' ', substr( $name, 5 ) ) ) ) ) ] = $value; 82 | } 83 | } 84 | 85 | return $headers; 86 | } 87 | 88 | /** 89 | * Reads the raw data from STDIN. 90 | * 91 | * @return string 92 | */ 93 | protected function read_raw_data() { 94 | return file_get_contents( 'php://input' ); 95 | } 96 | 97 | /** 98 | * Returns the Webhook secret 99 | * 100 | * @return string 101 | */ 102 | protected function secret() { 103 | return get_option( 'wpghs_secret' ); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | ## Ways to contribute 4 | 5 | 1. Grab [an open issue] and [submit a pull request]. 6 | 1. Try the plugin out on a test server and [report any issues you find]. 7 | 1. Let us know [what features you'd love to see]. 8 | 1. Participate in the [discussion forums]. 9 | 1. Help improve [the documentation]. 10 | 1. Translate the plugin [to another language]. 11 | 12 | ## Submit a pull request 13 | 14 | Want to propose a change? Great! We could use the help. Here's how: 15 | 16 | 1. Fork the project. 17 | 1. Create a descriptively named feature branch. 18 | 1. Commit your changes. 19 | 1. Submit a pull request. 20 | 21 | For more information see [GitHub flow] and [Contributing to Open Source]. 22 | 23 | This project uses [Composer] for dependency management, and there are a few scripts you use to help you along: 24 | 25 | * `composer test` will run PHPUnit for you, making sure all your tests pass. 26 | * `composer sniff` will run the code-sniffer for you, making sure you're adhering to the [WordPress Coding Standards] 27 | 28 | In order to the the plugin unit tests, you'll need [MySQL] installed on your development machine. Before running `composer test`, run: 29 | 30 | ```bash 31 | bash bin/install-wp-tests.sh 32 | ``` 33 | 34 | where `` and `` are for the root MySQL user. A new database will be created matching ``, if it doesn't exist. This database will be deleted every time the tests are run, so `wordpress_test` is commonly used as the database name. 35 | 36 | If you're opening a pull request with a new feature, please include unit tests. If you don't know how to write unit tests, open the PR anyway; we'll be glad to help you out. 37 | 38 | [an open issue]: https://github.com/benbalter/wordpress-github-sync/issues 39 | [submit a pull request]: #submit-a-pull-request 40 | [report any issues you find]: https://github.com/benbalter/wordpress-github-sync/issues/new 41 | [what features you'd love to see]: https://github.com/benbalter/wordpress-github-sync/issues/new 42 | [discussion forums]: https://github.com/benbalter/wordpress-github-sync/issues 43 | [the documentation]: https://github.com/benbalter/wordpress-github-sync/blob/master/README.md 44 | [to another language]: https://github.com/benbalter/wordpress-github-sync/tree/master/languages 45 | [GitHub flow]: https://guides.github.com/introduction/flow/ 46 | [Contributing to Open Source]: https://guides.github.com/activities/contributing-to-open-source/ 47 | [Composer]: https://getcomposer.org/ 48 | [WordPress Coding Standards]: https://make.wordpress.org/core/handbook/best-practices/coding-standards/php/ 49 | [MySQL]: https://www.mysql.com/ 50 | -------------------------------------------------------------------------------- /lib/client/persist.php: -------------------------------------------------------------------------------- 1 | tree()->is_changed() ) { 21 | return new WP_Error( 22 | 'no_commit', 23 | __( 24 | 'There were no changes, so no additional commit was added.', 25 | 'wp-github-sync' 26 | ) 27 | ); 28 | } 29 | 30 | $tree = $this->create_tree( $commit->tree() ); 31 | 32 | if ( is_wp_error( $tree ) ) { 33 | return $tree; 34 | } 35 | 36 | $commit->tree()->set_sha( $tree->sha ); 37 | $commit = $this->create_commit( $commit ); 38 | 39 | if ( is_wp_error( $commit ) ) { 40 | return $commit; 41 | } 42 | 43 | $ref = $this->set_ref( $commit->sha ); 44 | 45 | if ( is_wp_error( $ref ) ) { 46 | return $ref; 47 | } 48 | 49 | return true; 50 | } 51 | 52 | /** 53 | * Create the tree by a set of blob ids. 54 | * 55 | * @param WordPress_GitHub_Sync_Tree $tree Tree to create. 56 | * 57 | * @return stdClass|WP_Error 58 | */ 59 | protected function create_tree( WordPress_GitHub_Sync_Tree $tree ) { 60 | return $this->call( 'POST', $this->tree_endpoint(), $tree->to_body() ); 61 | } 62 | 63 | /** 64 | * Create the commit from tree sha. 65 | * 66 | * @param WordPress_GitHub_Sync_Commit $commit Commit to create. 67 | * 68 | * @return mixed 69 | */ 70 | protected function create_commit( WordPress_GitHub_Sync_Commit $commit ) { 71 | $body = $commit->to_body(); 72 | 73 | if ( $author = $this->export_user() ) { 74 | $body['author'] = $author; 75 | } 76 | 77 | return $this->call( 'POST', $this->commit_endpoint(), $body ); 78 | } 79 | 80 | /** 81 | * Updates the master branch to point to the new commit 82 | * 83 | * @param string $sha Sha for the commit for the master branch. 84 | * 85 | * @return mixed 86 | */ 87 | protected function set_ref( $sha ) { 88 | return $this->call( 'PATCH', $this->reference_endpoint(), array( 'sha' => $sha ) ); 89 | } 90 | 91 | /** 92 | * Get the data for the current user. 93 | * 94 | * @return array 95 | */ 96 | protected function export_user() { 97 | // @todo constant/abstract out? 98 | if ( $user_id = (int) get_option( '_wpghs_export_user_id' ) ) { 99 | delete_option( '_wpghs_export_user_id' ); 100 | } else { 101 | $user_id = get_current_user_id(); 102 | } 103 | 104 | $user = get_userdata( $user_id ); 105 | 106 | if ( ! $user ) { 107 | // @todo is this what we want to include here? 108 | return array( 109 | 'name' => 'Anonymous', 110 | 'email' => 'anonymous@users.noreply.github.com', 111 | ); 112 | } 113 | 114 | return array( 115 | 'name' => $user->display_name, 116 | 'email' => $user->user_email, 117 | ); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /tests/unit/test-cache.php: -------------------------------------------------------------------------------- 1 | api_cache = new WordPress_GitHub_Sync_Cache; 15 | $this->sha = '1234567890qwertyuiop'; 16 | } 17 | 18 | public function test_should_set_and_fetch_commit_from_memory() { 19 | $this->api_cache->set_commit( $this->sha, $this->commit ); 20 | 21 | $commit = $this->api_cache->fetch_commit( $this->sha ); 22 | 23 | $this->assertSame( $this->commit, $commit ); 24 | } 25 | 26 | public function test_should_set_and_fetch_tree_from_memory() { 27 | $this->api_cache->set_tree( $this->sha, $this->tree ); 28 | 29 | $tree = $this->api_cache->fetch_tree( $this->sha ); 30 | 31 | $this->assertSame( $this->tree, $tree ); 32 | } 33 | 34 | public function test_should_set_and_fetch_blob_from_memory() { 35 | $this->api_cache->set_blob( $this->sha, $this->blob ); 36 | 37 | $blob = $this->api_cache->fetch_blob( $this->sha ); 38 | 39 | $this->assertSame( $this->blob, $blob ); 40 | } 41 | 42 | public function test_should_set_and_fetch_commit_from_cache() { 43 | $data = new stdClass; 44 | $data->message = 'Commit message'; 45 | $this->commit->fake_data = $data; 46 | set_transient( 'wpghs_' . md5( 'commits_' . $this->sha ), $this->commit ); 47 | 48 | $commit = $this->api_cache->fetch_commit( $this->sha ); 49 | 50 | $this->assertSame( $data->message, $commit->fake_data->message ); 51 | } 52 | 53 | public function test_should_set_and_fetch_tree_from_cache() { 54 | $data = new stdClass; 55 | $data->blobs = array( $this->blob ); 56 | $this->tree->fake_data = $data; 57 | set_transient( 'wpghs_' . md5( 'trees_' . $this->sha ), $this->tree ); 58 | 59 | $tree = $this->api_cache->fetch_tree( $this->sha ); 60 | 61 | $this->assertCount( 1, $tree->fake_data->blobs ); 62 | $this->assertEquals( $this->blob, $tree->fake_data->blobs[0] ); 63 | } 64 | 65 | public function test_should_set_and_fetch_blob_from_cache() { 66 | $data = new stdClass; 67 | $data->content = 'Post content'; 68 | $this->blob->fake_data = $data; 69 | set_transient( 'wpghs_' . md5( 'blobs_' . $this->sha ), $this->blob ); 70 | 71 | $blob = $this->api_cache->fetch_blob( $this->sha ); 72 | 73 | $this->assertSame( $data->content, $blob->fake_data->content ); 74 | } 75 | 76 | public function test_should_return_false_if_cant_fetch_commit() { 77 | $this->assertFalse( $this->api_cache->fetch_commit( $this->sha ) ); 78 | } 79 | 80 | public function test_should_return_false_if_cant_fetch_tree() { 81 | $this->assertFalse( $this->api_cache->fetch_tree( $this->sha ) ); 82 | } 83 | 84 | public function test_should_return_false_if_cant_fetch_blob() { 85 | $this->assertFalse( $this->api_cache->fetch_blob( $this->sha ) ); 86 | } 87 | 88 | public function tearDown() { 89 | parent::tearDown(); 90 | 91 | $this->assertFalse( get_option( '_wpghs_api_cache' ) ); 92 | delete_transient( 'wpghs_' . md5( 'blobs_' . $this->sha ) ); 93 | delete_transient( 'wpghs_' . md5( 'trees_' . $this->sha ) ); 94 | delete_transient( 'wpghs_' . md5( 'commits_' . $this->sha ) ); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /helpers.php: -------------------------------------------------------------------------------- 1 | ' . apply_filters( 'wpghs_view_link_text', __( 'View this post on GitHub.', 'wp-github-sync' ) ) . ''; 15 | } 16 | 17 | /** 18 | * Returns the URL to view the current post on GitHub. 19 | * 20 | * @return string 21 | */ 22 | function get_the_github_view_url() { 23 | $wpghs_post = new WordPress_GitHub_Sync_Post( get_the_ID(), WordPress_GitHub_Sync::$instance->api() ); 24 | 25 | return $wpghs_post->github_view_url(); 26 | } 27 | 28 | /** 29 | * Returns the HTML markup to edit the current post on GitHub. 30 | * 31 | * @return string 32 | */ 33 | function get_the_github_edit_link() { 34 | return '' . apply_filters( 'wpghs_edit_link_text', __( 'Edit this post on GitHub.', 'wp-github-sync' ) ) . ''; 35 | } 36 | 37 | /** 38 | * Returns the URL to edit the current post on GitHub. 39 | * 40 | * @return string 41 | */ 42 | function get_the_github_edit_url() { 43 | $wpghs_post = new WordPress_GitHub_Sync_Post( get_the_ID(), WordPress_GitHub_Sync::$instance->api() ); 44 | 45 | return $wpghs_post->github_edit_url(); 46 | } 47 | 48 | 49 | /** 50 | * Common WPGHS function with attributes and shortcode 51 | * - type: 'link' (default) to return a HTML anchor tag with text, or 'url' for bare URL. 52 | * - target: 'view' (default) or 'edit' to return the respective link/url. 53 | * - text: text to be included in the link. Ignored if type='url'. 54 | * 55 | * Returns either a HTML formatted anchor tag or the bare URL of the current post on GitHub. 56 | * 57 | * @return string 58 | */ 59 | function write_wpghs_link( $atts ) { 60 | 61 | $args = shortcode_atts( 62 | array( 63 | 'type' => 'link', 64 | 'target' => 'view', 65 | 'text' => '', 66 | ), 67 | $atts 68 | ); 69 | $type = esc_attr( $args['type'] ); 70 | $target = esc_attr( $args['target'] ); 71 | $text = esc_attr( $args['text'] ); 72 | 73 | $output = ''; 74 | 75 | switch ( $target ) { 76 | case 'view': { 77 | $getter = get_the_github_view_url(); 78 | if ( ! empty( $text ) ) { 79 | $linktext = $text; 80 | } else { 81 | $linktext = __( 'View this post on GitHub', 'wp-github-sync' ); 82 | } 83 | break; 84 | } 85 | case 'edit': { 86 | $getter = get_the_github_edit_url(); 87 | if ( ! empty( $text ) ) { 88 | $linktext = $text; 89 | } else { 90 | $linktext = __( 'Edit this post on GitHub', 'wp-github-sync' ); 91 | } 92 | break; 93 | } 94 | default: { 95 | $getter = get_the_github_view_url(); 96 | $linktext = __( 'View this post on GitHub', 'wp-github-sync' ); 97 | break; 98 | } 99 | } 100 | 101 | switch ( $type ) { 102 | case 'link': { 103 | $output .= '' . $linktext . ''; 104 | break; 105 | } 106 | case 'url': { 107 | $output .= $getter; 108 | break; 109 | } 110 | default: { 111 | $output .= '' . $linktext . ''; 112 | break; 113 | } 114 | } 115 | 116 | return $output; 117 | 118 | } 119 | -------------------------------------------------------------------------------- /tests/unit/test-post.php: -------------------------------------------------------------------------------- 1 | id = $this->factory->post->create(); 19 | $this->post = get_post( $this->id ); 20 | 21 | $this->fetch 22 | ->shouldReceive( 'repository' ) 23 | ->andReturn( 'owner/repo' ); 24 | } 25 | 26 | public function test_should_return_correct_directory() { 27 | $post = new WordPress_GitHub_Sync_Post( $this->id, $this->api ); 28 | 29 | $this->assertEquals( '_posts/', $post->github_directory() ); 30 | } 31 | 32 | public function test_should_get_post_name() { 33 | $post = new WordPress_GitHub_Sync_Post( $this->id, $this->api ); 34 | 35 | $this->assertEquals( get_post( $this->id )->post_name, $post->name() ); 36 | } 37 | 38 | public function test_should_build_github_content() { 39 | $post = new WordPress_GitHub_Sync_Post( $this->id, $this->api ); 40 | 41 | $this->assertStringStartsWith( '---', $post->github_content() ); 42 | $this->assertStringEndsWith( $this->post->post_content, $post->github_content() ); 43 | } 44 | 45 | public function test_should_build_github_view_url() { 46 | $post = new WordPress_GitHub_Sync_Post( $this->id, $this->api ); 47 | 48 | $this->assertEquals( 'https://github.com/owner/repo/blob/master/_posts/' . get_the_date( 'Y-m-d-', $this->id ) . $this->post->post_name . '.md', $post->github_view_url() ); 49 | } 50 | 51 | public function test_should_build_github_edit_url() { 52 | $post = new WordPress_GitHub_Sync_Post( $this->id, $this->api ); 53 | 54 | $this->assertEquals( 'https://github.com/owner/repo/edit/master/_posts/' . get_the_date( 'Y-m-d-', $this->id ) . $this->post->post_name . '.md', $post->github_edit_url() ); 55 | } 56 | 57 | public function test_should_export_unpublished_to_drafts_folder() { 58 | $id = $this->factory->post->create( array( 'post_status' => 'draft' ) ); 59 | $post = new WordPress_GitHub_Sync_Post( $id, $this->api ); 60 | 61 | $this->assertEquals( '_drafts/', $post->github_directory() ); 62 | } 63 | 64 | public function test_should_export_published_post_to_posts_folder() { 65 | $id = $this->factory->post->create(); 66 | $post = new WordPress_GitHub_Sync_Post( $id, $this->api ); 67 | 68 | $this->assertEquals( '_posts/', $post->github_directory() ); 69 | } 70 | 71 | public function test_should_export_published_page_to_pages_folder() { 72 | $id = $this->factory->post->create( array( 'post_type' => 'page' ) ); 73 | $post = new WordPress_GitHub_Sync_Post( $id, $this->api ); 74 | 75 | $this->assertEquals( '_pages/', $post->github_directory() ); 76 | } 77 | 78 | public function test_should_export_published_unknown_post_type_to_root() { 79 | global $wp_version; 80 | 81 | if ( version_compare( $wp_version, '4.0', '<' ) && is_multisite() ) { 82 | $this->markTestSkipped( "Can't create post with unregistered type in Multisite v3.9." ); 83 | } 84 | 85 | $id = $this->factory->post->create( array( 'post_type' => 'widget' ) ); 86 | $post = new WordPress_GitHub_Sync_Post( $id, $this->api ); 87 | 88 | $this->assertEquals( '', $post->github_directory() ); 89 | } 90 | 91 | public function test_should_export_published_post_type_to_plural_folder() { 92 | register_post_type( 'widget', array( 93 | 'labels' => array( 'name' => 'Widgets' ), 94 | ) ); 95 | $id = $this->factory->post->create( array( 'post_type' => 'widget' ) ); 96 | $post = new WordPress_GitHub_Sync_Post( $id, $this->api ); 97 | 98 | $this->assertEquals( '_widgets/', $post->github_directory() ); 99 | } 100 | } 101 | 102 | -------------------------------------------------------------------------------- /tests/unit/test-blob.php: -------------------------------------------------------------------------------- 1 | '', 22 | 'utf8' => '', 23 | ); 24 | 25 | public function setUp() { 26 | parent::setUp(); 27 | 28 | $this->blob_data = new stdClass; 29 | $this->blob_data->sha = '1234567890qwertyuiop'; 30 | $this->blob_data->path = '_posts/2015-10-31-new-post.md'; 31 | $this->blob_data->content = base64_encode( << 35 | http://example.org/2015/11/02/hello-world/ 36 | --- 37 | Post content. 38 | MD 39 | ); 40 | $this->blob_data->encoding = 'base64'; 41 | 42 | $this->blob = new WordPress_GitHub_Sync_Blob( $this->blob_data ); 43 | 44 | $this->content['utf8'] = <<content['base64'] = base64_encode( $this->content['utf8'] ); 51 | } 52 | 53 | public function test_should_create_empty_blob() { 54 | $this->blob = new WordPress_GitHub_Sync_Blob( new stdClass ); 55 | 56 | $this->assertSame( '', $this->blob->sha() ); 57 | $this->assertSame( '', $this->blob->path() ); 58 | $this->assertSame( '', $this->blob->content() ); 59 | $this->assertSame( '', $this->blob->content_import() ); 60 | $this->assertEmpty( $this->blob->meta() ); 61 | $this->assertFalse( $this->blob->has_frontmatter() ); 62 | } 63 | 64 | public function test_should_interpret_data() { 65 | $this->assertSame( $this->blob_data->sha, $this->blob->sha() ); 66 | $this->assertSame( $this->blob_data->path, $this->blob->path() ); 67 | $this->assertSame( base64_decode( $this->blob_data->content ), $this->blob->content() ); 68 | $this->assertSame( 'Post content.', $this->blob->content_import() ); 69 | $this->assertTrue( $this->blob->has_frontmatter() ); 70 | $this->assertArrayHasKey( 'post_title', $meta = $this->blob->meta() ); 71 | $this->assertSame( 'New Post', $meta['post_title'] ); 72 | $this->assertArrayHasKey( 'permalink', $meta ); 73 | $this->assertSame( '/2015/11/02/hello-world/', $meta['permalink'] ); 74 | } 75 | 76 | public function test_should_generate_body_with_sha() { 77 | $body = $this->blob->to_body(); 78 | 79 | $this->assertInstanceOf( 'stdClass', $body ); 80 | $this->assertSame( '100644', $body->mode ); 81 | $this->assertSame( 'blob', $body->type ); 82 | $this->assertSame( $this->blob_data->path, $body->path ); 83 | $this->assertSame( $this->blob_data->sha, $body->sha ); 84 | } 85 | 86 | public function test_should_generate_body_with_content() { 87 | unset( $this->blob_data->sha ); 88 | $this->blob = new WordPress_GitHub_Sync_Blob( $this->blob_data ); 89 | 90 | $body = $this->blob->to_body(); 91 | 92 | $this->assertInstanceOf( 'stdClass', $body ); 93 | $this->assertSame( '100644', $body->mode ); 94 | $this->assertSame( 'blob', $body->type ); 95 | $this->assertSame( $this->blob_data->path, $body->path ); 96 | $this->assertSame( base64_decode( $this->blob_data->content ), $body->content ); 97 | } 98 | 99 | public function test_should_set_base64_content() { 100 | $this->blob->set_content( $this->content['base64'], true ); 101 | 102 | $this->assertSame( $this->content['utf8'], $this->blob->content() ); 103 | } 104 | 105 | public function test_should_set_utf8_content() { 106 | $this->blob->set_content( $this->content['utf8'], false ); 107 | 108 | $this->assertSame( $this->content['utf8'], $this->blob->content() ); 109 | } 110 | } 111 | 112 | function wpmarkdown_markdown_to_html( $content ) { 113 | return $content; 114 | } -------------------------------------------------------------------------------- /bin/wp2md: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ################################################################################ 4 | # Convert readme files between WordPress Plugin readme and Github markdown format 5 | # This script is used by the deploy-plugin.sh script 6 | # 7 | # Author: Sudar 8 | # 9 | # License: Beerware ;) 10 | # 11 | # Usage: 12 | # ./path/to/readme-converter.sh [from-file] [to-file] [format to-wp|from-wp] 13 | # 14 | # Refer to the README.md file for information about the different options 15 | # 16 | # Credit: Uses most of the code from the following places 17 | # https://github.com/ocean90/svn2git-tools/ 18 | ################################################################################ 19 | 20 | # wrapper for sed 21 | _sed() { 22 | # -E is used so that it is compatible in both Mac and Ubuntu. 23 | sed -E "$1" $2 > $2.tmp && mv $2.tmp $2 24 | } 25 | 26 | # Check if file exists 27 | file_exists () { 28 | if [ ! -f $1 ]; then 29 | echo "$1 doesn't exist" 30 | exit 1; 31 | fi 32 | } 33 | 34 | # Handle screenshots section for WP to Markdown format 35 | ss_wptomd () { 36 | awk ' 37 | BEGIN { # Set the field separator to a . for picking up line num 38 | FS = "." 39 | } 40 | /^==/ { # If we hit a new section stop 41 | flag = 0 42 | } 43 | NF && flag && $1 ~ /^[0-9]+$/ { # If the line is not empty and the flag is set add text 44 | print "![](screenshot-" $1 ".png)" 45 | sub(/^[0-9]+. */, "") # Remove the leading line number (no limit on fields) 46 | } 47 | /^== Screenshots ==/ { # If we hit the screenshot section start 48 | flag = 1 49 | } 50 | { # Print all the lines in the file 51 | print 52 | } 53 | ' $1 > $2 54 | } 55 | 56 | # Handle screenshots section for Markdown to WP format 57 | ss_mdtowp () { 58 | awk ' 59 | /^##/ { # If we hit a new section stop 60 | flag = 0 61 | } 62 | NF && flag && $0 ~ /^!\[\]/ { # If the line contains a markdown image 63 | total = split($0, arr, /[-.]/) 64 | num = arr[total - 1] 65 | next 66 | } 67 | flag && num && NF > 1 { # If we have a image number 68 | print num ". " $0 69 | num = 0 70 | next 71 | } 72 | /^## Screenshots ##/ { # If we hit the screenshot section start 73 | flag = 1 74 | } 75 | { # Print all the lines in the file 76 | print 77 | } 78 | ' $1 > $2 79 | } 80 | 81 | # WP to Markdown format 82 | wptomarkdown () { 83 | file_exists $1 84 | 85 | ss_wptomd $1 $2 86 | 87 | PLUGINMETA=("Contributors" "Donate link" "Donate Link" "Tags" "Requires at least" "Tested up to" "Stable tag" "License" "License URI" "Requires base plugin" "Requires base plugin version") 88 | for m in "${PLUGINMETA[@]}" 89 | do 90 | _sed 's/^'"$m"':/**'"$m"':**/g' $2 91 | done 92 | 93 | _sed "s/===([^=]+)===/#\1#/g" $2 94 | _sed "s/==([^=]+)==/##\1##/g" $2 95 | _sed "s/=([^=]+)=/###\1###/g" $2 96 | } 97 | 98 | # Markdown to WP format 99 | markdowntowp () { 100 | file_exists $1 101 | 102 | ss_mdtowp $1 $2 103 | 104 | PLUGINMETA=("Contributors" "Donate link" "Donate Link" "Tags" "Requires at least" "Tested up to" "Stable tag" "License" "License URI" "Requires base plugin" "Requires base plugin version") 105 | for m in "${PLUGINMETA[@]}" 106 | do 107 | _sed 's/^(\*\*|__)'"$m"':(\*\*|__)/'"$m"':/g' $2 108 | done 109 | 110 | echo "" >> $2 111 | cat CHANGELOG.md >> $2 112 | 113 | _sed "s/#### ([^#]+) ####/**\1**\\`echo -e '\n\r'`/g" $2 114 | _sed "s/###([^#]+)###/=\1=/g" $2 115 | _sed "s/##([^#]+)##/==\1==/g" $2 116 | _sed "s/#([^#]+)#/===\1===/g" $2 117 | _sed "s/\[([^#]+)\]\[([^#]+)\]/\1/g" $2 118 | } 119 | 120 | if [ $# -eq 3 ]; then 121 | 122 | if [ "$3" == "to-wp" ]; then 123 | markdowntowp $1 $2 124 | else 125 | wptomarkdown $1 $2 126 | fi 127 | 128 | else 129 | echo >&2 \ 130 | "usage: $0 [from-file] [to-file] [format to-wp|from-wp]" 131 | 132 | exit 1; 133 | fi 134 | -------------------------------------------------------------------------------- /lib/client/fetch.php: -------------------------------------------------------------------------------- 1 | call( 'GET', $this->reference_endpoint() ); 19 | 20 | if ( is_wp_error( $data ) ) { 21 | return $data; 22 | } 23 | 24 | return $this->commit( $data->object->sha ); 25 | } 26 | 27 | /** 28 | * Retrieves a commit by sha from the GitHub API 29 | * 30 | * @param string $sha Sha for commit to retrieve. 31 | * 32 | * @return WordPress_GitHub_Sync_Commit|WP_Error 33 | */ 34 | public function commit( $sha ) { 35 | if ( $cache = $this->app->cache()->fetch_commit( $sha ) ) { 36 | return $cache; 37 | } 38 | 39 | $data = $this->call( 'GET', $this->commit_endpoint() . '/' . $sha ); 40 | 41 | if ( is_wp_error( $data ) ) { 42 | return $data; 43 | } 44 | 45 | $commit = new WordPress_GitHub_Sync_Commit( $data ); 46 | $tree = $this->tree_recursive( $commit->tree_sha() ); 47 | 48 | if ( is_wp_error( $tree ) ) { 49 | return $tree; 50 | } 51 | 52 | $commit->set_tree( $tree ); 53 | 54 | return $this->app->cache()->set_commit( $sha, $commit ); 55 | } 56 | 57 | /** 58 | * Calls the content API to get the post's contents and metadata 59 | * 60 | * Returns Object the response from the API 61 | * 62 | * @param WordPress_GitHub_Sync_Post $post Post to retrieve remote contents for. 63 | * 64 | * @return mixed 65 | */ 66 | public function remote_contents( $post ) { 67 | return $this->call( 'GET', $this->content_endpoint() . $post->github_path() ); 68 | } 69 | 70 | /** 71 | * Retrieves a tree by sha recursively from the GitHub API 72 | * 73 | * @param string $sha Commit sha to retrieve tree from. 74 | * 75 | * @return WordPress_GitHub_Sync_Tree|WP_Error 76 | */ 77 | protected function tree_recursive( $sha ) { 78 | if ( $cache = $this->app->cache()->fetch_tree( $sha ) ) { 79 | return $cache; 80 | } 81 | 82 | $data = $this->call( 'GET', $this->tree_endpoint() . '/' . $sha . '?recursive=1' ); 83 | 84 | if ( is_wp_error( $data ) ) { 85 | return $data; 86 | } 87 | 88 | foreach ( $data->tree as $index => $thing ) { 89 | // We need to remove the trees because 90 | // the recursive tree includes both 91 | // the subtrees as well the subtrees' blobs. 92 | if ( 'tree' === $thing->type ) { 93 | unset( $data->tree[ $index ] ); 94 | } 95 | } 96 | 97 | $tree = new WordPress_GitHub_Sync_Tree( $data ); 98 | $tree->set_blobs( $this->blobs( $data->tree ) ); 99 | 100 | return $this->app->cache()->set_tree( $sha, $tree ); 101 | } 102 | 103 | /** 104 | * Generates blobs for recursive tree blob data. 105 | * 106 | * @param stdClass[] $blobs Array of tree blob data. 107 | * 108 | * @return WordPress_GitHub_Sync_Blob[] 109 | */ 110 | protected function blobs( array $blobs ) { 111 | $results = array(); 112 | 113 | foreach ( $blobs as $blob ) { 114 | $obj = $this->blob( $blob ); 115 | 116 | if ( ! is_wp_error( $obj ) ) { 117 | $results[] = $obj; 118 | } 119 | } 120 | 121 | return $results; 122 | } 123 | 124 | /** 125 | * Retrieves the blob data for a given sha 126 | * 127 | * @param stdClass $blob Tree blob data. 128 | * 129 | * @return WordPress_GitHub_Sync_Blob|WP_Error 130 | */ 131 | protected function blob( $blob ) { 132 | if ( $cache = $this->app->cache()->fetch_blob( $blob->sha ) ) { 133 | return $cache; 134 | } 135 | 136 | $data = $this->call( 'GET', $this->blob_endpoint() . '/' . $blob->sha ); 137 | 138 | if ( is_wp_error( $data ) ) { 139 | return $data; 140 | } 141 | 142 | $data->path = $blob->path; 143 | $obj = new WordPress_GitHub_Sync_Blob( $data ); 144 | 145 | return $this->app->cache()->set_blob( $obj->sha(), $obj ); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /lib/cli.php: -------------------------------------------------------------------------------- 1 | app = WordPress_GitHub_Sync::$instance; 24 | } 25 | 26 | /** 27 | * Exports an individual post 28 | * all your posts to GitHub 29 | * 30 | * ## OPTIONS 31 | * 32 | * 33 | * : The post ID to export or 'all' for full site 34 | * 35 | * 36 | * : The user ID you'd like to save the commit as 37 | * 38 | * ## EXAMPLES 39 | * 40 | * wp wpghs export all 1 41 | * wp wpghs export 1 1 42 | * 43 | * @synopsis 44 | * 45 | * @param array $args Command arguments. 46 | */ 47 | public function export( $args ) { 48 | list( $post_id, $user_id ) = $args; 49 | 50 | if ( ! is_numeric( $user_id ) ) { 51 | WP_CLI::error( __( 'Invalid user ID', 'wp-github-sync' ) ); 52 | } 53 | 54 | $this->app->export()->set_user( $user_id ); 55 | 56 | if ( 'all' === $post_id ) { 57 | WP_CLI::line( __( 'Starting full export to GitHub.', 'wp-github-sync' ) ); 58 | $this->app->controller()->export_all(); 59 | } elseif ( is_numeric( $post_id ) ) { 60 | WP_CLI::line( 61 | sprintf( 62 | __( 'Exporting post ID to GitHub: %d', 'wp-github-sync' ), 63 | $post_id 64 | ) 65 | ); 66 | $this->app->controller()->export_post( (int) $post_id ); 67 | } else { 68 | WP_CLI::error( __( 'Invalid post ID', 'wp-github-sync' ) ); 69 | } 70 | } 71 | 72 | /** 73 | * Imports the post in your GitHub repo 74 | * into your WordPress blog 75 | * 76 | * ## OPTIONS 77 | * 78 | * 79 | * : The user ID you'd like to save the commit as 80 | * 81 | * ## EXAMPLES 82 | * 83 | * wp wpghs import 1 84 | * 85 | * @synopsis 86 | * 87 | * @param array $args Command arguments. 88 | */ 89 | public function import( $args ) { 90 | list( $user_id ) = $args; 91 | 92 | if ( ! is_numeric( $user_id ) ) { 93 | WP_CLI::error( __( 'Invalid user ID', 'wp-github-sync' ) ); 94 | } 95 | 96 | update_option( '_wpghs_export_user_id', (int) $user_id ); 97 | 98 | WP_CLI::line( __( 'Starting import from GitHub.', 'wp-github-sync' ) ); 99 | 100 | $this->app->controller()->import_master(); 101 | } 102 | 103 | /** 104 | * Fetches the provided sha or the repository's 105 | * master branch and caches it. 106 | * 107 | * ## OPTIONS 108 | * 109 | * 110 | * : The user ID you'd like to save the commit as 111 | * 112 | * ## EXAMPLES 113 | * 114 | * wp wpghs prime --branch=master 115 | * wp wpghs prime --sha= 116 | * 117 | * @synopsis [--sha=] [--branch] 118 | * 119 | * @param array $args Command arguments. 120 | * @param array $assoc_args Command associated arguments. 121 | */ 122 | public function prime( $args, $assoc_args ) { 123 | if ( isset( $assoc_args['branch'] ) ) { 124 | WP_CLI::line( __( 'Starting branch import.', 'wp-github-sync' ) ); 125 | 126 | $commit = $this->app->api()->fetch()->master(); 127 | 128 | if ( is_wp_error( $commit ) ) { 129 | WP_CLI::error( 130 | sprintf( 131 | __( 'Failed to import and cache branch with error: %s', 'wp-github-sync' ), 132 | $commit->get_error_message() 133 | ) 134 | ); 135 | } else { 136 | WP_CLI::success( 137 | sprintf( 138 | __( 'Successfully imported and cached commit %s from branch.', 'wp-github-sync' ), 139 | $commit->sha() 140 | ) 141 | ); 142 | } 143 | } else if ( isset( $assoc_args['sha'] ) ) { 144 | WP_CLI::line( 'Starting sha import.' ); 145 | 146 | $commit = $this->app->api()->fetch()->commit( $assoc_args['sha'] ); 147 | 148 | WP_CLI::success( 149 | sprintf( 150 | __( 'Successfully imported and cached commit %s.', 'wp-github-sync' ), 151 | $commit->sha() 152 | ) 153 | ); 154 | } else { 155 | WP_CLI::error( 'Invalid fetch.' ); 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /bin/deploy: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # built on: 4 | # https://github.com/ocean90/svn2git-tools/blob/master/automated-wordpress-plugin-deployment/deploy.sh 5 | # http://scribu.net/blog/deploying-from-git-to-svn.html 6 | 7 | # main config 8 | 9 | # users 10 | GIT_USER="mAAdhaTTah" 11 | SVN_USER="JamesDiGioia" 12 | 13 | # dirs 14 | SRC_DIR=$(git rev-parse --show-toplevel) 15 | SLUG=$(basename $SRC_DIR) 16 | SVN_DIR="/tmp/$SLUG/trunk" 17 | 18 | # repos 19 | SVN_URL="http://plugins.svn.wordpress.org/$SLUG" 20 | 21 | # Let's begin... 22 | echo 23 | echo "Deploy WordPress plugin" 24 | echo "=======================" 25 | echo 26 | 27 | # Check version in README is the same as plugin file 28 | # translates both to unix line breaks to work around grep's failure to identify mac line breaks 29 | NEWVERSION1=`grep "Stable tag:" "$SRC_DIR/README.md" | awk -F' ' '{print $NF}'` 30 | echo "README version: $NEWVERSION1" 31 | NEWVERSION2=`grep "Version:" "$SRC_DIR/$SLUG.php" | awk -F' ' '{print $NF}'` 32 | echo "$SLUG.php version: $NEWVERSION2" 33 | 34 | if [ "$NEWVERSION1" != "$NEWVERSION2" ];then 35 | echo "Version in README & $SLUG.php don't match. Please fix and try again." 36 | exit 1 37 | fi 38 | 39 | # Also, make sure to check: 40 | # constant 41 | # CHANGELOG 42 | # @todo: something about this - checklist? 43 | 44 | echo "Versions match in README and $SLUG.php. Let's proceed..." 45 | 46 | # Check if version has already been deployed 47 | if git show-ref --quiet --tags --verify -- "refs/tags/$NEWVERSION1" 48 | then 49 | echo "Version $NEWVERSION1 already exists as git tag. Proceeding..." 50 | else 51 | # create git tag 52 | echo -n "Version $NEWVERSION1 does not exist. Creating and pushing new version tag to GitHub..." 53 | git tag $NEWVERSION1 54 | git push --quiet 55 | git push --tags --quiet 56 | echo "Done." 57 | fi 58 | 59 | # Check if working directory is clean 60 | if [ ! -z "$(git status --porcelain)" ]; then 61 | echo "There appears to be uncommitted changes in your working directory. Please commit or stash them and try again." 62 | exit 1 63 | fi 64 | 65 | # prep tmp folder for deploy 66 | if [ -d $SVN_DIR ]; then 67 | echo -n "Cleaning up previous deployment..." 68 | rm -Rf $SVN_DIR 69 | echo "Done." 70 | fi 71 | 72 | # checkout svn repo 73 | echo -n "Creating local copy of SVN repo..." 74 | svn checkout --quiet $SVN_URL/trunk $SVN_DIR 75 | echo "Done." 76 | 77 | # clean svn repo 78 | echo -n "Cleaning local copy of SVN repo..." 79 | for file in $(find $SVN_DIR/* -type f -and -not -path "*.svn*") 80 | do 81 | rm $file 82 | done 83 | find /tmp/$SLUG/trunk -type d -and -not -path "*.svn*" -empty -delete 84 | echo "Done." 85 | 86 | # copy current plugin to svn dir 87 | echo -n "Copying git files to SVN repo..." 88 | git checkout-index --quiet --all --force --prefix=$SVN_DIR/ 89 | echo "Done." 90 | 91 | cd $SVN_DIR/ 92 | 93 | # install npm, bower, and composer dependencies 94 | echo -n "Installing dependencies..." 95 | composer install --quiet --no-dev --optimize-autoloader &>/dev/null 96 | echo "Done." 97 | 98 | # transform the readme 99 | if [ -f README.md ]; then 100 | echo -n "Converting the README to WordPress format..." 101 | $SVN_DIR/bin/wp2md $SVN_DIR/README.md $SVN_DIR/README.txt to-wp 102 | echo "Done." 103 | fi 104 | 105 | # remove unneeded files via .svnignore 106 | echo -n "Removing unwanted development files using .svnignore..." 107 | for file in $(cat "$SVN_DIR/.svnignore" 2> /dev/null) 108 | do 109 | rm -rf $SVN_DIR/$file 110 | done 111 | echo "Done." 112 | 113 | # build release zip 114 | echo -n "Building production release zip..." 115 | zip -r $SLUG * --quiet 116 | mv $SLUG.zip /tmp/$SLUG.zip 117 | # todo: upload this zip directly to the GitHub release 118 | echo "Done. Zip file is in /tmp." 119 | 120 | # svn addremove 121 | echo "Adding new commit to SVN..." 122 | svn stat | awk '/^\?/ {print $2}' | xargs svn add > /dev/null 2>&1 123 | svn stat | awk '/^\M/ {print $2}' | xargs svn add > /dev/null 2>&1 124 | svn stat | awk '/^\!/ {print $2}' | xargs svn rm --force > /dev/null 2>&1 125 | 126 | svn ci --username=$SVN_USER -m "Deploy version $NEWVERSION1 to .org" -q 127 | 128 | echo "Done." 129 | 130 | echo -n "Deploying new tag to SVN repo..." 131 | svn copy $SVN_URL/trunk $SVN_URL/tags/$NEWVERSION1 -m "Release $NEWVERSION1" -q 132 | echo "Done." 133 | 134 | echo "Finished deploying $SLUG." 135 | -------------------------------------------------------------------------------- /tests/unit/test-commit.php: -------------------------------------------------------------------------------- 1 | sha = '1234567890qwertyuiop'; 16 | $data->url = 'https://api.github.com/path/to/endpoint'; 17 | $data->author = new stdClass; 18 | $data->author->email = 'jamesorodig@gmail.com'; 19 | $data->committer = new stdClass; 20 | $data->message = 'Commit message'; 21 | $data->parents = array( '0987654321poiuytrewq' ); 22 | $data->tree = new stdClass; 23 | $data->tree->sha = 'zxcvbnmasdfghjklpoiuytrewq1234567876543'; 24 | 25 | $commit = new WordPress_GitHub_Sync_Commit( $data ); 26 | 27 | $this->assertSame( $data->sha, $commit->sha() ); 28 | $this->assertSame( $data->url, $commit->url() ); 29 | $this->assertSame( $data->author, $commit->author() ); 30 | $this->assertSame( $data->author->email, $commit->author_email() ); 31 | $this->assertSame( $data->committer, $commit->committer() ); 32 | $this->assertSame( $data->message, $commit->message() ); 33 | $this->assertSame( $data->parents, $commit->parents() ); 34 | $this->assertSame( $data->tree->sha, $commit->tree_sha() ); 35 | 36 | $body = $commit->to_body(); 37 | 38 | $this->assertSame( $data->tree->sha, $body['tree'] ); 39 | $this->assertSame( $data->message, $body['message'] ); 40 | $this->assertSame( $data->parents, $body['parents'] ); 41 | } 42 | 43 | public function test_should_return_empty_commit() { 44 | $commit = new WordPress_GitHub_Sync_Commit( new stdClass ); 45 | 46 | $this->assertSame( '', $commit->sha() ); 47 | $this->assertSame( '', $commit->url() ); 48 | $this->assertFalse( $commit->author() ); 49 | $this->assertSame( '', $commit->author_email() ); 50 | $this->assertFalse( $commit->committer() ); 51 | $this->assertSame( '', $commit->message() ); 52 | $this->assertEmpty( $commit->parents() ); 53 | $this->assertSame( '', $commit->tree_sha() ); 54 | } 55 | 56 | public function test_should_be_unsynced_without_wpghs() { 57 | $commit = new WordPress_GitHub_Sync_Commit( new stdClass ); 58 | $commit->set_message( 'Commit message.' ); 59 | 60 | $this->assertFalse( $commit->already_synced() ); 61 | } 62 | 63 | public function test_should_be_synced_with_wpghs() { 64 | $commit = new WordPress_GitHub_Sync_Commit( new stdClass ); 65 | $commit->set_message( 'Commit message. - wpghs' ); 66 | 67 | $this->assertTrue( $commit->already_synced() ); 68 | } 69 | 70 | public function test_should_set_and_retrieve_tree() { 71 | $commit = new WordPress_GitHub_Sync_Commit( new stdClass ); 72 | $commit->set_tree( $this->tree ); 73 | $sha = 'qwsadjflkajsdf'; 74 | $this->tree 75 | ->shouldReceive( 'sha' ) 76 | ->andReturn( $sha ); 77 | 78 | $this->assertSame( $this->tree, $commit->tree() ); 79 | $this->assertSame( $sha, $commit->tree_sha() ); 80 | } 81 | 82 | public function test_should_set_self_to_parent_on_message_update() { 83 | $sha = '1233567890fghjkaisdfasd'; 84 | $data = new stdClass; 85 | $data->sha = $sha; 86 | 87 | $commit = new WordPress_GitHub_Sync_Commit( $data ); 88 | $commit->set_message( 'New message.' ); 89 | 90 | $this->assertCount( 1, $parents = $commit->parents() ); 91 | $this->assertSame( $sha, $parents[0] ); 92 | } 93 | 94 | public function test_should_set_self_to_parent_on_author_update() { 95 | $sha = '1233567890fghjkaisdfasd'; 96 | $data = new stdClass; 97 | $data->sha = $sha; 98 | 99 | $commit = new WordPress_GitHub_Sync_Commit( $data ); 100 | $commit->set_author( new stdClass ); 101 | 102 | $this->assertCount( 1, $parents = $commit->parents() ); 103 | $this->assertSame( $sha, $parents[0] ); 104 | } 105 | 106 | public function test_should_set_self_to_parent_on_committer_update() { 107 | $sha = '1233567890fghjkaisdfasd'; 108 | $data = new stdClass; 109 | $data->sha = $sha; 110 | 111 | $commit = new WordPress_GitHub_Sync_Commit( $data ); 112 | $commit->set_committer( new stdClass ); 113 | 114 | $this->assertCount( 1, $parents = $commit->parents() ); 115 | $this->assertSame( $sha, $parents[0] ); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /lib/blob.php: -------------------------------------------------------------------------------- 1 | data = $data; 54 | 55 | $this->interpret_data(); 56 | } 57 | 58 | /** 59 | * Returns the raw blob content. 60 | * 61 | * @return string 62 | */ 63 | public function content() { 64 | return $this->content; 65 | } 66 | 67 | /** 68 | * Set's the blob's content. 69 | * 70 | * @param string $content Raw blob content. 71 | * @param bool $base64 Whether the content is base64 encoded. 72 | * 73 | * @return $this 74 | */ 75 | public function set_content( $content, $base64 = false ) { 76 | if ( $base64 ) { 77 | $content = base64_decode( $content ); 78 | } 79 | 80 | $this->frontmatter = '---' === substr( $this->content = $content, 0, 3 ) ? true : false; 81 | 82 | return $this; 83 | } 84 | /** 85 | * Returns the blob sha. 86 | * 87 | * @return string 88 | */ 89 | public function sha() { 90 | return $this->sha; 91 | } 92 | 93 | /** 94 | * Return's the blob path. 95 | * 96 | * @return string 97 | */ 98 | public function path() { 99 | return $this->path; 100 | } 101 | 102 | /** 103 | * Whether the blob has frontmatter. 104 | * 105 | * @return bool 106 | */ 107 | public function has_frontmatter() { 108 | return $this->frontmatter; 109 | } 110 | 111 | /** 112 | * Returns the formatted/filtered blob content used for import. 113 | * 114 | * @return string 115 | */ 116 | public function content_import() { 117 | $content = $this->content(); 118 | 119 | if ( $this->has_frontmatter() ) { 120 | // Break out content. 121 | preg_match( '/(^---(.*?)---$)?(.*)/ms', $content, $matches ); 122 | $content = array_pop( $matches ); 123 | } 124 | 125 | if ( function_exists( 'wpmarkdown_markdown_to_html' ) ) { 126 | $content = wpmarkdown_markdown_to_html( $content ); 127 | } 128 | 129 | /** 130 | * Filters the content for import. 131 | */ 132 | return apply_filters( 'wpghs_content_import', trim( $content ) ); 133 | } 134 | 135 | /** 136 | * Returns the blob meta. 137 | * 138 | * @return array 139 | */ 140 | public function meta() { 141 | $meta = array(); 142 | 143 | if ( $this->has_frontmatter() ) { 144 | // Break out meta, if present. 145 | preg_match( '/(^---(.*?)---$)?(.*)/ms', $this->content(), $matches ); 146 | array_pop( $matches ); 147 | 148 | $meta = spyc_load( $matches[2] ); 149 | if ( isset( $meta['permalink'] ) ) { 150 | $meta['permalink'] = str_replace( home_url(), '', $meta['permalink'] ); 151 | } 152 | } 153 | 154 | return $meta; 155 | } 156 | 157 | /** 158 | * Formats the blob into an API call body. 159 | * 160 | * @return stdClass 161 | */ 162 | public function to_body() { 163 | $data = new stdClass; 164 | 165 | $data->mode = '100644'; 166 | $data->type = 'blob'; 167 | 168 | $data->path = $this->path(); 169 | 170 | if ( $this->sha() ) { 171 | $data->sha = $this->sha(); 172 | } else { 173 | $data->content = $this->content(); 174 | } 175 | 176 | return $data; 177 | } 178 | 179 | /** 180 | * Interprets the blob's data into properties. 181 | */ 182 | protected function interpret_data() { 183 | $this->sha = isset( $this->data->sha ) ? $this->data->sha : ''; 184 | $this->path = isset( $this->data->path ) ? $this->data->path : ''; 185 | 186 | $this->set_content( 187 | isset( $this->data->content ) ? trim( $this->data->content ) : '', 188 | isset( $this->data->encoding ) && 'base64' === $this->data->encoding ? true : false 189 | ); 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /bin/install-wp-tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ $# -lt 3 ]; then 4 | echo "usage: $0 [db-host] [wp-version] [skip-database-creation]" 5 | exit 1 6 | fi 7 | 8 | DB_NAME=$1 9 | DB_USER=$2 10 | DB_PASS=$3 11 | DB_HOST=${4-localhost} 12 | WP_VERSION=${5-latest} 13 | SKIP_DB_CREATE=${6-false} 14 | 15 | WP_TESTS_DIR=${WP_TESTS_DIR-/tmp/wordpress-tests-lib} 16 | WP_CORE_DIR=${WP_CORE_DIR-/tmp/wordpress/} 17 | 18 | download() { 19 | if [ `which curl` ]; then 20 | curl -s "$1" > "$2"; 21 | elif [ `which wget` ]; then 22 | wget -nv -O "$2" "$1" 23 | fi 24 | } 25 | 26 | if [[ $WP_VERSION =~ [0-9]+\.[0-9]+(\.[0-9]+)? ]]; then 27 | WP_TESTS_TAG="tags/$WP_VERSION" 28 | elif [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then 29 | WP_TESTS_TAG="trunk" 30 | else 31 | # http serves a single offer, whereas https serves multiple. we only want one 32 | download http://api.wordpress.org/core/version-check/1.7/ /tmp/wp-latest.json 33 | grep '[0-9]+\.[0-9]+(\.[0-9]+)?' /tmp/wp-latest.json 34 | LATEST_VERSION=$(grep -o '"version":"[^"]*' /tmp/wp-latest.json | sed 's/"version":"//') 35 | if [[ -z "$LATEST_VERSION" ]]; then 36 | echo "Latest WordPress version could not be found" 37 | exit 1 38 | fi 39 | WP_TESTS_TAG="tags/$LATEST_VERSION" 40 | fi 41 | 42 | set -ex 43 | 44 | install_wp() { 45 | 46 | if [ -d $WP_CORE_DIR ]; then 47 | return; 48 | fi 49 | 50 | mkdir -p $WP_CORE_DIR 51 | 52 | if [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then 53 | mkdir -p /tmp/wordpress-nightly 54 | download https://wordpress.org/nightly-builds/wordpress-latest.zip /tmp/wordpress-nightly/wordpress-nightly.zip 55 | unzip -q /tmp/wordpress-nightly/wordpress-nightly.zip -d /tmp/wordpress-nightly/ 56 | mv /tmp/wordpress-nightly/wordpress/* $WP_CORE_DIR 57 | else 58 | if [ $WP_VERSION == 'latest' ]; then 59 | local ARCHIVE_NAME='latest' 60 | else 61 | local ARCHIVE_NAME="wordpress-$WP_VERSION" 62 | fi 63 | download https://wordpress.org/${ARCHIVE_NAME}.tar.gz /tmp/wordpress.tar.gz 64 | tar --strip-components=1 -zxmf /tmp/wordpress.tar.gz -C $WP_CORE_DIR 65 | fi 66 | 67 | download https://raw.github.com/markoheijnen/wp-mysqli/master/db.php $WP_CORE_DIR/wp-content/db.php 68 | } 69 | 70 | install_test_suite() { 71 | # portable in-place argument for both GNU sed and Mac OSX sed 72 | if [[ $(uname -s) == 'Darwin' ]]; then 73 | local ioption='-i .bak' 74 | else 75 | local ioption='-i' 76 | fi 77 | 78 | # set up testing suite if it doesn't yet exist 79 | if [ ! -d $WP_TESTS_DIR ]; then 80 | # set up testing suite 81 | mkdir -p $WP_TESTS_DIR 82 | svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/includes/ $WP_TESTS_DIR/includes 83 | svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/data/ $WP_TESTS_DIR/data 84 | fi 85 | 86 | if [ ! -f wp-tests-config.php ]; then 87 | download https://develop.svn.wordpress.org/${WP_TESTS_TAG}/wp-tests-config-sample.php "$WP_TESTS_DIR"/wp-tests-config.php 88 | # remove all forward slashes in the end 89 | WP_CORE_DIR=$(echo $WP_CORE_DIR | sed "s:/\+$::") 90 | sed $ioption "s:dirname( __FILE__ ) . '/src/':'$WP_CORE_DIR/':" "$WP_TESTS_DIR"/wp-tests-config.php 91 | sed $ioption "s/youremptytestdbnamehere/$DB_NAME/" "$WP_TESTS_DIR"/wp-tests-config.php 92 | sed $ioption "s/yourusernamehere/$DB_USER/" "$WP_TESTS_DIR"/wp-tests-config.php 93 | sed $ioption "s/yourpasswordhere/$DB_PASS/" "$WP_TESTS_DIR"/wp-tests-config.php 94 | sed $ioption "s|localhost|${DB_HOST}|" "$WP_TESTS_DIR"/wp-tests-config.php 95 | fi 96 | 97 | } 98 | 99 | install_db() { 100 | 101 | if [ ${SKIP_DB_CREATE} = "true" ]; then 102 | return 0 103 | fi 104 | 105 | # parse DB_HOST for port or socket references 106 | local PARTS=(${DB_HOST//\:/ }) 107 | local DB_HOSTNAME=${PARTS[0]}; 108 | local DB_SOCK_OR_PORT=${PARTS[1]}; 109 | local EXTRA="" 110 | 111 | if ! [ -z $DB_HOSTNAME ] ; then 112 | if [ $(echo $DB_SOCK_OR_PORT | grep -e '^[0-9]\{1,\}$') ]; then 113 | EXTRA=" --host=$DB_HOSTNAME --port=$DB_SOCK_OR_PORT --protocol=tcp" 114 | elif ! [ -z $DB_SOCK_OR_PORT ] ; then 115 | EXTRA=" --socket=$DB_SOCK_OR_PORT" 116 | elif ! [ -z $DB_HOSTNAME ] ; then 117 | EXTRA=" --host=$DB_HOSTNAME --protocol=tcp" 118 | fi 119 | fi 120 | 121 | # create database 122 | mysqladmin create $DB_NAME --user="$DB_USER" --password="$DB_PASS"$EXTRA 123 | } 124 | 125 | install_wp 126 | install_test_suite 127 | install_db 128 | -------------------------------------------------------------------------------- /tests/include/testcase.php: -------------------------------------------------------------------------------- 1 | data_dir = dirname( __DIR__ ) . '/data/'; 99 | 100 | $this->app = Mockery::mock( 'WordPress_GitHub_Sync' ); 101 | $this->controller = Mockery::mock( 'WordPress_GitHub_Sync_Controller' ); 102 | $this->request = Mockery::mock( 'WordPress_GitHub_Sync_Request' ); 103 | $this->import = Mockery::mock( 'WordPress_GitHub_Sync_Import' ); 104 | $this->export = Mockery::mock( 'WordPress_GitHub_Sync_Export' ); 105 | $this->response = Mockery::mock( 'WordPress_GitHub_Sync_Response' ); 106 | $this->payload = Mockery::mock( 'WordPress_GitHub_Sync_Payload' ); 107 | $this->api = Mockery::mock( 'WordPress_GitHub_Sync_Api' ); 108 | $this->commit = Mockery::mock( 'WordPress_GitHub_Sync_Commit' ); 109 | $this->semaphore = Mockery::mock( 'WordPress_GitHub_Sync_Semaphore' ); 110 | $this->database = Mockery::mock( 'WordPress_GitHub_Sync_Database' ); 111 | $this->post = Mockery::mock( 'WordPress_GitHub_Sync_Post' ); 112 | $this->tree = Mockery::mock( 'WordPress_GitHub_Sync_Tree' ); 113 | $this->blob = Mockery::mock( 'WordPress_GitHub_Sync_Blob' ); 114 | $this->api_cache = Mockery::mock( 'WordPress_GitHub_Sync_Cache' ); 115 | $this->fetch = Mockery::mock( 'WordPress_GitHub_Sync_Fetch_Client' ); 116 | $this->persist = Mockery::mock( 'WordPress_GitHub_Sync_Persist_Client' ); 117 | 118 | WordPress_GitHub_Sync::$instance = $this->app; 119 | 120 | $this->app 121 | ->shouldReceive( 'request' ) 122 | ->andReturn( $this->request ) 123 | ->byDefault(); 124 | $this->app 125 | ->shouldReceive( 'import' ) 126 | ->andReturn( $this->import ) 127 | ->byDefault(); 128 | $this->app 129 | ->shouldReceive( 'export' ) 130 | ->andReturn( $this->export ) 131 | ->byDefault(); 132 | $this->app 133 | ->shouldReceive( 'response' ) 134 | ->andReturn( $this->response ) 135 | ->byDefault(); 136 | $this->app 137 | ->shouldReceive( 'api' ) 138 | ->andReturn( $this->api ) 139 | ->byDefault(); 140 | $this->app 141 | ->shouldReceive( 'semaphore' ) 142 | ->andReturn( $this->semaphore ) 143 | ->byDefault(); 144 | $this->app 145 | ->shouldReceive( 'database' ) 146 | ->andReturn( $this->database ) 147 | ->byDefault(); 148 | $this->app 149 | ->shouldReceive( 'blob' ) 150 | ->andReturn( $this->blob ) 151 | ->byDefault(); 152 | $this->app 153 | ->shouldReceive( 'cache' ) 154 | ->andReturn( $this->api_cache ) 155 | ->byDefault(); 156 | $this->api 157 | ->shouldReceive( 'fetch' ) 158 | ->andReturn( $this->fetch ) 159 | ->byDefault(); 160 | $this->api 161 | ->shouldReceive( 'persist' ) 162 | ->andReturn( $this->persist ) 163 | ->byDefault(); 164 | } 165 | 166 | public function tearDown() { 167 | parent::tearDown(); 168 | Mockery::close(); 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /lib/client/base.php: -------------------------------------------------------------------------------- 1 | app = $app; 30 | } 31 | 32 | /** 33 | * Generic GitHub API interface and response handler 34 | * 35 | * @param string $method HTTP method. 36 | * @param string $endpoint API endpoint. 37 | * @param array $body Request body. 38 | * 39 | * @return stdClass|WP_Error 40 | */ 41 | protected function call( $method, $endpoint, $body = array() ) { 42 | if ( is_wp_error( $error = $this->can_call() ) ) { 43 | return $error; 44 | } 45 | 46 | $args = array( 47 | 'method' => $method, 48 | 'headers' => array( 49 | 'Authorization' => 'token ' . $this->oauth_token(), 50 | ), 51 | ); 52 | 53 | if ( 'GET' !== $method ) { 54 | $args['body'] = function_exists( 'wp_json_encode' ) ? 55 | wp_json_encode( $body ) : 56 | json_encode( $body ); 57 | } 58 | 59 | $response = wp_remote_request( $endpoint, $args ); 60 | $status = wp_remote_retrieve_header( $response, 'status' ); 61 | $body = json_decode( wp_remote_retrieve_body( $response ) ); 62 | 63 | if ( '2' !== substr( $status, 0, 1 ) && '3' !== substr( $status, 0, 1 ) ) { 64 | return new WP_Error( 65 | strtolower( str_replace( ' ', '_', $status ) ), 66 | sprintf( 67 | __( 'Method %s to endpoint %s failed with error: %s', 'wp-github-sync' ), 68 | $method, 69 | $endpoint, 70 | $body && $body->message ? $body->message : 'Unknown error' 71 | ) 72 | ); 73 | } 74 | 75 | return $body; 76 | } 77 | 78 | /** 79 | * Validates whether the Api object can make a call. 80 | * 81 | * @return true|WP_Error 82 | */ 83 | protected function can_call() { 84 | if ( ! $this->oauth_token() ) { 85 | return new WP_Error( 86 | 'missing_token', 87 | __( 'WordPress-GitHub-Sync needs an auth token. Please update your settings.', 'wp-github-sync' ) 88 | ); 89 | } 90 | 91 | $repo = $this->repository(); 92 | 93 | if ( ! $repo ) { 94 | return new WP_Error( 95 | 'missing_repository', 96 | __( 'WordPress-GitHub-Sync needs a repository. Please update your settings.', 'wp-github-sync' ) 97 | ); 98 | } 99 | 100 | $parts = explode( '/', $repo ); 101 | 102 | if ( 2 !== count( $parts ) ) { 103 | return new WP_Error( 104 | 'malformed_repository', 105 | __( 'WordPress-GitHub-Sync needs a properly formed repository. Please update your settings.', 'wp-github-sync' ) 106 | ); 107 | } 108 | 109 | return true; 110 | } 111 | 112 | /** 113 | * Returns the repository to sync with 114 | * 115 | * @return string 116 | */ 117 | public function repository() { 118 | return (string) get_option( self::REPO_OPTION_KEY ); 119 | } 120 | 121 | /** 122 | * Returns the user's oauth token 123 | * 124 | * @return string 125 | */ 126 | public function oauth_token() { 127 | return (string) get_option( self::TOKEN_OPTION_KEY ); 128 | } 129 | 130 | /** 131 | * Returns the GitHub host to sync with (for GitHub Enterprise support) 132 | */ 133 | public function api_base() { 134 | return get_option( self::HOST_OPTION_KEY ); 135 | } 136 | 137 | /** 138 | * API endpoint for the master branch reference 139 | */ 140 | public function reference_endpoint() { 141 | $sync_branch = apply_filters( 'wpghs_sync_branch', 'master' ); 142 | 143 | if ( ! $sync_branch ) { 144 | throw new Exception( __( 'Sync branch not set. Filter `wpghs_sync_branch` misconfigured.', 'wp-github-sync' ) ); 145 | } 146 | 147 | $url = $this->api_base() . '/repos/'; 148 | $url = $url . $this->repository() . '/git/refs/heads/' . $sync_branch; 149 | 150 | return $url; 151 | } 152 | 153 | /** 154 | * Api to get and create commits 155 | */ 156 | public function commit_endpoint() { 157 | $url = $this->api_base() . '/repos/'; 158 | $url = $url . $this->repository() . '/git/commits'; 159 | 160 | return $url; 161 | } 162 | 163 | /** 164 | * Api to get and create trees 165 | */ 166 | public function tree_endpoint() { 167 | $url = $this->api_base() . '/repos/'; 168 | $url = $url . $this->repository() . '/git/trees'; 169 | 170 | return $url; 171 | } 172 | 173 | /** 174 | * Builds the proper blob API endpoint for a given post 175 | * 176 | * Returns String the relative API call path 177 | */ 178 | public function blob_endpoint() { 179 | $url = $this->api_base() . '/repos/'; 180 | $url = $url . $this->repository() . '/git/blobs'; 181 | 182 | return $url; 183 | } 184 | 185 | /** 186 | * Builds the proper content API endpoint for a given post 187 | * 188 | * Returns String the relative API call path 189 | */ 190 | public function content_endpoint() { 191 | $url = $this->api_base() . '/repos/'; 192 | $url = $url . $this->repository() . '/contents/'; 193 | 194 | return $url; 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /lib/import.php: -------------------------------------------------------------------------------- 1 | app = $app; 27 | } 28 | 29 | /** 30 | * Imports a payload. 31 | * 32 | * @param WordPress_GitHub_Sync_Payload $payload GitHub payload object. 33 | * 34 | * @return string|WP_Error 35 | */ 36 | public function payload( WordPress_GitHub_Sync_Payload $payload ) { 37 | /** 38 | * Whether there's an error during import. 39 | * 40 | * @var false|WP_Error $error 41 | */ 42 | $error = false; 43 | 44 | $result = $this->commit( $this->app->api()->fetch()->commit( $payload->get_commit_id() ) ); 45 | 46 | if ( is_wp_error( $result ) ) { 47 | $error = $result; 48 | } 49 | 50 | $removed = array(); 51 | foreach ( $payload->get_commits() as $commit ) { 52 | $removed = array_merge( $removed, $commit->removed ); 53 | } 54 | foreach ( array_unique( $removed ) as $path ) { 55 | $result = $this->app->database()->delete_post_by_path( $path ); 56 | 57 | if ( is_wp_error( $result ) ) { 58 | if ( $error ) { 59 | $error->add( $result->get_error_code(), $result->get_error_message() ); 60 | } else { 61 | $error = $result; 62 | } 63 | } 64 | } 65 | 66 | if ( $error ) { 67 | return $error; 68 | } 69 | 70 | return __( 'Payload processed', 'wp-github-sync' ); 71 | } 72 | 73 | /** 74 | * Imports the latest commit on the master branch. 75 | * 76 | * @return string|WP_Error 77 | */ 78 | public function master() { 79 | return $this->commit( $this->app->api()->fetch()->master() ); 80 | } 81 | 82 | /** 83 | * Imports a provided commit into the database. 84 | * 85 | * @param WordPress_GitHub_Sync_Commit|WP_Error $commit Commit to import. 86 | * 87 | * @return string|WP_Error 88 | */ 89 | protected function commit( $commit ) { 90 | if ( is_wp_error( $commit ) ) { 91 | return $commit; 92 | } 93 | 94 | if ( $commit->already_synced() ) { 95 | return new WP_Error( 'commit_synced', __( 'Already synced this commit.', 'wp-github-sync' ) ); 96 | } 97 | 98 | $posts = array(); 99 | $new = array(); 100 | 101 | foreach ( $commit->tree()->blobs() as $blob ) { 102 | if ( ! $this->importable_blob( $blob ) ) { 103 | continue; 104 | } 105 | 106 | $posts[] = $post = $this->blob_to_post( $blob ); 107 | 108 | if ( $post->is_new() ) { 109 | $new[] = $post; 110 | } 111 | } 112 | 113 | $result = $this->app->database()->save_posts( $posts, $commit->author_email() ); 114 | 115 | if ( is_wp_error( $result ) ) { 116 | return $result; 117 | } 118 | 119 | if ( $new ) { 120 | $result = $this->app->export()->new_posts( $new ); 121 | 122 | if ( is_wp_error( $result ) ) { 123 | return $result; 124 | } 125 | } 126 | 127 | return $posts; 128 | } 129 | 130 | /** 131 | * Checks whether the provided blob should be imported. 132 | * 133 | * @param WordPress_GitHub_Sync_Blob $blob Blob to validate. 134 | * 135 | * @return bool 136 | */ 137 | protected function importable_blob( WordPress_GitHub_Sync_Blob $blob ) { 138 | global $wpdb; 139 | 140 | // Skip the repo's readme. 141 | if ( 'readme' === strtolower( substr( $blob->path(), 0, 6 ) ) ) { 142 | return false; 143 | } 144 | 145 | // If the blob sha already matches a post, then move on. 146 | if ( ! is_wp_error( $this->app->database()->fetch_by_sha( $blob->sha() ) ) ) { 147 | return false; 148 | } 149 | 150 | if ( ! $blob->has_frontmatter() ) { 151 | return false; 152 | } 153 | 154 | return true; 155 | } 156 | 157 | /** 158 | * Imports a single blob content into matching post. 159 | * 160 | * @param WordPress_GitHub_Sync_Blob $blob Blob to transform into a Post. 161 | * 162 | * @return WordPress_GitHub_Sync_Post 163 | */ 164 | protected function blob_to_post( WordPress_GitHub_Sync_Blob $blob ) { 165 | $args = array( 'post_content' => $blob->content_import() ); 166 | $meta = $blob->meta(); 167 | 168 | if ( $meta ) { 169 | if ( array_key_exists( 'layout', $meta ) ) { 170 | $args['post_type'] = $meta['layout']; 171 | unset( $meta['layout'] ); 172 | } 173 | 174 | if ( array_key_exists( 'published', $meta ) ) { 175 | $args['post_status'] = true === $meta['published'] ? 'publish' : 'draft'; 176 | unset( $meta['published'] ); 177 | } 178 | 179 | if ( array_key_exists( 'post_title', $meta ) ) { 180 | $args['post_title'] = $meta['post_title']; 181 | unset( $meta['post_title'] ); 182 | } 183 | 184 | if ( array_key_exists( 'ID', $meta ) ) { 185 | $args['ID'] = $meta['ID']; 186 | unset( $meta['ID'] ); 187 | } 188 | 189 | if ( array_key_exists( 'post_date', $meta ) ) { 190 | 191 | if ( empty( $meta['post_date'] ) ) { 192 | $meta['post_date'] = current_time( 'mysql' ); 193 | } 194 | 195 | $args['post_date'] = $meta['post_date']; 196 | 197 | $args['post_date_gmt'] = get_gmt_from_date( $meta['post_date'] ); 198 | unset( $meta['post_date'] ); 199 | } 200 | } 201 | 202 | $meta['_sha'] = $blob->sha(); 203 | 204 | $post = new WordPress_GitHub_Sync_Post( $args, $this->app->api() ); 205 | $post->set_meta( $meta ); 206 | 207 | return $post; 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /lib/cache.php: -------------------------------------------------------------------------------- 1 | get( 'commits', $sha ); 66 | 67 | if ( $commit instanceof WordPress_GitHub_Sync_Commit ) { 68 | return $commit; 69 | } 70 | 71 | return false; 72 | } 73 | 74 | /** 75 | * Save commit to cache by sha. 76 | * 77 | * @param string $sha Commit sha to cache by. 78 | * @param WordPress_GitHub_Sync_Commit $commit Commit to cache. 79 | * 80 | * @return WordPress_GitHub_Sync_Commit 81 | */ 82 | public function set_commit( $sha, WordPress_GitHub_Sync_Commit $commit ) { 83 | return $this->save( 'commits', $sha, $commit, 0 ); 84 | } 85 | 86 | /** 87 | * Fetch tree from cache by sha. 88 | * 89 | * @param string $sha Tree sha to fetch from cache. 90 | * 91 | * @return false|WordPress_GitHub_Sync_Tree 92 | */ 93 | public function fetch_tree( $sha ) { 94 | $tree = $this->get( 'trees', $sha ); 95 | 96 | if ( $tree instanceof WordPress_GitHub_Sync_Tree ) { 97 | return $tree; 98 | } 99 | 100 | return false; 101 | } 102 | 103 | 104 | /** 105 | * Save tree to cache by sha. 106 | * 107 | * @param string $sha Tree sha to cache by. 108 | * @param WordPress_GitHub_Sync_Tree $tree Tree to cache. 109 | * 110 | * @return WordPress_GitHub_Sync_Tree 111 | */ 112 | public function set_tree( $sha, WordPress_GitHub_Sync_Tree $tree ) { 113 | return $this->save( 'trees', $sha, $tree, DAY_IN_SECONDS * 3 ); 114 | } 115 | 116 | /** 117 | * Fetch tree from cache by sha. 118 | * 119 | * @param string $sha Blob sha to fetch from cache. 120 | * 121 | * @return false|WordPress_GitHub_Sync_Blob 122 | */ 123 | public function fetch_blob( $sha ) { 124 | $blob = $this->get( 'blobs', $sha ); 125 | 126 | if ( $blob instanceof WordPress_GitHub_Sync_Blob ) { 127 | return $blob; 128 | } 129 | 130 | return false; 131 | } 132 | 133 | /** 134 | * Save blob to cache by sha. 135 | * 136 | * @param string $sha Blob sha to cache by. 137 | * @param WordPress_GitHub_Sync_Blob $blob Blob to cache. 138 | * 139 | * @return WordPress_GitHub_Sync_Blob 140 | */ 141 | public function set_blob( $sha, WordPress_GitHub_Sync_Blob $blob ) { 142 | return $this->save( 'blobs', $sha, $blob, 3 * DAY_IN_SECONDS ); 143 | } 144 | 145 | /** 146 | * Retrieve data from previous api calls by sha. 147 | * 148 | * @param string $type Object type to retrieve from cache. 149 | * @param string $sha Object sha to retrieve from cache. 150 | * 151 | * @return stdClass|false response object if cached, false if not 152 | */ 153 | protected function get( $type, $sha ) { 154 | if ( isset( $this->{$type}[ $sha ] ) ) { 155 | return $this->{$type}[ $sha ]; 156 | } 157 | 158 | if ( $data = get_transient( $this->cache_id( $type, $sha ) ) ) { 159 | return $this->{$type}[ $sha ] = $data; 160 | } 161 | 162 | return false; 163 | } 164 | 165 | /** 166 | * Save data from api call by sha. 167 | * 168 | * @param string $type Object type. 169 | * @param string $sha Object sha to cache by. 170 | * @param object $data Object to cache. 171 | * @param string $time Length of time to cache object for. 172 | * 173 | * @return mixed 174 | */ 175 | protected function save( $type, $sha, $data, $time ) { 176 | $this->{$type}[ $sha ] = $data; 177 | 178 | set_transient( $this->cache_id( $type, $sha ), $data, $time ); 179 | 180 | return $data; 181 | } 182 | 183 | /** 184 | * Generates the cache id for a given type & sha. 185 | * 186 | * @param string $type Object type. 187 | * @param string $sha Object sha. 188 | * 189 | * @return string 190 | */ 191 | protected function cache_id( $type, $sha ) { 192 | return 'wpghs_' . md5( $type . '_' . $sha ); 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /lib/payload.php: -------------------------------------------------------------------------------- 1 | app = $app; 41 | $this->data = $this->get_payload_from_raw_response( $raw_data ); 42 | 43 | if ( null === $this->data ) { 44 | switch ( json_last_error() ) { 45 | case JSON_ERROR_DEPTH: 46 | $this->error = __( 'Maximum stack depth exceeded', 'wp-github-sync' ); 47 | break; 48 | case JSON_ERROR_STATE_MISMATCH: 49 | $this->error = __( 'Underflow or the modes mismatch', 'wp-github-sync' ); 50 | break; 51 | case JSON_ERROR_CTRL_CHAR: 52 | $this->error = __( 'Unexpected control character found', 'wp-github-sync' ); 53 | break; 54 | case JSON_ERROR_SYNTAX: 55 | $this->error = __( 'Syntax error, malformed JSON', 'wp-github-sync' ); 56 | break; 57 | case JSON_ERROR_UTF8: 58 | $this->error = __( 'Malformed UTF-8 characters, possibly incorrectly encoded', 'wp-github-sync' ); 59 | break; 60 | default: 61 | $this->error = __( 'Unknown error', 'wp-github-sync' ); 62 | break; 63 | } 64 | } 65 | } 66 | 67 | 68 | /** 69 | * Attempts to get the JSON decoded string. 70 | * 71 | * @param string $raw_data A raw string from php://input 72 | * 73 | * @see WordPress_GitHub_Sync_Request::read_raw_data() 74 | * 75 | * @return Object|null An object from JSON Decode or false if failure. 76 | * 77 | * @author JayWood 78 | */ 79 | private function get_payload_from_raw_response( $raw_data ) { 80 | 81 | /* 82 | * Try this the old way first, despite this not working in some servers. Assuming there's a flag 83 | * at the Nginx or Apache level that auto-parses encoded strings. 84 | */ 85 | $maybe_decoded = json_decode( $raw_data ); 86 | if ( null !== $maybe_decoded ) { 87 | return $maybe_decoded; 88 | } 89 | 90 | /* 91 | * GitHub returns a raw string with Action and Payload keys by default, we have to parse that string 92 | * using parse_str() and then grab the payload. 93 | */ 94 | parse_str( $raw_data, $decoded_data ); 95 | 96 | if ( ! isset( $decoded_data['payload'] ) ) { 97 | return null; 98 | } 99 | 100 | return json_decode( $decoded_data['payload'] ); 101 | } 102 | 103 | /** 104 | * Returns whether payload should be imported. 105 | * 106 | * @return bool 107 | */ 108 | public function should_import() { 109 | // @todo how do we get this without importing the whole api object just for this? 110 | if ( strtolower( $this->data->repository->full_name ) !== strtolower( $this->app->api()->fetch()->repository() ) ) { 111 | return false; 112 | } 113 | 114 | // The last term in the ref is the payload_branch name. 115 | $refs = explode( '/', $this->data->ref ); 116 | $payload_branch = array_pop( $refs ); 117 | $sync_branch = apply_filters( 'wpghs_sync_branch', 'master' ); 118 | 119 | if ( ! $sync_branch ) { 120 | throw new Exception( __( 'Sync branch not set. Filter `wpghs_sync_branch` misconfigured.', 'wp-github-sync' ) ); 121 | } 122 | 123 | if ( $sync_branch !== $payload_branch ) { 124 | return false; 125 | } 126 | 127 | // We add a tag to commits we push out, so we shouldn't pull them in again. 128 | $tag = apply_filters( 'wpghs_commit_msg_tag', 'wpghs' ); 129 | 130 | if ( ! $tag ) { 131 | throw new Exception( __( 'Commit message tag not set. Filter `wpghs_commit_msg_tag` misconfigured.', 'wp-github-sync' ) ); 132 | } 133 | 134 | if ( $tag === substr( $this->message(), -1 * strlen( $tag ) ) ) { 135 | return false; 136 | } 137 | 138 | if ( ! $this->get_commit_id() ) { 139 | return false; 140 | } 141 | 142 | return true; 143 | } 144 | 145 | /** 146 | * Returns the sha of the head commit. 147 | * 148 | * @return string 149 | */ 150 | public function get_commit_id() { 151 | return $this->data->head_commit ? $this->data->head_commit->id : null; 152 | } 153 | 154 | /** 155 | * Returns the email address for the commit author. 156 | * 157 | * @return string 158 | */ 159 | public function get_author_email() { 160 | return $this->data->head_commit->author->email; 161 | } 162 | 163 | /** 164 | * Returns array commits for the payload. 165 | * 166 | * @return array 167 | */ 168 | public function get_commits() { 169 | return $this->data->commits; 170 | } 171 | 172 | /** 173 | * Returns the repository's full name. 174 | * 175 | * @return string 176 | */ 177 | public function get_repository_name() { 178 | return $this->data->repository->full_name; 179 | } 180 | 181 | /** 182 | * Return whether the payload has an error. 183 | * 184 | * @return bool 185 | */ 186 | public function has_error() { 187 | return $this->error !== null; 188 | } 189 | 190 | /** 191 | * Return the payload error string. 192 | * 193 | * @return string|null 194 | */ 195 | public function get_error() { 196 | return $this->error; 197 | } 198 | 199 | /** 200 | * Returns the payload's commit message. 201 | * 202 | * @return string 203 | */ 204 | protected function message() { 205 | return $this->data->head_commit->message; 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /lib/commit.php: -------------------------------------------------------------------------------- 1 | data = $data; 75 | 76 | $this->interpret_data(); 77 | } 78 | 79 | /** 80 | * Returns the commit sha. 81 | * 82 | * @return string 83 | */ 84 | public function sha() { 85 | return $this->sha; 86 | } 87 | 88 | /** 89 | * Return the commit's API url. 90 | * 91 | * @return string 92 | */ 93 | public function url() { 94 | return $this->url; 95 | } 96 | 97 | /** 98 | * Return the commit author. 99 | * 100 | * @return stdClass|false 101 | */ 102 | public function author() { 103 | return $this->author; 104 | } 105 | 106 | /** 107 | * Return's the commit author's email. 108 | * 109 | * @return string 110 | */ 111 | public function author_email() { 112 | if ( isset( $this->author->email ) ) { 113 | return $this->author->email; 114 | } 115 | 116 | return ''; 117 | } 118 | 119 | /** 120 | * Set's the commit author. 121 | * 122 | * @param stdClass $author Commit author data. 123 | * 124 | * @return $this 125 | */ 126 | public function set_author( stdClass $author ) { 127 | $this->author = $author; 128 | 129 | $this->set_to_parent(); 130 | 131 | return $this; 132 | } 133 | 134 | /** 135 | * Return the commit committer. 136 | * 137 | * @return stdClass|false 138 | */ 139 | public function committer() { 140 | return $this->committer; 141 | } 142 | 143 | /** 144 | * Set's the commit committer. 145 | * 146 | * @param stdClass $committer Committer data. 147 | * 148 | * @return $this 149 | */ 150 | public function set_committer( stdClass $committer ) { 151 | $this->committer = $committer; 152 | 153 | $this->set_to_parent(); 154 | 155 | return $this; 156 | } 157 | 158 | /** 159 | * Returns the commit message, if set. 160 | * 161 | * @return string 162 | */ 163 | public function message() { 164 | return $this->message; 165 | } 166 | 167 | /** 168 | * Set's the commit message; 169 | * 170 | * @param string $message Commit message. 171 | * 172 | * @return $this 173 | */ 174 | public function set_message( $message ) { 175 | $this->message = (string) $message; 176 | 177 | $this->set_to_parent(); 178 | 179 | return $this; 180 | } 181 | 182 | /** 183 | * Return the commit parents. 184 | * 185 | * @return string[] 186 | */ 187 | public function parents() { 188 | return $this->parents; 189 | } 190 | 191 | /** 192 | * Returns the commit's tree's sha. 193 | * 194 | * @return string 195 | */ 196 | public function tree_sha() { 197 | if ( $this->tree ) { 198 | return $this->tree->sha(); 199 | } 200 | 201 | if ( isset( $this->data->tree ) ) { 202 | return $this->data->tree->sha; 203 | } 204 | 205 | return ''; 206 | } 207 | 208 | /** 209 | * Return's the commit's tree. 210 | * 211 | * @return WordPress_GitHub_Sync_Tree 212 | */ 213 | public function tree() { 214 | return $this->tree; 215 | } 216 | 217 | /** 218 | * Set the commit's tree. 219 | * 220 | * @param WordPress_GitHub_Sync_Tree $tree New tree for commit. 221 | * 222 | * @return $this 223 | */ 224 | public function set_tree( WordPress_GitHub_Sync_Tree $tree ) { 225 | $this->tree = $tree; 226 | 227 | return $this; 228 | } 229 | 230 | /** 231 | * Returns whether the commit is currently synced. 232 | * 233 | * The commit message of every commit that's exported 234 | * by WPGHS ends with '- wpghs', so we don't sync those 235 | * commits down. 236 | * 237 | * @return bool 238 | */ 239 | public function already_synced() { 240 | return 'wpghs' === substr( $this->message, - 5 ); 241 | } 242 | 243 | /** 244 | * Transforms the commit into the API 245 | * body required to create a new commit. 246 | * 247 | * @return array 248 | */ 249 | public function to_body() { 250 | $body = array( 251 | 'tree' => $this->tree_sha(), 252 | 'message' => $this->message(), 253 | 'parents' => $this->parents(), 254 | ); 255 | 256 | // @todo set author here 257 | return $body; 258 | } 259 | 260 | /** 261 | * Interprets the raw data object into commit properties. 262 | */ 263 | protected function interpret_data() { 264 | $this->sha = isset( $this->data->sha ) ? $this->data->sha : ''; 265 | $this->url = isset( $this->data->url ) ? $this->data->url : ''; 266 | $this->author = isset( $this->data->author ) ? $this->data->author : false; 267 | $this->committer = isset( $this->data->committer ) ? $this->data->committer : false; 268 | $this->message = isset( $this->data->message ) ? $this->data->message : ''; 269 | $this->parents = isset( $this->data->parents ) ? $this->data->parents : array(); 270 | } 271 | 272 | /** 273 | * Assigns the current sha to be its parent. 274 | */ 275 | protected function set_to_parent() { 276 | if ( $this->sha ) { 277 | $this->parents = array( $this->sha ); 278 | $this->sha = ''; 279 | } 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /lib/tree.php: -------------------------------------------------------------------------------- 1 | data = $data; 61 | 62 | $this->interpret_data(); 63 | } 64 | 65 | /** 66 | * Returns the tree's raw data. 67 | * 68 | * @return stdClass 69 | */ 70 | public function get_data() { 71 | return $this->data; 72 | } 73 | 74 | /** 75 | * Return's the tree's sha. 76 | * 77 | * @return string 78 | */ 79 | public function sha() { 80 | return $this->sha; 81 | } 82 | 83 | /** 84 | * Updates the tree's sha. 85 | * 86 | * @param string $sha Tree sha. 87 | * 88 | * @return $this 89 | */ 90 | public function set_sha( $sha ) { 91 | $this->sha = $sha; 92 | 93 | return $this; 94 | } 95 | 96 | /** 97 | * Returns the tree's url. 98 | * 99 | * @return string 100 | */ 101 | public function url() { 102 | return $this->url; 103 | } 104 | 105 | /** 106 | * Returns the tree's blobs. 107 | * 108 | * @return WordPress_GitHub_Sync_Blob[] 109 | */ 110 | public function blobs() { 111 | return array_values( $this->paths ); 112 | } 113 | 114 | /** 115 | * Sets the tree's blobs to the provided array of blobs. 116 | * 117 | * @param WordPress_GitHub_Sync_Blob[] $blobs Array of blobs to set to tree. 118 | * 119 | * @return $this 120 | */ 121 | public function set_blobs( array $blobs ) { 122 | $this->paths = array(); 123 | $this->shas = array(); 124 | 125 | foreach ( $blobs as $blob ) { 126 | $this->add_blob( $blob ); 127 | } 128 | 129 | return $this; 130 | } 131 | 132 | /** 133 | * Adds the provided blob to the tree. 134 | * 135 | * @param WordPress_GitHub_Sync_Blob $blob Blob to add to tree. 136 | * 137 | * @return $this 138 | */ 139 | public function add_blob( WordPress_GitHub_Sync_Blob $blob ) { 140 | $this->paths[ $blob->path() ] = $this->shas[ $blob->sha() ] = $blob; 141 | 142 | return $this; 143 | } 144 | 145 | /** 146 | * Adds the provided post as a blob to the tree. 147 | * 148 | * @param WordPress_GitHub_Sync_Post $post Post to add to tree. 149 | * 150 | * @return $this 151 | */ 152 | public function add_post_to_tree( WordPress_GitHub_Sync_Post $post ) { 153 | $blob = $this->get_blob_for_post( $post ); 154 | 155 | if ( 156 | ! $blob->sha() || 157 | $blob->content_import() !== $post->github_content() 158 | ) { 159 | $this->shas[] = $this->paths[ $blob->path() ] = $post->to_blob(); 160 | $this->changed = true; 161 | 162 | if ( $blob->sha() ) { 163 | unset( $this->shas[ $blob->sha() ] ); 164 | } 165 | } 166 | 167 | return $this; 168 | } 169 | 170 | /** 171 | * Removes the provided post's blob from the tree. 172 | * 173 | * @param WordPress_GitHub_Sync_Post $post Post to remove from tree. 174 | * 175 | * @return $this 176 | */ 177 | public function remove_post_from_tree( WordPress_GitHub_Sync_Post $post ) { 178 | if ( isset( $this->shas[ $post->sha() ] ) ) { 179 | $blob = $this->shas[ $post->sha() ]; 180 | 181 | unset( $this->paths[ $blob->path() ] ); 182 | unset( $this->shas[ $post->sha() ] ); 183 | 184 | $this->changed = true; 185 | } else if ( isset( $this->paths[ $post->github_path() ] ) ) { 186 | unset( $this->paths[ $post->github_path() ] ); 187 | 188 | $this->changed = true; 189 | } 190 | 191 | return $this; 192 | } 193 | 194 | /** 195 | * Retrieves a tree blob for a given path. 196 | * 197 | * @param string $path Path to retrieve blob by. 198 | * 199 | * @return false|WordPress_GitHub_Sync_Blob 200 | */ 201 | public function get_blob_by_path( $path ) { 202 | return isset( $this->paths[ $path ] ) ? $this->paths[ $path ] : false; 203 | } 204 | 205 | /** 206 | * Retrieves a tree blob for a given path. 207 | * 208 | * @param string $sha Sha to retrieve blob by. 209 | * 210 | * @return false|WordPress_GitHub_Sync_Blob 211 | */ 212 | public function get_blob_by_sha( $sha ) { 213 | return isset( $this->shas[ $sha ] ) ? $this->shas[ $sha ] : false; 214 | } 215 | 216 | /** 217 | * Returns whether the tree has changed. 218 | * 219 | * @return bool 220 | */ 221 | public function is_changed() { 222 | return $this->changed; 223 | } 224 | 225 | /** 226 | * Formats the tree for an API call body. 227 | * 228 | * @return array 229 | */ 230 | public function to_body() { 231 | $tree = array(); 232 | 233 | foreach ( $this->blobs() as $blob ) { 234 | $tree[] = $blob->to_body(); 235 | } 236 | 237 | return array( 'tree' => $tree ); 238 | } 239 | 240 | /** 241 | * Interprets the Tree from the data. 242 | */ 243 | protected function interpret_data() { 244 | $this->sha = isset( $this->data->sha ) ? $this->data->sha : ''; 245 | $this->url = isset( $this->data->url ) ? $this->data->url : ''; 246 | } 247 | 248 | /** 249 | * Returns a blob for the provided post. 250 | * 251 | * @param WordPress_GitHub_Sync_Post $post Post to retrieve blob for. 252 | * 253 | * @return WordPress_GitHub_Sync_Blob 254 | */ 255 | protected function get_blob_for_post( WordPress_GitHub_Sync_Post $post ) { 256 | if ( $blob = $this->get_blob_by_sha( $post->sha() ) ) { 257 | return $blob; 258 | } 259 | 260 | if ( $blob = $this->get_blob_by_path( $post->github_path() ) ) { 261 | return $blob; 262 | } 263 | 264 | return $post->to_blob(); 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /tests/unit/client/test-persist.php: -------------------------------------------------------------------------------- 1 | persist = new WordPress_GitHub_Sync_Persist_Client( $this->app ); 12 | $this->commit 13 | ->shouldReceive( 'tree' ) 14 | ->andReturn( $this->tree ); 15 | } 16 | 17 | public function test_should_not_add_commit_if_no_change() { 18 | $this->tree 19 | ->shouldReceive( 'is_changed' ) 20 | ->once() 21 | ->andReturn( false ); 22 | 23 | $this->assertWPError( $error = $this->persist->commit( $this->commit ) ); 24 | $this->assertSame( 'no_commit', $error->get_error_code() ); 25 | } 26 | 27 | public function test_should_fail_if_create_tree_fails() { 28 | $this->tree 29 | ->shouldReceive( 'is_changed' ) 30 | ->once() 31 | ->andReturn( true ); 32 | $this->tree 33 | ->shouldReceive( 'to_body' ) 34 | ->once() 35 | ->andReturn( 36 | array( 37 | 'tree' => array( 38 | array( 39 | 'path' => '_posts/2015-10-23-new-post.md', 40 | 'type' => 'blob', 41 | 'content' => 'Post content 1', 42 | 'mode' => '100644', 43 | ) 44 | ) 45 | ) 46 | ); 47 | $this->set_post_trees( false ); 48 | 49 | $this->assertWPError( $error = $this->persist->commit( $this->commit ) ); 50 | $this->assertSame( '404_not_found', $error->get_error_code() ); 51 | } 52 | 53 | public function test_should_fail_if_create_commit_fails() { 54 | $this->tree 55 | ->shouldReceive( 'is_changed' ) 56 | ->once() 57 | ->andReturn( true ); 58 | $this->tree 59 | ->shouldReceive( 'to_body' ) 60 | ->once() 61 | ->andReturn( 62 | array( 63 | 'tree' => array( 64 | array( 65 | 'path' => '_posts/2015-10-23-new-post.md', 66 | 'type' => 'blob', 67 | 'content' => 'Post content 1', 68 | 'mode' => '100644', 69 | ) 70 | ) 71 | ) 72 | ); 73 | $this->tree 74 | ->shouldReceive( 'set_sha' ) 75 | ->once() 76 | ->with( 'c0d2cb90b51826096c826b61a0e74d2c973d7ad8' ); 77 | $this->commit 78 | ->shouldReceive( 'to_body' ) 79 | ->once() 80 | ->andReturn( array( 81 | 'tree' => 'c0d2cb90b51826096c826b61a0e74d2c973d7ad8', 82 | 'message' => 'Commit message', 83 | 'parents' => array( 'c0d2cb90b51826096c826b61a0e74d2c973d7ad8' ) 84 | ) ); 85 | $this->set_post_trees( true ); 86 | $this->set_post_commits( false ); 87 | 88 | $this->assertWPError( $error = $this->persist->commit( $this->commit ) ); 89 | $this->assertSame( '404_not_found', $error->get_error_code() ); 90 | } 91 | 92 | public function test_should_fail_if_update_master_fails() { 93 | $this->tree 94 | ->shouldReceive( 'is_changed' ) 95 | ->once() 96 | ->andReturn( true ); 97 | $this->tree 98 | ->shouldReceive( 'to_body' ) 99 | ->once() 100 | ->andReturn( 101 | array( 102 | 'tree' => array( 103 | array( 104 | 'path' => '_posts/2015-10-23-new-post.md', 105 | 'type' => 'blob', 106 | 'content' => 'Post content 1', 107 | 'mode' => '100644', 108 | ) 109 | ) 110 | ) 111 | ); 112 | $this->tree 113 | ->shouldReceive( 'set_sha' ) 114 | ->once() 115 | ->with( 'c0d2cb90b51826096c826b61a0e74d2c973d7ad8' ); 116 | $this->commit 117 | ->shouldReceive( 'to_body' ) 118 | ->once() 119 | ->andReturn( array( 120 | 'tree' => 'c0d2cb90b51826096c826b61a0e74d2c973d7ad8', 121 | 'message' => 'Commit message', 122 | 'parents' => array( 'c0d2cb90b51826096c826b61a0e74d2c973d7ad8' ) 123 | ) ); 124 | $this->set_post_trees( true ); 125 | $this->set_post_commits( true ); 126 | $this->set_patch_refs_heads_master( false ); 127 | 128 | $this->assertWPError( $error = $this->persist->commit( $this->commit ) ); 129 | $this->assertSame( '404_not_found', $error->get_error_code() ); 130 | } 131 | 132 | public function test_should_create_anonymous_commit() { 133 | $this->tree 134 | ->shouldReceive( 'is_changed' ) 135 | ->once() 136 | ->andReturn( true ); 137 | $this->tree 138 | ->shouldReceive( 'to_body' ) 139 | ->once() 140 | ->andReturn( 141 | array( 142 | 'tree' => array( 143 | array( 144 | 'path' => '_posts/2015-10-23-new-post.md', 145 | 'type' => 'blob', 146 | 'content' => 'Post content 1', 147 | 'mode' => '100644', 148 | ) 149 | ) 150 | ) 151 | ); 152 | $this->tree 153 | ->shouldReceive( 'set_sha' ) 154 | ->once() 155 | ->with( 'c0d2cb90b51826096c826b61a0e74d2c973d7ad8' ); 156 | $this->commit 157 | ->shouldReceive( 'to_body' ) 158 | ->once() 159 | ->andReturn( array( 160 | 'tree' => 'c0d2cb90b51826096c826b61a0e74d2c973d7ad8', 161 | 'message' => 'Commit message', 162 | 'parents' => array( 'c0d2cb90b51826096c826b61a0e74d2c973d7ad8' ) 163 | ) ); 164 | $this->set_post_trees( true ); 165 | $this->set_post_commits( true ); 166 | $this->set_patch_refs_heads_master( true ); 167 | 168 | $this->assertTrue( $this->persist->commit( $this->commit ) ); 169 | } 170 | 171 | public function test_should_create_authored_commit() { 172 | $this->tree 173 | ->shouldReceive( 'is_changed' ) 174 | ->once() 175 | ->andReturn( true ); 176 | $this->tree 177 | ->shouldReceive( 'to_body' ) 178 | ->once() 179 | ->andReturn( 180 | array( 181 | 'tree' => array( 182 | array( 183 | 'path' => '_posts/2015-10-23-new-post.md', 184 | 'type' => 'blob', 185 | 'content' => 'Post content 1', 186 | 'mode' => '100644', 187 | ) 188 | ) 189 | ) 190 | ); 191 | $this->tree 192 | ->shouldReceive( 'set_sha' ) 193 | ->once() 194 | ->with( 'c0d2cb90b51826096c826b61a0e74d2c973d7ad8' ); 195 | $this->commit 196 | ->shouldReceive( 'to_body' ) 197 | ->once() 198 | ->andReturn( array( 199 | 'tree' => 'c0d2cb90b51826096c826b61a0e74d2c973d7ad8', 200 | 'message' => 'Commit message', 201 | 'parents' => array( 'c0d2cb90b51826096c826b61a0e74d2c973d7ad8' ) 202 | ) ); 203 | $this->set_post_trees( true ); 204 | update_option( '_wpghs_export_user_id', $this->factory->user->create( array( 205 | 'display_name' => 'James DiGioia', 206 | 'user_email' => 'jamesorodig@gmail.com', 207 | ) ) ); 208 | $this->set_post_commits( true, false ); 209 | $this->set_patch_refs_heads_master( true ); 210 | 211 | $this->assertTrue( $this->persist->commit( $this->commit ) ); 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /lib/controller.php: -------------------------------------------------------------------------------- 1 | app = $app; 26 | } 27 | 28 | /** 29 | * Webhook callback as triggered from GitHub push. 30 | * 31 | * Reads the Webhook payload and syncs posts as necessary. 32 | * 33 | * @return boolean 34 | */ 35 | public function pull_posts() { 36 | $this->set_ajax(); 37 | if ( ! $this->app->semaphore()->is_open() ) { 38 | return $this->app->response()->error( new WP_Error( 39 | 'semaphore_locked', 40 | sprintf( __( '%s : Semaphore is locked, import/export already in progress.', 'wp-github-sync' ), 'Controller::pull_posts()' ) 41 | ) ); 42 | } 43 | 44 | if ( ! $this->app->request()->is_secret_valid() ) { 45 | return $this->app->response()->error( new WP_Error( 46 | 'invalid_headers', 47 | __( 'Failed to validate secret.', 'wp-github-sync' ) 48 | ) ); 49 | } 50 | 51 | $payload = $this->app->request()->payload(); 52 | 53 | if ( $payload->has_error() ) { 54 | return $this->app->response()->error( new WP_Error( 55 | 'invalid_payload', 56 | sprintf( 57 | __( "%s won't be imported. Error: %s", 'wp-github-sync' ), 58 | strtolower( $payload->get_commit_id() ) ? : '[Missing Commit ID]', 59 | $payload->get_error() 60 | ) 61 | ) ); 62 | } 63 | 64 | if ( ! $payload->should_import() ) { 65 | return $this->app->response()->error( new WP_Error( 66 | 'invalid_payload', 67 | sprintf( 68 | __( "%s won't be imported.", 'wp-github-sync' ), 69 | strtolower( $payload->get_commit_id() ) ? : '[Missing Commit ID]' 70 | ) 71 | ) ); 72 | } 73 | 74 | $this->app->semaphore()->lock(); 75 | remove_action( 'save_post', array( $this, 'export_post' ) ); 76 | remove_action( 'delete_post', array( $this, 'delete_post' ) ); 77 | 78 | $result = $this->app->import()->payload( $payload ); 79 | 80 | $this->app->semaphore()->unlock(); 81 | 82 | if ( is_wp_error( $result ) ) { 83 | return $this->app->response()->error( $result ); 84 | } 85 | 86 | return $this->app->response()->success( $result ); 87 | } 88 | 89 | /** 90 | * Imports posts from the current master branch. 91 | * 92 | * @return boolean 93 | */ 94 | public function import_master() { 95 | if ( ! $this->app->semaphore()->is_open() ) { 96 | return $this->app->response()->error( new WP_Error( 97 | 'semaphore_locked', 98 | sprintf( __( '%s : Semaphore is locked, import/export already in progress.', 'wp-github-sync' ), 'Controller::import_master()' ) 99 | ) ); 100 | } 101 | 102 | $this->app->semaphore()->lock(); 103 | remove_action( 'save_post', array( $this, 'export_post' ) ); 104 | remove_action( 'save_post', array( $this, 'delete_post' ) ); 105 | 106 | $result = $this->app->import()->master(); 107 | 108 | $this->app->semaphore()->unlock(); 109 | 110 | if ( is_wp_error( $result ) ) { 111 | update_option( '_wpghs_import_error', $result->get_error_message() ); 112 | 113 | return $this->app->response()->error( $result ); 114 | } 115 | 116 | update_option( '_wpghs_import_complete', 'yes' ); 117 | 118 | return $this->app->response()->success( $result ); 119 | } 120 | 121 | /** 122 | * Export all the posts in the database to GitHub. 123 | * 124 | * @return boolean 125 | */ 126 | public function export_all() { 127 | if ( ! $this->app->semaphore()->is_open() ) { 128 | return $this->app->response()->error( new WP_Error( 129 | 'semaphore_locked', 130 | sprintf( __( '%s : Semaphore is locked, import/export already in progress.', 'wp-github-sync' ), 'Controller::export_all()' ) 131 | ) ); 132 | } 133 | 134 | $this->app->semaphore()->lock(); 135 | $result = $this->app->export()->full(); 136 | $this->app->semaphore()->unlock(); 137 | 138 | // Maybe move option updating out of this class/upgrade message display? 139 | if ( is_wp_error( $result ) ) { 140 | update_option( '_wpghs_export_error', $result->get_error_message() ); 141 | 142 | return $this->app->response()->error( $result ); 143 | } else { 144 | update_option( '_wpghs_export_complete', 'yes' ); 145 | update_option( '_wpghs_fully_exported', 'yes' ); 146 | 147 | return $this->app->response()->success( $result ); 148 | } 149 | } 150 | 151 | /** 152 | * Exports a single post to GitHub by ID. 153 | * 154 | * Called on the save_post hook. 155 | * 156 | * @param int $post_id Post ID. 157 | * 158 | * @return boolean 159 | */ 160 | public function export_post( $post_id ) { 161 | if ( ! $this->app->semaphore()->is_open() ) { 162 | return $this->app->response()->error( new WP_Error( 163 | 'semaphore_locked', 164 | sprintf( __( '%s : Semaphore is locked, import/export already in progress.', 'wp-github-sync' ), 'Controller::export_post()' ) 165 | ) ); 166 | } 167 | 168 | $this->app->semaphore()->lock(); 169 | $result = $this->app->export()->update( $post_id ); 170 | $this->app->semaphore()->unlock(); 171 | 172 | if ( is_wp_error( $result ) ) { 173 | return $this->app->response()->error( $result ); 174 | } 175 | 176 | return $this->app->response()->success( $result ); 177 | } 178 | 179 | /** 180 | * Removes the post from the tree. 181 | * 182 | * Called the delete_post hook. 183 | * 184 | * @param int $post_id Post ID. 185 | * 186 | * @return boolean 187 | */ 188 | public function delete_post( $post_id ) { 189 | if ( ! $this->app->semaphore()->is_open() ) { 190 | return $this->app->response()->error( new WP_Error( 191 | 'semaphore_locked', 192 | sprintf( __( '%s : Semaphore is locked, import/export already in progress.', 'wp-github-sync' ), 'Controller::delete_post()' ) 193 | ) ); 194 | } 195 | 196 | $this->app->semaphore()->lock(); 197 | $result = $this->app->export()->delete( $post_id ); 198 | $this->app->semaphore()->unlock(); 199 | 200 | if ( is_wp_error( $result ) ) { 201 | return $this->app->response()->error( $result ); 202 | } 203 | 204 | return $this->app->response()->success( $result ); 205 | } 206 | 207 | /** 208 | * Indicates we're running our own AJAX hook 209 | * and thus should respond with JSON, rather 210 | * than just returning data. 211 | */ 212 | protected function set_ajax() { 213 | if ( ! defined( 'WPGHS_AJAX' ) ) { 214 | define( 'WPGHS_AJAX', true ); 215 | } 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /tests/unit/client/test-base.php: -------------------------------------------------------------------------------- 1 | http_responder = array( $this, 'mock_github_api' ); 38 | } 39 | 40 | /** 41 | * This does some checks and fails the test if something is wrong 42 | * or returns intended mock data for the given endpoint + method. 43 | * 44 | * @return void|string 45 | */ 46 | public function mock_github_api( $request, $url ) { 47 | $host_length = strlen( self::HOST_OPTION_VALUE ); 48 | 49 | if ( self::HOST_OPTION_VALUE !== substr( $url, 0, $host_length ) ) { 50 | $this->assertTrue( false, 'Called wrong host.' ); 51 | } 52 | 53 | if ( 54 | ! isset( $request['headers']['Authorization'] ) || 55 | 'token ' . self::TOKEN_OPTION_VALUE !== $request['headers']['Authorization'] 56 | ) { 57 | $this->assertTrue( false, 'Missing authorization key.' ); 58 | } 59 | 60 | $url = explode( '/', substr( $url, $host_length + 1 ) ); 61 | 62 | if ( 'repos' !== $url[0] ) { 63 | $this->assertTrue( false, 'Called wrong endpoint.' ); 64 | } 65 | 66 | $repo = $url[1] . '/' . $url[2]; 67 | 68 | if ( self::REPO_OPTION_VALUE !== $repo ) { 69 | $this->assertTrue( false, 'Called wrong repo.' ); 70 | } 71 | 72 | $parts = array_slice( $url, 4 ); 73 | array_unshift( $parts, strtolower( $request['method'] ) ); 74 | $endpoint = implode( '_', $parts ); 75 | $endpoint = str_replace( '?recursive=1', '', $endpoint ); 76 | $this->assertTrue( call_user_func( static::$validations[ $endpoint ], $request ), 'Request did not validate.' ); 77 | 78 | return static::$responses[ $endpoint ]; 79 | } 80 | 81 | protected function set_get_refs_heads_master( $succeed ) { 82 | $this->set_endpoint( 83 | function ( $request ) { 84 | if ( '[]' === $request['body'] ) { 85 | return false; 86 | } 87 | 88 | return true; 89 | }, $succeed ? '200 OK' : '404 Not Found', $succeed 90 | ); 91 | } 92 | 93 | protected function set_get_commits( $succeed ) { 94 | $this->set_endpoint( 95 | function ( $request ) { 96 | if ( '[]' === $request['body'] ) { 97 | return false; 98 | } 99 | 100 | return true; 101 | }, $succeed ? '200 OK' : '404 Not Found', $succeed, 'db2510854e6aeab68ead26b48328b19f4bdf926e' 102 | ); 103 | } 104 | 105 | protected function set_get_trees( $succeed ) { 106 | $this->set_endpoint( 107 | function ( $request ) { 108 | if ( '[]' === $request['body'] ) { 109 | return false; 110 | } 111 | 112 | return true; 113 | }, $succeed ? '200 OK' : '422 Unprocessable Entity', $succeed, '9108868e3800bec6763e51beb0d33e15036c3626' 114 | ); 115 | } 116 | 117 | protected function set_get_blobs( $succeed ) { 118 | $shas = array( 119 | '9fa5c7537f8582b71028ff34b8c20dfd0f3b2a25', 120 | '8d9b2e6fd93761211dc03abd71f4a9189d680fd0', 121 | '2d73165945b0ccbe4932f1363457986b0ed49f19', 122 | ); 123 | 124 | foreach ( $shas as $sha ) { 125 | $this->set_endpoint( 126 | function ( $request ) { 127 | if ( '[]' === $request['body'] ) { 128 | return false; 129 | } 130 | 131 | return true; 132 | }, $succeed ? '200 OK' : '404 Not Found', $succeed, $sha 133 | ); 134 | } 135 | } 136 | 137 | protected function set_post_trees( $succeed ) { 138 | $this->set_endpoint( 139 | function ( $request ) { 140 | $body = json_decode( $request['body'], true ); 141 | 142 | if ( ! isset( $body['tree'] ) ) { 143 | return false; 144 | } 145 | 146 | if ( 1 !== count( $body['tree'] ) ) { 147 | return false; 148 | } 149 | 150 | $blob = reset( $body['tree'] ); 151 | 152 | if ( 153 | ! isset( $blob['path'] ) || 154 | ! isset( $blob['type'] ) || 155 | ! isset( $blob['content'] ) || 156 | ! isset( $blob['mode'] ) 157 | ) { 158 | return false; 159 | } 160 | 161 | return true; 162 | }, 163 | $succeed ? '201 Created' : '404 Not Found', 164 | $succeed 165 | ); 166 | } 167 | 168 | protected function set_post_commits( $succeed, $anonymous = true ) { 169 | $this->set_endpoint( 170 | function ( $request ) use ( $anonymous ) { 171 | $body = json_decode( $request['body'], true ); 172 | 173 | if ( 174 | ! isset( $body['tree'] ) || 175 | ! isset( $body['message'] ) || 176 | ! isset( $body['parents'] ) || 177 | ! isset( $body['author'] ) 178 | ) { 179 | return false; 180 | } 181 | 182 | if ( 1 !== count( $body['parents'] ) ) { 183 | return false; 184 | } 185 | 186 | if ( ! $anonymous ) { 187 | if ( 188 | 'James DiGioia' !== $body['author']['name'] || 189 | 'jamesorodig@gmail.com' !== $body['author']['email'] 190 | ) { 191 | return false; 192 | } 193 | } else { 194 | if ( 195 | 'Anonymous' !== $body['author']['name'] || 196 | 'anonymous@users.noreply.github.com' !== $body['author']['email'] 197 | ) { 198 | return false; 199 | } 200 | } 201 | 202 | return true; 203 | }, 204 | $succeed ? '201 Created' : '404 Not Found', 205 | $succeed 206 | ); 207 | } 208 | 209 | protected function set_patch_refs_heads_master( $succeed ) { 210 | $this->set_endpoint( 211 | function ( $request ) { 212 | $body = json_decode( $request['body'], true ); 213 | 214 | if ( ! isset( $body['sha'] ) ) { 215 | return false; 216 | } 217 | 218 | return true; 219 | }, 220 | $succeed ? '201 Created' : '404 Not Found', 221 | $succeed 222 | ); 223 | } 224 | 225 | private function set_endpoint( $validation, $status, $succeed, $sha = '' ) { 226 | list( , $caller ) = debug_backtrace( false ); 227 | $endpoint = substr( $caller['function'], 4 ) . ( $sha ? "_$sha" : '' ); 228 | 229 | static::$validations[ $endpoint ] = $validation; 230 | 231 | static::$responses[ $endpoint ] = array( 232 | 'headers' => array( 233 | 'status' => $status, 234 | ), 235 | 'body' => file_get_contents( 236 | $this->data_dir . $endpoint . '_' . ( $succeed ? 'succeed' : 'fail' ) . '.json' 237 | ), 238 | ); 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /lib/export.php: -------------------------------------------------------------------------------- 1 | app = $app; 32 | } 33 | 34 | /** 35 | * Updates all of the current posts in the database on master. 36 | * 37 | * @return string|WP_Error 38 | */ 39 | public function full() { 40 | $posts = $this->app->database()->fetch_all_supported(); 41 | 42 | if ( is_wp_error( $posts ) ) { 43 | return $posts; 44 | } 45 | 46 | $master = $this->app->api()->fetch()->master(); 47 | 48 | if ( is_wp_error( $master ) ) { 49 | return $master; 50 | } 51 | 52 | foreach ( $posts as $post ) { 53 | $master->tree()->add_post_to_tree( $post ); 54 | } 55 | 56 | $master->set_message( 57 | apply_filters( 58 | 'wpghs_commit_msg_full', 59 | sprintf( 60 | 'Full export from WordPress at %s (%s)', 61 | site_url(), 62 | get_bloginfo( 'name' ) 63 | ) 64 | ) . $this->get_commit_msg_tag() 65 | ); 66 | 67 | $result = $this->app->api()->persist()->commit( $master ); 68 | 69 | if ( is_wp_error( $result ) ) { 70 | return $result; 71 | } 72 | 73 | return $this->update_shas( $posts ); 74 | } 75 | 76 | /** 77 | * Updates the provided post ID in master. 78 | * 79 | * @param int $post_id Post ID to update. 80 | * 81 | * @return string|WP_Error 82 | */ 83 | public function update( $post_id ) { 84 | $post = $this->app->database()->fetch_by_id( $post_id ); 85 | 86 | if ( is_wp_error( $post ) ) { 87 | return $post; 88 | } 89 | 90 | if ( 'trash' === $post->status() ) { 91 | return $this->delete( $post_id ); 92 | } 93 | 94 | $master = $this->app->api()->fetch()->master(); 95 | 96 | if ( is_wp_error( $master ) ) { 97 | return $master; 98 | } 99 | 100 | $master->tree()->add_post_to_tree( $post ); 101 | $master->set_message( 102 | apply_filters( 103 | 'wpghs_commit_msg_single', 104 | sprintf( 105 | 'Syncing %s from WordPress at %s (%s)', 106 | $post->github_path(), 107 | site_url(), 108 | get_bloginfo( 'name' ) 109 | ), 110 | $post 111 | ) . $this->get_commit_msg_tag() 112 | ); 113 | 114 | $result = $this->app->api()->persist()->commit( $master ); 115 | 116 | if ( is_wp_error( $result ) ) { 117 | return $result; 118 | } 119 | 120 | return $this->update_shas( array( $post ) ); 121 | } 122 | 123 | /** 124 | * Updates GitHub-created posts with latest WordPress data. 125 | * 126 | * @param array $posts Array of Posts to create. 127 | * 128 | * @return string|WP_Error 129 | */ 130 | public function new_posts( array $posts ) { 131 | $master = $this->app->api()->fetch()->master(); 132 | 133 | if ( is_wp_error( $master ) ) { 134 | return $master; 135 | } 136 | 137 | foreach ( $posts as $post ) { 138 | $master->tree()->add_post_to_tree( $post ); 139 | } 140 | 141 | $master->set_message( 142 | apply_filters( 143 | 'wpghs_commit_msg_new_posts', 144 | sprintf( 145 | 'Updating new posts from WordPress at %s (%s)', 146 | site_url(), 147 | get_bloginfo( 'name' ) 148 | ) 149 | ) . $this->get_commit_msg_tag() 150 | ); 151 | 152 | $result = $this->app->api()->persist()->commit( $master ); 153 | 154 | if ( is_wp_error( $result ) ) { 155 | return $result; 156 | } 157 | 158 | return $this->update_shas( $posts ); 159 | } 160 | 161 | /** 162 | * Deletes a provided post ID from master. 163 | * 164 | * @param int $post_id Post ID to delete. 165 | * 166 | * @return string|WP_Error 167 | */ 168 | public function delete( $post_id ) { 169 | $post = $this->app->database()->fetch_by_id( $post_id ); 170 | 171 | if ( is_wp_error( $post ) ) { 172 | return $post; 173 | } 174 | 175 | $master = $this->app->api()->fetch()->master(); 176 | 177 | if ( is_wp_error( $master ) ) { 178 | return $master; 179 | } 180 | 181 | $master->tree()->remove_post_from_tree( $post ); 182 | $master->set_message( 183 | apply_filters( 184 | 'wpghs_commit_msg_delete', 185 | sprintf( 186 | 'Deleting %s via WordPress at %s (%s)', 187 | $post->github_path(), 188 | site_url(), 189 | get_bloginfo( 'name' ) 190 | ), 191 | $post 192 | ) . $this->get_commit_msg_tag() 193 | ); 194 | 195 | $result = $this->app->api()->persist()->commit( $master ); 196 | 197 | if ( is_wp_error( $result ) ) { 198 | return $result; 199 | } 200 | 201 | return __( 'Export to GitHub completed successfully.', 'wp-github-sync' ); 202 | } 203 | 204 | /** 205 | * Use the new tree to save sha data 206 | * for all the updated posts. 207 | * 208 | * @param WordPress_GitHub_Sync_Post[] $posts Posts to fetch updated shas for. 209 | * 210 | * @return string|WP_Error 211 | */ 212 | protected function update_shas( array $posts ) { 213 | $master = $this->app->api()->fetch()->master(); 214 | $attempts = 1; 215 | 216 | while ( is_wp_error( $master ) && $attempts < 5 ) { 217 | $master = $this->app->api()->fetch()->master(); 218 | $attempts ++; 219 | } 220 | 221 | if ( is_wp_error( $master ) ) { 222 | // @todo throw a big warning! not having the latest shas is BAD 223 | // Solution: Show error message and link to kick off sha importing. 224 | return $master; 225 | } 226 | 227 | foreach ( $posts as $post ) { 228 | $blob = $master->tree()->get_blob_by_path( $post->github_path() ); 229 | 230 | if ( $blob ) { 231 | $this->app->database()->set_post_sha( $post, $blob->sha() ); 232 | } 233 | } 234 | 235 | return __( 'Export to GitHub completed successfully.', 'wp-github-sync' ); 236 | } 237 | 238 | /** 239 | * Saves the export user to the database. 240 | * 241 | * @param int $user_id User ID to export with. 242 | * 243 | * @return bool 244 | */ 245 | public function set_user( $user_id ) { 246 | return update_option( self::EXPORT_USER_OPTION, (int) $user_id ); 247 | } 248 | 249 | /** 250 | * Gets the commit message tag. 251 | * 252 | * @return string 253 | */ 254 | protected function get_commit_msg_tag() { 255 | $tag = apply_filters( 'wpghs_commit_msg_tag', 'wpghs' ); 256 | 257 | if ( ! $tag ) { 258 | throw new Exception( __( 'Commit message tag not set. Filter `wpghs_commit_msg_tag` misconfigured.', 'wp-github-sync' ) ); 259 | } 260 | 261 | return ' - ' . $tag; 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /tests/unit/test-tree.php: -------------------------------------------------------------------------------- 1 | sha = '1234567890qwertyuiop'; 11 | $data->url = 'https://api.github.com/trees'; 12 | 13 | $tree = new WordPress_GitHub_Sync_Tree( $data ); 14 | 15 | $this->assertSame( $data, $tree->get_data() ); 16 | $this->assertSame( $data->sha, $tree->sha() ); 17 | $this->assertSame( $data->url, $tree->url() ); 18 | $this->assertEmpty( $tree->blobs() ); 19 | } 20 | 21 | public function test_should_construct_empty() { 22 | $tree = new WordPress_GitHub_Sync_Tree( new stdClass ); 23 | 24 | $this->assertSame( '', $tree->sha() ); 25 | $this->assertSame( '', $tree->url() ); 26 | $this->assertEmpty( $tree->blobs() ); 27 | } 28 | 29 | public function test_should_set_sha() { 30 | $sha = '1234567890qwertyuiop'; 31 | $tree = new WordPress_GitHub_Sync_Tree( new stdClass ); 32 | 33 | $tree->set_sha( $sha ); 34 | 35 | $this->assertSame( $sha, $tree->sha() ); 36 | } 37 | 38 | public function test_should_set_array_of_blobs() { 39 | $this->blob 40 | ->shouldReceive( 'path' ) 41 | ->once() 42 | ->andReturn( '_posts/2015-10-31-new-post.md' ); 43 | $this->blob 44 | ->shouldReceive( 'sha' ) 45 | ->once() 46 | ->andReturn( '1234567890wertyuiop' ); 47 | $tree = new WordPress_GitHub_Sync_Tree( new stdClass ); 48 | $blobs = $tree->set_blobs( array( $this->blob ) )->blobs(); 49 | 50 | $this->assertCount( 1, $blobs ); 51 | $this->assertSame( $this->blob, $blobs[0] ); 52 | } 53 | 54 | public function test_should_add_post_to_tree() { 55 | $tree = new WordPress_GitHub_Sync_Tree( new stdClass ); 56 | 57 | $post_id = $this->factory->post->create(); 58 | $post = new WordPress_GitHub_Sync_Post( $post_id, $this->api ); 59 | $tree->add_post_to_tree( $post ); 60 | 61 | $this->assertCount( 1, $tree->blobs() ); 62 | $this->assertTrue( $tree->is_changed() ); 63 | 64 | $body = $tree->to_body(); 65 | 66 | $this->assertArrayHasKey( 'tree', $body ); 67 | $this->assertCount( 1, $body['tree'] ); 68 | } 69 | 70 | public function test_should_update_post_by_sha() { 71 | $sha = '1234567890qwertyuiop'; 72 | $path = '_posts/2015-10-31-new-post.md'; 73 | $this->blob 74 | ->shouldReceive( 'sha' ) 75 | ->andReturn( $sha ); 76 | $this->blob 77 | ->shouldReceive( 'path' ) 78 | ->andReturn( $path ); 79 | $this->blob 80 | ->shouldReceive( 'content_import' ) 81 | ->andReturn( 'Old content' ); 82 | $tree = new WordPress_GitHub_Sync_Tree( new stdClass ); 83 | $tree->add_blob( $this->blob ); 84 | 85 | $post_id = $this->factory->post->create(); 86 | update_post_meta( $post_id, '_sha', $sha ); 87 | $post = new WordPress_GitHub_Sync_Post( $post_id, $this->api ); 88 | $tree->add_post_to_tree( $post ); 89 | 90 | $this->assertCount( 1, $blobs = $tree->blobs() ); 91 | $this->assertNotSame( $this->blob, $blobs[0] ); 92 | $this->assertTrue( $tree->is_changed() ); 93 | 94 | $body = $tree->to_body(); 95 | 96 | $this->assertArrayHasKey( 'tree', $body ); 97 | $this->assertCount( 1, $body['tree'] ); 98 | } 99 | 100 | public function test_should_update_post_by_path() { 101 | $sha = '1234567890qwertyuiop'; 102 | $this->blob 103 | ->shouldReceive( 'sha' ) 104 | ->andReturn( $sha ); 105 | $post_id = $this->factory->post->create(); 106 | $post = new WordPress_GitHub_Sync_Post( $post_id, $this->api ); 107 | $this->blob 108 | ->shouldReceive( 'path' ) 109 | ->andReturn( $post->github_path() ); 110 | $this->blob 111 | ->shouldReceive( 'content_import' ) 112 | ->andReturn( 'Old content' ); 113 | $tree = new WordPress_GitHub_Sync_Tree( new stdClass ); 114 | $tree->add_blob( $this->blob ); 115 | 116 | $tree->add_post_to_tree( $post ); 117 | 118 | $this->assertCount( 1, $blobs = $tree->blobs() ); 119 | $this->assertNotSame( $this->blob, $blobs[0] ); 120 | $this->assertTrue( $tree->is_changed() ); 121 | 122 | $body = $tree->to_body(); 123 | 124 | $this->assertArrayHasKey( 'tree', $body ); 125 | $this->assertCount( 1, $body['tree'] ); 126 | } 127 | 128 | public function test_should_not_update_post_if_unchanged() { 129 | $sha = '1234567890qwertyuiop'; 130 | $this->blob 131 | ->shouldReceive( 'sha' ) 132 | ->andReturn( $sha ); 133 | $post_id = $this->factory->post->create(); 134 | $post = new WordPress_GitHub_Sync_Post( $post_id, $this->api ); 135 | $this->blob 136 | ->shouldReceive( 'path' ) 137 | ->andReturn( $post->github_path() ); 138 | $this->blob 139 | ->shouldReceive( 'content_import' ) 140 | ->andReturn( $post->github_content() ); 141 | $tree = new WordPress_GitHub_Sync_Tree( new stdClass ); 142 | $tree->add_blob( $this->blob ); 143 | 144 | $tree->add_post_to_tree( $post ); 145 | 146 | $this->assertCount( 1, $blobs = $tree->blobs() ); 147 | $this->assertSame( $this->blob, $blobs[0] ); 148 | $this->assertFalse( $tree->is_changed() ); 149 | 150 | $this->blob 151 | ->shouldReceive( 'to_body' ) 152 | ->andReturn( new stdClass ); 153 | $body = $tree->to_body(); 154 | 155 | $this->assertArrayHasKey( 'tree', $body ); 156 | $this->assertCount( 1, $body['tree'] ); 157 | } 158 | 159 | public function test_should_remove_post_by_sha() { 160 | $sha = '1234567890qwertyuiop'; 161 | $path = '_posts/2015-10-31-new-post.md'; 162 | $this->blob 163 | ->shouldReceive( 'sha' ) 164 | ->andReturn( $sha ); 165 | $this->blob 166 | ->shouldReceive( 'path' ) 167 | ->andReturn( $path ); 168 | $tree = new WordPress_GitHub_Sync_Tree( new stdClass ); 169 | $tree->add_blob( $this->blob ); 170 | 171 | $post_id = $this->factory->post->create(); 172 | update_post_meta( $post_id, '_sha', $sha ); 173 | $post = new WordPress_GitHub_Sync_Post( $post_id, $this->api ); 174 | $tree->remove_post_from_tree( $post ); 175 | 176 | $this->assertCount( 0, $blobs = $tree->blobs() ); 177 | $this->assertTrue( $tree->is_changed() ); 178 | 179 | $body = $tree->to_body(); 180 | 181 | $this->assertArrayHasKey( 'tree', $body ); 182 | $this->assertCount( 0, $body['tree'] ); 183 | } 184 | 185 | public function test_should_remove_post_by_path() { 186 | $sha = '1234567890qwertyuiop'; 187 | $this->blob 188 | ->shouldReceive( 'sha' ) 189 | ->andReturn( $sha ); 190 | $post_id = $this->factory->post->create(); 191 | $post = new WordPress_GitHub_Sync_Post( $post_id, $this->api ); 192 | $this->blob 193 | ->shouldReceive( 'path' ) 194 | ->andReturn( $post->github_path() ); 195 | $tree = new WordPress_GitHub_Sync_Tree( new stdClass ); 196 | $tree->add_blob( $this->blob ); 197 | 198 | $tree->remove_post_from_tree( $post ); 199 | 200 | $this->assertCount( 0, $blobs = $tree->blobs() ); 201 | $this->assertTrue( $tree->is_changed() ); 202 | 203 | $body = $tree->to_body(); 204 | 205 | $this->assertArrayHasKey( 'tree', $body ); 206 | $this->assertCount( 0, $body['tree'] ); 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /languages/wp-github-sync-zh_CN.po: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2015 WordPress GitHub Sync 2 | # This file is distributed under the same license as the WordPress GitHub Sync package. 3 | msgid "" 4 | msgstr "" 5 | "Project-Id-Version: WordPress GitHub Sync 1.3.4\n" 6 | "Report-Msgid-Bugs-To: http://wordpress.org/tag/wp-github-sync\n" 7 | "POT-Creation-Date: 2015-11-08 22:00:38+00:00\n" 8 | "MIME-Version: 1.0\n" 9 | "Content-Type: text/plain; charset=UTF-8\n" 10 | "Content-Transfer-Encoding: 8bit\n" 11 | "PO-Revision-Date: 2015-11-20 11:25+0800\n" 12 | "Last-Translator: Alex Lee \n" 13 | "Plural-Forms: nplurals=1; plural=0;\n" 14 | "Language-Team: \n" 15 | "Language: zh_CN\n" 16 | "X-Generator: Poedit 1.7.4\n" 17 | 18 | #: lib/admin.php:40 19 | msgid "GitHub hostname" 20 | msgstr "GitHub主机名" 21 | 22 | #: lib/admin.php:43 23 | msgid "The GitHub host to use. Can be changed to support a GitHub Enterprise installation." 24 | msgstr "GitHub服务器的主机名,企业账户可按实际作出改动。" 25 | 26 | #: lib/admin.php:48 27 | msgid "Repository" 28 | msgstr "项目名" 29 | 30 | #: lib/admin.php:51 31 | msgid "The GitHub repository to commit to, with owner ([OWNER]/[REPOSITORY]), e.g., benbalter/benbalter.github.com. The repository should contain an initial commit." 32 | msgstr "GitHub上的项目名,格或为([作者]/[项目名]),例如benbalter/benbalter.github.com。项目库必须至少包含一个初始提交。" 33 | 34 | #: lib/admin.php:56 35 | msgid "Oauth Token" 36 | msgstr "Oauth授权码" 37 | 38 | #: lib/admin.php:59 39 | msgid "A personal oauth token with public_repo scope." 40 | msgstr "GitHub上的 个人Oauth授权码,授权范围为public_repo。" 41 | 42 | #: lib/admin.php:64 43 | msgid "Webhook Secret" 44 | msgstr "Webhook密匙" 45 | 46 | #: lib/admin.php:67 47 | msgid "The webhook's secret phrase." 48 | msgstr "Webhook回调密匙。" 49 | 50 | #: lib/admin.php:72 51 | msgid "Default Import User" 52 | msgstr "默认导入人" 53 | 54 | #: lib/admin.php:75 55 | msgid "The fallback user for import, in case WordPress <--> GitHub Sync cannot find the committer in the database." 56 | msgstr "如果WordPress <--> GitHub在数据库中无法查找到提交者,将默认以该用户作为文章的作者。" 57 | 58 | #: lib/admin.php:108 59 | msgid "Export to GitHub started." 60 | msgstr "开始导出到GitHub。" 61 | 62 | #: lib/admin.php:115 63 | msgid "Export to GitHub failed with error:" 64 | msgstr "导出到GitHub过程中遇到错误中止。" 65 | 66 | #: lib/admin.php:122 lib/export.php:201 lib/export.php:235 67 | msgid "Export to GitHub completed successfully." 68 | msgstr "成功导出到GitHub。" 69 | 70 | #: lib/admin.php:134 views/options.php:9 71 | msgid "WordPress <--> GitHub Sync" 72 | msgstr "WordPress <--> GitHub Sync" 73 | 74 | #: lib/admin.php:135 75 | msgid "GitHub Sync" 76 | msgstr "GitHub同步" 77 | 78 | #: lib/cli.php:51 lib/cli.php:93 79 | msgid "Invalid user ID" 80 | msgstr "无效的用户ID" 81 | 82 | #: lib/cli.php:57 wp-github-sync.php:182 83 | msgid "Starting full export to GitHub." 84 | msgstr "准备全部导出到GitHub。" 85 | 86 | #: lib/cli.php:62 87 | msgid "Exporting post ID to GitHub: %d" 88 | msgstr "正在导出到GitHub的文章ID:%d" 89 | 90 | #: lib/cli.php:68 91 | msgid "Invalid post ID" 92 | msgstr "无效文章ID" 93 | 94 | #: lib/cli.php:98 wp-github-sync.php:194 95 | msgid "Starting import from GitHub." 96 | msgstr "准备从GitHub导入。" 97 | 98 | #: lib/client/base.php:84 99 | msgid "WordPress-GitHub-Sync needs an auth token. Please update your settings." 100 | msgstr "WordPress-GitHub-Sync需要指定一个授权码,请更新设置。" 101 | 102 | #: lib/client/base.php:93 103 | msgid "WordPress-GitHub-Sync needs a repository. Please update your settings." 104 | msgstr "WordPress-GitHub-Sync需要指定一个项目库,请更新设置。" 105 | 106 | #: lib/client/base.php:102 107 | msgid "WordPress-GitHub-Sync needs a properly formed repository. Please update your settings." 108 | msgstr "项目名称有误,请按格式修改。" 109 | 110 | #: lib/client/persist.php:23 111 | msgid "There were no changes, so no additional commit was added." 112 | msgstr "因为没有更新,所以未添加新的提交(Commit)。" 113 | 114 | # I guess have to leave this string 'untranslated'? As I came across this error message which was shown on GitHub if a sync unsuccessful (in payload unsuccessful response). 115 | #: lib/controller.php:39 lib/controller.php:86 lib/controller.php:114 116 | #: lib/controller.php:148 lib/controller.php:176 117 | msgid "%s : Semaphore is locked, import/export already in progress." 118 | msgstr "同步机制已触发,正在进行导入/导出。" 119 | 120 | #: lib/controller.php:46 121 | msgid "Failed to validate secret." 122 | msgstr "无法验证密匙。" 123 | 124 | #: lib/controller.php:56 125 | msgid "%s is an invalid repository." 126 | msgstr "%s并非有效项目库。" 127 | 128 | #: lib/database.php:62 129 | msgid "Querying for supported posts returned no results." 130 | msgstr "无法找到有效格式的文章。" 131 | 132 | #: lib/database.php:88 133 | msgid "Post ID %s is not supported by WPGHS. See wiki to find out how to add support." 134 | msgstr "ID为%s的文章格式不被WPGHS支持同步,请前往Wiki寻找同步办法。" 135 | 136 | #: lib/database.php:119 137 | msgid "Post for sha %s not found." 138 | msgstr "无法找到SHA为%s的文章。" 139 | 140 | #: lib/database.php:184 141 | msgid "Successfully saved posts." 142 | msgstr "成功保存文章。" 143 | 144 | #: lib/database.php:223 145 | msgid "Post not found for path %s." 146 | msgstr "无法找到路径%s。" 147 | 148 | #: lib/database.php:241 149 | msgid "Failed to delete post ID %d." 150 | msgstr "无法删除ID为%d的文章。" 151 | 152 | #: lib/database.php:249 153 | msgid "Successfully deleted post ID %d." 154 | msgstr "成功删除ID为%d的文章。" 155 | 156 | #: lib/database.php:340 157 | msgid "Commit user not found for email %s" 158 | msgstr "无法找到电邮为%s的用户" 159 | 160 | #: lib/database.php:413 161 | msgid "No change for post ID %d." 162 | msgstr "ID为%d的文章没有变动。" 163 | 164 | #: lib/database.php:421 165 | msgid "Successfully updated post ID %d." 166 | msgstr "ID为%d的文章已更新。" 167 | 168 | #: lib/import.php:70 169 | msgid "Payload processed" 170 | msgstr "同步已执行" 171 | 172 | #: lib/import.php:95 lib/payload.php:58 173 | msgid "Already synced this commit." 174 | msgstr "该提交(Commit)已经同步。" 175 | 176 | #: lib/payload.php:53 177 | msgid "Not on the master branch." 178 | msgstr "不在master分支上。" 179 | 180 | #: views/options.php:16 181 | msgid "Webhook callback" 182 | msgstr "Webhook回调地址" 183 | 184 | #: views/options.php:20 185 | msgid "Bulk actions" 186 | msgstr "批量操作" 187 | 188 | #: views/options.php:23 189 | msgid "Export to GitHub" 190 | msgstr "导出到GitHub" 191 | 192 | #: views/options.php:26 193 | msgid "Import from GitHub" 194 | msgstr "从GitHub导入" 195 | 196 | #: wp-github-sync.php:223 197 | msgid "To set up your site to sync with GitHub, update your settings and click \"Export to GitHub.\"" 198 | msgstr "要设置网站与GitHub同步,请更新设置并点击\"导出到 GitHub\"。" 199 | 200 | #. Plugin Name of the plugin/theme 201 | msgid "WordPress GitHub Sync" 202 | msgstr "" 203 | 204 | #. Plugin URI of the plugin/theme 205 | msgid "https://github.com/mAAdhaTTah/wordpress-github-sync" 206 | msgstr "" 207 | 208 | #. Description of the plugin/theme 209 | msgid "A WordPress plugin to sync content with a GitHub repository (or Jekyll site)." 210 | msgstr "让WordPress站点和GitHub项目库(或Jekyll站点)同步内容的WordPress插件" 211 | 212 | #. Author of the plugin/theme 213 | msgid "James DiGioia, Ben Balter" 214 | msgstr "" 215 | 216 | #. Author URI of the plugin/theme 217 | msgid "http://jamesdigioia.com" 218 | msgstr "" 219 | -------------------------------------------------------------------------------- /lib/admin.php: -------------------------------------------------------------------------------- 1 | 'https://api.github.com', 42 | 'name' => 'wpghs_host', 43 | 'help_text' => __( 'The GitHub host to use. This only needs to be changed to support a GitHub Enterprise installation.', 'wp-github-sync' ), 44 | ) 45 | ); 46 | 47 | register_setting( WordPress_GitHub_Sync::$text_domain, 'wpghs_repository' ); 48 | add_settings_field( 'wpghs_repository', __( 'Repository', 'wp-github-sync' ), array( $this, 'field_callback' ), WordPress_GitHub_Sync::$text_domain, 'general', array( 49 | 'default' => '', 50 | 'name' => 'wpghs_repository', 51 | 'help_text' => __( 'The GitHub repository to commit to, with owner ([OWNER]/[REPOSITORY]), e.g., github/hubot.github.com. The repository should contain an initial commit, which is satisfied by including a README when you create the repository on GitHub.', 'wp-github-sync' ), 52 | ) 53 | ); 54 | 55 | register_setting( WordPress_GitHub_Sync::$text_domain, 'wpghs_oauth_token' ); 56 | add_settings_field( 'wpghs_oauth_token', __( 'Oauth Token', 'wp-github-sync' ), array( $this, 'field_callback' ), WordPress_GitHub_Sync::$text_domain, 'general', array( 57 | 'default' => '', 58 | 'name' => 'wpghs_oauth_token', 59 | 'help_text' => __( "A personal oauth token with public_repo scope.", 'wp-github-sync' ), 60 | ) 61 | ); 62 | 63 | register_setting( WordPress_GitHub_Sync::$text_domain, 'wpghs_secret' ); 64 | add_settings_field( 'wpghs_secret', __( 'Webhook Secret', 'wp-github-sync' ), array( $this, 'field_callback' ), WordPress_GitHub_Sync::$text_domain, 'general', array( 65 | 'default' => '', 66 | 'name' => 'wpghs_secret', 67 | 'help_text' => __( "The webhook's secret phrase. This should be password strength, as it is used to verify the webhook's payload.", 'wp-github-sync' ), 68 | ) 69 | ); 70 | 71 | register_setting( WordPress_GitHub_Sync::$text_domain, 'wpghs_default_user' ); 72 | add_settings_field( 'wpghs_default_user', __( 'Default Import User', 'wp-github-sync' ), array( &$this, 'user_field_callback' ), WordPress_GitHub_Sync::$text_domain, 'general', array( 73 | 'default' => '', 74 | 'name' => 'wpghs_default_user', 75 | 'help_text' => __( 'The fallback user for import, in case WordPress <--> GitHub Sync cannot find the committer in the database.', 'wp-github-sync' ), 76 | ) 77 | ); 78 | } 79 | 80 | /** 81 | * Callback to render an individual options field 82 | * 83 | * @param array $args Field arguments. 84 | */ 85 | public function field_callback( $args ) { 86 | include dirname( dirname( __FILE__ ) ) . '/views/setting-field.php'; 87 | } 88 | 89 | /** 90 | * Callback to render the default import user field. 91 | * 92 | * @param array $args Field arguments. 93 | */ 94 | public function user_field_callback( $args ) { 95 | include dirname( dirname( __FILE__ ) ) . '/views/user-setting-field.php'; 96 | } 97 | 98 | /** 99 | * Displays settings messages from background processes 100 | */ 101 | public function section_callback() { 102 | if ( get_current_screen()->id !== 'settings_page_' . WordPress_GitHub_Sync::$text_domain ) { 103 | return; 104 | } 105 | 106 | if ( 'yes' === get_option( '_wpghs_export_started' ) ) { ?> 107 |
108 |

109 |
114 |
115 |

116 |
121 |
122 |

123 |
128 |
129 |

130 |
135 |
136 |

137 |
142 |
143 |

144 |
GitHub Sync', 'wp-github-sync' ), 155 | __( 'GitHub Sync', 'wp-github-sync' ), 156 | 'manage_options', 157 | WordPress_GitHub_Sync::$text_domain, 158 | array( $this, 'settings_page' ) 159 | ); 160 | } 161 | 162 | /** 163 | * Admin callback to trigger import/export because WordPress admin routing lol 164 | */ 165 | public function trigger_cron() { 166 | if ( ! current_user_can( 'manage_options' ) ) { 167 | return; 168 | } 169 | 170 | if ( get_current_screen()->id !== 'settings_page_' . WordPress_GitHub_Sync::$text_domain ) { 171 | return; 172 | } 173 | 174 | if ( ! isset( $_GET['action'] ) ) { 175 | return; 176 | } 177 | 178 | if ( 'export' === $_GET['action'] ) { 179 | WordPress_GitHub_Sync::$instance->start_export(); 180 | } 181 | 182 | if ( 'import' === $_GET['action'] ) { 183 | WordPress_GitHub_Sync::$instance->start_import(); 184 | } 185 | 186 | wp_redirect( admin_url( 'options-general.php?page=wp-github-sync' ) ); 187 | die; 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /tests/data/payload-valid.json: -------------------------------------------------------------------------------- 1 | { 2 | "ref": "refs/heads/master", 3 | "before": "bbe177c517891f5e33a59bf08fcb78e8ef766801", 4 | "after": "ad4e0a9e2597de40106d5f52e2041d8ebaeb0087", 5 | "created": false, 6 | "deleted": false, 7 | "forced": false, 8 | "base_ref": null, 9 | "compare": "https://github.com/owner/repo/compare/bbe177c51789...ad4e0a9e2597", 10 | "commits": [ 11 | { 12 | "id": "ad4e0a9e2597de40106d5f52e2041d8ebaeb0087", 13 | "distinct": true, 14 | "message": "Updated \"Recursive Closures in PHP\" - wpghs", 15 | "timestamp": "2015-10-26T17:37:42-04:00", 16 | "url": "https://github.com/owner/repo/commit/ad4e0a9e2597de40106d5f52e2041d8ebaeb0087", 17 | "author": { 18 | "name": "Author Name", 19 | "email": "username@github.com", 20 | "username": "owner" 21 | }, 22 | "committer": { 23 | "name": "Author Name", 24 | "email": "username@github.com", 25 | "username": "owner" 26 | }, 27 | "added": [ 28 | "_posts/2015-10-26-recursive-closures-in-php.md" 29 | ], 30 | "removed": [ 31 | "_drafts/2015-09-28-recursive-closures-in-php.md" 32 | ], 33 | "modified": [ 34 | 35 | ] 36 | } 37 | ], 38 | "head_commit": { 39 | "id": "ad4e0a9e2597de40106d5f52e2041d8ebaeb0087", 40 | "distinct": true, 41 | "message": "Updated \"Recursive Closures in PHP\"", 42 | "timestamp": "2015-10-26T17:37:42-04:00", 43 | "url": "https://github.com/owner/repo/commit/ad4e0a9e2597de40106d5f52e2041d8ebaeb0087", 44 | "author": { 45 | "name": "Author Name", 46 | "email": "username@github.com", 47 | "username": "owner" 48 | }, 49 | "committer": { 50 | "name": "Author Name", 51 | "email": "username@github.com", 52 | "username": "owner" 53 | }, 54 | "added": [ 55 | "_posts/2015-10-26-recursive-closures-in-php.md" 56 | ], 57 | "removed": [ 58 | "_drafts/2015-09-28-recursive-closures-in-php.md" 59 | ], 60 | "modified": [ 61 | 62 | ] 63 | }, 64 | "repository": { 65 | "id": 29217659, 66 | "name": "repo", 67 | "full_name": "owner/repo", 68 | "owner": { 69 | "name": "owner", 70 | "email": "username@github.com" 71 | }, 72 | "private": false, 73 | "html_url": "https://github.com/owner/repo", 74 | "description": "This git repo maintains a collection of my repos and works-in-progress published both on my website at JamesDiGioia.com as well as elsewhere.", 75 | "fork": false, 76 | "url": "https://github.com/owner/repo", 77 | "forks_url": "https://api.github.com/repos/owner/repo/forks", 78 | "keys_url": "https://api.github.com/repos/owner/repo/keys{/key_id}", 79 | "collaborators_url": "https://api.github.com/repos/owner/repo/collaborators{/collaborator}", 80 | "teams_url": "https://api.github.com/repos/owner/repo/teams", 81 | "hooks_url": "https://api.github.com/repos/owner/repo/hooks", 82 | "issue_events_url": "https://api.github.com/repos/owner/repo/issues/events{/number}", 83 | "events_url": "https://api.github.com/repos/owner/repo/events", 84 | "assignees_url": "https://api.github.com/repos/owner/repo/assignees{/user}", 85 | "branches_url": "https://api.github.com/repos/owner/repo/branches{/branch}", 86 | "tags_url": "https://api.github.com/repos/owner/repo/tags", 87 | "blobs_url": "https://api.github.com/repos/owner/repo/git/blobs{/sha}", 88 | "git_tags_url": "https://api.github.com/repos/owner/repo/git/tags{/sha}", 89 | "git_refs_url": "https://api.github.com/repos/owner/repo/git/refs{/sha}", 90 | "trees_url": "https://api.github.com/repos/owner/repo/git/trees{/sha}", 91 | "statuses_url": "https://api.github.com/repos/owner/repo/statuses/{sha}", 92 | "languages_url": "https://api.github.com/repos/owner/repo/languages", 93 | "stargazers_url": "https://api.github.com/repos/owner/repo/stargazers", 94 | "contributors_url": "https://api.github.com/repos/owner/repo/contributors", 95 | "subscribers_url": "https://api.github.com/repos/owner/repo/subscribers", 96 | "subscription_url": "https://api.github.com/repos/owner/repo/subscription", 97 | "commits_url": "https://api.github.com/repos/owner/repo/commits{/sha}", 98 | "git_commits_url": "https://api.github.com/repos/owner/repo/git/commits{/sha}", 99 | "comments_url": "https://api.github.com/repos/owner/repo/comments{/number}", 100 | "issue_comment_url": "https://api.github.com/repos/owner/repo/issues/comments{/number}", 101 | "contents_url": "https://api.github.com/repos/owner/repo/contents/{+path}", 102 | "compare_url": "https://api.github.com/repos/owner/repo/compare/{base}...{head}", 103 | "merges_url": "https://api.github.com/repos/owner/repo/merges", 104 | "archive_url": "https://api.github.com/repos/owner/repo/{archive_format}{/ref}", 105 | "downloads_url": "https://api.github.com/repos/owner/repo/downloads", 106 | "issues_url": "https://api.github.com/repos/owner/repo/issues{/number}", 107 | "pulls_url": "https://api.github.com/repos/owner/repo/pulls{/number}", 108 | "milestones_url": "https://api.github.com/repos/owner/repo/milestones{/number}", 109 | "notifications_url": "https://api.github.com/repos/owner/repo/notifications{?since,all,participating}", 110 | "labels_url": "https://api.github.com/repos/owner/repo/labels{/name}", 111 | "releases_url": "https://api.github.com/repos/owner/repo/releases{/id}", 112 | "created_at": 1421192829, 113 | "updated_at": "2015-04-25T16:55:49Z", 114 | "pushed_at": 1445895463, 115 | "git_url": "git://github.com/owner/repo.git", 116 | "ssh_url": "git@github.com:owner/repo.git", 117 | "clone_url": "https://github.com/owner/repo.git", 118 | "svn_url": "https://github.com/owner/repo", 119 | "homepage": "http://website.com/", 120 | "size": 10364, 121 | "stargazers_count": 0, 122 | "watchers_count": 0, 123 | "language": null, 124 | "has_issues": true, 125 | "has_downloads": true, 126 | "has_wiki": false, 127 | "has_pages": false, 128 | "forks_count": 0, 129 | "mirror_url": null, 130 | "open_issues_count": 0, 131 | "forks": 0, 132 | "open_issues": 0, 133 | "watchers": 0, 134 | "default_branch": "master", 135 | "stargazers": 0, 136 | "master_branch": "master" 137 | }, 138 | "pusher": { 139 | "name": "owner", 140 | "email": "username@github.com" 141 | }, 142 | "sender": { 143 | "login": "owner", 144 | "id": 4371429, 145 | "avatar_url": "https://avatars.githubusercontent.com/u/4371429?v=3", 146 | "gravatar_id": "", 147 | "url": "https://api.github.com/users/owner", 148 | "html_url": "https://github.com/owner", 149 | "followers_url": "https://api.github.com/users/owner/followers", 150 | "following_url": "https://api.github.com/users/owner/following{/other_user}", 151 | "gists_url": "https://api.github.com/users/owner/gists{/gist_id}", 152 | "starred_url": "https://api.github.com/users/owner/starred{/owner}{/repo}", 153 | "subscriptions_url": "https://api.github.com/users/owner/subscriptions", 154 | "organizations_url": "https://api.github.com/users/owner/orgs", 155 | "repos_url": "https://api.github.com/users/owner/repos", 156 | "events_url": "https://api.github.com/users/owner/events{/privacy}", 157 | "received_events_url": "https://api.github.com/users/owner/received_events", 158 | "type": "User", 159 | "site_admin": false 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /tests/data/payload-synced-commit.json: -------------------------------------------------------------------------------- 1 | { 2 | "ref": "refs/heads/master", 3 | "before": "bbe177c517891f5e33a59bf08fcb78e8ef766801", 4 | "after": "ad4e0a9e2597de40106d5f52e2041d8ebaeb0087", 5 | "created": false, 6 | "deleted": false, 7 | "forced": false, 8 | "base_ref": null, 9 | "compare": "https://github.com/owner/repo/compare/bbe177c51789...ad4e0a9e2597", 10 | "commits": [ 11 | { 12 | "id": "ad4e0a9e2597de40106d5f52e2041d8ebaeb0087", 13 | "distinct": true, 14 | "message": "Updated \"Recursive Closures in PHP\" - wpghs", 15 | "timestamp": "2015-10-26T17:37:42-04:00", 16 | "url": "https://github.com/owner/repo/commit/ad4e0a9e2597de40106d5f52e2041d8ebaeb0087", 17 | "author": { 18 | "name": "Author Name", 19 | "email": "username@github.com", 20 | "username": "owner" 21 | }, 22 | "committer": { 23 | "name": "Author Name", 24 | "email": "username@github.com", 25 | "username": "owner" 26 | }, 27 | "added": [ 28 | "_posts/2015-10-26-recursive-closures-in-php.md" 29 | ], 30 | "removed": [ 31 | "_drafts/2015-09-28-recursive-closures-in-php.md" 32 | ], 33 | "modified": [ 34 | 35 | ] 36 | } 37 | ], 38 | "head_commit": { 39 | "id": "ad4e0a9e2597de40106d5f52e2041d8ebaeb0087", 40 | "distinct": true, 41 | "message": "Updated \"Recursive Closures in PHP\" - wpghs", 42 | "timestamp": "2015-10-26T17:37:42-04:00", 43 | "url": "https://github.com/owner/repo/commit/ad4e0a9e2597de40106d5f52e2041d8ebaeb0087", 44 | "author": { 45 | "name": "Author Name", 46 | "email": "username@github.com", 47 | "username": "owner" 48 | }, 49 | "committer": { 50 | "name": "Author Name", 51 | "email": "username@github.com", 52 | "username": "owner" 53 | }, 54 | "added": [ 55 | "_posts/2015-10-26-recursive-closures-in-php.md" 56 | ], 57 | "removed": [ 58 | "_drafts/2015-09-28-recursive-closures-in-php.md" 59 | ], 60 | "modified": [ 61 | 62 | ] 63 | }, 64 | "repository": { 65 | "id": 29217659, 66 | "name": "repo", 67 | "full_name": "owner/repo", 68 | "owner": { 69 | "name": "owner", 70 | "email": "username@github.com" 71 | }, 72 | "private": false, 73 | "html_url": "https://github.com/owner/repo", 74 | "description": "This git repo maintains a collection of my repos and works-in-progress published both on my website at JamesDiGioia.com as well as elsewhere.", 75 | "fork": false, 76 | "url": "https://github.com/owner/repo", 77 | "forks_url": "https://api.github.com/repos/owner/repo/forks", 78 | "keys_url": "https://api.github.com/repos/owner/repo/keys{/key_id}", 79 | "collaborators_url": "https://api.github.com/repos/owner/repo/collaborators{/collaborator}", 80 | "teams_url": "https://api.github.com/repos/owner/repo/teams", 81 | "hooks_url": "https://api.github.com/repos/owner/repo/hooks", 82 | "issue_events_url": "https://api.github.com/repos/owner/repo/issues/events{/number}", 83 | "events_url": "https://api.github.com/repos/owner/repo/events", 84 | "assignees_url": "https://api.github.com/repos/owner/repo/assignees{/user}", 85 | "branches_url": "https://api.github.com/repos/owner/repo/branches{/branch}", 86 | "tags_url": "https://api.github.com/repos/owner/repo/tags", 87 | "blobs_url": "https://api.github.com/repos/owner/repo/git/blobs{/sha}", 88 | "git_tags_url": "https://api.github.com/repos/owner/repo/git/tags{/sha}", 89 | "git_refs_url": "https://api.github.com/repos/owner/repo/git/refs{/sha}", 90 | "trees_url": "https://api.github.com/repos/owner/repo/git/trees{/sha}", 91 | "statuses_url": "https://api.github.com/repos/owner/repo/statuses/{sha}", 92 | "languages_url": "https://api.github.com/repos/owner/repo/languages", 93 | "stargazers_url": "https://api.github.com/repos/owner/repo/stargazers", 94 | "contributors_url": "https://api.github.com/repos/owner/repo/contributors", 95 | "subscribers_url": "https://api.github.com/repos/owner/repo/subscribers", 96 | "subscription_url": "https://api.github.com/repos/owner/repo/subscription", 97 | "commits_url": "https://api.github.com/repos/owner/repo/commits{/sha}", 98 | "git_commits_url": "https://api.github.com/repos/owner/repo/git/commits{/sha}", 99 | "comments_url": "https://api.github.com/repos/owner/repo/comments{/number}", 100 | "issue_comment_url": "https://api.github.com/repos/owner/repo/issues/comments{/number}", 101 | "contents_url": "https://api.github.com/repos/owner/repo/contents/{+path}", 102 | "compare_url": "https://api.github.com/repos/owner/repo/compare/{base}...{head}", 103 | "merges_url": "https://api.github.com/repos/owner/repo/merges", 104 | "archive_url": "https://api.github.com/repos/owner/repo/{archive_format}{/ref}", 105 | "downloads_url": "https://api.github.com/repos/owner/repo/downloads", 106 | "issues_url": "https://api.github.com/repos/owner/repo/issues{/number}", 107 | "pulls_url": "https://api.github.com/repos/owner/repo/pulls{/number}", 108 | "milestones_url": "https://api.github.com/repos/owner/repo/milestones{/number}", 109 | "notifications_url": "https://api.github.com/repos/owner/repo/notifications{?since,all,participating}", 110 | "labels_url": "https://api.github.com/repos/owner/repo/labels{/name}", 111 | "releases_url": "https://api.github.com/repos/owner/repo/releases{/id}", 112 | "created_at": 1421192829, 113 | "updated_at": "2015-04-25T16:55:49Z", 114 | "pushed_at": 1445895463, 115 | "git_url": "git://github.com/owner/repo.git", 116 | "ssh_url": "git@github.com:owner/repo.git", 117 | "clone_url": "https://github.com/owner/repo.git", 118 | "svn_url": "https://github.com/owner/repo", 119 | "homepage": "http://website.com/", 120 | "size": 10364, 121 | "stargazers_count": 0, 122 | "watchers_count": 0, 123 | "language": null, 124 | "has_issues": true, 125 | "has_downloads": true, 126 | "has_wiki": false, 127 | "has_pages": false, 128 | "forks_count": 0, 129 | "mirror_url": null, 130 | "open_issues_count": 0, 131 | "forks": 0, 132 | "open_issues": 0, 133 | "watchers": 0, 134 | "default_branch": "master", 135 | "stargazers": 0, 136 | "master_branch": "master" 137 | }, 138 | "pusher": { 139 | "name": "owner", 140 | "email": "username@github.com" 141 | }, 142 | "sender": { 143 | "login": "owner", 144 | "id": 4371429, 145 | "avatar_url": "https://avatars.githubusercontent.com/u/4371429?v=3", 146 | "gravatar_id": "", 147 | "url": "https://api.github.com/users/owner", 148 | "html_url": "https://github.com/owner", 149 | "followers_url": "https://api.github.com/users/owner/followers", 150 | "following_url": "https://api.github.com/users/owner/following{/other_user}", 151 | "gists_url": "https://api.github.com/users/owner/gists{/gist_id}", 152 | "starred_url": "https://api.github.com/users/owner/starred{/owner}{/repo}", 153 | "subscriptions_url": "https://api.github.com/users/owner/subscriptions", 154 | "organizations_url": "https://api.github.com/users/owner/orgs", 155 | "repos_url": "https://api.github.com/users/owner/repos", 156 | "events_url": "https://api.github.com/users/owner/events{/privacy}", 157 | "received_events_url": "https://api.github.com/users/owner/received_events", 158 | "type": "User", 159 | "site_admin": false 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /tests/data/payload-invalid-branch.json: -------------------------------------------------------------------------------- 1 | { 2 | "ref": "refs/heads/new-post-branch", 3 | "before": "bbe177c517891f5e33a59bf08fcb78e8ef766801", 4 | "after": "ad4e0a9e2597de40106d5f52e2041d8ebaeb0087", 5 | "created": false, 6 | "deleted": false, 7 | "forced": false, 8 | "base_ref": null, 9 | "compare": "https://github.com/owner/repo/compare/bbe177c51789...ad4e0a9e2597", 10 | "commits": [ 11 | { 12 | "id": "ad4e0a9e2597de40106d5f52e2041d8ebaeb0087", 13 | "distinct": true, 14 | "message": "Updated \"Recursive Closures in PHP\" - wpghs", 15 | "timestamp": "2015-10-26T17:37:42-04:00", 16 | "url": "https://github.com/owner/repo/commit/ad4e0a9e2597de40106d5f52e2041d8ebaeb0087", 17 | "author": { 18 | "name": "Author Name", 19 | "email": "username@github.com", 20 | "username": "owner" 21 | }, 22 | "committer": { 23 | "name": "Author Name", 24 | "email": "username@github.com", 25 | "username": "owner" 26 | }, 27 | "added": [ 28 | "_posts/2015-10-26-recursive-closures-in-php.md" 29 | ], 30 | "removed": [ 31 | "_drafts/2015-09-28-recursive-closures-in-php.md" 32 | ], 33 | "modified": [ 34 | 35 | ] 36 | } 37 | ], 38 | "head_commit": { 39 | "id": "ad4e0a9e2597de40106d5f52e2041d8ebaeb0087", 40 | "distinct": true, 41 | "message": "Updated \"Recursive Closures in PHP\" - wpghs", 42 | "timestamp": "2015-10-26T17:37:42-04:00", 43 | "url": "https://github.com/owner/repo/commit/ad4e0a9e2597de40106d5f52e2041d8ebaeb0087", 44 | "author": { 45 | "name": "Author Name", 46 | "email": "username@github.com", 47 | "username": "owner" 48 | }, 49 | "committer": { 50 | "name": "Author Name", 51 | "email": "username@github.com", 52 | "username": "owner" 53 | }, 54 | "added": [ 55 | "_posts/2015-10-26-recursive-closures-in-php.md" 56 | ], 57 | "removed": [ 58 | "_drafts/2015-09-28-recursive-closures-in-php.md" 59 | ], 60 | "modified": [ 61 | 62 | ] 63 | }, 64 | "repository": { 65 | "id": 29217659, 66 | "name": "repo", 67 | "full_name": "owner/repo", 68 | "owner": { 69 | "name": "owner", 70 | "email": "username@github.com" 71 | }, 72 | "private": false, 73 | "html_url": "https://github.com/owner/repo", 74 | "description": "This git repo maintains a collection of my repos and works-in-progress published both on my website at JamesDiGioia.com as well as elsewhere.", 75 | "fork": false, 76 | "url": "https://github.com/owner/repo", 77 | "forks_url": "https://api.github.com/repos/owner/repo/forks", 78 | "keys_url": "https://api.github.com/repos/owner/repo/keys{/key_id}", 79 | "collaborators_url": "https://api.github.com/repos/owner/repo/collaborators{/collaborator}", 80 | "teams_url": "https://api.github.com/repos/owner/repo/teams", 81 | "hooks_url": "https://api.github.com/repos/owner/repo/hooks", 82 | "issue_events_url": "https://api.github.com/repos/owner/repo/issues/events{/number}", 83 | "events_url": "https://api.github.com/repos/owner/repo/events", 84 | "assignees_url": "https://api.github.com/repos/owner/repo/assignees{/user}", 85 | "branches_url": "https://api.github.com/repos/owner/repo/branches{/branch}", 86 | "tags_url": "https://api.github.com/repos/owner/repo/tags", 87 | "blobs_url": "https://api.github.com/repos/owner/repo/git/blobs{/sha}", 88 | "git_tags_url": "https://api.github.com/repos/owner/repo/git/tags{/sha}", 89 | "git_refs_url": "https://api.github.com/repos/owner/repo/git/refs{/sha}", 90 | "trees_url": "https://api.github.com/repos/owner/repo/git/trees{/sha}", 91 | "statuses_url": "https://api.github.com/repos/owner/repo/statuses/{sha}", 92 | "languages_url": "https://api.github.com/repos/owner/repo/languages", 93 | "stargazers_url": "https://api.github.com/repos/owner/repo/stargazers", 94 | "contributors_url": "https://api.github.com/repos/owner/repo/contributors", 95 | "subscribers_url": "https://api.github.com/repos/owner/repo/subscribers", 96 | "subscription_url": "https://api.github.com/repos/owner/repo/subscription", 97 | "commits_url": "https://api.github.com/repos/owner/repo/commits{/sha}", 98 | "git_commits_url": "https://api.github.com/repos/owner/repo/git/commits{/sha}", 99 | "comments_url": "https://api.github.com/repos/owner/repo/comments{/number}", 100 | "issue_comment_url": "https://api.github.com/repos/owner/repo/issues/comments{/number}", 101 | "contents_url": "https://api.github.com/repos/owner/repo/contents/{+path}", 102 | "compare_url": "https://api.github.com/repos/owner/repo/compare/{base}...{head}", 103 | "merges_url": "https://api.github.com/repos/owner/repo/merges", 104 | "archive_url": "https://api.github.com/repos/owner/repo/{archive_format}{/ref}", 105 | "downloads_url": "https://api.github.com/repos/owner/repo/downloads", 106 | "issues_url": "https://api.github.com/repos/owner/repo/issues{/number}", 107 | "pulls_url": "https://api.github.com/repos/owner/repo/pulls{/number}", 108 | "milestones_url": "https://api.github.com/repos/owner/repo/milestones{/number}", 109 | "notifications_url": "https://api.github.com/repos/owner/repo/notifications{?since,all,participating}", 110 | "labels_url": "https://api.github.com/repos/owner/repo/labels{/name}", 111 | "releases_url": "https://api.github.com/repos/owner/repo/releases{/id}", 112 | "created_at": 1421192829, 113 | "updated_at": "2015-04-25T16:55:49Z", 114 | "pushed_at": 1445895463, 115 | "git_url": "git://github.com/owner/repo.git", 116 | "ssh_url": "git@github.com:owner/repo.git", 117 | "clone_url": "https://github.com/owner/repo.git", 118 | "svn_url": "https://github.com/owner/repo", 119 | "homepage": "http://website.com/", 120 | "size": 10364, 121 | "stargazers_count": 0, 122 | "watchers_count": 0, 123 | "language": null, 124 | "has_issues": true, 125 | "has_downloads": true, 126 | "has_wiki": false, 127 | "has_pages": false, 128 | "forks_count": 0, 129 | "mirror_url": null, 130 | "open_issues_count": 0, 131 | "forks": 0, 132 | "open_issues": 0, 133 | "watchers": 0, 134 | "default_branch": "master", 135 | "stargazers": 0, 136 | "master_branch": "master" 137 | }, 138 | "pusher": { 139 | "name": "owner", 140 | "email": "username@github.com" 141 | }, 142 | "sender": { 143 | "login": "owner", 144 | "id": 4371429, 145 | "avatar_url": "https://avatars.githubusercontent.com/u/4371429?v=3", 146 | "gravatar_id": "", 147 | "url": "https://api.github.com/users/owner", 148 | "html_url": "https://github.com/owner", 149 | "followers_url": "https://api.github.com/users/owner/followers", 150 | "following_url": "https://api.github.com/users/owner/following{/other_user}", 151 | "gists_url": "https://api.github.com/users/owner/gists{/gist_id}", 152 | "starred_url": "https://api.github.com/users/owner/starred{/owner}{/repo}", 153 | "subscriptions_url": "https://api.github.com/users/owner/subscriptions", 154 | "organizations_url": "https://api.github.com/users/owner/orgs", 155 | "repos_url": "https://api.github.com/users/owner/repos", 156 | "events_url": "https://api.github.com/users/owner/events{/privacy}", 157 | "received_events_url": "https://api.github.com/users/owner/received_events", 158 | "type": "User", 159 | "site_admin": false 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /tests/data/payload-invalid-repo.json: -------------------------------------------------------------------------------- 1 | { 2 | "ref": "refs/heads/new-post-branch", 3 | "before": "bbe177c517891f5e33a59bf08fcb78e8ef766801", 4 | "after": "ad4e0a9e2597de40106d5f52e2041d8ebaeb0087", 5 | "created": false, 6 | "deleted": false, 7 | "forced": false, 8 | "base_ref": null, 9 | "compare": "https://github.com/owner/wrong-repo/compare/bbe177c51789...ad4e0a9e2597", 10 | "commits": [ 11 | { 12 | "id": "ad4e0a9e2597de40106d5f52e2041d8ebaeb0087", 13 | "distinct": true, 14 | "message": "Updated \"Recursive Closures in PHP\" - wpghs", 15 | "timestamp": "2015-10-26T17:37:42-04:00", 16 | "url": "https://github.com/owner/wrong-repo/commit/ad4e0a9e2597de40106d5f52e2041d8ebaeb0087", 17 | "author": { 18 | "name": "Author Name", 19 | "email": "username@github.com", 20 | "username": "owner" 21 | }, 22 | "committer": { 23 | "name": "Author Name", 24 | "email": "username@github.com", 25 | "username": "owner" 26 | }, 27 | "added": [ 28 | "_posts/2015-10-26-recursive-closures-in-php.md" 29 | ], 30 | "removed": [ 31 | "_drafts/2015-09-28-recursive-closures-in-php.md" 32 | ], 33 | "modified": [ 34 | 35 | ] 36 | } 37 | ], 38 | "head_commit": { 39 | "id": "ad4e0a9e2597de40106d5f52e2041d8ebaeb0087", 40 | "distinct": true, 41 | "message": "Updated \"Recursive Closures in PHP\" - wpghs", 42 | "timestamp": "2015-10-26T17:37:42-04:00", 43 | "url": "https://github.com/owner/wrong-repo/commit/ad4e0a9e2597de40106d5f52e2041d8ebaeb0087", 44 | "author": { 45 | "name": "Author Name", 46 | "email": "username@github.com", 47 | "username": "owner" 48 | }, 49 | "committer": { 50 | "name": "Author Name", 51 | "email": "username@github.com", 52 | "username": "owner" 53 | }, 54 | "added": [ 55 | "_posts/2015-10-26-recursive-closures-in-php.md" 56 | ], 57 | "removed": [ 58 | "_drafts/2015-09-28-recursive-closures-in-php.md" 59 | ], 60 | "modified": [ 61 | 62 | ] 63 | }, 64 | "repository": { 65 | "id": 29217659, 66 | "name": "repo", 67 | "full_name": "owner/wrong-repo", 68 | "owner": { 69 | "name": "owner", 70 | "email": "username@github.com" 71 | }, 72 | "private": false, 73 | "html_url": "https://github.com/owner/wrong-repo", 74 | "description": "This git repo maintains a collection of my repos and works-in-progress published both on my website at JamesDiGioia.com as well as elsewhere.", 75 | "fork": false, 76 | "url": "https://github.com/owner/wrong-repo", 77 | "forks_url": "https://api.github.com/repos/owner/wrong-repo/forks", 78 | "keys_url": "https://api.github.com/repos/owner/wrong-repo/keys{/key_id}", 79 | "collaborators_url": "https://api.github.com/repos/owner/wrong-repo/collaborators{/collaborator}", 80 | "teams_url": "https://api.github.com/repos/owner/wrong-repo/teams", 81 | "hooks_url": "https://api.github.com/repos/owner/wrong-repo/hooks", 82 | "issue_events_url": "https://api.github.com/repos/owner/wrong-repo/issues/events{/number}", 83 | "events_url": "https://api.github.com/repos/owner/wrong-repo/events", 84 | "assignees_url": "https://api.github.com/repos/owner/wrong-repo/assignees{/user}", 85 | "branches_url": "https://api.github.com/repos/owner/wrong-repo/branches{/branch}", 86 | "tags_url": "https://api.github.com/repos/owner/wrong-repo/tags", 87 | "blobs_url": "https://api.github.com/repos/owner/wrong-repo/git/blobs{/sha}", 88 | "git_tags_url": "https://api.github.com/repos/owner/wrong-repo/git/tags{/sha}", 89 | "git_refs_url": "https://api.github.com/repos/owner/wrong-repo/git/refs{/sha}", 90 | "trees_url": "https://api.github.com/repos/owner/wrong-repo/git/trees{/sha}", 91 | "statuses_url": "https://api.github.com/repos/owner/wrong-repo/statuses/{sha}", 92 | "languages_url": "https://api.github.com/repos/owner/wrong-repo/languages", 93 | "stargazers_url": "https://api.github.com/repos/owner/wrong-repo/stargazers", 94 | "contributors_url": "https://api.github.com/repos/owner/wrong-repo/contributors", 95 | "subscribers_url": "https://api.github.com/repos/owner/wrong-repo/subscribers", 96 | "subscription_url": "https://api.github.com/repos/owner/wrong-repo/subscription", 97 | "commits_url": "https://api.github.com/repos/owner/wrong-repo/commits{/sha}", 98 | "git_commits_url": "https://api.github.com/repos/owner/wrong-repo/git/commits{/sha}", 99 | "comments_url": "https://api.github.com/repos/owner/wrong-repo/comments{/number}", 100 | "issue_comment_url": "https://api.github.com/repos/owner/wrong-repo/issues/comments{/number}", 101 | "contents_url": "https://api.github.com/repos/owner/wrong-repo/contents/{+path}", 102 | "compare_url": "https://api.github.com/repos/owner/wrong-repo/compare/{base}...{head}", 103 | "merges_url": "https://api.github.com/repos/owner/wrong-repo/merges", 104 | "archive_url": "https://api.github.com/repos/owner/wrong-repo/{archive_format}{/ref}", 105 | "downloads_url": "https://api.github.com/repos/owner/wrong-repo/downloads", 106 | "issues_url": "https://api.github.com/repos/owner/wrong-repo/issues{/number}", 107 | "pulls_url": "https://api.github.com/repos/owner/wrong-repo/pulls{/number}", 108 | "milestones_url": "https://api.github.com/repos/owner/wrong-repo/milestones{/number}", 109 | "notifications_url": "https://api.github.com/repos/owner/wrong-repo/notifications{?since,all,participating}", 110 | "labels_url": "https://api.github.com/repos/owner/wrong-repo/labels{/name}", 111 | "releases_url": "https://api.github.com/repos/owner/wrong-repo/releases{/id}", 112 | "created_at": 1421192829, 113 | "updated_at": "2015-04-25T16:55:49Z", 114 | "pushed_at": 1445895463, 115 | "git_url": "git://github.com/owner/wrong-repo.git", 116 | "ssh_url": "git@github.com:owner/wrong-repo.git", 117 | "clone_url": "https://github.com/owner/wrong-repo.git", 118 | "svn_url": "https://github.com/owner/wrong-repo", 119 | "homepage": "http://website.com/", 120 | "size": 10364, 121 | "stargazers_count": 0, 122 | "watchers_count": 0, 123 | "language": null, 124 | "has_issues": true, 125 | "has_downloads": true, 126 | "has_wiki": false, 127 | "has_pages": false, 128 | "forks_count": 0, 129 | "mirror_url": null, 130 | "open_issues_count": 0, 131 | "forks": 0, 132 | "open_issues": 0, 133 | "watchers": 0, 134 | "default_branch": "master", 135 | "stargazers": 0, 136 | "master_branch": "master" 137 | }, 138 | "pusher": { 139 | "name": "owner", 140 | "email": "username@github.com" 141 | }, 142 | "sender": { 143 | "login": "owner", 144 | "id": 4371429, 145 | "avatar_url": "https://avatars.githubusercontent.com/u/4371429?v=3", 146 | "gravatar_id": "", 147 | "url": "https://api.github.com/users/owner", 148 | "html_url": "https://github.com/owner", 149 | "followers_url": "https://api.github.com/users/owner/followers", 150 | "following_url": "https://api.github.com/users/owner/following{/other_user}", 151 | "gists_url": "https://api.github.com/users/owner/gists{/gist_id}", 152 | "starred_url": "https://api.github.com/users/owner/starred{/owner}{/repo}", 153 | "subscriptions_url": "https://api.github.com/users/owner/subscriptions", 154 | "organizations_url": "https://api.github.com/users/owner/orgs", 155 | "repos_url": "https://api.github.com/users/owner/wrong-repos", 156 | "events_url": "https://api.github.com/users/owner/events{/privacy}", 157 | "received_events_url": "https://api.github.com/users/owner/received_events", 158 | "type": "User", 159 | "site_admin": false 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Changelog ## 2 | 3 | This change log follows the [Keep a Changelog standards][]. Versions follows [Semantic Versioning][]. 4 | 5 | ### [2.0.0][] ### 6 | 7 | * **POSSIBLY BREAKING**: Remove handling of custom post meta 8 | * If you were relying on WPGHS to export custom post meta, use `wpghs_post_meta` filter & `wpghs_pre_import_meta` to handle the meta yourself. See the [documentation][] for more information. 9 | * Switch from `sanitize_title` to `sanitize_file_name`. 10 | * This should ensure better fidelity to the original filename. 11 | * Don't export `post_date` if post isn't published. 12 | * `post_date` means the time the post was published. If it's not published, it shouldn't have a `post_date`. 13 | * Overwrite post_date from markdown, allowing remote user to modify publish date. 14 | 15 | ### [1.7.5][] ### 16 | 17 | * Fix plugin slug (props @gmays) 18 | 19 | ### [1.7.2][] ### 20 | 21 | * Fix messages (props @synchrophoto!) 22 | * Fix Markdown when importing w/ Jetpack (props @lite3!) 23 | * Fix bug in HTTP request on 4.6+ 24 | * Update dependencies 25 | 26 | ### [1.7.0][] ### 27 | 28 | * Add GitHub link shortcode (props @jonocarroll!) 29 | * Add boot hook (props @kennyfraser!) 30 | 31 | ### [1.6.1][] ### 32 | 33 | * Fixed bug where post_meta with the same name as built-in meta keys were getting overwritten 34 | 35 | ### [1.6.0][] ### 36 | 37 | * New filters: 38 | * `wpghs_pre_fetch_all_supported`: Filter the query args before all supported posts are queried. 39 | * `wpghs_is_post_supported`: Determines whether the post is supported by importing/exporting. 40 | * Bugfix: Set secret to password field. See [#124]. 41 | * Bugfix: Fix error when importing branch-deletion webhooks. 42 | * Bugfix: Fix "semaphore is locked" response from webhook. See [#121]. 43 | * Bugfix: Correctly display import/export messages in settings page. See [#127]. 44 | * Bugfix: Correctly set if post is new only when the matching ID is found in the database. 45 | 46 | ### [1.5.1][] ### 47 | 48 | * Added Chinese translation (props @malsony!). 49 | * Updated German translation (props @lsinger!). 50 | * Expire semaphore lock to avoid permanently locked install. 51 | 52 | ### [1.5.0][] ### 53 | 54 | * New WP-CLI command: 55 | * `prime`: Forces WPGHS to fetch the latest commit and save it in the cache. 56 | * New filters: 57 | * `wpghs_sync_branch`: Branch the WordPress install should sync itself with. 58 | * `wpghs_commit_msg_tag`: Tag appended to the end of the commit message. Split from message with ` - `. Used to determine if commit has been synced already. 59 | * These two new filters allow you to use WPGHS to keep multiple sites in sync. 60 | * This is an _advanced feature_. Your configuration may or may not be fully supported. **Use at your own risk.** 61 | * Eliminated some direct database calls in exchange for WP_Query usage. 62 | 63 | ### [1.4.1][] ### 64 | 65 | * Fix Database error handling 66 | * Fix bug where WPGHS would interfere with other plugins' AJAX hooks. 67 | * Fix transient key length to <40. 68 | 69 | ### [1.4.0][] ### 70 | 71 | * Major rewrite of the plugin internals. 72 | * *Massively* improved internal architecture. 73 | * Improved speed. 74 | * Upgraded caching implementation means updates happen faster. 75 | * Line-endings are now normalize to Unix-style. 76 | * New filter: `wpghs_line_endings` to set preferred line endings. 77 | * New filter: `wpghs_pre_import_args` 78 | * Called before post arguments are passed for an imported post. 79 | * New filter: `wpghs_pre_import_meta` 80 | * Called before post meta is imported from a post. 81 | * BREAKING: Remove reference to global `$wpghs` variable. 82 | * Use `WordPress_GitHub_Sync::$instance` instead. 83 | 84 | ### [1.3.4][] ### 85 | 86 | * Add German translation (props @lsinger). 87 | * Update folder names to default to untranslated. 88 | 89 | ### [1.3.3][] ### 90 | 91 | * Fix api bug where API call errors weren't getting kicked up to the calling method. 92 | 93 | ### [1.3.2][] ### 94 | 95 | * Fix deleting bug where posts that weren't present in the repo were being added. 96 | 97 | ### [1.3.1][] ### 98 | 99 | * Re-add validation of post before exporting. 100 | * Fixed bug where all post types/statuses were being exported. 101 | * Reverted busted SQL query 102 | 103 | ### [1.3][] ### 104 | 105 | * New Feature: Support importing posts from GitHub 106 | * New Feature: Support setting revision and new post users on import. 107 | * Note: There is a new setting, please selected a default/fallback user and saved the settings. 108 | 109 | ### [1.2][] ### 110 | 111 | * New Feature: Support displaying an "Edit|View on GitHub" link. 112 | * Update translation strings and implement pot file generation. 113 | * Redirect user away from settings page page after the import/export process starts. 114 | * Fix autoloader to be PHP 5.2 compatible. 115 | 116 | ### [1.1.1][] ### 117 | 118 | * Add WPGHS_Post as param to export content filter. 119 | 120 | ### [1.1.0][] ### 121 | 122 | * Add filters for content on import and export. 123 | 124 | ### [1.0.2][] ### 125 | 126 | * Hide password-protected posts from being exported to GitHub 127 | * Create post slug if WordPress hasn't created it yet (affects draft exporting) 128 | 129 | ### [1.0.1][] ### 130 | 131 | * Remove closure to enable PHP 5.2 compatibility (thanks @pdclark!) 132 | 133 | ### [1.0.0][] ### 134 | 135 | * Initial release 136 | * Supports full site sync, Markdown import/export, and custom post type & status support 137 | 138 | [Keep a Changelog standards]: http://keepachangelog.com/ 139 | [Semantic Versioning]: http://semver.org/ 140 | [#124]: https://github.com/mAAdhaTTah/wordpress-github-sync/issues/124 141 | [#121]: https://github.com/mAAdhaTTah/wordpress-github-sync/issues/121 142 | [#127]: https://github.com/mAAdhaTTah/wordpress-github-sync/issues/127 143 | [Unreleased]: https://github.com/mAAdhaTTah/wordpress-github-sync 144 | [2.0.0]: https://github.com/mAAdhaTTah/wordpress-github-sync/releases/tag/2.0.0 145 | [1.7.5]: https://github.com/mAAdhaTTah/wordpress-github-sync/releases/tag/1.7.5 146 | [1.7.2]: https://github.com/mAAdhaTTah/wordpress-github-sync/releases/tag/1.7.2 147 | [1.7.0]: https://github.com/mAAdhaTTah/wordpress-github-sync/releases/tag/1.7.0 148 | [1.6.1]: https://github.com/mAAdhaTTah/wordpress-github-sync/releases/tag/1.6.1 149 | [1.6.0]: https://github.com/mAAdhaTTah/wordpress-github-sync/releases/tag/1.6.0 150 | [1.5.1]: https://github.com/mAAdhaTTah/wordpress-github-sync/releases/tag/1.5.1 151 | [1.5.0]: https://github.com/mAAdhaTTah/wordpress-github-sync/releases/tag/1.5.0 152 | [1.4.1]: https://github.com/mAAdhaTTah/wordpress-github-sync/releases/tag/1.4.1 153 | [1.4.0]: https://github.com/mAAdhaTTah/wordpress-github-sync/releases/tag/1.4.0 154 | [1.3.4]: https://github.com/mAAdhaTTah/wordpress-github-sync/releases/tag/1.3.4 155 | [1.3.3]: https://github.com/mAAdhaTTah/wordpress-github-sync/releases/tag/1.3.3 156 | [1.3.2]: https://github.com/mAAdhaTTah/wordpress-github-sync/releases/tag/1.3.2 157 | [1.3.1]: https://github.com/mAAdhaTTah/wordpress-github-sync/releases/tag/1.3.1 158 | [1.3]: https://github.com/mAAdhaTTah/wordpress-github-sync/releases/tag/1.3 159 | [1.2]: https://github.com/mAAdhaTTah/wordpress-github-sync/releases/tag/1.2 160 | [1.1.1]: https://github.com/mAAdhaTTah/wordpress-github-sync/releases/tag/1.1.1 161 | [1.1.0]: https://github.com/mAAdhaTTah/wordpress-github-sync/releases/tag/1.1.0 162 | [1.0.2]: https://github.com/mAAdhaTTah/wordpress-github-sync/releases/tag/1.0.2 163 | [1.0.1]: https://github.com/mAAdhaTTah/wordpress-github-sync/releases/tag/1.0.1 164 | [1.0.0]: https://github.com/mAAdhaTTah/wordpress-github-sync/releases/tag/1.0.0 165 | [documentation]: https://github.com/mAAdhaTTah/wordpress-github-sync/wiki/Customizing-WordPress-GitHub-Sync-with-Filters 166 | -------------------------------------------------------------------------------- /languages/wp-github-sync.pot: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2017 WordPress GitHub Sync 2 | # This file is distributed under the same license as the WordPress GitHub Sync package. 3 | msgid "" 4 | msgstr "" 5 | "Project-Id-Version: WordPress GitHub Sync 2.0.0\n" 6 | "Report-Msgid-Bugs-To: http://wordpress.org/tag/wp-github-sync\n" 7 | "POT-Creation-Date: 2017-07-02 22:16:47+00:00\n" 8 | "MIME-Version: 1.0\n" 9 | "Content-Type: text/plain; charset=UTF-8\n" 10 | "Content-Transfer-Encoding: 8bit\n" 11 | "PO-Revision-Date: 2017-MO-DA HO:MI+ZONE\n" 12 | "Last-Translator: FULL NAME \n" 13 | "Language-Team: LANGUAGE \n" 14 | 15 | #: helpers.php:14 16 | msgid "View this post on GitHub." 17 | msgstr "" 18 | 19 | #: helpers.php:34 20 | msgid "Edit this post on GitHub." 21 | msgstr "" 22 | 23 | #: helpers.php:81 helpers.php:96 24 | msgid "View this post on GitHub" 25 | msgstr "" 26 | 27 | #: helpers.php:90 28 | msgid "Edit this post on GitHub" 29 | msgstr "" 30 | 31 | #: lib/admin.php:40 32 | msgid "GitHub hostname" 33 | msgstr "" 34 | 35 | #: lib/admin.php:43 36 | msgid "The GitHub host to use. This only needs to be changed to support a GitHub Enterprise installation." 37 | msgstr "" 38 | 39 | #: lib/admin.php:48 40 | msgid "Repository" 41 | msgstr "" 42 | 43 | #: lib/admin.php:51 44 | msgid "The GitHub repository to commit to, with owner ([OWNER]/[REPOSITORY]), e.g., github/hubot.github.com. The repository should contain an initial commit, which is satisfied by including a README when you create the repository on GitHub." 45 | msgstr "" 46 | 47 | #: lib/admin.php:56 48 | msgid "Oauth Token" 49 | msgstr "" 50 | 51 | #: lib/admin.php:59 52 | msgid "A personal oauth token with public_repo scope." 53 | msgstr "" 54 | 55 | #: lib/admin.php:64 56 | msgid "Webhook Secret" 57 | msgstr "" 58 | 59 | #: lib/admin.php:67 60 | msgid "The webhook's secret phrase. This should be password strength, as it is used to verify the webhook's payload." 61 | msgstr "" 62 | 63 | #: lib/admin.php:72 64 | msgid "Default Import User" 65 | msgstr "" 66 | 67 | #: lib/admin.php:75 68 | msgid "The fallback user for import, in case WordPress <--> GitHub Sync cannot find the committer in the database." 69 | msgstr "" 70 | 71 | #: lib/admin.php:108 72 | msgid "Export to GitHub started." 73 | msgstr "" 74 | 75 | #: lib/admin.php:115 76 | msgid "Export to GitHub failed with error:" 77 | msgstr "" 78 | 79 | #: lib/admin.php:122 lib/export.php:201 lib/export.php:235 80 | msgid "Export to GitHub completed successfully." 81 | msgstr "" 82 | 83 | #: lib/admin.php:129 84 | msgid "Import from GitHub started." 85 | msgstr "" 86 | 87 | #: lib/admin.php:136 88 | msgid "Import from GitHub failed with error:" 89 | msgstr "" 90 | 91 | #: lib/admin.php:143 92 | msgid "Import from GitHub completed successfully." 93 | msgstr "" 94 | 95 | #: lib/admin.php:154 views/options.php:9 96 | msgid "WordPress <--> GitHub Sync" 97 | msgstr "" 98 | 99 | #: lib/admin.php:155 100 | msgid "GitHub Sync" 101 | msgstr "" 102 | 103 | #: lib/cli.php:51 lib/cli.php:93 104 | msgid "Invalid user ID" 105 | msgstr "" 106 | 107 | #: lib/cli.php:57 108 | msgid "Starting full export to GitHub." 109 | msgstr "" 110 | 111 | #: lib/cli.php:62 112 | msgid "Exporting post ID to GitHub: %d" 113 | msgstr "" 114 | 115 | #: lib/cli.php:68 116 | msgid "Invalid post ID" 117 | msgstr "" 118 | 119 | #: lib/cli.php:98 120 | msgid "Starting import from GitHub." 121 | msgstr "" 122 | 123 | #: lib/cli.php:124 124 | msgid "Starting branch import." 125 | msgstr "" 126 | 127 | #: lib/cli.php:131 128 | msgid "Failed to import and cache branch with error: %s" 129 | msgstr "" 130 | 131 | #: lib/cli.php:138 132 | msgid "Successfully imported and cached commit %s from branch." 133 | msgstr "" 134 | 135 | #: lib/cli.php:150 136 | msgid "Successfully imported and cached commit %s." 137 | msgstr "" 138 | 139 | #: lib/client/base.php:67 140 | msgid "Method %s to endpoint %s failed with error: %s" 141 | msgstr "" 142 | 143 | #: lib/client/base.php:87 144 | msgid "WordPress-GitHub-Sync needs an auth token. Please update your settings." 145 | msgstr "" 146 | 147 | #: lib/client/base.php:96 148 | msgid "WordPress-GitHub-Sync needs a repository. Please update your settings." 149 | msgstr "" 150 | 151 | #: lib/client/base.php:105 152 | msgid "WordPress-GitHub-Sync needs a properly formed repository. Please update your settings." 153 | msgstr "" 154 | 155 | #: lib/client/base.php:144 lib/payload.php:84 156 | msgid "Sync branch not set. Filter `wpghs_sync_branch` misconfigured." 157 | msgstr "" 158 | 159 | #: lib/client/persist.php:23 160 | msgid "There were no changes, so no additional commit was added." 161 | msgstr "" 162 | 163 | #: lib/controller.php:40 lib/controller.php:98 lib/controller.php:130 164 | #: lib/controller.php:164 lib/controller.php:192 165 | msgid "%s : Semaphore is locked, import/export already in progress." 166 | msgstr "" 167 | 168 | #: lib/controller.php:47 169 | msgid "Failed to validate secret." 170 | msgstr "" 171 | 172 | #: lib/controller.php:57 173 | msgid "%s won't be imported. Error: %s" 174 | msgstr "" 175 | 176 | #: lib/controller.php:68 177 | msgid "%s won't be imported." 178 | msgstr "" 179 | 180 | #: lib/database.php:62 181 | msgid "Querying for supported posts returned no results." 182 | msgstr "" 183 | 184 | #: lib/database.php:88 185 | msgid "Post ID %s is not supported by WPGHS. See wiki to find out how to add support." 186 | msgstr "" 187 | 188 | #: lib/database.php:123 189 | msgid "Post for sha %s not found." 190 | msgstr "" 191 | 192 | #: lib/database.php:194 193 | msgid "Successfully saved posts." 194 | msgstr "" 195 | 196 | #: lib/database.php:256 197 | msgid "Post not found for path %s." 198 | msgstr "" 199 | 200 | #: lib/database.php:274 201 | msgid "Failed to delete post ID %d." 202 | msgstr "" 203 | 204 | #: lib/database.php:282 205 | msgid "Successfully deleted post ID %d." 206 | msgstr "" 207 | 208 | #: lib/database.php:373 209 | msgid "Commit user not found for email %s" 210 | msgstr "" 211 | 212 | #: lib/database.php:446 213 | msgid "No change for post ID %d." 214 | msgstr "" 215 | 216 | #: lib/database.php:454 217 | msgid "Successfully updated post ID %d." 218 | msgstr "" 219 | 220 | #: lib/export.php:258 lib/payload.php:95 221 | msgid "Commit message tag not set. Filter `wpghs_commit_msg_tag` misconfigured." 222 | msgstr "" 223 | 224 | #: lib/import.php:70 225 | msgid "Payload processed" 226 | msgstr "" 227 | 228 | #: lib/import.php:95 229 | msgid "Already synced this commit." 230 | msgstr "" 231 | 232 | #: lib/payload.php:46 233 | msgid "Maximum stack depth exceeded" 234 | msgstr "" 235 | 236 | #: lib/payload.php:49 237 | msgid "Underflow or the modes mismatch" 238 | msgstr "" 239 | 240 | #: lib/payload.php:52 241 | msgid "Unexpected control character found" 242 | msgstr "" 243 | 244 | #: lib/payload.php:55 245 | msgid "Syntax error, malformed JSON" 246 | msgstr "" 247 | 248 | #: lib/payload.php:58 249 | msgid "Malformed UTF-8 characters, possibly incorrectly encoded" 250 | msgstr "" 251 | 252 | #: lib/payload.php:61 253 | msgid "Unknown error" 254 | msgstr "" 255 | 256 | #: views/options.php:16 257 | msgid "Webhook callback" 258 | msgstr "" 259 | 260 | #: views/options.php:20 261 | msgid "Bulk actions" 262 | msgstr "" 263 | 264 | #: views/options.php:23 265 | msgid "Export to GitHub" 266 | msgstr "" 267 | 268 | #: views/options.php:26 269 | msgid "Import from GitHub" 270 | msgstr "" 271 | 272 | #: wp-github-sync.php:231 273 | msgid "To set up your site to sync with GitHub, update your settings and click \"Export to GitHub.\"" 274 | msgstr "" 275 | #. Plugin Name of the plugin/theme 276 | msgid "WordPress GitHub Sync" 277 | msgstr "" 278 | 279 | #. Plugin URI of the plugin/theme 280 | msgid "https://github.com/mAAdhaTTah/wordpress-github-sync" 281 | msgstr "" 282 | 283 | #. Description of the plugin/theme 284 | msgid "A WordPress plugin to sync content with a GitHub repository (or Jekyll site)." 285 | msgstr "" 286 | 287 | #. Author of the plugin/theme 288 | msgid "James DiGioia, Ben Balter" 289 | msgstr "" 290 | 291 | #. Author URI of the plugin/theme 292 | msgid "http://jamesdigioia.com" 293 | msgstr "" 294 | --------------------------------------------------------------------------------