├── .editorconfig ├── .gitignore ├── .scrutinizer.yml ├── .svnignore ├── .travis.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── bin └── install-wp-tests.sh ├── composer.json ├── index.php ├── lib ├── admin.php ├── api.php ├── blob.php ├── cli.php ├── client │ ├── base.php │ ├── fetch.php │ └── persist.php ├── controller.php ├── database.php ├── export.php ├── fileinfo.php ├── function.php ├── import.php ├── payload.php ├── post.php ├── request.php ├── response.php └── semaphore.php ├── phpcs.ruleset.xml ├── phpunit.xml.dist ├── tests ├── data │ ├── delete_contents__posts_test.md_succeed.json │ ├── get_blobs_2d73165945b0ccbe4932f1363457986b0ed49f19_fail.json │ ├── get_blobs_2d73165945b0ccbe4932f1363457986b0ed49f19_succeed.json │ ├── get_blobs_8d9b2e6fd93761211dc03abd71f4a9189d680fd0_fail.json │ ├── get_blobs_8d9b2e6fd93761211dc03abd71f4a9189d680fd0_succeed.json │ ├── get_blobs_9fa5c7537f8582b71028ff34b8c20dfd0f3b2a25_fail.json │ ├── get_blobs_9fa5c7537f8582b71028ff34b8c20dfd0f3b2a25_succeed.json │ ├── get_commits_db2510854e6aeab68ead26b48328b19f4bdf926e_fail.json │ ├── get_commits_db2510854e6aeab68ead26b48328b19f4bdf926e_succeed.json │ ├── get_compare_861f87e8851b8debb78db548269d29f8da4d94ac...master_succeed.json │ ├── get_refs_heads_master_fail.json │ ├── get_refs_heads_master_succeed.json │ ├── get_trees_9108868e3800bec6763e51beb0d33e15036c3626_fail.json │ ├── get_trees_9108868e3800bec6763e51beb0d33e15036c3626_succeed.json │ ├── get_trees_master_fail.json │ ├── get_trees_master_succeed.json │ ├── patch_refs_heads_master_fail.json │ ├── patch_refs_heads_master_succeed.json │ ├── payload-invalid-branch.json │ ├── payload-invalid-repo.json │ ├── payload-synced-commit.json │ ├── payload-valid.json │ ├── post_commits_fail.json │ ├── post_commits_succeed.json │ ├── post_trees_fail.json │ └── post_trees_succeed.json ├── include │ ├── bootstrap.php │ └── testcase.php └── unit │ └── client │ ├── test-base.php │ ├── test-fetch.php │ └── test-persist.php ├── views ├── checkbox-setting-field.php ├── options.php ├── setting-field.php ├── textarea-setting-field.php └── user-setting-field.php └── writing-on-github.php /.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 | [*.php] 22 | trim_trailing_whitespace = true 23 | indent_style = space 24 | indent_size = 4 25 | 26 | [*.yml] 27 | indent_style = space 28 | indent_size = 2 29 | 30 | [*.json] 31 | indent_style = space 32 | indent_size = 2 33 | 34 | [*.txt,wp-config-sample.php] 35 | end_of_line = crlf 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | 3 | # Ignore IDE settings 4 | .idea 5 | build 6 | # phpunit.xml 7 | 8 | # Ignore temp files 9 | \#*\# 10 | *~ 11 | -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | 2 | 3 | tools: 4 | php_mess_detector: 5 | config: 6 | code_size_rules: { cyclomatic_complexity: true, npath_complexity: true, excessive_parameter_list: true, excessive_public_count: true, too_many_fields: true, too_many_methods: true } 7 | design_rules: { number_of_class_children: true, depth_of_inheritance: true, coupling_between_objects: true } 8 | unused_code_rules: { unused_local_variable: true, unused_private_method: true, unused_formal_parameter: true } 9 | # naming_rules: { short_variable: true, long_variable: true, short_method: true, boolean_method_name: true } 10 | php_cs_fixer: 11 | config: 12 | level: all 13 | fixers: { unused_use: true, phpdoc_params: true, braces: true, php_closing_tag: true } 14 | php_analyzer: 15 | config: 16 | suspicious_code: { enabled: true, overriding_parameter: true, overriding_closure_use: true, parameter_closure_use_conflict: true, parameter_multiple_times: true, non_existent_class_in_instanceof_check: true, non_existent_class_in_catch_clause: true, assignment_of_null_return: true, non_commented_switch_fallthrough: true, non_commented_empty_catch_block: true, overriding_private_members: true, use_statement_alias_conflict: true, precedence_in_condition_assignment: true } 17 | verify_php_doc_comments: { enabled: true, parameters: true, return: true, suggest_more_specific_types: true, ask_for_return_if_not_inferrable: true, ask_for_param_type_annotation: true } 18 | loops_must_use_braces: { enabled: true } 19 | simplify_boolean_return: { enabled: true } 20 | phpunit_checks: { enabled: true } 21 | reflection_fixes: { enabled: true } 22 | use_statement_fixes: { enabled: true, order_alphabetically: true, remove_unused: true, preserve_multiple: false, preserve_blanklines: false } 23 | parameter_reference_check: { enabled: false } 24 | checkstyle: { enabled: false, no_trailing_whitespace: true, naming: { enabled: true, local_variable: '^[a-z][a-zA-Z0-9]*$', abstract_class_name: ^Abstract|Factory$, utility_class_name: 'Utils?$', constant_name: '^[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)*$', property_name: '^[a-z][a-zA-Z0-9]*$', method_name: '^(?:[a-z]|__)[a-zA-Z0-9]*$', parameter_name: '^[a-z][a-zA-Z0-9]*$', interface_name: '^[A-Z][a-zA-Z0-9]*Interface$', type_name: '^[A-Z][a-zA-Z0-9]*$', exception_name: '^[A-Z][a-zA-Z0-9]*Exception$', isser_method_name: '^(?:is|has|should|may|supports)' } } 25 | unreachable_code: { enabled: false } 26 | check_access_control: { enabled: false } 27 | typo_checks: { enabled: false } 28 | check_variables: { enabled: false } 29 | check_calls: { enabled: true, too_many_arguments: true, missing_argument: true, argument_type_checks: lenient } 30 | dead_assignments: { enabled: false } 31 | check_usage_context: { enabled: true, foreach: { value_as_reference: true, traversable: true } } 32 | reflection_checks: { enabled: false } 33 | precedence_checks: { enabled: true, assignment_in_condition: true, comparison_of_bit_result: true } 34 | basic_semantic_checks: { enabled: false } 35 | unused_code: { enabled: false } 36 | deprecation_checks: { enabled: false } 37 | useless_function_calls: { enabled: false } 38 | metrics_lack_of_cohesion_methods: { enabled: false } 39 | metrics_coupling: { enabled: true, stable_code: { namespace_prefixes: { }, classes: { } } } 40 | doctrine_parameter_binding: { enabled: false } 41 | doctrine_entity_manager_injection: { enabled: false } 42 | symfony_request_injection: { enabled: false } 43 | doc_comment_fixes: { enabled: false } 44 | php_code_sniffer: 45 | config: 46 | standard: WordPress 47 | sensiolabs_security_checker: true 48 | php_loc: true 49 | php_pdepend: true 50 | php_sim: true 51 | php_changetracking: true 52 | external_code_coverage: 53 | timeout: 1200 54 | # runs: 1 55 | -------------------------------------------------------------------------------- /.svnignore: -------------------------------------------------------------------------------- 1 | bin 2 | tests 3 | phpunit.xml.dist 4 | .editorconfig 5 | .gitignore 6 | .scrutinizer.yml 7 | .svnignore 8 | .travis.yml 9 | *.json 10 | *.lock 11 | *.md 12 | *.xml 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | os: 3 | - linux 4 | 5 | php: 6 | - 7.0 7 | - 7.1 8 | - 7.2 9 | - 7.3 10 | - 7.4 11 | 12 | services: 13 | - mysql 14 | 15 | env: 16 | # deploy master to svn truck 17 | - WP_VERSION=latest WP_MULTISITE=0 DEPLOYMASTER=1 18 | - WP_VERSION=latest WP_MULTISITE=1 19 | - WP_VERSION=5.2 WP_MULTISITE=0 20 | # - WP_VERSION=5.2 WP_MULTISITE=1 21 | 22 | before_script: 23 | - phpenv config-rm xdebug.ini 24 | - bash bin/install-wp-tests.sh wordpress_test root '' localhost $WP_VERSION 25 | - composer self-update 26 | - composer install 27 | - phpenv versions 28 | - php --version 29 | 30 | script: 31 | - ./vendor/bin/phpunit --coverage-clover=coverage.clover 32 | - git clone https://github.com/litefeel/deploy2wp.git 33 | - sh deploy2wp/scripts/install.sh 34 | 35 | after_script: 36 | - wget https://scrutinizer-ci.com/ocular.phar 37 | - php ocular.phar code-coverage:upload --format=php-clover coverage.clover 38 | 39 | after_success: 40 | - | 41 | echo "TRAVIS_PHP_VERSION:$TRAVIS_PHP_VERSION" 42 | if [[ "$DEPLOYMASTER" == "1" && "$TRAVIS_PHP_VERSION" == "7.4" ]]; then 43 | deploy2wp/scripts/deploy2wp.sh 44 | fi 45 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Changelog ## 2 | 3 | ### [1.11][1.11] ### 4 | 5 | * Fixed correct the url of files in the images directory 6 | 7 | ### [1.10][1.10] ### 8 | 9 | * Fixed cannot publish post from github in _draffts 10 | * Fixed cannot delete post from github 11 | * Change github path to /_posts/$year/$postname 12 | 13 | ### [1.9][1.9] ### 14 | 15 | * Fixed webhook error on apache server 16 | 17 | ### [1.8][1.8] ### 18 | 19 | * Apply post_date from github 20 | * Friendly error message when validate webhook event 21 | * Friendly error message when can not import 22 | 23 | ### [1.7][1.7] ### 24 | 25 | * Add an option to force import from github 26 | 27 | ### [1.6.1][1.6.1] ### 28 | 29 | * Compatible with wordpress 4.8.1 30 | * Add index.php 31 | 32 | ### [1.6][1.6] ### 33 | 34 | * Add Settings link on plugins page 35 | * Add an option of "Don't export content" 36 | * Fixed cannot auto export to github when change frontmatter from github 37 | * Don't export to github when has not changed 38 | * Reduce memory when import from github 39 | * Refactor the code to enhance stability 40 | 41 | ### [1.5.1][1.5.1] ### 42 | 43 | * Compatible with PHP5.0 44 | 45 | ### [1.5][1.5] ### 46 | 47 | * Add an option to ignore the author 48 | * Add an option to force export to github 49 | * Change permalink to link in frontmatter 50 | * Change export github commit message 51 | 52 | ### [1.4][1.4] ### 53 | 54 | * Ignore custom post meta 55 | * copy image files from /images/* to wp-content/uploads/writing-on-github/images/* 56 | 57 | ### [1.3][1.3] ### 58 | 59 | * Fixed backslash loss 60 | 61 | ### [1.2][1.2] ### 62 | 63 | * Add post_name to post meta 64 | * Add ignore metas to setting 65 | 66 | ### [1.1][1.1] ### 67 | 68 | * Transfer ownership 69 | 70 | ### [1.0][1.0] ### 71 | 72 | * Initial version 73 | 74 | 75 | 76 | [1.0]: https://github.com/litefeel/writing-on-github/releases/tag/1.0 77 | [1.1]: https://github.com/litefeel/writing-on-github/releases/tag/1.1 78 | [1.2]: https://github.com/litefeel/writing-on-github/releases/tag/1.2 79 | [1.3]: https://github.com/litefeel/writing-on-github/releases/tag/1.3 80 | [1.4]: https://github.com/litefeel/writing-on-github/releases/tag/1.4 81 | [1.5]: https://github.com/litefeel/writing-on-github/releases/tag/1.5 82 | [1.5.1]: https://github.com/litefeel/writing-on-github/releases/tag/1.5.1 83 | [1.6]: https://github.com/litefeel/writing-on-github/releases/tag/1.6 84 | [1.6.1]: https://github.com/litefeel/writing-on-github/releases/tag/1.6.1 85 | [1.7]: https://github.com/litefeel/writing-on-github/releases/tag/1.7 86 | [1.8]: https://github.com/litefeel/writing-on-github/releases/tag/1.8 87 | [1.9]: https://github.com/litefeel/writing-on-github/releases/tag/1.9 88 | [1.10]: https://github.com/litefeel/writing-on-github/releases/tag/1.10 89 | [1.11]: https://github.com/litefeel/writing-on-github/releases/tag/1.11 90 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at litefeel@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /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/litefeel/writing-on-github/issues 39 | [submit a pull request]: #submit-a-pull-request 40 | [report any issues you find]: https://github.com/litefeel/writing-on-github/issues/new 41 | [what features you'd love to see]: https://github.com/litefeel/writing-on-github/issues/new 42 | [discussion forums]: https://github.com/litefeel/writing-on-github/issues 43 | [the documentation]: https://github.com/litefeel/writing-on-github/blob/master/README.md 44 | [to another language]: https://translate.wordpress.org/projects/wp-plugins/writing-on-github 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Writing On GitHub # 2 | 3 | [![Build Status](https://travis-ci.com/litefeel/writing-on-github.svg?branch=master)](https://travis-ci.com/litefeel/writing-on-github) 4 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/litefeel/writing-on-github/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/litefeel/writing-on-github/?branch=master) 5 | 6 | **Contributors:** litefeel 7 | **Tags:** github, git, version control, content, collaboration, publishing, writing 8 | **Donate link:** https://www.paypal.me/litefeel 9 | **Requires at least:** 3.9 10 | **Tested up to:** 5.4 11 | **Stable tag:** 1.11 12 | **License:** GPLv2 13 | **License URI:** http://www.gnu.org/licenses/gpl-2.0.html 14 | 15 | A WordPress plugin to allow you writing on GitHub (or Jekyll site). 16 | 17 | ## Description ## 18 | 19 | *A WordPress plugin to allow you writing on GitHub (or Jekyll site).* 20 | 21 | Some code for this plugin comes from [Wordpress GitHub Sync](https://github.com/mAAdhaTTah/wordpress-github-sync), thanks. 22 | 23 | Ever wish you could collaboratively author content for your WordPress site (or expose change history publicly and accept pull requests from your readers)? 24 | 25 | Well, now you can! Introducing [Writing On GitHub](https://github.com/litefeel/writing-on-github)! 26 | 27 | ### Writing On GitHub does three things: ### 28 | 29 | 1. Allows content publishers to version their content in GitHub 30 | 2. Allows readers to submit proposed improvements to WordPress-served content via GitHub's Pull Request model 31 | 32 | ### Writing On GitHub might be able to do some other cool things: ### 33 | 34 | * Allow teams to collaboratively write and edit posts using GitHub (e.g., pull requests, issues, comments) 35 | * Allow you to sync the content of two different WordPress installations via GitHub 36 | * Allow you to stage and preview content before "deploying" to your production server 37 | 38 | ### How it works ### 39 | 40 | The sync action is based on two hooks: 41 | 42 | 1. A per-post sync fired in response to WordPress's `save_post` hook which pushes content to GitHub 43 | 2. A sync of all changed files triggered by GitHub's `push` webhook (outbound API call) 44 | 45 | ## Installation ## 46 | 47 | ### Using the WordPress Dashboard ### 48 | 49 | 1. Navigate to the 'Add New' in the plugins dashboard 50 | 2. Search for 'Writing On GitHub' 51 | 3. Click 'Install Now' 52 | 4. Activate the plugin on the Plugin dashboard 53 | 54 | ### Uploading in WordPress Dashboard ### 55 | 56 | 1. Download `writing-on-github.zip` from the WordPress plugins repository. 57 | 2. Navigate to the 'Add New' in the plugins dashboard 58 | 3. Navigate to the 'Upload' area 59 | 4. Select `writing-on-github.zip` from your computer 60 | 5. Click 'Install Now' 61 | 6. Activate the plugin in the Plugin dashboard 62 | 63 | ### Using FTP ### 64 | 65 | 1. Download `writing-on-github.zip` 66 | 2. Extract the `writing-on-github` directory to your computer 67 | 3. Upload the `writing-on-github` directory to the `/wp-content/plugins/` directory 68 | 4. Activate the plugin in the Plugin dashboard 69 | 70 | 71 | ### Configuring the plugin ### 72 | 73 | 1. [Create a personal oauth token](https://github.com/settings/tokens/new) with the `public_repo` scope. If you'd prefer not to use your account, you can create another GitHub account for this. 74 | 2. Configure your GitHub host, repository, secret (defined in the next step), and OAuth Token on the Writing On GitHub settings page within WordPress's administrative interface. Make sure the repository has an initial commit or the export will fail. 75 | 3. Create a WebHook within your repository with the provided callback URL and callback secret, using `application/json` as the content type. To set up a webhook on GitHub, head over to the **Settings** page of your repository, and click on **Webhooks & services**. After that, click on **Add webhook**. 76 | 4. Click `Export to GitHub` 77 | 78 | ## Frequently Asked Questions ## 79 | 80 | ### Markdown Support ### 81 | 82 | Writing On GitHub exports all posts as `.md` files for better display on GitHub, but all content is exported and imported as its original HTML. To enable writing, importing, and exporting in Markdown, please install and enable [WP-Markdown](https://wordpress.org/plugins/wp-markdown/), and Writing On GitHub will use it to convert your posts to and from Markdown. 83 | 84 | You can also activate the Markdown module from [Jetpack](https://wordpress.org/plugins/jetpack/) or the standalone [JP Markdown](https://wordpress.org/plugins/jetpack-markdown/) to save in Markdown and export that version to GitHub. 85 | 86 | ### GitHub directory structure ### 87 | 88 | ~~~ 89 | . 90 | ├── _pages 91 | | └── 2007-10-29-some-pages.md 92 | ├── _posts 93 | | └── 2009-04-26-some-posts.md 94 | └── images 95 | └── some-images # copy all files (include subdirectory) to wordpress 96 | ~~~ 97 | 98 | ### Importing from GitHub ### 99 | 100 | Writing On GitHub is also capable of importing posts directly from GitHub, without creating them in WordPress before hand. In order to have your post imported into GitHub, add this YAML Frontmatter to the top of your .md document: 101 | 102 | --- 103 | post_title: 'Post Title' 104 | post_name: 'this is post name' 105 | post_date: '2018-03-07 15:21:26' 106 | layout: post_type_probably_post 107 | published: true_or_false 108 | author: author_name 109 | tags: 110 | - tag_a 111 | - tag_b 112 | categories: 113 | - category_a 114 | - category_b 115 | --- 116 | Post goes here. 117 | 118 | and fill it out with the data related to the post you're writing. Save the post and commit it directly to the repository. After the post is added to WordPress, an additional commit will be added to the repository, updating the new post with the new information from the database. 119 | 120 | Note that Writing On GitHub will import posts from the `master` branch by default. Once set, do not change it. 121 | 122 | If Writing On GitHub cannot find the author for a given import, it will fallback to the default user as set on the settings page. **Make sure you set this user before you begin importing posts from GitHub.** Without it set, Writing On GitHub will default to no user being set for the author as well as unknown-author revisions. 123 | 124 | 125 | ### Contributing ### 126 | 127 | Found a bug? Want to take a stab at [one of the open issues](https://github.com/litefeel/writing-on-github/issues)? We'd love your help! 128 | 129 | See [the contributing documentation](CONTRIBUTING.md) for details. 130 | 131 | ### Prior Art ### 132 | 133 | * [WordPress Post Forking](https://github.com/post-forking/post-forking) 134 | * [WordPress to Jekyll exporter](https://github.com/benbalter/wordpress-to-jekyll-exporter) 135 | * [Writing in public, syncing with GitHub](https://konklone.com/post/writing-in-public-syncing-with-github) 136 | * [Wordpress GitHub Sync](https://github.com/mAAdhaTTah/wordpress-github-sync) 137 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "litefeel/writing-on-github", 3 | "description": "A WordPress plugin to allow you writing on GitHub (or Jekyll site).", 4 | "type": "wordpress-plugin", 5 | "minimum-stability": "stable", 6 | "license": "GPL", 7 | "authors": [ 8 | { 9 | "name": "litefeel", 10 | "email": "litefeel@gmail.com" 11 | } 12 | ], 13 | "autoload": { 14 | "classmap": ["lib/"], 15 | "files": ["lib/function.php"] 16 | }, 17 | "autoload-dev": { 18 | "classmap": ["tests/"] 19 | }, 20 | "require": { 21 | "php": ">=5.3", 22 | "mustangostang/spyc": "^0.6.1" 23 | }, 24 | "require-dev": { 25 | "jdgrimes/wp-http-testcase": "1.3.1", 26 | "mockery/mockery": "0.9.3", 27 | "phpunit/phpunit": "^4.8", 28 | "wp-coding-standards/wpcs": "0.7.1", 29 | "squizlabs/php_codesniffer": "~2.3" 30 | }, 31 | "scripts": { 32 | "sniff": "phpcs --runtime-set installed_paths vendor/wp-coding-standards/wpcs -p ./ --standard=WordPress --report=full --extensions=php --ignore=*/tests/*,*/vendor/*", 33 | "clean": "phpcbf --runtime-set installed_paths vendor/wp-coding-standards/wpcs -p ./ --standard=WordPress --report=full --extensions=php --ignore=*/tests/*,*/vendor/*", 34 | "test": "phpunit" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | plugin_file = $plugin_file; 23 | 24 | add_action( 'admin_menu', array( $this, 'add_admin_menu' ) ); 25 | add_action( 'admin_init', array( $this, 'register_settings' ) ); 26 | add_action( 'current_screen', array( $this, 'trigger_cron' ) ); 27 | add_filter( 'plugin_action_links', array($this, 'settings_links'), 10, 2 ); 28 | } 29 | 30 | /** 31 | * settings link 32 | * @param string[] $links 33 | * @param string $file 34 | * @return string[] 35 | */ 36 | public function settings_links( $links, $file ) { 37 | if ( $file != $this->plugin_file ) { 38 | return $links; 39 | } 40 | 41 | $settings_link = '' . __( 'Settings', 'writing-on-github' ) . ''; 43 | 44 | array_push( $links, $settings_link ); 45 | 46 | return $links; 47 | } 48 | 49 | /** 50 | * Callback to render the settings page view 51 | */ 52 | public function settings_page() { 53 | include dirname( dirname( __FILE__ ) ) . '/views/options.php'; 54 | } 55 | 56 | /** 57 | * Callback to register the plugin's options 58 | */ 59 | public function register_settings() { 60 | add_settings_section( 61 | 'general', 62 | 'General Settings', 63 | array( $this, 'section_callback' ), 64 | Writing_On_GitHub::$text_domain 65 | ); 66 | 67 | register_setting( Writing_On_GitHub::$text_domain, 'wogh_host' ); 68 | add_settings_field( 'wogh_host', __( 'GitHub hostname', 'writing-on-github' ), array( $this, 'field_callback' ), Writing_On_GitHub::$text_domain, 'general', array( 69 | 'default' => 'https://api.github.com', 70 | 'name' => 'wogh_host', 71 | 'help_text' => __( 'The GitHub host to use. This only needs to be changed to support a GitHub Enterprise installation.', 'writing-on-github' ), 72 | ) 73 | ); 74 | 75 | register_setting( Writing_On_GitHub::$text_domain, 'wogh_repository' ); 76 | add_settings_field( 'wogh_repository', __( 'Repository', 'writing-on-github' ), array( $this, 'field_callback' ), Writing_On_GitHub::$text_domain, 'general', array( 77 | 'default' => '', 78 | 'name' => 'wogh_repository', 79 | '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.', 'writing-on-github' ), 80 | ) 81 | ); 82 | 83 | register_setting( Writing_On_GitHub::$text_domain, 'wogh_branch' ); 84 | add_settings_field( 'wogh_branch', __( 'Branch', 'writing-on-github' ), array( $this, 'field_callback' ), Writing_On_GitHub::$text_domain, 'general', array( 85 | 'default' => 'master', 86 | 'name' => 'wogh_branch', 87 | 'help_text' => __( 'The GitHub branch to commit to, default is master.', 'writing-on-github' ), 88 | ) 89 | ); 90 | 91 | register_setting( Writing_On_GitHub::$text_domain, 'wogh_oauth_token' ); 92 | add_settings_field( 'wogh_oauth_token', __( 'Oauth Token', 'writing-on-github' ), array( $this, 'field_callback' ), Writing_On_GitHub::$text_domain, 'general', array( 93 | 'default' => '', 94 | 'name' => 'wogh_oauth_token', 95 | 'help_text' => __( "A personal oauth token with public_repo scope.", 'writing-on-github' ), 96 | ) 97 | ); 98 | 99 | register_setting( Writing_On_GitHub::$text_domain, 'wogh_secret' ); 100 | add_settings_field( 'wogh_secret', __( 'Webhook Secret', 'writing-on-github' ), array( $this, 'field_callback' ), Writing_On_GitHub::$text_domain, 'general', array( 101 | 'default' => '', 102 | 'name' => 'wogh_secret', 103 | 'help_text' => __( "The webhook's secret phrase. This should be password strength, as it is used to verify the webhook's payload.", 'writing-on-github' ), 104 | ) 105 | ); 106 | 107 | register_setting( Writing_On_GitHub::$text_domain, 'wogh_default_user' ); 108 | add_settings_field( 'wogh_default_user', __( 'Default Import User', 'writing-on-github' ), array( &$this, 'user_field_callback' ), Writing_On_GitHub::$text_domain, 'general', array( 109 | 'default' => '', 110 | 'name' => 'wogh_default_user', 111 | 'help_text' => __( 'The fallback user for import, in case Writing On GitHub cannot find the committer in the database.', 'writing-on-github' ), 112 | ) 113 | ); 114 | 115 | register_setting( Writing_On_GitHub::$text_domain, 'wogh_ignore_author' ); 116 | add_settings_field( 'wogh_ignore_author', __( 'Ignore author', 'writing-on-github' ), array( &$this, 'checkbox_field_callback' ), Writing_On_GitHub::$text_domain, 'general', array( 117 | 'default' => '', 118 | 'name' => 'wogh_ignore_author', 119 | 'help_text' => __( 'Do not export author and do not use author info from GitHub.', 'writing-on-github' ), 120 | ) 121 | ); 122 | 123 | register_setting( Writing_On_GitHub::$text_domain, 'wogh_dont_export_content' ); 124 | add_settings_field( 'wogh_dont_export_content', __( 'Don\'t export content', 'writing-on-github' ), array( &$this, 'checkbox_field_callback' ), Writing_On_GitHub::$text_domain, 'general', array( 125 | 'default' => '', 126 | 'name' => 'wogh_dont_export_content', 127 | 'help_text' => __( 'Do not export post content to github, only export meta.', 'writing-on-github' ), 128 | ) 129 | ); 130 | 131 | // register_setting( Writing_On_GitHub::$text_domain, 'wogh_ignore_metas' ); 132 | // add_settings_field( 'wogh_ignore_metas', __( 'Ignore post metas', 'writing-on-github' ), array( &$this, 'textarea_field_callback' ), Writing_On_GitHub::$text_domain, 'general', array( 133 | // 'default' => '', 134 | // 'name' => 'wogh_ignore_metas', 135 | // 'help_text' => __( 'These meta keys will be ignored and cannot be imported and exported. One meta key per line.', 'writing-on-github' ), 136 | // ) 137 | // ); 138 | } 139 | 140 | /** 141 | * Callback to render an individual options field 142 | * 143 | * @param array $args Field arguments. 144 | */ 145 | public function field_callback( $args ) { 146 | include dirname( dirname( __FILE__ ) ) . '/views/setting-field.php'; 147 | } 148 | 149 | /** 150 | * Callback to render the default import user field. 151 | * 152 | * @param array $args Field arguments. 153 | */ 154 | public function user_field_callback( $args ) { 155 | include dirname( dirname( __FILE__ ) ) . '/views/user-setting-field.php'; 156 | } 157 | 158 | /** 159 | * Callback to render the textarea field. 160 | * 161 | * @param array $args Field arguments. 162 | */ 163 | public function textarea_field_callback( $args ) { 164 | include dirname( dirname( __FILE__ ) ) . '/views/textarea-setting-field.php'; 165 | } 166 | 167 | /** 168 | * Callback to render the checkbox field. 169 | * 170 | * @param array $args Field arguments. 171 | */ 172 | public function checkbox_field_callback( $args ) { 173 | include dirname( dirname( __FILE__ ) ) . '/views/checkbox-setting-field.php'; 174 | } 175 | 176 | /** 177 | * Displays settings messages from background processes 178 | */ 179 | public function section_callback() { 180 | if ( get_current_screen()->id !== 'settings_page_' . Writing_On_GitHub::$text_domain ) { 181 | return; 182 | } 183 | 184 | if ( 'yes' === get_option( '_wogh_export_started' ) ) { ?> 185 |
186 |

187 |
192 |
193 |

194 |
199 |
200 |

201 |
206 |
207 |

208 |
213 |
214 |

215 |
220 |
221 |

222 |
id !== 'settings_page_' . Writing_On_GitHub::$text_domain ) { 249 | return; 250 | } 251 | 252 | if ( ! isset( $_GET['action'] ) ) { 253 | return; 254 | } 255 | 256 | if ( 'export' === $_GET['action'] ) { 257 | Writing_On_GitHub::$instance->start_export(); 258 | } 259 | if ( 'force_export' === $_GET['action'] ) { 260 | Writing_On_GitHub::$instance->start_export(true); 261 | } 262 | if ( 'import' === $_GET['action'] ) { 263 | Writing_On_GitHub::$instance->start_import(); 264 | } 265 | if ( 'force_import' === $_GET['action'] ) { 266 | Writing_On_GitHub::$instance->start_import(true); 267 | } 268 | 269 | wp_redirect( admin_url( 'options-general.php?page=writing-on-github' ) ); 270 | die; 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /lib/api.php: -------------------------------------------------------------------------------- 1 | app = $app; 40 | } 41 | 42 | /** 43 | * Lazy-load fetch client. 44 | * 45 | * @return Writing_On_GitHub_Fetch_Client 46 | */ 47 | public function fetch() { 48 | if ( ! $this->fetch ) { 49 | $this->fetch = new Writing_On_GitHub_Fetch_Client( $this->app ); 50 | } 51 | 52 | return $this->fetch; 53 | } 54 | 55 | /** 56 | * Lazy-load persist client. 57 | * 58 | * @return Writing_On_GitHub_Persist_Client 59 | */ 60 | public function persist() { 61 | if ( ! $this->persist ) { 62 | $this->persist = new Writing_On_GitHub_Persist_Client( $this->app ); 63 | } 64 | 65 | return $this->persist; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /lib/blob.php: -------------------------------------------------------------------------------- 1 | interpret_data( $data ); 66 | } 67 | 68 | public function id() { 69 | return $this->id; 70 | } 71 | 72 | public function set_id($id) { 73 | $this->id = $id; 74 | } 75 | 76 | /** 77 | * Returns the raw blob content. 78 | * 79 | * @return string 80 | */ 81 | public function content() { 82 | return $this->content; 83 | } 84 | 85 | /** 86 | * Set's the blob's content. 87 | * 88 | * @param string $content Raw blob content. 89 | * @param bool $base64 Whether the content is base64 encoded. 90 | * 91 | * @return $this 92 | */ 93 | public function set_content( $content, $base64 = false ) { 94 | if ( $base64 ) { 95 | $content = base64_decode( $content ); 96 | } 97 | 98 | // remove whitespace from the beginning of content, 99 | // To prevent blank lines before yml 100 | $this->content = ltrim( $content ); 101 | 102 | $this->frontmatter = '---' === substr( $this->content, 0, 3 ); 103 | 104 | return $this; 105 | } 106 | /** 107 | * Returns the blob sha. 108 | * 109 | * @return string 110 | */ 111 | public function sha() { 112 | return $this->sha; 113 | } 114 | 115 | /** 116 | * Return's the blob path. 117 | * 118 | * @return string 119 | */ 120 | public function path() { 121 | return $this->path; 122 | } 123 | 124 | /** 125 | * Whether the blob has frontmatter. 126 | * 127 | * @return bool 128 | */ 129 | public function has_frontmatter() { 130 | return $this->frontmatter; 131 | } 132 | 133 | /** 134 | * The front matter of github post 135 | * @return string 136 | */ 137 | public function front_matter() { 138 | return $this->front_matter; 139 | } 140 | 141 | /** 142 | * Content without front matter 143 | * @return string 144 | */ 145 | public function post_content() { 146 | if ( ! $this->post_content ) { 147 | $this->content_import(); 148 | } 149 | return $this->post_content; 150 | } 151 | 152 | /** 153 | * Returns the formatted/filtered blob content used for import. 154 | * 155 | * @return string 156 | */ 157 | public function content_import() { 158 | $this->post_content = $content = $this->content(); 159 | 160 | if ( $this->has_frontmatter() ) { 161 | // Break out content. 162 | preg_match( '/(^---(.*?)---$(\r\n|\n|\r)?)?(.*)/ms', $content, $matches ); 163 | $this->front_matter = $matches[1]; 164 | $this->post_content = $content = array_pop( $matches ); 165 | } 166 | 167 | if ( function_exists( 'wpmarkdown_markdown_to_html' ) ) { 168 | $content = wpmarkdown_markdown_to_html( $content ); 169 | } 170 | 171 | /** 172 | * Filters the content for import. 173 | */ 174 | return apply_filters( 'wogh_content_import', trim( $content ) ); 175 | } 176 | 177 | /** 178 | * Returns the blob meta. 179 | * 180 | * @return array 181 | */ 182 | public function meta() { 183 | $meta = array(); 184 | 185 | if ( $this->has_frontmatter() ) { 186 | // Break out meta, if present. 187 | preg_match( '/(^---(.*?)---$)?(.*)/ms', $this->content(), $matches ); 188 | array_pop( $matches ); 189 | 190 | $meta = spyc_load( $matches[2] ); 191 | if ( 'yes' == get_option('wogh_ignore_author') ) { 192 | unset($meta['author']); 193 | } 194 | // if ( isset( $meta['link'] ) ) { 195 | // $meta['link'] = str_replace( home_url(), '', $meta['link'] ); 196 | // } 197 | } 198 | 199 | return $meta; 200 | } 201 | 202 | /** 203 | * Formats the blob into an API call body. 204 | * 205 | * @return stdClass 206 | */ 207 | // public function to_body() { 208 | // $data = new stdClass; 209 | 210 | // $data->mode = '100644'; 211 | // $data->type = 'blob'; 212 | 213 | // $data->path = $this->path(); 214 | 215 | // if ( $this->sha() ) { 216 | // $data->sha = $this->sha(); 217 | // } else { 218 | // $data->content = $this->content(); 219 | // } 220 | 221 | // return $data; 222 | // } 223 | 224 | 225 | /** 226 | * Formats the blob into an API call body. 227 | * 228 | * @return stdClass 229 | */ 230 | public function to_body() { 231 | $data = new stdClass; 232 | 233 | // $data->mode = '100644'; 234 | // $data->type = 'blob'; 235 | 236 | $data->path = $this->path(); 237 | $data->content = base64_encode( $this->content() ); 238 | $data->sha = $this->sha; 239 | 240 | return $data; 241 | } 242 | 243 | /** 244 | * Interprets the blob's data into properties. 245 | */ 246 | protected function interpret_data( $data ) { 247 | $this->sha = isset( $data->sha ) ? $data->sha : ''; 248 | $this->path = isset( $data->path ) ? $data->path : ''; 249 | 250 | $this->set_content( 251 | isset( $data->content ) ? $data->content : '', 252 | isset( $data->encoding ) && 'base64' === $data->encoding ? true : false 253 | ); 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /lib/cli.php: -------------------------------------------------------------------------------- 1 | app = Writing_On_GitHub::$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 wogh export all 1 41 | * wp wogh 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', 'writing-on-github' ) ); 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.', 'writing-on-github' ) ); 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', 'writing-on-github' ), 63 | $post_id 64 | ) 65 | ); 66 | $this->app->controller()->export_post( (int) $post_id ); 67 | } else { 68 | WP_CLI::error( __( 'Invalid post ID', 'writing-on-github' ) ); 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 wogh 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', 'writing-on-github' ) ); 94 | } 95 | 96 | update_option( '_wogh_export_user_id', (int) $user_id ); 97 | 98 | WP_CLI::line( __( 'Starting import from GitHub.', 'writing-on-github' ) ); 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 wogh prime --branch=master 115 | * wp wogh 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.', 'writing-on-github' ) ); 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', 'writing-on-github' ), 132 | $commit->get_error_message() 133 | ) 134 | ); 135 | } else { 136 | WP_CLI::success( 137 | sprintf( 138 | __( 'Successfully imported and cached commit %s from branch.', 'writing-on-github' ), 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.', 'writing-on-github' ), 151 | $commit->sha() 152 | ) 153 | ); 154 | } else { 155 | WP_CLI::error( 'Invalid fetch.' ); 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /lib/client/base.php: -------------------------------------------------------------------------------- 1 | app = $app; 31 | } 32 | 33 | /** 34 | * Generic GitHub API interface and response handler 35 | * 36 | * @param string $method HTTP method. 37 | * @param string $endpoint API endpoint. 38 | * @param array $body Request body. 39 | * 40 | * @return stdClass|WP_Error 41 | */ 42 | protected function call( $method, $endpoint, $body = array() ) { 43 | if ( is_wp_error( $error = $this->can_call() ) ) { 44 | /* @var WP_Error $error */ 45 | return $error; 46 | } 47 | 48 | $args = array( 49 | 'method' => $method, 50 | 'headers' => array( 51 | 'Authorization' => 'token ' . $this->oauth_token(), 52 | ), 53 | ); 54 | 55 | if ( 'GET' !== $method ) { 56 | $args['body'] = json_encode( $body ); 57 | } 58 | 59 | // $tmpbody = isset( $args['body'] ) ? $args['body'] : ''; 60 | // error_log( "writing-on-github-call $method $endpoint $tmpbody" ); 61 | 62 | $response = wp_remote_request( $endpoint, $args ); 63 | $status = wp_remote_retrieve_header( $response, 'status' ); 64 | $body = json_decode( wp_remote_retrieve_body( $response ) ); 65 | 66 | if ( '2' !== substr( $status, 0, 1 ) && '3' !== substr( $status, 0, 1 ) ) { 67 | return new WP_Error( 68 | strtolower( str_replace( ' ', '_', $status ) ), 69 | sprintf( 70 | __( 'Method %s to endpoint %s failed with error: %s', 'writing-on-github' ), 71 | $method, 72 | $endpoint, 73 | ( $body && $body->message ) ? $body->message : 'Unknown error' 74 | ) 75 | ); 76 | } 77 | 78 | return $body; 79 | } 80 | 81 | /** 82 | * Validates whether the Api object can make a call. 83 | * 84 | * @return true|WP_Error 85 | */ 86 | protected function can_call() { 87 | if ( ! $this->oauth_token() ) { 88 | return new WP_Error( 89 | 'missing_token', 90 | __( 'Writing On GitHub needs an auth token. Please update your settings.', 'writing-on-github' ) 91 | ); 92 | } 93 | 94 | $repo = $this->repository(); 95 | 96 | if ( ! $repo ) { 97 | return new WP_Error( 98 | 'missing_repository', 99 | __( 'Writing On GitHub needs a repository. Please update your settings.', 'writing-on-github' ) 100 | ); 101 | } 102 | 103 | $parts = explode( '/', $repo ); 104 | 105 | if ( 2 !== count( $parts ) ) { 106 | return new WP_Error( 107 | 'malformed_repository', 108 | __( 'Writing On GitHub needs a properly formed repository. Please update your settings.', 'writing-on-github' ) 109 | ); 110 | } 111 | 112 | return true; 113 | } 114 | 115 | /** 116 | * Returns the repository to sync with 117 | * 118 | * @return string 119 | */ 120 | public function repository() { 121 | return (string) get_option( self::REPO_OPTION_KEY ); 122 | } 123 | 124 | /** 125 | * Returns the user's oauth token 126 | * 127 | * @return string 128 | */ 129 | public function oauth_token() { 130 | return (string) get_option( self::TOKEN_OPTION_KEY ); 131 | } 132 | 133 | /** 134 | * Returns the GitHub host to sync with (for GitHub Enterprise support) 135 | */ 136 | public function api_base() { 137 | return get_option( self::HOST_OPTION_KEY ); 138 | } 139 | 140 | public function branch() { 141 | $branch = get_option( self::BRANCH_OPTION_KEY ); 142 | return $branch ? $branch : 'master'; 143 | } 144 | 145 | /** 146 | * API endpoint for the master branch reference 147 | */ 148 | public function reference_endpoint() { 149 | $url = $this->api_base() . '/repos/'; 150 | $url = $url . $this->repository() . '/git/refs/heads/' . $this->branch(); 151 | 152 | return $url; 153 | } 154 | 155 | /** 156 | * Api to get and create commits 157 | */ 158 | public function commit_endpoint() { 159 | $url = $this->api_base() . '/repos/'; 160 | $url = $url . $this->repository() . '/git/commits'; 161 | 162 | return $url; 163 | } 164 | 165 | /** 166 | * Api to compare commits 167 | */ 168 | public function compare_endpoint() { 169 | $url = $this->api_base() . '/repos/'; 170 | $url = $url . $this->repository() . '/compare'; 171 | 172 | return $url; 173 | } 174 | 175 | /** 176 | * Api to get and create trees 177 | */ 178 | public function tree_endpoint() { 179 | $url = $this->api_base() . '/repos/'; 180 | $url = $url . $this->repository() . '/git/trees'; 181 | 182 | return $url; 183 | } 184 | 185 | /** 186 | * Builds the proper blob API endpoint for a given post 187 | * 188 | * Returns String the relative API call path 189 | */ 190 | public function blob_endpoint() { 191 | $url = $this->api_base() . '/repos/'; 192 | $url = $url . $this->repository() . '/git/blobs'; 193 | 194 | return $url; 195 | } 196 | 197 | /** 198 | * Builds the proper content API endpoint for a given post 199 | * 200 | * Returns String the relative API call path 201 | */ 202 | public function content_endpoint( $path = false ) { 203 | $url = $this->api_base() . '/repos/'; 204 | $url = $url . $this->repository() . '/contents'; 205 | 206 | if ( ! empty($path) ) { 207 | $url .= '/' . $path; 208 | } 209 | 210 | return $url; 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /lib/client/fetch.php: -------------------------------------------------------------------------------- 1 | compare_endpoint(); 22 | $branch = $this->branch(); 23 | $data = $this->call( 'GET', "$endpoint/$sha...$branch" ); 24 | 25 | if ( is_wp_error( $data ) ) { 26 | return $data; 27 | } 28 | 29 | $files = array(); 30 | foreach ($data->files as $file) { 31 | $file->path = $file->filename; 32 | $files[] = new Writing_On_GitHub_File_Info($file); 33 | } 34 | 35 | return $files; 36 | } 37 | 38 | /** 39 | * Calls the content API to get the post's contents and metadata 40 | * 41 | * Returns Object the response from the API 42 | * 43 | * @param Writing_On_GitHub_Post $post Post to retrieve remote contents for. 44 | * 45 | * @return mixed 46 | */ 47 | public function remote_contents( $post ) { 48 | return $this->call( 'GET', $this->content_endpoint( $post->github_path() ) ); 49 | } 50 | 51 | 52 | 53 | public function exists( $path ) { 54 | $result = $this->call( 'GET', $this->content_endpoint( $path ) ); 55 | if ( is_wp_error( $result ) ) { 56 | return false; 57 | } 58 | return true; 59 | } 60 | 61 | /** 62 | * Retrieves a tree by sha recursively from the GitHub API 63 | * 64 | * @param string $sha Commit sha to retrieve tree from. 65 | * 66 | * @return Writing_On_GitHub_File_Info[]|WP_Error 67 | */ 68 | public function tree_recursive( $sha = '_default' ) { 69 | 70 | if ( '_default' === $sha ) { 71 | $sha = $this->branch(); 72 | } 73 | 74 | $data = $this->call( 'GET', $this->tree_endpoint() . '/' . $sha . '?recursive=1' ); 75 | 76 | if ( is_wp_error( $data ) ) { 77 | return $data; 78 | } 79 | 80 | $files = array(); 81 | 82 | foreach ( $data->tree as $index => $thing ) { 83 | // We need to remove the trees because 84 | // the recursive tree includes both 85 | // the subtrees as well the subtrees' blobs. 86 | if ( 'blob' === $thing->type ) { 87 | $thing->status = ''; 88 | $files[] = new Writing_On_GitHub_File_Info( $thing ); 89 | } 90 | } 91 | 92 | return $files; 93 | } 94 | 95 | /** 96 | * Retrieves the blob data for a given sha 97 | * 98 | * @param Writing_On_GitHub_File_Info $fileinfo 99 | * 100 | * @return Writing_On_GitHub_Blob|WP_Error 101 | */ 102 | public function blob( Writing_On_GitHub_File_Info $fileinfo ) { 103 | $data = $this->call( 'GET', $this->blob_endpoint() . '/' . $fileinfo->sha ); 104 | 105 | if ( is_wp_error( $data ) ) { 106 | return $data; 107 | } 108 | 109 | $data->path = $fileinfo->path; 110 | return new Writing_On_GitHub_Blob( $data ); 111 | } 112 | 113 | /** 114 | * Get blob by path 115 | * @param string $path 116 | * @return Writing_On_GitHub_Blob|WP_Error 117 | */ 118 | public function blob_by_path( $path ) { 119 | $result = $this->call( 'GET', $this->content_endpoint( $path ) ); 120 | if ( is_wp_error( $result ) ) { 121 | return $result; 122 | } 123 | 124 | return new Writing_On_GitHub_Blob( $result ); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /lib/client/persist.php: -------------------------------------------------------------------------------- 1 | $user->display_name, 24 | 'email' => $user->user_email, 25 | ); 26 | } 27 | 28 | return false; 29 | } 30 | 31 | /** 32 | * Delete the file. 33 | * 34 | * @return array 35 | */ 36 | public function delete_file( $path, $sha, $message ) { 37 | $body = new stdClass(); 38 | $body->message = $message; 39 | $body->sha = $sha; 40 | $body->branch = $this->branch(); 41 | 42 | if ( $author = $this->export_user() ) { 43 | $body->author = $author; 44 | } 45 | 46 | return $this->call( 'DELETE', $this->content_endpoint( $path ), $body ); 47 | } 48 | 49 | /** 50 | * Create the file. 51 | * 52 | * @return array 53 | */ 54 | public function create_file( $blob, $message ) { 55 | $body = $blob->to_body(); 56 | $body->message = $message; 57 | $body->branch = $this->branch(); 58 | unset($body->sha); 59 | 60 | if ( $author = $this->export_user() ) { 61 | $body->author = $author; 62 | } 63 | 64 | return $this->call( 'PUT', $this->content_endpoint( $blob->path() ), $body ); 65 | } 66 | 67 | /** 68 | * Update the file. 69 | * 70 | * @return array 71 | */ 72 | public function update_file( $blob, $message ) { 73 | $body = $blob->to_body(); 74 | $body->message = $message; 75 | $body->branch = $this->branch(); 76 | 77 | if ( $author = $this->export_user() ) { 78 | $body->author = $author; 79 | } 80 | 81 | return $this->call( 'PUT', $this->content_endpoint( $blob->path() ), $body ); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /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.', 'writing-on-github' ), '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.', 'writing-on-github' ) 48 | ) ); 49 | } 50 | 51 | // ping 52 | if ( $this->app->request()->is_ping() ) { 53 | return $this->app->response()->success( __( 'Wordpress is ready.', 'writing-on-github' ) ); 54 | } 55 | 56 | // push 57 | if ( ! $this->app->request()->is_push() ) { 58 | return $this->app->response()->error( new WP_Error( 59 | 'invalid_headers', 60 | sprintf( 'Failed to validate webhook event: %s.', 61 | $this->app->request()->webhook_event() ) 62 | ) ); 63 | } 64 | $payload = $this->app->request()->payload(); 65 | 66 | $error = $payload->should_import(); 67 | if ( is_wp_error( $error ) ) { 68 | /* @var WP_Error $error */ 69 | return $this->app->response()->error( $error ); 70 | } 71 | 72 | $this->app->semaphore()->lock(); 73 | remove_action( 'save_post', array( $this, 'export_post' ) ); 74 | remove_action( 'delete_post', array( $this, 'delete_post' ) ); 75 | 76 | $result = $this->app->import()->payload( $payload ); 77 | 78 | $this->app->semaphore()->unlock(); 79 | 80 | if ( is_wp_error( $result ) ) { 81 | /* @var WP_Error $result */ 82 | return $this->app->response()->error( $result ); 83 | } 84 | 85 | return $this->app->response()->success( $result ); 86 | } 87 | 88 | /** 89 | * Imports posts from the current master branch. 90 | * @param integer $user_id 91 | * @param boolean $force 92 | * @return boolean 93 | */ 94 | public function import_master( $user_id = 0, $force = false ) { 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.', 'writing-on-github' ), '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 | if ( $user_id ) { 107 | wp_set_current_user( $user_id ); 108 | } 109 | 110 | $result = $this->app->import()->master( $force ); 111 | 112 | $this->app->semaphore()->unlock(); 113 | 114 | if ( is_wp_error( $result ) ) { 115 | /* @var WP_Error $result */ 116 | update_option( '_wogh_import_error', $result->get_error_message() ); 117 | 118 | return $this->app->response()->error( $result ); 119 | } 120 | 121 | update_option( '_wogh_import_complete', 'yes' ); 122 | 123 | return $this->app->response()->success( $result ); 124 | } 125 | 126 | /** 127 | * Export all the posts in the database to GitHub. 128 | * 129 | * @param int $user_id 130 | * @param boolean $force 131 | * @return boolean 132 | */ 133 | public function export_all( $user_id = 0, $force = false ) { 134 | if ( ! $this->app->semaphore()->is_open() ) { 135 | return $this->app->response()->error( new WP_Error( 136 | 'semaphore_locked', 137 | sprintf( __( '%s : Semaphore is locked, import/export already in progress.', 'writing-on-github' ), 'Controller::export_all()' ) 138 | ) ); 139 | } 140 | 141 | $this->app->semaphore()->lock(); 142 | 143 | if ( $user_id ) { 144 | wp_set_current_user( $user_id ); 145 | } 146 | 147 | $result = $this->app->export()->full($force); 148 | $this->app->semaphore()->unlock(); 149 | 150 | // Maybe move option updating out of this class/upgrade message display? 151 | if ( is_wp_error( $result ) ) { 152 | /* @var WP_Error $result */ 153 | update_option( '_wogh_export_error', $result->get_error_message() ); 154 | 155 | return $this->app->response()->error( $result ); 156 | } else { 157 | update_option( '_wogh_export_complete', 'yes' ); 158 | update_option( '_wogh_fully_exported', 'yes' ); 159 | 160 | return $this->app->response()->success( $result ); 161 | } 162 | } 163 | 164 | /** 165 | * Exports a single post to GitHub by ID. 166 | * 167 | * Called on the save_post hook. 168 | * 169 | * @param int $post_id Post ID. 170 | * 171 | * @return boolean 172 | */ 173 | public function export_post( $post_id ) { 174 | if ( wp_is_post_revision( $post_id ) ) { 175 | return; 176 | } 177 | 178 | if ( ! $this->app->semaphore()->is_open() ) { 179 | return $this->app->response()->error( new WP_Error( 180 | 'semaphore_locked', 181 | sprintf( __( '%s : Semaphore is locked, import/export already in progress.', 'writing-on-github' ), 'Controller::export_post()' ) 182 | ) ); 183 | } 184 | 185 | $this->app->semaphore()->lock(); 186 | $result = $this->app->export()->update( $post_id ); 187 | $this->app->semaphore()->unlock(); 188 | 189 | if ( is_wp_error( $result ) ) { 190 | /* @var WP_Error $result */ 191 | return $this->app->response()->error( $result ); 192 | } 193 | 194 | return $this->app->response()->success( $result ); 195 | } 196 | 197 | /** 198 | * Removes the post from the tree. 199 | * 200 | * Called the delete_post hook. 201 | * 202 | * @param int $post_id Post ID. 203 | * 204 | * @return boolean 205 | */ 206 | public function delete_post( $post_id ) { 207 | if ( wp_is_post_revision( $post_id ) ) { 208 | return; 209 | } 210 | 211 | if ( ! $this->app->semaphore()->is_open() ) { 212 | return $this->app->response()->error( new WP_Error( 213 | 'semaphore_locked', 214 | sprintf( __( '%s : Semaphore is locked, import/export already in progress.', 'writing-on-github' ), 'Controller::delete_post()' ) 215 | ) ); 216 | } 217 | 218 | $this->app->semaphore()->lock(); 219 | $result = $this->app->export()->delete( $post_id ); 220 | $this->app->semaphore()->unlock(); 221 | 222 | if ( is_wp_error( $result ) ) { 223 | /* @var WP_Error $result */ 224 | return $this->app->response()->error( $result ); 225 | } 226 | 227 | return $this->app->response()->success( $result ); 228 | } 229 | 230 | /** 231 | * Indicates we're running our own AJAX hook 232 | * and thus should respond with JSON, rather 233 | * than just returning data. 234 | */ 235 | protected function set_ajax() { 236 | if ( ! defined( 'WOGH_AJAX' ) ) { 237 | define( 'WOGH_AJAX', true ); 238 | } 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /lib/export.php: -------------------------------------------------------------------------------- 1 | app = $app; 27 | } 28 | 29 | /** 30 | * Updates all of the current posts in the database on master. 31 | * 32 | * @param bool $force 33 | * 34 | * @return string|WP_Error 35 | */ 36 | public function full( $force = false ) { 37 | $posts = $this->app->database()->fetch_all_supported( $force ); 38 | 39 | if ( is_wp_error( $posts ) ) { 40 | /* @var WP_Error $posts */ 41 | return $posts; 42 | } 43 | 44 | $error = ''; 45 | 46 | foreach ( $posts as $post ) { 47 | $result = $this->update( $post->id() ); 48 | if ( is_wp_error( $result ) ) { 49 | /* @var WP_Error $result */ 50 | $error = wogh_append_error( $error, $result ); 51 | } 52 | } 53 | 54 | if ( is_wp_error( $error ) ) { 55 | /* @var WP_Error $error */ 56 | return $error; 57 | } 58 | 59 | return __( 'Export to GitHub completed successfully.', 'writing-on-github' ); 60 | } 61 | 62 | 63 | /** 64 | * Check if it exists in github 65 | * @param int $post_id 66 | * @return boolean 67 | */ 68 | protected function github_path( $post_id ) { 69 | $github_path = get_post_meta( $post_id, '_wogh_github_path', true ); 70 | 71 | if ( $github_path && $this->app->api()->fetch()->exists( $github_path ) ) { 72 | return $github_path; 73 | } 74 | 75 | return false; 76 | } 77 | 78 | /** 79 | * Updates the provided post ID in master. 80 | * 81 | * @param int $post_id Post ID to update. 82 | * 83 | * @return string|WP_Error 84 | */ 85 | public function update( $post_id ) { 86 | $post = $this->app->database()->fetch_by_id( $post_id ); 87 | 88 | if ( is_wp_error( $post ) ) { 89 | /* @var WP_Error $post */ 90 | return $post; 91 | } 92 | 93 | if ( 'trash' === $post->status() ) { 94 | return $this->delete( $post_id ); 95 | } 96 | 97 | if ( $old_github_path = $this->github_path( $post->id() ) ) { 98 | error_log("old_github_path: $old_github_path"); 99 | $post->set_old_github_path($old_github_path); 100 | } 101 | 102 | $result = $this->export_post( $post ); 103 | 104 | if ( is_wp_error( $result ) ) { 105 | /* @var WP_Error $result */ 106 | return $result; 107 | } 108 | 109 | return __( 'Export to GitHub completed successfully.', 'writing-on-github' ); 110 | } 111 | 112 | /** 113 | * Post to blob 114 | * @param Writing_On_GitHub_Post $post 115 | * @return WP_Error|Writing_On_GitHub_Blob 116 | */ 117 | protected function post_to_blob( Writing_On_GitHub_Post $post ) { 118 | if ( ! $post->get_blob() 119 | && $post->old_github_path() 120 | && wogh_is_dont_export_content() ) { 121 | 122 | 123 | $blob = $this->app->api()->fetch()->blob_by_path( $post->old_github_path() ); 124 | 125 | if ( is_wp_error( $blob ) ) { 126 | /** @var WP_Error $blob */ 127 | return $blob; 128 | } 129 | 130 | $post->set_blob( $blob ); 131 | } 132 | 133 | return $post->to_blob(); 134 | } 135 | 136 | /** 137 | * Export post to github 138 | * @param Writing_On_GitHub_Post $post 139 | * @return WP_Error|true 140 | */ 141 | public function export_post( Writing_On_GitHub_Post $post ) { 142 | // check blob 143 | $blob = $this->post_to_blob( $post ); 144 | if ( is_wp_error( $blob ) ) { 145 | /** @var WP_Error $blob */ 146 | return $blob; 147 | } 148 | 149 | $result = false; 150 | 151 | $persist = $this->app->api()->persist(); 152 | $github_path = $post->github_path(); 153 | $old_github_path = $post->old_github_path(); 154 | 155 | if ( $old_github_path && $old_github_path != $github_path ) { 156 | // rename 157 | $message = apply_filters( 158 | 'wogh_commit_msg_move_post', 159 | sprintf( 160 | 'Move %s to %s via WordPress at %s (%s)', 161 | $old_github_path, $github_path, 162 | site_url(), 163 | get_bloginfo( 'name' ) 164 | ) 165 | ) . $this->get_commit_msg_tag(); 166 | 167 | $result = $persist->delete_file( $post->old_github_path(), $blob->sha(), $message ); 168 | if ( is_wp_error( $result ) ) { 169 | return $result; 170 | } 171 | 172 | $result = $persist->create_file( $blob, $message ); 173 | if ( is_wp_error( $result ) ) { 174 | return $result; 175 | } 176 | } elseif ( ! $old_github_path ) { 177 | // create new 178 | $message = apply_filters( 179 | 'wogh_commit_msg_new_post', 180 | sprintf( 181 | 'Create new post %s from WordPress at %s (%s)', 182 | $github_path, 183 | site_url(), 184 | get_bloginfo( 'name' ) 185 | ) 186 | ) . $this->get_commit_msg_tag(); 187 | $result = $persist->create_file( $blob, $message ); 188 | if ( is_wp_error( $result ) ) { 189 | return $result; 190 | } 191 | } elseif ( $old_github_path && $old_github_path == $github_path ) { 192 | // update 193 | $sha = wogh_git_sha( $blob->content() ); 194 | if ( $sha === $blob->sha() ) { 195 | // don't export when has not changed 196 | return true; 197 | } 198 | $message = apply_filters( 199 | 'wogh_commit_msg_update_post', 200 | sprintf( 201 | 'Update post %s from WordPress at %s (%s)', 202 | $github_path, 203 | site_url(), 204 | get_bloginfo( 'name' ) 205 | ) 206 | ) . $this->get_commit_msg_tag(); 207 | $result = $persist->update_file( $blob, $message ); 208 | if ( is_wp_error( $result ) ) { 209 | return $result; 210 | } 211 | } 212 | 213 | $sha = $result->content->sha; 214 | $post->set_sha( $sha ); 215 | $post->set_old_github_path( $github_path ); 216 | 217 | return true; 218 | } 219 | 220 | /** 221 | * Deletes a provided post ID from master. 222 | * 223 | * @param int $post_id Post ID to delete. 224 | * 225 | * @return string|WP_Error 226 | */ 227 | public function delete( $post_id ) { 228 | $post = $this->app->database()->fetch_by_id( $post_id ); 229 | 230 | if ( is_wp_error( $post ) ) { 231 | /* @var WP_Error $post */ 232 | return $post; 233 | } 234 | 235 | $github_path = get_post_meta( $post_id, '_wogh_github_path', true ); 236 | 237 | $message = apply_filters( 238 | 'wogh_commit_msg_delete', 239 | sprintf( 240 | 'Deleting %s via WordPress at %s (%s)', 241 | $github_path, 242 | site_url(), 243 | get_bloginfo( 'name' ) 244 | ), 245 | $post 246 | ) . $this->get_commit_msg_tag(); 247 | 248 | $result = $this->app->api()->persist()->delete_file( $github_path, $post->sha(), $message ); 249 | 250 | if ( is_wp_error( $result ) ) { 251 | /* @var WP_Error $result */ 252 | return $result; 253 | } 254 | 255 | return __( 'Export to GitHub completed successfully.', 'writing-on-github' ); 256 | } 257 | 258 | 259 | /** 260 | * Saves the export user to the database. 261 | * 262 | * @param int $user_id User ID to export with. 263 | * 264 | * @return bool 265 | */ 266 | public function set_user( $user_id ) { 267 | return update_option( self::EXPORT_USER_OPTION, (int) $user_id ); 268 | } 269 | 270 | /** 271 | * Gets the commit message tag. 272 | * 273 | * @return string 274 | */ 275 | protected function get_commit_msg_tag() { 276 | $tag = apply_filters( 'wogh_commit_msg_tag', 'wogh' ); 277 | 278 | if ( ! $tag ) { 279 | throw new Exception( __( 'Commit message tag not set. Filter `wogh_commit_msg_tag` misconfigured.', 'writing-on-github' ) ); 280 | } 281 | 282 | return ' - ' . $tag; 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /lib/fileinfo.php: -------------------------------------------------------------------------------- 1 | sha = $data->sha; 11 | $this->path = $data->path; 12 | $this->status = $data->status; 13 | } 14 | 15 | public $sha; 16 | public $path; 17 | public $status; // added removed modified 18 | } 19 | -------------------------------------------------------------------------------- /lib/function.php: -------------------------------------------------------------------------------- 1 | add( $error2->get_error_code(), $error2->get_error_message() ); 13 | } 14 | return $error2; 15 | } 16 | 17 | /** 18 | * Test is equal front matter of post and blob 19 | * @param Writing_On_GitHub_Post $post 20 | * @param Writing_On_GitHub_Blob $blob 21 | * @return bool 22 | */ 23 | function wogh_equal_front_matter( $post, $blob ) { 24 | $str1 = $post->front_matter(); 25 | $str2 = $blob->front_matter(); 26 | return trim($str1) === trim($str2); 27 | } 28 | 29 | function wogh_equal_path( $post, $blob ) { 30 | $str1 = $post->github_path(); 31 | $str2 = $blob->path(); 32 | return trim($str1) === trim($str2); 33 | } 34 | 35 | /** 36 | * Check is dont export wordpress content 37 | * @return bool 38 | */ 39 | function wogh_is_dont_export_content() { 40 | return 'yes' === get_option( 'wogh_dont_export_content' ); 41 | } 42 | 43 | /** 44 | * Calc git sha 45 | * https://git-scm.com/book/en/v2/Git-Internals-Git-Objects#_object_storage 46 | * @param string $content 47 | * @return string 48 | */ 49 | function wogh_git_sha( $content ) { 50 | // $header = "blob $len\0" 51 | // sha1($header . $content) 52 | $len = strlen( $content ); 53 | return sha1( "blob $len\0$content" ); 54 | } 55 | -------------------------------------------------------------------------------- /lib/import.php: -------------------------------------------------------------------------------- 1 | app = $app; 27 | } 28 | 29 | /** 30 | * Imports a payload. 31 | * @param Writing_On_GitHub_Payload $payload 32 | * 33 | * @return string|WP_Error 34 | */ 35 | public function payload( Writing_On_GitHub_Payload $payload ) { 36 | 37 | $result = $this->app->api()->fetch()->compare( $payload->get_before_commit_id() ); 38 | 39 | if ( is_wp_error( $result ) ) { 40 | /* @var WP_Error $result */ 41 | return $result; 42 | } 43 | 44 | if ( is_array( $result ) ) { 45 | $result = $this->import_files( $result ); 46 | } 47 | 48 | if ( is_wp_error( $result ) ) { 49 | return $result; 50 | } 51 | 52 | return __( 'Payload processed', 'writing-on-github' ); 53 | } 54 | 55 | /** 56 | * import blob by files 57 | * @param Writing_On_GitHub_File_Info[] $files 58 | * @param boolean $force 59 | * 60 | * @return true|WP_Error 61 | */ 62 | protected function import_files( $files, $force = false ) { 63 | 64 | $error = true; 65 | 66 | foreach ( $files as $file ) { 67 | if ( ! $this->importable_file( $file ) ) { 68 | continue; 69 | } 70 | 71 | $blob = $this->app->api()->fetch()->blob( $file ); 72 | // network error ? 73 | if ( ! $blob instanceof Writing_On_GitHub_Blob ) { 74 | continue; 75 | } 76 | 77 | $is_remove = 'removed' == $file->status; 78 | 79 | $result = false; 80 | if ( $this->importable_raw_file( $blob ) ) { 81 | $result = $this->import_raw_file( $blob, $is_remove ); 82 | } elseif ( $this->importable_post( $blob ) ) { 83 | if ( $is_remove ) { 84 | $result = $this->delete_post( $blob ); 85 | } else { 86 | $result = $this->import_post( $blob, $force ); 87 | } 88 | } 89 | 90 | if ( is_wp_error( $result ) ) { 91 | /* @var WP_Error $result */ 92 | $error = wogh_append_error( $error, $result ); 93 | } 94 | } 95 | 96 | return $error; 97 | } 98 | 99 | /** 100 | * Imports the latest commit on the master branch. 101 | * 102 | * @param boolean $force 103 | * @return string|WP_Error 104 | */ 105 | public function master( $force = false ) { 106 | $result = $this->app->api()->fetch()->tree_recursive(); 107 | 108 | if ( is_wp_error( $result ) ) { 109 | /* @var WP_Error $result */ 110 | return $result; 111 | } 112 | 113 | if ( is_array( $result ) ) { 114 | $result = $this->import_files( $result, $force ); 115 | } 116 | 117 | if ( is_wp_error( $result ) ) { 118 | /* @var WP_Error $result */ 119 | return $result; 120 | } 121 | 122 | return __( 'Payload processed', 'writing-on-github' ); 123 | } 124 | 125 | /** 126 | * Checks whether the provided blob should be imported. 127 | * 128 | * @param Writing_On_GitHub_File_Info $file 129 | * 130 | * @return bool 131 | */ 132 | protected function importable_file( Writing_On_GitHub_File_Info $file ) { 133 | 134 | $path = $file->path; 135 | 136 | // only _pages, _posts and images 137 | $prefixs = array( '_pages/', '_posts/', '_drafts/', 'images/'); 138 | foreach ($prefixs as $prefix) { 139 | if ( ! strncasecmp($path, $prefix, strlen( $prefix ) ) ) { 140 | return true; 141 | } 142 | } 143 | return false; 144 | } 145 | 146 | /** 147 | * Checks whether the provided blob should be imported. 148 | * 149 | * @param Writing_On_GitHub_Blob $blob Blob to validate. 150 | * 151 | * @return bool 152 | */ 153 | protected function importable_post( Writing_On_GitHub_Blob $blob ) { 154 | // global $wpdb; 155 | 156 | // // Skip the repo's readme. 157 | // if ( 'readme' === strtolower( substr( $blob->path(), 0, 6 ) ) ) { 158 | // return false; 159 | // } 160 | 161 | // // If the blob sha already matches a post, then move on. 162 | // if ( ! is_wp_error( $this->app->database()->fetch_by_sha( $blob->sha() ) ) ) { 163 | // return false; 164 | // } 165 | 166 | if ( ! $blob->has_frontmatter() ) { 167 | return false; 168 | } 169 | 170 | return true; 171 | } 172 | 173 | /** 174 | * Delete post 175 | * @param Writing_On_GitHub_Blob $blob 176 | * @return WP_Error|bool 177 | */ 178 | protected function delete_post( Writing_On_GitHub_Blob $blob ) { 179 | $id = false; 180 | $meta = $blob->meta(); 181 | if ( ! empty( $meta ) ) { 182 | if ( array_key_exists( 'ID', $meta ) ) { 183 | $id = $meta['ID']; 184 | } 185 | } 186 | 187 | if ( empty( $id ) ) { 188 | return false; 189 | } 190 | $result = $this->app->database()->delete_post( $id ); 191 | if ( is_wp_error( $result ) ) { 192 | /* @var WP_Error $result */ 193 | return $result; 194 | } 195 | return true; 196 | } 197 | 198 | /** 199 | * Imports a post into wordpress 200 | * @param Writing_On_GitHub_Blob $blob 201 | * @param boolean $force 202 | * @return WP_Error|bool 203 | */ 204 | protected function import_post( Writing_On_GitHub_Blob $blob, $force = false ) { 205 | $post = $this->blob_to_post( $blob, $force ); 206 | 207 | if ( ! $post instanceof Writing_On_GitHub_Post ) { 208 | return false; 209 | } 210 | 211 | $result = $this->app->database()->save_post( $post ); 212 | if ( is_wp_error( $result ) ) { 213 | /** @var WP_Error $result */ 214 | return $result; 215 | } 216 | 217 | if ( $post->is_new() || 218 | ! wogh_equal_path( $post, $blob ) || 219 | ! wogh_equal_front_matter( $post, $blob ) ) { 220 | 221 | $result = $this->app->export()->export_post( $post ); 222 | 223 | if ( is_wp_error( $result ) ) { 224 | /** @var WP_Error $result */ 225 | return $result; 226 | } 227 | } 228 | 229 | clean_post_cache( $post->id() ); 230 | 231 | return true; 232 | } 233 | 234 | /** 235 | * import raw file 236 | * @param Writing_On_GitHub_Blob $blob 237 | * @return bool 238 | */ 239 | protected function importable_raw_file( Writing_On_GitHub_Blob $blob ) { 240 | if ( $blob->has_frontmatter() ) { 241 | return false; 242 | } 243 | 244 | // only images 245 | if ( strncasecmp($blob->path(), 'images/', strlen('images/') ) != 0) { 246 | return false; 247 | } 248 | 249 | return true; 250 | } 251 | 252 | /** 253 | * Imports a raw file content into file system. 254 | * @param Writing_On_GitHub_Blob $blob 255 | * @param bool $is_remove 256 | */ 257 | protected function import_raw_file( Writing_On_GitHub_Blob $blob, $is_remove ) { 258 | $arr = wp_upload_dir(); 259 | $path = $arr['basedir'] . '/writing-on-github/' . $blob->path(); 260 | if ( $is_remove ) { 261 | if ( file_exists($path) ) { 262 | unlink($path); 263 | } 264 | } else { 265 | $dirname = dirname($path); 266 | if ( ! file_exists($dirname) ) { 267 | wp_mkdir_p($dirname); 268 | } 269 | 270 | file_put_contents($path, $blob->content()); 271 | } 272 | return true; 273 | } 274 | 275 | /** 276 | * Imports a single blob content into matching post. 277 | * 278 | * @param Writing_On_GitHub_Blob $blob Blob to transform into a Post. 279 | * @param boolean $force 280 | * 281 | * @return Writing_On_GitHub_Post|false 282 | */ 283 | protected function blob_to_post( Writing_On_GitHub_Blob $blob, $force = false ) { 284 | $args = array( 'post_content' => $blob->content_import() ); 285 | $meta = $blob->meta(); 286 | 287 | $id = false; 288 | 289 | if ( ! empty( $meta ) ) { 290 | if ( array_key_exists( 'layout', $meta ) ) { 291 | $args['post_type'] = $meta['layout']; 292 | unset( $meta['layout'] ); 293 | } 294 | 295 | if ( array_key_exists( 'published', $meta ) ) { 296 | $args['post_status'] = true === $meta['published'] ? 'publish' : 'draft'; 297 | unset( $meta['published'] ); 298 | } 299 | 300 | if ( array_key_exists( 'post_title', $meta ) ) { 301 | $args['post_title'] = $meta['post_title']; 302 | unset( $meta['post_title'] ); 303 | } 304 | 305 | if ( array_key_exists( 'post_name', $meta ) ) { 306 | $args['post_name'] = $meta['post_name']; 307 | unset( $meta['post_name'] ); 308 | } 309 | 310 | if ( array_key_exists( 'ID', $meta ) ) { 311 | $id = $args['ID'] = $meta['ID']; 312 | $blob->set_id( $id ); 313 | unset( $meta['ID'] ); 314 | } 315 | 316 | if ( array_key_exists( 'post_date', $meta ) ) { 317 | if ( empty( $meta['post_date'] ) ) { 318 | $meta['post_date'] = current_time( 'mysql' ); 319 | } 320 | 321 | $args['post_date'] = $meta['post_date']; 322 | unset( $meta['post_date'] ); 323 | } 324 | } 325 | 326 | $meta['_wogh_sha'] = $blob->sha(); 327 | 328 | if ( ! $force && $id ) { 329 | $old_sha = get_post_meta( $id, '_wogh_sha', true ); 330 | $old_github_path = get_post_meta( $id, '_wogh_github_path', true ); 331 | 332 | // dont save post when has same sha 333 | if ( $old_sha && $old_sha == $meta['_wogh_sha'] && 334 | $old_github_path && $old_github_path == $blob->path() ) { 335 | return false; 336 | } 337 | } 338 | 339 | $post = new Writing_On_GitHub_Post( $args, $this->app->api() ); 340 | $post->set_old_github_path( $blob->path() ); 341 | $post->set_meta( $meta ); 342 | $post->set_blob( $blob ); 343 | $blob->set_id( $post->id() ); 344 | 345 | return $post; 346 | } 347 | } 348 | -------------------------------------------------------------------------------- /lib/payload.php: -------------------------------------------------------------------------------- 1 | app = $app; 34 | $this->data = json_decode( $raw_data ); 35 | } 36 | 37 | /** 38 | * Returns whether payload should be imported. 39 | * 40 | * @return true|WP_Error 41 | */ 42 | public function should_import() { 43 | // @todo how do we get this without importing the whole api object just for this? 44 | if ( strtolower( $this->data->repository->full_name ) !== strtolower( $this->app->api()->fetch()->repository() ) ) { 45 | return new WP_Error( 46 | 'incorrect_repository', 47 | sprintf( 'Incorrect repository, %s -> %s .', 48 | $this->data->repository->full_name, 49 | $this->app->api()->fetch()->repository() 50 | ) 51 | ); 52 | } 53 | 54 | // The last term in the ref is the payload_branch name. 55 | $refs = explode( '/', $this->data->ref ); 56 | $payload_branch = array_pop( $refs ); 57 | 58 | $branch = $this->app->api()->fetch()->branch(); 59 | 60 | if ( $branch !== $payload_branch ) { 61 | return new WP_Error( 62 | 'incorrect_branch', 63 | sprintf( 'Incorrect branch, %s -> %s .', 64 | $payload_branch, 65 | $branch 66 | ) 67 | ); 68 | } 69 | 70 | // We add a tag to commits we push out, so we shouldn't pull them in again. 71 | $tag = apply_filters( 'wogh_commit_msg_tag', 'wogh' ); 72 | 73 | if ( ! $tag ) { 74 | throw new Exception( __( 'Commit message tag not set. Filter `wogh_commit_msg_tag` misconfigured.', 'writing-on-github' ) ); 75 | } 76 | 77 | if ( $tag === substr( $this->message(), -1 * strlen( $tag ) ) ) { 78 | return new WP_Error( 79 | 'skip_import', 80 | 'Skip import on auto export post.' 81 | ); 82 | } 83 | 84 | if ( ! $this->get_commit_id() ) { 85 | return new WP_Error( 86 | 'invalid_payload', 87 | "[Missing Commit ID] won't be imported." 88 | ); 89 | } 90 | 91 | return true; 92 | } 93 | 94 | public function get_before_commit_id() { 95 | return $this->data->before ? $this->data->before : null; 96 | } 97 | 98 | /** 99 | * Returns the sha of the head commit. 100 | * 101 | * @return string 102 | */ 103 | public function get_commit_id() { 104 | return $this->data->head_commit ? $this->data->head_commit->id : null; 105 | } 106 | 107 | /** 108 | * Returns the email address for the commit author. 109 | * 110 | * @return string 111 | */ 112 | public function get_author_email() { 113 | return $this->data->head_commit->author->email; 114 | } 115 | 116 | /** 117 | * Returns array commits for the payload. 118 | * 119 | * @return array 120 | */ 121 | public function get_commits() { 122 | return $this->data->commits; 123 | } 124 | 125 | /** 126 | * Returns the repository's full name. 127 | * 128 | * @return string 129 | */ 130 | public function get_repository_name() { 131 | return $this->data->repository->full_name; 132 | } 133 | 134 | /** 135 | * Returns the payload's commit message. 136 | * 137 | * @return string 138 | */ 139 | protected function message() { 140 | return $this->data->head_commit->message; 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /lib/request.php: -------------------------------------------------------------------------------- 1 | app = $app; 39 | } 40 | 41 | /** 42 | * Validates the header's secret. 43 | * 44 | * @return true|WP_Error 45 | */ 46 | public function is_secret_valid() { 47 | $headers = $this->headers(); 48 | 49 | $this->raw_data = $this->read_raw_data(); 50 | 51 | // Validate request secret. 52 | $hash = hash_hmac( 'sha1', $this->raw_data, $this->secret() ); 53 | if ( 'sha1=' . $hash !== $headers['X-Hub-Signature'] ) { 54 | return false; 55 | } 56 | 57 | // [X-Hub-Signature] => sha1=3cf3da70de401f7dfff053392f60cc534efed3b4 58 | // [Content-Type] => application/json 59 | // [X-Github-Delivery] => b2102500-0acf-11e7-8acb-fd86a3497c2f 60 | // [X-Github-Event] => ping 61 | 62 | return true; 63 | } 64 | 65 | /** 66 | * Validates the ping event. 67 | * @return boolean 68 | */ 69 | public function is_ping() { 70 | return 'ping' == $this->webhook_event(); 71 | } 72 | 73 | /** 74 | * Validates the push event. 75 | * @return boolean 76 | */ 77 | public function is_push() { 78 | return 'push' == $this->webhook_event(); 79 | } 80 | 81 | /** 82 | * Return X-Github-Event in headers. 83 | * @return string 84 | */ 85 | public function webhook_event() { 86 | $headers = $this->headers(); 87 | return $headers['X-Github-Event']; 88 | } 89 | 90 | /** 91 | * Returns a payload object for the given request. 92 | * 93 | * @return Writing_On_GitHub_Payload 94 | */ 95 | public function payload() { 96 | return new Writing_On_GitHub_Payload( $this->app, $this->raw_data ); 97 | } 98 | 99 | /** 100 | * Cross-server header support. 101 | * 102 | * Returns an array of the request's headers. 103 | * 104 | * @return array 105 | */ 106 | protected function headers() { 107 | if ( ! empty( $this->headers ) ) { 108 | return $this->headers; 109 | } 110 | 111 | $this->headers = array(); 112 | if ( function_exists( 'getallheaders' ) ) { 113 | $headers = getallheaders(); 114 | // github webhook 115 | // content-type: application/json 116 | // Expect: 117 | // User-Agent: GitHub-Hookshot/7a71d82 118 | // X-GitHub-Delivery: a331b200-2537-11e8-9d7e-ce0853020b44 119 | // X-GitHub-Event: push 120 | // X-Hub-Signature: sha1=98185ffa2c4684c9a1324c57086709acca9dddc7 121 | foreach ( $headers as $name => $value ) { 122 | $this->headers[ str_replace( ' ', '-', ucwords( strtolower( str_replace( '-', ' ', $name ) ) ) ) ] = $value; 123 | } 124 | } else { 125 | /** 126 | * Nginx and pre 5.4 workaround. 127 | * @see http://www.php.net/manual/en/function.getallheaders.php 128 | */ 129 | foreach ( $_SERVER as $name => $value ) { 130 | if ( 'HTTP_' === substr( $name, 0, 5 ) ) { 131 | $this->headers[ str_replace( ' ', '-', ucwords( strtolower( str_replace( '_', ' ', substr( $name, 5 ) ) ) ) ) ] = $value; 132 | } 133 | } 134 | } 135 | 136 | return $this->headers; 137 | } 138 | 139 | /** 140 | * Reads the raw data from STDIN. 141 | * 142 | * @return string 143 | */ 144 | protected function read_raw_data() { 145 | return file_get_contents( 'php://input' ); 146 | } 147 | 148 | /** 149 | * Returns the Webhook secret 150 | * 151 | * @return string 152 | */ 153 | protected function secret() { 154 | return get_option( 'wogh_secret' ); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /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( 'WOGH_AJAX' ) && WOGH_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( 'WOGH_AJAX' ) && WOGH_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 | Writing_On_GitHub::write_log( $msg ); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /lib/semaphore.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | Generally-applicable sniffs for WordPress plugins 4 | 5 | 6 | 7 | 8 | */node_modules/* 9 | */vendor/* 10 | 11 | 12 | */tests/* 13 | 14 | 15 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | ./tests/unit 12 | 13 | 14 | 15 | 16 | . 17 | 18 | ./vendor/ 19 | ./tests/ 20 | ./views// 21 | ./lib/cli.php 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /tests/data/delete_contents__posts_test.md_succeed.json: -------------------------------------------------------------------------------- 1 | { 2 | "content": null, 3 | "commit": { 4 | "sha": "30cbde04d6398f9c81ece2faabf64a8dccedc772", 5 | "url": "https://api.github.com/repos/woghtest/wogh-test/git/commits/30cbde04d6398f9c81ece2faabf64a8dccedc772", 6 | "html_url": "https://github.com/woghtest/wogh-test/commit/30cbde04d6398f9c81ece2faabf64a8dccedc772", 7 | "author": { 8 | "name": "litefeel", 9 | "email": "litefeel@gmail.com", 10 | "date": "2017-08-10T01:44:35Z" 11 | }, 12 | "committer": { 13 | "name": "litefeel", 14 | "email": "litefeel@gmail.com", 15 | "date": "2017-08-10T01:44:35Z" 16 | }, 17 | "tree": { 18 | "sha": "a64be87674bf8bfcff749d89d1ef2ee5020ad083", 19 | "url": "https://api.github.com/repos/woghtest/wogh-test/git/trees/a64be87674bf8bfcff749d89d1ef2ee5020ad083" 20 | }, 21 | "message": "my commit message", 22 | "parents": [ 23 | { 24 | "sha": "e85e3d7425d15bfd74a885a02cb865741c7a4476", 25 | "url": "https://api.github.com/repos/woghtest/wogh-test/git/commits/e85e3d7425d15bfd74a885a02cb865741c7a4476", 26 | "html_url": "https://github.com/woghtest/wogh-test/commit/e85e3d7425d15bfd74a885a02cb865741c7a4476" 27 | } 28 | ] 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /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_2d73165945b0ccbe4932f1363457986b0ed49f19_succeed.json: -------------------------------------------------------------------------------- 1 | { 2 | "sha": "2d73165945b0ccbe4932f1363457986b0ed49f19", 3 | "size": 283, 4 | "url": "https://api.github.com/repos/woghtest/wogh-test/git/blobs/2d73165945b0ccbe4932f1363457986b0ed49f19", 5 | "content": "LS0tCklEOiAxCnBvc3RfdGl0bGU6IEhlbGxvIHdvcmxkIQphdXRob3I6IHRl\nc3QKcG9zdF9kYXRlOiAyMDE1LTExLTAyIDAwOjM0OjUxCnBvc3RfZXhjZXJw\ndDogIiIKbGF5b3V0OiBwb3N0CnBlcm1hbGluazogPgogIGh0dHA6Ly9qYW1l\nc2RpZ2lvaWEubmdyb2suY29tLzIwMTUvMTEvMDIvaGVsbG8td29ybGQvCnB1\nYmxpc2hlZDogdHJ1ZQotLS0KV2VsY29tZSB0byBXb3JkUHJlc3MuIFRoaXMg\naXMgeW91ciBmaXJzdCBwb3N0LiBFZGl0IG9yIGRlbGV0ZSBpdCwgdGhlbiBz\ndGFydCB3cml0aW5nIQ==\n", 6 | "encoding": "base64" 7 | } 8 | -------------------------------------------------------------------------------- /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_8d9b2e6fd93761211dc03abd71f4a9189d680fd0_succeed.json: -------------------------------------------------------------------------------- 1 | { 2 | "sha": "8d9b2e6fd93761211dc03abd71f4a9189d680fd0", 3 | "size": 1155, 4 | "url": "https://api.github.com/repos/woghtest/wogh-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 | -------------------------------------------------------------------------------- /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_blobs_9fa5c7537f8582b71028ff34b8c20dfd0f3b2a25_succeed.json: -------------------------------------------------------------------------------- 1 | { 2 | "sha": "9fa5c7537f8582b71028ff34b8c20dfd0f3b2a25", 3 | "size": 13, 4 | "url": "https://api.github.com/repos/woghtest/wogh-test/git/blobs/9fa5c7537f8582b71028ff34b8c20dfd0f3b2a25", 5 | "content": "IyB3cGdocy10ZXN0Cg==\n", 6 | "encoding": "base64" 7 | } 8 | -------------------------------------------------------------------------------- /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_commits_db2510854e6aeab68ead26b48328b19f4bdf926e_succeed.json: -------------------------------------------------------------------------------- 1 | { 2 | "sha": "7497c0574b9430ff5e5521b4572b7452ea36a056", 3 | "url": "https://api.github.com/repos/woghtest/wogh-test/git/commits/7497c0574b9430ff5e5521b4572b7452ea36a056", 4 | "html_url": "https://github.com/woghtest/wogh-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/woghtest/wogh-test/git/trees/9108868e3800bec6763e51beb0d33e15036c3626" 18 | }, 19 | "message": "Initial full site export - wogh", 20 | "parents": [ 21 | { 22 | "sha": "db2510854e6aeab68ead26b48328b19f4bdf926e", 23 | "url": "https://api.github.com/repos/woghtest/wogh-test/git/commits/db2510854e6aeab68ead26b48328b19f4bdf926e", 24 | "html_url": "https://github.com/woghtest/wogh-test/commit/db2510854e6aeab68ead26b48328b19f4bdf926e" 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /tests/data/get_compare_861f87e8851b8debb78db548269d29f8da4d94ac...master_succeed.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://api.github.com/repos/litefeel/testwpsync/compare/861f87e8851b8debb78db548269d29f8da4d94ac...master", 3 | "html_url": "https://github.com/litefeel/testwpsync/compare/861f87e8851b8debb78db548269d29f8da4d94ac...master", 4 | "permalink_url": "https://github.com/litefeel/testwpsync/compare/litefeel:861f87e...litefeel:855f7be", 5 | "diff_url": "https://github.com/litefeel/testwpsync/compare/861f87e8851b8debb78db548269d29f8da4d94ac...master.diff", 6 | "patch_url": "https://github.com/litefeel/testwpsync/compare/861f87e8851b8debb78db548269d29f8da4d94ac...master.patch", 7 | "base_commit": { 8 | "sha": "861f87e8851b8debb78db548269d29f8da4d94ac", 9 | "commit": { 10 | "author": { 11 | "name": "zheyu", 12 | "email": "lite3@qq.com", 13 | "date": "2017-03-07T09:31:35Z" 14 | }, 15 | "committer": { 16 | "name": "GitHub", 17 | "email": "noreply@github.com", 18 | "date": "2017-03-07T09:31:35Z" 19 | }, 20 | "message": "Delete test.md", 21 | "tree": { 22 | "sha": "319e0a277fe443ca863b56ce184dbeafb0fc6f05", 23 | "url": "https://api.github.com/repos/litefeel/testwpsync/git/trees/319e0a277fe443ca863b56ce184dbeafb0fc6f05" 24 | }, 25 | "url": "https://api.github.com/repos/litefeel/testwpsync/git/commits/861f87e8851b8debb78db548269d29f8da4d94ac", 26 | "comment_count": 0 27 | }, 28 | "url": "https://api.github.com/repos/litefeel/testwpsync/commits/861f87e8851b8debb78db548269d29f8da4d94ac", 29 | "html_url": "https://github.com/litefeel/testwpsync/commit/861f87e8851b8debb78db548269d29f8da4d94ac", 30 | "comments_url": "https://api.github.com/repos/litefeel/testwpsync/commits/861f87e8851b8debb78db548269d29f8da4d94ac/comments", 31 | "author": { 32 | "login": "litefeel", 33 | "id": 1288575, 34 | "avatar_url": "https://avatars0.githubusercontent.com/u/1288575?v=4", 35 | "gravatar_id": "", 36 | "url": "https://api.github.com/users/litefeel", 37 | "html_url": "https://github.com/litefeel", 38 | "followers_url": "https://api.github.com/users/litefeel/followers", 39 | "following_url": "https://api.github.com/users/litefeel/following{/other_user}", 40 | "gists_url": "https://api.github.com/users/litefeel/gists{/gist_id}", 41 | "starred_url": "https://api.github.com/users/litefeel/starred{/owner}{/repo}", 42 | "subscriptions_url": "https://api.github.com/users/litefeel/subscriptions", 43 | "organizations_url": "https://api.github.com/users/litefeel/orgs", 44 | "repos_url": "https://api.github.com/users/litefeel/repos", 45 | "events_url": "https://api.github.com/users/litefeel/events{/privacy}", 46 | "received_events_url": "https://api.github.com/users/litefeel/received_events", 47 | "type": "User", 48 | "site_admin": false 49 | }, 50 | "committer": { 51 | "login": "web-flow", 52 | "id": 19864447, 53 | "avatar_url": "https://avatars3.githubusercontent.com/u/19864447?v=4", 54 | "gravatar_id": "", 55 | "url": "https://api.github.com/users/web-flow", 56 | "html_url": "https://github.com/web-flow", 57 | "followers_url": "https://api.github.com/users/web-flow/followers", 58 | "following_url": "https://api.github.com/users/web-flow/following{/other_user}", 59 | "gists_url": "https://api.github.com/users/web-flow/gists{/gist_id}", 60 | "starred_url": "https://api.github.com/users/web-flow/starred{/owner}{/repo}", 61 | "subscriptions_url": "https://api.github.com/users/web-flow/subscriptions", 62 | "organizations_url": "https://api.github.com/users/web-flow/orgs", 63 | "repos_url": "https://api.github.com/users/web-flow/repos", 64 | "events_url": "https://api.github.com/users/web-flow/events{/privacy}", 65 | "received_events_url": "https://api.github.com/users/web-flow/received_events", 66 | "type": "User", 67 | "site_admin": false 68 | }, 69 | "parents": [ 70 | { 71 | "sha": "644b923d454171799a6a83601dfdaf426890a822", 72 | "url": "https://api.github.com/repos/litefeel/testwpsync/commits/644b923d454171799a6a83601dfdaf426890a822", 73 | "html_url": "https://github.com/litefeel/testwpsync/commit/644b923d454171799a6a83601dfdaf426890a822" 74 | } 75 | ] 76 | }, 77 | "merge_base_commit": { 78 | "sha": "861f87e8851b8debb78db548269d29f8da4d94ac", 79 | "commit": { 80 | "author": { 81 | "name": "zheyu", 82 | "email": "lite3@qq.com", 83 | "date": "2017-03-07T09:31:35Z" 84 | }, 85 | "committer": { 86 | "name": "GitHub", 87 | "email": "noreply@github.com", 88 | "date": "2017-03-07T09:31:35Z" 89 | }, 90 | "message": "Delete test.md", 91 | "tree": { 92 | "sha": "319e0a277fe443ca863b56ce184dbeafb0fc6f05", 93 | "url": "https://api.github.com/repos/litefeel/testwpsync/git/trees/319e0a277fe443ca863b56ce184dbeafb0fc6f05" 94 | }, 95 | "url": "https://api.github.com/repos/litefeel/testwpsync/git/commits/861f87e8851b8debb78db548269d29f8da4d94ac", 96 | "comment_count": 0 97 | }, 98 | "url": "https://api.github.com/repos/litefeel/testwpsync/commits/861f87e8851b8debb78db548269d29f8da4d94ac", 99 | "html_url": "https://github.com/litefeel/testwpsync/commit/861f87e8851b8debb78db548269d29f8da4d94ac", 100 | "comments_url": "https://api.github.com/repos/litefeel/testwpsync/commits/861f87e8851b8debb78db548269d29f8da4d94ac/comments", 101 | "author": { 102 | "login": "litefeel", 103 | "id": 1288575, 104 | "avatar_url": "https://avatars0.githubusercontent.com/u/1288575?v=4", 105 | "gravatar_id": "", 106 | "url": "https://api.github.com/users/litefeel", 107 | "html_url": "https://github.com/litefeel", 108 | "followers_url": "https://api.github.com/users/litefeel/followers", 109 | "following_url": "https://api.github.com/users/litefeel/following{/other_user}", 110 | "gists_url": "https://api.github.com/users/litefeel/gists{/gist_id}", 111 | "starred_url": "https://api.github.com/users/litefeel/starred{/owner}{/repo}", 112 | "subscriptions_url": "https://api.github.com/users/litefeel/subscriptions", 113 | "organizations_url": "https://api.github.com/users/litefeel/orgs", 114 | "repos_url": "https://api.github.com/users/litefeel/repos", 115 | "events_url": "https://api.github.com/users/litefeel/events{/privacy}", 116 | "received_events_url": "https://api.github.com/users/litefeel/received_events", 117 | "type": "User", 118 | "site_admin": false 119 | }, 120 | "committer": { 121 | "login": "web-flow", 122 | "id": 19864447, 123 | "avatar_url": "https://avatars3.githubusercontent.com/u/19864447?v=4", 124 | "gravatar_id": "", 125 | "url": "https://api.github.com/users/web-flow", 126 | "html_url": "https://github.com/web-flow", 127 | "followers_url": "https://api.github.com/users/web-flow/followers", 128 | "following_url": "https://api.github.com/users/web-flow/following{/other_user}", 129 | "gists_url": "https://api.github.com/users/web-flow/gists{/gist_id}", 130 | "starred_url": "https://api.github.com/users/web-flow/starred{/owner}{/repo}", 131 | "subscriptions_url": "https://api.github.com/users/web-flow/subscriptions", 132 | "organizations_url": "https://api.github.com/users/web-flow/orgs", 133 | "repos_url": "https://api.github.com/users/web-flow/repos", 134 | "events_url": "https://api.github.com/users/web-flow/events{/privacy}", 135 | "received_events_url": "https://api.github.com/users/web-flow/received_events", 136 | "type": "User", 137 | "site_admin": false 138 | }, 139 | "parents": [ 140 | { 141 | "sha": "644b923d454171799a6a83601dfdaf426890a822", 142 | "url": "https://api.github.com/repos/litefeel/testwpsync/commits/644b923d454171799a6a83601dfdaf426890a822", 143 | "html_url": "https://github.com/litefeel/testwpsync/commit/644b923d454171799a6a83601dfdaf426890a822" 144 | } 145 | ] 146 | }, 147 | "status": "ahead", 148 | "ahead_by": 47, 149 | "behind_by": 0, 150 | "total_commits": 47, 151 | "commits": [ 152 | { 153 | "sha": "8a7068c4e0a4291416448252a4deeeff28f93d43", 154 | "commit": { 155 | "author": { 156 | "name": "zheyu", 157 | "email": "lite3@qq.com", 158 | "date": "2017-03-07T09:34:21Z" 159 | }, 160 | "committer": { 161 | "name": "GitHub", 162 | "email": "noreply@github.com", 163 | "date": "2017-03-07T09:34:21Z" 164 | }, 165 | "message": "Create fdsafds.md", 166 | "tree": { 167 | "sha": "dfd81a50822447808e355519c25ea82fac44d75b", 168 | "url": "https://api.github.com/repos/litefeel/testwpsync/git/trees/dfd81a50822447808e355519c25ea82fac44d75b" 169 | }, 170 | "url": "https://api.github.com/repos/litefeel/testwpsync/git/commits/8a7068c4e0a4291416448252a4deeeff28f93d43", 171 | "comment_count": 0 172 | }, 173 | "url": "https://api.github.com/repos/litefeel/testwpsync/commits/8a7068c4e0a4291416448252a4deeeff28f93d43", 174 | "html_url": "https://github.com/litefeel/testwpsync/commit/8a7068c4e0a4291416448252a4deeeff28f93d43", 175 | "comments_url": "https://api.github.com/repos/litefeel/testwpsync/commits/8a7068c4e0a4291416448252a4deeeff28f93d43/comments", 176 | "author": { 177 | "login": "litefeel", 178 | "id": 1288575, 179 | "avatar_url": "https://avatars0.githubusercontent.com/u/1288575?v=4", 180 | "gravatar_id": "", 181 | "url": "https://api.github.com/users/litefeel", 182 | "html_url": "https://github.com/litefeel", 183 | "followers_url": "https://api.github.com/users/litefeel/followers", 184 | "following_url": "https://api.github.com/users/litefeel/following{/other_user}", 185 | "gists_url": "https://api.github.com/users/litefeel/gists{/gist_id}", 186 | "starred_url": "https://api.github.com/users/litefeel/starred{/owner}{/repo}", 187 | "subscriptions_url": "https://api.github.com/users/litefeel/subscriptions", 188 | "organizations_url": "https://api.github.com/users/litefeel/orgs", 189 | "repos_url": "https://api.github.com/users/litefeel/repos", 190 | "events_url": "https://api.github.com/users/litefeel/events{/privacy}", 191 | "received_events_url": "https://api.github.com/users/litefeel/received_events", 192 | "type": "User", 193 | "site_admin": false 194 | }, 195 | "committer": { 196 | "login": "web-flow", 197 | "id": 19864447, 198 | "avatar_url": "https://avatars3.githubusercontent.com/u/19864447?v=4", 199 | "gravatar_id": "", 200 | "url": "https://api.github.com/users/web-flow", 201 | "html_url": "https://github.com/web-flow", 202 | "followers_url": "https://api.github.com/users/web-flow/followers", 203 | "following_url": "https://api.github.com/users/web-flow/following{/other_user}", 204 | "gists_url": "https://api.github.com/users/web-flow/gists{/gist_id}", 205 | "starred_url": "https://api.github.com/users/web-flow/starred{/owner}{/repo}", 206 | "subscriptions_url": "https://api.github.com/users/web-flow/subscriptions", 207 | "organizations_url": "https://api.github.com/users/web-flow/orgs", 208 | "repos_url": "https://api.github.com/users/web-flow/repos", 209 | "events_url": "https://api.github.com/users/web-flow/events{/privacy}", 210 | "received_events_url": "https://api.github.com/users/web-flow/received_events", 211 | "type": "User", 212 | "site_admin": false 213 | }, 214 | "parents": [ 215 | { 216 | "sha": "861f87e8851b8debb78db548269d29f8da4d94ac", 217 | "url": "https://api.github.com/repos/litefeel/testwpsync/commits/861f87e8851b8debb78db548269d29f8da4d94ac", 218 | "html_url": "https://github.com/litefeel/testwpsync/commit/861f87e8851b8debb78db548269d29f8da4d94ac" 219 | } 220 | ] 221 | } 222 | ], 223 | "files": [ 224 | { 225 | "sha": "c5dec71bfb292cc066494489e564a97cf371d2f3", 226 | "filename": "_posts/2016-06-28-test-save.md", 227 | "status": "modified", 228 | "additions": 2, 229 | "deletions": 0, 230 | "changes": 2, 231 | "blob_url": "https://github.com/litefeel/testwpsync/blob/855f7be17977329b3bd7b3b020d6df234e90cb29/_posts/2016-06-28-test-save.md", 232 | "raw_url": "https://github.com/litefeel/testwpsync/raw/855f7be17977329b3bd7b3b020d6df234e90cb29/_posts/2016-06-28-test-save.md", 233 | "contents_url": "https://api.github.com/repos/litefeel/testwpsync/contents/_posts/2016-06-28-test-save.md?ref=855f7be17977329b3bd7b3b020d6df234e90cb29", 234 | "patch": "@@ -40,3 +40,5 @@ fdsa\n fdsa\n fdsdd\n s\n+fdsaf\n+dsa=========================" 235 | } 236 | ] 237 | } 238 | -------------------------------------------------------------------------------- /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/get_refs_heads_master_succeed.json: -------------------------------------------------------------------------------- 1 | { 2 | "ref": "refs/heads/master", 3 | "url": "https://api.github.com/repos/woghtest/wogh-test/git/refs/heads/master", 4 | "object": { 5 | "sha": "db2510854e6aeab68ead26b48328b19f4bdf926e", 6 | "type": "commit", 7 | "url": "https://api.github.com/repos/woghtest/wogh-test/git/commits/db2510854e6aeab68ead26b48328b19f4bdf926e" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /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/data/get_trees_9108868e3800bec6763e51beb0d33e15036c3626_succeed.json: -------------------------------------------------------------------------------- 1 | { 2 | "sha": "9108868e3800bec6763e51beb0d33e15036c3626", 3 | "url": "https://api.github.com/repos/woghtest/wogh-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/woghtest/wogh-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/woghtest/wogh-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/woghtest/wogh-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/woghtest/wogh-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/woghtest/wogh-test/git/blobs/2d73165945b0ccbe4932f1363457986b0ed49f19" 42 | } 43 | ], 44 | "truncated": false 45 | } 46 | -------------------------------------------------------------------------------- /tests/data/get_trees_master_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/data/get_trees_master_succeed.json: -------------------------------------------------------------------------------- 1 | { 2 | "sha": "9108868e3800bec6763e51beb0d33e15036c3626", 3 | "url": "https://api.github.com/repos/woghtest/wogh-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/woghtest/wogh-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/woghtest/wogh-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/woghtest/wogh-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/woghtest/wogh-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/woghtest/wogh-test/git/blobs/2d73165945b0ccbe4932f1363457986b0ed49f19" 42 | } 43 | ], 44 | "truncated": false 45 | } 46 | -------------------------------------------------------------------------------- /tests/data/patch_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_succeed.json: -------------------------------------------------------------------------------- 1 | { 2 | "ref": "refs/heads/master", 3 | "url": "https://api.github.com/repos/woghtest/wogh-test/git/refs/heads/master", 4 | "object": { 5 | "sha": "ff2b3d42e86fa7e38c7b7886f490ae54b431f524", 6 | "type": "commit", 7 | "url": "https://api.github.com/repos/woghtest/wogh-test/git/commits/ff2b3d42e86fa7e38c7b7886f490ae54b431f524" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /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\" - wogh", 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\" - wogh", 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\" - wogh", 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\" - wogh", 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 | -------------------------------------------------------------------------------- /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\" - wogh", 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\" - wogh", 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-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\" - wogh", 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/post_commits_fail.json: -------------------------------------------------------------------------------- 1 | { 2 | "message": "Not Found", 3 | "documentation_url": "https://developer.github.com/v3" 4 | } 5 | -------------------------------------------------------------------------------- /tests/data/post_commits_succeed.json: -------------------------------------------------------------------------------- 1 | { 2 | "sha": "ff2b3d42e86fa7e38c7b7886f490ae54b431f524", 3 | "url": "https://api.github.com/repos/woghtest/wogh-test/git/commits/ff2b3d42e86fa7e38c7b7886f490ae54b431f524", 4 | "html_url": "https://github.com/woghtest/wogh-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/woghtest/wogh-test/git/trees/c0d2cb90b51826096c826b61a0e74d2c973d7ad8" 18 | }, 19 | "message": "New commit via paw.", 20 | "parents": [ 21 | { 22 | "sha": "7497c0574b9430ff5e5521b4572b7452ea36a056", 23 | "url": "https://api.github.com/repos/woghtest/wogh-test/git/commits/7497c0574b9430ff5e5521b4572b7452ea36a056", 24 | "html_url": "https://github.com/woghtest/wogh-test/commit/7497c0574b9430ff5e5521b4572b7452ea36a056" 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /tests/data/post_trees_fail.json: -------------------------------------------------------------------------------- 1 | { 2 | "message": "Not Found", 3 | "documentation_url": "https://developer.github.com/v3" 4 | } 5 | -------------------------------------------------------------------------------- /tests/data/post_trees_succeed.json: -------------------------------------------------------------------------------- 1 | { 2 | "sha": "c0d2cb90b51826096c826b61a0e74d2c973d7ad8", 3 | "url": "https://api.github.com/repos/woghtest/wogh-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/woghtest/wogh-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/woghtest/wogh-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/woghtest/wogh-test/git/trees/168aff0ee6be2ffa50b34e55859c38a6b511aaad" 26 | } 27 | ], 28 | "truncated": false 29 | } 30 | -------------------------------------------------------------------------------- /tests/include/bootstrap.php: -------------------------------------------------------------------------------- 1 | data_dir = dirname( __DIR__ ) . '/data/'; 84 | 85 | $this->app = Mockery::mock( 'Writing_On_GitHub' ); 86 | $this->controller = Mockery::mock( 'Writing_On_GitHub_Controller' ); 87 | $this->request = Mockery::mock( 'Writing_On_GitHub_Request' ); 88 | $this->import = Mockery::mock( 'Writing_On_GitHub_Import' ); 89 | $this->export = Mockery::mock( 'Writing_On_GitHub_Export' ); 90 | $this->response = Mockery::mock( 'Writing_On_GitHub_Response' ); 91 | $this->payload = Mockery::mock( 'Writing_On_GitHub_Payload' ); 92 | $this->api = Mockery::mock( 'Writing_On_GitHub_Api' ); 93 | $this->semaphore = Mockery::mock( 'Writing_On_GitHub_Semaphore' ); 94 | $this->database = Mockery::mock( 'Writing_On_GitHub_Database' ); 95 | $this->post = Mockery::mock( 'Writing_On_GitHub_Post' ); 96 | $this->blob = Mockery::mock( 'Writing_On_GitHub_Blob' ); 97 | $this->fetch = Mockery::mock( 'Writing_On_GitHub_Fetch_Client' ); 98 | $this->persist = Mockery::mock( 'Writing_On_GitHub_Persist_Client' ); 99 | 100 | Writing_On_GitHub::$instance = $this->app; 101 | 102 | $this->app 103 | ->shouldReceive( 'request' ) 104 | ->andReturn( $this->request ) 105 | ->byDefault(); 106 | $this->app 107 | ->shouldReceive( 'import' ) 108 | ->andReturn( $this->import ) 109 | ->byDefault(); 110 | $this->app 111 | ->shouldReceive( 'export' ) 112 | ->andReturn( $this->export ) 113 | ->byDefault(); 114 | $this->app 115 | ->shouldReceive( 'response' ) 116 | ->andReturn( $this->response ) 117 | ->byDefault(); 118 | $this->app 119 | ->shouldReceive( 'api' ) 120 | ->andReturn( $this->api ) 121 | ->byDefault(); 122 | $this->app 123 | ->shouldReceive( 'semaphore' ) 124 | ->andReturn( $this->semaphore ) 125 | ->byDefault(); 126 | $this->app 127 | ->shouldReceive( 'database' ) 128 | ->andReturn( $this->database ) 129 | ->byDefault(); 130 | $this->app 131 | ->shouldReceive( 'blob' ) 132 | ->andReturn( $this->blob ) 133 | ->byDefault(); 134 | $this->api 135 | ->shouldReceive( 'fetch' ) 136 | ->andReturn( $this->fetch ) 137 | ->byDefault(); 138 | $this->api 139 | ->shouldReceive( 'persist' ) 140 | ->andReturn( $this->persist ) 141 | ->byDefault(); 142 | } 143 | 144 | public function tearDown() { 145 | Mockery::close(); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /tests/unit/client/test-base.php: -------------------------------------------------------------------------------- 1 | http_responder = array( $this, 'mock_github_api' ); 44 | } 45 | 46 | /** 47 | * This does some checks and fails the test if something is wrong 48 | * or returns intended mock data for the given endpoint + method. 49 | * 50 | * @return void|string 51 | */ 52 | public function mock_github_api( $request, $url ) { 53 | $host_length = strlen( self::HOST_OPTION_VALUE ); 54 | 55 | if ( self::HOST_OPTION_VALUE !== substr( $url, 0, $host_length ) ) { 56 | $this->assertTrue( false, 'Called wrong host.' ); 57 | } 58 | 59 | if ( 60 | ! isset( $request['headers']['Authorization'] ) || 61 | 'token ' . self::TOKEN_OPTION_VALUE !== $request['headers']['Authorization'] 62 | ) { 63 | $this->assertTrue( false, 'Missing authorization key.' ); 64 | } 65 | 66 | $url = explode( '/', substr( $url, $host_length + 1 ) ); 67 | 68 | if ( 'repos' !== $url[0] ) { 69 | $this->assertTrue( false, 'Called wrong endpoint.' ); 70 | } 71 | 72 | $repo = $url[1] . '/' . $url[2]; 73 | 74 | if ( self::REPO_OPTION_VALUE !== $repo ) { 75 | $this->assertTrue( false, 'Called wrong repo.' ); 76 | } 77 | 78 | // git api 79 | $parts = array_slice( $url, $url[3] === 'git' ? 4 : 3 ); 80 | array_unshift( $parts, strtolower( $request['method'] ) ); 81 | $endpoint = implode( '_', $parts ); 82 | $endpoint = str_replace( '?recursive=1', '', $endpoint ); 83 | $this->assertTrue( call_user_func( static::$validations[ $endpoint ], $request ), 'Request did not validate.' ); 84 | 85 | return static::$responses[ $endpoint ]; 86 | } 87 | 88 | protected function set_get_refs_heads_master( $succeed ) { 89 | $this->set_endpoint( 90 | function ( $request ) { 91 | if ( '[]' === $request['body'] ) { 92 | return false; 93 | } 94 | 95 | return true; 96 | }, $succeed ? '200 OK' : '404 Not Found', $succeed 97 | ); 98 | } 99 | 100 | protected function set_get_commits( $succeed ) { 101 | $this->set_endpoint( 102 | function ( $request ) { 103 | if ( '[]' === $request['body'] ) { 104 | return false; 105 | } 106 | 107 | return true; 108 | }, $succeed ? '200 OK' : '404 Not Found', $succeed, 'db2510854e6aeab68ead26b48328b19f4bdf926e' 109 | ); 110 | } 111 | 112 | protected function set_get_trees( $succeed, $sha = '' ) { 113 | $this->set_endpoint( 114 | function ( $request ) { 115 | if ( '[]' === $request['body'] ) { 116 | return false; 117 | } 118 | 119 | return true; 120 | }, $succeed ? '200 OK' : '422 Unprocessable Entity', $succeed, 121 | $sha ? $sha : '9108868e3800bec6763e51beb0d33e15036c3626' 122 | ); 123 | } 124 | 125 | protected function set_get_blobs( $succeed ) { 126 | $shas = array( 127 | '9fa5c7537f8582b71028ff34b8c20dfd0f3b2a25', 128 | '8d9b2e6fd93761211dc03abd71f4a9189d680fd0', 129 | '2d73165945b0ccbe4932f1363457986b0ed49f19', 130 | ); 131 | 132 | foreach ( $shas as $sha ) { 133 | $this->set_endpoint( 134 | function ( $request ) { 135 | if ( '[]' === $request['body'] ) { 136 | return false; 137 | } 138 | 139 | return true; 140 | }, $succeed ? '200 OK' : '404 Not Found', $succeed, $sha 141 | ); 142 | } 143 | } 144 | 145 | protected function set_post_trees( $succeed ) { 146 | $this->set_endpoint( 147 | function ( $request ) { 148 | $body = json_decode( $request['body'], true ); 149 | 150 | if ( ! isset( $body['tree'] ) ) { 151 | return false; 152 | } 153 | 154 | if ( 1 !== count( $body['tree'] ) ) { 155 | return false; 156 | } 157 | 158 | $blob = reset( $body['tree'] ); 159 | 160 | if ( 161 | ! isset( $blob['path'] ) || 162 | ! isset( $blob['type'] ) || 163 | ! isset( $blob['content'] ) || 164 | ! isset( $blob['mode'] ) 165 | ) { 166 | return false; 167 | } 168 | 169 | return true; 170 | }, 171 | $succeed ? '201 Created' : '404 Not Found', 172 | $succeed 173 | ); 174 | } 175 | 176 | protected function set_post_commits( $succeed, $anonymous = true ) { 177 | $this->set_endpoint( 178 | function ( $request ) use ( $anonymous ) { 179 | $body = json_decode( $request['body'], true ); 180 | 181 | if ( 182 | ! isset( $body['tree'] ) || 183 | ! isset( $body['message'] ) || 184 | ! isset( $body['parents'] ) || 185 | ! isset( $body['author'] ) 186 | ) { 187 | return false; 188 | } 189 | 190 | if ( 1 !== count( $body['parents'] ) ) { 191 | return false; 192 | } 193 | 194 | if ( ! $anonymous ) { 195 | if ( 196 | 'James DiGioia' !== $body['author']['name'] || 197 | 'jamesorodig@gmail.com' !== $body['author']['email'] 198 | ) { 199 | return false; 200 | } 201 | } else { 202 | if ( 203 | 'Anonymous' !== $body['author']['name'] || 204 | 'anonymous@users.noreply.github.com' !== $body['author']['email'] 205 | ) { 206 | return false; 207 | } 208 | } 209 | 210 | return true; 211 | }, 212 | $succeed ? '201 Created' : '404 Not Found', 213 | $succeed 214 | ); 215 | } 216 | 217 | protected function set_get_compare( $succeed ) { 218 | $this->set_endpoint( 219 | function ( $request ) { 220 | if ( '[]' === $request['body'] ) { 221 | return false; 222 | } 223 | return true; 224 | }, 225 | $succeed ? '200 OK' : '404 Not Found', 226 | $succeed, 227 | '861f87e8851b8debb78db548269d29f8da4d94ac...master' 228 | ); 229 | } 230 | 231 | protected function set_delete_contents( $succeed, $filepath ) { 232 | $this->set_endpoint( 233 | function ( $request ) { 234 | if ( '[]' === $request['body'] ) { 235 | return false; 236 | } 237 | return true; 238 | }, 239 | $succeed ? '200 OK' : '404 Not Found', 240 | $succeed, 241 | $filepath 242 | ); 243 | } 244 | 245 | protected function set_patch_refs_heads_master( $succeed ) { 246 | $this->set_endpoint( 247 | function ( $request ) { 248 | $body = json_decode( $request['body'], true ); 249 | 250 | if ( ! isset( $body['sha'] ) ) { 251 | return false; 252 | } 253 | 254 | return true; 255 | }, 256 | $succeed ? '201 Created' : '404 Not Found', 257 | $succeed 258 | ); 259 | } 260 | 261 | private function set_endpoint( $validation, $status, $succeed, $sha = '' ) { 262 | list( , $caller ) = debug_backtrace( false ); 263 | $endpoint = substr( $caller['function'], 4 ) . ( $sha ? "_$sha" : '' ); 264 | 265 | static::$validations[ $endpoint ] = $validation; 266 | 267 | static::$responses[ $endpoint ] = array( 268 | 'headers' => array( 269 | 'status' => $status, 270 | ), 271 | 'body' => file_get_contents( 272 | $this->data_dir . $endpoint . '_' . ( $succeed ? 'succeed' : 'fail' ) . '.json' 273 | ), 274 | ); 275 | } 276 | } 277 | -------------------------------------------------------------------------------- /tests/unit/client/test-fetch.php: -------------------------------------------------------------------------------- 1 | fetch = new Writing_On_GitHub_Fetch_Client( $this->app ); 13 | } 14 | 15 | public function test_should_fail_if_missing_token() { 16 | delete_option( Base::TOKEN_OPTION_KEY ); 17 | 18 | $this->assertInstanceOf( 'WP_Error', $error = $this->fetch->tree_recursive() ); 19 | $this->assertSame( 'missing_token', $error->get_error_code() ); 20 | } 21 | 22 | public function test_should_fail_if_missing_repo() { 23 | delete_option( Base::REPO_OPTION_KEY ); 24 | 25 | $this->assertInstanceOf( 'WP_Error', $error = $this->fetch->tree_recursive() ); 26 | $this->assertSame( 'missing_repository', $error->get_error_code() ); 27 | } 28 | 29 | public function test_should_fail_if_malformed_repo() { 30 | // If you find a particular name passing that shouldn't, 31 | // add it to the list here and make it pass. 32 | $this->malformed_repo( 'repositoryname' ); 33 | } 34 | 35 | public function test_compare() { 36 | $this->set_get_trees( true, 'master' ); 37 | $this->set_get_compare( true ); 38 | $infos = $this->fetch->compare( '861f87e8851b8debb78db548269d29f8da4d94ac' ); 39 | $this->assertCount( 1, $infos ); 40 | $this->assertInstanceOf( 'Writing_On_GitHub_File_Info', $infos[0] ); 41 | } 42 | 43 | public function test_should_return_files_with_tree() { 44 | // $this->set_get_refs_heads_master( true ); 45 | // $this->set_get_commits( true ); 46 | $this->set_get_trees( true, 'master' ); 47 | 48 | $this->assertCount( 3, $this->fetch->tree_recursive() ); 49 | } 50 | 51 | public function test_should_fail_if_cant_fetch_tree() { 52 | $this->set_get_trees( false, 'master' ); 53 | 54 | $this->assertInstanceOf( 'WP_Error', $error = $this->fetch->tree_recursive() ); 55 | $this->assertSame( '422_unprocessable_entity', $error->get_error_code() ); 56 | } 57 | 58 | public function test_should_return_commit_with_no_blobs_if_api_fails() { 59 | $this->set_get_trees( true, 'master' ); 60 | $this->set_get_blobs( false ); 61 | 62 | $this->assertCount( 3, $files = $this->fetch->tree_recursive() ); 63 | 64 | foreach ( $files as $file ) { 65 | $this->assertInstanceOf( 'WP_Error', $error = $this->fetch->blob( $file ) ); 66 | $this->assertSame( '404_not_found', $error->get_error_code() ); 67 | } 68 | } 69 | 70 | // public function test_should_return_and_validate_full_commit() { 71 | // $this->set_get_refs_heads_master( true ); 72 | // $this->set_get_commits( true ); 73 | // $this->set_get_trees( true ); 74 | // $this->set_get_blobs( true ); 75 | // $this->api_cache 76 | // ->shouldReceive( 'set_blob' ) 77 | // ->times( 3 ) 78 | // ->with( 79 | // Mockery::anyOf( 80 | // '9fa5c7537f8582b71028ff34b8c20dfd0f3b2a25', 81 | // '8d9b2e6fd93761211dc03abd71f4a9189d680fd0', 82 | // '2d73165945b0ccbe4932f1363457986b0ed49f19' 83 | // ), 84 | // Mockery::type( 'Writing_On_GitHub_Blob' ) 85 | // ) 86 | // ->andReturnUsing( function ( $sha, $blob ) { 87 | // return $blob; 88 | // } ); 89 | // $this->api_cache 90 | // ->shouldReceive( 'set_tree' ) 91 | // ->once() 92 | // ->with( '9108868e3800bec6763e51beb0d33e15036c3626', Mockery::type( 'Writing_On_GitHub_Tree' ) ) 93 | // ->andReturnUsing( function ( $sha, $tree ) { 94 | // return $tree; 95 | // } ); 96 | // $this->api_cache 97 | // ->shouldReceive( 'set_commit' ) 98 | // ->once() 99 | // ->with( 'db2510854e6aeab68ead26b48328b19f4bdf926e', Mockery::type( 'Writing_On_GitHub_Commit' ) ) 100 | // ->andReturnUsing( function ( $sha, $commit ) { 101 | // return $commit; 102 | // } ); 103 | 104 | // $this->assertInstanceOf( 'Writing_On_GitHub_Commit', $master = $this->fetch->master() ); 105 | 106 | // /** 107 | // * Validate the commit's api data mapped correctly. 108 | // */ 109 | // $this->assertSame( '7497c0574b9430ff5e5521b4572b7452ea36a056', $master->sha() ); 110 | // $this->assertSame( 'test@test.com', $master->author()->email ); 111 | // $this->assertSame( '2015-11-02T00:36:54Z', $master->author()->date ); 112 | // $this->assertSame( 'test@test.com', $master->committer()->email ); 113 | // $this->assertSame( '2015-11-02T00:36:54Z', $master->committer()->date ); 114 | // $this->assertSame( 'Initial full site export - wogh', $master->message() ); 115 | // $this->assertCount( 1, $parents = $master->parents() ); 116 | // $this->assertSame( 'db2510854e6aeab68ead26b48328b19f4bdf926e', $parents[0]->sha ); 117 | 118 | // $this->assertInstanceOf( 'Writing_On_GitHub_Tree', $tree = $master->tree() ); 119 | 120 | // /** 121 | // * Validate the tree's api data mapped correctly. 122 | // */ 123 | // $this->assertSame( '9108868e3800bec6763e51beb0d33e15036c3626', $tree->sha() ); 124 | 125 | // $this->assertCount( 3, $blobs = $tree->blobs() ); 126 | 127 | // /** 128 | // * Validate the blobs' api data mapped correctly. 129 | // */ 130 | // $blobs = $tree->blobs(); 131 | // $this->assertCount( 3, $blobs ); 132 | // foreach ( $blobs as $blob ) { 133 | // $this->assertTrue( in_array( $blob->sha(), array( 134 | // '2d73165945b0ccbe4932f1363457986b0ed49f19', 135 | // '8d9b2e6fd93761211dc03abd71f4a9189d680fd0', 136 | // '9fa5c7537f8582b71028ff34b8c20dfd0f3b2a25', 137 | // ) ) ); 138 | // $this->assertTrue( in_array( $blob->path(), array( 139 | // '_pages/sample-page.md', 140 | // '_posts/2015-11-02-hello-world.md', 141 | // 'README.md', 142 | // ) ) ); 143 | // } 144 | // } 145 | 146 | protected function malformed_repo( $repo ) { 147 | update_option( Base::REPO_OPTION_KEY, $repo ); 148 | 149 | $this->assertInstanceOf( 'WP_Error', $error = $this->fetch->tree_recursive() ); 150 | $this->assertSame( 'malformed_repository', $error->get_error_code() ); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /tests/unit/client/test-persist.php: -------------------------------------------------------------------------------- 1 | persist = new Writing_On_GitHub_Persist_Client( $this->app ); 12 | // $this->commit 13 | // ->shouldReceive( 'tree' ) 14 | // ->andReturn( $this->tree ); 15 | } 16 | 17 | public function test_should_delete_file() { 18 | // $this->tree->shouldReceive(); 19 | 20 | $this->set_delete_contents( true, '_posts_test.md' ); 21 | $this->persist->delete_file( '_posts_test.md', 'mysha', 'this is message for delete file' ); 22 | } 23 | 24 | // public function test_should_not_add_commit_if_no_change() { 25 | // $this->tree 26 | // ->shouldReceive( 'is_changed' ) 27 | // ->once() 28 | // ->andReturn( false ); 29 | 30 | // $this->assertWPError( $error = $this->persist->commit( $this->commit ) ); 31 | // $this->assertSame( 'no_commit', $error->get_error_code() ); 32 | // } 33 | 34 | // public function test_should_fail_if_create_tree_fails() { 35 | // $this->tree 36 | // ->shouldReceive( 'is_changed' ) 37 | // ->once() 38 | // ->andReturn( true ); 39 | // $this->tree 40 | // ->shouldReceive( 'to_body' ) 41 | // ->once() 42 | // ->andReturn( 43 | // array( 44 | // 'tree' => array( 45 | // array( 46 | // 'path' => '_posts/2015-10-23-new-post.md', 47 | // 'type' => 'blob', 48 | // 'content' => 'Post content 1', 49 | // 'mode' => '100644', 50 | // ) 51 | // ) 52 | // ) 53 | // ); 54 | // $this->set_post_trees( false ); 55 | 56 | // $this->assertWPError( $error = $this->persist->commit( $this->commit ) ); 57 | // $this->assertSame( '404_not_found', $error->get_error_code() ); 58 | // } 59 | 60 | // public function test_should_fail_if_create_commit_fails() { 61 | // $this->tree 62 | // ->shouldReceive( 'is_changed' ) 63 | // ->once() 64 | // ->andReturn( true ); 65 | // $this->tree 66 | // ->shouldReceive( 'to_body' ) 67 | // ->once() 68 | // ->andReturn( 69 | // array( 70 | // 'tree' => array( 71 | // array( 72 | // 'path' => '_posts/2015-10-23-new-post.md', 73 | // 'type' => 'blob', 74 | // 'content' => 'Post content 1', 75 | // 'mode' => '100644', 76 | // ) 77 | // ) 78 | // ) 79 | // ); 80 | // $this->tree 81 | // ->shouldReceive( 'set_sha' ) 82 | // ->once() 83 | // ->with( 'c0d2cb90b51826096c826b61a0e74d2c973d7ad8' ); 84 | // $this->commit 85 | // ->shouldReceive( 'to_body' ) 86 | // ->once() 87 | // ->andReturn( array( 88 | // 'tree' => 'c0d2cb90b51826096c826b61a0e74d2c973d7ad8', 89 | // 'message' => 'Commit message', 90 | // 'parents' => array( 'c0d2cb90b51826096c826b61a0e74d2c973d7ad8' ) 91 | // ) ); 92 | // $this->set_post_trees( true ); 93 | // $this->set_post_commits( false ); 94 | 95 | // $this->assertWPError( $error = $this->persist->commit( $this->commit ) ); 96 | // $this->assertSame( '404_not_found', $error->get_error_code() ); 97 | // } 98 | 99 | // public function test_should_fail_if_update_master_fails() { 100 | // $this->tree 101 | // ->shouldReceive( 'is_changed' ) 102 | // ->once() 103 | // ->andReturn( true ); 104 | // $this->tree 105 | // ->shouldReceive( 'to_body' ) 106 | // ->once() 107 | // ->andReturn( 108 | // array( 109 | // 'tree' => array( 110 | // array( 111 | // 'path' => '_posts/2015-10-23-new-post.md', 112 | // 'type' => 'blob', 113 | // 'content' => 'Post content 1', 114 | // 'mode' => '100644', 115 | // ) 116 | // ) 117 | // ) 118 | // ); 119 | // $this->tree 120 | // ->shouldReceive( 'set_sha' ) 121 | // ->once() 122 | // ->with( 'c0d2cb90b51826096c826b61a0e74d2c973d7ad8' ); 123 | // $this->commit 124 | // ->shouldReceive( 'to_body' ) 125 | // ->once() 126 | // ->andReturn( array( 127 | // 'tree' => 'c0d2cb90b51826096c826b61a0e74d2c973d7ad8', 128 | // 'message' => 'Commit message', 129 | // 'parents' => array( 'c0d2cb90b51826096c826b61a0e74d2c973d7ad8' ) 130 | // ) ); 131 | // $this->set_post_trees( true ); 132 | // $this->set_post_commits( true ); 133 | // $this->set_patch_refs_heads_master( false ); 134 | 135 | // $this->assertWPError( $error = $this->persist->commit( $this->commit ) ); 136 | // $this->assertSame( '404_not_found', $error->get_error_code() ); 137 | // } 138 | 139 | // public function test_should_create_anonymous_commit() { 140 | // $this->tree 141 | // ->shouldReceive( 'is_changed' ) 142 | // ->once() 143 | // ->andReturn( true ); 144 | // $this->tree 145 | // ->shouldReceive( 'to_body' ) 146 | // ->once() 147 | // ->andReturn( 148 | // array( 149 | // 'tree' => array( 150 | // array( 151 | // 'path' => '_posts/2015-10-23-new-post.md', 152 | // 'type' => 'blob', 153 | // 'content' => 'Post content 1', 154 | // 'mode' => '100644', 155 | // ) 156 | // ) 157 | // ) 158 | // ); 159 | // $this->tree 160 | // ->shouldReceive( 'set_sha' ) 161 | // ->once() 162 | // ->with( 'c0d2cb90b51826096c826b61a0e74d2c973d7ad8' ); 163 | // $this->commit 164 | // ->shouldReceive( 'to_body' ) 165 | // ->once() 166 | // ->andReturn( array( 167 | // 'tree' => 'c0d2cb90b51826096c826b61a0e74d2c973d7ad8', 168 | // 'message' => 'Commit message', 169 | // 'parents' => array( 'c0d2cb90b51826096c826b61a0e74d2c973d7ad8' ) 170 | // ) ); 171 | // $this->set_post_trees( true ); 172 | // $this->set_post_commits( true ); 173 | // $this->set_patch_refs_heads_master( true ); 174 | 175 | // $this->assertTrue( $this->persist->commit( $this->commit ) ); 176 | // } 177 | 178 | // public function test_should_create_authored_commit() { 179 | // $this->tree 180 | // ->shouldReceive( 'is_changed' ) 181 | // ->once() 182 | // ->andReturn( true ); 183 | // $this->tree 184 | // ->shouldReceive( 'to_body' ) 185 | // ->once() 186 | // ->andReturn( 187 | // array( 188 | // 'tree' => array( 189 | // array( 190 | // 'path' => '_posts/2015-10-23-new-post.md', 191 | // 'type' => 'blob', 192 | // 'content' => 'Post content 1', 193 | // 'mode' => '100644', 194 | // ) 195 | // ) 196 | // ) 197 | // ); 198 | // $this->tree 199 | // ->shouldReceive( 'set_sha' ) 200 | // ->once() 201 | // ->with( 'c0d2cb90b51826096c826b61a0e74d2c973d7ad8' ); 202 | // $this->commit 203 | // ->shouldReceive( 'to_body' ) 204 | // ->once() 205 | // ->andReturn( array( 206 | // 'tree' => 'c0d2cb90b51826096c826b61a0e74d2c973d7ad8', 207 | // 'message' => 'Commit message', 208 | // 'parents' => array( 'c0d2cb90b51826096c826b61a0e74d2c973d7ad8' ) 209 | // ) ); 210 | // $this->set_post_trees( true ); 211 | // update_option( '_wogh_export_user_id', $this->factory->user->create( array( 212 | // 'display_name' => 'James DiGioia', 213 | // 'user_email' => 'jamesorodig@gmail.com', 214 | // ) ) ); 215 | // $this->set_post_commits( true, false ); 216 | // $this->set_patch_refs_heads_master( true ); 217 | 218 | // $this->assertTrue( $this->persist->commit( $this->commit ) ); 219 | // } 220 | } 221 | -------------------------------------------------------------------------------- /views/checkbox-setting-field.php: -------------------------------------------------------------------------------- 1 | 8 | 9 | /> 10 |

11 | -------------------------------------------------------------------------------- /views/options.php: -------------------------------------------------------------------------------- 1 | 8 |
9 |

10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 29 | 30 | 31 | 32 | 40 | 41 |
?action=wogh_push_request
22 |

23 | 24 |

25 |

26 | 27 |

28 |
33 |

34 | 35 |

36 |

37 | 38 |

39 |
42 | 43 |
44 |
45 | -------------------------------------------------------------------------------- /views/setting-field.php: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 |

11 | -------------------------------------------------------------------------------- /views/textarea-setting-field.php: -------------------------------------------------------------------------------- 1 | 8 | 9 | 12 |

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

10 | -------------------------------------------------------------------------------- /writing-on-github.php: -------------------------------------------------------------------------------- 1 | admin = new Writing_On_GitHub_Admin( plugin_basename( __FILE__ ) ); 112 | } 113 | 114 | $this->controller = new Writing_On_GitHub_Controller( $this ); 115 | 116 | if ( defined( 'WP_CLI' ) && WP_CLI ) { 117 | WP_CLI::add_command( 'wogh', $this->cli() ); 118 | } 119 | } 120 | 121 | /** 122 | * Attaches the plugin's hooks into WordPress. 123 | */ 124 | public function boot() { 125 | register_activation_hook( __FILE__, array( $this, 'activate' ) ); 126 | add_action( 'admin_notices', array( $this, 'activation_notice' ) ); 127 | 128 | add_action( 'init', array( $this, 'l10n' ) ); 129 | 130 | // Controller actions. 131 | add_action( 'save_post', array( $this->controller, 'export_post' ) ); 132 | add_action( 'delete_post', array( $this->controller, 'delete_post' ) ); 133 | add_action( 'wp_ajax_nopriv_wogh_push_request', array( $this->controller, 'pull_posts' ) ); 134 | add_action( 'wogh_export', array( $this->controller, 'export_all' ), 10, 2 ); 135 | add_action( 'wogh_import', array( $this->controller, 'import_master' ), 10, 2 ); 136 | add_filter( 'get_edit_post_link', array( $this, 'edit_post_link' ), 10, 3 ); 137 | 138 | // add_filter( 'wogh_post_meta', array( $this, 'ignore_post_meta' ), 10, 1 ); 139 | // add_filter( 'wogh_pre_import_meta', array( $this, 'ignore_post_meta' ), 10, 1 ); 140 | add_filter( 'the_content', array( $this, 'the_content' ) ); 141 | 142 | do_action( 'wogh_boot', $this ); 143 | } 144 | 145 | public function edit_post_link($link, $postID, $context) { 146 | if ( ! wp_is_post_revision( $postID ) ) { 147 | $post = new Writing_On_GitHub_Post( $postID, Writing_On_GitHub::$instance->api() ); 148 | if ( $post->is_on_github() ) { 149 | return $post->github_edit_url(); 150 | } 151 | } 152 | 153 | return $link; 154 | } 155 | 156 | public function ignore_post_meta($meta) { 157 | $ignore_meta_keys = get_option('wogh_ignore_metas'); 158 | if (empty($ignore_meta_keys)) { 159 | return $meta; 160 | } 161 | 162 | $keys = preg_split("/\\r\\n|\\r|\\n/", $ignore_meta_keys); 163 | if (empty($keys)) { 164 | return $meta; 165 | } 166 | foreach ($keys as $key => $value) { 167 | $keys[$key] = trim($value); 168 | } 169 | 170 | foreach ($meta as $key => $value) { 171 | if (in_array($key, $keys)) { 172 | unset($meta[$key]); 173 | } 174 | } 175 | 176 | return $meta; 177 | } 178 | 179 | public function the_content($content) { 180 | $arr = wp_upload_dir(); 181 | $baseurl = $arr['baseurl'] . '/writing-on-github'; 182 | $basedir = $arr['basedir'] . '/writing-on-github'; 183 | 184 | $content = preg_replace_callback( 185 | '/(]*?src=[\'"])\S*?(\/images\/\S+)([\'"].*?>)/', 186 | function($matchs) use ($baseurl, $basedir) { 187 | if (is_file($basedir . $matchs[2])) { 188 | $url = $baseurl . $matchs[2]; 189 | return "${matchs[1]}$url${matchs[3]}"; 190 | } 191 | return "${matchs[0]}"; 192 | }, 193 | $content 194 | ); 195 | 196 | $content = preg_replace_callback( 197 | '/(]*?href=[\'"])\S*?(\/images\/S+)\s*([\'"].*?>)/', 198 | function($matchs) use ($baseurl, $basedir) { 199 | if (is_file($basedir . $matchs[2])) { 200 | $url = $baseurl . $matchs[2]; 201 | return "${matchs[1]}$url${matchs[3]}"; 202 | } 203 | return "${matchs[0]}"; 204 | }, 205 | $content 206 | ); 207 | return $content; 208 | } 209 | 210 | /** 211 | * Init i18n files 212 | */ 213 | public function l10n() { 214 | load_plugin_textdomain( self::$text_domain ); 215 | } 216 | 217 | /** 218 | * Sets and kicks off the export cronjob 219 | */ 220 | public function start_export( $force = false ) { 221 | $this->start_cron( 'export', $force ); 222 | } 223 | 224 | /** 225 | * Sets and kicks off the import cronjob 226 | */ 227 | public function start_import( $force = false ) { 228 | $this->start_cron( 'import', $force ); 229 | } 230 | 231 | /** 232 | * Enables the admin notice on initial activation 233 | */ 234 | public function activate() { 235 | if ( 'yes' !== get_option( '_wogh_fully_exported' ) ) { 236 | set_transient( '_wogh_activated', 'yes' ); 237 | } 238 | } 239 | 240 | /** 241 | * Displays the activation admin notice 242 | */ 243 | public function activation_notice() { 244 | if ( ! get_transient( '_wogh_activated' ) ) { 245 | return; 246 | } 247 | 248 | delete_transient( '_wogh_activated' ); 249 | 250 | ?>
251 |

252 | settings and click "Export to GitHub."', 'writing-on-github' ), 255 | admin_url( 'options-general.php?page=' . static::$text_domain) 256 | ); 257 | ?> 258 |

259 |
controller; 269 | } 270 | 271 | /** 272 | * Lazy-load the CLI object. 273 | * 274 | * @return Writing_On_GitHub_CLI 275 | */ 276 | public function cli() { 277 | if ( ! $this->cli ) { 278 | $this->cli = new Writing_On_GitHub_CLI; 279 | } 280 | 281 | return $this->cli; 282 | } 283 | 284 | /** 285 | * Lazy-load the Request object. 286 | * 287 | * @return Writing_On_GitHub_Request 288 | */ 289 | public function request() { 290 | if ( ! $this->request ) { 291 | $this->request = new Writing_On_GitHub_Request( $this ); 292 | } 293 | 294 | return $this->request; 295 | } 296 | 297 | /** 298 | * Lazy-load the Response object. 299 | * 300 | * @return Writing_On_GitHub_Response 301 | */ 302 | public function response() { 303 | if ( ! $this->response ) { 304 | $this->response = new Writing_On_GitHub_Response( $this ); 305 | } 306 | 307 | return $this->response; 308 | } 309 | 310 | /** 311 | * Lazy-load the Api object. 312 | * 313 | * @return Writing_On_GitHub_Api 314 | */ 315 | public function api() { 316 | if ( ! $this->api ) { 317 | $this->api = new Writing_On_GitHub_Api( $this ); 318 | } 319 | 320 | return $this->api; 321 | } 322 | 323 | /** 324 | * Lazy-load the Import object. 325 | * 326 | * @return Writing_On_GitHub_Import 327 | */ 328 | public function import() { 329 | if ( ! $this->import ) { 330 | $this->import = new Writing_On_GitHub_Import( $this ); 331 | } 332 | 333 | return $this->import; 334 | } 335 | 336 | /** 337 | * Lazy-load the Export object. 338 | * 339 | * @return Writing_On_GitHub_Export 340 | */ 341 | public function export() { 342 | if ( ! $this->export ) { 343 | $this->export = new Writing_On_GitHub_Export( $this ); 344 | } 345 | 346 | return $this->export; 347 | } 348 | 349 | /** 350 | * Lazy-load the Semaphore object. 351 | * 352 | * @return Writing_On_GitHub_Semaphore 353 | */ 354 | public function semaphore() { 355 | if ( ! $this->semaphore ) { 356 | $this->semaphore = new Writing_On_GitHub_Semaphore; 357 | } 358 | 359 | return $this->semaphore; 360 | } 361 | 362 | /** 363 | * Lazy-load the Database object. 364 | * 365 | * @return Writing_On_GitHub_Database 366 | */ 367 | public function database() { 368 | if ( ! $this->database ) { 369 | $this->database = new Writing_On_GitHub_Database( $this ); 370 | } 371 | 372 | return $this->database; 373 | } 374 | 375 | /** 376 | * Print to WP_CLI if in CLI environment or 377 | * write to debug.log if WP_DEBUG is enabled 378 | * @source http://www.stumiller.me/sending-output-to-the-wordpress-debug-log/ 379 | * 380 | * @param mixed $msg 381 | * @param string $write 382 | */ 383 | public static function write_log( $msg, $write = 'line' ) { 384 | if ( defined( 'WP_CLI' ) && WP_CLI ) { 385 | if ( is_array( $msg ) || is_object( $msg ) ) { 386 | WP_CLI::print_value( $msg ); 387 | } else { 388 | WP_CLI::$write( $msg ); 389 | } 390 | } elseif ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { 391 | if ( is_array( $msg ) || is_object( $msg ) ) { 392 | error_log( print_r( $msg, true ) ); 393 | } else { 394 | error_log( $msg ); 395 | } 396 | } 397 | } 398 | 399 | /** 400 | * Kicks of an import or export cronjob. 401 | * 402 | * @param bool $force 403 | * @param string $type 404 | */ 405 | protected function start_cron( $type, $force = false ) { 406 | update_option( '_wogh_' . $type . '_started', 'yes' ); 407 | $user_id = get_current_user_id(); 408 | wp_schedule_single_event( time(), 'wogh_' . $type . '', array( $user_id, $force ) ); 409 | spawn_cron(); 410 | } 411 | } 412 | --------------------------------------------------------------------------------