├── 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 |
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 ""
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 |
114 |
121 |
128 |
135 |
142 | 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 |
--------------------------------------------------------------------------------