Commit 9a8ebff2 authored by Administrator's avatar Administrator 💬

Let's fool around with git-sync!

parent f149fcc1
Pipeline #26 passed with stage
in 1 minute and 34 seconds
{
"env": {
"browser": true,
"node": true
},
"parser": "babel-eslint",
"parserOptions": {
"ecmaVersion": 7,
"sourceType": "module"
},
"rules": {
"accessor-pairs": 2,
"array-bracket-spacing": 0,
"block-scoped-var": 0,
"brace-style": [2, "1tbs", { "allowSingleLine": true }],
"camelcase": 0,
"comma-dangle": [2, "never"],
"comma-spacing": [2, { "before": false, "after": true }],
"comma-style": [2, "last"],
"complexity": 0,
"computed-property-spacing": 0,
"consistent-return": 0,
"consistent-this": 0,
"constructor-super": 2,
"curly": [2, "multi-line"],
"default-case": 0,
"dot-location": [2, "property"],
"dot-notation": 0,
"eol-last": 2,
"eqeqeq": [2, "allow-null"],
"func-names": 0,
"func-style": 0,
"generator-star-spacing": [2, { "before": true, "after": true }],
"guard-for-in": 0,
"handle-callback-err": [2, "^(err|error)$" ],
"indent": [2, 4, { "SwitchCase": 1 }],
"key-spacing": [2, { "beforeColon": false, "afterColon": true }],
"linebreak-style": 0,
"lines-around-comment": 0,
"max-nested-callbacks": 0,
"new-cap": [2, { "newIsCap": true, "capIsNew": false }],
"new-parens": 2,
"newline-after-var": 0,
"no-alert": 0,
"no-array-constructor": 2,
"no-caller": 2,
"no-catch-shadow": 0,
"no-cond-assign": 2,
"no-console": 0,
"no-constant-condition": 0,
"no-continue": 0,
"no-control-regex": 2,
"no-debugger": 2,
"no-delete-var": 2,
"no-div-regex": 0,
"no-dupe-args": 2,
"no-dupe-keys": 2,
"no-duplicate-case": 2,
"no-else-return": 0,
"no-empty": 0,
"no-empty-character-class": 2,
"no-eq-null": 0,
"no-eval": 2,
"no-ex-assign": 2,
"no-extend-native": 2,
"no-extra-bind": 2,
"no-extra-boolean-cast": 2,
"no-extra-parens": 0,
"no-extra-semi": 0,
"no-fallthrough": 2,
"no-floating-decimal": 2,
"no-func-assign": 2,
"no-implied-eval": 2,
"no-inline-comments": 0,
"no-inner-declarations": [2, "functions"],
"no-invalid-regexp": 2,
"no-irregular-whitespace": 2,
"no-iterator": 2,
"no-label-var": 2,
"no-labels": 2,
"no-lone-blocks": 2,
"no-lonely-if": 0,
"no-loop-func": 0,
"no-mixed-requires": 0,
"no-mixed-spaces-and-tabs": 2,
"no-multi-spaces": 2,
"no-multi-str": 2,
"no-multiple-empty-lines": [2, { "max": 1 }],
"no-native-reassign": 2,
"no-negated-in-lhs": 2,
"no-nested-ternary": 0,
"no-new": 2,
"no-new-func": 0,
"no-new-object": 2,
"no-new-require": 2,
"no-new-wrappers": 2,
"no-obj-calls": 2,
"no-octal": 2,
"no-octal-escape": 2,
"no-param-reassign": 0,
"no-path-concat": 0,
"no-process-env": 0,
"no-process-exit": 0,
"no-proto": 0,
"no-redeclare": 2,
"no-regex-spaces": 2,
"no-restricted-modules": 0,
"no-return-assign": 2,
"no-script-url": 0,
"no-self-compare": 2,
"no-sequences": 2,
"no-shadow": 0,
"no-shadow-restricted-names": 2,
"no-spaced-func": 2,
"no-sparse-arrays": 2,
"no-sync": 0,
"no-ternary": 0,
"no-this-before-super": 2,
"no-throw-literal": 2,
"no-trailing-spaces": 2,
"no-undef": 2,
"no-undef-init": 2,
"no-undefined": 0,
"no-underscore-dangle": 0,
"no-unexpected-multiline": 2,
"no-unneeded-ternary": 2,
"no-unreachable": 2,
"no-unused-expressions": 0,
"no-unused-vars": [2, { "vars": "all", "args": "none" }],
"no-use-before-define": 0,
"no-var": 0,
"no-void": 0,
"no-warning-comments": 0,
"no-with": 2,
"object-curly-spacing": 0,
"object-shorthand": 0,
"one-var": [2, { "initialized": "never" }],
"operator-assignment": 0,
"operator-linebreak": [2, "after", { "overrides": { "?": "before", ":": "before" } }],
"padded-blocks": 0,
"prefer-const": 0,
"quote-props": 0,
"quotes": [2, "single", "avoid-escape"],
"radix": 2,
"semi": [2, "always"],
"semi-spacing": 0,
"sort-vars": 0,
"keyword-spacing": [2, {"after": true, "overrides": {"throw": { "after": true}, "return": { "before": true }}}],
"space-before-blocks": [2, "always"],
"space-before-function-paren": [2, "never"],
"space-in-parens": [2, "never"],
"space-infix-ops": 2,
"space-unary-ops": [2, { "words": true, "nonwords": false }],
"spaced-comment": [2, "always", { "markers": ["global", "globals", "eslint", "eslint-disable", "*package", "!"] }],
"strict": 0,
"use-isnan": 2,
"valid-jsdoc": 0,
"valid-typeof": 2,
"vars-on-top": 0,
"wrap-iife": [2, "any"],
"wrap-regex": 0,
"yoda": [2, "never"]
}
}
node_modules
*.js.map
*.css.map
/.idea
# v2.0.3
## 03/07/2019
1. [](#bugifx)
* Properly fallback to config message if not there yet (#134)
# v2.0.2
## 02/21/2019
1. [](#improved)
* Fixed InitCommand spelling (#132, thanks @alex-mohemian)
1. [](#bugfix)
* Fixed PHP 5.6 incompatibility introduced by latest release.
# v2.0.1
## 02/19/2019
1. [](#new)
* Added new `init` CLI command (`bin/plugin git-sync init`) (#128, thanks @LeonRyan and @alex-mohemian)
1. [](#improved)
* Allow setting a personalised commit message (#123, thanks @kyed)
* Added better directions for Azure + IIS users for the Git Binary
1. [](#bugfix)
* Fixed `LC_ALL` to use `C` instead of en_US.UTF-8`, to be more flexible (#124, #125, thanks @lambopedia)
# v2.0.0
## 10/15/2018
1. [](#new)
* Added support for new awesome Grav 1.6 Scheduler
* Added logic to display custom nested folders in wizard
* Other than `pages`, it is now possible to enable `config`, `data`, `plugins` and `themes` for synchronization. You can also add any custom folder you have in your `user` (#4, #21, #34, #58, #63, #83)
* Allow users with `admin.pages` permissions to synchronize through quick tray (#79, thanks @apfrod)
* When using Grav as committer, the user email will be now used for the commit (#81, thanks @apfrod)
* Added support for Webhook Secret (Bitbucket does not yet support them) (#72, #73, thanks @pathmissing)
* Added options to turn automatic synchronization on/off with page saves, delete and media changes (#105, thanks @AmauryCarrade)
1. [](#improved)
* Fixed alignment of the git icon in the Wizard (#115)
* Prevent Wizard modal to get canceled when clicking on the overlay background (#115)
* Quick tray icon is now smarter. If GitSync has not been initialized yet, it will take you straight to wizard, otherwise it would perform a synchronization (#115)
* Rearranged blueprint order (thanks @paulhibbitts)
* GitLab: Updated wizard instructions to be inline with the new GitLab UI (#90)
* Tweaked alignment of links in the wizard (#57)
* Properly support local branches that aren't `master` (#56)
* Allow to specify custom local_repository (default, `USER_DIR`) (#95, thanks @Hydraner, also #54, #33, #25)
* Webhook URL is now more robust and secure, by default it is generated with a random value
* Git icon from Admin has been replaced to use the `git` text icon instead of the logo
* Prevent next step if Step 1 and Step 2 are not filled in (#92)
* Added notice in Step 2 explaning what GitSync expect from the repository structure (#92)
1. [](#bugfix)
* Fixed issue where on first initialization the checkout process would error out
* Fixed issue with Pages save.
* Fixed JS error in plugins list
* Fixed nested folders not synchronizing
* Fixed issue where Wizard wouldn't work in case the `admin` path was modified (#27, #94, #77, thanks @pathmissing)
* Fixed webhook generated URL when multi-lang active (#71)
* Resolved issue with untracked/uncommited files at the root of the `sync` folder. (#101, thanks @ScottHamper)
# v1.0.4
## 08/16/2017
1. [](#new)
* CLI: Added `status` command to check config and git (#52, thanks @karfau)
* Allow local branches to be named differently than the remote branches (#48, thanks @denniswebb)
* Added support for new Admin Navigation Tray
1. [](#bugfix)
* Fixed minimum Git required version to support `--all` (#32,#49, thanks @redrohX)
# v1.0.3
## 02/21/2017
1. [](#bugfix)
* Fixed issue with new 'author' option that could trigger errors when settings were not saved. (#23)
* Fixed the 'More Details' button triggering the Modal to close instead of just expanding the details
# v1.0.2
## 02/18/2017
1. [](#new)
* It is now possible to change the committer name. You can choose between Git User, GitSync Committer Name, Grav User Name, Grav User Fullname (#14).
2. [](#improved)
* Added more documentation and description about the support of 2FA and Access Tokens (#16, #19, thanks @OleVik)
* Added 4th Generic Git choice in the wizard for self-hosted and custom git services (Gogs/Gitea) (#7 - #22 - thanks @erlepereira)
1. [](#bugfix)
* Fixed issue preventing the custom Git Binary Path from getting used (#15)
* Fixed issue with Webhook auto-generated URL where it would display double slashes in case of root domain (#15)
* Fixed issue with the modal not properly restoring the tutorial steps of the active selected service
# v1.0.1
## 01/29/2017
1. [](#bugfix)
* Changed default GitSync email for commits
# v1.0.0
## 01/25/2017
1. [](#new)
* Released plugin to stable GPM channel
# v1.0.0-rc.3
## 01/19/2017
1. [](#new)
* Added logger setting to log Git command executions
1. [](#improved)
* Improved Windows compatibility
# v1.0.0-rc.2
## 01/16/2017
1. [](#new)
* Allow to change the path for the `git` binary (#1)
* Added CLI for synchronizing `bin/plugin git-sync sync` (#2)
* More security: Git password will now get encrypted and won't load in admin
1. [](#improved)
* Wizard: Improved Bitbucket explanation about stripping out `user@` from the copied HTTPS url (#3)
1. [](#bugfix)
* Fixed potential issue when retrieving the currently installed git version
* Fixed issue that would not properly hide the password from error messages if the password contained special chars
* Fixed issue preventing the plugin to properly get setup the very first time and causing 401 error (#4)
* Workaround for error thrown when removing the plugin
# v1.0.0-rc.1
## 12/19/2016
1. [](#new)
* Initial Release
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
![](images/gitsync-logo.png)
**Git Sync** is a Plugin for [Grav CMS](http://github.com/getgrav/grav) that allows to seamlessly synchronize a Git repository with your Grav site, and vice-versa.
Git Sync captures any change that you make on your site and instantly updates your git repository. In the same way, Git Sync supports _webhooks_, allowing to automatically synchronize your site if the repository changes.
Thanks to this powerful bi-directional flow, Git Sync can now turn your site into a collaborative environment where the source of truth is always your git repository and unlimited collaborators and sites can share and contribute to the same content.
## Videos: Setup and Demo
| Up and Running in 2 mins | 2-way Sync Demonstration |
| ------------ | ----------------- |
| [![Up and Running in 2 mins](https://img.youtube.com/vi/avcGP0FAzB8/0.jpg)](https://www.youtube.com/watch?v=avcGP0FAzB8) | [![2-way Sync Demonstration](https://img.youtube.com/vi/3fy78afacyw/0.jpg)](https://www.youtube.com/watch?v=3fy78afacyw) |
## Installation using the GPM (Grav Package Manager)
To install git-sync simply run this command from the Grav root folder
```
bin/gpm install git-sync
```
After having installed the plugin, make sure to go in the plugin settings in order to get the Wizard configuration started.
## Features
<img src="wizard.png" width="500" />
* Easy step-by-step Wizard setup will guide you through a detailed process for setting things up
* Supported hosting services: [GitHub](https://github.com), [BitBucket](https://bitbucket.org), [GitLab](https://gitlab.com) as well as any self-hosted and git service with webhooks support.
* Private repositories
* Synchronize any folder under `user` (pages, themes, config)
* 2FA (Two-Factor Authentication) and Access Token support
* Webhooks support allow for automatic synchronization from the Git Repository with secure Webhook URL auto-generated and support for Webhook Secret (when available)
* Automatically handles simple merges behind the scenes
* Easy one-click button to reset your local changes and restores it to the actual state of the git repository
* Easy one-click button to manually synchronize
* Support for Admin Quick Tray so you can synchronize from anywhere in Admin
* Ability to customize whether GitSync should synchronize upon save or just manually
* Customize the Committer Name, choose between Git User, GitSync Commiter Name, Grav User Name and Grav user Fullname
* With the built-in Form Process action `gitsync`, you can trigger the synchronization anytime someone submits a post.
* Any 3rd party plugin can integrate with Git Sync and trigger the synchronization through the `gitsync` event.
* Built-in CLI command to automate synchronizations
* Log any command performed by GitSync to ensure everything runs smoothly or debug potential issues
# Command Line Interface
Git Sync comes with a CLI that allows to run synchronizations right within your terminal. This feature is extremely useful in case you'd like to run an autonomous periodic crontab job to synchronize with your repository.
To execute the command simply run:
```bash
bin/plugin git-sync sync
```
# Requirements
In order for the plugin to work, the server needs to run `git` 1.7.1 and above.
The PHP `exec()` and `escapeshellarg()` functions are required to be enabled.
# Sponsored by
This plugin could not have been realized without the sponsorship of [Hibbitts Design](http://www.hibbittsdesign.org/blog/) and the development of [Trilby Media](http://trilby.media).
import Settings from 'git-sync';
import request from 'admin/utils/request';
import toastr from 'admin/utils/toastr';
import { config } from 'grav-config';
import $ from 'jquery';
const WIZARD = $('[data-remodal-id="wizard"]');
const RESET_LOCAL = $('[data-remodal-id="reset-local"]');
const SERVICES = { 'github': 'github.com', 'bitbucket': 'bitbucket.org', 'gitlab': 'gitlab.com', 'allothers': 'allothers.repo' };
const TEMPLATES = {
REPO_URL: 'https://{placeholder}/getgrav/grav.git'
};
const openWizard = () => {
const modal = WIZARD.remodal({ closeOnConfirm: false });
const previous = WIZARD.find('[data-gitsync-action="previous"]');
const next = WIZARD.find('[data-gitsync-action="next"]');
const save = WIZARD.find('[data-gitsync-action="save"]');
STEP = 0;
WIZARD.find(`form > [class^=step-]:not(.step-${STEP}) > .panel`).hide().removeClass('hidden');
WIZARD.find(`form > [class="step-${STEP}"] > .panel`).show();
next.removeClass('hidden');
previous.addClass('hidden');
save.addClass('hidden');
const webhook = $('[name="data[webhook]"]').val();
const webhook_secret = $('[name="data[webhook_secret]"]').val();
$('[name="gitsync[repository]"]').trigger('change');
$('[name="gitsync[webhook]"]').val(webhook);
$('[name="gitsync[webhook_secret]"]').val(webhook_secret);
$('.gitsync-webhook').text(webhook);
modal.open();
};
const disableButton = (next) => {
next
.attr('disabled', 'disabled')
.addClass('hint--top');
};
const enableButton = (next) => {
next
.attr('disabled', null)
.removeClass('hint--top');
};
let STEP = 0;
let STEPS = 0;
let SERVICE = null;
$(document).on('closed', WIZARD, function(e) {
STEP = 0;
});
$(document).on('click', '[data-gitsync-useraction]', (event) => {
event.preventDefault();
const target = $(event.target).closest('[data-gitsync-useraction]');
const action = target.data('gitsyncUseraction');
const URI = `${config.current_url}.json`;
switch (action) {
case 'wizard':
openWizard();
break;
case 'sync':
const relativeURI = target.data('gitsync-uri');
target.find('i').removeClass('fa-cloud fa-git').addClass('fa-circle-o-notch fa-spin');
request(relativeURI || URI, {
method: 'post',
body: { task: 'synchronize' }
}, () => {
target.find('i').removeClass('fa-circle-o-notch fa-spin').addClass(relativeURI ? 'fa-git' : 'fa-cloud');
});
break;
case 'reset':
const modal = RESET_LOCAL.remodal({ closeOnConfirm: false });
modal.open();
if (!RESET_LOCAL.data('_reset_event_set_')) {
RESET_LOCAL.find('[data-gitsync-action="reset-local"]').one('click', () => {
modal.close();
RESET_LOCAL.data('_reset_event_set_', true);
target.find('i').removeClass('fa-history').addClass('fa-circle-o-notch fa-spin');
request(URI, {
method: 'post',
body: { task: 'resetlocal' }
}, () => {
RESET_LOCAL.data('_reset_event_set_', false);
target.find('i').removeClass('fa-circle-o-notch fa-spin').addClass('fa-history');
});
});
}
break;
}
});
$(document).on('click', '[data-gitsync-action]', (event) => {
event.preventDefault();
const target = $(event.target).closest('[data-gitsync-action]');
const previous = WIZARD.find('[data-gitsync-action="previous"]');
const next = WIZARD.find('[data-gitsync-action="next"]');
const save = WIZARD.find('[data-gitsync-action="save"]');
const action = target.data('gitsyncAction');
const user = $('[name="gitsync[repo_user]"]').val();
const password = $('[name="gitsync[repo_password]"]').val();
const repository = $('[name="gitsync[repo_url]"]').val();
const webhook = $('[name="gitsync[webhook]"]').val();
const webhook_enabled = $('[name="gitsync[webhook_enabled]"]').is(':checked');
const webhook_secret = $('[name="gitsync[webhook_secret]"]').val();
if (target.attr('disabled')) {
return;
}
let error = [];
if (!user) {
error.push('Username is missing.');
}
/*
if (!password) {
error.push('Password is missing.');
}
*/
if (!repository) {
error.push('Repository is missing.');
}
if (['save', 'test'].includes(action)) {
if (error.length) {
toastr.error(error.join('<br />'));
return false;
}
}
if (action === 'save') {
const folders = $('[name="gitsync[folders]"]:checked').map((i, item) => item.value);
$('[name="data[repository]"]').val(repository);
$('[name="data[user]"]').val(user);
$('[name="data[password]"]').val(password);
$('[name="data[webhook]"]').val(webhook);
$(`[name="data[webhook_enabled]"][value="${webhook_enabled ? 1 : 0}"]`).prop('checked', true);
$('[name="data[webhook_secret]"]').val(webhook_secret);
const dataFolders = $('[name="data[folders][]"]');
if (dataFolders && dataFolders[0] && dataFolders[0].selectize) {
dataFolders[0].selectize.setValue(folders.toArray());
}
$('[name="task"][value="save"]').trigger('click');
return false;
}
if (action === 'test') {
const URI = `${config.current_url}.json`;
const test = global.btoa(JSON.stringify({ user, password, repository }));
request(URI, {
method: 'post',
body: { test, task: 'testConnection' }
});
return false;
}
WIZARD.find(`.step-${STEP} > .panel`).slideUp();
STEP += action === 'next' ? +1 : -1;
WIZARD.find(`.step-${STEP} > .panel`).slideDown();
save.addClass('hidden');
if (action === 'next') {
previous.removeClass('hidden');
}
if (STEP <= 0) {
previous.addClass('hidden');
enableButton(next);
}
if (STEP > 0) {
next.removeClass('hidden');
}
if (STEP === 1) {
const selectedRepo = $('[name="gitsync[repository]"]:checked');
if (!selectedRepo.length) {
disableButton(next);
} else {
enableButton(next);
}
}
if (STEP === 2) {
const repoURL = $('[name="gitsync[repo_url]"]').val();
if (!repoURL.length) {
disableButton(next);
} else {
enableButton(next);
}
}
if (STEP === STEPS) {
next.addClass('hidden');
previous.removeClass('hidden');
save.removeClass('hidden');
}
});
$(document).on('change', '[name="gitsync[repository]"]', () => {
enableButton(WIZARD.find('[data-gitsync-action="next"]'));
});
$(document).on('input', '[name="gitsync[repo_url]"]', (event) => {
const target = $(event.currentTarget);
const value = target.val();
const next = WIZARD.find('[data-gitsync-action="next"]');
if (value.length) {
enableButton(next);
} else {
disableButton(next);
}
});
$(document).on('keyup', '[data-gitsync-uribase] [name="gitsync[webhook]"]', (event) => {
const target = $(event.currentTarget);
const value = target.val();
$('.gitsync-webhook').text(value);
});
$(document).on('keyup', '[data-gitsync-uribase] [name="gitsync[webhook_secret]"]', (event) => {
$('[data-gitsync-uribase] [name="gitsync[webhook_enabled]"]').trigger('change');
});
$(document).on('change', '[data-gitsync-uribase] [name="gitsync[webhook_enabled]"]', (event) => {
const target = $(event.currentTarget);
const checked = target.is(':checked');
const secret = $('[name="gitsync[webhook_secret]"]').val();
target.closest('.webhook-secret-wrapper').find('label:last-child')[checked ? 'removeClass' : 'addClass']('hidden');
$('.gitsync-webhook-secret').html(!checked || !secret.length ? '<em>leave empty</em>' : `<code>${secret}</code>`);
});
$(document).on('change', '[name="gitsync[repository]"]', (event) => {
const target = $(event.target);
if (!target.is(':checked')) {
return;
}
SERVICE = target.val();
Object.keys(SERVICES).forEach((service) => {
WIZARD.find(`.hidden-step-${service}`)[service === SERVICE ? 'removeClass' : 'addClass']('hidden');
if (service === SERVICE) {
WIZARD.find('.webhook-secret-wrapper')[service === 'bitbucket' ? 'addClass' : 'removeClass']('hidden');
WIZARD
.find('input[name="gitsync[repo_url]"][placeholder]')
.attr('placeholder', TEMPLATES.REPO_URL.replace(/\{placeholder\}/, SERVICES[service]));
}
});
});
$(document).on('click', '[data-access-tokens-details]', (event) => {
event.preventDefault();
const button = $(event.currentTarget);
const panel = button.closest('.access-tokens').find('.access-tokens-details');
panel.slideToggle(250, () => {
const isVisible = panel.is(':visible');
const icon = button.find('.fa');
icon.removeClass('fa-chevron-down fa-chevron-up').addClass(`fa-chevron-${isVisible ? 'up' : 'down'}`);
});
});
const showNotices = (element) => {
const target = $(element);
const selection = target.val().replace(/\//g, '-');
const column = target.closest('.columns').find('.column:last');
column.find('[class*="description-"]').addClass('hidden');
column.find(`.description-${selection}`).removeClass('hidden').hide().fadeIn({
duration: 250
});
};
$(document).on('input', '[data-remodal-id="wizard"] .step-4 input[type="checkbox"]', (event) => {
const target = $(event.currentTarget);
if (!target.is(':checked')) {
return;
}
showNotices(target);
});
$(document).on('mouseenter', '[data-remodal-id="wizard"] .step-4 .info-desc', (event) => {
const target = $(event.currentTarget).siblings('input[type="checkbox"]');
showNotices(target);
});
$(document).on('mouseleave', '[data-remodal-id="wizard"] .step-4 label', (event) => {
const target = $(event.currentTarget);
const container = target.closest('.columns');
const column = container.find('.column:last-child');
column.find('[class*="description-"]').addClass('hidden');
});
$(document).on('mouseleave', '[data-remodal-id="wizard"] .columns .column:first-child', (event) => {
const target = $(event.currentTarget);
const column = target.siblings('.column');
column.find('[class*="description-"]').addClass('hidden');
});
$(document).ready(() => {
STEPS = WIZARD.find('[class^="step-"]').length - 1;
WIZARD.wrapInner('<form></form>');
RESET_LOCAL.wrapInner('<form></form>');
if (WIZARD.length && (Settings.first_time || !Settings.git_installed)) {
openWizard();
}
});
export default Settings;
name: Git Sync
version: 2.0.3
description: Allows to synchronize portions of Grav with Git Repositories (GitHub, BitBucket, GitLab)
icon: git
author:
name: Trilby Media, LLC
email: hello@trilby.media
url: http://trilby.media
homepage: http://trilby.media
keywords: grav, plugin, git, sync, github, bitbucket, gitlab
issues: https://github.com/trilbymedia/grav-plugin-git-sync/issues
docs: https://github.com/trilbymedia/grav-plugin-git-sync
license: MIT
dependencies:
- { name: grav, version: '>=1.5.0' }
- { name: form, version: '>=2.16.3' }
form:
validation: strict
fields:
Basic:
type: section
title: Basic Settings
underline: true
enabled:
type: toggle
label: Plugin Status
highlight: 1
default: 0
options:
1: Enabled
0: Disabled
validate:
type: bool
folders:
type: select
multiple: true
label: Folders to Sync
classes: fancy
description: Removing folders after they have been synced may cause undesired results.
default:
- pages
options:
- pages
- themes
- plugins
- config
- data
selectize:
create: true
validate:
type: commalist
Sync:
type: section
title: Automatic Synchronization Settings
underline: true
SyncNotice:
type: spacer
markdown: true
text: |
! To improve the speed of saving pages you can disable automatic sync. Then, changes to a page will not be sent to the remote repository on every save. To sync your changes to the repository tap the GitSync button (<i class="fa fa-git"></i>) in the top left of the Administration Panel, or use the below Scheduler option to add the GitSync Syncronization Job to the Scheduler (<strong>Grav 1.6 required</strong>).
sync.on_save:
type: toggle
label: Sync on Page Save
help: Sync with the remote directory when a page is saved through the admin
default: 1
highlight: 1
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
sync.on_delete:
type: toggle
label: Sync on Page Delete
help: Sync with the remote directory when a page is deleted through the admin
default: 1
highlight: 1
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
sync.on_media:
type: toggle
label: Sync on Media Changes
help: Sync with the remote directory when a media is uploaded or deleted through the admin immediately (instead of only syncing when the page is saved)
default: 1
highlight: 1
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
sync.cron_enable:
type: toggle
label: Add Sync to Scheduler
help: Add GitSync Job to the Scheduler so it can automatically perform synchronization at the given time
default: 0
highlight: 1
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
sync.cron_at:
type: cron
label: Run Sync at
help: When should the Scheduler run the automatic GitSync synchronization job
default: '0 12,23 * * *'
Repo:
type: section
title: Git Repository Settings
underline: true
local_repository:
type: hidden
multiple: false
size: medium
label: Local Repository Path
repository:
type: text
label: Git Repository
placeholder: https://github.com/user/repository.git
user:
type: text
label: Git User
placeholder: Username, not email
autocomplete: off
password:
type: enc-password
label: Git Password or Token
placeholder: Your Git Password or Token
description: Enter your password or token to encrypt and securely store it, then save the settings. It will not show up here for security reasons.
autocomplete: off
webhook:
type: text
label: Repository Web Hook URL
placeholder: /_git-sync
data-default@: '\Grav\Plugin\GitSyncPlugin::generateRandomWebhook'
webhook_enabled:
type: toggle
label: Web Hook Secret
highlight: 1
default: 0
options:
1: Enabled
0: Disabled
description: With this setting enabled, only authorized webhook calls will be able to trigger a synchronization (recommended)
webhook_secret:
type: text
label: Repository Web Hook Secret
placeholder: Your Web Hook Secret
data-default@: '\Grav\Plugin\GitSyncPlugin::generateWebhookSecret'
description: You can either use this randomly generated string or enter your own secret. <br /> **Bitbucket** does not yet support Webhook Secrets.
markdown: true
Advanced:
type: section
title: Advanced Git Settings
underline: true
branch:
type: text
default: master
label: Local Branch
placeholder: master
remote.name:
type: text
default: origin
label: Remote Name
placeholder: origin
remote.branch:
type: text
default: master
label: Remote Branch
placeholder: master
git.author:
type: select
default: gituser
label: Commits Author
options:
gituser: Use Git User Name
gitsync: Use GitSync Committer Name
gravuser: Use Grav User Name
gravfull: Use Grav User Full Name
git.message:
type: text
default: (Grav GitSync) Automatic Commit
label: Commit message
placeholder: (Grav GitSync) Automatic Commit
help: You can use {{pageTitle}} or {{pageRoute}} in your message as placeholders for the title or route of the page being saved
git.name:
type: text
default: GitSync
label: Committer Name
placeholder: GitSync
git.email:
type: text
default: git-sync@trilby.media
label: Committer Email
placeholder: git-sync@trilby.media
git.bin:
type: text
default: git
label: Git Binary Path
help: If the default `git` command doesn't work on your machine or if you want to specify a custom path, do it in here
placeholder: /usr/bin/git
logging:
type: toggle
default: 0
label: Log Git Commands
help: Logs git commands. Useful to debug and troubleshoot git execution
highlight: 0
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
Actions:
type: section
title: Actions
underline: true
_wizard:
type: git-wizard
label: Text Variable
help: Text to add to the top of a page
<?php
namespace Grav\Plugin\GitSync;
use Grav\Common\Grav;
use Grav\Common\Plugin;
use Grav\Common\Utils;
use Grav\Plugin\Admin\AdminBaseController;
class AdminController extends AdminBaseController
{
protected $action;
protected $target;
protected $active;
protected $plugin;
protected $task_prefix = 'task';
public $git;
/**
* @param Plugin $plugin
*/
public function __construct(Plugin $plugin)
{
$post = !empty($_POST) ? $_POST : [];
$this->grav = Grav::instance();
$this->active = false;
$uri = $this->grav['uri'];
$this->post = $this->getPost($post);
$this->plugin = $plugin;
// Ensure the controller should be running
if (Utils::isAdminPlugin()) {
$routeDetails = $this->grav['admin']->getRouteDetails();
$target = array_pop($routeDetails);
$this->git = new GitSync($plugin);
// return null if this is not running
if ($target != $plugin->name) {
return;
}
$this->action = !empty($this->post['action']) ? $this->post['action'] : $uri->param('action');
$this->target = $target;
$this->active = true;
$this->admin = Grav::instance()['admin'];
$task = !empty($post['task']) ? $post['task'] : $uri->param('task');
if ($task && ($this->target == $plugin->name || $uri->route() == '/lessons')) {
$this->task = $task;
$this->active = true;
}
}
}
public function taskTestConnection()
{
$post = $this->post;
$data = json_decode(base64_decode($post['test']));
try {
Helper::testRepository($data->user, $data->password, $data->repository);
echo json_encode([
'status' => "success",
'message' => 'The connection to the repository has been successful.'
]);
} catch (\Exception $e) {
$invalid = str_replace($data->password, '{password}', $e->getMessage());
echo json_encode([
'status' => "error",
'message' => $invalid
]);
}
exit;
}
public function taskSynchronize()
{
try {
$this->plugin->synchronize();
echo json_encode([
'status' => "success",
'message' => 'GitSync has successfully synchronized with the repository.'
]);
} catch (\Exception $e) {
$invalid = str_replace($this->git->getConfig('password', null), '{password}', $e->getMessage());
echo json_encode([
'status' => "error",
'message' => $invalid
]);
}
exit;
}
public function taskResetLocal()
{
try {
$this->plugin->reset();
echo json_encode([
'status' => "success",
'message' => 'GitSync has successfully reset your local changes and synchronized with the repository.'
]);
} catch (\Exception $e) {
$invalid = str_replace($this->git->getConfig('password', null), '{password}', $e->getMessage());
echo json_encode([
'status' => "error",
'message' => $invalid
]);
}
exit;
}
/**
* Performs a task or action on a post or target.
*
* @return bool|mixed
*/
public function execute()
{
$success = false;
$params = [];
// Handle Task & Action
if ($this->post && $this->task) {
// validate nonce
if (!$this->validateNonce()) {
return false;
}
$method = $this->task_prefix . ucfirst($this->task);
} elseif ($this->target) {
if (!$this->action) {
return false;
}
$method = strtolower($this->action) . ucfirst($this->target);
} else {
return false;
}
if (!method_exists($this, $method)) {
return false;
}
$success = call_user_func_array([$this, $method], $params);
// Grab redirect parameter.
$redirect = isset($this->post['_redirect']) ? $this->post['_redirect'] : null;
unset($this->post['_redirect']);
// Redirect if requested.
if ($redirect) {
$this->setRedirect($redirect);
}
return $success;
}
public function isActive()
{
return (bool) $this->active;
}
}
<?php
namespace Grav\Plugin\GitSync;
use Grav\Common\Grav;
use Grav\Common\Plugin;
use Grav\Common\Utils;
use RocketTheme\Toolbox\File\File;
use SebastianBergmann\Git\Git;
class GitSync extends Git
{
private $user;
private $password;
protected $grav;
protected $config;
protected $repositoryPath;
static public $instance = null;
public function __construct(Plugin $plugin = null)
{
$this->grav = Grav::instance();
$this->config = $this->grav['config']->get('plugins.git-sync');
$this->repositoryPath = isset($this->config['local_repository']) && $this->config['local_repository'] ? $this->config['local_repository'] : USER_DIR;
parent::__construct($this->repositoryPath);
static::$instance = $this;
$this->user = isset($this->config['user']) ? $this->config['user'] : null;
$this->password = isset($this->config['password']) ? $this->config['password'] : null;
unset($this->config['user']);
unset($this->config['password']);
}
static public function instance()
{
return static::$instance = is_null(static::$instance) ? new static : static::$instance;
}
public function getUser()
{
return $this->user;
}
public function getPassword()
{
return $this->password;
}
public function setConfig($obj)
{
$this->config = $obj;
$this->user = $this->config['user'];
$this->password = $this->config['password'];
}
public function getRuntimeInformation()
{
$result = array(
'repositoryPath' => $this->repositoryPath,
'username' => $this->user,
'password' => $this->password
);
foreach ($this->config as $key => $item) {
if (is_array($item)) {
$count = count($item);
$arr = $item;
if ($count == 0) {// empty array, could still be associative
$arr = '[]';
} else if (isset($item[0])) {// fast check for plain array with numeric keys
$arr = '[\'' . join('\', \'', $item) . '\']';
}
$result[$key] = $arr;
} else {
$result[$key] = $item;
}
}
return $result;
}
public function testRepository($url)
{
return $this->execute("ls-remote \"${url}\"");
}
public function initializeRepository()
{
if (!Helper::isGitInitialized()) {
$branch = $this->getRemote('branch', null);
$local_branch = $this->getConfig('branch', $branch);
$this->execute('init');
$this->execute('checkout ' . $local_branch, true);
}
$this->enableSparseCheckout();
return true;
}
public function setUser($name = null, $email = null)
{
$name = $this->getConfig('git', $name)['name'];
$email = $this->getConfig('git', $email)['email'];
$this->execute("config user.name \"{$name}\"");
$this->execute("config user.email \"{$email}\"");
return true;
}
public function hasRemote($name = null)
{
$name = $this->getRemote('name', $name);
try {
$version = Helper::isGitInstalled(true);
// remote get-url 'name' supported from 2.7.0 and above
if (version_compare($version, '2.7.0', '>=')) {
$command = "remote get-url \"{$name}\"";
} else {
$command = "config --get remote.{$name}.url";
}
$this->execute($command);
} catch (\Exception $e) {
return false;
}
return true;
}
public function enableSparseCheckout()
{
$folders = $this->config['folders'];
$this->execute("config core.sparsecheckout true");
$sparse = [];
foreach ($folders as $folder) {
$sparse[] = $folder . '/';
$sparse[] = $folder . '/*';
}
$file = File::instance(rtrim($this->repositoryPath, '/') . '/.git/info/sparse-checkout');
$file->save(implode("\r\n", $sparse));
$file->free();
$ignore = ['/*'];
foreach ($folders as $folder) {
$folder = rtrim($folder,'/');
$nested = substr_count($folder, '/');
if ($nested) {
$subfolders = explode('/', $folder);
$nested_tracking = '';
foreach ($subfolders as $index => $subfolder) {
$last = $index === (count($subfolders) - 1);
$nested_tracking .= $subfolder . '/';
if (!in_array('!/' . $nested_tracking, $ignore)) {
$ignore[] = rtrim($nested_tracking . (!$last ? '*' : ''), '/');
$ignore[] = rtrim('!/' . $nested_tracking, '/');
}
}
} else {
$ignore[] = '!/' . $folder;
}
}
$file = File::instance(rtrim($this->repositoryPath, '/') . '/.gitignore');
$file->save(implode("\r\n", $ignore));
$file->free();
}
public function addRemote($alias = null, $url = null, $authenticated = false)
{
$alias = $this->getRemote('name', $alias);
$url = $this->getConfig('repository', $url);
if ($authenticated) {
$user = $this->user ?: $this->config->get('user');
$password = Helper::decrypt($this->password ?: $this->config->get('password'));
$url = Helper::prepareRepository($user, $password, $url);
}
$command = $this->hasRemote($alias) ? 'set-url' : 'add';
return $this->execute("remote ${command} ${alias} \"${url}\"");
}
public function add()
{
$version = Helper::isGitInstalled(true);
$add = 'add';
// With the introduction of customizable paths,
// it appears that the add command should always
// add everything that is not committed to ensure
// there are no orphan changes left behind
/*
$folders = $this->config['folders'];
$paths = [];
foreach ($folders as $folder) {
$paths[] = $folder;
}
*/
$paths = ['.'];
if (version_compare($version, '2.0', '<')) {
$add .= ' --all';
}
return $this->execute($add . ' ' . implode(' ', $paths));
}
public function commit($message = '(Grav GitSync) Automatic Commit')
{
$authorType = $this->getGitConfig('author', 'gituser');
if (defined('GRAV_CLI') && in_array($authorType, ['gravuser', 'gravfull'])) {
$authorType = 'gituser';
}
// @TODO: After 1.6 it should be changed to `$configMessage ?? $message`
// get message from config, it any, or stick to the default one
$config = $this->getConfig('git', null);
$message = isset($config['message']) ? $config['message'] : $message;
// get Page Title and Route from Post
$uri = $this->grav['uri'];
$page_title = $uri->post('data.header.title');
$page_route = $uri->post('data.route');
$pageTitle = $page_title ? $page_title : 'NO TITLE FOUND';
$pageRoute = $page_route ? $page_route : 'NO ROUTE FOUND';
// include page title and route in the message, if placeholders exist
$message = str_replace('{{pageTitle}}', $pageTitle, $message);
$message = str_replace('{{pageRoute}}', $pageRoute, $message);
switch ($authorType) {
case 'gitsync':
$user = $this->getConfig('git', null)['name'];
$email = $this->getConfig('git', null)['email'];
break;
case 'gravuser':
$user = $this->grav['session']->user->username;
$email = $this->grav['session']->user->email;
break;
case 'gravfull':
$user = $this->grav['session']->user->fullname;
$email = $this->grav['session']->user->email;
break;
case 'gituser':
default:
$user = $this->user;
$email = $this->getConfig('git', null)['email'];
break;
}
$author = $user . ' <' . $email . '>';
$author = '--author="' . $author . '"';
$message .= ' from ' . $user;
$this->add();
return $this->execute("commit " . $author . " -m " . escapeshellarg($message));
}
public function fetch($name = null, $branch = null)
{
$name = $this->getRemote('name', $name);
$branch = $this->getRemote('branch', $branch);
return $this->execute("fetch {$name} {$branch}");
}
public function pull($name = null, $branch = null)
{
$name = $this->getRemote('name', $name);
$branch = $this->getRemote('branch', $branch);
$version = $version = Helper::isGitInstalled(true);
$unrelated_histories = '--allow-unrelated-histories';
// --allow-unrelated-histories starts at 2.9.0
if (version_compare($version, '2.9.0', '<')) {
$unrelated_histories = '';
}
return $this->execute("pull {$unrelated_histories} -X theirs {$name} {$branch}");
}
public function push($name = null, $branch = null)
{
$name = $this->getRemote('name', $name);
$branch = $this->getRemote('branch', $branch);
$local_branch = $this->getConfig('branch', $branch);
return $this->execute("push {$name} {$local_branch}:{$branch}");
}
public function sync($name = null, $branch = null)
{
$name = $this->getRemote('name', $name);
$branch = $this->getRemote('branch', $branch);
$this->addRemote(null, null, true);
$this->fetch($name, $branch);
$this->pull($name, $branch);
$this->push($name, $branch);
$this->addRemote();
return true;
}
public function reset()
{
return $this->execute("reset --hard HEAD");
}
public function isWorkingCopyClean()
{
$message = 'nothing to commit';
$output = $this->execute('status');
return (substr($output[count($output)-1], 0, strlen($message)) === $message);
}
public function hasChangesToCommit()
{
$folders = $this->config['folders'];
$paths = [];
foreach ($folders as $folder) {
$folder = explode('/', $folder);
$paths[] = is_array($folder) ? array_shift($folder) : $folder;
}
$message = 'nothing to commit';
$output = $this->execute('status ' . implode(' ', $paths));
return (substr($output[count($output)-1], 0, strlen($message)) !== $message);
}
public function execute($command, $quiet = false)
{
try {
$bin = Helper::getGitBinary($this->getGitConfig('bin', 'git'));
$version = Helper::isGitInstalled(true);
// -C <path> supported from 1.8.5 and above
if (version_compare($version, '1.8.5', '>=')) {
$command = $bin . ' -C ' . escapeshellarg($this->repositoryPath) . ' ' . $command;
} else {
$command = 'cd ' . $this->repositoryPath . ' && ' . $bin . ' ' . $command;
}
$command .= ' 2>&1';
if (DIRECTORY_SEPARATOR == '/') {
$command = 'LC_ALL=C ' . $command;
}
if ($this->getConfig('logging', false)) {
$log_command = Helper::preventReadablePassword($command, $this->password);
$this->grav['log']->notice('gitsync[command]: ' . $log_command);
exec($command, $output, $returnValue);
$log_output = Helper::preventReadablePassword(implode("\n", $output), $this->password);
$this->grav['log']->notice('gitsync[output]: ' . $log_output);
} else {
exec($command, $output, $returnValue);
}
if ($returnValue !== 0 && !$quiet) {
throw new \RuntimeException(implode("\r\n", $output));
}
return $output;
} catch (\RuntimeException $e) {
$message = $e->getMessage();
$message = Helper::preventReadablePassword($message, $this->password);
// handle scary messages
if (Utils::contains($message, "remote: error: cannot lock ref")) {
$message = 'GitSync: An error occurred while trying to synchronize. This could mean GitSync is already running. Please try again.';
}
throw new \RuntimeException($message);
}
}
public function getGitConfig($type, $value)
{
return isset($this->config['git']) && isset($this->config['git'][$type]) ? $this->config['git'][$type] : $value;
}
public function getRemote($type, $value)
{
return !$value && isset($this->config['remote']) ? $this->config['remote'][$type] : $value;
}
public function getConfig($type, $value)
{
return !$value && isset($this->config[$type]) ? $this->config[$type] : $value;
}
}
<?php
namespace Grav\Plugin\GitSync;
use Defuse\Crypto\Crypto;
use Grav\Common\Grav;
use SebastianBergmann\Git\RuntimeException;
class Helper {
private static $hash = '594ef69d-6c29-45f7-893a-f1b4342687d3';
/**
* Checks if the user/ folder is already initialized
*
* @return bool
*/
public static function isGitInitialized()
{
return file_exists(rtrim(USER_DIR, '/') . '/.git');
}
public static function isGitInstalled($version = false)
{
$bin = Helper::getGitBinary();
exec($bin . ' --version', $output, $returnValue);
$installed = $returnValue !== 0 ? false : true;
if ($version && $output) {
$output = explode(' ', array_shift($output));
$installed = array_filter($output, function($item) {
return version_compare($item, '0.0.1', '>=');
});
$installed = array_shift($installed);
}
return $installed;
}
public static function getGitBinary($override = false)
{
$grav = Grav::instance()['config'];
return $override ?: $grav->get('plugins.git-sync.git.bin', 'git');
}
public static function prepareRepository($user, $password, $repository)
{
$password = urlencode($password);
return str_replace('://', "://${user}:${password}@", $repository);
}
public static function testRepository($user, $password, $repository) {
$git = new GitSync();
$repository = self::prepareRepository($user, $password, $repository);
try {
return $git->testRepository($repository);
} catch (RuntimeException $e) {
return $e->getMessage();
}
}
public static function encrypt($password)
{
return 'gitsync-' . Crypto::encryptWithPassword($password, self::$hash);
}
public static function decrypt($enc_password)
{
if (substr($enc_password, 0, 8) === 'gitsync-') {
$enc_password = substr($enc_password, 8);
return Crypto::decryptWithPassword($enc_password, self::$hash);
} else {
return $enc_password;
}
}
public static function synchronize()
{
if (!Helper::isGitInstalled() || !Helper::isGitInitialized()) {
return true;
}
$git = new GitSync();
if ($git->hasChangesToCommit()) {
$git->commit();
}
// synchronize with remote
$git->sync();
return true;
}
public static function preventReadablePassword($str, $password) {
return str_replace(urlencode(self::decrypt($password)), '{password}', $str);
}
}
<?php namespace Grav\Plugin\Console;
use Grav\Console\ConsoleCommand;
use Grav\Plugin\GitSync\GitSync;
/**
* Class InitCommand
*
* @package Grav\Plugin\Console
*/
class InitCommand extends ConsoleCommand
{
protected function configure()
{
$this
->setName('init')
->setDescription('Initializes your git repository')
->setHelp('The <info>init</info> command runs the same git commands as the onAdminAfterSave function. Use this to manually initialize git-sync (useful for automated deployments).')
;
}
protected function serve()
{
require_once __DIR__ . '/../vendor/autoload.php';
$plugin = new GitSync();
$repository = $plugin->getConfig('repository', false);
$this->output->writeln('');
if (!$repository) {
$this->output->writeln('<red>ERROR:</red> No repository has been configured!');
}
$this->output->writeln('Initializing <cyan>' . $repository . '</cyan>');
$this->output->write('Starting initialization...');
$plugin->initializeRepository();
$plugin->setUser();
$plugin->addRemote();
$this->output->writeln('completed.');
}
}
<?php
namespace Grav\Plugin\Console;
use Grav\Console\ConsoleCommand;
use Grav\Plugin\GitSync\GitSync;
use Grav\Plugin\GitSync\Helper;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Input\InputOption;
/**
* Class LogCommand
*
* @package Grav\Plugin\Console
*/
class StatusCommand extends ConsoleCommand
{
protected function configure()
{
$this
->setName('status')
->setDescription('Checks the status of plugin config, git and git workspace. No files get modified!')
->addOption(
'fetch', 'f',
InputOption::VALUE_NONE,
'additionally do a git fetch to look updates (changes not files in workspace)'
)
->setHelp(<<<'EOF'
The <info>%command.name%</info> command checks if the plugin is usable the way it has been configured.
While doing this it prints the available information for your inspection.
<comment>No files in the workspace are modified when running this test.</comment>
The <info>--fetch</info> option can be used to see differences between the remote in the <info>git status</info> (last check)
It also returns with an error code and a helpful message when something is not normal:
<error>100</error> : <info>git</info> binary not working as expected
<error>50</error> : <info>repositoryFolder</info> and git workspace root do not match
<error>10</error> : <info>repository</info> is not configured
<error>5</error> : state of workspace not clean
<error>1</error> : Some checks can throw a <info>RuntimeException</info> which is not caught, read the message for details
EOF
)
;
}
protected function serve()
{
require_once __DIR__ . '/../vendor/autoload.php';
$plugin = new GitSync();
$this->output->writeln('');
$this->console_header('plugin runtime information:');
$info = $plugin->getRuntimeInformation();
$info['isGitInitialized'] = Helper::isGitInitialized();
$info['gitVersion'] = Helper::isGitInstalled(true);
ksort($info);
dump($info);
if (!Helper::isGitInstalled()) {
throw new RuntimeException('git binary not found', 100);
}
$this->console_header('detect git workspace root:');
$git_root = $plugin->execute('rev-parse --show-toplevel');
$this->console_log($git_root, '');
if (rtrim($info['repositoryPath'], '/') !== rtrim($git_root[0], '/')) {
throw new RuntimeException('git root and repositoryPath do not match', 50);
}
// needed to prevent out put in logs:
$password = Helper::decrypt($plugin->getPassword());
$this->console_header('local git config:');
$this->console_log(
$plugin->execute('config --local -l'), $password
);
$this->console_header(
'Testing connection to repository', 'git ls-remote', true
);
$repository = $plugin->getConfig('repository', false);
if (!$repository) {
throw new RuntimeException('No repository has been configured', 10);
}
$testRepository = $plugin->testRepository(
Helper::prepareRepository(
$plugin->getUser(),
$password,
$repository)
);
$this->console_log($testRepository, $password);
$fetched = false;
if ($this->input->getOption('fetch')) {
$remote = $plugin->getRemote('name', '');
$this->console_header(
'Looking for updates', "git fetch $remote", true
);
$this->console_log($plugin->fetch($remote), $password);
$fetched = true;
}
$this->console_header(
'Checking workspace status', 'git status', true
);
$git_status = $plugin->execute('status');
$this->console_log($git_status, $password);
if (!$plugin->isWorkingCopyClean()) {
throw new RuntimeException('Working state is not clean.', 5);
}
if ($fetched) {
$uptodate = strpos($git_status[1], 'branch is up-to-date with') > 0;
if ($uptodate) {
$this->console_header(
'Congrats: You should be able to run the <info>sync</info> command without problems!'
);
} else {
$this->output->writeln('<yellow>You are not in sync!</yellow>');
$this->output->writeln('Take a look at the output of git status to see more details.');
$this->output->writeln('In most cases the <info>sync</info> command is able to fix this.');
}
} else {
$this->console_header('Looks good: use <info>--fetch</info> option to check for updates.');
}
}
private function console_header($readable, $cmd = '', $remote_action = false)
{
$this->output->writeln(
"<yellow>$readable</yellow>" . ($cmd ? "(<info>$cmd</info>)" : ''). ($remote_action ? '...' : '')
);
}
private function console_log($lines, $password)
{
foreach ($lines as $line) {
$this->output->writeln(' ' . Helper::preventReadablePassword($line, $password));
}
}
}
<?php
namespace Grav\Plugin\Console;
use Grav\Console\ConsoleCommand;
use Grav\Plugin\GitSync\GitSync;
/**
* Class LogCommand
*
* @package Grav\Plugin\Console
*/
class SyncCommand extends ConsoleCommand
{
protected function configure()
{
$this
->setName('sync')
->setDescription('Performs a synchronization of your site')
->setHelp('The <info>sync</info> command performs a synchronization of your site. Useful if you want to run a periodic crontab job to automate it.')
;
}
protected function serve()
{
require_once __DIR__ . '/../vendor/autoload.php';
$plugin = new GitSync();
$repository = $plugin->getConfig('repository', false);
$this->output->writeln('');
if (!$repository) {
$this->output->writeln('<red>ERROR:</red> No repository has been configured');
}
$this->output->writeln('Synchronizing with <cyan>' . $repository . '</cyan>');
if ($plugin->hasChangesToCommit()) {
$this->output->writeln('Changes detected, adding and committing...');
$plugin->add();
$plugin->commit();
}
$this->output->write('Starting Synchronization...');
$plugin->sync();
$this->output->writeln('completed.');
}
}
{
"require": {
"sebastian/git": "^2.1",
"defuse/php-encryption": "^2.0"
},
"autoload": {
"psr-4": {
"Grav\\Plugin\\GitSync\\": "classes/"
}
},
"license": "Apache-2.0",
"authors": [
{
"name": "Trilby Media, LLC",
"email": "devs@trilbymedia.com"
}
]
}
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"content-hash": "49491bf051948fdafa70414b48b9ea34",
"packages": [
{
"name": "defuse/php-encryption",
"version": "2.0.3",
"source": {
"type": "git",
"url": "https://github.com/defuse/php-encryption.git",
"reference": "2c6fea3d9a4eaaa8cef86b2a89f3660818117b33"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/defuse/php-encryption/zipball/2c6fea3d9a4eaaa8cef86b2a89f3660818117b33",
"reference": "2c6fea3d9a4eaaa8cef86b2a89f3660818117b33",
"shasum": ""
},
"require": {
"ext-openssl": "*",
"paragonie/random_compat": "~2.0",
"php": ">=5.4.0"
},
"require-dev": {
"nikic/php-parser": "^2.0"
},
"type": "library",
"autoload": {
"classmap": [
"src"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Taylor Hornby",
"email": "taylor@defuse.ca",
"homepage": "https://defuse.ca/"
},
{
"name": "Scott Arciszewski",
"email": "info@paragonie.com",
"homepage": "https://paragonie.com"
}
],
"description": "Secure PHP Encryption Library",
"keywords": [
"aes",
"authenticated encryption",
"cipher",
"crypto",
"cryptography",
"encrypt",
"encryption",
"openssl",
"security",
"symmetric key cryptography"
],
"time": "2016-10-10T15:20:26+00:00"
},
{
"name": "paragonie/random_compat",
"version": "v2.0.4",
"source": {
"type": "git",
"url": "https://github.com/paragonie/random_compat.git",
"reference": "a9b97968bcde1c4de2a5ec6cbd06a0f6c919b46e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/paragonie/random_compat/zipball/a9b97968bcde1c4de2a5ec6cbd06a0f6c919b46e",
"reference": "a9b97968bcde1c4de2a5ec6cbd06a0f6c919b46e",
"shasum": ""
},
"require": {
"php": ">=5.2.0"
},
"require-dev": {
"phpunit/phpunit": "4.*|5.*"
},
"suggest": {
"ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
},
"type": "library",
"autoload": {
"files": [
"lib/random.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Paragon Initiative Enterprises",
"email": "security@paragonie.com",
"homepage": "https://paragonie.com"
}
],
"description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
"keywords": [
"csprng",
"pseudorandom",
"random"
],
"time": "2016-11-07T23:38:38+00:00"
},
{
"name": "sebastian/git",
"version": "2.1.3",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/git.git",
"reference": "5100bc50cd9e70f424c643618e142214225024f3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/git/zipball/5100bc50cd9e70f424c643618e142214225024f3",
"reference": "5100bc50cd9e70f424c643618e142214225024f3",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.1-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
}
],
"description": "Simple wrapper for Git",
"homepage": "http://www.github.com/sebastianbergmann/git",
"keywords": [
"git"
],
"time": "2016-06-15T09:30:19+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],
"platform-dev": []
}
@font-face {
font-family: 'gitsync';
src:
url('../fonts/gitsync.ttf?ivrc6k') format('truetype'),
url('../fonts/gitsync.woff?ivrc6k') format('woff'),
url('../fonts/gitsync.svg?ivrc6k#gitsync') format('svg');
font-weight: normal;
font-style: normal;
}
.fa.fa-git,
.fa.fa-git-square {
font-family: 'gitsync' !important;
vertical-align: middle;
}
.fa-git-square:before {
content: "\e900" !important;
}
.fa-git:before {
content: "\e901" !important;
}
[data-remodal-id="wizard"] .button[disabled], [data-remodal-id="wizard"] .button[disabled]:hover, [data-remodal-id="reset-local"] .button[disabled], [data-remodal-id="reset-local"] .button[disabled]:hover {
background: #ccc;
cursor: default; }
[data-remodal-id="wizard"] .button[disabled]:active, [data-remodal-id="reset-local"] .button[disabled]:active {
margin: 0; }
[data-remodal-id="wizard"] form [class^="step-"] a:not(.button):not([target]), [data-remodal-id="reset-local"] form [class^="step-"] a:not(.button):not([target]) {
vertical-align: middle;
color: #5591c7; }
[data-remodal-id="wizard"] form [class^="step-"] a:not(.button):not([target]):hover, [data-remodal-id="reset-local"] form [class^="step-"] a:not(.button):not([target]):hover {
color: #366188; }
[data-remodal-id="wizard"] .step-1 a, [data-remodal-id="reset-local"] .step-1 a {
vertical-align: middle; }
[data-remodal-id="wizard"] .access-tokens h3, [data-remodal-id="reset-local"] .access-tokens h3 {
margin-top: 1rem; }
[data-remodal-id="wizard"] .access-tokens a, [data-remodal-id="reset-local"] .access-tokens a {
vertical-align: inherit !important; }
[data-remodal-id="wizard"] .access-tokens-details > p, [data-remodal-id="reset-local"] .access-tokens-details > p {
margin-top: 0; }
[data-remodal-id="wizard"] .access-tokens-details > div > ul, [data-remodal-id="reset-local"] .access-tokens-details > div > ul {
margin-bottom: 0; }
[data-remodal-id="wizard"] .center, [data-remodal-id="reset-local"] .center {
text-align: center; }
[data-remodal-id="wizard"] h1, [data-remodal-id="reset-local"] h1 {
margin-bottom: 0;
padding-top: 0.5rem;
border-top: 3px solid transparent; }
[data-remodal-id="wizard"] .wizard-padding, [data-remodal-id="reset-local"] .wizard-padding {
padding: 0 3rem; }
[data-remodal-id="wizard"] .wizard-padding p, [data-remodal-id="reset-local"] .wizard-padding p {
padding: 0; }
[data-remodal-id="wizard"] label.disabled, [data-remodal-id="reset-local"] label.disabled {
color: #ccc; }
[data-remodal-id="wizard"] label img, [data-remodal-id="reset-local"] label img {
max-width: 100px;
display: inline-block;
vertical-align: middle;
margin-left: 0.5rem; }
[data-remodal-id="wizard"] label a, [data-remodal-id="reset-local"] label a {
margin-left: 0.5rem; }
[data-remodal-id="wizard"] .columns, [data-remodal-id="reset-local"] .columns {
display: flex;
align-items: center;
justify-content: center; }
[data-remodal-id="wizard"] .columns .column, [data-remodal-id="reset-local"] .columns .column {
flex: 1; }
[data-remodal-id="wizard"] .columns .column:first-child, [data-remodal-id="reset-local"] .columns .column:first-child {
width: 35%;
flex: none;
border-right: 1px solid #ddd;
margin-right: 2rem; }
[data-remodal-id="wizard"] .step-1 div.column:nth-child(1), [data-remodal-id="reset-local"] .step-1 div.column:nth-child(1) {
flex-grow: 1.5;
width: 15%; }
[data-remodal-id="wizard"] .step-1 div.column:nth-child(1) > label:nth-child(4), [data-remodal-id="reset-local"] .step-1 div.column:nth-child(1) > label:nth-child(4) {
width: 110%; }
[data-remodal-id="wizard"] .step-1 div.column:nth-child(2), [data-remodal-id="reset-local"] .step-1 div.column:nth-child(2) {
margin-left: 25px; }
[data-remodal-id="wizard"] .step-1 div.column:nth-child(2) > label:nth-child(1) > input:nth-child(1), [data-remodal-id="wizard"] .step-1 div.column:nth-child(2) > label:nth-child(2) > input:nth-child(1), [data-remodal-id="reset-local"] .step-1 div.column:nth-child(2) > label:nth-child(1) > input:nth-child(1), [data-remodal-id="reset-local"] .step-1 div.column:nth-child(2) > label:nth-child(2) > input:nth-child(1) {
width: 85%; }
[data-remodal-id="wizard"] .step-2 .info, [data-remodal-id="reset-local"] .step-2 .info {
margin: 0.2rem 0;
padding: 0.5rem 1.5rem; }
[data-remodal-id="wizard"] .step-2 ol, [data-remodal-id="wizard"] .step-2 ul, [data-remodal-id="reset-local"] .step-2 ol, [data-remodal-id="reset-local"] .step-2 ul {
padding-left: 1rem; }
[data-remodal-id="wizard"] .step-4 .info, [data-remodal-id="reset-local"] .step-4 .info {
font-size: 100%;
margin: 0.2rem 0; }
[data-remodal-id="wizard"] .step-4 .alert, [data-remodal-id="wizard"] .step-4 .warning, [data-remodal-id="reset-local"] .step-4 .alert, [data-remodal-id="reset-local"] .step-4 .warning {
padding: 0.5rem 1.5rem; }
[data-remodal-id="wizard"] .step-4 .fa.fa-warning, [data-remodal-id="reset-local"] .step-4 .fa.fa-warning {
color: #ff7d3b;
font-size: 1.2rem; }
[data-remodal-id="wizard"] .step-4 .wizard-padding label > *, [data-remodal-id="reset-local"] .step-4 .wizard-padding label > * {
vertical-align: middle; }
[data-remodal-id="wizard"] .step-4 .info-desc, [data-remodal-id="reset-local"] .step-4 .info-desc {
color: #0091ff;
float: right;
margin-right: .5rem;
font-size: 1.2rem; }
[data-remodal-id="wizard"] .step-4 hr, [data-remodal-id="reset-local"] .step-4 hr {
margin: .5rem 0; }
#admin-main [data-grav-field="git-wizard"] {
margin: 0 1rem; }
#admin-main [data-grav-field="git-wizard"] .danger.button-bar {
margin: 0;
padding: 0;
background: transparent; }
#admin-main [data-grav-field="git-wizard"] .button {
top: 0 !important;
-webkit-transform: translateY(0) !important;
-moz-transform: translateY(0) !important;
-ms-transform: translateY(0) !important;
-o-transform: translateY(0) !important;
transform: translateY(0) !important; }
/*# sourceMappingURL=../css-compiled/git-sync.css.map */
\ No newline at end of file
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>Generated by IcoMoon</metadata>
<defs>
<font id="gitsync" horiz-adv-x="1024">
<font-face units-per-em="1024" ascent="960" descent="-64" />
<missing-glyph horiz-adv-x="1024" />
<glyph unicode="&#x20;" horiz-adv-x="512" d="" />
<glyph unicode="&#xe900;" glyph-name="git-square" horiz-adv-x="878" d="M332.571 203.428c0-30.857-28-37.714-53.143-37.714-24.571 0-61.143 4-61.143 36 0 31.429 30.857 36.571 56 36.571 24 0 58.286-4 58.286-34.857zM312 469.714c0-28.571-11.429-48.571-42.286-48.571-31.429 0-44 18.286-44 48s11.429 51.429 44 51.429c29.143 0 42.286-24 42.286-50.857zM406.857 512.571v71.429c-24.571-9.143-50.857-16.571-77.143-16.571-18.857 10.857-40.571 16.571-62.857 16.571-65.143 0-116.571-48-116.571-114.286 0-35.429 23.429-84.571 58.857-96.571v-1.714c-18.286-8-21.714-30.286-21.714-48.571 0-18.857 6.857-34.286 23.429-44v-1.714c-38.857-12.571-64.571-37.143-64.571-79.429 0-72.571 69.143-93.143 129.714-93.143 73.143 0 128 26.857 128 107.429 0 57.143-52 74.286-99.429 82.857-16 2.857-43.429 14.286-43.429 34.286 0 18.857 10.286 26.857 28 29.714 58.286 11.429 95.429 56.571 95.429 116.571 0 10.286-2.286 20-5.714 29.714 9.143 2.286 18.857 4.571 28 7.429zM440.571 273.143h78.286c-1.143 15.429-1.143 31.429-1.143 46.857v221.143c0 13.143 0 26.286 1.143 39.429h-78.286c1.714-13.143 1.714-27.429 1.714-40.571v-224c0-14.286 0-28.571-1.714-42.857zM731.429 282.286v69.143c-11.429-8-25.143-12-38.857-12-25.714 0-30.286 25.714-30.286 46.857v128.571h29.714c10.286 0 20-1.143 30.286-1.143v66.857h-60c0 19.429-1.143 38.857 1.714 58.286h-80c1.714-10.286 2.286-20.571 2.286-31.429v-26.857h-34.286v-66.857c6.857 0.571 13.714 1.714 21.143 1.714 4 0 8.571-0.571 13.143-0.571v-1.143h-1.143v-124c0-61.714 9.143-121.143 84.571-121.143 21.143 0 42.857 3.429 61.714 13.714zM528 685.714c0 26.857-20 52-48 52s-48.571-24.571-48.571-52c0-26.857 21.143-50.857 48.571-50.857s48 24.571 48 50.857zM877.714 713.143v-548.571c0-90.857-73.714-164.571-164.571-164.571h-548.571c-90.857 0-164.571 73.714-164.571 164.571v548.571c0 90.857 73.714 164.571 164.571 164.571h548.571c90.857 0 164.571-73.714 164.571-164.571z" />
<glyph unicode="&#xe901;" glyph-name="git" d="M340 85.714c0 50.286-55.429 57.143-94.286 57.143-40.571 0-90.286-8.571-90.286-59.429 0-51.429 58.857-57.714 98.286-57.714 41.714 0 86.286 10.286 86.286 60zM306.286 517.143c0 42.857-20.571 81.714-68 81.714-52.571 0-70.857-34.857-70.857-82.857 0-47.429 20.571-77.143 70.857-77.143 49.714 0 68 32 68 78.286zM460 702.286v-115.429c-14.857-5.143-29.714-9.143-45.143-12.571 5.714-15.429 9.143-31.429 9.143-48 0-96.571-59.429-170.286-154.286-188-28.571-5.714-45.143-17.714-45.143-48.571 0-87.429 230.857-28 230.857-189.143 0-130.857-88.571-173.714-207.429-173.714-97.714 0-209.143 32.571-209.143 150.286 0 68.571 41.714 108 104 128.571v2.286c-26.286 16-38.286 41.143-38.286 72 0 29.143 6.286 65.143 36 78.286v2.286c-57.714 19.429-95.429 98.857-95.429 156.571 0 106.857 82.857 185.143 188.571 185.143 35.429 0 70.857-9.143 101.714-26.857 42.857 0 85.143 11.429 124.571 26.857zM641.714 198.857h-126.857c2.286 25.714 2.286 50.857 2.286 76.571v348c0 24.571 0.571 49.143-2.286 73.143h126.857c-2.857-23.429-2.286-47.429-2.286-70.857v-350.286c0-25.714 0-50.857 2.286-76.571zM985.143 325.714v-112c-30.286-16.571-65.143-22.286-99.429-22.286-122.286 0-136.571 96.571-136.571 196v200.571h1.143v2.286c-7.429 0-14.286 1.143-21.143 1.143-11.429 0-22.857-1.714-33.714-3.429v108.571h54.857v43.429c0 17.143-0.571 34.286-3.429 50.857h129.714c-4.571-31.429-3.429-62.857-3.429-94.286h97.714v-108.571c-16.571 0-33.143 2.286-49.143 2.286h-48.571v-208.571c0-33.714 7.429-74.857 49.714-74.857 22.286 0 44 6.286 62.286 18.857zM656 866.857c0-42.857-33.143-82.857-77.143-82.857-45.143 0-78.857 39.429-78.857 82.857 0 44 33.143 84 78.857 84 45.143 0 77.143-41.143 77.143-84z" />
</font></defs></svg>
\ No newline at end of file
<?php
namespace Grav\Plugin;
use Grav\Common\Data\Data;
use Grav\Common\Grav;
use Grav\Common\Plugin;
use Grav\Plugin\GitSync\AdminController;
use Grav\Plugin\GitSync\GitSync;
use Grav\Plugin\GitSync\Helper;
use RocketTheme\Toolbox\Event\Event;
/**
* Class GitSyncPlugin
*
* @package Grav\Plugin
*/
class GitSyncPlugin extends Plugin
{
protected $controller;
protected $git;
/**
* @return array
*/
public static function getSubscribedEvents()
{
return [
'onPluginsInitialized' => ['onPluginsInitialized', 1000],
'onPageInitialized' => ['onPageInitialized', 0],
'onFormProcessed' => ['onFormProcessed', 0],
'onSchedulerInitialized' => ['onSchedulerInitialized', 0]
];
}
/**
* @return string
*/
public static function generateWebhookSecret()
{
return bin2hex(openssl_random_pseudo_bytes(24));
}
/**
* @return string
*/
public static function generateRandomWebhook()
{
return '/_git-sync-' . bin2hex(openssl_random_pseudo_bytes(6));
}
/**
* Initialize the plugin
*/
public function onPluginsInitialized()
{
require_once __DIR__ . '/vendor/autoload.php';
$this->enable(['gitsync' => ['synchronize', 0]]);
$this->init();
if ($this->isAdmin()) {
$this->enable([
'onTwigTemplatePaths' => ['onTwigTemplatePaths', 0],
'onTwigSiteVariables' => ['onTwigSiteVariables', 0],
'onAdminMenu' => ['onAdminMenu', 0],
'onAdminSave' => ['onAdminSave', 0],
'onAdminAfterSave' => ['onAdminAfterSave', 0],
'onAdminAfterSaveAs' => ['onAdminAfterSaveAs', 0],
'onAdminAfterDelete' => ['onAdminAfterDelete', 0],
'onAdminAfterAddMedia' => ['onAdminAfterMedia', 0],
'onAdminAfterDelMedia' => ['onAdminAfterMedia', 0],
]);
return;
} else {
$config = $this->config->get('plugins.' . $this->name);
$route = $this->grav['uri']->route();
$webhook = isset($config['webhook']) ? $config['webhook'] : false;
$secret = isset($config['webhook_secret']) ? $config['webhook_secret'] : false;
$enabled = isset($config['webhook_enabled']) ? $config['webhook_enabled'] : false;
if ($route === $webhook && $_SERVER['REQUEST_METHOD'] === 'POST') {
if ($secret && $enabled) {
if (!$this->isRequestAuthorized($secret)) {
http_response_code(401);
header('Content-Type: application/json');
echo json_encode([
'status' => 'error',
'message' => 'Unauthorized request'
]);
exit;
}
}
try {
$this->synchronize();
header('Content-Type: application/json');
echo json_encode([
'status' => 'success',
'message' => 'GitSync completed the synchronization'
]);
} catch (\Exception $e) {
http_response_code(500);
header('Content-Type: application/json');
echo json_encode([
'status' => 'error',
'message' => 'GitSync failed to synchronize'
]);
}
exit;
}
}
}
/**
* Returns true if the request contains a valid signature or token
* @param string $secret local secret
* @return boolean whether or not the request is authorized
*/
public function isRequestAuthorized($secret)
{
if (isset($_SERVER['HTTP_X_HUB_SIGNATURE'])) {
$payload = file_get_contents('php://input');
return $this->isGithubSignatureValid($secret, $_SERVER['HTTP_X_HUB_SIGNATURE'], $payload);
} elseif (isset($_SERVER['HTTP_X_GITLAB_TOKEN'])) {
return $this->isGitlabTokenValid($secret, $_SERVER['HTTP_X_GITLAB_TOKEN']);
}
return false;
}
/**
* Hashes the webhook request body with the client secret and
* checks if it matches the webhook signature header
* @param string $secret The webhook secret
* @param string $signatureHeader The signature of the webhook request
* @param string $payload The webhook request body
* @return boolean Whether the signature is valid or not
*/
public function isGithubSignatureValid($secret, $signatureHeader, $payload)
{
list($algorigthm, $signature) = explode('=', $signatureHeader);
if ($signature === hash_hmac($algorigthm, $payload, $secret)) {
return true;
}
return false;
}
/**
* Returns true if given Gitlab token matches secret
* @param string $secret local secret
* @param string $token token received from Gitlab webhook request
* @return boolean whether or not secret and token match
*/
public function isGitlabTokenValid($secret, $token)
{
return $secret === $token;
}
public function onAdminMenu()
{
$base = rtrim($this->grav['base_url'], '/') . '/' . trim($this->grav['admin']->base, '/');
$options = [
'hint' => Helper::isGitInitialized() ? 'Synchronize GitSync' : 'Configure GitSync',
'class' => 'gitsync-sync',
'location' => 'pages',
'route' => Helper::isGitInitialized() ? 'admin' : 'admin/plugins/git-sync',
'icon' => 'fa-' . $this->grav['plugins']->get('git-sync')->blueprints()->get('icon')
];
if (Helper::isGitInstalled()) {
if (Helper::isGitInitialized()) {
$options['data'] = [
'gitsync-useraction' => 'sync',
'gitsync-uri' => $base . '/plugins/git-sync'
];
}
$this->grav['twig']->plugins_quick_tray['GitSync'] = $options;
}
}
public function init()
{
if ($this->isAdmin()) {
/** @var AdminController controller */
$this->controller = new AdminController($this);
} else {
$this->controller = new \stdClass;
$this->controller->git = new GitSync($this);
}
$this->git = $this->controller->git;
}
public function synchronize()
{
if (!Helper::isGitInstalled() || !Helper::isGitInitialized()) {
return true;
}
$this->grav->fireEvent('onGitSyncBeforeSynchronize');
if ($this->git->hasChangesToCommit()) {
$this->git->commit();
}
// synchronize with remote
$this->git->sync();
$this->grav->fireEvent('onGitSyncAfterSynchronize');
return true;
}
public function onSchedulerInitialized(Event $event)
{
/** @var Config $config */
$config = Grav::instance()['config'];
$run_at = $config->get('plugins.git-sync.sync.cron_at', '0 12,23 * * *');
if ($config->get('plugins.git-sync.sync.cron_enable', false)) {
/** @var Scheduler $scheduler */
$scheduler = $event['scheduler'];
$job = $scheduler->addFunction('Grav\Plugin\GitSync\Helper::synchronize', [], 'GitSync');
$job->at($run_at);
}
}
public function reset()
{
if (!Helper::isGitInstalled() || !Helper::isGitInitialized()) {
return true;
}
$this->grav->fireEvent('onGitSyncBeforeReset');
$this->git->reset();
$this->grav->fireEvent('onGitSyncAfterReset');
return true;
}
/**
* Add current directory to twig lookup paths.
*/
public function onTwigTemplatePaths()
{
$this->grav['twig']->twig_paths[] = __DIR__ . '/templates';
}
/**
* Set needed variables to display cart.
*/
public function onTwigSiteVariables()
{
// workaround for admin plugin issue that doesn't properly unsubscribe
// events upon plugin uninstall
if (!class_exists('Grav\Plugin\GitSync\Helper')) {
return false;
}
$user = $this->grav['user'];
if (!$user->authenticated) {
return false;
}
$settings = [
'first_time' => !Helper::isGitInitialized(),
'git_installed' => Helper::isGitInstalled()
];
$this->grav['twig']->twig_vars['git_sync'] = $settings;
$adminPath = trim($this->grav['admin']->base, '/');
if ($this->grav['uri']->path() === "/$adminPath/plugins/git-sync") {
$this->grav['assets']->addCss('plugin://git-sync/css-compiled/git-sync.css');
} else {
$this->grav['assets']->addInlineJs('var GitSync = ' . json_encode($settings) . ';');
}
$this->grav['assets']->addJs('plugin://git-sync/js/vendor.js', ['loading' => 'defer', 'priority' => 0]);
$this->grav['assets']->addJs('plugin://git-sync/js/app.js', ['loading' => 'defer', 'priority' => 0]);
$this->grav['assets']->addCss('plugin://git-sync/css-compiled/git-sync-icon.css');
return true;
}
public function onPageInitialized()
{
if ($this->isAdmin() && $this->controller->isActive()) {
$this->controller->execute();
$this->controller->redirect();
}
}
public function onAdminSave($event)
{
$obj = $event['object'];
$adminPath = trim($this->grav['admin']->base, '/');
$isPluginRoute = $this->grav['uri']->path() == "/$adminPath/plugins/" . $this->name;
if ($obj instanceof Data) {
if (!$isPluginRoute || !Helper::isGitInstalled()) {
return true;
} else {
// empty password, keep current one or encrypt if haven't already
$password = $obj->get('password', false);
if (!$password) { // set to !()
$current_password = $this->controller->git->getPassword();
// password exists but was never encrypted
if (substr($current_password, 0, 8) !== 'gitsync-') {
$current_password = Helper::encrypt($current_password);
}
} else {
// password is getting changed
$current_password = Helper::encrypt($password);
}
$obj->set('password', $current_password);
}
}
return $obj;
}
public function onAdminAfterSave($event)
{
if (!$this->grav['config']->get('plugins.git-sync.sync.on_save', true)) {
return true;
}
$obj = $event['object'];
$adminPath = trim($this->grav['admin']->base, '/');
$uriPath = $this->grav['uri']->path();
$isPluginRoute = $uriPath == "/$adminPath/plugins/" . $this->name;
if ($obj instanceof Data) {
$folders = $this->controller->git->getConfig('folders', $event['object']->get('folders', []));
$data_type = preg_replace('#^/' . preg_quote($adminPath, '#') . '/#', '', $uriPath);
$data_type = explode('/', $data_type);
$data_type = array_shift($data_type);
if (!Helper::isGitInstalled() || (!$isPluginRoute && !in_array($this->getFolderMapping($data_type), $folders))) {
return true;
}
if ($isPluginRoute) {
$this->controller->git->setConfig($obj);
// initialize git if not done yet
$this->controller->git->initializeRepository();
// set committer and remote data
$this->controller->git->setUser();
$this->controller->git->addRemote();
}
}
$this->synchronize();
return true;
}
public function onAdminAfterSaveAs()
{
if ($this->grav['config']->get('plugins.git-sync.sync.on_save', true)) {
$this->synchronize();
}
return true;
}
public function onAdminAfterDelete()
{
if ($this->grav['config']->get('plugins.git-sync.sync.on_delete', true)) {
$this->synchronize();
}
return true;
}
public function onAdminAfterMedia()
{
if ($this->grav['config']->get('plugins.git-sync.sync.on_media', true)) {
$this->synchronize();
}
return true;
}
public function onFormProcessed(Event $event)
{
$action = $event['action'];
if ($action == 'gitsync') {
$this->synchronize();
}
}
public function getFolderMapping($data_type) {
switch ($data_type) {
case 'user':
return 'accounts';
case 'themes':
return 'config';
case 'config':
case 'data':
case 'plugins':
case 'pages':
return $data_type;
}
}
}
#!/bin/sh
gosass -input scss/ -output css/ -sourcemap -watch -style compressed
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="bitbucket" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="863.566px" height="267.72px" viewBox="0 0 863.566 267.72" style="enable-background:new 0 0 863.566 267.72;"
xml:space="preserve">
<style type="text/css">
.st0{fill:#205081;}
</style>
<g>
<g>
<g>
<g>
<path class="st0" d="M432.851,136.489c-9.223,0-18.545,2.275-24.859,4.641v-34.558c0-1.738-1.414-3.146-3.15-3.146h-18.628
c-1.736,0-3.13,1.408-3.13,3.146v119.01c0,1.51,1.086,2.826,2.577,3.092c11.492,2.096,25.048,2.83,33.187,2.83
c34.984,0,42.139-14.87,42.139-37.211v-26.049C460.984,147.162,451.519,136.489,432.851,136.489z M419.926,209.871
c-4.717,0-8.624-0.182-11.935-0.571v-46.559c5.13-2.299,12.346-4.616,18.132-4.616c7.058,0,9.938,2.766,9.938,9.568v27.313
C436.061,204.862,433.568,209.871,419.926,209.871z"/>
<path class="st0" d="M546.674,138.31H528.07c-1.736,0-3.146,1.413-3.146,3.127v58.95c-6.71,3.401-16.155,6.929-21.777,6.929
c-4.193,0-5.742-1.549-5.742-5.746v-60.133c0-1.714-1.432-3.127-3.19-3.127h-18.583c-1.762,0-3.154,1.413-3.154,3.127v62.509
c0,18.283,7.195,27.559,21.369,27.559c9.872,0,23.308-3.392,34.652-8.726l1.372,4.645c0.409,1.344,1.617,2.265,3.028,2.265
l13.775-0.004c1.741,0,3.153-1.412,3.153-3.147v-85.1C549.827,139.723,548.415,138.31,546.674,138.31z"/>
<path class="st0" d="M621.62,210.423c-0.121-0.877-0.633-1.647-1.33-2.147c-0.732-0.489-1.636-0.653-2.493-0.46
c-5.337,1.216-10.939,1.874-16.195,1.874c-11.798,0-15.213-3.005-15.213-13.394v-24.6c0-10.397,3.415-13.402,15.213-13.402
c2.806,0,9.794,0.403,16.195,1.869c0.857,0.196,1.761,0.034,2.493-0.471c0.719-0.484,1.227-1.261,1.33-2.142l2.212-14.727
c0.22-1.552-0.694-3.03-2.212-3.474c-7.175-2.112-15.583-2.861-20.549-2.861c-28.132,0-39.604,10.246-39.604,35.392v24.231
c0,25.137,11.472,35.392,39.604,35.392c6.826,0,14.7-1.096,20.549-2.855c1.518-0.449,2.432-1.938,2.212-3.483L621.62,210.423z"
/>
<path class="st0" d="M749.865,136.489c-24.967,0-38.149,12.434-38.149,35.941v23.499c0,24.265,12.364,35.574,38.889,35.574
c10.792,0,22.304-1.892,31.566-5.171c1.472-0.53,2.353-2.075,2.045-3.621l-2.761-13.316c-0.184-0.855-0.716-1.61-1.452-2.05
c-0.755-0.459-1.655-0.58-2.491-0.337c-8.567,2.414-16.687,3.597-24.907,3.597c-13.556,0-15.97-4.393-15.97-13.587v-3.217
h46.213c1.755,0,3.148-1.393,3.148-3.156v-17.13C785.995,148.248,774.524,136.489,749.865,136.489z M736.635,173.262v-3.019
c0-8.77,4.279-12.857,13.419-12.857c10.1,0,12.117,4.928,12.117,12.857v3.019H736.635z"/>
<path class="st0" d="M302.116,138.269H283.51c-1.739,0-3.154,1.418-3.154,3.148v85.085c0,1.745,1.415,3.152,3.154,3.152h18.605
c1.779,0,3.168-1.407,3.168-3.152v-85.085C305.284,139.687,303.895,138.269,302.116,138.269z"/>
<path class="st0" d="M302.116,103.416H283.51c-1.739,0-3.154,1.428-3.154,3.157v16.029c0,1.763,1.415,3.159,3.154,3.159h18.605
c1.779,0,3.168-1.396,3.168-3.159v-16.029C305.284,104.844,303.895,103.416,302.116,103.416z"/>
<g>
<path class="st0" d="M255.251,164.776c7.711-4.242,11.059-10.188,11.059-19.331v-12.513c0-18.323-10.995-27.217-33.572-27.217
h-45.939c-1.721,0-3.133,1.408-3.133,3.145v117.642c0,1.745,1.412,3.152,3.133,3.152h49.213
c21.917,0,34.493-10.643,34.493-29.211v-12.922C270.504,176.444,264.881,168.172,255.251,164.776z M209.308,127.347h20.691
c9.366,0,10.676,4.087,10.676,8.463v10.721c0,6.972-3.255,9.95-10.86,9.95h-4.474c-1.762,0-3.174,1.417-3.174,3.157v14.781
c0,1.751,1.412,3.161,3.174,3.161h8.093c8.119,0,11.413,3.14,11.413,10.849v9.097c0,7.843-3.374,10.488-13.415,10.488h-22.122
V127.347z"/>
</g>
<path class="st0" d="M372.022,211.568c-0.123-0.886-0.612-1.666-1.347-2.166c-0.741-0.489-1.659-0.652-2.518-0.433
c-3.291,0.82-6.441,1.268-8.872,1.268c-4.602,0-6.646-2.003-6.646-6.476v-45.085h18.074c1.74,0,3.148-1.408,3.148-3.151v-14.069
c0-1.734-1.408-3.148-3.148-3.148h-18.074v-21.676c0-0.917-0.41-1.779-1.083-2.372c-0.694-0.606-1.617-0.88-2.52-0.749
l-18.605,2.59c-1.554,0.225-2.713,1.556-2.713,3.129v19.078h-10.145c-1.737,0-3.152,1.413-3.152,3.148v14.069
c0,1.743,1.415,3.151,3.152,3.151h10.145v48.007c0,16.472,8.646,24.835,25.738,24.835c4.868,0,13.128-1.127,18.526-2.999
c1.451-0.499,2.311-1.938,2.085-3.454L372.022,211.568z"/>
<path class="st0" d="M847.374,211.564c-0.122-0.886-0.632-1.666-1.373-2.168c-0.733-0.487-1.657-0.652-2.493-0.427
c-3.314,0.816-6.441,1.268-8.893,1.268c-4.582,0-6.665-2.009-6.665-6.481v-45.085h18.096c1.757,0,3.144-1.417,3.144-3.146
v-14.069c0-1.734-1.387-3.149-3.144-3.149H827.95v-21.68c0-0.919-0.368-1.78-1.066-2.371c-0.695-0.603-1.613-0.877-2.514-0.745
l-18.607,2.59c-1.553,0.22-2.722,1.551-2.722,3.113v19.093h-10.138c-1.722,0-3.147,1.414-3.147,3.149v14.069
c0,1.729,1.426,3.146,3.147,3.146h10.138v48.013c0,16.466,8.671,24.829,25.746,24.829c4.887,0,13.125-1.126,18.524-2.997
c1.433-0.501,2.326-1.934,2.104-3.454L847.374,211.564z"/>
</g>
</g>
</g>
</g>
<path class="st0" d="M708.825,224.391l-24.88-41.074l24.001-40.449c0.578-0.975,0.589-2.184,0.028-3.166
c-0.561-0.984-1.605-1.592-2.737-1.592h-20.553c-1.126,0-2.166,0.602-2.729,1.576l-22.832,39.584v-72.678
c0-1.741-1.411-3.15-3.15-3.15h-18.606c-1.739,0-3.15,1.409-3.15,3.15v119.911c0,1.74,1.411,3.15,3.15,3.15h18.606
c1.739,0,3.15-1.41,3.15-3.15v-39.244l24.295,40.654c0.568,0.951,1.596,1.535,2.704,1.535h20.173c0.006,0,0.014,0,0.02,0
c1.74,0,3.15-1.412,3.15-3.15C709.466,225.58,709.228,224.92,708.825,224.391z"/>
<path class="st0" d="M89.219,94.963v0.002V94.963c-33.675,0-61.172,9.053-61.172,20.294c0,2.961,7.343,45.415,10.256,62.251
c1.306,7.551,20.818,18.621,50.9,18.621l0.031-0.09v0.09c30.081,0,49.593-11.07,50.899-18.621
c2.913-16.836,10.256-59.29,10.256-62.251C150.39,104.017,122.894,94.963,89.219,94.963z M89.219,182.49
c-10.739,0-19.445-8.707-19.445-19.445c0-10.74,8.706-19.445,19.445-19.445c10.739,0,19.445,8.705,19.445,19.445
C108.664,173.783,99.958,182.49,89.219,182.49z M89.208,121.53c-21.636-0.035-39.169-3.794-39.162-8.398
c0.007-4.604,17.554-8.307,39.19-8.272c21.636,0.034,39.169,3.793,39.162,8.398C128.39,117.863,110.844,121.564,89.208,121.53z"/>
<path class="st0" d="M133.185,194.382c-0.93,0-1.675,0.658-1.675,0.658s-15.064,11.929-42.29,11.93
c-27.227-0.001-42.29-11.93-42.29-11.93s-0.746-0.658-1.675-0.658c-1.111,0-2.164,0.746-2.164,2.393
c0,0.174,0.017,0.348,0.049,0.518c2.338,12.514,4.046,21.393,4.346,22.744c2.041,9.205,20.048,16.151,41.733,16.152l0,0h0.001h0.001
l0,0c21.686-0.001,39.692-6.947,41.733-16.152c0.3-1.352,2.008-10.23,4.346-22.744c0.032-0.17,0.049-0.344,0.049-0.518
C135.349,195.128,134.295,194.382,133.185,194.382z"/>
<circle class="st0" cx="89.2" cy="163.039" r="9.745"/>
</svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 2002 542" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;">
<g transform="matrix(1,0,0,1,-199.492,-929.431)">
<g id="Layer-1" transform="matrix(4.16667,0,0,4.16667,0,0)">
<path d="M140.375,278.65L100.019,278.65C98.978,278.65 98.133,279.495 98.133,280.537L98.133,300.267C98.133,301.308 98.978,302.156 100.019,302.156L115.762,302.156L115.762,326.67C115.762,326.67 112.227,327.875 102.454,327.875C90.924,327.875 74.817,323.662 74.817,288.243C74.817,252.818 91.589,248.157 107.335,248.157C120.965,248.157 126.837,250.556 130.573,251.713C131.747,252.072 132.833,250.903 132.833,249.862L137.335,230.798C137.335,230.31 137.17,229.723 136.614,229.325C135.097,228.242 125.84,223.063 102.454,223.063C75.513,223.063 47.878,234.525 47.878,289.625C47.878,344.726 79.518,352.937 106.18,352.937C128.256,352.937 141.648,343.504 141.648,343.504C142.2,343.199 142.26,342.427 142.26,342.074L142.26,280.537C142.26,279.495 141.416,278.65 140.375,278.65" style="fill:rgb(14,13,13);fill-rule:nonzero;"/>
<path d="M348.353,229.665C348.353,228.615 347.521,227.768 346.48,227.768L323.757,227.768C322.719,227.768 321.875,228.615 321.875,229.665C321.875,229.67 321.881,273.578 321.881,273.578L286.462,273.578L286.462,229.665C286.462,228.615 285.626,227.768 284.587,227.768L261.866,227.768C260.832,227.768 259.989,228.615 259.989,229.665L259.989,348.568C259.989,349.617 260.832,350.471 261.866,350.471L284.587,350.471C285.626,350.471 286.462,349.617 286.462,348.568L286.462,297.709L321.881,297.709C321.881,297.709 321.82,348.564 321.82,348.568C321.82,349.617 322.663,350.471 323.702,350.471L346.478,350.471C347.519,350.471 348.351,349.617 348.353,348.568L348.353,229.665Z" style="fill:rgb(14,13,13);fill-rule:nonzero;"/>
<path d="M183.254,245.268C183.254,237.086 176.694,230.475 168.601,230.475C160.516,230.475 153.951,237.086 153.951,245.268C153.951,253.442 160.516,260.072 168.601,260.072C176.694,260.072 183.254,253.442 183.254,245.268" style="fill:rgb(14,13,13);fill-rule:nonzero;"/>
<path d="M181.629,323.486L181.629,268.6C181.629,267.558 180.788,266.706 179.749,266.706L157.098,266.706C156.059,266.706 155.129,267.778 155.129,268.819L155.129,347.455C155.129,349.765 156.569,350.453 158.433,350.453L178.841,350.453C181.08,350.453 181.629,349.353 181.629,347.418L181.629,323.486Z" style="fill:rgb(14,13,13);fill-rule:nonzero;"/>
<path d="M434.71,266.885L412.161,266.885C411.127,266.885 410.285,267.738 410.285,268.786L410.285,327.088C410.285,327.088 404.557,331.28 396.426,331.28C388.296,331.28 386.138,327.59 386.138,319.629L386.138,268.786C386.138,267.738 385.298,266.885 384.263,266.885L361.378,266.885C360.345,266.885 359.499,267.738 359.499,268.786L359.499,323.479C359.499,347.125 372.678,352.91 390.808,352.91C405.681,352.91 417.673,344.694 417.673,344.694C417.673,344.694 418.244,349.024 418.502,349.537C418.761,350.049 419.434,350.567 420.161,350.567L434.72,350.502C435.753,350.502 436.599,349.649 436.599,348.604L436.592,268.786C436.592,267.738 435.749,266.885 434.71,266.885" style="fill:rgb(14,13,13);fill-rule:nonzero;"/>
<path d="M487.445,331.207C479.624,330.969 474.319,327.42 474.319,327.42L474.319,289.766C474.319,289.766 479.552,286.558 485.973,285.984C494.093,285.257 501.918,287.71 501.918,307.08C501.918,327.506 498.386,331.537 487.445,331.207M496.339,264.214C483.532,264.214 474.821,269.928 474.821,269.928L474.821,229.665C474.821,228.615 473.982,227.768 472.946,227.768L450.159,227.768C449.123,227.768 448.281,228.615 448.281,229.665L448.281,348.568C448.281,349.617 449.123,350.471 450.162,350.471L465.971,350.471C466.683,350.471 467.222,350.103 467.621,349.461C468.013,348.822 468.581,343.978 468.581,343.978C468.581,343.978 477.898,352.809 495.538,352.809C516.246,352.809 528.122,342.305 528.122,305.654C528.122,269.003 509.155,264.214 496.339,264.214" style="fill:rgb(14,13,13);fill-rule:nonzero;"/>
<path d="M246.935,266.695L229.891,266.695C229.891,266.695 229.866,244.182 229.866,244.177C229.866,243.324 229.426,242.899 228.44,242.899L205.212,242.899C204.309,242.899 203.825,243.296 203.825,244.164L203.825,267.433C203.825,267.433 192.184,270.243 191.397,270.47C190.615,270.698 190.038,271.421 190.038,272.283L190.038,286.905C190.038,287.957 190.877,288.805 191.915,288.805L203.825,288.805L203.825,323.984C203.825,350.113 222.154,352.679 234.521,352.679C240.171,352.679 246.932,350.865 248.047,350.453C248.723,350.205 249.116,349.506 249.116,348.748L249.134,332.662C249.134,331.613 248.248,330.763 247.249,330.763C246.256,330.763 243.716,331.168 241.099,331.168C232.728,331.168 229.891,327.275 229.891,322.236C229.891,317.201 229.89,288.805 229.89,288.805L246.935,288.805C247.974,288.805 248.817,287.957 248.817,286.905L248.817,268.59C248.817,267.54 247.974,266.695 246.935,266.695" style="fill:rgb(14,13,13);fill-rule:nonzero;"/>
</g>
</g>
</svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="1202px" height="455px" viewBox="0 0 1202 455" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
<!-- Generator: Sketch 3.2.2 (9983) - http://www.bohemiancoding.com/sketch -->
<title>wm_no_bg</title>
<desc>Created with Sketch.</desc>
<defs>
<path id="path-1" d="M0,1173.3333 L1999.99995,1173.3333 L1999.99995,0 L0,0 L0,1173.3333 L0,1173.3333 Z"></path>
</defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
<g id="gitlab_logo" sketch:type="MSLayerGroup" transform="translate(-439.000000, -360.000000)">
<g id="g10" transform="translate(1000.000000, 587.333300) scale(1, -1) translate(-1000.000000, -587.333300) translate(0.000000, 0.333300)">
<g id="g12" transform="translate(1316.014500, 505.244121)" fill="#8C929D" sketch:type="MSShapeGroup">
<path d="M22.6666661,162.666663 L0.835999979,162.666663 L0.905333311,0.178666662 L89.2199978,0.178666662 L89.2199978,20.2719995 L22.7359994,20.2719995 L22.6666661,162.666663 L22.6666661,162.666663 Z" id="path14"></path>
</g>
<g id="g16">
<g id="g18-Clipped">
<mask id="mask-2" sketch:name="path22" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<g id="path22"></g>
<g id="g18" mask="url(#mask-2)">
<g transform="translate(438.666658, 358.666658)">
<g id="g24" stroke-width="1" fill="none" sketch:type="MSLayerGroup" transform="translate(977.327440, 143.284396)">
<path d="M73.3333315,31.9999992 C67.8759983,26.294666 58.6973319,20.5879995 46.2933322,20.5879995 C29.6719993,20.5879995 22.9746661,28.7746659 22.9746661,39.4426657 C22.9746661,55.5666653 34.1373325,63.2573318 57.9533319,63.2573318 C62.4186651,63.2573318 69.6119983,62.7613318 73.3333315,62.0173318 L73.3333315,31.9999992 L73.3333315,31.9999992 Z M50.7586654,130.48533 C33.1293325,130.48533 16.9599996,124.235997 4.34266656,113.83333 L12.0586664,100.467997 C20.9893328,105.677331 31.9053325,110.887997 47.5333321,110.887997 C65.394665,110.887997 73.3333315,101.707997 73.3333315,86.3279978 L73.3333315,78.3893314 C69.8599983,79.1333314 62.6666651,79.6306647 58.2013319,79.6306647 C19.9973328,79.6306647 0.647999984,66.234665 0.647999984,38.2013324 C0.647999984,13.1466663 16.0279996,0.494666654 39.3466657,0.494666654 C55.0546653,0.494666654 70.1079982,7.68933314 75.3173315,19.3479995 L79.2866647,3.47199991 L94.6679976,3.47199991 L94.6679976,86.5759978 C94.6679976,112.871997 83.2559979,130.48533 50.7586654,130.48533 L50.7586654,130.48533 Z" id="path26" fill="#8C929D" sketch:type="MSShapeGroup"></path>
</g>
<g id="g28" stroke-width="1" fill="none" sketch:type="MSLayerGroup" transform="translate(1099.766904, 143.128930)">
<path d="M42.6666656,19.9999995 C34.4799991,19.9999995 27.2853327,20.9919995 21.8279995,23.4733327 L21.8279995,90.8253311 L21.8279995,98.6386642 C29.2706659,104.841331 38.4493324,109.306664 50.1093321,109.306664 C71.1946649,109.306664 79.3813313,94.4226643 79.3813313,70.3586649 C79.3813313,36.1253324 66.2333317,19.9999995 42.6666656,19.9999995 M51.841332,130.64133 C32.3306659,130.64133 21.8279995,117.367997 21.8279995,117.367997 L21.8279995,138.330663 L21.7586661,166.114663 L11.9159997,166.114663 L0.425333323,166.114663 L0.494666654,7.59599981 C11.1613331,3.13066659 25.7973327,0.65066665 41.6746656,0.65066665 C82.3586646,0.65066665 101.955997,26.6973327 101.955997,71.5986649 C101.955997,107.073331 83.8426646,130.64133 51.841332,130.64133" id="path30" fill="#8C929D" sketch:type="MSShapeGroup"></path>
</g>
<g id="g32" stroke-width="1" fill="none" sketch:type="MSLayerGroup" transform="translate(584.042117, 143.630796)">
<path d="M78.6666647,147.999996 C98.0159975,147.999996 110.419997,141.550663 118.606664,135.099997 L127.987997,151.350663 C115.20133,162.563996 98.0026642,168.590662 79.6586647,168.590662 C33.2693325,168.590662 0.771999981,140.30933 0.771999981,83.2533313 C0.771999981,23.4666661 35.8306658,0.147999996 75.9373314,0.147999996 C96.0319976,0.147999996 113.149331,4.86133321 124.311997,9.57466643 L123.854664,73.4533315 L123.854664,80.9893313 L123.854664,93.5479977 L64.3173317,93.5479977 L64.3173317,73.4533315 L102.271997,73.4533315 L102.729331,24.9559994 C97.7679976,22.4746661 89.0853311,20.4906662 77.4266647,20.4906662 C45.1773322,20.4906662 23.5946661,40.7773323 23.5946661,83.5013312 C23.5946661,126.91333 45.9213322,147.999996 78.6666647,147.999996" id="path34" fill="#8C929D" sketch:type="MSShapeGroup"></path>
</g>
<g id="g36" stroke-width="1" fill="none" sketch:type="MSLayerGroup" transform="translate(793.569045, 142.577463)">
<path d="M22.6666661,166.666662 L1.33199997,166.666662 L1.4013333,139.378663 L1.4013333,128.214663 L1.4013333,121.707997 L1.4013333,110.353331 L1.4013333,45.3586655 L1.4013333,45.1106655 C1.4013333,18.8146662 12.813333,1.2013333 45.3106655,1.2013333 C49.7999988,1.2013333 54.193332,1.60933329 58.4586652,2.38399994 L58.4586652,21.5426661 C55.3706653,21.0693328 52.0746654,20.7999995 48.5359988,20.7999995 C30.6746659,20.7999995 22.7359994,29.9786659 22.7359994,45.3586655 L22.7359994,110.353331 L58.4586652,110.353331 L58.4586652,128.214663 L22.7359994,128.214663 L22.6666661,166.666662 L22.6666661,166.666662 Z" id="path38" fill="#8C929D" sketch:type="MSShapeGroup"></path>
</g>
<path d="M740.766646,146.755996 L762.101312,146.755996 L762.101312,270.793327 L740.766646,270.793327 L740.766646,146.755996 L740.766646,146.755996 Z" id="path40" fill="#8C929D" sketch:type="MSShapeGroup"></path>
<path d="M740.766646,287.909326 L762.101312,287.909326 L762.101312,309.243992 L740.766646,309.243992 L740.766646,287.909326 L740.766646,287.909326 Z" id="path42" fill="#8C929D" sketch:type="MSShapeGroup"></path>
<g id="g44" stroke-width="1" fill="none" sketch:type="MSLayerGroup" transform="translate(0.532000, 0.774933)">
<path d="M491.999988,194.666662 L464.441322,279.481326 L409.82399,447.578655 C407.014656,456.226655 394.778657,456.226655 391.96799,447.578655 L337.349325,279.481326 L155.982663,279.481326 L101.362664,447.578655 C98.5533309,456.226655 86.3173312,456.226655 83.5066646,447.578655 L28.8893326,279.481326 L1.33199997,194.666662 C-1.18266664,186.930662 1.57199996,178.455996 8.1519998,173.674662 L246.665327,0.385333324 L485.179988,173.674662 C491.759988,178.455996 494.513321,186.930662 491.999988,194.666662" id="path46" fill="#FC6D26" sketch:type="MSShapeGroup"></path>
</g>
<g id="g48" stroke-width="1" fill="none" sketch:type="MSLayerGroup" transform="translate(155.197863, 1.160267)">
<path d="M91.9999977,0 L91.9999977,0 L182.683995,279.095993 L1.31599997,279.095993 L91.9999977,0 L91.9999977,0 Z" id="path50" fill="#E24329" sketch:type="MSShapeGroup"></path>
</g>
<g id="g52" stroke-width="1" fill="none" sketch:type="MSLayerGroup" transform="translate(247.197860, 1.160267)">
<g id="path54"></g>
</g>
<g id="g56" stroke-width="1" fill="none" sketch:type="MSLayerGroup" transform="translate(28.531199, 1.160800)">
<path d="M218.666661,0 L127.982663,279.09466 L0.890666644,279.09466 L218.666661,0 L218.666661,0 Z" id="path58" fill="#FC6D26" sketch:type="MSShapeGroup"></path>
</g>
<g id="g60" stroke-width="1" fill="none" sketch:type="MSLayerGroup" transform="translate(247.197860, 1.160800)">
<g id="path62"></g>
</g>
<g id="g64" stroke-width="1" fill="none" sketch:type="MSLayerGroup" transform="translate(0.088533, 0.255867)">
<path d="M29.3333326,279.999993 L29.3333326,279.999993 L1.77466662,195.185328 C-0.738666648,187.449329 2.01466662,178.974662 8.59599979,174.194662 L247.109327,0.905333311 L29.3333326,279.999993 L29.3333326,279.999993 Z" id="path66" fill="#FCA326" sketch:type="MSShapeGroup"></path>
</g>
<g id="g68" stroke-width="1" fill="none" sketch:type="MSLayerGroup" transform="translate(247.197860, 1.160267)">
<g id="path70"></g>
</g>
<g id="g72" stroke-width="1" fill="none" sketch:type="MSLayerGroup" transform="translate(29.421866, 280.255593)">
<path d="M0,0 L127.091997,0 L72.4733315,168.097329 C69.6626649,176.746662 57.4266652,176.746662 54.617332,168.097329 L0,0 L0,0 Z" id="path74" fill="#E24329" sketch:type="MSShapeGroup"></path>
</g>
<g id="g76" stroke-width="1" fill="none" sketch:type="MSLayerGroup" transform="translate(247.197860, 1.160800)">
<path d="M0,0 L90.6839977,279.09466 L217.775995,279.09466 L0,0 L0,0 Z" id="path78" fill="#FC6D26" sketch:type="MSShapeGroup"></path>
</g>
<g id="g80" stroke-width="1" fill="none" sketch:type="MSLayerGroup" transform="translate(246.307061, 0.255867)">
<path d="M218.666661,279.999993 L218.666661,279.999993 L246.225327,195.185328 C248.73866,187.449329 245.985327,178.974662 239.403994,174.194662 L0.890666644,0.905333311 L218.666661,279.999993 L218.666661,279.999993 Z" id="path82" fill="#FCA326" sketch:type="MSShapeGroup"></path>
</g>
<g id="g84" stroke-width="1" fill="none" sketch:type="MSLayerGroup" transform="translate(336.973725, 280.255593)">
<path d="M127.999997,0 L0.907999977,0 L55.5266653,168.097329 C58.3373319,176.746662 70.5733316,176.746662 73.3826648,168.097329 L127.999997,0 L127.999997,0 Z" id="path86" fill="#E24329" sketch:type="MSShapeGroup"></path>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
viewBox="0 0 272.96 114.01"
version="1.1"
id="svg10"
sodipodi:docname="gitlogo.svg"
inkscape:version="0.92.0 r15299">
<metadata
id="metadata16">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs14" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1070"
inkscape:window-height="735"
id="namedview12"
showgrid="false"
inkscape:zoom="1.0807445"
inkscape:cx="115.66102"
inkscape:cy="55.154427"
inkscape:window-x="2350"
inkscape:window-y="328"
inkscape:window-maximized="0"
inkscape:current-layer="svg10" />
<path
fill="#f05133"
d="M111.78,51.977,62.035,2.2381c-2.8622-2.8648-7.5082-2.8648-10.374,0l-10.329,10.33,13.102,13.102c3.0459-1.0284,6.5371-0.33888,8.9639,2.0884,2.4394,2.4424,3.124,5.9634,2.0698,9.0195l12.628,12.628c3.0551-1.0528,6.58-0.37262,9.0195,2.0712,3.4106,3.4096,3.4106,8.9345,0,12.345-3.4111,3.4116-8.936,3.4116-12.349,0-2.5645-2.5665-3.1988-6.3345-1.8999-9.4942l-11.777-11.777-0.001,30.991c0.8315,0.41162,1.6162,0.961,2.3091,1.6509,3.4096,3.4092,3.4096,8.9331,0,12.348-3.4106,3.4091-8.938,3.4091-12.345,0-3.4101-3.4146-3.4101-8.9385,0-12.348,0.84275-0.84125,1.8179-1.478,2.8584-1.9048v-31.279c-1.041-0.425-2.015-1.057-2.859-1.905-2.583-2.581-3.2051-6.372-1.8804-9.5439l-12.916-12.918-34.106,34.105c-2.8657,2.867-2.8657,7.513,0,10.378l49.742,49.739c2.8638,2.8648,7.5082,2.8648,10.376,0l49.512-49.504c2.8648-2.8662,2.8648-7.5136,0-10.379"
id="path8" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:46.82492828px;line-height:27.43648148px;font-family:'Helvetica Rounded LT Std';-inkscape-font-specification:'Helvetica Rounded LT Std';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#999999;fill-opacity:1;stroke:none;stroke-width:1.0974592px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;"
x="128.30931"
y="69.483566"
id="text20"
transform="scale(0.9595033,1.0422059)"><tspan
sodipodi:role="line"
id="tspan18"
x="128.30931"
y="69.483566"
style="fill:#999999;stroke-width:1.0974592px;font-family: 'HelveticaNeue-Light', 'Helvetica Neue Light', 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif;font-weight: 300">Others</tspan></text>
</svg>
!function(e){function t(t){for(var n,s,d=t[0],i=t[1],c=t[2],u=0,f=[];u<d.length;u++)s=d[u],o[s]&&f.push(o[s][0]),o[s]=0;for(n in i)Object.prototype.hasOwnProperty.call(i,n)&&(e[n]=i[n]);for(l&&l(t);f.length;)f.shift()();return r.push.apply(r,c||[]),a()}function a(){for(var e,t=0;t<r.length;t++){for(var a=r[t],n=!0,d=1;d<a.length;d++){var i=a[d];0!==o[i]&&(n=!1)}n&&(r.splice(t--,1),e=s(s.s=a[0]))}return e}var n={},o={0:0},r=[];function s(t){if(n[t])return n[t].exports;var a=n[t]={i:t,l:!1,exports:{}};return e[t].call(a.exports,a,a.exports,s),a.l=!0,a.exports}s.m=e,s.c=n,s.d=function(e,t,a){s.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:a})},s.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},s.t=function(e,t){if(1&t&&(e=s(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var a=Object.create(null);if(s.r(a),Object.defineProperty(a,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var n in e)s.d(a,n,function(t){return e[t]}.bind(null,n));return a},s.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return s.d(t,"a",t),t},s.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},s.p="";var d=window.webpackJsonp=window.webpackJsonp||[],i=d.push.bind(d);d.push=t,d=d.slice();for(var c=0;c<d.length;c++)t(d[c]);var l=i;r.push([7,1]),a()}([,function(e,t){e.exports=jQuery},function(e,t){e.exports=GravAdmin},,,function(e,t,a){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var n,o=a(13),r=(n=o)&&n.__esModule?n:{default:n};r.default.options.positionClass="toast-top-right",r.default.options.preventDuplicates=!0,t.default=r.default},function(e,t,a){"use strict";(function(e){Object.defineProperty(t,"__esModule",{value:!0});var n=r(a(1)),o=r(a(6));function r(e){return e&&e.__esModule?e:{default:e}}var s=(0,n.default)("#offline-status");(0,n.default)(window).on("offline",function(){s.slideDown()}),(0,n.default)(window).on("online",function(){s.slideUp()}),(0,n.default)(document).ready(function(){o.default||s.slideDown()}),t.default=void 0===e.navigator.onLine||e.navigator.onLine}).call(this,a(0))},function(e,t,a){"use strict";a(8)},function(e,t,a){"use strict";(function(e){Object.defineProperty(t,"__esModule",{value:!0});var n=i(a(9)),o=i(a(10)),r=i(a(5)),s=a(2),d=i(a(1));function i(e){return e&&e.__esModule?e:{default:e}}var c=(0,d.default)('[data-remodal-id="wizard"]'),l=(0,d.default)('[data-remodal-id="reset-local"]'),u={github:"github.com",bitbucket:"bitbucket.org",gitlab:"gitlab.com",allothers:"allothers.repo"},f={REPO_URL:"https://{placeholder}/getgrav/grav.git"},p=function(){var e=c.remodal({closeOnConfirm:!1}),t=c.find('[data-gitsync-action="previous"]'),a=c.find('[data-gitsync-action="next"]'),n=c.find('[data-gitsync-action="save"]');m=0,c.find("form > [class^=step-]:not(.step-"+m+") > .panel").hide().removeClass("hidden"),c.find('form > [class="step-'+m+'"] > .panel').show(),a.removeClass("hidden"),t.addClass("hidden"),n.addClass("hidden");var o=(0,d.default)('[name="data[webhook]"]').val(),r=(0,d.default)('[name="data[webhook_secret]"]').val();(0,d.default)('[name="gitsync[repository]"]').trigger("change"),(0,d.default)('[name="gitsync[webhook]"]').val(o),(0,d.default)('[name="gitsync[webhook_secret]"]').val(r),(0,d.default)(".gitsync-webhook").text(o),e.open()},h=function(e){e.attr("disabled","disabled").addClass("hint--top")},v=function(e){e.attr("disabled",null).removeClass("hint--top")},m=0,g=0,b=null;(0,d.default)(document).on("closed",c,function(e){m=0}),(0,d.default)(document).on("click","[data-gitsync-useraction]",function(e){e.preventDefault();var t=(0,d.default)(e.target).closest("[data-gitsync-useraction]"),a=t.data("gitsyncUseraction"),n=s.config.current_url+".json";switch(a){case"wizard":p();break;case"sync":var r=t.data("gitsync-uri");t.find("i").removeClass("fa-cloud fa-git").addClass("fa-circle-o-notch fa-spin"),(0,o.default)(r||n,{method:"post",body:{task:"synchronize"}},function(){t.find("i").removeClass("fa-circle-o-notch fa-spin").addClass(r?"fa-git":"fa-cloud")});break;case"reset":var i=l.remodal({closeOnConfirm:!1});i.open(),l.data("_reset_event_set_")||l.find('[data-gitsync-action="reset-local"]').one("click",function(){i.close(),l.data("_reset_event_set_",!0),t.find("i").removeClass("fa-history").addClass("fa-circle-o-notch fa-spin"),(0,o.default)(n,{method:"post",body:{task:"resetlocal"}},function(){l.data("_reset_event_set_",!1),t.find("i").removeClass("fa-circle-o-notch fa-spin").addClass("fa-history")})})}}),(0,d.default)(document).on("click","[data-gitsync-action]",function(t){t.preventDefault();var a=(0,d.default)(t.target).closest("[data-gitsync-action]"),n=c.find('[data-gitsync-action="previous"]'),i=c.find('[data-gitsync-action="next"]'),l=c.find('[data-gitsync-action="save"]'),u=a.data("gitsyncAction"),f=(0,d.default)('[name="gitsync[repo_user]"]').val(),p=(0,d.default)('[name="gitsync[repo_password]"]').val(),b=(0,d.default)('[name="gitsync[repo_url]"]').val(),y=(0,d.default)('[name="gitsync[webhook]"]').val(),k=(0,d.default)('[name="gitsync[webhook_enabled]"]').is(":checked"),_=(0,d.default)('[name="gitsync[webhook_secret]"]').val();if(!a.attr("disabled")){var w=[];if(f||w.push("Username is missing."),b||w.push("Repository is missing."),["save","test"].includes(u)&&w.length)return r.default.error(w.join("<br />")),!1;if("save"===u){var C=(0,d.default)('[name="gitsync[folders]"]:checked').map(function(e,t){return t.value});(0,d.default)('[name="data[repository]"]').val(b),(0,d.default)('[name="data[user]"]').val(f),(0,d.default)('[name="data[password]"]').val(p),(0,d.default)('[name="data[webhook]"]').val(y),(0,d.default)('[name="data[webhook_enabled]"][value="'+(k?1:0)+'"]').prop("checked",!0),(0,d.default)('[name="data[webhook_secret]"]').val(_);var O=(0,d.default)('[name="data[folders][]"]');return O&&O[0]&&O[0].selectize&&O[0].selectize.setValue(C.toArray()),(0,d.default)('[name="task"][value="save"]').trigger("click"),!1}if("test"===u){var j=s.config.current_url+".json",x=e.btoa(JSON.stringify({user:f,password:p,repository:b}));return(0,o.default)(j,{method:"post",body:{test:x,task:"testConnection"}}),!1}if(c.find(".step-"+m+" > .panel").slideUp(),m+="next"===u?1:-1,c.find(".step-"+m+" > .panel").slideDown(),l.addClass("hidden"),"next"===u&&n.removeClass("hidden"),m<=0&&(n.addClass("hidden"),v(i)),m>0&&i.removeClass("hidden"),1===m)(0,d.default)('[name="gitsync[repository]"]:checked').length?v(i):h(i);if(2===m)(0,d.default)('[name="gitsync[repo_url]"]').val().length?v(i):h(i);m===g&&(i.addClass("hidden"),n.removeClass("hidden"),l.removeClass("hidden"))}}),(0,d.default)(document).on("change",'[name="gitsync[repository]"]',function(){v(c.find('[data-gitsync-action="next"]'))}),(0,d.default)(document).on("input",'[name="gitsync[repo_url]"]',function(e){var t=(0,d.default)(e.currentTarget).val(),a=c.find('[data-gitsync-action="next"]');t.length?v(a):h(a)}),(0,d.default)(document).on("keyup",'[data-gitsync-uribase] [name="gitsync[webhook]"]',function(e){var t=(0,d.default)(e.currentTarget).val();(0,d.default)(".gitsync-webhook").text(t)}),(0,d.default)(document).on("keyup",'[data-gitsync-uribase] [name="gitsync[webhook_secret]"]',function(e){(0,d.default)('[data-gitsync-uribase] [name="gitsync[webhook_enabled]"]').trigger("change")}),(0,d.default)(document).on("change",'[data-gitsync-uribase] [name="gitsync[webhook_enabled]"]',function(e){var t=(0,d.default)(e.currentTarget),a=t.is(":checked"),n=(0,d.default)('[name="gitsync[webhook_secret]"]').val();t.closest(".webhook-secret-wrapper").find("label:last-child")[a?"removeClass":"addClass"]("hidden"),(0,d.default)(".gitsync-webhook-secret").html(a&&n.length?"<code>"+n+"</code>":"<em>leave empty</em>")}),(0,d.default)(document).on("change",'[name="gitsync[repository]"]',function(e){var t=(0,d.default)(e.target);t.is(":checked")&&(b=t.val(),Object.keys(u).forEach(function(e){c.find(".hidden-step-"+e)[e===b?"removeClass":"addClass"]("hidden"),e===b&&(c.find(".webhook-secret-wrapper")["bitbucket"===e?"addClass":"removeClass"]("hidden"),c.find('input[name="gitsync[repo_url]"][placeholder]').attr("placeholder",f.REPO_URL.replace(/\{placeholder\}/,u[e])))}))}),(0,d.default)(document).on("click","[data-access-tokens-details]",function(e){e.preventDefault();var t=(0,d.default)(e.currentTarget),a=t.closest(".access-tokens").find(".access-tokens-details");a.slideToggle(250,function(){var e=a.is(":visible");t.find(".fa").removeClass("fa-chevron-down fa-chevron-up").addClass("fa-chevron-"+(e?"up":"down"))})});var y=function(e){var t=(0,d.default)(e),a=t.val().replace(/\//g,"-"),n=t.closest(".columns").find(".column:last");n.find('[class*="description-"]').addClass("hidden"),n.find(".description-"+a).removeClass("hidden").hide().fadeIn({duration:250})};(0,d.default)(document).on("input",'[data-remodal-id="wizard"] .step-4 input[type="checkbox"]',function(e){var t=(0,d.default)(e.currentTarget);t.is(":checked")&&y(t)}),(0,d.default)(document).on("mouseenter",'[data-remodal-id="wizard"] .step-4 .info-desc',function(e){var t=(0,d.default)(e.currentTarget).siblings('input[type="checkbox"]');y(t)}),(0,d.default)(document).on("mouseleave",'[data-remodal-id="wizard"] .step-4 label',function(e){(0,d.default)(e.currentTarget).closest(".columns").find(".column:last-child").find('[class*="description-"]').addClass("hidden")}),(0,d.default)(document).on("mouseleave",'[data-remodal-id="wizard"] .columns .column:first-child',function(e){(0,d.default)(e.currentTarget).siblings(".column").find('[class*="description-"]').addClass("hidden")}),(0,d.default)(document).ready(function(){g=c.find('[class^="step-"]').length-1,c.wrapInner("<form></form>"),l.wrapInner("<form></form>"),!c.length||!n.default.first_time&&n.default.git_installed||p()}),t.default=n.default}).call(this,a(0))},function(e,t){e.exports=GitSync},function(e,t,a){"use strict";(function(e){Object.defineProperty(t,"__esModule",{value:!0});var n=a(12),o=a(2),r=void 0;t.default=function(t){var a=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},s=arguments.length>2&&void 0!==arguments[2]?arguments[2]:function(){return!0};if("function"==typeof a&&(s=a,a={}),a.method&&"post"===a.method){var d=new FormData;a.body=Object.assign({"admin-nonce":o.config.admin_nonce},a.body||{}),Object.keys(a.body).map(function(e){return d.append(e,a.body[e])}),a.body=d}return a=Object.assign({credentials:"same-origin",headers:{Accept:"application/json"}},a),e(t,a).then(function(e){return r=e,e}).then(n.parseStatus).then(n.parseJSON).then(n.userFeedback).then(function(e){return s(e,r)}).catch(n.userFeedbackError)}}).call(this,a(11))},,function(e,t,a){"use strict";(function(e){Object.defineProperty(t,"__esModule",{value:!0}),t.parseStatus=function(e){return e},t.parseJSON=function(e){return e.text().then(function(e){var t=e;try{t=JSON.parse(e)}catch(t){var a=document.createElement("div");a.innerHTML=e;var n=new Error;throw n.stack=(0,d.default)(a.innerText),n}return t})},t.userFeedback=function(e){if(c)return!0;var t=e.status||(e.error?"error":""),a=e.message||(e.error?e.error.message:null),n=e.toastr||null,d=void 0;switch(t){case"unauthenticated":throw document.location.href=s.config.base_url_relative,l("Logged out");case"unauthorized":t="error",a=a||"Unauthorized.";break;case"error":t="error",a=a||"Unknown error.";break;case"success":t="success",a=a||"";break;default:t="error",a=a||"Invalid AJAX response."}n&&(d=Object.assign({},o.default.options),Object.keys(n).forEach(function(e){o.default.options[e]=n[e]}));a&&(r.default||!r.default&&"error"!==t)&&o.default["success"===t?"success":"error"](a);n&&(o.default.options=d);return e},t.userFeedbackError=function(e){if(c)return!0;var t=e.stack?"<pre><code>"+e.stack+"</code></pre>":"";o.default.error("Fetch Failed: <br /> "+e.message+" "+t)};var n=i(a(1)),o=i(a(5)),r=i(a(6)),s=a(2),d=i(a(15));function i(e){return e&&e.__esModule?e:{default:e}}var c=!1,l=function(e){var t=new Error(e.statusText||e||"");return t.response=e,t};(0,n.default)(e).on("beforeunload._ajax",function(){c=!0})}).call(this,a(0))}]);
\ No newline at end of file
(window.webpackJsonp=window.webpackJsonp||[]).push([[1],[function(e,t){var r;r=function(){return this}();try{r=r||Function("return this")()||(0,eval)("this")}catch(e){"object"==typeof window&&(r=window)}e.exports=r},,,function(e,t){e.exports=function(e){return null==e?"":e.toString()}},function(e,t){e.exports=[" ","\n","\r","\t","\f","\v"," "," ","᠎"," "," "," "," "," "," "," "," "," "," "," ","\u2028","\u2029"," "," "," "]},,,,,,,function(e,t,r){(function(t){(function(){!function(e){"use strict";if(!e.fetch){var t={searchParams:"URLSearchParams"in e,iterable:"Symbol"in e&&"iterator"in Symbol,blob:"FileReader"in e&&"Blob"in e&&function(){try{return new Blob,!0}catch(e){return!1}}(),formData:"FormData"in e,arrayBuffer:"ArrayBuffer"in e};if(t.arrayBuffer)var r=["[object Int8Array]","[object Uint8Array]","[object Uint8ClampedArray]","[object Int16Array]","[object Uint16Array]","[object Int32Array]","[object Uint32Array]","[object Float32Array]","[object Float64Array]"],n=function(e){return e&&DataView.prototype.isPrototypeOf(e)},o=ArrayBuffer.isView||function(e){return e&&r.indexOf(Object.prototype.toString.call(e))>-1};l.prototype.append=function(e,t){e=a(e),t=u(t);var r=this.map[e];this.map[e]=r?r+","+t:t},l.prototype.delete=function(e){delete this.map[a(e)]},l.prototype.get=function(e){return e=a(e),this.has(e)?this.map[e]:null},l.prototype.has=function(e){return this.map.hasOwnProperty(a(e))},l.prototype.set=function(e,t){this.map[a(e)]=u(t)},l.prototype.forEach=function(e,t){for(var r in this.map)this.map.hasOwnProperty(r)&&e.call(t,this.map[r],r,this)},l.prototype.keys=function(){var e=[];return this.forEach(function(t,r){e.push(r)}),c(e)},l.prototype.values=function(){var e=[];return this.forEach(function(t){e.push(t)}),c(e)},l.prototype.entries=function(){var e=[];return this.forEach(function(t,r){e.push([r,t])}),c(e)},t.iterable&&(l.prototype[Symbol.iterator]=l.prototype.entries);var i=["DELETE","GET","HEAD","OPTIONS","POST","PUT"];m.prototype.clone=function(){return new m(this,{body:this._bodyInit})},y.call(m.prototype),y.call(w.prototype),w.prototype.clone=function(){return new w(this._bodyInit,{status:this.status,statusText:this.statusText,headers:new l(this.headers),url:this.url})},w.error=function(){var e=new w(null,{status:0,statusText:""});return e.type="error",e};var s=[301,302,303,307,308];w.redirect=function(e,t){if(-1===s.indexOf(t))throw new RangeError("Invalid status code");return new w(null,{status:t,headers:{location:e}})},e.Headers=l,e.Request=m,e.Response=w,e.fetch=function(e,r){return new Promise(function(n,o){var i=new m(e,r),s=new XMLHttpRequest;s.onload=function(){var e,t,r={status:s.status,statusText:s.statusText,headers:(e=s.getAllResponseHeaders()||"",t=new l,e.replace(/\r?\n[\t ]+/g," ").split(/\r?\n/).forEach(function(e){var r=e.split(":"),n=r.shift().trim();if(n){var o=r.join(":").trim();t.append(n,o)}}),t)};r.url="responseURL"in s?s.responseURL:r.headers.get("X-Request-URL");var o="response"in s?s.response:s.responseText;n(new w(o,r))},s.onerror=function(){o(new TypeError("Network request failed"))},s.ontimeout=function(){o(new TypeError("Network request failed"))},s.open(i.method,i.url,!0),"include"===i.credentials?s.withCredentials=!0:"omit"===i.credentials&&(s.withCredentials=!1),"responseType"in s&&t.blob&&(s.responseType="blob"),i.headers.forEach(function(e,t){s.setRequestHeader(t,e)}),s.send(void 0===i._bodyInit?null:i._bodyInit)})},e.fetch.polyfill=!0}function a(e){if("string"!=typeof e&&(e=String(e)),/[^a-z0-9\-#$%&'*+.\^_`|~]/i.test(e))throw new TypeError("Invalid character in header field name");return e.toLowerCase()}function u(e){return"string"!=typeof e&&(e=String(e)),e}function c(e){var r={next:function(){var t=e.shift();return{done:void 0===t,value:t}}};return t.iterable&&(r[Symbol.iterator]=function(){return r}),r}function l(e){this.map={},e instanceof l?e.forEach(function(e,t){this.append(t,e)},this):Array.isArray(e)?e.forEach(function(e){this.append(e[0],e[1])},this):e&&Object.getOwnPropertyNames(e).forEach(function(t){this.append(t,e[t])},this)}function d(e){if(e.bodyUsed)return Promise.reject(new TypeError("Already read"));e.bodyUsed=!0}function f(e){return new Promise(function(t,r){e.onload=function(){t(e.result)},e.onerror=function(){r(e.error)}})}function h(e){var t=new FileReader,r=f(t);return t.readAsArrayBuffer(e),r}function p(e){if(e.slice)return e.slice(0);var t=new Uint8Array(e.byteLength);return t.set(new Uint8Array(e)),t.buffer}function y(){return this.bodyUsed=!1,this._initBody=function(e){if(this._bodyInit=e,e)if("string"==typeof e)this._bodyText=e;else if(t.blob&&Blob.prototype.isPrototypeOf(e))this._bodyBlob=e;else if(t.formData&&FormData.prototype.isPrototypeOf(e))this._bodyFormData=e;else if(t.searchParams&&URLSearchParams.prototype.isPrototypeOf(e))this._bodyText=e.toString();else if(t.arrayBuffer&&t.blob&&n(e))this._bodyArrayBuffer=p(e.buffer),this._bodyInit=new Blob([this._bodyArrayBuffer]);else{if(!t.arrayBuffer||!ArrayBuffer.prototype.isPrototypeOf(e)&&!o(e))throw new Error("unsupported BodyInit type");this._bodyArrayBuffer=p(e)}else this._bodyText="";this.headers.get("content-type")||("string"==typeof e?this.headers.set("content-type","text/plain;charset=UTF-8"):this._bodyBlob&&this._bodyBlob.type?this.headers.set("content-type",this._bodyBlob.type):t.searchParams&&URLSearchParams.prototype.isPrototypeOf(e)&&this.headers.set("content-type","application/x-www-form-urlencoded;charset=UTF-8"))},t.blob&&(this.blob=function(){var e=d(this);if(e)return e;if(this._bodyBlob)return Promise.resolve(this._bodyBlob);if(this._bodyArrayBuffer)return Promise.resolve(new Blob([this._bodyArrayBuffer]));if(this._bodyFormData)throw new Error("could not read FormData body as blob");return Promise.resolve(new Blob([this._bodyText]))},this.arrayBuffer=function(){return this._bodyArrayBuffer?d(this)||Promise.resolve(this._bodyArrayBuffer):this.blob().then(h)}),this.text=function(){var e,t,r,n=d(this);if(n)return n;if(this._bodyBlob)return e=this._bodyBlob,t=new FileReader,r=f(t),t.readAsText(e),r;if(this._bodyArrayBuffer)return Promise.resolve(function(e){for(var t=new Uint8Array(e),r=new Array(t.length),n=0;n<t.length;n++)r[n]=String.fromCharCode(t[n]);return r.join("")}(this._bodyArrayBuffer));if(this._bodyFormData)throw new Error("could not read FormData body as text");return Promise.resolve(this._bodyText)},t.formData&&(this.formData=function(){return this.text().then(b)}),this.json=function(){return this.text().then(JSON.parse)},this}function m(e,t){var r,n,o=(t=t||{}).body;if(e instanceof m){if(e.bodyUsed)throw new TypeError("Already read");this.url=e.url,this.credentials=e.credentials,t.headers||(this.headers=new l(e.headers)),this.method=e.method,this.mode=e.mode,o||null==e._bodyInit||(o=e._bodyInit,e.bodyUsed=!0)}else this.url=String(e);if(this.credentials=t.credentials||this.credentials||"omit",!t.headers&&this.headers||(this.headers=new l(t.headers)),this.method=(r=t.method||this.method||"GET",n=r.toUpperCase(),i.indexOf(n)>-1?n:r),this.mode=t.mode||this.mode||null,this.referrer=null,("GET"===this.method||"HEAD"===this.method)&&o)throw new TypeError("Body not allowed for GET or HEAD requests");this._initBody(o)}function b(e){var t=new FormData;return e.trim().split("&").forEach(function(e){if(e){var r=e.split("="),n=r.shift().replace(/\+/g," "),o=r.join("=").replace(/\+/g," ");t.append(decodeURIComponent(n),decodeURIComponent(o))}}),t}function w(e,t){t||(t={}),this.type="default",this.status=void 0===t.status?200:t.status,this.ok=this.status>=200&&this.status<300,this.statusText="statusText"in t?t.statusText:"OK",this.headers=new l(t.headers),this.url=t.url||"",this._initBody(e)}}("undefined"!=typeof self?self:this),e.exports=t.fetch}).call(t)}).call(this,r(0))},,function(e,t,r){var n,o;r(14),n=[r(1)],void 0===(o=function(e){return function(){var t,r,n,o=0,i={error:"error",info:"info",success:"success",warning:"warning"},s={clear:function(r,n){var o=d();t||a(o),u(r,o,n)||function(r){for(var n=t.children(),o=n.length-1;o>=0;o--)u(e(n[o]),r)}(o)},remove:function(r){var n=d();t||a(n),r&&0===e(":focus",r).length?f(r):t.children().length&&t.remove()},error:function(e,t,r){return l({type:i.error,iconClass:d().iconClasses.error,message:e,optionsOverride:r,title:t})},getContainer:a,info:function(e,t,r){return l({type:i.info,iconClass:d().iconClasses.info,message:e,optionsOverride:r,title:t})},options:{},subscribe:function(e){r=e},success:function(e,t,r){return l({type:i.success,iconClass:d().iconClasses.success,message:e,optionsOverride:r,title:t})},version:"2.1.4",warning:function(e,t,r){return l({type:i.warning,iconClass:d().iconClasses.warning,message:e,optionsOverride:r,title:t})}};return s;function a(r,n){return r||(r=d()),(t=e("#"+r.containerId)).length?t:(n&&(t=function(r){return(t=e("<div/>").attr("id",r.containerId).addClass(r.positionClass)).appendTo(e(r.target)),t}(r)),t)}function u(t,r,n){var o=!(!n||!n.force)&&n.force;return!(!t||!o&&0!==e(":focus",t).length||(t[r.hideMethod]({duration:r.hideDuration,easing:r.hideEasing,complete:function(){f(t)}}),0))}function c(e){r&&r(e)}function l(r){var i=d(),s=r.iconClass||i.iconClass;if(void 0!==r.optionsOverride&&(i=e.extend(i,r.optionsOverride),s=r.optionsOverride.iconClass||s),!function(e,t){if(e.preventDuplicates){if(t.message===n)return!0;n=t.message}return!1}(i,r)){o++,t=a(i,!0);var u=null,l=e("<div/>"),h=e("<div/>"),p=e("<div/>"),y=e("<div/>"),m=e(i.closeHtml),b={intervalId:null,hideEta:null,maxHideTime:null},w={toastId:o,state:"visible",startTime:new Date,options:i,map:r};return r.iconClass&&l.addClass(i.toastClass).addClass(s),function(){if(r.title){var e=r.title;i.escapeHtml&&(e=v(r.title)),h.append(e).addClass(i.titleClass),l.append(h)}}(),function(){if(r.message){var e=r.message;i.escapeHtml&&(e=v(r.message)),p.append(e).addClass(i.messageClass),l.append(p)}}(),i.closeButton&&(m.addClass(i.closeClass).attr("role","button"),l.prepend(m)),i.progressBar&&(y.addClass(i.progressClass),l.prepend(y)),i.rtl&&l.addClass("rtl"),i.newestOnTop?t.prepend(l):t.append(l),function(){var e="";switch(r.iconClass){case"toast-success":case"toast-info":e="polite";break;default:e="assertive"}l.attr("aria-live",e)}(),l.hide(),l[i.showMethod]({duration:i.showDuration,easing:i.showEasing,complete:i.onShown}),i.timeOut>0&&(u=setTimeout(g,i.timeOut),b.maxHideTime=parseFloat(i.timeOut),b.hideEta=(new Date).getTime()+b.maxHideTime,i.progressBar&&(b.intervalId=setInterval(x,10))),i.closeOnHover&&l.hover(C,T),!i.onclick&&i.tapToDismiss&&l.click(g),i.closeButton&&m&&m.click(function(e){e.stopPropagation?e.stopPropagation():void 0!==e.cancelBubble&&!0!==e.cancelBubble&&(e.cancelBubble=!0),i.onCloseClick&&i.onCloseClick(e),g(!0)}),i.onclick&&l.click(function(e){i.onclick(e),g()}),c(w),i.debug&&console,l}function v(e){return null==e&&(e=""),e.replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/'/g,"&#39;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}function g(t){var r=t&&!1!==i.closeMethod?i.closeMethod:i.hideMethod,n=t&&!1!==i.closeDuration?i.closeDuration:i.hideDuration,o=t&&!1!==i.closeEasing?i.closeEasing:i.hideEasing;if(!e(":focus",l).length||t)return clearTimeout(b.intervalId),l[r]({duration:n,easing:o,complete:function(){f(l),clearTimeout(u),i.onHidden&&"hidden"!==w.state&&i.onHidden(),w.state="hidden",w.endTime=new Date,c(w)}})}function T(){(i.timeOut>0||i.extendedTimeOut>0)&&(u=setTimeout(g,i.extendedTimeOut),b.maxHideTime=parseFloat(i.extendedTimeOut),b.hideEta=(new Date).getTime()+b.maxHideTime)}function C(){clearTimeout(u),b.hideEta=0,l.stop(!0,!0)[i.showMethod]({duration:i.showDuration,easing:i.showEasing})}function x(){var e=(b.hideEta-(new Date).getTime())/b.maxHideTime*100;y.width(e+"%")}}function d(){return e.extend({},{tapToDismiss:!0,toastClass:"toast",containerId:"toast-container",debug:!1,showMethod:"fadeIn",showDuration:300,showEasing:"swing",onShown:void 0,hideMethod:"fadeOut",hideDuration:1e3,hideEasing:"swing",onHidden:void 0,closeMethod:!1,closeDuration:!1,closeEasing:!1,closeOnHover:!0,extendedTimeOut:1e3,iconClasses:{error:"toast-error",info:"toast-info",success:"toast-success",warning:"toast-warning"},iconClass:"toast-info",positionClass:"toast-top-right",timeOut:5e3,titleClass:"toast-title",messageClass:"toast-message",escapeHtml:!1,target:"body",closeHtml:'<button type="button">&times;</button>',closeClass:"toast-close-button",newestOnTop:!0,preventDuplicates:!1,progressBar:!1,progressClass:"toast-progress",rtl:!1},s.options)}function f(e){t||(t=a()),e.is(":visible")||(e.remove(),e=null,0===t.children().length&&(t.remove(),n=void 0))}}()}.apply(t,n))||(e.exports=o)},function(e,t){e.exports=function(){throw new Error("define cannot be used indirect")}},function(e,t,r){var n=r(3),o=r(4),i=r(16),s=r(17);e.exports=function(e,t){return e=n(e),i(s(e,t=t||o),t)}},function(e,t,r){var n=r(3),o=r(4);e.exports=function(e,t){e=n(e),t=t||o;for(var r,i,s=0,a=e.length,u=t.length,c=!0;c&&s<a;)for(c=!1,r=-1,i=e.charAt(s);++r<u;)if(i===t[r]){c=!0,s++;break}return s>=a?"":e.substr(s,a)}},function(e,t,r){var n=r(3),o=r(4);e.exports=function(e,t){e=n(e),t=t||o;for(var r,i,s=e.length-1,a=t.length,u=!0;u&&s>=0;)for(u=!1,r=-1,i=e.charAt(s);++r<a;)if(i===t[r]){u=!0,s--;break}return s>=0?e.substring(0,s+1):""}}]]);
\ No newline at end of file
{
"name": "trilby-grav-plugin-gitsync",
"version": "1.1.0",
"description": "Git Sync Grav Plugin",
"main": "app/main.js",
"scripts": {
"watch": "NODE_ENV=development webpack --watch --progress --colors --mode development --config webpack.conf.js",
"dev": "NODE_ENV=development webpack --progress --colors --config webpack.conf.js",
"prod": "NODE_ENV=production webpack --mode production --config webpack.conf.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/trilbymedia/grav-plugin-git-sync.git"
},
"author": "Trilby Media, LLC",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/trilbymedia/grav-plugin-git-sync/issues"
},
"homepage": "https://github.com/trilbymedia/grav-plugin-git-sync#readme",
"dependencies": {
"vex-js": "^4.1.0"
},
"devDependencies": {
"babel-core": "^6.26.3",
"babel-eslint": "^8.2.6",
"babel-loader": "^7.1.5",
"babel-polyfill": "^6.26.0",
"babel-preset-es2015": "^6.24.1",
"babel-preset-stage-3": "^6.24.1",
"css-loader": "^1.0.0",
"eslint": "^5.2.0",
"eslint-loader": "^2.1.0",
"exports-loader": "^0.7.0",
"imports-loader": "^0.8.0",
"json-loader": "^0.5.7",
"style-loader": "^0.21.0",
"webpack": "^4.16.2",
"webpack-cli": "^3.1.0",
"whatwg-fetch": "^2.0.4"
}
}
$page-background: #f7f7f7;
$content-text: #666;
$header-background: $page-background;
$calendar-text: $content-text;
$calendar-details-background: #dcdcdc;
$calendar-details-background-alt: #cecece;
$calendar-day-background: #ffffff;
$toast-danger-background: #C62828;
// Vendors
@import "vendor/bourbon/bourbon";
// Load Template Configuration
@import "configuration/colors";
@import "plugin/wizard";
[data-remodal-id="wizard"], [data-remodal-id="reset-local"] {
.button[disabled] {
&, &:hover {
background: #ccc;
cursor: default;
}
&:active {
margin: 0;
}
}
form [class^="step-"] a:not(.button):not([target]) {
vertical-align: middle;
color: #5591c7;
&:hover {
color: #366188;
}
}
.step-1 a {
vertical-align: middle;
}
.access-tokens {
h3 {
margin-top: 1rem;
}
a {
vertical-align: inherit !important;
}
}
.access-tokens-details {
> p {
margin-top: 0;
}
> div > ul {
margin-bottom: 0;
}
}
.center {
text-align: center;
}
h1 {
margin-bottom: 0;
padding-top: 0.5rem;
border-top: 3px solid transparent;
}
.wizard-padding {
padding: 0 3rem;
p {
padding: 0;
}
}
label {
&.disabled {
color: #ccc;
}
img {
max-width: 100px;
display: inline-block;
vertical-align: middle;
margin-left: 0.5rem;
}
a {
margin-left: 0.5rem;
}
}
.columns {
display: flex;
align-items: center;
justify-content: center;
.column {
flex: 1;
&:first-child {
width: 35%;
flex: none;
border-right: 1px solid #ddd;
margin-right: 2rem;
}
}
}
.step-1 {
div.column:nth-child(1) {
flex-grow: 1.5;
width: 15%;
> label:nth-child(4) {
width: 110%;
}
}
div.column:nth-child(2) {
margin-left: 25px;
}
div.column:nth-child(2) > label:nth-child(1) > input:nth-child(1), div.column:nth-child(2) > label:nth-child(2) > input:nth-child(1) {
width: 85%;
}
}
.step-2 {
.info {
margin: 0.2rem 0;
padding: 0.5rem 1.5rem;
}
ol, ul {
padding-left: 1rem;
}
}
.step-4 {
.info {
font-size: 100%;
margin: 0.2rem 0;
}
.alert, .warning {
padding: 0.5rem 1.5rem;
}
.fa.fa-warning {
color: #ff7d3b;
font-size: 1.2rem;
}
.wizard-padding label > * {
vertical-align: middle;
}
.info-desc {
color: #0091ff;
float: right;
margin-right: .5rem;
font-size: 1.2rem;
}
hr {
margin: .5rem 0;
}
}
}
#admin-main [data-grav-field="git-wizard"] {
margin: 0 1rem;
.danger.button-bar {
margin: 0;
padding: 0;
background: transparent;
}
.button {
top: 0 !important;
@include transform(translateY(0) !important);
}
}
// The following features have been deprecated and will be removed in the next MAJOR version release
@mixin inline-block {
display: inline-block;
@warn "The inline-block mixin is deprecated and will be removed in the next major version release";
}
@mixin button ($style: simple, $base-color: #4294f0, $text-size: inherit, $padding: 7px 18px) {
@if type-of($style) == string and type-of($base-color) == color {
@include buttonstyle($style, $base-color, $text-size, $padding);
}
@if type-of($style) == string and type-of($base-color) == number {
$padding: $text-size;
$text-size: $base-color;
$base-color: #4294f0;
@if $padding == inherit {
$padding: 7px 18px;
}
@include buttonstyle($style, $base-color, $text-size, $padding);
}
@if type-of($style) == color and type-of($base-color) == color {
$base-color: $style;
$style: simple;
@include buttonstyle($style, $base-color, $text-size, $padding);
}
@if type-of($style) == color and type-of($base-color) == number {
$padding: $text-size;
$text-size: $base-color;
$base-color: $style;
$style: simple;
@if $padding == inherit {
$padding: 7px 18px;
}
@include buttonstyle($style, $base-color, $text-size, $padding);
}
@if type-of($style) == number {
$padding: $base-color;
$text-size: $style;
$base-color: #4294f0;
$style: simple;
@if $padding == #4294f0 {
$padding: 7px 18px;
}
@include buttonstyle($style, $base-color, $text-size, $padding);
}
&:disabled {
cursor: not-allowed;
opacity: 0.5;
}
@warn "The button mixin is deprecated and will be removed in the next major version release";
}
// Selector Style Button
@mixin buttonstyle($type, $b-color, $t-size, $pad) {
// Grayscale button
@if $type == simple and $b-color == grayscale($b-color) {
@include simple($b-color, true, $t-size, $pad);
}
@if $type == shiny and $b-color == grayscale($b-color) {
@include shiny($b-color, true, $t-size, $pad);
}
@if $type == pill and $b-color == grayscale($b-color) {
@include pill($b-color, true, $t-size, $pad);
}
@if $type == flat and $b-color == grayscale($b-color) {
@include flat($b-color, true, $t-size, $pad);
}
// Colored button
@if $type == simple {
@include simple($b-color, false, $t-size, $pad);
}
@else if $type == shiny {
@include shiny($b-color, false, $t-size, $pad);
}
@else if $type == pill {
@include pill($b-color, false, $t-size, $pad);
}
@else if $type == flat {
@include flat($b-color, false, $t-size, $pad);
}
}
// Simple Button
@mixin simple($base-color, $grayscale: false, $textsize: inherit, $padding: 7px 18px) {
$color: hsl(0, 0, 100%);
$border: adjust-color($base-color, $saturation: 9%, $lightness: -14%);
$inset-shadow: adjust-color($base-color, $saturation: -8%, $lightness: 15%);
$stop-gradient: adjust-color($base-color, $saturation: 9%, $lightness: -11%);
$text-shadow: adjust-color($base-color, $saturation: 15%, $lightness: -18%);
@if is-light($base-color) {
$color: hsl(0, 0, 20%);
$text-shadow: adjust-color($base-color, $saturation: 10%, $lightness: 4%);
}
@if $grayscale == true {
$border: grayscale($border);
$inset-shadow: grayscale($inset-shadow);
$stop-gradient: grayscale($stop-gradient);
$text-shadow: grayscale($text-shadow);
}
border: 1px solid $border;
border-radius: 3px;
box-shadow: inset 0 1px 0 0 $inset-shadow;
color: $color;
display: inline-block;
font-size: $textsize;
font-weight: bold;
@include linear-gradient ($base-color, $stop-gradient);
padding: $padding;
text-decoration: none;
text-shadow: 0 1px 0 $text-shadow;
background-clip: padding-box;
&:hover:not(:disabled) {
$base-color-hover: adjust-color($base-color, $saturation: -4%, $lightness: -5%);
$inset-shadow-hover: adjust-color($base-color, $saturation: -7%, $lightness: 5%);
$stop-gradient-hover: adjust-color($base-color, $saturation: 8%, $lightness: -14%);
@if $grayscale == true {
$base-color-hover: grayscale($base-color-hover);
$inset-shadow-hover: grayscale($inset-shadow-hover);
$stop-gradient-hover: grayscale($stop-gradient-hover);
}
@include linear-gradient ($base-color-hover, $stop-gradient-hover);
box-shadow: inset 0 1px 0 0 $inset-shadow-hover;
cursor: pointer;
}
&:active:not(:disabled),
&:focus:not(:disabled) {
$border-active: adjust-color($base-color, $saturation: 9%, $lightness: -14%);
$inset-shadow-active: adjust-color($base-color, $saturation: 7%, $lightness: -17%);
@if $grayscale == true {
$border-active: grayscale($border-active);
$inset-shadow-active: grayscale($inset-shadow-active);
}
border: 1px solid $border-active;
box-shadow: inset 0 0 8px 4px $inset-shadow-active, inset 0 0 8px 4px $inset-shadow-active;
}
}
// Shiny Button
@mixin shiny($base-color, $grayscale: false, $textsize: inherit, $padding: 7px 18px) {
$color: hsl(0, 0, 100%);
$border: adjust-color($base-color, $red: -117, $green: -111, $blue: -81);
$border-bottom: adjust-color($base-color, $red: -126, $green: -127, $blue: -122);
$fourth-stop: adjust-color($base-color, $red: -79, $green: -70, $blue: -46);
$inset-shadow: adjust-color($base-color, $red: 37, $green: 29, $blue: 12);
$second-stop: adjust-color($base-color, $red: -56, $green: -50, $blue: -33);
$text-shadow: adjust-color($base-color, $red: -140, $green: -141, $blue: -114);
$third-stop: adjust-color($base-color, $red: -86, $green: -75, $blue: -48);
@if is-light($base-color) {
$color: hsl(0, 0, 20%);
$text-shadow: adjust-color($base-color, $saturation: 10%, $lightness: 4%);
}
@if $grayscale == true {
$border: grayscale($border);
$border-bottom: grayscale($border-bottom);
$fourth-stop: grayscale($fourth-stop);
$inset-shadow: grayscale($inset-shadow);
$second-stop: grayscale($second-stop);
$text-shadow: grayscale($text-shadow);
$third-stop: grayscale($third-stop);
}
@include linear-gradient(top, $base-color 0%, $second-stop 50%, $third-stop 50%, $fourth-stop 100%);
border: 1px solid $border;
border-bottom: 1px solid $border-bottom;
border-radius: 5px;
box-shadow: inset 0 1px 0 0 $inset-shadow;
color: $color;
display: inline-block;
font-size: $textsize;
font-weight: bold;
padding: $padding;
text-align: center;
text-decoration: none;
text-shadow: 0 -1px 1px $text-shadow;
&:hover:not(:disabled) {
$first-stop-hover: adjust-color($base-color, $red: -13, $green: -15, $blue: -18);
$second-stop-hover: adjust-color($base-color, $red: -66, $green: -62, $blue: -51);
$third-stop-hover: adjust-color($base-color, $red: -93, $green: -85, $blue: -66);
$fourth-stop-hover: adjust-color($base-color, $red: -86, $green: -80, $blue: -63);
@if $grayscale == true {
$first-stop-hover: grayscale($first-stop-hover);
$second-stop-hover: grayscale($second-stop-hover);
$third-stop-hover: grayscale($third-stop-hover);
$fourth-stop-hover: grayscale($fourth-stop-hover);
}
@include linear-gradient(top, $first-stop-hover 0%,
$second-stop-hover 50%,
$third-stop-hover 50%,
$fourth-stop-hover 100%);
cursor: pointer;
}
&:active:not(:disabled),
&:focus:not(:disabled) {
$inset-shadow-active: adjust-color($base-color, $red: -111, $green: -116, $blue: -122);
@if $grayscale == true {
$inset-shadow-active: grayscale($inset-shadow-active);
}
box-shadow: inset 0 0 20px 0 $inset-shadow-active;
}
}
// Pill Button
@mixin pill($base-color, $grayscale: false, $textsize: inherit, $padding: 7px 18px) {
$color: hsl(0, 0, 100%);
$border-bottom: adjust-color($base-color, $hue: 8, $saturation: -11%, $lightness: -26%);
$border-sides: adjust-color($base-color, $hue: 4, $saturation: -21%, $lightness: -21%);
$border-top: adjust-color($base-color, $hue: -1, $saturation: -30%, $lightness: -15%);
$inset-shadow: adjust-color($base-color, $hue: -1, $saturation: -1%, $lightness: 7%);
$stop-gradient: adjust-color($base-color, $hue: 8, $saturation: 14%, $lightness: -10%);
$text-shadow: adjust-color($base-color, $hue: 5, $saturation: -19%, $lightness: -15%);
@if is-light($base-color) {
$color: hsl(0, 0, 20%);
$text-shadow: adjust-color($base-color, $saturation: 10%, $lightness: 4%);
}
@if $grayscale == true {
$border-bottom: grayscale($border-bottom);
$border-sides: grayscale($border-sides);
$border-top: grayscale($border-top);
$inset-shadow: grayscale($inset-shadow);
$stop-gradient: grayscale($stop-gradient);
$text-shadow: grayscale($text-shadow);
}
border: 1px solid $border-top;
border-color: $border-top $border-sides $border-bottom;
border-radius: 16px;
box-shadow: inset 0 1px 0 0 $inset-shadow;
color: $color;
display: inline-block;
font-size: $textsize;
font-weight: normal;
line-height: 1;
@include linear-gradient ($base-color, $stop-gradient);
padding: $padding;
text-align: center;
text-decoration: none;
text-shadow: 0 -1px 1px $text-shadow;
background-clip: padding-box;
&:hover:not(:disabled) {
$base-color-hover: adjust-color($base-color, $lightness: -4.5%);
$border-bottom: adjust-color($base-color, $hue: 8, $saturation: 13.5%, $lightness: -32%);
$border-sides: adjust-color($base-color, $hue: 4, $saturation: -2%, $lightness: -27%);
$border-top: adjust-color($base-color, $hue: -1, $saturation: -17%, $lightness: -21%);
$inset-shadow-hover: adjust-color($base-color, $saturation: -1%, $lightness: 3%);
$stop-gradient-hover: adjust-color($base-color, $hue: 8, $saturation: -4%, $lightness: -15.5%);
$text-shadow-hover: adjust-color($base-color, $hue: 5, $saturation: -5%, $lightness: -22%);
@if $grayscale == true {
$base-color-hover: grayscale($base-color-hover);
$border-bottom: grayscale($border-bottom);
$border-sides: grayscale($border-sides);
$border-top: grayscale($border-top);
$inset-shadow-hover: grayscale($inset-shadow-hover);
$stop-gradient-hover: grayscale($stop-gradient-hover);
$text-shadow-hover: grayscale($text-shadow-hover);
}
@include linear-gradient ($base-color-hover, $stop-gradient-hover);
background-clip: padding-box;
border: 1px solid $border-top;
border-color: $border-top $border-sides $border-bottom;
box-shadow: inset 0 1px 0 0 $inset-shadow-hover;
cursor: pointer;
text-shadow: 0 -1px 1px $text-shadow-hover;
}
&:active:not(:disabled),
&:focus:not(:disabled) {
$active-color: adjust-color($base-color, $hue: 4, $saturation: -12%, $lightness: -10%);
$border-active: adjust-color($base-color, $hue: 6, $saturation: -2.5%, $lightness: -30%);
$border-bottom-active: adjust-color($base-color, $hue: 11, $saturation: 6%, $lightness: -31%);
$inset-shadow-active: adjust-color($base-color, $hue: 9, $saturation: 2%, $lightness: -21.5%);
$text-shadow-active: adjust-color($base-color, $hue: 5, $saturation: -12%, $lightness: -21.5%);
@if $grayscale == true {
$active-color: grayscale($active-color);
$border-active: grayscale($border-active);
$border-bottom-active: grayscale($border-bottom-active);
$inset-shadow-active: grayscale($inset-shadow-active);
$text-shadow-active: grayscale($text-shadow-active);
}
background: $active-color;
border: 1px solid $border-active;
border-bottom: 1px solid $border-bottom-active;
box-shadow: inset 0 0 6px 3px $inset-shadow-active;
text-shadow: 0 -1px 1px $text-shadow-active;
}
}
// Flat Button
@mixin flat($base-color, $grayscale: false, $textsize: inherit, $padding: 7px 18px) {
$color: hsl(0, 0, 100%);
@if is-light($base-color) {
$color: hsl(0, 0, 20%);
}
background-color: $base-color;
border-radius: 3px;
border: 0;
color: $color;
display: inline-block;
font-size: $textsize;
font-weight: bold;
padding: $padding;
text-decoration: none;
background-clip: padding-box;
&:hover:not(:disabled){
$base-color-hover: adjust-color($base-color, $saturation: 4%, $lightness: 5%);
@if $grayscale == true {
$base-color-hover: grayscale($base-color-hover);
}
background-color: $base-color-hover;
cursor: pointer;
}
&:active:not(:disabled),
&:focus:not(:disabled) {
$base-color-active: adjust-color($base-color, $saturation: -4%, $lightness: -5%);
@if $grayscale == true {
$base-color-active: grayscale($base-color-active);
}
background-color: $base-color-active;
cursor: pointer;
}
}
// Flexible grid
@function flex-grid($columns, $container-columns: $fg-max-columns) {
$width: $columns * $fg-column + ($columns - 1) * $fg-gutter;
$container-width: $container-columns * $fg-column + ($container-columns - 1) * $fg-gutter;
@return percentage($width / $container-width);
@warn "The flex-grid function is deprecated and will be removed in the next major version release";
}
// Flexible gutter
@function flex-gutter($container-columns: $fg-max-columns, $gutter: $fg-gutter) {
$container-width: $container-columns * $fg-column + ($container-columns - 1) * $fg-gutter;
@return percentage($gutter / $container-width);
@warn "The flex-gutter function is deprecated and will be removed in the next major version release";
}
@function grid-width($n) {
@return $n * $gw-column + ($n - 1) * $gw-gutter;
@warn "The grid-width function is deprecated and will be removed in the next major version release";
}
@function golden-ratio($value, $increment) {
@return modular-scale($increment, $value, $ratio: $golden);
@warn "The golden-ratio function is deprecated and will be removed in the next major version release. Please use the modular-scale function, instead.";
}
@mixin box-sizing($box) {
@include prefixer(box-sizing, $box, webkit moz spec);
@warn "The box-sizing mixin is deprecated and will be removed in the next major version release. This property can now be used un-prefixed.";
}
// Bourbon 4.2.7
// http://bourbon.io
// Copyright 2011-2015 thoughtbot, inc.
// MIT License
@import "settings/prefixer";
@import "settings/px-to-em";
@import "settings/asset-pipeline";
@import "functions/assign-inputs";
@import "functions/contains";
@import "functions/contains-falsy";
@import "functions/is-length";
@import "functions/is-light";
@import "functions/is-number";
@import "functions/is-size";
@import "functions/px-to-em";
@import "functions/px-to-rem";
@import "functions/shade";
@import "functions/strip-units";
@import "functions/tint";
@import "functions/transition-property-name";
@import "functions/unpack";
@import "functions/modular-scale";
@import "helpers/convert-units";
@import "helpers/directional-values";
@import "helpers/font-source-declaration";
@import "helpers/gradient-positions-parser";
@import "helpers/linear-angle-parser";
@import "helpers/linear-gradient-parser";
@import "helpers/linear-positions-parser";
@import "helpers/linear-side-corner-parser";
@import "helpers/radial-arg-parser";
@import "helpers/radial-positions-parser";
@import "helpers/radial-gradient-parser";
@import "helpers/render-gradients";
@import "helpers/shape-size-stripper";
@import "helpers/str-to-num";
@import "css3/animation";
@import "css3/appearance";
@import "css3/backface-visibility";
@import "css3/background";
@import "css3/background-image";
@import "css3/border-image";
@import "css3/calc";
@import "css3/columns";
@import "css3/filter";
@import "css3/flex-box";
@import "css3/font-face";
@import "css3/font-feature-settings";
@import "css3/hidpi-media-query";
@import "css3/hyphens";
@import "css3/image-rendering";
@import "css3/keyframes";
@import "css3/linear-gradient";
@import "css3/perspective";
@import "css3/placeholder";
@import "css3/radial-gradient";
@import "css3/selection";
@import "css3/text-decoration";
@import "css3/transform";
@import "css3/transition";
@import "css3/user-select";
@import "addons/border-color";
@import "addons/border-radius";
@import "addons/border-style";
@import "addons/border-width";
@import "addons/buttons";
@import "addons/clearfix";
@import "addons/ellipsis";
@import "addons/font-stacks";
@import "addons/hide-text";
@import "addons/margin";
@import "addons/padding";
@import "addons/position";
@import "addons/prefixer";
@import "addons/retina-image";
@import "addons/size";
@import "addons/text-inputs";
@import "addons/timing-functions";
@import "addons/triangle";
@import "addons/word-wrap";
@import "bourbon-deprecated-upcoming";
@charset "UTF-8";
/// Provides a quick method for targeting `border-color` on specific sides of a box. Use a `null` value to “skip” a side.
///
/// @param {Arglist} $vals
/// List of arguments
///
/// @example scss - Usage
/// .element {
/// @include border-color(#a60b55 #76cd9c null #e8ae1a);
/// }
///
/// @example css - CSS Output
/// .element {
/// border-left-color: #e8ae1a;
/// border-right-color: #76cd9c;
/// border-top-color: #a60b55;
/// }
///
/// @require {mixin} directional-property
///
/// @output `border-color`
@mixin border-color($vals...) {
@include directional-property(border, color, $vals...);
}
@charset "UTF-8";
/// Provides a quick method for targeting `border-radius` on both corners on the side of a box.
///
/// @param {Number} $radii
/// List of arguments
///
/// @example scss - Usage
/// .element-one {
/// @include border-top-radius(5px);
/// }
///
/// .element-two {
/// @include border-left-radius(3px);
/// }
///
/// @example css - CSS Output
/// .element-one {
/// border-top-left-radius: 5px;
/// border-top-right-radius: 5px;
/// }
///
/// .element-two {
/// border-bottom-left-radius: 3px;
/// border-top-left-radius: 3px;
/// }
///
/// @output `border-radius`
@mixin border-top-radius($radii) {
border-top-left-radius: $radii;
border-top-right-radius: $radii;
}
@mixin border-right-radius($radii) {
border-bottom-right-radius: $radii;
border-top-right-radius: $radii;
}
@mixin border-bottom-radius($radii) {
border-bottom-left-radius: $radii;
border-bottom-right-radius: $radii;
}
@mixin border-left-radius($radii) {
border-bottom-left-radius: $radii;
border-top-left-radius: $radii;
}
@charset "UTF-8";
/// Provides a quick method for targeting `border-style` on specific sides of a box. Use a `null` value to “skip” a side.
///
/// @param {Arglist} $vals
/// List of arguments
///
/// @example scss - Usage
/// .element {
/// @include border-style(dashed null solid);
/// }
///
/// @example css - CSS Output
/// .element {
/// border-bottom-style: solid;
/// border-top-style: dashed;
/// }
///
/// @require {mixin} directional-property
///
/// @output `border-style`
@mixin border-style($vals...) {
@include directional-property(border, style, $vals...);
}
@charset "UTF-8";
/// Provides a quick method for targeting `border-width` on specific sides of a box. Use a `null` value to “skip” a side.
///
/// @param {Arglist} $vals
/// List of arguments
///
/// @example scss - Usage
/// .element {
/// @include border-width(1em null 20px);
/// }
///
/// @example css - CSS Output
/// .element {
/// border-bottom-width: 20px;
/// border-top-width: 1em;
/// }
///
/// @require {mixin} directional-property
///
/// @output `border-width`
@mixin border-width($vals...) {
@include directional-property(border, width, $vals...);
}
@charset "UTF-8";
/// Generates variables for all buttons. Please note that you must use interpolation on the variable: `#{$all-buttons}`.
///
/// @example scss - Usage
/// #{$all-buttons} {
/// background-color: #f00;
/// }
///
/// #{$all-buttons-focus},
/// #{$all-buttons-hover} {
/// background-color: #0f0;
/// }
///
/// #{$all-buttons-active} {
/// background-color: #00f;
/// }
///
/// @example css - CSS Output
/// button,
/// input[type="button"],
/// input[type="reset"],
/// input[type="submit"] {
/// background-color: #f00;
/// }
///
/// button:focus,
/// input[type="button"]:focus,
/// input[type="reset"]:focus,
/// input[type="submit"]:focus,
/// button:hover,
/// input[type="button"]:hover,
/// input[type="reset"]:hover,
/// input[type="submit"]:hover {
/// background-color: #0f0;
/// }
///
/// button:active,
/// input[type="button"]:active,
/// input[type="reset"]:active,
/// input[type="submit"]:active {
/// background-color: #00f;
/// }
///
/// @require assign-inputs
///
/// @type List
///
/// @todo Remove double assigned variables (Lines 59–62) in v5.0.0
$buttons-list: 'button',
'input[type="button"]',
'input[type="reset"]',
'input[type="submit"]';
$all-buttons: assign-inputs($buttons-list);
$all-buttons-active: assign-inputs($buttons-list, active);
$all-buttons-focus: assign-inputs($buttons-list, focus);
$all-buttons-hover: assign-inputs($buttons-list, hover);
$all-button-inputs: $all-buttons;
$all-button-inputs-active: $all-buttons-active;
$all-button-inputs-focus: $all-buttons-focus;
$all-button-inputs-hover: $all-buttons-hover;
@charset "UTF-8";
/// Provides an easy way to include a clearfix for containing floats.
///
/// @link http://cssmojo.com/latest_new_clearfix_so_far/
///
/// @example scss - Usage
/// .element {
/// @include clearfix;
/// }
///
/// @example css - CSS Output
/// .element::after {
/// clear: both;
/// content: "";
/// display: table;
/// }
@mixin clearfix {
&::after {
clear: both;
content: "";
display: table;
}
}
@charset "UTF-8";
/// Truncates text and adds an ellipsis to represent overflow.
///
/// @param {Number} $width [100%]
/// Max-width for the string to respect before being truncated
///
/// @example scss - Usage
/// .element {
/// @include ellipsis;
/// }
///
/// @example css - CSS Output
/// .element {
/// display: inline-block;
/// max-width: 100%;
/// overflow: hidden;
/// text-overflow: ellipsis;
/// white-space: nowrap;
/// word-wrap: normal;
/// }
@mixin ellipsis($width: 100%) {
display: inline-block;
max-width: $width;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
word-wrap: normal;
}
@charset "UTF-8";
/// Georgia font stack.
///
/// @type List
$georgia: "Georgia", "Cambria", "Times New Roman", "Times", serif;
/// Helvetica font stack.
///
/// @type List
$helvetica: "Helvetica Neue", "Helvetica", "Roboto", "Arial", sans-serif;
/// Lucida Grande font stack.
///
/// @type List
$lucida-grande: "Lucida Grande", "Tahoma", "Verdana", "Arial", sans-serif;
/// Monospace font stack.
///
/// @type List
$monospace: "Bitstream Vera Sans Mono", "Consolas", "Courier", monospace;
/// Verdana font stack.
///
/// @type List
$verdana: "Verdana", "Geneva", sans-serif;
/// Hides the text in an element, commonly used to show an image. Some elements will need block-level styles applied.
///
/// @link http://zeldman.com/2012/03/01/replacing-the-9999px-hack-new-image-replacement
///
/// @example scss - Usage
/// .element {
/// @include hide-text;
/// }
///
/// @example css - CSS Output
/// .element {
/// overflow: hidden;
/// text-indent: 101%;
/// white-space: nowrap;
/// }
///
/// @todo Remove height argument in v5.0.0
@mixin hide-text($height: null) {
overflow: hidden;
text-indent: 101%;
white-space: nowrap;
@if $height {
@warn "The `hide-text` mixin has changed and no longer requires a height. The height argument will no longer be accepted in v5.0.0";
}
}
@charset "UTF-8";
/// Provides a quick method for targeting `margin` on specific sides of a box. Use a `null` value to “skip” a side.
///
/// @param {Arglist} $vals
/// List of arguments
///
/// @example scss - Usage
/// .element {
/// @include margin(null 10px 3em 20vh);
/// }
///
/// @example css - CSS Output
/// .element {
/// margin-bottom: 3em;
/// margin-left: 20vh;
/// margin-right: 10px;
/// }
///
/// @require {mixin} directional-property
///
/// @output `margin`
@mixin margin($vals...) {
@include directional-property(margin, false, $vals...);
}
@charset "UTF-8";
/// Provides a quick method for targeting `padding` on specific sides of a box. Use a `null` value to “skip” a side.
///
/// @param {Arglist} $vals
/// List of arguments
///
/// @example scss - Usage
/// .element {
/// @include padding(12vh null 10px 5%);
/// }
///
/// @example css - CSS Output
/// .element {
/// padding-bottom: 10px;
/// padding-left: 5%;
/// padding-top: 12vh;
/// }
///
/// @require {mixin} directional-property
///
/// @output `padding`
@mixin padding($vals...) {
@include directional-property(padding, false, $vals...);
}
@charset "UTF-8";
/// Provides a quick method for setting an element’s position. Use a `null` value to “skip” a side.
///
/// @param {Position} $position [relative]
/// A CSS position value
///
/// @param {Arglist} $coordinates [null null null null]
/// List of values that correspond to the 4-value syntax for the edges of a box
///
/// @example scss - Usage
/// .element {
/// @include position(absolute, 0 null null 10em);
/// }
///
/// @example css - CSS Output
/// .element {
/// left: 10em;
/// position: absolute;
/// top: 0;
/// }
///
/// @require {function} is-length
/// @require {function} unpack
@mixin position($position: relative, $coordinates: null null null null) {
@if type-of($position) == list {
$coordinates: $position;
$position: relative;
}
$coordinates: unpack($coordinates);
$offsets: (
top: nth($coordinates, 1),
right: nth($coordinates, 2),
bottom: nth($coordinates, 3),
left: nth($coordinates, 4)
);
position: $position;
@each $offset, $value in $offsets {
@if is-length($value) {
#{$offset}: $value;
}
}
}
@charset "UTF-8";
/// A mixin for generating vendor prefixes on non-standardized properties.
///
/// @param {String} $property
/// Property to prefix
///
/// @param {*} $value
/// Value to use
///
/// @param {List} $prefixes
/// Prefixes to define
///
/// @example scss - Usage
/// .element {
/// @include prefixer(border-radius, 10px, webkit ms spec);
/// }
///
/// @example css - CSS Output
/// .element {
/// -webkit-border-radius: 10px;
/// -moz-border-radius: 10px;
/// border-radius: 10px;
/// }
///
/// @require {variable} $prefix-for-webkit
/// @require {variable} $prefix-for-mozilla
/// @require {variable} $prefix-for-microsoft
/// @require {variable} $prefix-for-opera
/// @require {variable} $prefix-for-spec
@mixin prefixer($property, $value, $prefixes) {
@each $prefix in $prefixes {
@if $prefix == webkit {
@if $prefix-for-webkit {
-webkit-#{$property}: $value;
}
} @else if $prefix == moz {
@if $prefix-for-mozilla {
-moz-#{$property}: $value;
}
} @else if $prefix == ms {
@if $prefix-for-microsoft {
-ms-#{$property}: $value;
}
} @else if $prefix == o {
@if $prefix-for-opera {
-o-#{$property}: $value;
}
} @else if $prefix == spec {
@if $prefix-for-spec {
#{$property}: $value;
}
} @else {
@warn "Unrecognized prefix: #{$prefix}";
}
}
}
@mixin disable-prefix-for-all() {
$prefix-for-webkit: false !global;
$prefix-for-mozilla: false !global;
$prefix-for-microsoft: false !global;
$prefix-for-opera: false !global;
$prefix-for-spec: false !global;
}
@mixin retina-image($filename, $background-size, $extension: png, $retina-filename: null, $retina-suffix: _2x, $asset-pipeline: $asset-pipeline) {
@if $asset-pipeline {
background-image: image-url("#{$filename}.#{$extension}");
} @else {
background-image: url("#{$filename}.#{$extension}");
}
@include hidpi {
@if $asset-pipeline {
@if $retina-filename {
background-image: image-url("#{$retina-filename}.#{$extension}");
} @else {
background-image: image-url("#{$filename}#{$retina-suffix}.#{$extension}");
}
} @else {
@if $retina-filename {
background-image: url("#{$retina-filename}.#{$extension}");
} @else {
background-image: url("#{$filename}#{$retina-suffix}.#{$extension}");
}
}
background-size: $background-size;
}
}
@charset "UTF-8";
/// Sets the `width` and `height` of the element.
///
/// @param {List} $size
/// A list of at most 2 size values.
///
/// If there is only a single value in `$size` it is used for both width and height. All units are supported.
///
/// @example scss - Usage
/// .first-element {
/// @include size(2em);
/// }
///
/// .second-element {
/// @include size(auto 10em);
/// }
///
/// @example css - CSS Output
/// .first-element {
/// width: 2em;
/// height: 2em;
/// }
///
/// .second-element {
/// width: auto;
/// height: 10em;
/// }
///
/// @todo Refactor in 5.0.0 to use a comma-separated argument
@mixin size($value) {
$width: nth($value, 1);
$height: $width;
@if length($value) > 1 {
$height: nth($value, 2);
}
@if is-size($height) {
height: $height;
} @else {
@warn "`#{$height}` is not a valid length for the `$height` parameter in the `size` mixin.";
}
@if is-size($width) {
width: $width;
} @else {
@warn "`#{$width}` is not a valid length for the `$width` parameter in the `size` mixin.";
}
}
@charset "UTF-8";
/// Generates variables for all text-based inputs. Please note that you must use interpolation on the variable: `#{$all-text-inputs}`.
///
/// @example scss - Usage
/// #{$all-text-inputs} {
/// border: 1px solid #f00;
/// }
///
/// #{$all-text-inputs-focus},
/// #{$all-text-inputs-hover} {
/// border: 1px solid #0f0;
/// }
///
/// #{$all-text-inputs-active} {
/// border: 1px solid #00f;
/// }
///
/// @example css - CSS Output
/// input[type="color"],
/// input[type="date"],
/// input[type="datetime"],
/// input[type="datetime-local"],
/// input[type="email"],
/// input[type="month"],
/// input[type="number"],
/// input[type="password"],
/// input[type="search"],
/// input[type="tel"],
/// input[type="text"],
/// input[type="time"],
/// input[type="url"],
/// input[type="week"],
/// textarea {
/// border: 1px solid #f00;
/// }
///
/// input[type="color"]:focus,
/// input[type="date"]:focus,
/// input[type="datetime"]:focus,
/// input[type="datetime-local"]:focus,
/// input[type="email"]:focus,
/// input[type="month"]:focus,
/// input[type="number"]:focus,
/// input[type="password"]:focus,
/// input[type="search"]:focus,
/// input[type="tel"]:focus,
/// input[type="text"]:focus,
/// input[type="time"]:focus,
/// input[type="url"]:focus,
/// input[type="week"]:focus,
/// textarea:focus,
/// input[type="color"]:hover,
/// input[type="date"]:hover,
/// input[type="datetime"]:hover,
/// input[type="datetime-local"]:hover,
/// input[type="email"]:hover,
/// input[type="month"]:hover,
/// input[type="number"]:hover,
/// input[type="password"]:hover,
/// input[type="search"]:hover,
/// input[type="tel"]:hover,
/// input[type="text"]:hover,
/// input[type="time"]:hover,
/// input[type="url"]:hover,
/// input[type="week"]:hover,
/// textarea:hover {
/// border: 1px solid #0f0;
/// }
///
/// input[type="color"]:active,
/// input[type="date"]:active,
/// input[type="datetime"]:active,
/// input[type="datetime-local"]:active,
/// input[type="email"]:active,
/// input[type="month"]:active,
/// input[type="number"]:active,
/// input[type="password"]:active,
/// input[type="search"]:active,
/// input[type="tel"]:active,
/// input[type="text"]:active,
/// input[type="time"]:active,
/// input[type="url"]:active,
/// input[type="week"]:active,
/// textarea:active {
/// border: 1px solid #00f;
/// }
///
/// @require assign-inputs
///
/// @type List
$text-inputs-list: 'input[type="color"]',
'input[type="date"]',
'input[type="datetime"]',
'input[type="datetime-local"]',
'input[type="email"]',
'input[type="month"]',
'input[type="number"]',
'input[type="password"]',
'input[type="search"]',
'input[type="tel"]',
'input[type="text"]',
'input[type="time"]',
'input[type="url"]',
'input[type="week"]',
'input:not([type])',
'textarea';
$all-text-inputs: assign-inputs($text-inputs-list);
$all-text-inputs-active: assign-inputs($text-inputs-list, active);
$all-text-inputs-focus: assign-inputs($text-inputs-list, focus);
$all-text-inputs-hover: assign-inputs($text-inputs-list, hover);
@charset "UTF-8";
/// CSS cubic-bezier timing functions. Timing functions courtesy of jquery.easie (github.com/jaukia/easie)
///
/// Timing functions are the same as demoed here: http://jqueryui.com/resources/demos/effect/easing.html
///
/// @type cubic-bezier
$ease-in-quad: cubic-bezier(0.550, 0.085, 0.680, 0.530);
$ease-in-cubic: cubic-bezier(0.550, 0.055, 0.675, 0.190);
$ease-in-quart: cubic-bezier(0.895, 0.030, 0.685, 0.220);
$ease-in-quint: cubic-bezier(0.755, 0.050, 0.855, 0.060);
$ease-in-sine: cubic-bezier(0.470, 0.000, 0.745, 0.715);
$ease-in-expo: cubic-bezier(0.950, 0.050, 0.795, 0.035);
$ease-in-circ: cubic-bezier(0.600, 0.040, 0.980, 0.335);
$ease-in-back: cubic-bezier(0.600, -0.280, 0.735, 0.045);
$ease-out-quad: cubic-bezier(0.250, 0.460, 0.450, 0.940);
$ease-out-cubic: cubic-bezier(0.215, 0.610, 0.355, 1.000);
$ease-out-quart: cubic-bezier(0.165, 0.840, 0.440, 1.000);
$ease-out-quint: cubic-bezier(0.230, 1.000, 0.320, 1.000);
$ease-out-sine: cubic-bezier(0.390, 0.575, 0.565, 1.000);
$ease-out-expo: cubic-bezier(0.190, 1.000, 0.220, 1.000);
$ease-out-circ: cubic-bezier(0.075, 0.820, 0.165, 1.000);
$ease-out-back: cubic-bezier(0.175, 0.885, 0.320, 1.275);
$ease-in-out-quad: cubic-bezier(0.455, 0.030, 0.515, 0.955);
$ease-in-out-cubic: cubic-bezier(0.645, 0.045, 0.355, 1.000);
$ease-in-out-quart: cubic-bezier(0.770, 0.000, 0.175, 1.000);
$ease-in-out-quint: cubic-bezier(0.860, 0.000, 0.070, 1.000);
$ease-in-out-sine: cubic-bezier(0.445, 0.050, 0.550, 0.950);
$ease-in-out-expo: cubic-bezier(1.000, 0.000, 0.000, 1.000);
$ease-in-out-circ: cubic-bezier(0.785, 0.135, 0.150, 0.860);
$ease-in-out-back: cubic-bezier(0.680, -0.550, 0.265, 1.550);
@mixin triangle($size, $color, $direction) {
$width: nth($size, 1);
$height: nth($size, length($size));
$foreground-color: nth($color, 1);
$background-color: if(length($color) == 2, nth($color, 2), transparent);
height: 0;
width: 0;
@if ($direction == up) or ($direction == down) or ($direction == right) or ($direction == left) {
$width: $width / 2;
$height: if(length($size) > 1, $height, $height/2);
@if $direction == up {
border-bottom: $height solid $foreground-color;
border-left: $width solid $background-color;
border-right: $width solid $background-color;
} @else if $direction == right {
border-bottom: $width solid $background-color;
border-left: $height solid $foreground-color;
border-top: $width solid $background-color;
} @else if $direction == down {
border-left: $width solid $background-color;
border-right: $width solid $background-color;
border-top: $height solid $foreground-color;
} @else if $direction == left {
border-bottom: $width solid $background-color;
border-right: $height solid $foreground-color;
border-top: $width solid $background-color;
}
} @else if ($direction == up-right) or ($direction == up-left) {
border-top: $height solid $foreground-color;
@if $direction == up-right {
border-left: $width solid $background-color;
} @else if $direction == up-left {
border-right: $width solid $background-color;
}
} @else if ($direction == down-right) or ($direction == down-left) {
border-bottom: $height solid $foreground-color;
@if $direction == down-right {
border-left: $width solid $background-color;
} @else if $direction == down-left {
border-right: $width solid $background-color;
}
} @else if ($direction == inset-up) {
border-color: $background-color $background-color $foreground-color;
border-style: solid;
border-width: $height $width;
} @else if ($direction == inset-down) {
border-color: $foreground-color $background-color $background-color;
border-style: solid;
border-width: $height $width;
} @else if ($direction == inset-right) {
border-color: $background-color $background-color $background-color $foreground-color;
border-style: solid;
border-width: $width $height;
} @else if ($direction == inset-left) {
border-color: $background-color $foreground-color $background-color $background-color;
border-style: solid;
border-width: $width $height;
}
}
@charset "UTF-8";
/// Provides an easy way to change the `word-wrap` property.
///
/// @param {String} $wrap [break-word]
/// Value for the `word-break` property.
///
/// @example scss - Usage
/// .wrapper {
/// @include word-wrap(break-word);
/// }
///
/// @example css - CSS Output
/// .wrapper {
/// overflow-wrap: break-word;
/// word-break: break-all;
/// word-wrap: break-word;
/// }
@mixin word-wrap($wrap: break-word) {
overflow-wrap: $wrap;
word-wrap: $wrap;
@if $wrap == break-word {
word-break: break-all;
} @else {
word-break: $wrap;
}
}
// http://www.w3.org/TR/css3-animations/#the-animation-name-property-
// Each of these mixins support comma separated lists of values, which allows different transitions for individual properties to be described in a single style rule. Each value in the list corresponds to the value at that same position in the other properties.
@mixin animation($animations...) {
@include prefixer(animation, $animations, webkit moz spec);
}
@mixin animation-name($names...) {
@include prefixer(animation-name, $names, webkit moz spec);
}
@mixin animation-duration($times...) {
@include prefixer(animation-duration, $times, webkit moz spec);
}
@mixin animation-timing-function($motions...) {
// ease | linear | ease-in | ease-out | ease-in-out
@include prefixer(animation-timing-function, $motions, webkit moz spec);
}
@mixin animation-iteration-count($values...) {
// infinite | <number>
@include prefixer(animation-iteration-count, $values, webkit moz spec);
}
@mixin animation-direction($directions...) {
// normal | alternate
@include prefixer(animation-direction, $directions, webkit moz spec);
}
@mixin animation-play-state($states...) {
// running | paused
@include prefixer(animation-play-state, $states, webkit moz spec);
}
@mixin animation-delay($times...) {
@include prefixer(animation-delay, $times, webkit moz spec);
}
@mixin animation-fill-mode($modes...) {
// none | forwards | backwards | both
@include prefixer(animation-fill-mode, $modes, webkit moz spec);
}
@mixin appearance($value) {
@include prefixer(appearance, $value, webkit moz ms o spec);
}
@mixin backface-visibility($visibility) {
@include prefixer(backface-visibility, $visibility, webkit spec);
}
//************************************************************************//
// Background-image property for adding multiple background images with
// gradients, or for stringing multiple gradients together.
//************************************************************************//
@mixin background-image($images...) {
$webkit-images: ();
$spec-images: ();
@each $image in $images {
$webkit-image: ();
$spec-image: ();
@if (type-of($image) == string) {
$url-str: str-slice($image, 1, 3);
$gradient-type: str-slice($image, 1, 6);
@if $url-str == "url" {
$webkit-image: $image;
$spec-image: $image;
}
@else if $gradient-type == "linear" {
$gradients: _linear-gradient-parser($image);
$webkit-image: map-get($gradients, webkit-image);
$spec-image: map-get($gradients, spec-image);
}
@else if $gradient-type == "radial" {
$gradients: _radial-gradient-parser($image);
$webkit-image: map-get($gradients, webkit-image);
$spec-image: map-get($gradients, spec-image);
}
}
$webkit-images: append($webkit-images, $webkit-image, comma);
$spec-images: append($spec-images, $spec-image, comma);
}
background-image: $webkit-images;
background-image: $spec-images;
}
//************************************************************************//
// Background property for adding multiple backgrounds using shorthand
// notation.
//************************************************************************//
@mixin background($backgrounds...) {
$webkit-backgrounds: ();
$spec-backgrounds: ();
@each $background in $backgrounds {
$webkit-background: ();
$spec-background: ();
$background-type: type-of($background);
@if $background-type == string or $background-type == list {
$background-str: if($background-type == list, nth($background, 1), $background);
$url-str: str-slice($background-str, 1, 3);
$gradient-type: str-slice($background-str, 1, 6);
@if $url-str == "url" {
$webkit-background: $background;
$spec-background: $background;
}
@else if $gradient-type == "linear" {
$gradients: _linear-gradient-parser("#{$background}");
$webkit-background: map-get($gradients, webkit-image);
$spec-background: map-get($gradients, spec-image);
}
@else if $gradient-type == "radial" {
$gradients: _radial-gradient-parser("#{$background}");
$webkit-background: map-get($gradients, webkit-image);
$spec-background: map-get($gradients, spec-image);
}
@else {
$webkit-background: $background;
$spec-background: $background;
}
}
@else {
$webkit-background: $background;
$spec-background: $background;
}
$webkit-backgrounds: append($webkit-backgrounds, $webkit-background, comma);
$spec-backgrounds: append($spec-backgrounds, $spec-background, comma);
}
background: $webkit-backgrounds;
background: $spec-backgrounds;
}
@mixin border-image($borders...) {
$webkit-borders: ();
$spec-borders: ();
@each $border in $borders {
$webkit-border: ();
$spec-border: ();
$border-type: type-of($border);
@if $border-type == string or list {
$border-str: if($border-type == list, nth($border, 1), $border);
$url-str: str-slice($border-str, 1, 3);
$gradient-type: str-slice($border-str, 1, 6);
@if $url-str == "url" {
$webkit-border: $border;
$spec-border: $border;
}
@else if $gradient-type == "linear" {
$gradients: _linear-gradient-parser("#{$border}");
$webkit-border: map-get($gradients, webkit-image);
$spec-border: map-get($gradients, spec-image);
}
@else if $gradient-type == "radial" {
$gradients: _radial-gradient-parser("#{$border}");
$webkit-border: map-get($gradients, webkit-image);
$spec-border: map-get($gradients, spec-image);
}
@else {
$webkit-border: $border;
$spec-border: $border;
}
}
@else {
$webkit-border: $border;
$spec-border: $border;
}
$webkit-borders: append($webkit-borders, $webkit-border, comma);
$spec-borders: append($spec-borders, $spec-border, comma);
}
-webkit-border-image: $webkit-borders;
border-image: $spec-borders;
border-style: solid;
}
//Examples:
// @include border-image(url("image.png"));
// @include border-image(url("image.png") 20 stretch);
// @include border-image(linear-gradient(45deg, orange, yellow));
// @include border-image(linear-gradient(45deg, orange, yellow) stretch);
// @include border-image(linear-gradient(45deg, orange, yellow) 20 30 40 50 stretch round);
// @include border-image(radial-gradient(top, cover, orange, yellow, orange));
@mixin calc($property, $value) {
#{$property}: -webkit-calc(#{$value});
#{$property}: calc(#{$value});
}
@mixin columns($arg: auto) {
// <column-count> || <column-width>
@include prefixer(columns, $arg, webkit moz spec);
}
@mixin column-count($int: auto) {
// auto || integer
@include prefixer(column-count, $int, webkit moz spec);
}
@mixin column-gap($length: normal) {
// normal || length
@include prefixer(column-gap, $length, webkit moz spec);
}
@mixin column-fill($arg: auto) {
// auto || length
@include prefixer(column-fill, $arg, webkit moz spec);
}
@mixin column-rule($arg) {
// <border-width> || <border-style> || <color>
@include prefixer(column-rule, $arg, webkit moz spec);
}
@mixin column-rule-color($color) {
@include prefixer(column-rule-color, $color, webkit moz spec);
}
@mixin column-rule-style($style: none) {
// none | hidden | dashed | dotted | double | groove | inset | inset | outset | ridge | solid
@include prefixer(column-rule-style, $style, webkit moz spec);
}
@mixin column-rule-width ($width: none) {
@include prefixer(column-rule-width, $width, webkit moz spec);
}
@mixin column-span($arg: none) {
// none || all
@include prefixer(column-span, $arg, webkit moz spec);
}
@mixin column-width($length: auto) {
// auto || length
@include prefixer(column-width, $length, webkit moz spec);
}
@mixin filter($function: none) {
// <filter-function> [<filter-function]* | none
@include prefixer(filter, $function, webkit spec);
}
// CSS3 Flexible Box Model and property defaults
// Custom shorthand notation for flexbox
@mixin box($orient: inline-axis, $pack: start, $align: stretch) {
@include display-box;
@include box-orient($orient);
@include box-pack($pack);
@include box-align($align);
}
@mixin display-box {
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox; // IE 10
display: box;
}
@mixin box-orient($orient: inline-axis) {
// horizontal|vertical|inline-axis|block-axis|inherit
@include prefixer(box-orient, $orient, webkit moz spec);
}
@mixin box-pack($pack: start) {
// start|end|center|justify
@include prefixer(box-pack, $pack, webkit moz spec);
-ms-flex-pack: $pack; // IE 10
}
@mixin box-align($align: stretch) {
// start|end|center|baseline|stretch
@include prefixer(box-align, $align, webkit moz spec);
-ms-flex-align: $align; // IE 10
}
@mixin box-direction($direction: normal) {
// normal|reverse|inherit
@include prefixer(box-direction, $direction, webkit moz spec);
-ms-flex-direction: $direction; // IE 10
}
@mixin box-lines($lines: single) {
// single|multiple
@include prefixer(box-lines, $lines, webkit moz spec);
}
@mixin box-ordinal-group($int: 1) {
@include prefixer(box-ordinal-group, $int, webkit moz spec);
-ms-flex-order: $int; // IE 10
}
@mixin box-flex($value: 0) {
@include prefixer(box-flex, $value, webkit moz spec);
-ms-flex: $value; // IE 10
}
@mixin box-flex-group($int: 1) {
@include prefixer(box-flex-group, $int, webkit moz spec);
}
// CSS3 Flexible Box Model and property defaults
// Unified attributes for 2009, 2011, and 2012 flavours.
// 2009 - display (box | inline-box)
// 2011 - display (flexbox | inline-flexbox)
// 2012 - display (flex | inline-flex)
@mixin display($value) {
// flex | inline-flex
@if $value == "flex" {
// 2009
display: -webkit-box;
display: -moz-box;
display: box;
// 2012
display: -webkit-flex;
display: -moz-flex;
display: -ms-flexbox; // 2011 (IE 10)
display: flex;
} @else if $value == "inline-flex" {
display: -webkit-inline-box;
display: -moz-inline-box;
display: inline-box;
display: -webkit-inline-flex;
display: -moz-inline-flex;
display: -ms-inline-flexbox;
display: inline-flex;
} @else {
display: $value;
}
}
// 2009 - box-flex (integer)
// 2011 - flex (decimal | width decimal)
// 2012 - flex (integer integer width)
@mixin flex($value) {
// Grab flex-grow for older browsers.
$flex-grow: nth($value, 1);
// 2009
@include prefixer(box-flex, $flex-grow, webkit moz spec);
// 2011 (IE 10), 2012
@include prefixer(flex, $value, webkit moz ms spec);
}
// 2009 - box-orient ( horizontal | vertical | inline-axis | block-axis)
// - box-direction (normal | reverse)
// 2011 - flex-direction (row | row-reverse | column | column-reverse)
// 2012 - flex-direction (row | row-reverse | column | column-reverse)
@mixin flex-direction($value: row) {
// Alt values.
$value-2009: $value;
$value-2011: $value;
$direction: normal;
@if $value == row {
$value-2009: horizontal;
} @else if $value == "row-reverse" {
$value-2009: horizontal;
$direction: reverse;
} @else if $value == column {
$value-2009: vertical;
} @else if $value == "column-reverse" {
$value-2009: vertical;
$direction: reverse;
}
// 2009
@include prefixer(box-orient, $value-2009, webkit moz spec);
@include prefixer(box-direction, $direction, webkit moz spec);
// 2012
@include prefixer(flex-direction, $value, webkit moz spec);
// 2011 (IE 10)
-ms-flex-direction: $value;
}
// 2009 - box-lines (single | multiple)
// 2011 - flex-wrap (nowrap | wrap | wrap-reverse)
// 2012 - flex-wrap (nowrap | wrap | wrap-reverse)
@mixin flex-wrap($value: nowrap) {
// Alt values
$alt-value: $value;
@if $value == nowrap {
$alt-value: single;
} @else if $value == wrap {
$alt-value: multiple;
} @else if $value == "wrap-reverse" {
$alt-value: multiple;
}
@include prefixer(box-lines, $alt-value, webkit moz spec);
@include prefixer(flex-wrap, $value, webkit moz ms spec);
}
// 2009 - TODO: parse values into flex-direction/flex-wrap
// 2011 - TODO: parse values into flex-direction/flex-wrap
// 2012 - flex-flow (flex-direction || flex-wrap)
@mixin flex-flow($value) {
@include prefixer(flex-flow, $value, webkit moz spec);
}
// 2009 - box-ordinal-group (integer)
// 2011 - flex-order (integer)
// 2012 - order (integer)
@mixin order($int: 0) {
// 2009
@include prefixer(box-ordinal-group, $int, webkit moz spec);
// 2012
@include prefixer(order, $int, webkit moz spec);
// 2011 (IE 10)
-ms-flex-order: $int;
}
// 2012 - flex-grow (number)
@mixin flex-grow($number: 0) {
@include prefixer(flex-grow, $number, webkit moz spec);
-ms-flex-positive: $number;
}
// 2012 - flex-shrink (number)
@mixin flex-shrink($number: 1) {
@include prefixer(flex-shrink, $number, webkit moz spec);
-ms-flex-negative: $number;
}
// 2012 - flex-basis (number)
@mixin flex-basis($width: auto) {
@include prefixer(flex-basis, $width, webkit moz spec);
-ms-flex-preferred-size: $width;
}
// 2009 - box-pack (start | end | center | justify)
// 2011 - flex-pack (start | end | center | justify)
// 2012 - justify-content (flex-start | flex-end | center | space-between | space-around)
@mixin justify-content($value: flex-start) {
// Alt values.
$alt-value: $value;
@if $value == "flex-start" {
$alt-value: start;
} @else if $value == "flex-end" {
$alt-value: end;
} @else if $value == "space-between" {
$alt-value: justify;
} @else if $value == "space-around" {
$alt-value: distribute;
}
// 2009
@include prefixer(box-pack, $alt-value, webkit moz spec);
// 2012
@include prefixer(justify-content, $value, webkit moz ms o spec);
// 2011 (IE 10)
-ms-flex-pack: $alt-value;
}
// 2009 - box-align (start | end | center | baseline | stretch)
// 2011 - flex-align (start | end | center | baseline | stretch)
// 2012 - align-items (flex-start | flex-end | center | baseline | stretch)
@mixin align-items($value: stretch) {
$alt-value: $value;
@if $value == "flex-start" {
$alt-value: start;
} @else if $value == "flex-end" {
$alt-value: end;
}
// 2009
@include prefixer(box-align, $alt-value, webkit moz spec);
// 2012
@include prefixer(align-items, $value, webkit moz ms o spec);
// 2011 (IE 10)
-ms-flex-align: $alt-value;
}
// 2011 - flex-item-align (auto | start | end | center | baseline | stretch)
// 2012 - align-self (auto | flex-start | flex-end | center | baseline | stretch)
@mixin align-self($value: auto) {
$value-2011: $value;
@if $value == "flex-start" {
$value-2011: start;
} @else if $value == "flex-end" {
$value-2011: end;
}
// 2012
@include prefixer(align-self, $value, webkit moz spec);
// 2011 (IE 10)
-ms-flex-item-align: $value-2011;
}
// 2011 - flex-line-pack (start | end | center | justify | distribute | stretch)
// 2012 - align-content (flex-start | flex-end | center | space-between | space-around | stretch)
@mixin align-content($value: stretch) {
$value-2011: $value;
@if $value == "flex-start" {
$value-2011: start;
} @else if $value == "flex-end" {
$value-2011: end;
} @else if $value == "space-between" {
$value-2011: justify;
} @else if $value == "space-around" {
$value-2011: distribute;
}
// 2012
@include prefixer(align-content, $value, webkit moz spec);
// 2011 (IE 10)
-ms-flex-line-pack: $value-2011;
}
@mixin font-face(
$font-family,
$file-path,
$weight: normal,
$style: normal,
$asset-pipeline: $asset-pipeline,
$file-formats: eot woff2 woff ttf svg) {
$font-url-prefix: font-url-prefixer($asset-pipeline);
@font-face {
font-family: $font-family;
font-style: $style;
font-weight: $weight;
src: font-source-declaration(
$font-family,
$file-path,
$asset-pipeline,
$file-formats,
$font-url-prefix
);
}
}
@mixin font-feature-settings($settings...) {
@if length($settings) == 0 { $settings: none; }
@include prefixer(font-feature-settings, $settings, webkit moz ms spec);
}
// HiDPI mixin. Default value set to 1.3 to target Google Nexus 7 (http://bjango.com/articles/min-device-pixel-ratio/)
@mixin hidpi($ratio: 1.3) {
@media only screen and (-webkit-min-device-pixel-ratio: $ratio),
only screen and (min--moz-device-pixel-ratio: $ratio),
only screen and (-o-min-device-pixel-ratio: #{$ratio}/1),
only screen and (min-resolution: round($ratio * 96dpi)),
only screen and (min-resolution: $ratio * 1dppx) {
@content;
}
}
@mixin hyphens($hyphenation: none) {
// none | manual | auto
@include prefixer(hyphens, $hyphenation, webkit moz ms spec);
}
@mixin image-rendering ($mode:auto) {
@if ($mode == crisp-edges) {
-ms-interpolation-mode: nearest-neighbor; // IE8+
image-rendering: -moz-crisp-edges;
image-rendering: -o-crisp-edges;
image-rendering: -webkit-optimize-contrast;
image-rendering: crisp-edges;
}
@else {
image-rendering: $mode;
}
}
// Adds keyframes blocks for supported prefixes, removing redundant prefixes in the block's content
@mixin keyframes($name) {
$original-prefix-for-webkit: $prefix-for-webkit;
$original-prefix-for-mozilla: $prefix-for-mozilla;
$original-prefix-for-microsoft: $prefix-for-microsoft;
$original-prefix-for-opera: $prefix-for-opera;
$original-prefix-for-spec: $prefix-for-spec;
@if $original-prefix-for-webkit {
@include disable-prefix-for-all();
$prefix-for-webkit: true !global;
@-webkit-keyframes #{$name} {
@content;
}
}
@if $original-prefix-for-mozilla {
@include disable-prefix-for-all();
$prefix-for-mozilla: true !global;
@-moz-keyframes #{$name} {
@content;
}
}
$prefix-for-webkit: $original-prefix-for-webkit !global;
$prefix-for-mozilla: $original-prefix-for-mozilla !global;
$prefix-for-microsoft: $original-prefix-for-microsoft !global;
$prefix-for-opera: $original-prefix-for-opera !global;
$prefix-for-spec: $original-prefix-for-spec !global;
@if $original-prefix-for-spec {
@keyframes #{$name} {
@content;
}
}
}
@mixin linear-gradient($pos, $g1, $g2: null,
$g3: null, $g4: null,
$g5: null, $g6: null,
$g7: null, $g8: null,
$g9: null, $g10: null,
$fallback: null) {
// Detect what type of value exists in $pos
$pos-type: type-of(nth($pos, 1));
$pos-spec: null;
$pos-degree: null;
// If $pos is missing from mixin, reassign vars and add default position
@if ($pos-type == color) or (nth($pos, 1) == "transparent") {
$g10: $g9; $g9: $g8; $g8: $g7; $g7: $g6; $g6: $g5;
$g5: $g4; $g4: $g3; $g3: $g2; $g2: $g1; $g1: $pos;
$pos: null;
}
@if $pos {
$positions: _linear-positions-parser($pos);
$pos-degree: nth($positions, 1);
$pos-spec: nth($positions, 2);
}
$full: $g1, $g2, $g3, $g4, $g5, $g6, $g7, $g8, $g9, $g10;
// Set $g1 as the default fallback color
$fallback-color: nth($g1, 1);
// If $fallback is a color use that color as the fallback color
@if (type-of($fallback) == color) or ($fallback == "transparent") {
$fallback-color: $fallback;
}
background-color: $fallback-color;
background-image: -webkit-linear-gradient($pos-degree $full); // Safari 5.1+, Chrome
background-image: unquote("linear-gradient(#{$pos-spec}#{$full})");
}
@mixin perspective($depth: none) {
// none | <length>
@include prefixer(perspective, $depth, webkit moz spec);
}
@mixin perspective-origin($value: 50% 50%) {
@include prefixer(perspective-origin, $value, webkit moz spec);
}
@mixin placeholder {
$placeholders: ":-webkit-input" ":-moz" "-moz" "-ms-input";
@each $placeholder in $placeholders {
&:#{$placeholder}-placeholder {
@content;
}
}
}
// Requires Sass 3.1+
@mixin radial-gradient($g1, $g2,
$g3: null, $g4: null,
$g5: null, $g6: null,
$g7: null, $g8: null,
$g9: null, $g10: null,
$pos: null,
$shape-size: null,
$fallback: null) {
$data: _radial-arg-parser($g1, $g2, $pos, $shape-size);
$g1: nth($data, 1);
$g2: nth($data, 2);
$pos: nth($data, 3);
$shape-size: nth($data, 4);
$full: $g1, $g2, $g3, $g4, $g5, $g6, $g7, $g8, $g9, $g10;
// Strip deprecated cover/contain for spec
$shape-size-spec: _shape-size-stripper($shape-size);
// Set $g1 as the default fallback color
$first-color: nth($full, 1);
$fallback-color: nth($first-color, 1);
@if (type-of($fallback) == color) or ($fallback == "transparent") {
$fallback-color: $fallback;
}
// Add Commas and spaces
$shape-size: if($shape-size, "#{$shape-size}, ", null);
$pos: if($pos, "#{$pos}, ", null);
$pos-spec: if($pos, "at #{$pos}", null);
$shape-size-spec: if(($shape-size-spec != " ") and ($pos == null), "#{$shape-size-spec}, ", "#{$shape-size-spec} ");
background-color: $fallback-color;
background-image: -webkit-radial-gradient(#{$pos}#{$shape-size}#{$full});
background-image: radial-gradient(#{$shape-size-spec}#{$pos-spec}#{$full});
}
@charset "UTF-8";
/// Outputs the spec and prefixed versions of the `::selection` pseudo-element.
///
/// @param {Bool} $current-selector [false]
/// If set to `true`, it takes the current element into consideration.
///
/// @example scss - Usage
/// .element {
/// @include selection(true) {
/// background-color: #ffbb52;
/// }
/// }
///
/// @example css - CSS Output
/// .element::-moz-selection {
/// background-color: #ffbb52;
/// }
///
/// .element::selection {
/// background-color: #ffbb52;
/// }
@mixin selection($current-selector: false) {
@if $current-selector {
&::-moz-selection {
@content;
}
&::selection {
@content;
}
} @else {
::-moz-selection {
@content;
}
::selection {
@content;
}
}
}
@mixin text-decoration($value) {
// <text-decoration-line> || <text-decoration-style> || <text-decoration-color>
@include prefixer(text-decoration, $value, moz);
}
@mixin text-decoration-line($line: none) {
// none || underline || overline || line-through
@include prefixer(text-decoration-line, $line, moz);
}
@mixin text-decoration-style($style: solid) {
// solid || double || dotted || dashed || wavy
@include prefixer(text-decoration-style, $style, moz webkit);
}
@mixin text-decoration-color($color: currentColor) {
// currentColor || <color>
@include prefixer(text-decoration-color, $color, moz);
}
@mixin transform($property: none) {
// none | <transform-function>
@include prefixer(transform, $property, webkit moz ms o spec);
}
@mixin transform-origin($axes: 50%) {
// x-axis - left | center | right | length | %
// y-axis - top | center | bottom | length | %
// z-axis - length
@include prefixer(transform-origin, $axes, webkit moz ms o spec);
}
@mixin transform-style($style: flat) {
@include prefixer(transform-style, $style, webkit moz ms o spec);
}
// Shorthand mixin. Supports multiple parentheses-deliminated values for each variable.
// Example: @include transition (all 2s ease-in-out);
// @include transition (opacity 1s ease-in 2s, width 2s ease-out);
// @include transition-property (transform, opacity);
@mixin transition($properties...) {
// Fix for vendor-prefix transform property
$needs-prefixes: false;
$webkit: ();
$moz: ();
$spec: ();
// Create lists for vendor-prefixed transform
@each $list in $properties {
@if nth($list, 1) == "transform" {
$needs-prefixes: true;
$list1: -webkit-transform;
$list2: -moz-transform;
$list3: ();
@each $var in $list {
$list3: join($list3, $var);
@if $var != "transform" {
$list1: join($list1, $var);
$list2: join($list2, $var);
}
}
$webkit: append($webkit, $list1);
$moz: append($moz, $list2);
$spec: append($spec, $list3);
} @else {
$webkit: append($webkit, $list, comma);
$moz: append($moz, $list, comma);
$spec: append($spec, $list, comma);
}
}
@if $needs-prefixes {
-webkit-transition: $webkit;
-moz-transition: $moz;
transition: $spec;
} @else {
@if length($properties) >= 1 {
@include prefixer(transition, $properties, webkit moz spec);
} @else {
$properties: all 0.15s ease-out 0s;
@include prefixer(transition, $properties, webkit moz spec);
}
}
}
@mixin transition-property($properties...) {
-webkit-transition-property: transition-property-names($properties, "webkit");
-moz-transition-property: transition-property-names($properties, "moz");
transition-property: transition-property-names($properties, false);
}
@mixin transition-duration($times...) {
@include prefixer(transition-duration, $times, webkit moz spec);
}
@mixin transition-timing-function($motions...) {
// ease | linear | ease-in | ease-out | ease-in-out | cubic-bezier()
@include prefixer(transition-timing-function, $motions, webkit moz spec);
}
@mixin transition-delay($times...) {
@include prefixer(transition-delay, $times, webkit moz spec);
}
@mixin user-select($value: none) {
@include prefixer(user-select, $value, webkit moz ms spec);
}
@function assign-inputs($inputs, $pseudo: null) {
$list: ();
@each $input in $inputs {
$input: unquote($input);
$input: if($pseudo, $input + ":" + $pseudo, $input);
$list: append($list, $input, comma);
}
@return $list;
}
@charset "UTF-8";
/// Checks if a list does not contains a value.
///
/// @access private
///
/// @param {List} $list
/// The list to check against.
///
/// @return {Bool}
@function contains-falsy($list) {
@each $item in $list {
@if not $item {
@return true;
}
}
@return false;
}
@charset "UTF-8";
/// Checks if a list contains a value(s).
///
/// @access private
///
/// @param {List} $list
/// The list to check against.
///
/// @param {List} $values
/// A single value or list of values to check for.
///
/// @example scss - Usage
/// contains($list, $value)
///
/// @return {Bool}
@function contains($list, $values...) {
@each $value in $values {
@if type-of(index($list, $value)) != "number" {
@return false;
}
}
@return true;
}
@charset "UTF-8";
/// Checks for a valid CSS length.
///
/// @param {String} $value
@function is-length($value) {
@return type-of($value) != "null" and (str-slice($value + "", 1, 4) == "calc"
or index(auto inherit initial 0, $value)
or (type-of($value) == "number" and not(unitless($value))));
}
@charset "UTF-8";
/// Programatically determines whether a color is light or dark.
///
/// @link http://robots.thoughtbot.com/closer-look-color-lightness
///
/// @param {Color (Hex)} $color
///
/// @example scss - Usage
/// is-light($color)
///
/// @return {Bool}
@function is-light($hex-color) {
$-local-red: red(rgba($hex-color, 1));
$-local-green: green(rgba($hex-color, 1));
$-local-blue: blue(rgba($hex-color, 1));
$-local-lightness: ($-local-red * 0.2126 + $-local-green * 0.7152 + $-local-blue * 0.0722) / 255;
@return $-local-lightness > 0.6;
}
@charset "UTF-8";
/// Checks for a valid number.
///
/// @param {Number} $value
///
/// @require {function} contains
@function is-number($value) {
@return contains("0" "1" "2" "3" "4" "5" "6" "7" "8" "9" 0 1 2 3 4 5 6 7 8 9, $value);
}
@charset "UTF-8";
/// Checks for a valid CSS size.
///
/// @param {String} $value
///
/// @require {function} contains
/// @require {function} is-length
@function is-size($value) {
@return is-length($value)
or contains("fill" "fit-content" "min-content" "max-content", $value);
}
// Scaling Variables
$golden: 1.618;
$minor-second: 1.067;
$major-second: 1.125;
$minor-third: 1.2;
$major-third: 1.25;
$perfect-fourth: 1.333;
$augmented-fourth: 1.414;
$perfect-fifth: 1.5;
$minor-sixth: 1.6;
$major-sixth: 1.667;
$minor-seventh: 1.778;
$major-seventh: 1.875;
$octave: 2;
$major-tenth: 2.5;
$major-eleventh: 2.667;
$major-twelfth: 3;
$double-octave: 4;
$modular-scale-ratio: $perfect-fourth !default;
$modular-scale-base: em($em-base) !default;
@function modular-scale($increment, $value: $modular-scale-base, $ratio: $modular-scale-ratio) {
$v1: nth($value, 1);
$v2: nth($value, length($value));
$value: $v1;
// scale $v2 to just above $v1
@while $v2 > $v1 {
$v2: ($v2 / $ratio); // will be off-by-1
}
@while $v2 < $v1 {
$v2: ($v2 * $ratio); // will fix off-by-1
}
// check AFTER scaling $v2 to prevent double-counting corner-case
$double-stranded: $v2 > $v1;
@if $increment > 0 {
@for $i from 1 through $increment {
@if $double-stranded and ($v1 * $ratio) > $v2 {
$value: $v2;
$v2: ($v2 * $ratio);
} @else {
$v1: ($v1 * $ratio);
$value: $v1;
}
}
}
@if $increment < 0 {
// adjust $v2 to just below $v1
@if $double-stranded {
$v2: ($v2 / $ratio);
}
@for $i from $increment through -1 {
@if $double-stranded and ($v1 / $ratio) < $v2 {
$value: $v2;
$v2: ($v2 / $ratio);
} @else {
$v1: ($v1 / $ratio);
$value: $v1;
}
}
}
@return $value;
}
// Convert pixels to ems
// eg. for a relational value of 12px write em(12) when the parent is 16px
// if the parent is another value say 24px write em(12, 24)
@function em($pxval, $base: $em-base) {
@if not unitless($pxval) {
$pxval: strip-units($pxval);
}
@if not unitless($base) {
$base: strip-units($base);
}
@return ($pxval / $base) * 1em;
}
// Convert pixels to rems
// eg. for a relational value of 12px write rem(12)
// Assumes $em-base is the font-size of <html>
@function rem($pxval) {
@if not unitless($pxval) {
$pxval: strip-units($pxval);
}
$base: $em-base;
@if not unitless($base) {
$base: strip-units($base);
}
@return ($pxval / $base) * 1rem;
}
@charset "UTF-8";
/// Mixes a color with black.
///
/// @param {Color} $color
///
/// @param {Number (Percentage)} $percent
/// The amount of black to be mixed in.
///
/// @example scss - Usage
/// .element {
/// background-color: shade(#ffbb52, 60%);
/// }
///
/// @example css - CSS Output
/// .element {
/// background-color: #664a20;
/// }
///
/// @return {Color}
@function shade($color, $percent) {
@return mix(#000, $color, $percent);
}
@charset "UTF-8";
/// Strips the unit from a number.
///
/// @param {Number (With Unit)} $value
///
/// @example scss - Usage
/// $dimension: strip-units(10em);
///
/// @example css - CSS Output
/// $dimension: 10;
///
/// @return {Number (Unitless)}
@function strip-units($value) {
@return ($value / ($value * 0 + 1));
}
@charset "UTF-8";
/// Mixes a color with white.
///
/// @param {Color} $color
///
/// @param {Number (Percentage)} $percent
/// The amount of white to be mixed in.
///
/// @example scss - Usage
/// .element {
/// background-color: tint(#6ecaa6, 40%);
/// }
///
/// @example css - CSS Output
/// .element {
/// background-color: #a8dfc9;
/// }
///
/// @return {Color}
@function tint($color, $percent) {
@return mix(#fff, $color, $percent);
}
// Return vendor-prefixed property names if appropriate
// Example: transition-property-names((transform, color, background), moz) -> -moz-transform, color, background
//************************************************************************//
@function transition-property-names($props, $vendor: false) {
$new-props: ();
@each $prop in $props {
$new-props: append($new-props, transition-property-name($prop, $vendor), comma);
}
@return $new-props;
}
@function transition-property-name($prop, $vendor: false) {
// put other properties that need to be prefixed here aswell
@if $vendor and $prop == transform {
@return unquote('-'+$vendor+'-'+$prop);
}
@else {
@return $prop;
}
}
@charset "UTF-8";
/// Converts shorthand to the 4-value syntax.
///
/// @param {List} $shorthand
///
/// @example scss - Usage
/// .element {
/// margin: unpack(1em 2em);
/// }
///
/// @example css - CSS Output
/// .element {
/// margin: 1em 2em 1em 2em;
/// }
@function unpack($shorthand) {
@if length($shorthand) == 1 {
@return nth($shorthand, 1) nth($shorthand, 1) nth($shorthand, 1) nth($shorthand, 1);
} @else if length($shorthand) == 2 {
@return nth($shorthand, 1) nth($shorthand, 2) nth($shorthand, 1) nth($shorthand, 2);
} @else if length($shorthand) == 3 {
@return nth($shorthand, 1) nth($shorthand, 2) nth($shorthand, 3) nth($shorthand, 2);
} @else {
@return $shorthand;
}
}
//************************************************************************//
// Helper function for str-to-num fn.
// Source: http://sassmeister.com/gist/9647408
//************************************************************************//
@function _convert-units($number, $unit) {
$strings: "px", "cm", "mm", "%", "ch", "pica", "in", "em", "rem", "pt", "pc", "ex", "vw", "vh", "vmin", "vmax", "deg", "rad", "grad", "turn";
$units: 1px, 1cm, 1mm, 1%, 1ch, 1pica, 1in, 1em, 1rem, 1pt, 1pc, 1ex, 1vw, 1vh, 1vmin, 1vmax, 1deg, 1rad, 1grad, 1turn;
$index: index($strings, $unit);
@if not $index {
@warn "Unknown unit `#{$unit}`.";
@return false;
}
@if type-of($number) != "number" {
@warn "`#{$number} is not a number`";
@return false;
}
@return $number * nth($units, $index);
}
@charset "UTF-8";
/// Directional-property mixins are shorthands for writing properties like the following
///
/// @ignore You can also use `false` instead of `null`.
///
/// @param {List} $vals
/// List of directional values
///
/// @example scss - Usage
/// .element {
/// @include border-style(dotted null);
/// @include margin(null 0 10px);
/// }
///
/// @example css - CSS Output
/// .element {
/// border-bottom-style: dotted;
/// border-top-style: dotted;
/// margin-bottom: 10px;
/// margin-left: 0;
/// margin-right: 0;
/// }
///
/// @require {function} contains-falsy
///
/// @return {List}
@function collapse-directionals($vals) {
$output: null;
$a: nth($vals, 1);
$b: if(length($vals) < 2, $a, nth($vals, 2));
$c: if(length($vals) < 3, $a, nth($vals, 3));
$d: if(length($vals) < 2, $a, nth($vals, if(length($vals) < 4, 2, 4)));
@if $a == 0 { $a: 0; }
@if $b == 0 { $b: 0; }
@if $c == 0 { $c: 0; }
@if $d == 0 { $d: 0; }
@if $a == $b and $a == $c and $a == $d { $output: $a; }
@else if $a == $c and $b == $d { $output: $a $b; }
@else if $b == $d { $output: $a $b $c; }
@else { $output: $a $b $c $d; }
@return $output;
}
/// Output directional properties, for instance `margin`.
///
/// @access private
///
/// @param {String} $pre
/// Prefix to use
/// @param {String} $suf
/// Suffix to use
/// @param {List} $vals
/// List of values
///
/// @require {function} collapse-directionals
/// @require {function} contains-falsy
@mixin directional-property($pre, $suf, $vals) {
// Property Names
$top: $pre + "-top" + if($suf, "-#{$suf}", "");
$bottom: $pre + "-bottom" + if($suf, "-#{$suf}", "");
$left: $pre + "-left" + if($suf, "-#{$suf}", "");
$right: $pre + "-right" + if($suf, "-#{$suf}", "");
$all: $pre + if($suf, "-#{$suf}", "");
$vals: collapse-directionals($vals);
@if contains-falsy($vals) {
@if nth($vals, 1) { #{$top}: nth($vals, 1); }
@if length($vals) == 1 {
@if nth($vals, 1) { #{$right}: nth($vals, 1); }
} @else {
@if nth($vals, 2) { #{$right}: nth($vals, 2); }
}
@if length($vals) == 2 {
@if nth($vals, 1) { #{$bottom}: nth($vals, 1); }
@if nth($vals, 2) { #{$left}: nth($vals, 2); }
} @else if length($vals) == 3 {
@if nth($vals, 3) { #{$bottom}: nth($vals, 3); }
@if nth($vals, 2) { #{$left}: nth($vals, 2); }
} @else if length($vals) == 4 {
@if nth($vals, 3) { #{$bottom}: nth($vals, 3); }
@if nth($vals, 4) { #{$left}: nth($vals, 4); }
}
} @else {
#{$all}: $vals;
}
}
// Used for creating the source string for fonts using @font-face
// Reference: http://goo.gl/Ru1bKP
@function font-url-prefixer($asset-pipeline) {
@if $asset-pipeline == true {
@return font-url;
} @else {
@return url;
}
}
@function font-source-declaration(
$font-family,
$file-path,
$asset-pipeline,
$file-formats,
$font-url) {
$src: ();
$formats-map: (
eot: "#{$file-path}.eot?#iefix" format("embedded-opentype"),
woff2: "#{$file-path}.woff2" format("woff2"),
woff: "#{$file-path}.woff" format("woff"),
ttf: "#{$file-path}.ttf" format("truetype"),
svg: "#{$file-path}.svg##{$font-family}" format("svg")
);
@each $key, $values in $formats-map {
@if contains($file-formats, $key) {
$file-path: nth($values, 1);
$font-format: nth($values, 2);
@if $asset-pipeline == true {
$src: append($src, font-url($file-path) $font-format, comma);
} @else {
$src: append($src, url($file-path) $font-format, comma);
}
}
}
@return $src;
}
@function _gradient-positions-parser($gradient-type, $gradient-positions) {
@if $gradient-positions
and ($gradient-type == linear)
and (type-of($gradient-positions) != color) {
$gradient-positions: _linear-positions-parser($gradient-positions);
}
@else if $gradient-positions
and ($gradient-type == radial)
and (type-of($gradient-positions) != color) {
$gradient-positions: _radial-positions-parser($gradient-positions);
}
@return $gradient-positions;
}
// Private function for linear-gradient-parser
@function _linear-angle-parser($image, $first-val, $prefix, $suffix) {
$offset: null;
$unit-short: str-slice($first-val, str-length($first-val) - 2, str-length($first-val));
$unit-long: str-slice($first-val, str-length($first-val) - 3, str-length($first-val));
@if ($unit-long == "grad") or
($unit-long == "turn") {
$offset: if($unit-long == "grad", -100grad * 3, -0.75turn);
}
@else if ($unit-short == "deg") or
($unit-short == "rad") {
$offset: if($unit-short == "deg", -90 * 3, 1.6rad);
}
@if $offset {
$num: _str-to-num($first-val);
@return (
webkit-image: -webkit- + $prefix + ($offset - $num) + $suffix,
spec-image: $image
);
}
}
@function _linear-gradient-parser($image) {
$image: unquote($image);
$gradients: ();
$start: str-index($image, "(");
$end: str-index($image, ",");
$first-val: str-slice($image, $start + 1, $end - 1);
$prefix: str-slice($image, 1, $start);
$suffix: str-slice($image, $end, str-length($image));
$has-multiple-vals: str-index($first-val, " ");
$has-single-position: unquote(_position-flipper($first-val) + "");
$has-angle: is-number(str-slice($first-val, 1, 1));
@if $has-multiple-vals {
$gradients: _linear-side-corner-parser($image, $first-val, $prefix, $suffix, $has-multiple-vals);
}
@else if $has-single-position != "" {
$pos: unquote($has-single-position + "");
$gradients: (
webkit-image: -webkit- + $image,
spec-image: $prefix + "to " + $pos + $suffix
);
}
@else if $has-angle {
// Rotate degree for webkit
$gradients: _linear-angle-parser($image, $first-val, $prefix, $suffix);
}
@else {
$gradients: (
webkit-image: -webkit- + $image,
spec-image: $image
);
}
@return $gradients;
}
@function _linear-positions-parser($pos) {
$type: type-of(nth($pos, 1));
$spec: null;
$degree: null;
$side: null;
$corner: null;
$length: length($pos);
// Parse Side and corner positions
@if ($length > 1) {
@if nth($pos, 1) == "to" { // Newer syntax
$side: nth($pos, 2);
@if $length == 2 { // eg. to top
// Swap for backwards compatibility
$degree: _position-flipper(nth($pos, 2));
}
@else if $length == 3 { // eg. to top left
$corner: nth($pos, 3);
}
}
@else if $length == 2 { // Older syntax ("top left")
$side: _position-flipper(nth($pos, 1));
$corner: _position-flipper(nth($pos, 2));
}
@if ("#{$side} #{$corner}" == "left top") or ("#{$side} #{$corner}" == "top left") {
$degree: _position-flipper(#{$side}) _position-flipper(#{$corner});
}
@else if ("#{$side} #{$corner}" == "right top") or ("#{$side} #{$corner}" == "top right") {
$degree: _position-flipper(#{$side}) _position-flipper(#{$corner});
}
@else if ("#{$side} #{$corner}" == "right bottom") or ("#{$side} #{$corner}" == "bottom right") {
$degree: _position-flipper(#{$side}) _position-flipper(#{$corner});
}
@else if ("#{$side} #{$corner}" == "left bottom") or ("#{$side} #{$corner}" == "bottom left") {
$degree: _position-flipper(#{$side}) _position-flipper(#{$corner});
}
$spec: to $side $corner;
}
@else if $length == 1 {
// Swap for backwards compatibility
@if $type == string {
$degree: $pos;
$spec: to _position-flipper($pos);
}
@else {
$degree: -270 - $pos; //rotate the gradient opposite from spec
$spec: $pos;
}
}
$degree: unquote($degree + ",");
$spec: unquote($spec + ",");
@return $degree $spec;
}
@function _position-flipper($pos) {
@return if($pos == left, right, null)
if($pos == right, left, null)
if($pos == top, bottom, null)
if($pos == bottom, top, null);
}
// Private function for linear-gradient-parser
@function _linear-side-corner-parser($image, $first-val, $prefix, $suffix, $has-multiple-vals) {
$val-1: str-slice($first-val, 1, $has-multiple-vals - 1);
$val-2: str-slice($first-val, $has-multiple-vals + 1, str-length($first-val));
$val-3: null;
$has-val-3: str-index($val-2, " ");
@if $has-val-3 {
$val-3: str-slice($val-2, $has-val-3 + 1, str-length($val-2));
$val-2: str-slice($val-2, 1, $has-val-3 - 1);
}
$pos: _position-flipper($val-1) _position-flipper($val-2) _position-flipper($val-3);
$pos: unquote($pos + "");
// Use old spec for webkit
@if $val-1 == "to" {
@return (
webkit-image: -webkit- + $prefix + $pos + $suffix,
spec-image: $image
);
}
// Bring the code up to spec
@else {
@return (
webkit-image: -webkit- + $image,
spec-image: $prefix + "to " + $pos + $suffix
);
}
}
@function _radial-arg-parser($g1, $g2, $pos, $shape-size) {
@each $value in $g1, $g2 {
$first-val: nth($value, 1);
$pos-type: type-of($first-val);
$spec-at-index: null;
// Determine if spec was passed to mixin
@if type-of($value) == list {
$spec-at-index: if(index($value, at), index($value, at), false);
}
@if $spec-at-index {
@if $spec-at-index > 1 {
@for $i from 1 through ($spec-at-index - 1) {
$shape-size: $shape-size nth($value, $i);
}
@for $i from ($spec-at-index + 1) through length($value) {
$pos: $pos nth($value, $i);
}
}
@else if $spec-at-index == 1 {
@for $i from ($spec-at-index + 1) through length($value) {
$pos: $pos nth($value, $i);
}
}
$g1: null;
}
// If not spec calculate correct values
@else {
@if ($pos-type != color) or ($first-val != "transparent") {
@if ($pos-type == number)
or ($first-val == "center")
or ($first-val == "top")
or ($first-val == "right")
or ($first-val == "bottom")
or ($first-val == "left") {
$pos: $value;
@if $pos == $g1 {
$g1: null;
}
}
@else if
($first-val == "ellipse")
or ($first-val == "circle")
or ($first-val == "closest-side")
or ($first-val == "closest-corner")
or ($first-val == "farthest-side")
or ($first-val == "farthest-corner")
or ($first-val == "contain")
or ($first-val == "cover") {
$shape-size: $value;
@if $value == $g1 {
$g1: null;
}
@else if $value == $g2 {
$g2: null;
}
}
}
}
}
@return $g1, $g2, $pos, $shape-size;
}
@function _radial-gradient-parser($image) {
$image: unquote($image);
$gradients: ();
$start: str-index($image, "(");
$end: str-index($image, ",");
$first-val: str-slice($image, $start + 1, $end - 1);
$prefix: str-slice($image, 1, $start);
$suffix: str-slice($image, $end, str-length($image));
$is-spec-syntax: str-index($first-val, "at");
@if $is-spec-syntax and $is-spec-syntax > 1 {
$keyword: str-slice($first-val, 1, $is-spec-syntax - 2);
$pos: str-slice($first-val, $is-spec-syntax + 3, str-length($first-val));
$pos: append($pos, $keyword, comma);
$gradients: (
webkit-image: -webkit- + $prefix + $pos + $suffix,
spec-image: $image
);
}
@else if $is-spec-syntax == 1 {
$pos: str-slice($first-val, $is-spec-syntax + 3, str-length($first-val));
$gradients: (
webkit-image: -webkit- + $prefix + $pos + $suffix,
spec-image: $image
);
}
@else if str-index($image, "cover") or str-index($image, "contain") {
@warn "Radial-gradient needs to be updated to conform to latest spec.";
$gradients: (
webkit-image: null,
spec-image: $image
);
}
@else {
$gradients: (
webkit-image: -webkit- + $image,
spec-image: $image
);
}
@return $gradients;
}
@function _radial-positions-parser($gradient-pos) {
$shape-size: nth($gradient-pos, 1);
$pos: nth($gradient-pos, 2);
$shape-size-spec: _shape-size-stripper($shape-size);
$pre-spec: unquote(if($pos, "#{$pos}, ", null))
unquote(if($shape-size, "#{$shape-size},", null));
$pos-spec: if($pos, "at #{$pos}", null);
$spec: "#{$shape-size-spec} #{$pos-spec}";
// Add comma
@if ($spec != " ") {
$spec: "#{$spec},";
}
@return $pre-spec $spec;
}
// User for linear and radial gradients within background-image or border-image properties
@function _render-gradients($gradient-positions, $gradients, $gradient-type, $vendor: false) {
$pre-spec: null;
$spec: null;
$vendor-gradients: null;
@if $gradient-type == linear {
@if $gradient-positions {
$pre-spec: nth($gradient-positions, 1);
$spec: nth($gradient-positions, 2);
}
}
@else if $gradient-type == radial {
$pre-spec: nth($gradient-positions, 1);
$spec: nth($gradient-positions, 2);
}
@if $vendor {
$vendor-gradients: -#{$vendor}-#{$gradient-type}-gradient(#{$pre-spec} $gradients);
}
@else if $vendor == false {
$vendor-gradients: "#{$gradient-type}-gradient(#{$spec} #{$gradients})";
$vendor-gradients: unquote($vendor-gradients);
}
@return $vendor-gradients;
}
@function _shape-size-stripper($shape-size) {
$shape-size-spec: null;
@each $value in $shape-size {
@if ($value == "cover") or ($value == "contain") {
$value: null;
}
$shape-size-spec: "#{$shape-size-spec} #{$value}";
}
@return $shape-size-spec;
}
//************************************************************************//
// Helper function for linear/radial-gradient-parsers.
// Source: http://sassmeister.com/gist/9647408
//************************************************************************//
@function _str-to-num($string) {
// Matrices
$strings: "0" "1" "2" "3" "4" "5" "6" "7" "8" "9";
$numbers: 0 1 2 3 4 5 6 7 8 9;
// Result
$result: 0;
$divider: 0;
$minus: false;
// Looping through all characters
@for $i from 1 through str-length($string) {
$character: str-slice($string, $i, $i);
$index: index($strings, $character);
@if $character == "-" {
$minus: true;
}
@else if $character == "." {
$divider: 1;
}
@else {
@if not $index {
$result: if($minus, $result * -1, $result);
@return _convert-units($result, str-slice($string, $i));
}
$number: nth($numbers, $index);
@if $divider == 0 {
$result: $result * 10;
}
@else {
// Move the decimal dot to the left
$divider: $divider * 10;
$number: $number / $divider;
}
$result: $result + $number;
}
}
@return if($minus, $result * -1, $result);
}
@charset "UTF-8";
/// A global setting to enable or disable the `$asset-pipeline` variable for all functions that accept it.
///
/// @type Bool
$asset-pipeline: false !default;
@charset "UTF-8";
/// Global variables to enable or disable vendor prefixes
$prefix-for-webkit: true !default;
$prefix-for-mozilla: true !default;
$prefix-for-microsoft: true !default;
$prefix-for-opera: true !default;
$prefix-for-spec: true !default;
{% extends "forms/field.html.twig" %}
{% set passwordStored = value ? 1 : 0 %}
{% set isEncrypted = value|starts_with('gitsync-') %}
{% set value = '' %}
{% block input_attributes %}
type="password"
class="input"
placeholder="{{ passwordStored ? (isEncrypted ? 'Your password is securely stored.' : 'Your password is stored but not encrypted.') : field.placeholder }}"
{{ parent() }}
{% endblock %}
{% extends "forms/default/field.html.twig" %}
{% block label %}{% endblock %}
{% block input %}
<script type="text/javascript">
GitSync = {
'first_time': {{ git_sync.first_time|json_encode|raw }},
'git_installed': {{ git_sync.git_installed|json_encode|raw }}
};
</script>
<a href="#" data-gitsync-useraction="wizard" class="button button-primary"><i class="fa fa-fw fa-magic"></i> Wizard</a>
<a href="#" data-gitsync-useraction="sync" class="button button-primary"><i class="fa fa-fw fa-cloud"></i> Synchronize</a>
<span class="danger button-bar"><a href="#" data-gitsync-useraction="reset" class="button danger"><i class="fa fa-fw fa-history"></i> Reset Local Copy</a></span>
{% include 'partials/modal-wizard.html.twig' %}
{% include 'partials/modal-reset.html.twig' %}
{% endblock %}
<div class="remodal" data-remodal-id="reset-local" data-remodal-options="hashTracking: false">
<h1>Git Sync - Reset Local Copy</h1>
<div class="step-0">
<div class="panel">
<p>By proceeding with the reset, all your local changes, if any, will be reverted to the repository state.</p>
<p>
Are you sure you want to continue?
</p>
</div>
</div>
<div class="button-bar">
<a class="button secondary" data-remodal-action="cancel" href="#">No</a>
<a class="button" data-gitsync-action="reset-local" href="#"> Yes</a>
</div>
</div>
{% set settings = config.plugins['git-sync'] %}
{% set frontend_url = (uri.base ~ uri.rootUrl)|trim('/') %}
<div class="remodal" data-remodal-id="wizard" data-remodal-options="hashTracking: false, closeOnOutsideClick: false">
<h1>Git Sync - Wizard</h1>
{% if git_sync.git_installed %}
<div class="step-0">
<div class="panel">
<p>
This wizard will guide you through a few simple steps that will help you set up the plugin and your <i class="fa fa-fw fa-git"></i> repository. When done you will have a bi-directional synchronization link between your site and the <i class="fa fa-fw fa-git"></i> repository.
</p>
<p>
Press <strong>Next</strong> to continue.
</p>
</div>
</div>
<div class="step-1">
{% set selectedRepo = 'github.com' in settings.repository ? 'github' : selectedRepo %}
{% set selectedRepo = 'bitbucket.com' in settings.repository ? 'bitbucket' : selectedRepo %}
{% set selectedRepo = 'gitlab.com' in settings.repository ? 'gitlab' : selectedRepo %}
{% set selectedRepo = not (settings.repository is empty) and not selectedRepo ? 'allothers' : selectedRepo %}
<h1>Step 1 - Hosting Service</h1>
<div class="panel hidden disabled">
<p>
Select the <i class="fa fa-fw fa-git"></i> repository-hosting service that you will be using to remotely store your data. Once you picked one, insert the username and password or token to access the service. Passwords are saved securely.
</p>
<div class="columns wizard-padding">
<div class="column">
<label>
<input type="radio" value="github" name="gitsync[repository]" {{ selectedRepo == 'github' ? 'checked' : '' }} />
<img src="{{ url('plugin://git-sync/images/repos/github.svg') }}" />
<a href="https://github.com/join?source=header-home" target="_blank">(create account)</a>
</label>
<label>
<input type="radio" value="bitbucket" name="gitsync[repository]" {{ selectedRepo == 'bitbucket' ? 'checked' : '' }} />
<img src="{{ url('plugin://git-sync/images/repos/bitbucket.svg') }}" />
<a href="https://bitbucket.org/account/signup/" target="_blank">(create account)</a>
</label>
<label>
<input type="radio" value="gitlab" name="gitsync[repository]" {{ selectedRepo == 'gitlab' ? 'checked' : '' }} />
<img src="{{ url('plugin://git-sync/images/repos/gitlab.svg') }}" />
<a href="https://gitlab.com/users/sign_in" target="_blank">(create account)</a>
</label>
<label>
<input type="radio" value="allothers" name="gitsync[repository]" {{ selectedRepo == 'allothers' ? 'checked' : '' }} />
<img src="{{ url('plugin://git-sync/images/repos/gitlogo.svg') }}" />
<small>(any Git service with webhooks)</small>
</label>
</div>
<div class="column">
<label>
Git User
<input type="text" name="gitsync[repo_user]" placeholder="Username, not email" value="{{ settings.user|default('') }}" />
</label>
<label>
Git Password or Token
<input type="password" name="gitsync[repo_password]" placeholder="Password" value="" />
</label>
</div>
</div>
<div class="access-tokens wizard-padding">
<h3>Access Tokens and 2FA (Two-factor Authentication)</h3>
<p>If you have never used Access Tokens or don't know what 2FA is, you most likely only need your <strong>regular password</strong> for the field above. You can then skip this step and press <strong>Next</strong> to continue.</p>
<p><a href="#" class="button" data-access-tokens-details>More Details <i class="fa fa-fw fa-chevron-down"></i></a></p>
<div class="access-tokens-details" style="display: none;">
<p>With GitSync you have the option to use an Access Token instead of your password, however if you have <strong>2FA (Two-factor authentication)</strong> enabled for your git service, you can use Access Tokens.</p>
<p>Using a token is more secure than using your password, because you can limit what can be done using it and it can be revoked without worrying about changing the password. As we are just syncing, we only need to give it read-and-write access to repositories and webhooks.
</p>
<div class="hidden-step-github hidden">
<h4>GitHub</h4>
<p>
With <a href="https://help.github.com/articles/creating-an-access-token-for-command-line-use/" target="_blank">GitHub's Personal Access Tokens</a>, select the following Scopes:
</p>
<ul>
<li><i class="fa fa-check-square-o"></i> <code>repo</code> (Full control of private repositories)</li>
<li><i class="fa fa-check-square-o"></i> <code>admin:repo_hook</code> (Full control of repository hooks)</li>
</ul>
</div>
<div class="hidden-step-bitbucket hidden">
<h4>Bitbucket</h4>
<p>
With <a href="https://confluence.atlassian.com/bitbucket/app-passwords-828781300.html" target="_blank">BitBucket's App Passwords</a>, select the following Permissions:
</p>
<ul>
<li>Repositories:
<ul>
<li><i class="fa fa-check-square-o"></i> <code>Read</code></li>
<li><i class="fa fa-check-square-o"></i> <code>Write</code></li>
</ul>
</li>
<li>Webhooks
<ul>
<li><i class="fa fa-check-square-o"></i> <code>Read and write</code></li>
</ul>
</li>
</ul>
</div>
<div class="hidden-step-gitlab hidden">
<h4>GitLab</h4>
<p>
For <a href="https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html" target="_blank">GitLab's Personal Access Tokens</a> select the following Scopes:
</p>
<ul>
<li><i class="fa fa-check-square-o"></i> <code>API</code> (Access your API)</li>
</ul>
</div>
<div class="hidden-step-allothers hidden">
<h4>Other Git Repositories</h4>
<p>Usually under your profile settings is where you can generate a new Access Token. You want to ensure that you have enough read/write access for the repository as well as <code>webhook</code> access</p>
</div>
</div>
</div>
<p>
Press <strong>Next</strong> to continue.
</p>
</div>
</div>
<div class="step-2">
<h1>Step 2 - Setting up the Repository</h1>
<div class="panel hidden disabled">
<p>
If you already have a repository set up and ready to use, you can skip this step. Otherwise, please follow these steps:
</p>
<div class="wizard-padding">
<ol>
<li>If you are creating a new repository (recommended), you must create an initial commit.
<ul>
<li>The service providers often ask you to do this when you made it.</li>
<li>Check the box that says Initialize repository with a README or .gitignore</li>
<li>This is required for GitSync to work.</li>
</ul>
</li>
<li>Once you have made your repository, copy the full HTTPS URL and paste it into the box below.
<ul>
<li>Most services offer both SSH and HTTPS addresses, <strong>but only HTTPS is supported at this time</strong>.</li>
</ul>
</li>
</ol>
</div>
<div class="hidden-step-github wizard-padding hidden">
<h4>GitHub</h4>
<p>
Follow the instructions on <a href="https://help.github.com/articles/creating-a-new-repository/" target="_blank">GitHub Help</a> to create a new GitHub repository.
</p>
</div>
<div class="hidden-step-bitbucket wizard-padding hidden">
<h4>Bitbucket</h4>
<p>
Follow the instructions on <a href="https://confluence.atlassian.com/bitbucket/create-a-git-repository-759857290.html" target="_blank">Atlassian Documentation</a> to create a new Bitbucket repository. Make sure you select <strong>Git</strong> as repository type.
</p>
</div>
<div class="hidden-step-gitlab wizard-padding hidden">
<h4>GitLab</h4>
<p>
GitLab calls repositories "Projects". Follow the instructions on <a href="https://docs.gitlab.com/ce/gitlab-basics/create-project.html" target="_blank">GitLab Documentation</a> to create a new GitLab repository.
</p>
</div>
<div class="hidden-step-allothers wizard-padding hidden">
<h4>Other Git Repositories</h4>
</div>
<p class="hidden-step-bitbucket hidden">
When you copy the HTTPS URL from Bitbucket, it also adds <code>your-user@</code> at the beginning of the URL which is not needed and should be removed. The URL be something like <code>https://bitbucket.org/your-user/your-repository.git</code>
</p>
<p>
<label>
Git Repository (<strong>HTTPS only</strong>)
<input type="text" name="gitsync[repo_url]" placeholder="https://github.com/getgrav/grav.git" value="{{ settings.repository|default('') }}" />
</label>
<span>
GitSync will use the above repository to store the <strong>user/</strong> folder items selected in Step 4.
</span>
</p>
<p class="center">
<a href="#" class="button" data-gitsync-action="test"><i class="fa fa-fw fa-plug"></i> Test Connection</a>
</p>
<p>
Press <strong>Next</strong> to continue.
</p>
</div>
</div>
<div class="step-3">
<h1>Step 3 - Setting up the Webhook</h1>
<div class="panel hidden disabled" data-gitsync-uribase="{{ frontend_url }}">
<p>
A Webhook synchronizes the site when a change happens in the repository, and is set up at the service provider. This is a special URL to your site, that you add to your repository's settings. We've auto-generated one for you below, but you can change this to whatever you'd like.</p>
<p>
<label>
Webhook
<input type="text" name="gitsync[webhook]" placeholder="/_git-sync" value="{{ settings.webhook|default('/_git-sync') }}" />
</label>
</p>
<p>
You can also use a secret token if your service provider supports it:</p>
<p class="webhook-secret-wrapper">
<label for="gitsync-webhook-secret-input">
<input id="gitsync-webhook-secret-input" type="checkbox" name="gitsync[webhook_enabled]" {{ settings.webhook_enabled ? 'checked' }}>
Use Webhook Secret
</label>
<label class="{{ not settings.webhook_enabled ? 'hidden' }}">
Webhook Secret
<input type="text" name="gitsync[webhook_secret]" placeholder="Your Web Hook Secret" value="{{ settings.webhook_secret }}" />
</label>
</p>
<p>
Follow the steps below to add the Webhook to your service provider.</p>
<div class="hidden-step-github wizard-padding hidden">
<h4>GitHub</h4>
<ol>
<li>Head to the repository on GitHub and click on <code>Settings</code></li>
<li>Click on <code>Webhooks</code> in the left sidebar</li>
<li>Click on the <code>Add webhook</code>-button on the right</li>
<li>
Fill out the form as follows
<ul>
<li><strong>Payload URL</strong>: <code>{{ frontend_url }}<span class="gitsync-webhook">{{ settings.webhook|default('/_git-sync') }}</span></code></li>
<li><strong>Content type</strong>: <code>application/json</code></li>
<li><strong>Secret</strong>: <span class="gitsync-webhook-secret">{{ (settings.webhook_enabled and settings.webhook_secret ? '<code>' ~ settings.webhook_secret ~ '</code>' : '<em>leave empty</em>')|raw }}</span></li>
<li><strong>Which events would you like to trigger this webhook?</strong> <code>Just the push event.</code></li>
<li><strong>Active</strong>: <i class="fa fa-check-square-o"></i></li>
</ul>
</li>
<li>Click the <code>Add webhook</code>-button</li>
</ol>
</div>
<div class="hidden-step-bitbucket wizard-padding hidden">
<h4>Bitbucket</h4>
<ol>
<li>Head to the repository on Bitbucket and click on <code><i class="fa fa-cog"></i> Settings</code> in the sidebar (if the sidebar is collapsed, this is represented by just a <i class="fa fa-cog"></i>)</li>
<li>Click on <code>Webhooks</code> on the left sidebar of the page that just loaded</li>
<li>Click on the <code>Add webhook</code>-button at the top</li>
<li>
Fill out the form as follows
<ul>
<li><strong>Title</strong>: <em>any title you like, eg. GitSync</em></li>
<li><strong>URL</strong>: <code>{{ frontend_url }}<span class="gitsync-webhook">{{ settings.webhook|default('/_git-sync') }}</span></code></li>
<li><strong>Active</strong>: <i class="fa fa-check-square-o"></i></li>
<li><strong>Repository push</strong>: <i class="fa fa-dot-circle-o"></i></li>
</ul>
</li>
<li>Click the <code>Save</code>-button</li>
</ol>
</div>
<div class="hidden-step-gitlab wizard-padding hidden">
<h4>GitLab</h4>
<ol>
<li>Head to the repository on GitLab and click on <code><i class="fa fa-cog"></i> <strong>Settings</strong></code> dropdown-button from the page sidebar</li>
<li>Select <code>Integrations</code> (or <code>Webhooks</code> if < v8.16) from the list</li>
<li>
Fill out the form as follows
<ul>
<li><strong>URL</strong>: <code>{{ frontend_url }}<span class="gitsync-webhook">{{ settings.webhook|default('/_git-sync') }}</span></code></li>
<li><strong>Secret Token</strong>: <span class="gitsync-webhook-secret">{{ (settings.webhook_enabled and settings.webhook_secret ? '<code>' ~ settings.webhook_secret ~ '</code>' : '<em>leave empty</em>')|raw }}</span></li>
<li><strong>Push events</strong>: <i class="fa fa-check-square-o"></i></li>
</ul>
</li>
<li>Click the <code>Add Webhook</code>-button</li>
</ol>
</div>
<div class="hidden-step-allothers wizard-padding hidden">
<h4>Other git systems (Generic)</h4>
<ol>
<li>
Locate the area where you add the webhook in the repository. This will likely be in the settings for the repository you are adding.
</li>
<li>
Please note this is an advanced option and dependent on the choice of git solution in use.
</li>
<li>
Compile the form as follows
<ul>
<li><strong>URL</strong>: <code>{{ frontend_url }}<span class="gitsync-webhook">{{ settings.webhook|default('/_git-sync') }}</span></code></li>
<li><strong>Secret Token</strong>: <span class="gitsync-webhook-secret">{{ (settings.webhook_enabled and settings.webhook_secret ? '<code>' ~ settings.webhook_secret ~ '</code>' : '<em>leave empty</em>')|raw }}</span></li>
<li><strong>Push events</strong>: <i class="fa fa-check-square-o"></i></li>
</ul>
</li>
<li>Click the <code>Add Webhook</code> button</li>
</ol>
</div>
<p>
Press <strong>Next</strong> to continue.
</p>
</div>
</div>
<div class="step-4">
<h1>Step 4 - Choose What to Synchronize</h1>
<div class="panel hidden disabled">
<p>
Select which <strong>user/</strong> folders you would like to independently synchronize (default: Pages).
</p>
<div class="columns wizard-padding">
<div class="column">
<label>
<input type="checkbox" value="pages" name="gitsync[folders]" {{ not settings.folders or 'pages' in settings.folders ? 'checked' : '' }} />
<span>Pages</span>
<i class="info-desc fa fa-info-circle"></i>
</label>
<label>
<input type="checkbox" value="themes" name="gitsync[folders]" {{ 'themes' in settings.folders ? 'checked' : '' }} />
<span>Themes</span>
<i class="info-desc fa fa-info-circle"></i>
</label>
<label>
<input type="checkbox" value="plugins" name="gitsync[folders]" {{ 'plugins' in settings.folders ? 'checked' : '' }} />
<span>Plugins</span>
<i class="info-desc fa fa-info-circle"></i>
</label>
<label>
<input type="checkbox" value="config" name="gitsync[folders]" {{ 'config' in settings.folders ? 'checked' : '' }} />
<span>Config</span>
<span class="hint--right" data-hint="This folder might contain sensitive data"><i class="fa fa-warning"></i></span>
<i class="info-desc fa fa-info-circle"></i>
</label>
<label>
<input type="checkbox" value="data" name="gitsync[folders]"{{ 'data' in settings.folders ? 'checked' : '' }} />
<span>Data</span>
<span class="hint--right" data-hint="This folder might contain sensitive data"><i class="fa fa-warning"></i></span>
<i class="info-desc fa fa-info-circle"></i>
</label>
{% if 'accounts' in settings.folders %}
<label>
<input type="checkbox" value="accounts" name="gitsync[folders]" {{ 'accounts' in settings.folders ? 'checked' : '' }} />
<span>Accounts</span>
<span class="hint--right" data-hint="This folder contains sensitive data"><i class="fa fa-warning"></i></span>
<i class="info-desc fa fa-info-circle"></i>
</label>
{% endif %}
{% for folder in settings.folders if '/' in folder %}
{% if loop.index0 == 0%}<hr />{% endif %}
<label>
<input type="checkbox" value="{{ folder }}" name="gitsync[folders]" checked />
<span>{{ folder }}</span>
<i class="info-desc fa fa-info-circle"></i>
</label>
{% endfor %}
</div>
<div class="column">
<div class="description">
<p class="description-accounts hidden">
This folder contains all the user accounts of the site.
</p>
<p class="description-config hidden">
This folder contains all the site configurations.
</p>
<p class="description-data hidden">
This folder contains all the stored data from plugins.
</p>
<p class="description-pages hidden">
This folders contains all the pages of your site.
</p>
<p class="description-plugins hidden">
This folders contains all the plugins installed in your site.
</p>
<p class="description-themes hidden">
This folders contains all the themes installed in your site.
</p>
<p class="info warning description-accounts description-config description-data hidden">Careful! This folder can contain sensitive data and synchronizing it will potentially expose the data publicly. Private repository recommended.</p>
<p class="info alert description-themes hidden">Note that changes happening in this folder can't be detected automatically by GitSync, therefore a manual synchronization will be required.</p>
{% for folder in settings.folders if '/' in folder %}
<p class="description-{{ folder|replace({'/': '-'}) }} hidden">
This folders was manually added either by you in the settings or by your Skeleton.
</p>
{% endfor %}
</div>
</div>
</div>
<p>
Press <strong>Save</strong> to complete the setup.
</p>
</div>
</div>
<div class="button-bar">
<a class="button secondary float-left" data-remodal-action="cancel" href="#">Cancel</a>
<a class="button hidden" data-gitsync-action="previous" href="#"><i class="fa fa-fw fa-chevron-left"></i> Previous</a>
<a class="button" data-gitsync-action="next" href="#">Next <i class="fa fa-fw fa-chevron-right"></i></a>
<a class="button hidden" data-gitsync-action="save" href="#"><i class="fa fa-fw fa-check"></i> Save</a>
</div>
{% else %}
<div class="step-0">
<div class="panel">
<p>
The <strong>GitSync</strong> plugin requires the <i class="fa fa-fw fa-git"></i> (Git) binary to be installed and accessible in order to work.</p>
<p>
If <i class="fa fa-fw fa-git"></i> (Git) is missing from your hosting provider, you should open a ticket with them and request it to be installed.
</p>
<p>You can also specify a custom path in the Advanced section down below at the <strong>Git Binary Path</strong> field. If you are using <strong>Windows</strong> (VM, Azure or a local setup), you might have to change the location accordingly (quotes do matter), for example: <br /> <code>"D:\Program Files\Git\bin\git.exe"</code>.</p>
</div>
</div>
<div class="button-bar">
<a class="button secondary float-left" data-remodal-action="cancel" href="#">Close</a>
</div>
{% endif %}
</div>
<?php
// autoload.php @generated by Composer
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInitc058e1b5c686df0909ec1cc39bb5d854::getLoader();
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Autoload;
/**
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
*
* $loader = new \Composer\Autoload\ClassLoader();
*
* // register classes with namespaces
* $loader->add('Symfony\Component', __DIR__.'/component');
* $loader->add('Symfony', __DIR__.'/framework');
*
* // activate the autoloader
* $loader->register();
*
* // to enable searching the include path (eg. for PEAR packages)
* $loader->setUseIncludePath(true);
*
* In this example, if you try to use a class in the Symfony\Component
* namespace or one of its children (Symfony\Component\Console for instance),
* the autoloader will first look for the class under the component/
* directory, and it will then fallback to the framework/ directory if not
* found before giving up.
*
* This class is loosely based on the Symfony UniversalClassLoader.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @see http://www.php-fig.org/psr/psr-0/
* @see http://www.php-fig.org/psr/psr-4/
*/
class ClassLoader
{
// PSR-4
private $prefixLengthsPsr4 = array();
private $prefixDirsPsr4 = array();
private $fallbackDirsPsr4 = array();
// PSR-0
private $prefixesPsr0 = array();
private $fallbackDirsPsr0 = array();
private $useIncludePath = false;
private $classMap = array();
private $classMapAuthoritative = false;
private $missingClasses = array();
private $apcuPrefix;
public function getPrefixes()
{
if (!empty($this->prefixesPsr0)) {
return call_user_func_array('array_merge', $this->prefixesPsr0);
}
return array();
}
public function getPrefixesPsr4()
{
return $this->prefixDirsPsr4;
}
public function getFallbackDirs()
{
return $this->fallbackDirsPsr0;
}
public function getFallbackDirsPsr4()
{
return $this->fallbackDirsPsr4;
}
public function getClassMap()
{
return $this->classMap;
}
/**
* @param array $classMap Class to filename map
*/
public function addClassMap(array $classMap)
{
if ($this->classMap) {
$this->classMap = array_merge($this->classMap, $classMap);
} else {
$this->classMap = $classMap;
}
}
/**
* Registers a set of PSR-0 directories for a given prefix, either
* appending or prepending to the ones previously set for this prefix.
*
* @param string $prefix The prefix
* @param array|string $paths The PSR-0 root directories
* @param bool $prepend Whether to prepend the directories
*/
public function add($prefix, $paths, $prepend = false)
{
if (!$prefix) {
if ($prepend) {
$this->fallbackDirsPsr0 = array_merge(
(array) $paths,
$this->fallbackDirsPsr0
);
} else {
$this->fallbackDirsPsr0 = array_merge(
$this->fallbackDirsPsr0,
(array) $paths
);
}
return;
}
$first = $prefix[0];
if (!isset($this->prefixesPsr0[$first][$prefix])) {
$this->prefixesPsr0[$first][$prefix] = (array) $paths;
return;
}
if ($prepend) {
$this->prefixesPsr0[$first][$prefix] = array_merge(
(array) $paths,
$this->prefixesPsr0[$first][$prefix]
);
} else {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$this->prefixesPsr0[$first][$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-4 directories for a given namespace, either
* appending or prepending to the ones previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param array|string $paths The PSR-4 base directories
* @param bool $prepend Whether to prepend the directories
*
* @throws \InvalidArgumentException
*/
public function addPsr4($prefix, $paths, $prepend = false)
{
if (!$prefix) {
// Register directories for the root namespace.
if ($prepend) {
$this->fallbackDirsPsr4 = array_merge(
(array) $paths,
$this->fallbackDirsPsr4
);
} else {
$this->fallbackDirsPsr4 = array_merge(
$this->fallbackDirsPsr4,
(array) $paths
);
}
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
// Register directories for a new namespace.
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
} elseif ($prepend) {
// Prepend directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
(array) $paths,
$this->prefixDirsPsr4[$prefix]
);
} else {
// Append directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$this->prefixDirsPsr4[$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-0 directories for a given prefix,
* replacing any others previously set for this prefix.
*
* @param string $prefix The prefix
* @param array|string $paths The PSR-0 base directories
*/
public function set($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr0 = (array) $paths;
} else {
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
}
}
/**
* Registers a set of PSR-4 directories for a given namespace,
* replacing any others previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param array|string $paths The PSR-4 base directories
*
* @throws \InvalidArgumentException
*/
public function setPsr4($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr4 = (array) $paths;
} else {
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
}
}
/**
* Turns on searching the include path for class files.
*
* @param bool $useIncludePath
*/
public function setUseIncludePath($useIncludePath)
{
$this->useIncludePath = $useIncludePath;
}
/**
* Can be used to check if the autoloader uses the include path to check
* for classes.
*
* @return bool
*/
public function getUseIncludePath()
{
return $this->useIncludePath;
}
/**
* Turns off searching the prefix and fallback directories for classes
* that have not been registered with the class map.
*
* @param bool $classMapAuthoritative
*/
public function setClassMapAuthoritative($classMapAuthoritative)
{
$this->classMapAuthoritative = $classMapAuthoritative;
}
/**
* Should class lookup fail if not found in the current class map?
*
* @return bool
*/
public function isClassMapAuthoritative()
{
return $this->classMapAuthoritative;
}
/**
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
*
* @param string|null $apcuPrefix
*/
public function setApcuPrefix($apcuPrefix)
{
$this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null;
}
/**
* The APCu prefix in use, or null if APCu caching is not enabled.
*
* @return string|null
*/
public function getApcuPrefix()
{
return $this->apcuPrefix;
}
/**
* Registers this instance as an autoloader.
*
* @param bool $prepend Whether to prepend the autoloader or not
*/
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
}
/**
* Unregisters this instance as an autoloader.
*/
public function unregister()
{
spl_autoload_unregister(array($this, 'loadClass'));
}
/**
* Loads the given class or interface.
*
* @param string $class The name of the class
* @return bool|null True if loaded, null otherwise
*/
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
includeFile($file);
return true;
}
}
/**
* Finds the path to the file where the class is defined.
*
* @param string $class The name of the class
*
* @return string|false The path if found, false otherwise
*/
public function findFile($class)
{
// class map lookup
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
return false;
}
if (null !== $this->apcuPrefix) {
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
if ($hit) {
return $file;
}
}
$file = $this->findFileWithExtension($class, '.php');
// Search for Hack files if we are running on HHVM
if (false === $file && defined('HHVM_VERSION')) {
$file = $this->findFileWithExtension($class, '.hh');
}
if (null !== $this->apcuPrefix) {
apcu_add($this->apcuPrefix.$class, $file);
}
if (false === $file) {
// Remember that this class does not exist.
$this->missingClasses[$class] = true;
}
return $file;
}
private function findFileWithExtension($class, $ext)
{
// PSR-4 lookup
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
$first = $class[0];
if (isset($this->prefixLengthsPsr4[$first])) {
foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) {
if (0 === strpos($class, $prefix)) {
foreach ($this->prefixDirsPsr4[$prefix] as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) {
return $file;
}
}
}
}
}
// PSR-4 fallback dirs
foreach ($this->fallbackDirsPsr4 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
return $file;
}
}
// PSR-0 lookup
if (false !== $pos = strrpos($class, '\\')) {
// namespaced class name
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
} else {
// PEAR-like class name
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
}
if (isset($this->prefixesPsr0[$first])) {
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
if (0 === strpos($class, $prefix)) {
foreach ($dirs as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
}
}
}
// PSR-0 fallback dirs
foreach ($this->fallbackDirsPsr0 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
// PSR-0 include paths.
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
return $file;
}
return false;
}
}
/**
* Scope isolated include.
*
* Prevents access to $this/self from included files.
*/
function includeFile($file)
{
include $file;
}
Copyright (c) 2016 Nils Adermann, Jordi Boggiano
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
<?php
// autoload_classmap.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'Defuse\\Crypto\\Core' => $vendorDir . '/defuse/php-encryption/src/Core.php',
'Defuse\\Crypto\\Crypto' => $vendorDir . '/defuse/php-encryption/src/Crypto.php',
'Defuse\\Crypto\\DerivedKeys' => $vendorDir . '/defuse/php-encryption/src/DerivedKeys.php',
'Defuse\\Crypto\\Encoding' => $vendorDir . '/defuse/php-encryption/src/Encoding.php',
'Defuse\\Crypto\\Exception\\BadFormatException' => $vendorDir . '/defuse/php-encryption/src/Exception/BadFormatException.php',
'Defuse\\Crypto\\Exception\\CryptoException' => $vendorDir . '/defuse/php-encryption/src/Exception/CryptoException.php',
'Defuse\\Crypto\\Exception\\EnvironmentIsBrokenException' => $vendorDir . '/defuse/php-encryption/src/Exception/EnvironmentIsBrokenException.php',
'Defuse\\Crypto\\Exception\\IOException' => $vendorDir . '/defuse/php-encryption/src/Exception/IOException.php',
'Defuse\\Crypto\\Exception\\WrongKeyOrModifiedCiphertextException' => $vendorDir . '/defuse/php-encryption/src/Exception/WrongKeyOrModifiedCiphertextException.php',
'Defuse\\Crypto\\File' => $vendorDir . '/defuse/php-encryption/src/File.php',
'Defuse\\Crypto\\Key' => $vendorDir . '/defuse/php-encryption/src/Key.php',
'Defuse\\Crypto\\KeyOrPassword' => $vendorDir . '/defuse/php-encryption/src/KeyOrPassword.php',
'Defuse\\Crypto\\KeyProtectedByPassword' => $vendorDir . '/defuse/php-encryption/src/KeyProtectedByPassword.php',
'Defuse\\Crypto\\RuntimeTests' => $vendorDir . '/defuse/php-encryption/src/RuntimeTests.php',
'SebastianBergmann\\Git\\Exception' => $vendorDir . '/sebastian/git/src/Exception/Exception.php',
'SebastianBergmann\\Git\\Git' => $vendorDir . '/sebastian/git/src/Git.php',
'SebastianBergmann\\Git\\RuntimeException' => $vendorDir . '/sebastian/git/src/Exception/RuntimeException.php',
);
<?php
// autoload_files.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'5255c38a0faeba867671b61dfda6d864' => $vendorDir . '/paragonie/random_compat/lib/random.php',
);
<?php
// autoload_namespaces.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
);
<?php
// autoload_psr4.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'Grav\\Plugin\\GitSync\\' => array($baseDir . '/classes'),
);
<?php
// autoload_real.php @generated by Composer
class ComposerAutoloaderInitc058e1b5c686df0909ec1cc39bb5d854
{
private static $loader;
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}
spl_autoload_register(array('ComposerAutoloaderInitc058e1b5c686df0909ec1cc39bb5d854', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
spl_autoload_unregister(array('ComposerAutoloaderInitc058e1b5c686df0909ec1cc39bb5d854', 'loadClassLoader'));
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
if ($useStaticLoader) {
require_once __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInitc058e1b5c686df0909ec1cc39bb5d854::getInitializer($loader));
} else {
$map = require __DIR__ . '/autoload_namespaces.php';
foreach ($map as $namespace => $path) {
$loader->set($namespace, $path);
}
$map = require __DIR__ . '/autoload_psr4.php';
foreach ($map as $namespace => $path) {
$loader->setPsr4($namespace, $path);
}
$classMap = require __DIR__ . '/autoload_classmap.php';
if ($classMap) {
$loader->addClassMap($classMap);
}
}
$loader->register(true);
if ($useStaticLoader) {
$includeFiles = Composer\Autoload\ComposerStaticInitc058e1b5c686df0909ec1cc39bb5d854::$files;
} else {
$includeFiles = require __DIR__ . '/autoload_files.php';
}
foreach ($includeFiles as $fileIdentifier => $file) {
composerRequirec058e1b5c686df0909ec1cc39bb5d854($fileIdentifier, $file);
}
return $loader;
}
}
function composerRequirec058e1b5c686df0909ec1cc39bb5d854($fileIdentifier, $file)
{
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
require $file;
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
}
}
<?php
// autoload_static.php @generated by Composer
namespace Composer\Autoload;
class ComposerStaticInitc058e1b5c686df0909ec1cc39bb5d854
{
public static $files = array (
'5255c38a0faeba867671b61dfda6d864' => __DIR__ . '/..' . '/paragonie/random_compat/lib/random.php',
);
public static $prefixLengthsPsr4 = array (
'G' =>
array (
'Grav\\Plugin\\GitSync\\' => 20,
),
);
public static $prefixDirsPsr4 = array (
'Grav\\Plugin\\GitSync\\' =>
array (
0 => __DIR__ . '/../..' . '/classes',
),
);
public static $classMap = array (
'Defuse\\Crypto\\Core' => __DIR__ . '/..' . '/defuse/php-encryption/src/Core.php',
'Defuse\\Crypto\\Crypto' => __DIR__ . '/..' . '/defuse/php-encryption/src/Crypto.php',
'Defuse\\Crypto\\DerivedKeys' => __DIR__ . '/..' . '/defuse/php-encryption/src/DerivedKeys.php',
'Defuse\\Crypto\\Encoding' => __DIR__ . '/..' . '/defuse/php-encryption/src/Encoding.php',
'Defuse\\Crypto\\Exception\\BadFormatException' => __DIR__ . '/..' . '/defuse/php-encryption/src/Exception/BadFormatException.php',
'Defuse\\Crypto\\Exception\\CryptoException' => __DIR__ . '/..' . '/defuse/php-encryption/src/Exception/CryptoException.php',
'Defuse\\Crypto\\Exception\\EnvironmentIsBrokenException' => __DIR__ . '/..' . '/defuse/php-encryption/src/Exception/EnvironmentIsBrokenException.php',
'Defuse\\Crypto\\Exception\\IOException' => __DIR__ . '/..' . '/defuse/php-encryption/src/Exception/IOException.php',
'Defuse\\Crypto\\Exception\\WrongKeyOrModifiedCiphertextException' => __DIR__ . '/..' . '/defuse/php-encryption/src/Exception/WrongKeyOrModifiedCiphertextException.php',
'Defuse\\Crypto\\File' => __DIR__ . '/..' . '/defuse/php-encryption/src/File.php',
'Defuse\\Crypto\\Key' => __DIR__ . '/..' . '/defuse/php-encryption/src/Key.php',
'Defuse\\Crypto\\KeyOrPassword' => __DIR__ . '/..' . '/defuse/php-encryption/src/KeyOrPassword.php',
'Defuse\\Crypto\\KeyProtectedByPassword' => __DIR__ . '/..' . '/defuse/php-encryption/src/KeyProtectedByPassword.php',
'Defuse\\Crypto\\RuntimeTests' => __DIR__ . '/..' . '/defuse/php-encryption/src/RuntimeTests.php',
'SebastianBergmann\\Git\\Exception' => __DIR__ . '/..' . '/sebastian/git/src/Exception/Exception.php',
'SebastianBergmann\\Git\\Git' => __DIR__ . '/..' . '/sebastian/git/src/Git.php',
'SebastianBergmann\\Git\\RuntimeException' => __DIR__ . '/..' . '/sebastian/git/src/Exception/RuntimeException.php',
);
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInitc058e1b5c686df0909ec1cc39bb5d854::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInitc058e1b5c686df0909ec1cc39bb5d854::$prefixDirsPsr4;
$loader->classMap = ComposerStaticInitc058e1b5c686df0909ec1cc39bb5d854::$classMap;
}, null, ClassLoader::class);
}
}
[
{
"name": "sebastian/git",
"version": "2.1.3",
"version_normalized": "2.1.3.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/git.git",
"reference": "5100bc50cd9e70f424c643618e142214225024f3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/git/zipball/5100bc50cd9e70f424c643618e142214225024f3",
"reference": "5100bc50cd9e70f424c643618e142214225024f3",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"time": "2016-06-15T09:30:19+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.1-dev"
}
},
"installation-source": "dist",
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
}
],
"description": "Simple wrapper for Git",
"homepage": "http://www.github.com/sebastianbergmann/git",
"keywords": [
"git"
]
},
{
"name": "paragonie/random_compat",
"version": "v2.0.4",
"version_normalized": "2.0.4.0",
"source": {
"type": "git",
"url": "https://github.com/paragonie/random_compat.git",
"reference": "a9b97968bcde1c4de2a5ec6cbd06a0f6c919b46e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/paragonie/random_compat/zipball/a9b97968bcde1c4de2a5ec6cbd06a0f6c919b46e",
"reference": "a9b97968bcde1c4de2a5ec6cbd06a0f6c919b46e",
"shasum": ""
},
"require": {
"php": ">=5.2.0"
},
"require-dev": {
"phpunit/phpunit": "4.*|5.*"
},
"suggest": {
"ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
},
"time": "2016-11-07T23:38:38+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"files": [
"lib/random.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Paragon Initiative Enterprises",
"email": "security@paragonie.com",
"homepage": "https://paragonie.com"
}
],
"description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
"keywords": [
"csprng",
"pseudorandom",
"random"
]
},
{
"name": "defuse/php-encryption",
"version": "2.0.3",
"version_normalized": "2.0.3.0",
"source": {
"type": "git",
"url": "https://github.com/defuse/php-encryption.git",
"reference": "2c6fea3d9a4eaaa8cef86b2a89f3660818117b33"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/defuse/php-encryption/zipball/2c6fea3d9a4eaaa8cef86b2a89f3660818117b33",
"reference": "2c6fea3d9a4eaaa8cef86b2a89f3660818117b33",
"shasum": ""
},
"require": {
"ext-openssl": "*",
"paragonie/random_compat": "~2.0",
"php": ">=5.4.0"
},
"require-dev": {
"nikic/php-parser": "^2.0"
},
"time": "2016-10-10T15:20:26+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"classmap": [
"src"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Taylor Hornby",
"email": "taylor@defuse.ca",
"homepage": "https://defuse.ca/"
},
{
"name": "Scott Arciszewski",
"email": "info@paragonie.com",
"homepage": "https://paragonie.com"
}
],
"description": "Secure PHP Encryption Library",
"keywords": [
"aes",
"authenticated encryption",
"cipher",
"crypto",
"cryptography",
"encrypt",
"encryption",
"openssl",
"security",
"symmetric key cryptography"
]
}
]
*~
/test/unit/File/big-generated-file
/composer.lock
/vendor
defuse-crypto.phar
defuse-crypto.phar.sig
composer.phar
box.phar
phpunit.phar
phpunit.phar.asc
test/unit/File/tmp
<?php
$config = Symfony\CS\Config\Config::create()
->level(Symfony\CS\FixerInterface::PSR2_LEVEL)
->fixers([
'blankline_after_open_tag',
'empty_return',
'extra_empty_lines',
'function_typehint_space',
'join_function',
'method_argument_default_value',
'multiline_array_trailing_comma',
'no_blank_lines_after_class_opening',
'no_empty_lines_after_phpdocs',
'phpdoc_indent',
'phpdoc_no_access',
'phpdoc_no_empty_return',
'phpdoc_no_package',
'phpdoc_params',
'phpdoc_scalar',
'phpdoc_separation',
'phpdoc_trim',
'phpdoc_type_to_var',
'phpdoc_types',
'phpdoc_var_without_name',
'remove_leading_slash_use',
'remove_lines_between_uses',
'short_bool_cast',
'single_quote',
'spaces_after_semicolon',
'spaces_before_semicolon',
'spaces_cast',
'standardize_not_equal',
'ternary_spaces',
'trim_array_spaces',
'unneeded_control_parentheses',
'unused_use',
'whitespacy_lines',
'align_double_arrow',
'concat_with_spaces',
'logical_not_operators_with_successor_space',
'multiline_spaces_before_semicolon',
'newline_after_open_tag',
'ordered_use',
'php_unit_construct',
'phpdoc_order',
'short_array_syntax',
]);
if (null === $input->getArgument('path')) {
$config
->finder(
Symfony\CS\Finder\DefaultFinder::create()
->in('src')
->in('test')
->exclude('vendor')
);
}
return $config;
The MIT License (MIT)
Copyright (c) 2016 Taylor Hornby <https://defuse.ca> and Paragon Initiative
Enterprises <https://paragonie.com>.
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
php-encryption
===============
[![Build Status](https://travis-ci.org/defuse/php-encryption.svg?branch=master)](https://travis-ci.org/defuse/php-encryption)
This is a library for encrypting data with a key or password in PHP. **It
requires PHP 5.4 or newer.** The current version is v2.0.0, which is expected to
remain stable and supported by its authors with security and bugfixes until at
least January 1st, 2019.
The library is a joint effort between [Taylor Hornby](https://defuse.ca/) and
[Scott Arciszewski](https://paragonie.com/blog/author/scott-arcizewski) as well
as numerous open-source contributors.
What separates this library from other PHP encryption libraries is, firstly,
that it is secure. The authors used to encounter insecure PHP encryption code on
a daily basis, so they created this library to bring more security to the
ecosystem. Secondly, this library is "difficult to misuse." Like
[libsodium](https://github.com/jedisct1/libsodium), its API is designed to be
easy to use in a secure way and hard to use in an insecure way.
Dependencies
------------
This library requres no special dependencies except for PHP 5.4 or newer with
the OpenSSL extensions enabled (this is the default). It uses
[random\_compat](https://github.com/paragonie/random_compat), which is bundled
in with this library so that your users will not need to follow any special
installation steps.
Getting Started
----------------
Start with the [**Tutorial**](docs/Tutorial.md). You can find instructions for
obtaining this library's code securely in the [Installing and
Verifying](docs/InstallingAndVerifying.md) documentation.
After you've read the tutorial and got the code, refer to the formal
documentation for each of the classes this library provides:
- [Crypto](docs/classes/Crypto.md)
- [File](docs/classes/File.md)
- [Key](docs/classes/Key.md)
- [KeyProtectedByPassword](docs/classes/KeyProtectedByPassword.md)
If you encounter difficulties, see the [FAQ](docs/FAQ.md) answers. The fixes to
the most commonly-reported problems are explained there.
If you're a cryptographer and want to understand the nitty-gritty details of how
this library works, look at the [Cryptography Details](docs/CryptoDetails.md)
documentation.
If you're interested in contributing to this library, see the [Internal
Developer Documentation](docs/InternalDeveloperDocs.md).
Examples
---------
If the documentation is not enough for you to understand how to use this
library, then you can look at an example project that uses this library:
- [encutil](https://github.com/defuse/encutil)
Security Audit Status
---------------------
This code has not been subjected to a formal, paid, security audit. However, it
has received lots of review from members of the PHP security community, and the
authors are experienced with cryptography. In all likelihood, you are safer
using this library than almost any other encryption library for PHP.
If you use this library as a part of your business and would like to help fund
a formal audit, please [contact Taylor Hornby](https://defuse.ca/contact.htm).
Public Keys
------------
The GnuPG public key used to sign releases is available in
[dist/signingkey.asc](https://github.com/defuse/php-encryption/raw/master/dist/signingkey.asc). Its fingerprint is:
```
2FA6 1D8D 99B9 2658 6BAC 3D53 385E E055 A129 1538
```
You can verify it against the Taylor Hornby's [contact
page](https://defuse.ca/contact.htm) and
[twitter](https://twitter.com/DefuseSec/status/723741424253059074).
{
"name": "defuse/php-encryption",
"description": "Secure PHP Encryption Library",
"license": "MIT",
"version": "2.0.3",
"keywords": ["security", "encryption", "AES", "openssl", "cipher", "cryptography", "symmetric key cryptography", "crypto", "encrypt", "authenticated encryption"],
"authors": [
{
"name": "Taylor Hornby",
"email": "taylor@defuse.ca",
"homepage": "https://defuse.ca/"
},
{
"name": "Scott Arciszewski",
"email": "info@paragonie.com",
"homepage": "https://paragonie.com"
}
],
"autoload": {
"classmap": ["src"]
},
"require": {
"paragonie/random_compat": "~2.0",
"ext-openssl": "*",
"php": ">=5.4.0"
},
"require-dev": {
"nikic/php-parser": "^2.0"
}
}
# This builds defuse-crypto.phar. To run this Makefile, `box` and `composer`
# must be installed and in your $PATH. Run it from inside the dist/ directory.
box := $(shell which box)
composer := "composer"
.PHONY: all
all: build-phar
.PHONY: sign-phar
sign-phar:
gpg -u 7B4B2D98 --armor --output defuse-crypto.phar.sig --detach-sig defuse-crypto.phar
# ensure we run in clean tree. export git tree and run there.
.PHONY: build-phar
build-phar:
@echo "Creating .phar from revision $(shell git rev-parse HEAD)."
rm -rf worktree
install -d worktree
(cd $(CURDIR)/..; git archive HEAD) | tar -x -C worktree
$(MAKE) -f $(CURDIR)/Makefile -C worktree defuse-crypto.phar
mv worktree/*.phar .
rm -rf worktree
.PHONY: clean
clean:
rm -vf defuse-crypto.phar defuse-crypto.phar.sig
# Inside workdir/:
defuse-crypto.phar: dist/box.json composer.lock
cp dist/box.json .
php -d phar.readonly=0 $(box) build -c box.json -v
composer.lock:
$(composer) install --no-dev
{
"chmod": "0755",
"finder": [
{
"in": "src",
"name": "*.php"
},
{
"in": "vendor/composer",
"name": "*.php"
},
{
"in": "vendor/paragonie",
"name": "*.php",
"exclude": "other"
}
],
"compactors": [
"Herrera\\Box\\Compactor\\Php"
],
"main": "vendor/autoload.php",
"output": "defuse-crypto.phar",
"shebang": false,
"stub": true
}
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v2
mQINBFarvO4BEACdQBaLt6SUBx1cB5liUu1qo+YwVLh9bxTregQtmEREMdTVqXYt
e5b79uL4pQp2GlKHcEyRURS+6rIIruM0oh9ZYGTJYPAkCDzJxaU2awZeFbfBvpCm
iF66/O4ZJI4mlT8dFKmxBJxDhfeOR2UmmhDiEsJK9FxBKUzvo/dWrX2pBzf8Y122
iIaVraSo+tymaf7vriaIf/NnSKhDw8dtQYGM4NMrxxsPTfbCF8XiboDgTkoD2A+6
NpOJYxA4Veedsf2TP9YLhljH4m5yYlfjjqBzbBCPWuE6Hhy5Xze9mncgDr7LKenm
Ctf2NxW6y4O3RCI+9eLlBfFWB+KuGV87/b5daetX7NNLbjID8z2rqEa+d6wu5xA5
Ta2uiVkAOEovr3XnkayZ9zth+Za7w7Ai0ln0N/LVMkM+Gu4z/pJv6HjmTGDM2wJb
fs+UOM0TFdg+N81Do67XT2M4o0MeHyUqsIiWpYa2Qf1PNmqTQNJnRk8uZZ9I96Nh
eCgNuCbhsQiYBMicox+xmuWAlGAfA06y0kCtmqGhiBGArdJlWvUqPqGiZ4Hln9z0
FJmXDOh0Q/FIPxcDg8mKRRbx+lOP389PLsPpj4b2B/4PEgfpCCOwuKpLotATZxC1
9JwFk0Y/cvUUkq4a+nAJBNtBbtRJkEesuuUnRq6XexmnE3uUucDcV0XCSwARAQAB
tCBUYXlsb3IgSG9ybmJ5IDx0YXlsb3JAZGVmdXNlLmNhPokCPQQTAQgAJwUCVqu8
7gIbAwUJB4TOAAULCQgHAgYVCAkKCwIEFgIDAQIeAQIXgAAKCRA4XuBVoSkVOJbx
EACG0F9blPMAsK05EWyNnnS4mw25zPfbaqqEvYbquAeM0nBpRDm7sRn2MNR0AL4g
7XrtxE/4qYkdEl6f2wFCQeRhZgxE3w22llredzLme11Hic8hn4i7ysdIw0r9dMMR
kjgR5UcWpv8iU847czyK09PkKW2EaLRbX2qbA7rNU5qCFKeD4Sy4bBTteISeVsHo
Vr9o1/bRrMhgZ++ts8hYf0LmujIf5cxp+qcdKwCXSnS/gmmXaKRMCPv/Wdlq9bt6
LX9jZB9lXBdGxcBJeFOsTG+QRDiVjg3d6i3o3TAKV87ALBI4v2ADEYtN8lviHo3/
SovVKv6zrUsZHxhoGiLTiksNrYsKMmiMxdJCoOazmtUPnZ4UOtT8NdqMPoKvdoRz
f4rhZ+f5jSVD9OuX2PDmfyq21Rdiym7Vcgr+uTIFJ3ShRHjWb/ytCwoB2FeGY6+G
AKY58bTQvUIqEJvSov/+TAqZ4BfOuSdTLcHglV1OdUu2SFZvU2gmyVp0l5elGv5t
FyUlBJUkQT9MtvsdLOR7vQi8QapV+9LWpqwvaj9hyEJz848DQ2sdYTphUytFHv7H
k58DAtVhTrVjHyeefjiYtMl6vSAgTjy5LWAUpo5TfhdGrAi0Tdd/GD7amHoWoDy8
EKXKq2xPLo3JOdkWYQUi5NErzEskfsSzpCOgyDJmGetWK7kCDQRWq7zuARAAu7/i
cm8cjgLhHEX/bgfwOT2hLOLSjjve0O8YFSuJO9XqIHXqmfVOrqWtfG0Mh4bwlfqc
MAvBfF5NSSPfAE4ftBAQ1e5jEv8hJeqICpq3IHTFX4etBs49NfNkyveQl/amVTu1
+/O5J4CuIcsEf3y0Xuu38n7EB3SfMQCWLcOR1NyZoX3bI+CGRpOVVoFse3ljSWL4
LhLVl0WiEMXULsussEoN+c6x9KCyAi/jFOrxrTrFC//sZesKj6KucoqKGfwMWrrv
IeRT9Ga8Wn5MJnQu0aWg+zVVYqTedXZLNLODgQIInFnXO0seBXy15yDok1y5bkx2
sinKg4+mueYaGUpoUti0hM3J3yaC34i6Cwa8MQoLNw1JIS/oNtKjpMxyV10w8aoc
PHRK3n7UYp10mJHx7aM+lldSKvVS1NTQmI4vloNLwMp324H5ANDFAlRUz7mysVnu
DEEvigPSPxs5ZYENu/i7pCQC5qHfhrlBrQwTjhegr0pQPcumy2fO5SGC9l/5B7ev
bqQSZmDeWWoTvh2w2wl5/RWAsgZKx6rDtkCqYx7sSBY17uorrxP24LP4zhq7NxRV
nfdsLogbCFNVQ66u7qvq5zFccdFtg9h1HQWdS7wbnKSBGZoo5gl6js7GGtxfGbb0
oQ9kp6eciF4U92r6POhVgbRe4CfPo50nqgZBddkAEQEAAYkCJQQYAQgADwUCVqu8
7gIbDAUJB4TOAAAKCRA4XuBVoSkVOFJ8D/9J8IJ4XWUU3FYIaHJ3XeSoxDmTi7d5
WmNdf1lmwz82MQjG4uw17oCbvQzmj4/a/CM1Ly4v0WwBhUf9aiNErD0ByHASFnuc
tlQBLVJdk0vRyD0fZakGg64qCA76hiySjMhlGHkQFyP2mDORc2GNu/OqFGm79pXT
ZUplXxd431E603/agM5xJrweutMMpP1nBFTSEMJvbMNzDVN8I1A1CH4zVmAVxOUk
sQ5L5rXW+KeXWyiMF24+l2CMnkQ2CxfHpkcpfPJs1Cbt+TIBSSofIqK8QJXrb/2f
Zpl/ftqW7Xe86rJFrB/Y/77LDWx10rqWEvfCqrBxrMj7ONAQfbKQF/IjAwDN17Wf
1K74rqKnRu+KHCyNM89s1iDbQC9kzZfzYt4AEOvAH/ZQDMZffzPSbnfkBerExFpa
93XMuiR66jiBsf9IXIQeydpJD4Ogl2sSUSxFEJxJ/bBSxPxC5w7/BVMA7Am1y8Zo
3hrpqnX2PBzxG7L0FZ6fYkfR3p8JS7vI6nByBf2IDv8W32wn43olPf+u6uobHLvt
ttapOjwPAhPDalRuxs9U6WSg06QJkT/0F8TFUPWpsFmKTl+G4Ty7PHWsjeeNHJCL
7/5RQboFY3k8Jy3/sIofABO6Un9LJivDuu9PxqA0IgvaS6Mja8JdCCk9Nyk4vHB7
IEgAL/CYqrk38w==
=lmD7
-----END PGP PUBLIC KEY BLOCK-----
Cryptography Details
=====================
Here is a high-level description of how this library works. Any discrepancy
between this documentation and the actual implementation will be considered
a security bug.
Let's start with the following definitions:
- HKDF-SHA256(*k*, *n*, *info*, *s*) is the key derivation function specified in
RFC 5869 (using the SHA256 hash function). The parameters are:
- *k*: The initial keying material.
- *n*: The number of output bytes.
- *info*: The info string.
- *s*: The salt.
- AES-256-CTR(*m*, *k*, *iv*) is AES-256 encryption in CTR mode. The parameters
are:
- *m*: An arbitrary-length (possibly zero-length) message.
- *k*: A 32-byte key.
- *iv*: A 16-byte initialization vector (nonce).
- PBKDF2-SHA256(*p*, *s*, *i*, *n*) is the password-based key derivation
function defined in RFC 2898 (using the SHA256 hash function). The parameters
are:
- *p*: The password string.
- *s*: The salt string.
- *i*: The iteration count.
- *n*: The output length in bytes.
- VERSION is the string `"\xDE\xF5\x02\x00"`.
- AUTHINFO is the string `"DefusePHP|V2|KeyForAuthentication"`.
- ENCRINFO is the string `"DefusePHP|V2|KeyForEncryption"`.
To encrypt a message *m* using a 32-byte key *k*, the following steps are taken:
1. Generate a random 32-byte string *salt*.
2. Derive the 32-byte authentication key *akey* = HKDF-SHA256(*k*, 32, AUTHINFO, *salt*).
3. Derive the 32-byte encryption key *ekey* = HKDF-SHA256(*k*, 32, ENCRINFO, *salt*).
4. Generate a random 16-byte initialization vector *iv*.
5. Compute *c* = AES-256-CTR(*m*, *ekey*, *iv*).
6. Combine *ctxt* = VERSION || *salt* || *iv* || *c*.
7. Compute *h* = HMAC-SHA256(*ctxt*, *akey*).
8. Output *ctxt* || *h*.
Decryption is roughly the reverse process (see the code for details, since the
security of the decryption routine is highly implementation-dependent).
For encryption using a password *p*, steps 1-3 above are replaced by:
1. Generate a random 32-byte string *salt*.
2. Compute *k* = PBKDF2-SHA256(SHA256(*p*), *salt*, 100000, 32).
3. Derive the 32-byte authentication key *akey* = HKDF-SHA256(*k*, 32, AUTHINFO, *salt*)
4. Derive the 32-byte encryption key *ekey* = HKDF-SHA256(*k*, 32, ENCRINFO, *salt*)
The remainder of the process is the same. Notice the reuse of the same *salt*
for PBKDF2-SHA256 and HKDF-SHA256. The prehashing of the password in step 2 is
done to prevent a [DoS attack using long
passwords](https://github.com/defuse/php-encryption/issues/230).
For `KeyProtectedByPassword`, the serialized key is encrypted according to the
password encryption defined above. However, the actual password used for
encryption is the SHA256 hash of the password the user provided. This is done in
order to provide domain separation between the message encryption in the user's
application and the internal key encryption done by this library. It fixes
a [key replacement chosen-protocol
attack](https://github.com/defuse/php-encryption/issues/240).
Frequently Asked Questions
===========================
How do I use this library to encrypt passwords?
------------------------------------------------
Passwords should not be encrypted, they should be hashed with a *slow* password
hashing function that's designed to slow down password guessing attacks. See
[How to Safely Store Your Users' Passwords in
2016](https://paragonie.com/blog/2016/02/how-safely-store-password-in-2016).
How do I give it the same key every time instead of a new random key?
----------------------------------------------------------------------
A `Key` object can be saved to a string by calling its `saveToAsciiSafeString()`
method. You will have to save that string somewhere safe, and then load it back
into a `Key` object using `Key`'s `loadFromAsciiSafeString` static method.
Where you store the string depends on your application. For example if you are
using `KeyProtectedByPassword` to encrypt files with a user's login password,
then you should not store the `Key` at all. If you are protecting sensitive data
on a server that may be compromised, then you should store it in a hardware
security module. When in doubt, consult a security expert.
Why is an EnvironmentIsBrokenException getting thrown?
-------------------------------------------------------
Either you've encountered a bug in this library, or your system doesn't support
the use of this library. For example, if your system does not have a secure
random number generator, this library will refuse to run, by throwing that
exception, instead of falling back to an insecure random number generator.
Why am I getting a BadFormatException when loading a Key from a string?
------------------------------------------------------------------------
If you're getting this exception, then the string you're giving to
`loadFromAsciiSafeString()` is *not* the same as the string you got from
`saveToAsciiSafeString()`. Perhaps your database column isn't wide enough and
it's truncating the string as you insert it?
Getting The Code
=================
There are two ways to use this library in your applications. You can either:
1. Use [Composer](https://getcomposer.org/), or
2. `require_once` a single `.phar` file in your application.
Option 1: Using Composer
-------------------------
Run this inside the directory of your composer-enabled project:
```sh
composer require defuse/php-encryption
```
Unfortunately, composer doesn't provide a way for you to verify that the code
you're getting was signed by this library's authors. If you want a more secure
option, use the `.phar` method described below.
Option 2: Including a PHAR
----------------------------
The `.phar` option lets you include this library into your project simply by
calling `require_once` on a single file. Download `defuse-crypto.phar` and
`defuse-crypto.phar.sig` from this project's
[releases](https://github.com/defuse/php-encryption/releases) page.
You should verify the integrity of the `.phar`. The `defuse-crypto.phar.sig`
contains the signature of `defuse-crypto.phar`. It is signed with Taylor
Hornby's PGP key. You can find Taylor's public key in `dist/signingkey.asc`. You
can verify the public key's fingerprint against the Taylor Hornby's [contact
page](https://defuse.ca/contact.htm) and
[twitter](https://twitter.com/DefuseSec/status/723741424253059074).
Once you have verified the signature, it is safe to use the `.phar`. Place it
somewhere in your file system, e.g. `/var/www/lib/defuse-crypto.phar`, and then
pass that path to `require_once`.
```php
<?php
require_once('/var/www/lib/defuse-crypto.phar');
// ... the Crypto, File, Key, and KeyProtectedByPassword classes are now
// available ...
// ...
```
Information for the Developers of php-encryption
=================================================
Status
-------
This library is currently frozen under a long-term support release. We do not
plan to add any new features. We will maintain the library by fixing any bugs
that are reported, or security vulnerabilities that are found.
Development Environment
------------------------
Development is done on Linux. To run the tests, you will need to have the
following tools installed:
- `php` (with OpenSSL enabled, if you're compiling from source).
- `gpg`
- `composer`
Running the Tests
------------------
First do `composer install` and then you can run the tests by running
`./test.sh`. This will download a PHPUnit PHAR, verify its cryptographic
signatures, and then use it to run the tests in `test/unit`.
Reporting Bugs
---------------
Please report bugs, even critical security vulnerabilities, by opening an issue
on GitHub. We recommend disclosing security vulnerabilities found in this
library *publicly* as soon as possible.
Philosophy
-----------
This library is developed around several core values:
- Rule #1: Security is prioritized over everything else.
> Whenever there is a conflict between security and some other property,
> security will be favored. For example, the library has runtime tests,
> which make it slower, but will hopefully stop it from encrypting stuff
> if the platform it's running on is broken.
- Rule #2: It should be difficult to misuse the library.
> We assume the developers using this library have no experience with
> cryptography. We only assume that they know that the "key" is something
> you need to encrypt and decrypt the messages, and that it must be kept
> secret. Whenever possible, the library should refuse to encrypt or decrypt
> messages when it is not being used correctly.
- Rule #3: The library aims only to be compatible with itself.
> Other PHP encryption libraries try to support every possible type of
> encryption, even the insecure ones (e.g. ECB mode). Because there are so
> many options, inexperienced developers must decide whether to use "CBC
> mode" or "ECB mode" when both are meaningless terms to them. This
> inevitably leads to vulnerabilities.
> This library will only support one secure mode. A developer using this
> library will call "encrypt" and "decrypt" methods without worrying about
> how they are implemented.
- Rule #4: The library should require no special installation.
> Some PHP encryption libraries, like libsodium-php, are not straightforward
> to install and cannot packaged with "just download and extract"
> applications. This library will always be just a handful of PHP files that
> you can copy to your source tree and require().
Publishing Releases
--------------------
To make a release, you will need to install [composer](https://getcomposer.org/)
and [box](https://github.com/box-project/box2) on your system. They will need to
be available in your `$PATH` so that running the commands `composer` and `box`
in your terminal run them, respectively. You will also need the private key for
signing (ID: 7B4B2D98) available.
Once you have those tools installed and the key available follow these steps:
**Remember to set the version number in `composer.json`!**
Make a fresh clone of the repository:
```
git clone <url>
```
Check out the branch you want to release:
```
git checkout <branchname>
```
Check that the version number in composer.json is correct:
```
cat composer.json
```
Run the tests:
```
composer install
./test.sh
```
Generate the `.phar`:
```
cd dist
make build-phar
```
Test the `.phar`:
```
cd ../
./test.sh dist/defuse-crypto.phar
```
Sign the `.phar`:
```
cd dist
make sign-phar
```
Tag the release:
```
git -c user.signingkey=7B4B2D98 tag -s "<TAG NAME>" -m "<TAG MESSAGE>"
```
`<TAG NAME>` should be in the format `v2.0.0` and `<TAG MESSAGE>` should look
like "Release of v2.0.0."
Push the tag to github, then use the
[releases](https://github.com/defuse/php-encryption/releases) page to draft
a new release for that tag. Upload the `.phar` and the `.phar.sig` file to be
included as part of that release.
Tutorial
=========
Hello! If you're reading this file, it's because you want to add encryption to
one of your PHP projects. My job, as the person writing this documentation, is
to help you make sure you're doing the right thing and then show you how to use
this library to do it. To help me help you, please read the documentation
*carefully* and *deliberately*.
A Word of Caution
------------------
Encryption is not magic dust you can sprinkle on a system to make it more
secure. The way encryption is integrated into a system's design needs to be
carefully thought out. Sometimes, encryption is the wrong thing to use. Other
times, encryption needs to be used in a very specific way in order for it to
work as intended. Even if you are sure of what you are doing, we strongly
recommend seeking advice from an expert.
The first step is to think about your application's threat model. Ask yourself
the following questions. Who will want to attack my application, and what will
they get out of it? Are they trying to steal some information? Trying to alter
or destroy some information? Or just trying to make the system go down so people
can't access it? Then ask yourself how encryption can help combat those threats.
If you're going to add encryption to your application, you should have a very
clear idea of exactly which kinds of attacks it's helping to secure your
application against. Once you have your threat model, think about what kinds of
attacks it *does not* cover, and whether or not you should improve your threat
model to include those attacks.
**This isn't for storing user login passwords:** The most common use of
cryptography in web applications is to protect the users' login passwords. If
you're trying to use this library to "encrypt" your users' passwords, you're in
the wrong place. Passwords shouldn't be *encrypted*, they should be *hashed*
with a slow computation-heavy function that makes password guessing attacks more
expensive. See [How to Safely Store Your Users' Passwords in
2016](https://paragonie.com/blog/2016/02/how-safely-store-password-in-2016).
**This isn't for encrypting network communication:** Likewise, if you're trying
to encrypt messages sent between two parties over the Internet, you don't want
to be using this library. For that, set up a TLS connection between the two
points, or, if it's a chat app, use the [Signal
Protocol](https://whispersystems.org/blog/advanced-ratcheting/).
What this library provides is symmetric encryption for "data at rest." This
means it is not suitable for use in building protocols where "data is in motion"
(i.e. moving over a network) except in limited set of cases.
Getting the Code
-----------------
There are several different ways to obtain this library's code and to add it to
your project. Even if you've already cloned the code from GitHub, you should
take steps to verify the cryptographic signatures to make sure the code you got
was not intercepted and modified by an attacker.
Please head over to the [**Installing and
Verifying**](InstallingAndVerifying.md) documentation to get the code, and then
come back here to continue the tutorial.
Using the Library
------------------
I'm going to assume you know what symmetric encryption is, and the difference
between symmetric and asymmetric encryption. If you don't, I recommend taking
[Dan Boneh's Cryptography I course](https://www.coursera.org/learn/crypto/) on
Coursera.
To give you a quick introduction to the library, I'm going to explain how it
would be used in two sterotypical scenarios. Hopefully, one of these sterotypes
is close enough to what you want to do that you'll be able to figure out what
needs to be different on your own.
### Formal Documentation
While this tutorial should get you up and running fast, it's important to
understand how this library behaves. Please make sure to read the formal
documentation of all of the functions you're using, since there are some
important security warnings there.
The following classes are available for you to use:
- [Crypto](classes/Crypto.md): Encrypting and decrypting strings.
- [File](classes/File.md): Encrypting and decrypting files.
- [Key](classes/Key.md): Represents a secret encryption key.
- [KeyProtectedByPassword](classes/KeyProtectedByPassword.md): Represents
a secret encryption key that needs to be "unlocked" by a password before it
can be used.
### Scenario #1: Keep data secret from the database administrator
In this scenario, our threat model is as follows. Alice is a server
administrator responsible for managing a trusted web server. Eve is a database
administrator responsible for managing a database server. Dave is a web
developer working on code that will eventually run on the trusted web server.
Let's say Alice and Dave trust each other, and Alice is going to host Dave's
application on her server. But both Alice and Dave don't trust Eve. They know
Eve is a good database administrator, but she might have incentive to steal the
data from the database. They want to keep some of the web application's data
secret from Eve.
In order to do that, Alice will write a script that generates a random
encryption key and prints it to standard output:
```php
<?php
// generate-key.php
$key = Key::createNewRandomKey();
echo $key->saveToAsciiSafeString();
```
Alice will run this script once and save the output to a configuration file, say
in `/etc/daveapp-secret-key.txt` and set the file permissions so that only the
user that the website PHP scripts run as can access it.
Dave will write his code to load the key from the configuration file:
```php
<?php
function loadEncryptionKeyFromConfig()
{
$keyAscii = // ... load the contents of /etc/daveapp-secret-key.txt
return Key::loadFromAsciiSafeString($keyAscii);
}
```
Then, whenever Dave wants to save a secret value to the database, he will first
encrypt it:
```php
<?php
// ...
$key = loadEncryptionKeyFromConfig();
// ...
$ciphertext = Crypto::encrypt($secret_data, $key);
// ... save $ciphertext into the database ...
```
Whenever Dave wants to get the value back from the database, he must decrypt it
using the same key:
```php
// ...
$key = loadEncryptionKeyFromConfig();
// ...
$ciphertext = // ... load $ciphertext from the database
try {
$secret_data = Crypto::decrypt($ciphertext, $key);
} catch (Defuse\Crypto\WrongKeyOrModifiedCiphertextException $ex) {
// An attack! Either the wrong key was loaded, or the ciphertext has
// changed since it was created -- either corrupted in the database or
// intentionally modified by Eve trying to carry out an attack.
// ... handle this case in a way that's suitable to your application ...
}
```
Note that if anyone ever steals the key from Alice's server, they can decrypt
all of the ciphertexts that are stored in the database. As part of our threat
model, we are assuming Alice's server administration skills and Dave's secure
coding skills are good enough to stop Eve from being able to steal the key.
Under those assumptions, this solution will prevent Eve from seeing data that's
stored in the database.
However, notice that our threat model says nothing about what could happen if
Eve wants to *modify* the data. With this solution, Eve will not be able to
alter any individual ciphertext (because each ciphertext has its own
cryptographic integrity check), but Eve *will* be able to swap ciphertexts for
one another, and revert ciphertexts to what they used to be at previous times.
If we needed to defend against such attacks, we would have to re-design our
threat model and come up with a different solution.
### Scenario #2: Encrypting account data with the user's login password
This scenario is like Scenario 1, but subtly different. The threat model is as
follows. We have Alice, a server administrator, and Dave, the developer. Alice
and Dave trust each other, and Alice wants to host Dave's web application,
including its database, on her server. Alice is worried about her server getting
hacked. The application will store the users' credit card numbers, and Alice
wants to protect them in case the server gets hacked.
We can model the situation like this: after the server gets hacked, the attacker
will have read and write access to all data on it until the attack is detected
and Alice rebuilds the server. We'll call the time the attacker has access to
the server the *exposure window.* One idea to minimize loss is to encrypt the
users' credit card numbers using a key made from their login password. Then, as
long as the users all have strong passwords, and they are never logged in during
the exposure window, their credit cards will be protected from the attacker.
To implement this, Dave will use the `KeyProtectedByPassword` class. When a new
user account is created, Dave will save a new key to their account, one that's
protected by their login password:
```php
<?php
function CreateUserAccount($username, $password)
{
// ... other user account creation stuff, including password hashing
$protected_key = KeyProtectedByPassword::createRandomPasswordProtectedKey($password);
$protected_key_encoded = $protected_key->saveToAsciiSafeString();
// ... save $protected_key_encoded into the user's account record
}
```
Then, when the user logs in, Dave's code will load the protected key from the
user's account record, unlock it to get a `Key` object, and save the `Key`
object somewhere safe (like temporary memory-backed session storage). Note that
wherever Dave's code saves the key, it must be destroyed once the user logs out,
or else the attacker might be able to find users' keys even if they were never
logged in during the attack.
```php
<?php
// ... authenticate the user using a good password hashing scheme
// keep the user's password in $password
$protected_key_encoded = // ... load it from the user's account record
$protected_key = KeyProtectedByPassword::loadFromAsciiSafeString($protected_key_encoded);
$user_key = $protected_key->unlockKey($password);
$user_key_encoded = $user_key->saveToAsciiSafeString();
// ... save $user_key_encoded in the session
```
```php
<?php
// ... when the user is logging out ...
// ... securely wipe the saved $user_key_encoded from the system ...
```
When a user adds their credit card number, Dave's code will get the key from the
session and use it to encrypt the credit card number:
```php
<?php
// ...
$user_key_encoded = // ... get it out of the session ...
$user_key = Key::loadFromAsciiSafeString($user_key_encoded);
// ...
$credit_card_number = // ... get credit card number from the user
$encrypted_card_number = Crypto::encrypt($credit_card_number, $user_key);
// ... save $encrypted_card_number in the database
```
When the application needs to use the credit card number, it will decrypt it:
```php
<?php
// ...
$user_key_encoded = // ... get it out of the session
$user_key = Key::loadFromAsciiSafeString($user_key_encoded);
// ...
$encrypted_card_number = // ... load it from the database ...
try {
$credit_card_number = Crypto::decrypt($encrypted_card_number, $user_key);
} catch (Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException $ex) {
// Either there's a bug in our code, we're trying to decrypt with the
// wrong key, or the encrypted credit card number was corrupted in the
// database.
// ... handle this case ...
}
```
With all caveats carefully heeded, this solution limits credit card number
exposure in the case where Alice's server gets hacked for a short amount of
time. Remember to think about the attacks that *aren't* included in our threat
model. The attacker is still free to do all sorts of harmful things like
modifying the server's data which may go undetected if Alice doesn't have secure
backups to compare against.
Getting Help
-------------
If you're having difficulty using the library, see if your problem is already
solved by an answer in the [FAQ](FAQ.md).
Upgrading From Version 1.2
===========================
With version 2.0.0 of this library came major changes to the ciphertext format,
algorithms used for encryption, and API.
In version 1.2, keys were represented by 16-byte string variables. In version
2.0.0, keys are represented by objects, instances of the `Key` class. This
change was made in order to make it harder to misuse the API. For example, in
version 1.2, you could pass in *any* 16-byte string, but in version 2.0.0 you
need a `Key` object, which you can only get if you're "doing the right thing."
This means that for all of your old version 1.2 keys, you'll have to:
1. Generate a new version 2.0.0 key.
2. For all of the ciphertexts encrypted under the old key:
1. Decrypt the ciphertext using the old version 1.2 key.
2. Re-encrypt it using the new version 2.0.0 key.
Use the special `Crypto::legacyDecrypt()` method to decrypt the old ciphertexts
using the old key and then re-encrypt them using `Crypto::encrypt()` with the
new key. Your code will look something like the following. To avoid data loss,
securely back up your keys and data before running your upgrade code.
```php
<?php
// ...
$legacy_ciphertext = // ... get the ciphertext you want to upgrade ...
$legacy_key = // ... get the key to decrypt this ciphertext ...
// Generate the new key that we'll re-encrypt the ciphertext with.
$new_key = Key::createNewRandomKey();
// ... save it somewhere ...
// Decrypt it.
try {
$plaintext = Crypto::legacyDecrypt($legacy_ciphertext, $legacy_key);
} catch (Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException $ex)
{
// ... TODO: handle this case appropriately ...
}
// Re-encrypt it.
$new_ciphertext = Crypto::encrypt($plaintext, $new_key);
// ... replace the old $legacy_ciphertext with the new $new_ciphertext
// ...
```
Class: Defuse\Crypto\Crypto
============================
The `Crypto` class provides encryption and decryption of strings either using
a secret key or secret password. For encryption and decryption of large files,
see the `File` class.
This code for this class is in `src/Crypto.php`.
Instance Methods
-----------------
This class has no instance methods.
Static Methods
---------------
### Crypto::encrypt($plaintext, Key $key, $raw\_binary = false)
**Description:**
Encrypts a plaintext string using a secret key.
**Parameters:**
1. `$plaintext` is the string to encrypt.
2. `$key` is an instance of `Key` containing the secret key for encryption.
3. `$raw_binary` determines whether the output will be a byte string (true) or
hex encoded (false, the default).
**Return value:**
Returns a ciphertext string representing `$plaintext` encrypted with the key
`$key`. Knowledge of `$key` is required in order to decrypt the ciphertext and
recover the plaintext.
**Exceptions:**
- `Defuse\Crypto\Exception\EnvironmentIsBrokenException` is thrown either when
the platform the code is running on cannot safely perform encryption for some
reason (e.g. it lacks a secure random number generator), or the runtime tests
detected a bug in this library.
**Side-effects and performance:**
This method runs a small and very fast set of self-tests if it is the very first
time one of the `Crypto` methods has been called. The performance overhead is
negligible and can be safely ignored in all applications.
**Cautions:**
The ciphertext returned by this method is decryptable by anyone with knowledge
of the key `$key`. It is the caller's responsibility to keep `$key` secret.
Where `$key` should be stored is up to the caller and depends on the threat
model the caller is designing their application under. If you are unsure where
to store `$key`, consult with a professional cryptographer to get help designing
your application.
### Crypto::decrypt($ciphertext, Key $key, $raw\_binary = false)
**Description:**
Decrypts a ciphertext string using a secret key.
**Parameters:**
1. `$ciphertext` is the ciphertext to be decrypted.
2. `$key` is an instance of `Key` containing the secret key for decryption.
3. `$raw_binary` must have the same value as the `$raw_binary` given to the
call to `encrypt()` that generated `$ciphertext`.
**Return value:**
If the decryption succeeds, returns a string containing the same value as the
string that was passed to `encrypt()` when `$ciphertext` was produced. Upon
a successful return, the caller can be assured that `$ciphertext` could not have
been produced except by someone with knowledge of `$key`.
**Exceptions:**
- `Defuse\Crypto\Exception\EnvironmentIsBrokenException` is thrown either when
the platform the code is running on cannot safely perform encryption for some
reason (e.g. it lacks a secure random number generator), or the runtime tests
detected a bug in this library.
- `Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException` is thrown if
the `$key` is not the correct key for the given ciphertext, or if the
ciphertext has been modified (possibly maliciously). There is no way to
distinguish between these two cases.
**Side-effects and performance:**
This method runs a small and very fast set of self-tests if it is the very first
time one of the `Crypto` methods has been called. The performance overhead is
negligible and can be safely ignored in all applications.
**Cautions:**
It is impossible in principle to distinguish between the case where you attempt
to decrypt with the wrong key and the case where you attempt to decrypt
a modified (corrupted) ciphertext. It is up to the caller how to best deal with
this ambiguity, as it depends on the application this library is being used in.
If in doubt, consult with a professional cryptographer.
### Crypto::encryptWithPassword($plaintext, $password, $raw\_binary = false)
**Description:**
Encrypts a plaintext string using a secret password.
**Parameters:**
1. `$plaintext` is the string to encrypt.
2. `$password` is a string containing the secret password used for encryption.
3. `$raw_binary` determines whether the output will be a byte string (true) or
hex encoded (false, the default).
**Return value:**
Returns a ciphertext string representing `$plaintext` encrypted with a key
derived from `$password`. Knowledge of `$password` is required in order to
decrypt the ciphertext and recover the plaintext.
**Exceptions:**
- `Defuse\Crypto\Exception\EnvironmentIsBrokenException` is thrown either when
the platform the code is running on cannot safely perform encryption for some
reason (e.g. it lacks a secure random number generator), or the runtime tests
detected a bug in this library.
**Side-effects and performance:**
This method is intentionally slow, using a lot of CPU resources for a fraction
of a second. It applies key stretching to the password in order to make password
guessing attacks more computationally expensive. If you need a faster way to
encrypt multiple ciphertexts under the same password, see the
`KeyProtectedByPassword` class.
This method runs a small and very fast set of self-tests if it is the very first
time one of the `Crypto` methods has been called. The performance overhead is
negligible and can be safely ignored in all applications.
**Cautions:**
PHP stack traces display (portions of) the arguments passed to methods on the
call stack. If an exception is thrown inside this call, and it is uncaught, the
value of `$password` may be leaked out to an attacker through the stack trace.
We recommend configuring PHP to never output stack traces (either displaying
them to the user or saving them to log files).
### Crypto::decryptWithPassword($ciphertext, $password, $raw\_binary = false)
**Description:**
Decrypts a ciphertext string using a secret password.
**Parameters:**
1. `$ciphertext` is the ciphertext to be decrypted.
2. `$password` is a string containing the secret password used for decryption.
3. `$raw_binary` must have the same value as the `$raw_binary` given to the
call to `encryptWithPassword()` that generated `$ciphertext`.
**Return value:**
If the decryption succeeds, returns a string containing the same value as the
string that was passed to `encryptWithPassword()` when `$ciphertext` was
produced. Upon a successful return, the caller can be assured that `$ciphertext`
could not have been produced except by someone with knowledge of `$password`.
**Exceptions:**
- `Defuse\Crypto\Exception\EnvironmentIsBrokenException` is thrown either when
the platform the code is running on cannot safely perform encryption for some
reason (e.g. it lacks a secure random number generator), or the runtime tests
detected a bug in this library.
- `Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException` is thrown if
the `$password` is not the correct password for the given ciphertext, or if
the ciphertext has been modified (possibly maliciously). There is no way to
distinguish between these two cases.
**Side-effects:**
This method is intentionally slow. It applies key stretching to the password in
order to make password guessing attacks more computationally expensive. If you
need a faster way to encrypt multiple ciphertexts under the same password, see
the `KeyProtectedByPassword` class.
This method runs a small and very fast set of self-tests if it is the very first
time one of the `Crypto` methods has been called. The performance overhead is
negligible and can be safely ignored in all applications.
**Cautions:**
PHP stack traces display (portions of) the arguments passed to methods on the
call stack. If an exception is thrown inside this call, and it is uncaught, the
value of `$password` may be leaked out to an attacker through the stack trace.
We recommend configuring PHP to never output stack traces (either displaying
them to the user or saving them to log files).
It is impossible in principle to distinguish between the case where you attempt
to decrypt with the wrong password and the case where you attempt to decrypt
a modified (corrupted) ciphertext. It is up to the caller how to best deal with
this ambiguity, as it depends on the application this library is being used in.
If in doubt, consult with a professional cryptographer.
### Crypto::legacyDecrypt($ciphertext, $key)
**Description:**
Decrypts a ciphertext produced by version 1 of this library so that the
plaintext can be re-encrypted into a version 2 ciphertext. See [Upgrading from
v1.2](../UpgradingFromV1.2.md).
**Parameters:**
1. `$ciphertext` is a ciphertext produced by version 1.x of this library.
2. `$key` is a 16-byte string (*not* a Key object) containing the key that was
used with version 1.x of this library to produce `$ciphertext`.
**Return value:**
If the decryption succeeds, returns the string that was encrypted to make
`$ciphertext` by version 1.x of this library. Upon a successful return, the
caller can be assured that `$ciphertext` could not have been produced except by
someone with knowledge of `$key`.
**Exceptions:**
- `Defuse\Crypto\Exception\EnvironmentIsBrokenException` is thrown either when
the platform the code is running on cannot safely perform encryption for some
reason (e.g. it lacks a secure random number generator), or the runtime tests
detected a bug in this library.
- `Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException` is thrown if
the `$key` is not the correct key for the given ciphertext, or if the
ciphertext has been modified (possibly maliciously). There is no way to
distinguish between these two cases.
**Side-effects:**
This method runs a small and very fast set of self-tests if it is the very first
time one of the `Crypto` methods has been called. The performance overhead is
negligible and can be safely ignored in all applications.
**Cautions:**
PHP stack traces display (portions of) the arguments passed to methods on the
call stack. If an exception is thrown inside this call, and it is uncaught, the
value of `$key` may be leaked out to an attacker through the stack trace. We
recommend configuring PHP to never output stack traces (either displaying them
to the user or saving them to log files).
It is impossible in principle to distinguish between the case where you attempt
to decrypt with the wrong key and the case where you attempt to decrypt
a modified (corrupted) ciphertext. It is up to the caller how to best deal with
this ambiguity, as it depends on the application this library is being used in.
If in doubt, consult with a professional cryptographer.
Class: Defuse\Crypto\File
==========================
Instance Methods
-----------------
This class has no instance methods.
Static Methods
---------------
### File::encryptFile($inputFilename, $outputFilename, Key $key)
**Description:**
Encrypts a file using a secret key.
**Parameters:**
1. `$inputFilename` is the path to a file containing the plaintext to encrypt.
2. `$outputFilename` is the path to save the ciphertext file.
3. `$key` is an instance of `Key` containing the secret key for encryption.
**Behavior:**
Encrypts the contents of the input file, writing the result to the output file.
If the output file already exists, it is overwritten.
**Return value:**
Does not return a value.
**Exceptions:**
- `Defuse\Crypto\Exception\IOException` is thrown if there is an I/O error.
- `Defuse\Crypto\Exception\EnvironmentIsBrokenException` is thrown either when
the platform the code is running on cannot safely perform encryption for some
reason (e.g. it lacks a secure random number generator), or the runtime tests
detected a bug in this library.
**Side-effects and performance:**
None.
**Cautions:**
The ciphertext output by this method is decryptable by anyone with knowledge of
the key `$key`. It is the caller's responsibility to keep `$key` secret. Where
`$key` should be stored is up to the caller and depends on the threat model the
caller is designing their application under. If you are unsure where to store
`$key`, consult with a professional cryptographer to get help designing your
application.
### File::decryptFile($inputFilename, $outputFilename, Key $key)
**Description:**
Decrypts a file using a secret key.
**Parameters:**
1. `$inputFilename` is the path to a file containing the ciphertext to decrypt.
2. `$outputFilename` is the path to save the decrypted plaintext file.
3. `$key` is an instance of `Key` containing the secret key for decryption.
**Behavior:**
Decrypts the contents of the input file, writing the result to the output file.
If the output file already exists, it is overwritten.
**Return value:**
Does not return a value.
**Exceptions:**
- `Defuse\Crypto\Exception\IOException` is thrown if there is an I/O error.
- `Defuse\Crypto\Exception\EnvironmentIsBrokenException` is thrown either when
the platform the code is running on cannot safely perform encryption for some
reason (e.g. it lacks a secure random number generator), or the runtime tests
detected a bug in this library.
- `Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException` is thrown if
the `$key` is not the correct key for the given ciphertext, or if the
ciphertext has been modified (possibly maliciously). There is no way to
distinguish between these two cases.
**Side-effects and performance:**
The input ciphertext is processed in two passes. The first pass verifies the
integrity and the second pass performs the actual decryption of the file and
writing to the output file. This is done in a streaming manner so that only
a small part of the file is ever loaded into memory at a time.
**Cautions:**
Be aware that when `Defuse\Crypto\WrongKeyOrModifiedCiphertextException` is
thrown, some partial plaintext data may have been written to the output. Any
plaintext data that is output is guaranteed to be a prefix of the original
plaintext (i.e. at worst it was truncated). This can only happen if an attacker
modifies the input between the first pass (integrity check) and the second pass
(decryption) over the file.
It is impossible in principle to distinguish between the case where you attempt
to decrypt with the wrong key and the case where you attempt to decrypt
a modified (corrupted) ciphertext. It is up to the caller how to best deal with
this ambiguity, as it depends on the application this library is being used in.
If in doubt, consult with a professional cryptographer.
### File::encryptFileWithPassword($inputFilename, $outputFilename, $password)
**Description:**
Encrypts a file with a password.
**Parameters:**
1. `$inputFilename` is the path to a file containing the plaintext to encrypt.
2. `$outputFilename` is the path to save the ciphertext file.
3. `$password` is the password used for decryption.
**Behavior:**
Encrypts the contents of the input file, writing the result to the output file.
If the output file already exists, it is overwritten.
**Return value:**
Does not return a value.
**Exceptions:**
- `Defuse\Crypto\Exception\IOException` is thrown if there is an I/O error.
- `Defuse\Crypto\Exception\EnvironmentIsBrokenException` is thrown either when
the platform the code is running on cannot safely perform encryption for some
reason (e.g. it lacks a secure random number generator), or the runtime tests
detected a bug in this library.
**Side-effects and performance:**
This method is intentionally slow, using a lot of CPU resources for a fraction
of a second. It applies key stretching to the password in order to make password
guessing attacks more computationally expensive. If you need a faster way to
encrypt multiple ciphertexts under the same password, see the
`KeyProtectedByPassword` class.
**Cautions:**
PHP stack traces display (portions of) the arguments passed to methods on the
call stack. If an exception is thrown inside this call, and it is uncaught, the
value of `$password` may be leaked out to an attacker through the stack trace.
We recommend configuring PHP to never output stack traces (either displaying
them to the user or saving them to log files).
### File::decryptFileWithPassword($inputFilename, $outputFilename, $password)
**Description:**
Decrypts a file with a password.
**Parameters:**
1. `$inputFilename` is the path to a file containing the ciphertext to decrypt.
2. `$outputFilename` is the path to save the decrypted plaintext file.
3. `$password` is the password used for decryption.
**Behavior:**
Decrypts the contents of the input file, writing the result to the output file.
If the output file already exists, it is overwritten.
**Return value:**
Does not return a value.
**Exceptions:**
- `Defuse\Crypto\Exception\IOException` is thrown if there is an I/O error.
- `Defuse\Crypto\Exception\EnvironmentIsBrokenException` is thrown either when
the platform the code is running on cannot safely perform encryption for some
reason (e.g. it lacks a secure random number generator), or the runtime tests
detected a bug in this library.
- `Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException` is thrown if
the `$password` is not the correct key for the given ciphertext, or if the
ciphertext has been modified (possibly maliciously). There is no way to
distinguish between these two cases.
**Side-effects and performance:**
This method is intentionally slow, using a lot of CPU resources for a fraction
of a second. It applies key stretching to the password in order to make password
guessing attacks more computationally expensive. If you need a faster way to
encrypt multiple ciphertexts under the same password, see the
`KeyProtectedByPassword` class.
The input ciphertext is processed in two passes. The first pass verifies the
integrity and the second pass performs the actual decryption of the file and
writing to the output file. This is done in a streaming manner so that only
a small part of the file is ever loaded into memory at a time.
**Cautions:**
PHP stack traces display (portions of) the arguments passed to methods on the
call stack. If an exception is thrown inside this call, and it is uncaught, the
value of `$password` may be leaked out to an attacker through the stack trace.
We recommend configuring PHP to never output stack traces (either displaying
them to the user or saving them to log files).
Be aware that when `Defuse\Crypto\WrongKeyOrModifiedCiphertextException` is
thrown, some partial plaintext data may have been written to the output. Any
plaintext data that is output is guaranteed to be a prefix of the original
plaintext (i.e. at worst it was truncated). This can only happen if an attacker
modifies the input between the first pass (integrity check) and the second pass
(decryption) over the file.
It is impossible in principle to distinguish between the case where you attempt
to decrypt with the wrong password and the case where you attempt to decrypt
a modified (corrupted) ciphertext. It is up to the caller how to best deal with
this ambiguity, as it depends on the application this library is being used in.
If in doubt, consult with a professional cryptographer.
### File::encryptResource($inputHandle, $outputHandle, Key $key)
**Description:**
Encrypts a resource (stream) with a secret key.
**Parameters:**
1. `$inputHandle` is a handle to a resource (like a file pointer) containing the
plaintext to encrypt.
2. `$outputHandle` is a handle to a resource (like a file pointer) that the
ciphertext will be written to.
3. `$key` is an instance of `Key` containing the secret key for encryption.
**Behavior:**
Encrypts the data read from the input stream and writes it to the output stream.
**Return value:**
Does not return a value.
**Exceptions:**
- `Defuse\Crypto\Exception\IOException` is thrown if there is an I/O error.
- `Defuse\Crypto\Exception\EnvironmentIsBrokenException` is thrown either when
the platform the code is running on cannot safely perform encryption for some
reason (e.g. it lacks a secure random number generator), or the runtime tests
detected a bug in this library.
**Side-effects and performance:**
None.
**Cautions:**
The ciphertext output by this method is decryptable by anyone with knowledge of
the key `$key`. It is the caller's responsibility to keep `$key` secret. Where
`$key` should be stored is up to the caller and depends on the threat model the
caller is designing their application under. If you are unsure where to store
`$key`, consult with a professional cryptographer to get help designing your
application.
### File::decryptResource($inputHandle, $outputHandle, Key $key)
**Description:**
Decrypts a resource (stream) with a secret key.
**Parameters:**
1. `$inputHandle` is a handle to a file-backed resource containing the
ciphertext to decrypt. It must be a file not a network stream or standard
input.
2. `$outputHandle` is a handle to a resource (like a file pointer) that the
plaintext will be written to.
3. `$key` is an instance of `Key` containing the secret key for decryption.
**Behavior:**
Decrypts the data read from the input stream and writes it to the output stream.
**Return value:**
Does not return a value.
**Exceptions:**
- `Defuse\Crypto\Exception\IOException` is thrown if there is an I/O error.
- `Defuse\Crypto\Exception\EnvironmentIsBrokenException` is thrown either when
the platform the code is running on cannot safely perform encryption for some
reason (e.g. it lacks a secure random number generator), or the runtime tests
detected a bug in this library.
- `Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException` is thrown if
the `$key` is not the correct key for the given ciphertext, or if the
ciphertext has been modified (possibly maliciously). There is no way to
distinguish between these two cases.
**Side-effects and performance:**
The input ciphertext is processed in two passes. The first pass verifies the
integrity and the second pass performs the actual decryption of the file and
writing to the output file. This is done in a streaming manner so that only
a small part of the file is ever loaded into memory at a time.
**Cautions:**
Be aware that when `Defuse\Crypto\WrongKeyOrModifiedCiphertextException` is
thrown, some partial plaintext data may have been written to the output. Any
plaintext data that is output is guaranteed to be a prefix of the original
plaintext (i.e. at worst it was truncated). This can only happen if an attacker
modifies the input between the first pass (integrity check) and the second pass
(decryption) over the file.
It is impossible in principle to distinguish between the case where you attempt
to decrypt with the wrong key and the case where you attempt to decrypt
a modified (corrupted) ciphertext. It is up to the caller how to best deal with
this ambiguity, as it depends on the application this library is being used in.
If in doubt, consult with a professional cryptographer.
### File::encryptResourceWithPassword($inputHandle, $outputHandle, $password)
**Description:**
Encrypts a resource (stream) with a password.
**Parameters:**
1. `$inputHandle` is a handle to a resource (like a file pointer) containing the
plaintext to encrypt.
2. `$outputHandle` is a handle to a resource (like a file pointer) that the
ciphertext will be written to.
3. `$password` is the password used for encryption.
**Behavior:**
Encrypts the data read from the input stream and writes it to the output stream.
**Return value:**
Does not return a value.
**Exceptions:**
- `Defuse\Crypto\Exception\IOException` is thrown if there is an I/O error.
- `Defuse\Crypto\Exception\EnvironmentIsBrokenException` is thrown either when
the platform the code is running on cannot safely perform encryption for some
reason (e.g. it lacks a secure random number generator), or the runtime tests
detected a bug in this library.
**Side-effects and performance:**
This method is intentionally slow, using a lot of CPU resources for a fraction
of a second. It applies key stretching to the password in order to make password
guessing attacks more computationally expensive. If you need a faster way to
encrypt multiple ciphertexts under the same password, see the
`KeyProtectedByPassword` class.
**Cautions:**
PHP stack traces display (portions of) the arguments passed to methods on the
call stack. If an exception is thrown inside this call, and it is uncaught, the
value of `$password` may be leaked out to an attacker through the stack trace.
We recommend configuring PHP to never output stack traces (either displaying
them to the user or saving them to log files).
### File::decryptResourceWithPassword($inputHandle, $outputHandle, $password)
**Description:**
Decrypts a resource (stream) with a password.
**Parameters:**
1. `$inputHandle` is a handle to a file-backed resource containing the
ciphertext to decrypt. It must be a file not a network stream or standard
input.
2. `$outputHandle` is a handle to a resource (like a file pointer) that the
plaintext will be written to.
3. `$password` is the password used for decryption.
**Behavior:**
Decrypts the data read from the input stream and writes it to the output stream.
**Return value:**
Does not return a value.
**Exceptions:**
- `Defuse\Crypto\Exception\IOException` is thrown if there is an I/O error.
- `Defuse\Crypto\Exception\EnvironmentIsBrokenException` is thrown either when
the platform the code is running on cannot safely perform encryption for some
reason (e.g. it lacks a secure random number generator), or the runtime tests
detected a bug in this library.
- `Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException` is thrown if
the `$password` is not the correct key for the given ciphertext, or if the
ciphertext has been modified (possibly maliciously). There is no way to
distinguish between these two cases.
**Side-effects and performance:**
This method is intentionally slow, using a lot of CPU resources for a fraction
of a second. It applies key stretching to the password in order to make password
guessing attacks more computationally expensive. If you need a faster way to
encrypt multiple ciphertexts under the same password, see the
`KeyProtectedByPassword` class.
The input ciphertext is processed in two passes. The first pass verifies the
integrity and the second pass performs the actual decryption of the file and
writing to the output file. This is done in a streaming manner so that only
a small part of the file is ever loaded into memory at a time.
**Cautions:**
PHP stack traces display (portions of) the arguments passed to methods on the
call stack. If an exception is thrown inside this call, and it is uncaught, the
value of `$password` may be leaked out to an attacker through the stack trace.
We recommend configuring PHP to never output stack traces (either displaying
them to the user or saving them to log files).
Be aware that when `Defuse\Crypto\WrongKeyOrModifiedCiphertextException` is
thrown, some partial plaintext data may have been written to the output. Any
plaintext data that is output is guaranteed to be a prefix of the original
plaintext (i.e. at worst it was truncated). This can only happen if an attacker
modifies the input between the first pass (integrity check) and the second pass
(decryption) over the file.
It is impossible in principle to distinguish between the case where you attempt
to decrypt with the wrong password and the case where you attempt to decrypt
a modified (corrupted) ciphertext. It is up to the caller how to best deal with
this ambiguity, as it depends on the application this library is being used in.
If in doubt, consult with a professional cryptographer.
Class: Defuse\Crypto\Key
=========================
The `Key` class represents a secret key used for encrypting and decrypting. Once
you have a `Key` instance, you can use it with the `Crypto` class to encrypt and
decrypt strings and with the `File` class to encrypt and decrypt files.
Instance Methods
-----------------
### saveToAsciiSafeString()
**Description:**
Saves the encryption key to a string of printable ASCII characters, which can be
loaded again into a `Key` instance using `Key::loadFromAsciiSafeString()`.
**Parameters:**
This method does not take any parameters.
**Return value:**
Returns a string of printable ASCII characters representing this `Key` instance,
which can be loaded back into an instance of `Key` using
`Key::loadFromAsciiSafeString()`.
**Exceptions:**
- `Defuse\Crypto\Exception\EnvironmentIsBrokenException` is thrown either when
the platform the code is running on cannot safely perform encryption for some
reason (e.g. it lacks a secure random number generator), or the runtime tests
detected a bug in this library.
**Side-effects and performance:**
None.
**Cautions:**
This method currently returns a hexadecimal string. You should not rely on this
behavior. For example, it may be improved in the future to return a base64
string.
Static Methods
---------------
### Key::createNewRandomKey()
**Description:**
Generates a new random key and returns an instance of `Key`.
**Parameters:**
This method does not take any parameters.
**Return value:**
Returns an instance of `Key` containing a randomly-generated encryption key.
**Exceptions:**
- `Defuse\Crypto\Exception\EnvironmentIsBrokenException` is thrown either when
the platform the code is running on cannot safely perform encryption for some
reason (e.g. it lacks a secure random number generator), or the runtime tests
detected a bug in this library.
**Side-effects and performance:**
None.
**Cautions:**
None.
### Key::loadFromAsciiSafeString($saved\_key\_string)
**Description:**
Loads an instance of `Key` that was saved to a string by
`saveToAsciiSafeString()`.
**Parameters:**
1. `$saved_key_string` is the string returned from `saveToAsciiSafeString()`
when the original `Key` instance was saved.
**Return value:**
Returns an instance of `Key` representing the same encryption key as the one
that was represented by the `Key` instance that got saved into
`$saved_key_string` by a call to `saveToAsciiSafeString()`.
**Exceptions:**
- `Defuse\Crypto\Exception\EnvironmentIsBrokenException` is thrown either when
the platform the code is running on cannot safely perform encryption for some
reason (e.g. it lacks a secure random number generator), or the runtime tests
detected a bug in this library.
- `Defuse\Crypto\Exception\BadFormatException` is thrown whenever
`$saved_key_string` does not represent a valid `Key` instance.
**Side-effects and performance:**
None.
**Cautions:**
None.
Class: Defuse\Crypto\KeyProtectedByPassword
============================================
The `KeyProtectedByPassword` class represents a key that is "locked" with
a password. In order to obtain an instance of `Key` that you can use for
encrypting and decrypting, a `KeyProtectedByPassword` must first be "unlocked"
by providing the correct password.
`KeyProtectedByPassword` provides an alternative to using the
`encryptWithPassword()`, `decryptWithPassword()`, `encryptFileWithPassword()`,
and `decryptFileWithPassword()` methods with several advantages:
- The slow and computationally-expensive key stretching is run only once when
you unlock a `KeyProtectedByPassword` to obtain the `Key`.
- You do not have to keep the original password in memory to encrypt and decrypt
things. After you've obtained the `Key` from a `KeyProtectedByPassword`, the
password is no longer necessary.
Instance Methods
-----------------
### saveToAsciiSafeString()
**Description:**
Saves the protected key to a string of printable ASCII characters, which can be
loaded again into a `KeyProtectedByPassword` instance using
`KeyProtectedByPassword::loadFromAsciiSafeString()`.
**Parameters:**
This method does not take any parameters.
**Return value:**
Returns a string of printable ASCII characters representing this
`KeyProtectedByPassword` instance, which can be loaded back into an instance of
`KeyProtectedByPassword` using
`KeyProtectedByPassword::loadFromAsciiSafeString()`.
**Exceptions:**
- `Defuse\Crypto\Exception\EnvironmentIsBrokenException` is thrown either when
the platform the code is running on cannot safely perform encryption for some
reason (e.g. it lacks a secure random number generator), or the runtime tests
detected a bug in this library.
**Side-effects and performance:**
None.
**Cautions:**
This method currently returns a hexadecimal string. You should not rely on this
behavior. For example, it may be improved in the future to return a base64
string.
### unlockKey($password)
**Description:**
Unlocks the password-protected key, obtaining a `Key` which can be used for
encryption and decryption.
**Parameters:**
1. `$password` is the password required to unlock this `KeyProtectedByPassword`
to obtain the `Key`.
**Return value:**
If `$password` is the correct password, then this method returns an instance of
the `Key` class.
**Exceptions:**
- `Defuse\Crypto\Exception\EnvironmentIsBrokenException` is thrown either when
the platform the code is running on cannot safely perform encryption for some
reason (e.g. it lacks a secure random number generator), or the runtime tests
detected a bug in this library.
- `Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException` is thrown if
either the given `$password` is not the correct password for this
`KeyProtectedByPassword` or the ciphertext stored internally by this object
has been modified, i.e. it was accidentally corrupted or intentionally
corrupted by an attacker. There is no way for the caller to distinguish
between these two cases.
**Side-effects and performance:**
This method runs a small and very fast set of self-tests if it is the very first
time this method or one of the `Crypto` methods has been called. The performance
overhead is negligible and can be safely ignored in all applications.
**Cautions:**
PHP stack traces display (portions of) the arguments passed to methods on the
call stack. If an exception is thrown inside this call, and it is uncaught, the
value of `$password` may be leaked out to an attacker through the stack trace.
We recommend configuring PHP to never output stack traces (either displaying
them to the user or saving them to log files).
It is impossible in principle to distinguish between the case where you attempt
to unlock with the wrong password and the case where you attempt to unlock
a modified (corrupted) `KeyProtectedByPassword. It is up to the caller how to
best deal with this ambiguity, as it depends on the application this library is
being used in. If in doubt, consult with a professional cryptographer.
Static Methods
---------------
### KeyProtectedByPassword::createRandomPasswordProtectedKey($password)
**Description:**
Generates a new random key that's protected by the string `$password` and
returns an instance of `KeyProtectedByPassword`.
**Parameters:**
1. `$password` is the password used to protect the random key.
**Return value:**
Returns an instance of `KeyProtectedByPassword` containing a randomly-generated
encryption key that's protected by the password `$password`.
**Exceptions:**
- `Defuse\Crypto\Exception\EnvironmentIsBrokenException` is thrown either when
the platform the code is running on cannot safely perform encryption for some
reason (e.g. it lacks a secure random number generator), or the runtime tests
detected a bug in this library.
**Side-effects and performance:**
This method runs a small and very fast set of self-tests if it is the very first
time this method or one of the `Crypto` methods has been called. The performance
overhead is negligible and can be safely ignored in all applications.
**Cautions:**
PHP stack traces display (portions of) the arguments passed to methods on the
call stack. If an exception is thrown inside this call, and it is uncaught, the
value of `$password` may be leaked out to an attacker through the stack trace.
We recommend configuring PHP to never output stack traces (either displaying
them to the user or saving them to log files).
Be aware that if you protecting multiple keys with the same password, an
attacker with write access to your system will be able to swap the protected
keys around so that the wrong key gets used next time it is unlocked. This could
lead to data being encrypted with the wrong key, perhaps one that the attacker
knows.
### KeyProtectedByPassword::loadFromAsciiSafeString($saved\_key\_string)
**Description:**
Loads an instance of `KeyProtectedByPassword` that was saved to a string by
`saveToAsciiSafeString()`.
**Parameters:**
1. `$saved_key_string` is the string returned from `saveToAsciiSafeString()`
when the original `KeyProtectedByPassword` instance was saved.
**Return value:**
Returns an instance of `KeyProtectedByPassword` representing the same
password-protected key as the one that was represented by the
`KeyProtectedByPassword` instance that got saved into `$saved_key_string` by
a call to `saveToAsciiSafeString()`.
**Exceptions:**
- `Defuse\Crypto\Exception\EnvironmentIsBrokenException` is thrown either when
the platform the code is running on cannot safely perform encryption for some
reason (e.g. it lacks a secure random number generator), or the runtime tests
detected a bug in this library.
- `Defuse\Crypto\Exception\BadFormatException` is thrown whenever
`$saved_key_string` does not represent a valid `KeyProtectedByPassword`
instance.
**Side-effects and performance:**
None.
**Cautions:**
None.
<?php
namespace Defuse\Crypto;
use Defuse\Crypto\Exception as Ex;
final class Core
{
const HEADER_VERSION_SIZE = 4;
const MINIMUM_CIPHERTEXT_SIZE = 84;
const CURRENT_VERSION = "\xDE\xF5\x02\x00";
const CIPHER_METHOD = 'aes-256-ctr';
const BLOCK_BYTE_SIZE = 16;
const KEY_BYTE_SIZE = 32;
const SALT_BYTE_SIZE = 32;
const MAC_BYTE_SIZE = 32;
const HASH_FUNCTION_NAME = 'sha256';
const ENCRYPTION_INFO_STRING = 'DefusePHP|V2|KeyForEncryption';
const AUTHENTICATION_INFO_STRING = 'DefusePHP|V2|KeyForAuthentication';
const BUFFER_BYTE_SIZE = 1048576;
const LEGACY_CIPHER_METHOD = 'aes-128-cbc';
const LEGACY_BLOCK_BYTE_SIZE = 16;
const LEGACY_KEY_BYTE_SIZE = 16;
const LEGACY_HASH_FUNCTION_NAME = 'sha256';
const LEGACY_MAC_BYTE_SIZE = 32;
const LEGACY_ENCRYPTION_INFO_STRING = 'DefusePHP|KeyForEncryption';
const LEGACY_AUTHENTICATION_INFO_STRING = 'DefusePHP|KeyForAuthentication';
/*
* V2.0 Format: VERSION (4 bytes) || SALT (32 bytes) || IV (16 bytes) ||
* CIPHERTEXT (varies) || HMAC (32 bytes)
*
* V1.0 Format: HMAC (32 bytes) || IV (16 bytes) || CIPHERTEXT (varies).
*/
/**
* Adds an integer to a block-sized counter.
*
* @param string $ctr
* @param int $inc
*
* @throws Ex\EnvironmentIsBrokenException
*
* @return string
*/
public static function incrementCounter($ctr, $inc)
{
if (Core::ourStrlen($ctr) !== Core::BLOCK_BYTE_SIZE) {
throw new Ex\EnvironmentIsBrokenException(
'Trying to increment a nonce of the wrong size.'
);
}
if (! \is_int($inc)) {
throw new Ex\EnvironmentIsBrokenException(
'Trying to increment nonce by a non-integer.'
);
}
if ($inc < 0) {
throw new Ex\EnvironmentIsBrokenException(
'Trying to increment nonce by a negative amount.'
);
}
if ($inc > PHP_INT_MAX - 255) {
throw new Ex\EnvironmentIsBrokenException(
'Integer overflow may occur.'
);
}
/*
* We start at the rightmost byte (big-endian)
* So, too, does OpenSSL: http://stackoverflow.com/a/3146214/2224584
*/
for ($i = Core::BLOCK_BYTE_SIZE - 1; $i >= 0; --$i) {
$sum = \ord($ctr[$i]) + $inc;
/* Detect integer overflow and fail. */
if (! \is_int($sum)) {
throw new Ex\EnvironmentIsBrokenException(
'Integer overflow in CTR mode nonce increment.'
);
}
$ctr[$i] = \pack('C', $sum & 0xFF);
$inc = $sum >> 8;
}
return $ctr;
}
/**
* Returns a random byte string of the specified length.
*
* @param int $octets
*
* @throws Ex\EnvironmentIsBrokenException
*
* @return string
*/
public static function secureRandom($octets)
{
self::ensureFunctionExists('random_bytes');
try {
return \random_bytes($octets);
} catch (\Exception $ex) {
throw new Ex\EnvironmentIsBrokenException(
'Your system does not have a secure random number generator.'
);
}
}
/**
* Computes the HKDF key derivation function specified in
* http://tools.ietf.org/html/rfc5869.
*
* @param string $hash Hash Function
* @param string $ikm Initial Keying Material
* @param int $length How many bytes?
* @param string $info What sort of key are we deriving?
* @param string $salt
*
* @throws Ex\EnvironmentIsBrokenException
*
* @return string
*/
public static function HKDF($hash, $ikm, $length, $info = '', $salt = null)
{
$digest_length = Core::ourStrlen(\hash_hmac($hash, '', '', true));
// Sanity-check the desired output length.
if (empty($length) || ! \is_int($length) ||
$length < 0 || $length > 255 * $digest_length) {
throw new Ex\EnvironmentIsBrokenException(
'Bad output length requested of HKDF.'
);
}
// "if [salt] not provided, is set to a string of HashLen zeroes."
if (\is_null($salt)) {
$salt = \str_repeat("\x00", $digest_length);
}
// HKDF-Extract:
// PRK = HMAC-Hash(salt, IKM)
// The salt is the HMAC key.
$prk = \hash_hmac($hash, $ikm, $salt, true);
// HKDF-Expand:
// This check is useless, but it serves as a reminder to the spec.
if (Core::ourStrlen($prk) < $digest_length) {
throw new Ex\EnvironmentIsBrokenException();
}
// T(0) = ''
$t = '';
$last_block = '';
for ($block_index = 1; Core::ourStrlen($t) < $length; ++$block_index) {
// T(i) = HMAC-Hash(PRK, T(i-1) | info | 0x??)
$last_block = \hash_hmac(
$hash,
$last_block . $info . \chr($block_index),
$prk,
true
);
// T = T(1) | T(2) | T(3) | ... | T(N)
$t .= $last_block;
}
// ORM = first L octets of T
$orm = Core::ourSubstr($t, 0, $length);
if ($orm === false) {
throw new Ex\EnvironmentIsBrokenException();
}
return $orm;
}
/**
* Checks if two equal-length strings are the same without leaking
* information through side channels.
*
* @param string $expected
* @param string $given
*
* @throws Ex\EnvironmentIsBrokenException
*
* @return bool
*/
public static function hashEquals($expected, $given)
{
static $native = null;
if ($native === null) {
$native = \function_exists('hash_equals');
}
if ($native) {
return \hash_equals($expected, $given);
}
// We can't just compare the strings with '==', since it would make
// timing attacks possible. We could use the XOR-OR constant-time
// comparison algorithm, but that may not be a reliable defense in an
// interpreted language. So we use the approach of HMACing both strings
// with a random key and comparing the HMACs.
// We're not attempting to make variable-length string comparison
// secure, as that's very difficult. Make sure the strings are the same
// length.
if (Core::ourStrlen($expected) !== Core::ourStrlen($given)) {
throw new Ex\EnvironmentIsBrokenException();
}
$blind = Core::secureRandom(32);
$message_compare = \hash_hmac(Core::HASH_FUNCTION_NAME, $given, $blind);
$correct_compare = \hash_hmac(Core::HASH_FUNCTION_NAME, $expected, $blind);
return $correct_compare === $message_compare;
}
/**
* Throws an exception if the constant doesn't exist.
*
* @param string $name
*
* @throws Ex\EnvironmentIsBrokenException
*/
public static function ensureConstantExists($name)
{
if (! \defined($name)) {
throw new Ex\EnvironmentIsBrokenException();
}
}
/**
* Throws an exception if the function doesn't exist.
*
* @param string $name
*
* @throws Ex\EnvironmentIsBrokenException
*/
public static function ensureFunctionExists($name)
{
if (! \function_exists($name)) {
throw new Ex\EnvironmentIsBrokenException();
}
}
/*
* We need these strlen() and substr() functions because when
* 'mbstring.func_overload' is set in php.ini, the standard strlen() and
* substr() are replaced by mb_strlen() and mb_substr().
*/
/**
* Computes the length of a string in bytes.
*
* @param string $str
*
* @throws Ex\EnvironmentIsBrokenException
*
* @return int
*/
public static function ourStrlen($str)
{
static $exists = null;
if ($exists === null) {
$exists = \function_exists('mb_strlen');
}
if ($exists) {
$length = \mb_strlen($str, '8bit');
if ($length === false) {
throw new Ex\EnvironmentIsBrokenException();
}
return $length;
} else {
return \strlen($str);
}
}
/**
* Behaves roughly like the function substr() in PHP 7 does.
*
* @param string $str
* @param int $start
* @param int $length
*
* @throws Ex\EnvironmentIsBrokenException
*
* @return string
*/
public static function ourSubstr($str, $start, $length = null)
{
static $exists = null;
if ($exists === null) {
$exists = \function_exists('mb_substr');
}
if ($exists) {
// mb_substr($str, 0, NULL, '8bit') returns an empty string on PHP
// 5.3, so we have to find the length ourselves.
if (! isset($length)) {
if ($start >= 0) {
$length = Core::ourStrlen($str) - $start;
} else {
$length = -$start;
}
}
// This is required to make mb_substr behavior identical to substr.
// Without this, mb_substr() would return false, contra to what the
// PHP documentation says (it doesn't say it can return false.)
if ($start === Core::ourStrlen($str) && $length === 0) {
return '';
}
if ($start > Core::ourStrlen($str)) {
return false;
}
$substr = \mb_substr($str, $start, $length, '8bit');
if (Core::ourStrlen($substr) !== $length) {
throw new Ex\EnvironmentIsBrokenException(
'Your version of PHP has bug #66797. Its implementation of
mb_substr() is incorrect. See the details here:
https://bugs.php.net/bug.php?id=66797'
);
}
return $substr;
}
// Unlike mb_substr(), substr() doesn't accept NULL for length
if (isset($length)) {
return \substr($str, $start, $length);
} else {
return \substr($str, $start);
}
}
/**
* Computes the PBKDF2 password-based key derivation function.
*
* The PBKDF2 function is defined in RFC 2898. Test vectors can be found in
* RFC 6070. This implementation of PBKDF2 was originally created by Taylor
* Hornby, with improvements from http://www.variations-of-shadow.com/.
*
* @param string $algorithm The hash algorithm to use. Recommended: SHA256
* @param string $password The password.
* @param string $salt A salt that is unique to the password.
* @param int $count Iteration count. Higher is better, but slower. Recommended: At least 1000.
* @param int $key_length The length of the derived key in bytes.
* @param bool $raw_output If true, the key is returned in raw binary format. Hex encoded otherwise.
*
* @throws Ex\EnvironmentIsBrokenException
*
* @return string A $key_length-byte key derived from the password and salt.
*/
public static function pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output = false)
{
// Type checks:
if (! \is_string($algorithm)) {
throw new \InvalidArgumentException(
'pbkdf2(): algorithm must be a string'
);
}
if (! \is_string($password)) {
throw new \InvalidArgumentException(
'pbkdf2(): password must be a string'
);
}
if (! \is_string($salt)) {
throw new \InvalidArgumentException(
'pbkdf2(): salt must be a string'
);
}
// Coerce strings to integers with no information loss or overflow
$count += 0;
$key_length += 0;
$algorithm = \strtolower($algorithm);
if (! \in_array($algorithm, \hash_algos(), true)) {
throw new Ex\EnvironmentIsBrokenException(
'Invalid or unsupported hash algorithm.'
);
}
// Whitelist, or we could end up with people using CRC32.
$ok_algorithms = [
'sha1', 'sha224', 'sha256', 'sha384', 'sha512',
'ripemd160', 'ripemd256', 'ripemd320', 'whirlpool',
];
if (! \in_array($algorithm, $ok_algorithms, true)) {
throw new Ex\EnvironmentIsBrokenException(
'Algorithm is not a secure cryptographic hash function.'
);
}
if ($count <= 0 || $key_length <= 0) {
throw new Ex\EnvironmentIsBrokenException(
'Invalid PBKDF2 parameters.'
);
}
if (\function_exists('hash_pbkdf2')) {
// The output length is in NIBBLES (4-bits) if $raw_output is false!
if (! $raw_output) {
$key_length = $key_length * 2;
}
return \hash_pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output);
}
$hash_length = Core::ourStrlen(\hash($algorithm, '', true));
$block_count = \ceil($key_length / $hash_length);
$output = '';
for ($i = 1; $i <= $block_count; $i++) {
// $i encoded as 4 bytes, big endian.
$last = $salt . \pack('N', $i);
// first iteration
$last = $xorsum = \hash_hmac($algorithm, $last, $password, true);
// perform the other $count - 1 iterations
for ($j = 1; $j < $count; $j++) {
$xorsum ^= ($last = \hash_hmac($algorithm, $last, $password, true));
}
$output .= $xorsum;
}
if ($raw_output) {
return Core::ourSubstr($output, 0, $key_length);
} else {
return Encoding::binToHex(Core::ourSubstr($output, 0, $key_length));
}
}
}
<?php
namespace Defuse\Crypto;
use Defuse\Crypto\Exception as Ex;
class Crypto
{
/**
* Encrypts a string with a Key.
*
* @param string $plaintext
* @param Key $key
* @param bool $raw_binary
*
* @throws Ex\EnvironmentIsBrokenException
*
* @return string
*/
public static function encrypt($plaintext, Key $key, $raw_binary = false)
{
return self::encryptInternal(
$plaintext,
KeyOrPassword::createFromKey($key),
$raw_binary
);
}
/**
* Encrypts a string with a password, using a slow key derivation function
* to make password cracking more expensive.
*
* @param string $plaintext
* @param string $password
* @param bool $raw_binary
*
* @throws Ex\EnvironmentIsBrokenException
*
* @return string
*/
public static function encryptWithPassword($plaintext, $password, $raw_binary = false)
{
return self::encryptInternal(
$plaintext,
KeyOrPassword::createFromPassword($password),
$raw_binary
);
}
/**
* Decrypts a ciphertext to a string with a Key.
*
* @param string $ciphertext
* @param Key $key
* @param bool $raw_binary
*
* @throws Ex\EnvironmentIsBrokenException
* @throws Ex\WrongKeyOrModifiedCiphertextException
*
* @return string
*/
public static function decrypt($ciphertext, Key $key, $raw_binary = false)
{
return self::decryptInternal(
$ciphertext,
KeyOrPassword::createFromKey($key),
$raw_binary
);
}
/**
* Decrypts a ciphertext to a string with a password, using a slow key
* derivation function to make password cracking more expensive.
*
* @param string $ciphertext
* @param string $password
* @param bool $raw_binary
*
* @throws Ex\EnvironmentIsBrokenException
* @throws Ex\WrongKeyOrModifiedCiphertextException
*
* @return string
*/
public static function decryptWithPassword($ciphertext, $password, $raw_binary = false)
{
return self::decryptInternal(
$ciphertext,
KeyOrPassword::createFromPassword($password),
$raw_binary
);
}
/**
* Decrypts a legacy ciphertext produced by version 1 of this library.
*
* @param string $ciphertext
* @param string $key
*
* @throws Ex\EnvironmentIsBrokenException
* @throws Ex\WrongKeyOrModifiedCiphertextException
*
* @return string
*/
public static function legacyDecrypt($ciphertext, $key)
{
RuntimeTests::runtimeTest();
// Extract the HMAC from the front of the ciphertext.
if (Core::ourStrlen($ciphertext) <= Core::LEGACY_MAC_BYTE_SIZE) {
throw new Ex\WrongKeyOrModifiedCiphertextException(
'Ciphertext is too short.'
);
}
$hmac = Core::ourSubstr($ciphertext, 0, Core::LEGACY_MAC_BYTE_SIZE);
if ($hmac === false) {
throw new Ex\EnvironmentIsBrokenException();
}
$ciphertext = Core::ourSubstr($ciphertext, Core::LEGACY_MAC_BYTE_SIZE);
if ($ciphertext === false) {
throw new Ex\EnvironmentIsBrokenException();
}
// Regenerate the same authentication sub-key.
$akey = Core::HKDF(
Core::LEGACY_HASH_FUNCTION_NAME,
$key,
Core::LEGACY_KEY_BYTE_SIZE,
Core::LEGACY_AUTHENTICATION_INFO_STRING,
null
);
if (self::verifyHMAC($hmac, $ciphertext, $akey)) {
// Regenerate the same encryption sub-key.
$ekey = Core::HKDF(
Core::LEGACY_HASH_FUNCTION_NAME,
$key,
Core::LEGACY_KEY_BYTE_SIZE,
Core::LEGACY_ENCRYPTION_INFO_STRING,
null
);
// Extract the IV from the ciphertext.
if (Core::ourStrlen($ciphertext) <= Core::LEGACY_BLOCK_BYTE_SIZE) {
throw new Ex\WrongKeyOrModifiedCiphertextException(
'Ciphertext is too short.'
);
}
$iv = Core::ourSubstr($ciphertext, 0, Core::LEGACY_BLOCK_BYTE_SIZE);
if ($iv === false) {
throw new Ex\EnvironmentIsBrokenException();
}
$ciphertext = Core::ourSubstr($ciphertext, Core::LEGACY_BLOCK_BYTE_SIZE);
if ($ciphertext === false) {
throw new Ex\EnvironmentIsBrokenException();
}
// Do the decryption.
$plaintext = self::plainDecrypt($ciphertext, $ekey, $iv, Core::LEGACY_CIPHER_METHOD);
return $plaintext;
} else {
throw new Ex\WrongKeyOrModifiedCiphertextException(
'Integrity check failed.'
);
}
}
/**
* Encrypts a string with either a key or a password.
*
* @param string $plaintext
* @param KeyOrPassword $secret
* @param bool $raw_binary
*
* @return string
*/
private static function encryptInternal($plaintext, KeyOrPassword $secret, $raw_binary)
{
RuntimeTests::runtimeTest();
$salt = Core::secureRandom(Core::SALT_BYTE_SIZE);
$keys = $secret->deriveKeys($salt);
$ekey = $keys->getEncryptionKey();
$akey = $keys->getAuthenticationKey();
$iv = Core::secureRandom(Core::BLOCK_BYTE_SIZE);
$ciphertext = Core::CURRENT_VERSION . $salt . $iv . self::plainEncrypt($plaintext, $ekey, $iv);
$auth = \hash_hmac(Core::HASH_FUNCTION_NAME, $ciphertext, $akey, true);
$ciphertext = $ciphertext . $auth;
if ($raw_binary) {
return $ciphertext;
}
return Encoding::binToHex($ciphertext);
}
/**
* Decrypts a ciphertext to a string with either a key or a password.
*
* @param string $ciphertext
* @param KeyOrPassword $secret
* @param bool $raw_binary
*
* @throws Ex\EnvironmentIsBrokenException
* @throws Ex\WrongKeyOrModifiedCiphertextException
*
* @return string
*/
private static function decryptInternal($ciphertext, KeyOrPassword $secret, $raw_binary)
{
RuntimeTests::runtimeTest();
if (! $raw_binary) {
try {
$ciphertext = Encoding::hexToBin($ciphertext);
} catch (Ex\BadFormatException $ex) {
throw new Ex\WrongKeyOrModifiedCiphertextException(
'Ciphertext has invalid hex encoding.'
);
}
}
if (Core::ourStrlen($ciphertext) < Core::MINIMUM_CIPHERTEXT_SIZE) {
throw new Ex\WrongKeyOrModifiedCiphertextException(
'Ciphertext is too short.'
);
}
// Get and check the version header.
$header = Core::ourSubstr($ciphertext, 0, Core::HEADER_VERSION_SIZE);
if ($header !== Core::CURRENT_VERSION) {
throw new Ex\WrongKeyOrModifiedCiphertextException(
'Bad version header.'
);
}
// Get the salt.
$salt = Core::ourSubstr(
$ciphertext,
Core::HEADER_VERSION_SIZE,
Core::SALT_BYTE_SIZE
);
if ($salt === false) {
throw new Ex\EnvironmentIsBrokenException();
}
// Get the IV.
$iv = Core::ourSubstr(
$ciphertext,
Core::HEADER_VERSION_SIZE + Core::SALT_BYTE_SIZE,
Core::BLOCK_BYTE_SIZE
);
if ($iv === false) {
throw new Ex\EnvironmentIsBrokenException();
}
// Get the HMAC.
$hmac = Core::ourSubstr(
$ciphertext,
Core::ourStrlen($ciphertext) - Core::MAC_BYTE_SIZE,
Core::MAC_BYTE_SIZE
);
if ($hmac === false) {
throw new Ex\EnvironmentIsBrokenException();
}
// Get the actual encrypted ciphertext.
$encrypted = Core::ourSubstr(
$ciphertext,
Core::HEADER_VERSION_SIZE + Core::SALT_BYTE_SIZE +
Core::BLOCK_BYTE_SIZE,
Core::ourStrlen($ciphertext) - Core::MAC_BYTE_SIZE - Core::SALT_BYTE_SIZE -
Core::BLOCK_BYTE_SIZE - Core::HEADER_VERSION_SIZE
);
if ($encrypted === false) {
throw new Ex\EnvironmentIsBrokenException();
}
// Derive the separate encryption and authentication keys from the key
// or password, whichever it is.
$keys = $secret->deriveKeys($salt);
if (self::verifyHMAC($hmac, $header . $salt . $iv . $encrypted, $keys->getAuthenticationKey())) {
$plaintext = self::plainDecrypt($encrypted, $keys->getEncryptionKey(), $iv, Core::CIPHER_METHOD);
return $plaintext;
} else {
throw new Ex\WrongKeyOrModifiedCiphertextException(
'Integrity check failed.'
);
}
}
/**
* Raw unauthenticated encryption (insecure on its own).
*
* @param string $plaintext
* @param string $key
* @param string $iv
*
* @throws Ex\EnvironmentIsBrokenException
*
* @return string
*/
protected static function plainEncrypt($plaintext, $key, $iv)
{
Core::ensureConstantExists('OPENSSL_RAW_DATA');
Core::ensureFunctionExists('openssl_encrypt');
$ciphertext = \openssl_encrypt(
$plaintext,
Core::CIPHER_METHOD,
$key,
OPENSSL_RAW_DATA,
$iv
);
if ($ciphertext === false) {
throw new Ex\EnvironmentIsBrokenException(
'openssl_encrypt() failed.'
);
}
return $ciphertext;
}
/**
* Raw unauthenticated decryption (insecure on its own).
*
* @param string $ciphertext
* @param string $key
* @param string $iv
* @param string $cipherMethod
*
* @throws Ex\EnvironmentIsBrokenException
*
* @return string
*/
protected static function plainDecrypt($ciphertext, $key, $iv, $cipherMethod)
{
Core::ensureConstantExists('OPENSSL_RAW_DATA');
Core::ensureFunctionExists('openssl_decrypt');
$plaintext = \openssl_decrypt(
$ciphertext,
$cipherMethod,
$key,
OPENSSL_RAW_DATA,
$iv
);
if ($plaintext === false) {
throw new Ex\EnvironmentIsBrokenException(
'openssl_decrypt() failed.'
);
}
return $plaintext;
}
/**
* Verifies an HMAC without leaking information through side-channels.
*
* @param string $correct_hmac
* @param string $message
* @param string $key
*
* @throws Ex\EnvironmentIsBrokenException
*
* @return bool
*/
protected static function verifyHMAC($correct_hmac, $message, $key)
{
$message_hmac = \hash_hmac(Core::HASH_FUNCTION_NAME, $message, $key, true);
return Core::hashEquals($correct_hmac, $message_hmac);
}
}
<?php
namespace Defuse\Crypto;
final class DerivedKeys
{
private $akey = null;
private $ekey = null;
/**
* Returns the authentication key.
*/
public function getAuthenticationKey()
{
return $this->akey;
}
/**
* Returns the encryption key.
*/
public function getEncryptionKey()
{
return $this->ekey;
}
/**
* Constructor for DerivedKeys.
*
* @param string $akey
* @param string $ekey
*/
public function __construct($akey, $ekey)
{
$this->akey = $akey;
$this->ekey = $ekey;
}
}
<?php
namespace Defuse\Crypto;
use Defuse\Crypto\Exception as Ex;
final class Encoding
{
const CHECKSUM_BYTE_SIZE = 32;
const CHECKSUM_HASH_ALGO = 'sha256';
const SERIALIZE_HEADER_BYTES = 4;
/**
* Converts a byte string to a hexadecimal string without leaking
* information through side channels.
*
* @param string $byte_string
*
* @throws Ex\EnvironmentIsBrokenException
*
* @return string
*/
public static function binToHex($byte_string)
{
$hex = '';
$len = Core::ourStrlen($byte_string);
for ($i = 0; $i < $len; ++$i) {
$c = \ord($byte_string[$i]) & 0xf;
$b = \ord($byte_string[$i]) >> 4;
$hex .= \pack(
'CC',
87 + $b + ((($b - 10) >> 8) & ~38),
87 + $c + ((($c - 10) >> 8) & ~38)
);
}
return $hex;
}
/**
* Converts a hexadecimal string into a byte string without leaking
* information through side channels.
*
* @param string $hex_string
*
* @throws Ex\BadFormatException
* @throws Ex\EnvironmentIsBrokenException
*
* @return string
*/
public static function hexToBin($hex_string)
{
$hex_pos = 0;
$bin = '';
$hex_len = Core::ourStrlen($hex_string);
$state = 0;
$c_acc = 0;
while ($hex_pos < $hex_len) {
$c = \ord($hex_string[$hex_pos]);
$c_num = $c ^ 48;
$c_num0 = ($c_num - 10) >> 8;
$c_alpha = ($c & ~32) - 55;
$c_alpha0 = (($c_alpha - 10) ^ ($c_alpha - 16)) >> 8;
if (($c_num0 | $c_alpha0) === 0) {
throw new Ex\BadFormatException(
'Encoding::hexToBin() input is not a hex string.'
);
}
$c_val = ($c_num0 & $c_num) | ($c_alpha & $c_alpha0);
if ($state === 0) {
$c_acc = $c_val * 16;
} else {
$bin .= \pack('C', $c_acc | $c_val);
}
$state ^= 1;
++$hex_pos;
}
return $bin;
}
/*
* SECURITY NOTE ON APPLYING CHECKSUMS TO SECRETS:
*
* The checksum introduces a potential security weakness. For example,
* suppose we apply a checksum to a key, and that an adversary has an
* exploit against the process containing the key, such that they can
* overwrite an arbitrary byte of memory and then cause the checksum to
* be verified and learn the result.
*
* In this scenario, the adversary can extract the key one byte at
* a time by overwriting it with their guess of its value and then
* asking if the checksum matches. If it does, their guess was right.
* This kind of attack may be more easy to implement and more reliable
* than a remote code execution attack.
*
* This attack also applies to authenticated encryption as a whole, in
* the situation where the adversary can overwrite a byte of the key
* and then cause a valid ciphertext to be decrypted, and then
* determine whether the MAC check passed or failed.
*
* By using the full SHA256 hash instead of truncating it, I'm ensuring
* that both ways of going about the attack are equivalently difficult.
* A shorter checksum of say 32 bits might be more useful to the
* adversary as an oracle in case their writes are coarser grained.
*
* Because the scenario assumes a serious vulnerability, we don't try
* to prevent attacks of this style.
*/
/**
* INTERNAL USE ONLY: Applies a version header, applies a checksum, and
* then encodes a byte string into a range of printable ASCII characters.
*
* @param string $header
* @param string $bytes
*
* @throws Ex\EnvironmentIsBrokenException
*
* @return string
*/
public static function saveBytesToChecksummedAsciiSafeString($header, $bytes)
{
// Headers must be a constant length to prevent one type's header from
// being a prefix of another type's header, leading to ambiguity.
if (Core::ourStrlen($header) !== self::SERIALIZE_HEADER_BYTES) {
throw new Ex\EnvironmentIsBrokenException(
'Header must be ' . self::SERIALIZE_HEADER_BYTES . ' bytes.'
);
}
return Encoding::binToHex(
$header .
$bytes .
\hash(
self::CHECKSUM_HASH_ALGO,
$header . $bytes,
true
)
);
}
/**
* INTERNAL USE ONLY: Decodes, verifies the header and checksum, and returns
* the encoded byte string.
*
* @param string $expected_header
* @param string $string
*
* @throws Ex\EnvironmentIsBrokenException
* @throws Ex\BadFormatException
*
* @return string
*/
public static function loadBytesFromChecksummedAsciiSafeString($expected_header, $string)
{
// Headers must be a constant length to prevent one type's header from
// being a prefix of another type's header, leading to ambiguity.
if (Core::ourStrlen($expected_header) !== self::SERIALIZE_HEADER_BYTES) {
throw new Ex\EnvironmentIsBrokenException(
'Header must be 4 bytes.'
);
}
$bytes = Encoding::hexToBin($string);
/* Make sure we have enough bytes to get the version header and checksum. */
if (Core::ourStrlen($bytes) < self::SERIALIZE_HEADER_BYTES + self::CHECKSUM_BYTE_SIZE) {
throw new Ex\BadFormatException(
'Encoded data is shorter than expected.'
);
}
/* Grab the version header. */
$actual_header = Core::ourSubstr($bytes, 0, self::SERIALIZE_HEADER_BYTES);
if ($actual_header !== $expected_header) {
throw new Ex\BadFormatException(
'Invalid header.'
);
}
/* Grab the bytes that are part of the checksum. */
$checked_bytes = Core::ourSubstr(
$bytes,
0,
Core::ourStrlen($bytes) - self::CHECKSUM_BYTE_SIZE
);
/* Grab the included checksum. */
$checksum_a = Core::ourSubstr(
$bytes,
Core::ourStrlen($bytes) - self::CHECKSUM_BYTE_SIZE,
self::CHECKSUM_BYTE_SIZE
);
/* Re-compute the checksum. */
$checksum_b = \hash(self::CHECKSUM_HASH_ALGO, $checked_bytes, true);
/* Check if the checksum matches. */
if (! Core::hashEquals($checksum_a, $checksum_b)) {
throw new Ex\BadFormatException(
"Data is corrupted, the checksum doesn't match"
);
}
return Core::ourSubstr(
$bytes,
self::SERIALIZE_HEADER_BYTES,
Core::ourStrlen($bytes) - self::SERIALIZE_HEADER_BYTES - self::CHECKSUM_BYTE_SIZE
);
}
}
<?php
namespace Defuse\Crypto\Exception;
class BadFormatException extends \Defuse\Crypto\Exception\CryptoException
{
}
<?php
namespace Defuse\Crypto\Exception;
class CryptoException extends \Exception
{
}
<?php
namespace Defuse\Crypto\Exception;
class EnvironmentIsBrokenException extends \Defuse\Crypto\Exception\CryptoException
{
}
<?php
namespace Defuse\Crypto\Exception;
class IOException extends \Defuse\Crypto\Exception\CryptoException
{
}
<?php
namespace Defuse\Crypto\Exception;
class WrongKeyOrModifiedCiphertextException extends \Defuse\Crypto\Exception\CryptoException
{
}
<?php
namespace Defuse\Crypto;
use Defuse\Crypto\Exception as Ex;
final class File
{
/**
* Encrypts the input file, saving the ciphertext to the output file.
*
* @param string $inputFilename
* @param string $outputFilename
* @param Key $key
*
* @throws Ex\EnvironmentIsBrokenException
* @throws Ex\IOException
*/
public static function encryptFile($inputFilename, $outputFilename, Key $key)
{
self::encryptFileInternal(
$inputFilename,
$outputFilename,
KeyOrPassword::createFromKey($key)
);
}
/**
* Encrypts a file with a password, using a slow key derivation function to
* make password cracking more expensive.
*
* @param string $inputFilename
* @param string $outputFilename
* @param string $password
*
* @throws Ex\EnvironmentIsBrokenException
* @throws Ex\IOException
*/
public static function encryptFileWithPassword($inputFilename, $outputFilename, $password)
{
self::encryptFileInternal(
$inputFilename,
$outputFilename,
KeyOrPassword::createFromPassword($password)
);
}
/**
* Decrypts the input file, saving the plaintext to the output file.
*
* @param string $inputFilename
* @param string $outputFilename
* @param Key $key
*
* @throws Ex\EnvironmentIsBrokenException
* @throws Ex\IOException
* @throws Ex\WrongKeyOrModifiedCiphertextException
*/
public static function decryptFile($inputFilename, $outputFilename, Key $key)
{
self::decryptFileInternal(
$inputFilename,
$outputFilename,
KeyOrPassword::createFromKey($key)
);
}
/**
* Decrypts a file with a password, using a slow key derivation function to
* make password cracking more expensive.
*
* @param string $inputFilename
* @param string $outputFilename
* @param string $password
*
* @throws Ex\EnvironmentIsBrokenException
* @throws Ex\IOException
* @throws Ex\WrongKeyOrModifiedCiphertextException
*/
public static function decryptFileWithPassword($inputFilename, $outputFilename, $password)
{
self::decryptFileInternal(
$inputFilename,
$outputFilename,
KeyOrPassword::createFromPassword($password)
);
}
/**
* Takes two resource handles and encrypts the contents of the first,
* writing the ciphertext into the second.
*
* @param resource $inputHandle
* @param resource $outputHandle
* @param Key $key
*
* @throws Ex\EnvironmentIsBrokenException
* @throws Ex\WrongKeyOrModifiedCiphertextException
*/
public static function encryptResource($inputHandle, $outputHandle, Key $key)
{
self::encryptResourceInternal(
$inputHandle,
$outputHandle,
KeyOrPassword::createFromKey($key)
);
}
/**
* Encrypts the contents of one resource handle into another with a
* password, using a slow key derivation function to make password cracking
* more expensive.
*
* @param resource $inputHandle
* @param resource $outputHandle
* @param string $password
*
* @throws Ex\EnvironmentIsBrokenException
* @throws Ex\IOException
* @throws Ex\WrongKeyOrModifiedCiphertextException
*/
public static function encryptResourceWithPassword($inputHandle, $outputHandle, $password)
{
self::encryptResourceInternal(
$inputHandle,
$outputHandle,
KeyOrPassword::createFromPassword($password)
);
}
/**
* Takes two resource handles and decrypts the contents of the first,
* writing the plaintext into the second.
*
* @param resource $inputHandle
* @param resource $outputHandle
* @param Key $key
*
* @throws Ex\EnvironmentIsBrokenException
* @throws Ex\IOException
* @throws Ex\WrongKeyOrModifiedCiphertextException
*/
public static function decryptResource($inputHandle, $outputHandle, Key $key)
{
self::decryptResourceInternal(
$inputHandle,
$outputHandle,
KeyOrPassword::createFromKey($key)
);
}
/**
* Decrypts the contents of one resource into another with a password, using
* a slow key derivation function to make password cracking more expensive.
*
* @param resource $inputHandle
* @param resource $outputHandle
* @param string $password
*
* @throws Ex\EnvironmentIsBrokenException
* @throws Ex\IOException
* @throws Ex\WrongKeyOrModifiedCiphertextException
*/
public static function decryptResourceWithPassword($inputHandle, $outputHandle, $password)
{
self::decryptResourceInternal(
$inputHandle,
$outputHandle,
KeyOrPassword::createFromPassword($password)
);
}
/**
* Encrypts a file with either a key or a password.
*
* @param string $inputFilename
* @param string $outputFilename
* @param KeyOrPassword $secret
*
* @throws Ex\CryptoException
* @throws Ex\IOException
*/
private static function encryptFileInternal($inputFilename, $outputFilename, KeyOrPassword $secret)
{
/* Open the input file. */
$if = @\fopen($inputFilename, 'rb');
if ($if === false) {
throw new Ex\IOException(
'Cannot open input file for encrypting: ' .
self::getLastErrorMessage()
);
}
if (\is_callable('\\stream_set_read_buffer')) {
/* This call can fail, but the only consequence is performance. */
\stream_set_read_buffer($if, 0);
}
/* Open the output file. */
$of = @\fopen($outputFilename, 'wb');
if ($of === false) {
\fclose($if);
throw new Ex\IOException(
'Cannot open output file for encrypting: ' .
self::getLastErrorMessage()
);
}
if (\is_callable('\\stream_set_write_buffer')) {
/* This call can fail, but the only consequence is performance. */
\stream_set_write_buffer($of, 0);
}
/* Perform the encryption. */
try {
self::encryptResourceInternal($if, $of, $secret);
} catch (Ex\CryptoException $ex) {
\fclose($if);
\fclose($of);
throw $ex;
}
/* Close the input file. */
if (\fclose($if) === false) {
\fclose($of);
throw new Ex\IOException(
'Cannot close input file after encrypting'
);
}
/* Close the output file. */
if (\fclose($of) === false) {
throw new Ex\IOException(
'Cannot close output file after encrypting'
);
}
}
/**
* Decrypts a file with either a key or a password.
*
* @param string $inputFilename
* @param string $outputFilename
* @param KeyOrPassword $secret
*
* @throws Ex\CryptoException
* @throws Ex\IOException
*/
private static function decryptFileInternal($inputFilename, $outputFilename, KeyOrPassword $secret)
{
/* Open the input file. */
$if = @\fopen($inputFilename, 'rb');
if ($if === false) {
throw new Ex\IOException(
'Cannot open input file for decrypting: ' .
self::getLastErrorMessage()
);
}
if (\is_callable('\\stream_set_read_buffer')) {
/* This call can fail, but the only consequence is performance. */
\stream_set_read_buffer($if, 0);
}
/* Open the output file. */
$of = @\fopen($outputFilename, 'wb');
if ($of === false) {
\fclose($if);
throw new Ex\IOException(
'Cannot open output file for decrypting: ' .
self::getLastErrorMessage()
);
}
if (\is_callable('\\stream_set_write_buffer')) {
/* This call can fail, but the only consequence is performance. */
\stream_set_write_buffer($of, 0);
}
/* Perform the decryption. */
try {
self::decryptResourceInternal($if, $of, $secret);
} catch (Ex\CryptoException $ex) {
\fclose($if);
\fclose($of);
throw $ex;
}
/* Close the input file. */
if (\fclose($if) === false) {
\fclose($of);
throw new Ex\IOException(
'Cannot close input file after decrypting'
);
}
/* Close the output file. */
if (\fclose($of) === false) {
throw new Ex\IOException(
'Cannot close output file after decrypting'
);
}
}
/**
* Encrypts a resource with either a key or a password.
*
* @param resource $inputHandle
* @param resource $outputHandle
* @param KeyOrPassword $secret
*
* @throws Ex\EnvironmentIsBrokenException
* @throws Ex\IOException
*/
private static function encryptResourceInternal($inputHandle, $outputHandle, KeyOrPassword $secret)
{
if (! \is_resource($inputHandle)) {
throw new Ex\IOException(
'Input handle must be a resource!'
);
}
if (! \is_resource($outputHandle)) {
throw new Ex\IOException(
'Output handle must be a resource!'
);
}
$inputStat = \fstat($inputHandle);
$inputSize = $inputStat['size'];
$file_salt = Core::secureRandom(Core::SALT_BYTE_SIZE);
$keys = $secret->deriveKeys($file_salt);
$ekey = $keys->getEncryptionKey();
$akey = $keys->getAuthenticationKey();
$ivsize = Core::BLOCK_BYTE_SIZE;
$iv = Core::secureRandom($ivsize);
/* Initialize a streaming HMAC state. */
$hmac = \hash_init(Core::HASH_FUNCTION_NAME, HASH_HMAC, $akey);
if ($hmac === false) {
throw new Ex\EnvironmentIsBrokenException(
'Cannot initialize a hash context'
);
}
/* Write the header, salt, and IV. */
self::writeBytes(
$outputHandle,
Core::CURRENT_VERSION . $file_salt . $iv,
Core::HEADER_VERSION_SIZE + Core::SALT_BYTE_SIZE + $ivsize
);
/* Add the header, salt, and IV to the HMAC. */
\hash_update($hmac, Core::CURRENT_VERSION);
\hash_update($hmac, $file_salt);
\hash_update($hmac, $iv);
/* $thisIv will be incremented after each call to the encryption. */
$thisIv = $iv;
/* How many blocks do we encrypt at a time? We increment by this value. */
$inc = Core::BUFFER_BYTE_SIZE / Core::BLOCK_BYTE_SIZE;
/* Loop until we reach the end of the input file. */
$at_file_end = false;
while (! (\feof($inputHandle) || $at_file_end)) {
/* Find out if we can read a full buffer, or only a partial one. */
$pos = \ftell($inputHandle);
if ($pos === false) {
throw new Ex\IOException(
'Could not get current position in input file during encryption'
);
}
if ($pos + Core::BUFFER_BYTE_SIZE >= $inputSize) {
/* We're at the end of the file, so we need to break out of the loop. */
$at_file_end = true;
$read = self::readBytes(
$inputHandle,
$inputSize - $pos
);
} else {
$read = self::readBytes(
$inputHandle,
Core::BUFFER_BYTE_SIZE
);
}
/* Encrypt this buffer. */
$encrypted = \openssl_encrypt(
$read,
Core::CIPHER_METHOD,
$ekey,
OPENSSL_RAW_DATA,
$thisIv
);
if ($encrypted === false) {
throw new Ex\EnvironmentIsBrokenException(
'OpenSSL encryption error'
);
}
/* Write this buffer's ciphertext. */
self::writeBytes($outputHandle, $encrypted, Core::ourStrlen($encrypted));
/* Add this buffer's ciphertext to the HMAC. */
\hash_update($hmac, $encrypted);
/* Increment the counter by the number of blocks in a buffer. */
$thisIv = Core::incrementCounter($thisIv, $inc);
/* WARNING: Usually, unless the file is a multiple of the buffer
* size, $thisIv will contain an incorrect value here on the last
* iteration of this loop. */
}
/* Get the HMAC and append it to the ciphertext. */
$final_mac = \hash_final($hmac, true);
self::writeBytes($outputHandle, $final_mac, Core::MAC_BYTE_SIZE);
}
/**
* Decrypts a file-backed resource with either a key or a password.
*
* @param resource $inputHandle
* @param resource $outputHandle
* @param KeyOrPassword $secret
*
* @throws Ex\EnvironmentIsBrokenException
* @throws Ex\IOException
* @throws Ex\WrongKeyOrModifiedCiphertextException
*/
public static function decryptResourceInternal($inputHandle, $outputHandle, KeyOrPassword $secret)
{
if (! \is_resource($inputHandle)) {
throw new Ex\IOException(
'Input handle must be a resource!'
);
}
if (! \is_resource($outputHandle)) {
throw new Ex\IOException(
'Output handle must be a resource!'
);
}
/* Make sure the file is big enough for all the reads we need to do. */
$stat = \fstat($inputHandle);
if ($stat['size'] < Core::MINIMUM_CIPHERTEXT_SIZE) {
throw new Ex\WrongKeyOrModifiedCiphertextException(
'Input file is too small to have been created by this library.'
);
}
/* Check the version header. */
$header = self::readBytes($inputHandle, Core::HEADER_VERSION_SIZE);
if ($header !== Core::CURRENT_VERSION) {
throw new Ex\WrongKeyOrModifiedCiphertextException(
'Bad version header.'
);
}
/* Get the salt. */
$file_salt = self::readBytes($inputHandle, Core::SALT_BYTE_SIZE);
/* Get the IV. */
$ivsize = Core::BLOCK_BYTE_SIZE;
$iv = self::readBytes($inputHandle, $ivsize);
/* Derive the authentication and encryption keys. */
$keys = $secret->deriveKeys($file_salt);
$ekey = $keys->getEncryptionKey();
$akey = $keys->getAuthenticationKey();
/* We'll store the MAC of each buffer-sized chunk as we verify the
* actual MAC, so that we can check them again when decrypting. */
$macs = [];
/* $thisIv will be incremented after each call to the decryption. */
$thisIv = $iv;
/* How many blocks do we encrypt at a time? We increment by this value. */
$inc = Core::BUFFER_BYTE_SIZE / Core::BLOCK_BYTE_SIZE;
/* Get the HMAC. */
if (\fseek($inputHandle, (-1 * Core::MAC_BYTE_SIZE), SEEK_END) === false) {
throw new Ex\IOException(
'Cannot seek to beginning of MAC within input file'
);
}
/* Get the position of the last byte in the actual ciphertext. */
$cipher_end = \ftell($inputHandle);
if ($cipher_end === false) {
throw new Ex\IOException(
'Cannot read input file'
);
}
/* We have the position of the first byte of the HMAC. Go back by one. */
--$cipher_end;
/* Read the HMAC. */
$stored_mac = self::readBytes($inputHandle, Core::MAC_BYTE_SIZE);
/* Initialize a streaming HMAC state. */
$hmac = \hash_init(Core::HASH_FUNCTION_NAME, HASH_HMAC, $akey);
if ($hmac === false) {
throw new Ex\EnvironmentIsBrokenException(
'Cannot initialize a hash context'
);
}
/* Reset file pointer to the beginning of the file after the header */
if (\fseek($inputHandle, Core::HEADER_VERSION_SIZE, SEEK_SET) === false) {
throw new Ex\IOException(
'Cannot read seek within input file'
);
}
/* Seek to the start of the actual ciphertext. */
if (\fseek($inputHandle, Core::SALT_BYTE_SIZE + $ivsize, SEEK_CUR) === false) {
throw new Ex\IOException(
'Cannot seek input file to beginning of ciphertext'
);
}
/* PASS #1: Calculating the HMAC. */
\hash_update($hmac, $header);
\hash_update($hmac, $file_salt);
\hash_update($hmac, $iv);
$hmac2 = \hash_copy($hmac);
$break = false;
while (! $break) {
$pos = \ftell($inputHandle);
if ($pos === false) {
throw new Ex\IOException(
'Could not get current position in input file during decryption'
);
}
/* Read the next buffer-sized chunk (or less). */
if ($pos + Core::BUFFER_BYTE_SIZE >= $cipher_end) {
$break = true;
$read = self::readBytes(
$inputHandle,
$cipher_end - $pos + 1
);
} else {
$read = self::readBytes(
$inputHandle,
Core::BUFFER_BYTE_SIZE
);
}
/* Update the HMAC. */
\hash_update($hmac, $read);
/* Remember this buffer-sized chunk's HMAC. */
$chunk_mac = \hash_copy($hmac);
if ($chunk_mac === false) {
throw new Ex\EnvironmentIsBrokenException(
'Cannot duplicate a hash context'
);
}
$macs []= \hash_final($chunk_mac);
}
/* Get the final HMAC, which should match the stored one. */
$final_mac = \hash_final($hmac, true);
/* Verify the HMAC. */
if (! Core::hashEquals($final_mac, $stored_mac)) {
throw new Ex\WrongKeyOrModifiedCiphertextException(
'Integrity check failed.'
);
}
/* PASS #2: Decrypt and write output. */
/* Rewind to the start of the actual ciphertext. */
if (\fseek($inputHandle, Core::SALT_BYTE_SIZE + $ivsize + Core::HEADER_VERSION_SIZE, SEEK_SET) === false) {
throw new Ex\IOException(
'Could not move the input file pointer during decryption'
);
}
$at_file_end = false;
while (! $at_file_end) {
$pos = \ftell($inputHandle);
if ($pos === false) {
throw new Ex\IOException(
'Could not get current position in input file during decryption'
);
}
/* Read the next buffer-sized chunk (or less). */
if ($pos + Core::BUFFER_BYTE_SIZE >= $cipher_end) {
$at_file_end = true;
$read = self::readBytes(
$inputHandle,
$cipher_end - $pos + 1
);
} else {
$read = self::readBytes(
$inputHandle,
Core::BUFFER_BYTE_SIZE
);
}
/* Recalculate the MAC (so far) and compare it with the one we
* remembered from pass #1 to ensure attackers didn't change the
* ciphertext after MAC verification. */
\hash_update($hmac2, $read);
$calc_mac = \hash_copy($hmac2);
if ($calc_mac === false) {
throw new Ex\EnvironmentIsBrokenException(
'Cannot duplicate a hash context'
);
}
$calc = \hash_final($calc_mac);
if (empty($macs)) {
throw new Ex\WrongKeyOrModifiedCiphertextException(
'File was modified after MAC verification'
);
} elseif (! Core::hashEquals(\array_shift($macs), $calc)) {
throw new Ex\WrongKeyOrModifiedCiphertextException(
'File was modified after MAC verification'
);
}
/* Decrypt this buffer-sized chunk. */
$decrypted = \openssl_decrypt(
$read,
Core::CIPHER_METHOD,
$ekey,
OPENSSL_RAW_DATA,
$thisIv
);
if ($decrypted === false) {
throw new Ex\EnvironmentIsBrokenException(
'OpenSSL decryption error'
);
}
/* Write the plaintext to the output file. */
self::writeBytes(
$outputHandle,
$decrypted,
Core::ourStrlen($decrypted)
);
/* Increment the IV by the amount of blocks in a buffer. */
$thisIv = Core::incrementCounter($thisIv, $inc);
/* WARNING: Usually, unless the file is a multiple of the buffer
* size, $thisIv will contain an incorrect value here on the last
* iteration of this loop. */
}
}
/**
* Read from a stream; prevent partial reads.
*
* @param resource $stream
* @param int $num_bytes
*
* @throws Ex\IOException
* @throws Ex\EnvironmentIsBrokenException
*
* @return string
*/
public static function readBytes($stream, $num_bytes)
{
if ($num_bytes < 0) {
throw new Ex\EnvironmentIsBrokenException(
'Tried to read less than 0 bytes'
);
} elseif ($num_bytes === 0) {
return '';
}
$buf = '';
$remaining = $num_bytes;
while ($remaining > 0 && ! \feof($stream)) {
$read = \fread($stream, $remaining);
if ($read === false) {
throw new Ex\IOException(
'Could not read from the file'
);
}
$buf .= $read;
$remaining -= Core::ourStrlen($read);
}
if (Core::ourStrlen($buf) !== $num_bytes) {
throw new Ex\IOException(
'Tried to read past the end of the file'
);
}
return $buf;
}
/**
* Write to a stream; prevents partial writes.
*
* @param resource $stream
* @param string $buf
* @param int $num_bytes
*
* @throws Ex\IOException
*
* @return string
*/
public static function writeBytes($stream, $buf, $num_bytes = null)
{
$bufSize = Core::ourStrlen($buf);
if ($num_bytes === null) {
$num_bytes = $bufSize;
}
if ($num_bytes > $bufSize) {
throw new Ex\IOException(
'Trying to write more bytes than the buffer contains.'
);
}
if ($num_bytes < 0) {
throw new Ex\IOException(
'Tried to write less than 0 bytes'
);
}
$remaining = $num_bytes;
while ($remaining > 0) {
$written = \fwrite($stream, $buf, $remaining);
if ($written === false) {
throw new Ex\IOException(
'Could not write to the file'
);
}
$buf = Core::ourSubstr($buf, $written, null);
$remaining -= $written;
}
return $num_bytes;
}
/**
* Returns the last PHP error's or warning's message string.
*
* @return string
*/
private static function getLastErrorMessage()
{
$error = error_get_last();
if ($error === null) {
return '[no PHP error]';
} else {
return $error['message'];
}
}
}
<?php
namespace Defuse\Crypto;
use Defuse\Crypto\Exception as Ex;
final class Key
{
const KEY_CURRENT_VERSION = "\xDE\xF0\x00\x00";
const KEY_BYTE_SIZE = 32;
private $key_bytes = null;
/**
* Creates new random key.
*
* @throws Ex\EnvironmentIsBrokenException
*
* @return Key
*/
public static function createNewRandomKey()
{
return new Key(Core::secureRandom(self::KEY_BYTE_SIZE));
}
/**
* Loads a Key from its encoded form.
*
* @param string $saved_key_string
*
* @throws Ex\BadFormatException
* @throws Ex\EnvironmentIsBrokenException
*
* @return Key
*/
public static function loadFromAsciiSafeString($saved_key_string)
{
$key_bytes = Encoding::loadBytesFromChecksummedAsciiSafeString(self::KEY_CURRENT_VERSION, $saved_key_string);
return new Key($key_bytes);
}
/**
* Encodes the Key into a string of printable ASCII characters.
*
* @throws Ex\EnvironmentIsBrokenException
*
* @return string
*/
public function saveToAsciiSafeString()
{
return Encoding::saveBytesToChecksummedAsciiSafeString(
self::KEY_CURRENT_VERSION,
$this->key_bytes
);
}
/**
* Gets the raw bytes of the key.
*
* @return string
*/
public function getRawBytes()
{
return $this->key_bytes;
}
/**
* Constructs a new Key object from a string of raw bytes.
*
* @param string $bytes
*
* @throws Ex\EnvironmentIsBrokenException
*/
private function __construct($bytes)
{
if (Core::ourStrlen($bytes) !== self::KEY_BYTE_SIZE) {
throw new Ex\EnvironmentIsBrokenException(
'Bad key length.'
);
}
$this->key_bytes = $bytes;
}
}
<?php
namespace Defuse\Crypto;
use Defuse\Crypto\Exception as Ex;
final class KeyOrPassword
{
const PBKDF2_ITERATIONS = 100000;
const SECRET_TYPE_KEY = 1;
const SECRET_TYPE_PASSWORD = 2;
private $secret_type = null;
private $secret = null;
/**
* Initializes an instance of KeyOrPassword from a key.
*
* @param Key $key
*
* @return KeyOrPassword
*/
public static function createFromKey(Key $key)
{
return new KeyOrPassword(self::SECRET_TYPE_KEY, $key);
}
/**
* Initializes an instance of KeyOrPassword from a password.
*
* @param string $password
*
* @return KeyOrPassword
*/
public static function createFromPassword($password)
{
return new KeyOrPassword(self::SECRET_TYPE_PASSWORD, $password);
}
/**
* Derives authentication and encryption keys from the secret, using a slow
* key derivation function if the secret is a password.
*
* @param string $salt
*
* @throws Ex\EnvironmentIsBrokenException
*
* @return DerivedKeys
*/
public function deriveKeys($salt)
{
if (Core::ourStrlen($salt) !== Core::SALT_BYTE_SIZE) {
throw new Ex\EnvironmentIsBrokenException('Bad salt.');
}
if ($this->secret_type === self::SECRET_TYPE_KEY) {
$akey = Core::HKDF(
Core::HASH_FUNCTION_NAME,
$this->secret->getRawBytes(),
Core::KEY_BYTE_SIZE,
Core::AUTHENTICATION_INFO_STRING,
$salt
);
$ekey = Core::HKDF(
Core::HASH_FUNCTION_NAME,
$this->secret->getRawBytes(),
Core::KEY_BYTE_SIZE,
Core::ENCRYPTION_INFO_STRING,
$salt
);
return new DerivedKeys($akey, $ekey);
} elseif ($this->secret_type === self::SECRET_TYPE_PASSWORD) {
/* Our PBKDF2 polyfill is vulnerable to a DoS attack documented in
* GitHub issue #230. The fix is to pre-hash the password to ensure
* it is short. We do the prehashing here instead of in pbkdf2() so
* that pbkdf2() still computes the function as defined by the
* standard. */
$prehash = \hash(Core::HASH_FUNCTION_NAME, $this->secret, true);
$prekey = Core::pbkdf2(
Core::HASH_FUNCTION_NAME,
$prehash,
$salt,
self::PBKDF2_ITERATIONS,
Core::KEY_BYTE_SIZE,
true
);
$akey = Core::HKDF(
Core::HASH_FUNCTION_NAME,
$prekey,
Core::KEY_BYTE_SIZE,
Core::AUTHENTICATION_INFO_STRING,
$salt
);
/* Note the cryptographic re-use of $salt here. */
$ekey = Core::HKDF(
Core::HASH_FUNCTION_NAME,
$prekey,
Core::KEY_BYTE_SIZE,
Core::ENCRYPTION_INFO_STRING,
$salt
);
return new DerivedKeys($akey, $ekey);
} else {
throw new Ex\EnvironmentIsBrokenException('Bad secret type.');
}
}
/**
* Constructor for KeyOrPassword.
*
* @param int $secret_type
* @param mixed $secret (either a Key or a password string)
*/
private function __construct($secret_type, $secret)
{
$this->secret_type = $secret_type;
$this->secret = $secret;
}
}
<?php
namespace Defuse\Crypto;
use Defuse\Crypto\Exception as Ex;
final class KeyProtectedByPassword
{
const PASSWORD_KEY_CURRENT_VERSION = "\xDE\xF1\x00\x00";
private $encrypted_key = null;
/**
* Creates a random key protected by the provided password.
*
* @param string $password
*
* @throws Ex\EnvironmentIsBrokenException
*
* @return KeyProtectedByPassword
*/
public static function createRandomPasswordProtectedKey($password)
{
$inner_key = Key::createNewRandomKey();
/* The password is hashed as a form of poor-man's domain separation
* between this use of encryptWithPassword() and other uses of
* encryptWithPassword() that the user may also be using as part of the
* same protocol. */
$encrypted_key = Crypto::encryptWithPassword(
$inner_key->saveToAsciiSafeString(),
\hash(Core::HASH_FUNCTION_NAME, $password, true),
true
);
return new KeyProtectedByPassword($encrypted_key);
}
/**
* Loads a KeyProtectedByPassword from its encoded form.
*
* @param string $saved_key_string
*
* @throws Ex\BadFormatException
*
* @return KeyProtectedByPassword
*/
public static function loadFromAsciiSafeString($saved_key_string)
{
$encrypted_key = Encoding::loadBytesFromChecksummedAsciiSafeString(
self::PASSWORD_KEY_CURRENT_VERSION,
$saved_key_string
);
return new KeyProtectedByPassword($encrypted_key);
}
/**
* Encodes the KeyProtectedByPassword into a string of printable ASCII
* characters.
*
* @throws Ex\EnvironmentIsBrokenException
*
* @return string
*/
public function saveToAsciiSafeString()
{
return Encoding::saveBytesToChecksummedAsciiSafeString(
self::PASSWORD_KEY_CURRENT_VERSION,
$this->encrypted_key
);
}
/**
* Decrypts the protected key, returning an unprotected Key object that can
* be used for encryption and decryption.
*
* @throws Ex\EnvironmentIsBrokenException
* @throws Ex\WrongKeyOrModifiedCiphertextException
*
* @return Key
*/
public function unlockKey($password)
{
try {
$inner_key_encoded = Crypto::decryptWithPassword(
$this->encrypted_key,
\hash(Core::HASH_FUNCTION_NAME, $password, true),
true
);
return Key::loadFromAsciiSafeString($inner_key_encoded);
} catch (Ex\BadFormatException $ex) {
/* This should never happen unless an attacker replaced the
* encrypted key ciphertext with some other ciphertext that was
* encrypted with the same password. We transform the exception type
* here in order to make the API simpler, avoiding the need to
* document that this method might throw an Ex\BadFormatException. */
throw new Ex\WrongKeyOrModifiedCiphertextException(
"The decrypted key was found to be in an invalid format. " .
"This very likely indicates it was modified by an attacker."
);
}
}
/**
* Constructor for KeyProtectedByPassword.
*
* @param string $encrypted_key
*/
private function __construct($encrypted_key)
{
$this->encrypted_key = $encrypted_key;
}
}
<?php
namespace Defuse\Crypto;
use Defuse\Crypto\Exception as Ex;
/*
* We're using static class inheritance to get access to protected methods
* inside Crypto. To make it easy to know where the method we're calling can be
* found, within this file, prefix calls with `Crypto::` or `RuntimeTests::`,
* and don't use `self::`.
*/
class RuntimeTests extends Crypto
{
/**
* Runs the runtime tests.
*
* @throws Ex\EnvironmentIsBrokenException
*/
public static function runtimeTest()
{
// 0: Tests haven't been run yet.
// 1: Tests have passed.
// 2: Tests are running right now.
// 3: Tests have failed.
static $test_state = 0;
if ($test_state === 1 || $test_state === 2) {
return;
}
if ($test_state === 3) {
/* If an intermittent problem caused a test to fail previously, we
* want that to be indicated to the user with every call to this
* library. This way, if the user first does something they really
* don't care about, and just ignores all exceptions, they won't get
* screwed when they then start to use the library for something
* they do care about. */
throw new Ex\EnvironmentIsBrokenException('Tests failed previously.');
}
try {
$test_state = 2;
Core::ensureFunctionExists('openssl_get_cipher_methods');
if (\in_array(Core::CIPHER_METHOD, \openssl_get_cipher_methods()) === false) {
throw new Ex\EnvironmentIsBrokenException(
'Cipher method not supported. This is normally caused by an outdated ' .
'version of OpenSSL (and/or OpenSSL compiled for FIPS compliance). ' .
'Please upgrade to a newer version of OpenSSL that supports ' .
Core::CIPHER_METHOD . ' to use this library.'
);
}
RuntimeTests::AESTestVector();
RuntimeTests::HMACTestVector();
RuntimeTests::HKDFTestVector();
RuntimeTests::testEncryptDecrypt();
if (Core::ourStrlen(Key::createNewRandomKey()->getRawBytes()) != Core::KEY_BYTE_SIZE) {
throw new Ex\EnvironmentIsBrokenException();
}
if (Core::ENCRYPTION_INFO_STRING == Core::AUTHENTICATION_INFO_STRING) {
throw new Ex\EnvironmentIsBrokenException();
}
} catch (Ex\EnvironmentIsBrokenException $ex) {
// Do this, otherwise it will stay in the "tests are running" state.
$test_state = 3;
throw $ex;
}
// Change this to '0' make the tests always re-run (for benchmarking).
$test_state = 1;
}
/**
* High-level tests of Crypto operations.
*
* @throws Ex\EnvironmentIsBrokenException
*/
private static function testEncryptDecrypt()
{
$key = Key::createNewRandomKey();
$data = "EnCrYpT EvErYThInG\x00\x00";
// Make sure encrypting then decrypting doesn't change the message.
$ciphertext = Crypto::encrypt($data, $key, true);
try {
$decrypted = Crypto::decrypt($ciphertext, $key, true);
} catch (Ex\WrongKeyOrModifiedCiphertextException $ex) {
// It's important to catch this and change it into a
// Ex\EnvironmentIsBrokenException, otherwise a test failure could trick
// the user into thinking it's just an invalid ciphertext!
throw new Ex\EnvironmentIsBrokenException();
}
if ($decrypted !== $data) {
throw new Ex\EnvironmentIsBrokenException();
}
// Modifying the ciphertext: Appending a string.
try {
Crypto::decrypt($ciphertext . 'a', $key, true);
throw new Ex\EnvironmentIsBrokenException();
} catch (Ex\WrongKeyOrModifiedCiphertextException $e) { /* expected */
}
// Modifying the ciphertext: Changing an HMAC byte.
$indices_to_change = [
0, // The header.
Core::HEADER_VERSION_SIZE + 1, // the salt
Core::HEADER_VERSION_SIZE + Core::SALT_BYTE_SIZE + 1, // the IV
Core::HEADER_VERSION_SIZE + Core::SALT_BYTE_SIZE + Core::BLOCK_BYTE_SIZE + 1, // the ciphertext
];
foreach ($indices_to_change as $index) {
try {
$ciphertext[$index] = \chr((\ord($ciphertext[$index]) + 1) % 256);
Crypto::decrypt($ciphertext, $key, true);
throw new Ex\EnvironmentIsBrokenException();
} catch (Ex\WrongKeyOrModifiedCiphertextException $e) { /* expected */
}
}
// Decrypting with the wrong key.
$key = Key::createNewRandomKey();
$data = 'abcdef';
$ciphertext = Crypto::encrypt($data, $key, true);
$wrong_key = Key::createNewRandomKey();
try {
Crypto::decrypt($ciphertext, $wrong_key, true);
throw new Ex\EnvironmentIsBrokenException();
} catch (Ex\WrongKeyOrModifiedCiphertextException $e) { /* expected */
}
// Ciphertext too small.
$key = Key::createNewRandomKey();
$ciphertext = \str_repeat('A', Core::MINIMUM_CIPHERTEXT_SIZE - 1);
try {
Crypto::decrypt($ciphertext, $key, true);
throw new Ex\EnvironmentIsBrokenException();
} catch (Ex\WrongKeyOrModifiedCiphertextException $e) { /* expected */
}
}
/**
* Test HKDF against test vectors.
*
* @throws Ex\EnvironmentIsBrokenException
*/
private static function HKDFTestVector()
{
// HKDF test vectors from RFC 5869
// Test Case 1
$ikm = \str_repeat("\x0b", 22);
$salt = Encoding::hexToBin('000102030405060708090a0b0c');
$info = Encoding::hexToBin('f0f1f2f3f4f5f6f7f8f9');
$length = 42;
$okm = Encoding::hexToBin(
'3cb25f25faacd57a90434f64d0362f2a' .
'2d2d0a90cf1a5a4c5db02d56ecc4c5bf' .
'34007208d5b887185865'
);
$computed_okm = Core::HKDF('sha256', $ikm, $length, $info, $salt);
if ($computed_okm !== $okm) {
throw new Ex\EnvironmentIsBrokenException();
}
// Test Case 7
$ikm = \str_repeat("\x0c", 22);
$length = 42;
$okm = Encoding::hexToBin(
'2c91117204d745f3500d636a62f64f0a' .
'b3bae548aa53d423b0d1f27ebba6f5e5' .
'673a081d70cce7acfc48'
);
$computed_okm = Core::HKDF('sha1', $ikm, $length, '', null);
if ($computed_okm !== $okm) {
throw new Ex\EnvironmentIsBrokenException();
}
}
/**
* Test HMAC against test vectors.
*
* @throws Ex\EnvironmentIsBrokenException
*/
private static function HMACTestVector()
{
// HMAC test vector From RFC 4231 (Test Case 1)
$key = \str_repeat("\x0b", 20);
$data = 'Hi There';
$correct = 'b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7';
if (\hash_hmac(Core::HASH_FUNCTION_NAME, $data, $key) !== $correct) {
throw new Ex\EnvironmentIsBrokenException();
}
}
/**
* Test AES against test vectors.
*
* @throws Ex\EnvironmentIsBrokenException
*/
private static function AESTestVector()
{
// AES CTR mode test vector from NIST SP 800-38A
$key = Encoding::hexToBin(
'603deb1015ca71be2b73aef0857d7781' .
'1f352c073b6108d72d9810a30914dff4'
);
$iv = Encoding::hexToBin('f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff');
$plaintext = Encoding::hexToBin(
'6bc1bee22e409f96e93d7e117393172a' .
'ae2d8a571e03ac9c9eb76fac45af8e51' .
'30c81c46a35ce411e5fbc1191a0a52ef' .
'f69f2445df4f9b17ad2b417be66c3710'
);
$ciphertext = Encoding::hexToBin(
'601ec313775789a5b7a7f504bbf3d228' .
'f443e3ca4d62b59aca84e990cacaf5c5' .
'2b0930daa23de94ce87017ba2d84988d' .
'dfc9c58db67aada613c2dd08457941a6'
);
$computed_ciphertext = Crypto::plainEncrypt($plaintext, $key, $iv);
if ($computed_ciphertext !== $ciphertext) {
echo \str_repeat("\n", 30);
echo \bin2hex($computed_ciphertext);
echo "\n---\n";
echo \bin2hex($ciphertext);
echo \str_repeat("\n", 30);
throw new Ex\EnvironmentIsBrokenException();
}
$computed_plaintext = Crypto::plainDecrypt($ciphertext, $key, $iv, Core::CIPHER_METHOD);
if ($computed_plaintext !== $plaintext) {
throw new Ex\EnvironmentIsBrokenException();
}
}
}
The MIT License (MIT)
Copyright (c) 2015 Paragon Initiative Enterprises
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
#!/usr/bin/env bash
basedir=$( dirname $( readlink -f ${BASH_SOURCE[0]} ) )
php -dphar.readonly=0 "$basedir/other/build_phar.php" $*
\ No newline at end of file
{
"name": "paragonie/random_compat",
"description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
"keywords": [
"csprng",
"random",
"pseudorandom"
],
"license": "MIT",
"type": "library",
"authors": [
{
"name": "Paragon Initiative Enterprises",
"email": "security@paragonie.com",
"homepage": "https://paragonie.com"
}
],
"support": {
"issues": "https://github.com/paragonie/random_compat/issues",
"email": "info@paragonie.com",
"source": "https://github.com/paragonie/random_compat"
},
"require": {
"php": ">=5.2.0"
},
"require-dev": {
"phpunit/phpunit": "4.*|5.*"
},
"suggest": {
"ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
},
"autoload": {
"files": ["lib/random.php"]
}
}
-----BEGIN PUBLIC KEY-----
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEEd+wCqJDrx5B4OldM0dQE0ZMX+lx1ZWm
pui0SUqD4G29L3NGsz9UhJ/0HjBdbnkhIK5xviT0X5vtjacF6ajgcCArbTB+ds+p
+h7Q084NuSuIpNb6YPfoUFgC/CL9kAoc
-----END PUBLIC KEY-----
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v2.0.22 (MingW32)
iQEcBAABAgAGBQJWtW1hAAoJEGuXocKCZATaJf0H+wbZGgskK1dcRTsuVJl9IWip
QwGw/qIKI280SD6/ckoUMxKDCJiFuPR14zmqnS36k7N5UNPnpdTJTS8T11jttSpg
1LCmgpbEIpgaTah+cELDqFCav99fS+bEiAL5lWDAHBTE/XPjGVCqeehyPYref4IW
NDBIEsvnHPHPLsn6X5jq4+Yj5oUixgxaMPiR+bcO4Sh+RzOVB6i2D0upWfRXBFXA
NNnsg9/zjvoC7ZW73y9uSH+dPJTt/Vgfeiv52/v41XliyzbUyLalf02GNPY+9goV
JHG1ulEEBJOCiUD9cE1PUIJwHA/HqyhHIvV350YoEFiHl8iSwm7SiZu5kPjaq74=
=B6+8
-----END PGP SIGNATURE-----
<?php
/**
* Random_* Compatibility Library
* for using the new PHP 7 random_* API in PHP 5 projects
*
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2016 Paragon Initiative Enterprises
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
if (!is_callable('RandomCompat_strlen')) {
if (
defined('MB_OVERLOAD_STRING') &&
ini_get('mbstring.func_overload') & MB_OVERLOAD_STRING
) {
/**
* strlen() implementation that isn't brittle to mbstring.func_overload
*
* This version uses mb_strlen() in '8bit' mode to treat strings as raw
* binary rather than UTF-8, ISO-8859-1, etc
*
* @param string $binary_string
*
* @throws TypeError
*
* @return int
*/
function RandomCompat_strlen($binary_string)
{
if (!is_string($binary_string)) {
throw new TypeError(
'RandomCompat_strlen() expects a string'
);
}
return mb_strlen($binary_string, '8bit');
}
} else {
/**
* strlen() implementation that isn't brittle to mbstring.func_overload
*
* This version just used the default strlen()
*
* @param string $binary_string
*
* @throws TypeError
*
* @return int
*/
function RandomCompat_strlen($binary_string)
{
if (!is_string($binary_string)) {
throw new TypeError(
'RandomCompat_strlen() expects a string'
);
}
return strlen($binary_string);
}
}
}
if (!is_callable('RandomCompat_substr')) {
if (
defined('MB_OVERLOAD_STRING')
&&
ini_get('mbstring.func_overload') & MB_OVERLOAD_STRING
) {
/**
* substr() implementation that isn't brittle to mbstring.func_overload
*
* This version uses mb_substr() in '8bit' mode to treat strings as raw
* binary rather than UTF-8, ISO-8859-1, etc
*
* @param string $binary_string
* @param int $start
* @param int $length (optional)
*
* @throws TypeError
*
* @return string
*/
function RandomCompat_substr($binary_string, $start, $length = null)
{
if (!is_string($binary_string)) {
throw new TypeError(
'RandomCompat_substr(): First argument should be a string'
);
}
if (!is_int($start)) {
throw new TypeError(
'RandomCompat_substr(): Second argument should be an integer'
);
}
if ($length === null) {
/**
* mb_substr($str, 0, NULL, '8bit') returns an empty string on
* PHP 5.3, so we have to find the length ourselves.
*/
$length = RandomCompat_strlen($length) - $start;
} elseif (!is_int($length)) {
throw new TypeError(
'RandomCompat_substr(): Third argument should be an integer, or omitted'
);
}
// Consistency with PHP's behavior
if ($start === RandomCompat_strlen($binary_string) && $length === 0) {
return '';
}
if ($start > RandomCompat_strlen($binary_string)) {
return false;
}
return mb_substr($binary_string, $start, $length, '8bit');
}
} else {
/**
* substr() implementation that isn't brittle to mbstring.func_overload
*
* This version just uses the default substr()
*
* @param string $binary_string
* @param int $start
* @param int $length (optional)
*
* @throws TypeError
*
* @return string
*/
function RandomCompat_substr($binary_string, $start, $length = null)
{
if (!is_string($binary_string)) {
throw new TypeError(
'RandomCompat_substr(): First argument should be a string'
);
}
if (!is_int($start)) {
throw new TypeError(
'RandomCompat_substr(): Second argument should be an integer'
);
}
if ($length !== null) {
if (!is_int($length)) {
throw new TypeError(
'RandomCompat_substr(): Third argument should be an integer, or omitted'
);
}
return substr($binary_string, $start, $length);
}
return substr($binary_string, $start);
}
}
}
<?php
/**
* Random_* Compatibility Library
* for using the new PHP 7 random_* API in PHP 5 projects
*
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2016 Paragon Initiative Enterprises
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
if (!is_callable('RandomCompat_intval')) {
/**
* Cast to an integer if we can, safely.
*
* If you pass it a float in the range (~PHP_INT_MAX, PHP_INT_MAX)
* (non-inclusive), it will sanely cast it to an int. If you it's equal to
* ~PHP_INT_MAX or PHP_INT_MAX, we let it fail as not an integer. Floats
* lose precision, so the <= and => operators might accidentally let a float
* through.
*
* @param int|float $number The number we want to convert to an int
* @param boolean $fail_open Set to true to not throw an exception
*
* @return int (or float if $fail_open)
*
* @throws TypeError
*/
function RandomCompat_intval($number, $fail_open = false)
{
if (is_numeric($number)) {
$number += 0;
}
if (
is_float($number)
&&
$number > ~PHP_INT_MAX
&&
$number < PHP_INT_MAX
) {
$number = (int) $number;
}
if (is_int($number) || $fail_open) {
return $number;
}
throw new TypeError(
'Expected an integer.'
);
}
}
<?php
/**
* Random_* Compatibility Library
* for using the new PHP 7 random_* API in PHP 5 projects
*
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2016 Paragon Initiative Enterprises
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
if (!class_exists('Error', false)) {
// We can't really avoid making this extend Exception in PHP 5.
class Error extends Exception
{
}
}
if (!class_exists('TypeError', false)) {
if (is_subclass_of('Error', 'Exception')) {
class TypeError extends Error
{
}
} else {
class TypeError extends Exception
{
}
}
}
<?php
/**
* Random_* Compatibility Library
* for using the new PHP 7 random_* API in PHP 5 projects
*
* @version 2.0.4
* @released 2016-11-07
*
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2016 Paragon Initiative Enterprises
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
if (!defined('PHP_VERSION_ID')) {
// This constant was introduced in PHP 5.2.7
$RandomCompatversion = array_map('intval', explode('.', PHP_VERSION));
define(
'PHP_VERSION_ID',
$RandomCompatversion[0] * 10000
+ $RandomCompatversion[1] * 100
+ $RandomCompatversion[2]
);
$RandomCompatversion = null;
}
/**
* PHP 7.0.0 and newer have these functions natively.
*/
if (PHP_VERSION_ID < 70000) {
if (!defined('RANDOM_COMPAT_READ_BUFFER')) {
define('RANDOM_COMPAT_READ_BUFFER', 8);
}
$RandomCompatDIR = dirname(__FILE__);
require_once $RandomCompatDIR.'/byte_safe_strings.php';
require_once $RandomCompatDIR.'/cast_to_int.php';
require_once $RandomCompatDIR.'/error_polyfill.php';
if (!is_callable('random_bytes')) {
/**
* PHP 5.2.0 - 5.6.x way to implement random_bytes()
*
* We use conditional statements here to define the function in accordance
* to the operating environment. It's a micro-optimization.
*
* In order of preference:
* 1. Use libsodium if available.
* 2. fread() /dev/urandom if available (never on Windows)
* 3. mcrypt_create_iv($bytes, MCRYPT_DEV_URANDOM)
* 4. COM('CAPICOM.Utilities.1')->GetRandom()
* 5. openssl_random_pseudo_bytes() (absolute last resort)
*
* See RATIONALE.md for our reasoning behind this particular order
*/
if (extension_loaded('libsodium')) {
// See random_bytes_libsodium.php
if (PHP_VERSION_ID >= 50300 && is_callable('\\Sodium\\randombytes_buf')) {
require_once $RandomCompatDIR.'/random_bytes_libsodium.php';
} elseif (method_exists('Sodium', 'randombytes_buf')) {
require_once $RandomCompatDIR.'/random_bytes_libsodium_legacy.php';
}
}
/**
* Reading directly from /dev/urandom:
*/
if (DIRECTORY_SEPARATOR === '/') {
// DIRECTORY_SEPARATOR === '/' on Unix-like OSes -- this is a fast
// way to exclude Windows.
$RandomCompatUrandom = true;
$RandomCompat_basedir = ini_get('open_basedir');
if (!empty($RandomCompat_basedir)) {
$RandomCompat_open_basedir = explode(
PATH_SEPARATOR,
strtolower($RandomCompat_basedir)
);
$RandomCompatUrandom = (array() !== array_intersect(
array('/dev', '/dev/', '/dev/urandom'),
$RandomCompat_open_basedir
));
$RandomCompat_open_basedir = null;
}
if (
!is_callable('random_bytes')
&&
$RandomCompatUrandom
&&
@is_readable('/dev/urandom')
) {
// Error suppression on is_readable() in case of an open_basedir
// or safe_mode failure. All we care about is whether or not we
// can read it at this point. If the PHP environment is going to
// panic over trying to see if the file can be read in the first
// place, that is not helpful to us here.
// See random_bytes_dev_urandom.php
require_once $RandomCompatDIR.'/random_bytes_dev_urandom.php';
}
// Unset variables after use
$RandomCompat_basedir = null;
} else {
$RandomCompatUrandom = false;
}
/**
* mcrypt_create_iv()
*
* We only want to use mcypt_create_iv() if:
*
* - random_bytes() hasn't already been defined
* - PHP >= 5.3.7
* - the mcrypt extensions is loaded
* - One of these two conditions is true:
* - We're on Windows (DIRECTORY_SEPARATOR !== '/')
* - We're not on Windows and /dev/urandom is readabale
* (i.e. we're not in a chroot jail)
* - Special case:
* - If we're not on Windows, but the PHP version is between
* 5.6.10 and 5.6.12, we don't want to use mcrypt. It will
* hang indefinitely. This is bad.
*/
if (
!is_callable('random_bytes')
&&
PHP_VERSION_ID >= 50307
&&
extension_loaded('mcrypt')
) {
// Prevent this code from hanging indefinitely on non-Windows;
// see https://bugs.php.net/bug.php?id=69833
if (
DIRECTORY_SEPARATOR !== '/' ||
(PHP_VERSION_ID <= 50609 || PHP_VERSION_ID >= 50613)
) {
// See random_bytes_mcrypt.php
require_once $RandomCompatDIR.'/random_bytes_mcrypt.php';
}
}
$RandomCompatUrandom = null;
/**
* This is a Windows-specific fallback, for when the mcrypt extension
* isn't loaded.
*/
if (
!is_callable('random_bytes')
&&
extension_loaded('com_dotnet')
&&
class_exists('COM')
) {
$RandomCompat_disabled_classes = preg_split(
'#\s*,\s*#',
strtolower(ini_get('disable_classes'))
);
if (!in_array('com', $RandomCompat_disabled_classes)) {
try {
$RandomCompatCOMtest = new COM('CAPICOM.Utilities.1');
if (method_exists($RandomCompatCOMtest, 'GetRandom')) {
// See random_bytes_com_dotnet.php
require_once $RandomCompatDIR.'/random_bytes_com_dotnet.php';
}
} catch (com_exception $e) {
// Don't try to use it.
}
}
$RandomCompat_disabled_classes = null;
$RandomCompatCOMtest = null;
}
/**
* throw new Exception
*/
if (!is_callable('random_bytes')) {
/**
* We don't have any more options, so let's throw an exception right now
* and hope the developer won't let it fail silently.
*/
function random_bytes($length)
{
throw new Exception(
'There is no suitable CSPRNG installed on your system'
);
}
}
}
if (!is_callable('random_int')) {
require_once $RandomCompatDIR.'/random_int.php';
}
$RandomCompatDIR = null;
}
<?php
/**
* Random_* Compatibility Library
* for using the new PHP 7 random_* API in PHP 5 projects
*
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2016 Paragon Initiative Enterprises
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
if (!is_callable('random_bytes')) {
/**
* Windows with PHP < 5.3.0 will not have the function
* openssl_random_pseudo_bytes() available, so let's use
* CAPICOM to work around this deficiency.
*
* @param int $bytes
*
* @throws Exception
*
* @return string
*/
function random_bytes($bytes)
{
try {
$bytes = RandomCompat_intval($bytes);
} catch (TypeError $ex) {
throw new TypeError(
'random_bytes(): $bytes must be an integer'
);
}
if ($bytes < 1) {
throw new Error(
'Length must be greater than 0'
);
}
$buf = '';
$util = new COM('CAPICOM.Utilities.1');
$execCount = 0;
/**
* Let's not let it loop forever. If we run N times and fail to
* get N bytes of random data, then CAPICOM has failed us.
*/
do {
$buf .= base64_decode($util->GetRandom($bytes, 0));
if (RandomCompat_strlen($buf) >= $bytes) {
/**
* Return our random entropy buffer here:
*/
return RandomCompat_substr($buf, 0, $bytes);
}
++$execCount;
} while ($execCount < $bytes);
/**
* If we reach here, PHP has failed us.
*/
throw new Exception(
'Could not gather sufficient random data'
);
}
}
\ No newline at end of file
<?php
/**
* Random_* Compatibility Library
* for using the new PHP 7 random_* API in PHP 5 projects
*
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2016 Paragon Initiative Enterprises
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
if (!defined('RANDOM_COMPAT_READ_BUFFER')) {
define('RANDOM_COMPAT_READ_BUFFER', 8);
}
if (!is_callable('random_bytes')) {
/**
* Unless open_basedir is enabled, use /dev/urandom for
* random numbers in accordance with best practices
*
* Why we use /dev/urandom and not /dev/random
* @ref http://sockpuppet.org/blog/2014/02/25/safely-generate-random-numbers
*
* @param int $bytes
*
* @throws Exception
*
* @return string
*/
function random_bytes($bytes)
{
static $fp = null;
/**
* This block should only be run once
*/
if (empty($fp)) {
/**
* We use /dev/urandom if it is a char device.
* We never fall back to /dev/random
*/
$fp = fopen('/dev/urandom', 'rb');
if (!empty($fp)) {
$st = fstat($fp);
if (($st['mode'] & 0170000) !== 020000) {
fclose($fp);
$fp = false;
}
}
if (!empty($fp)) {
/**
* stream_set_read_buffer() does not exist in HHVM
*
* If we don't set the stream's read buffer to 0, PHP will
* internally buffer 8192 bytes, which can waste entropy
*
* stream_set_read_buffer returns 0 on success
*/
if (is_callable('stream_set_read_buffer')) {
stream_set_read_buffer($fp, RANDOM_COMPAT_READ_BUFFER);
}
if (is_callable('stream_set_chunk_size')) {
stream_set_chunk_size($fp, RANDOM_COMPAT_READ_BUFFER);
}
}
}
try {
$bytes = RandomCompat_intval($bytes);
} catch (TypeError $ex) {
throw new TypeError(
'random_bytes(): $bytes must be an integer'
);
}
if ($bytes < 1) {
throw new Error(
'Length must be greater than 0'
);
}
/**
* This if() block only runs if we managed to open a file handle
*
* It does not belong in an else {} block, because the above
* if (empty($fp)) line is logic that should only be run once per
* page load.
*/
if (!empty($fp)) {
$remaining = $bytes;
$buf = '';
/**
* We use fread() in a loop to protect against partial reads
*/
do {
$read = fread($fp, $remaining);
if ($read === false) {
/**
* We cannot safely read from the file. Exit the
* do-while loop and trigger the exception condition
*/
$buf = false;
break;
}
/**
* Decrease the number of bytes returned from remaining
*/
$remaining -= RandomCompat_strlen($read);
$buf .= $read;
} while ($remaining > 0);
/**
* Is our result valid?
*/
if ($buf !== false) {
if (RandomCompat_strlen($buf) === $bytes) {
/**
* Return our random entropy buffer here:
*/
return $buf;
}
}
}
/**
* If we reach here, PHP has failed us.
*/
throw new Exception(
'Error reading from source device'
);
}
}
<?php
/**
* Random_* Compatibility Library
* for using the new PHP 7 random_* API in PHP 5 projects
*
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2016 Paragon Initiative Enterprises
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
if (!is_callable('random_bytes')) {
/**
* If the libsodium PHP extension is loaded, we'll use it above any other
* solution.
*
* libsodium-php project:
* @ref https://github.com/jedisct1/libsodium-php
*
* @param int $bytes
*
* @throws Exception
*
* @return string
*/
function random_bytes($bytes)
{
try {
$bytes = RandomCompat_intval($bytes);
} catch (TypeError $ex) {
throw new TypeError(
'random_bytes(): $bytes must be an integer'
);
}
if ($bytes < 1) {
throw new Error(
'Length must be greater than 0'
);
}
/**
* \Sodium\randombytes_buf() doesn't allow more than 2147483647 bytes to be
* generated in one invocation.
*/
if ($bytes > 2147483647) {
$buf = '';
for ($i = 0; $i < $bytes; $i += 1073741824) {
$n = ($bytes - $i) > 1073741824
? 1073741824
: $bytes - $i;
$buf .= \Sodium\randombytes_buf($n);
}
} else {
$buf = \Sodium\randombytes_buf($bytes);
}
if ($buf !== false) {
if (RandomCompat_strlen($buf) === $bytes) {
return $buf;
}
}
/**
* If we reach here, PHP has failed us.
*/
throw new Exception(
'Could not gather sufficient random data'
);
}
}
<?php
/**
* Random_* Compatibility Library
* for using the new PHP 7 random_* API in PHP 5 projects
*
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2016 Paragon Initiative Enterprises
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
if (!is_callable('random_bytes')) {
/**
* If the libsodium PHP extension is loaded, we'll use it above any other
* solution.
*
* libsodium-php project:
* @ref https://github.com/jedisct1/libsodium-php
*
* @param int $bytes
*
* @throws Exception
*
* @return string
*/
function random_bytes($bytes)
{
try {
$bytes = RandomCompat_intval($bytes);
} catch (TypeError $ex) {
throw new TypeError(
'random_bytes(): $bytes must be an integer'
);
}
if ($bytes < 1) {
throw new Error(
'Length must be greater than 0'
);
}
/**
* \Sodium\randombytes_buf() doesn't allow more than 2147483647 bytes to be
* generated in one invocation.
*/
if ($bytes > 2147483647) {
$buf = '';
for ($i = 0; $i < $bytes; $i += 1073741824) {
$n = ($bytes - $i) > 1073741824
? 1073741824
: $bytes - $i;
$buf .= Sodium::randombytes_buf($n);
}
} else {
$buf = Sodium::randombytes_buf($bytes);
}
if ($buf !== false) {
if (RandomCompat_strlen($buf) === $bytes) {
return $buf;
}
}
/**
* If we reach here, PHP has failed us.
*/
throw new Exception(
'Could not gather sufficient random data'
);
}
}
<?php
/**
* Random_* Compatibility Library
* for using the new PHP 7 random_* API in PHP 5 projects
*
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2016 Paragon Initiative Enterprises
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
if (!is_callable('random_bytes')) {
/**
* Powered by ext/mcrypt (and thankfully NOT libmcrypt)
*
* @ref https://bugs.php.net/bug.php?id=55169
* @ref https://github.com/php/php-src/blob/c568ffe5171d942161fc8dda066bce844bdef676/ext/mcrypt/mcrypt.c#L1321-L1386
*
* @param int $bytes
*
* @throws Exception
*
* @return string
*/
function random_bytes($bytes)
{
try {
$bytes = RandomCompat_intval($bytes);
} catch (TypeError $ex) {
throw new TypeError(
'random_bytes(): $bytes must be an integer'
);
}
if ($bytes < 1) {
throw new Error(
'Length must be greater than 0'
);
}
$buf = @mcrypt_create_iv($bytes, MCRYPT_DEV_URANDOM);
if (
$buf !== false
&&
RandomCompat_strlen($buf) === $bytes
) {
/**
* Return our random entropy buffer here:
*/
return $buf;
}
/**
* If we reach here, PHP has failed us.
*/
throw new Exception(
'Could not gather sufficient random data'
);
}
}
<?php
/**
* Random_* Compatibility Library
* for using the new PHP 7 random_* API in PHP 5 projects
*
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2016 Paragon Initiative Enterprises
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/**
* Fetch a random integer between $min and $max inclusive
*
* @param int $min
* @param int $max
*
* @throws Exception
*
* @return int
*/
function random_int($min, $max)
{
/**
* Type and input logic checks
*
* If you pass it a float in the range (~PHP_INT_MAX, PHP_INT_MAX)
* (non-inclusive), it will sanely cast it to an int. If you it's equal to
* ~PHP_INT_MAX or PHP_INT_MAX, we let it fail as not an integer. Floats
* lose precision, so the <= and => operators might accidentally let a float
* through.
*/
try {
$min = RandomCompat_intval($min);
} catch (TypeError $ex) {
throw new TypeError(
'random_int(): $min must be an integer'
);
}
try {
$max = RandomCompat_intval($max);
} catch (TypeError $ex) {
throw new TypeError(
'random_int(): $max must be an integer'
);
}
/**
* Now that we've verified our weak typing system has given us an integer,
* let's validate the logic then we can move forward with generating random
* integers along a given range.
*/
if ($min > $max) {
throw new Error(
'Minimum value must be less than or equal to the maximum value'
);
}
if ($max === $min) {
return $min;
}
/**
* Initialize variables to 0
*
* We want to store:
* $bytes => the number of random bytes we need
* $mask => an integer bitmask (for use with the &) operator
* so we can minimize the number of discards
*/
$attempts = $bits = $bytes = $mask = $valueShift = 0;
/**
* At this point, $range is a positive number greater than 0. It might
* overflow, however, if $max - $min > PHP_INT_MAX. PHP will cast it to
* a float and we will lose some precision.
*/
$range = $max - $min;
/**
* Test for integer overflow:
*/
if (!is_int($range)) {
/**
* Still safely calculate wider ranges.
* Provided by @CodesInChaos, @oittaa
*
* @ref https://gist.github.com/CodesInChaos/03f9ea0b58e8b2b8d435
*
* We use ~0 as a mask in this case because it generates all 1s
*
* @ref https://eval.in/400356 (32-bit)
* @ref http://3v4l.org/XX9r5 (64-bit)
*/
$bytes = PHP_INT_SIZE;
$mask = ~0;
} else {
/**
* $bits is effectively ceil(log($range, 2)) without dealing with
* type juggling
*/
while ($range > 0) {
if ($bits % 8 === 0) {
++$bytes;
}
++$bits;
$range >>= 1;
$mask = $mask << 1 | 1;
}
$valueShift = $min;
}
/**
* Now that we have our parameters set up, let's begin generating
* random integers until one falls between $min and $max
*/
do {
/**
* The rejection probability is at most 0.5, so this corresponds
* to a failure probability of 2^-128 for a working RNG
*/
if ($attempts > 128) {
throw new Exception(
'random_int: RNG is broken - too many rejections'
);
}
/**
* Let's grab the necessary number of random bytes
*/
$randomByteString = random_bytes($bytes);
if ($randomByteString === false) {
throw new Exception(
'Random number generator failure'
);
}
/**
* Let's turn $randomByteString into an integer
*
* This uses bitwise operators (<< and |) to build an integer
* out of the values extracted from ord()
*
* Example: [9F] | [6D] | [32] | [0C] =>
* 159 + 27904 + 3276800 + 201326592 =>
* 204631455
*/
$val = 0;
for ($i = 0; $i < $bytes; ++$i) {
$val |= ord($randomByteString[$i]) << ($i * 8);
}
/**
* Apply mask
*/
$val &= $mask;
$val += $valueShift;
++$attempts;
/**
* If $val overflows to a floating point number,
* ... or is larger than $max,
* ... or smaller than $min,
* then try again.
*/
} while (!is_int($val) || $val > $max || $val < $min);
return (int) $val;
}
<?php
$dist = dirname(__DIR__).'/dist';
if (!is_dir($dist)) {
mkdir($dist, 0755);
}
if (file_exists($dist.'/random_compat.phar')) {
unlink($dist.'/random_compat.phar');
}
$phar = new Phar(
$dist.'/random_compat.phar',
FilesystemIterator::CURRENT_AS_FILEINFO | \FilesystemIterator::KEY_AS_FILENAME,
'random_compat.phar'
);
rename(
dirname(__DIR__).'/lib/random.php',
dirname(__DIR__).'/lib/index.php'
);
$phar->buildFromDirectory(dirname(__DIR__).'/lib');
rename(
dirname(__DIR__).'/lib/index.php',
dirname(__DIR__).'/lib/random.php'
);
/**
* If we pass an (optional) path to a private key as a second argument, we will
* sign the Phar with OpenSSL.
*
* If you leave this out, it will produce an unsigned .phar!
*/
if ($argc > 1) {
if (!@is_readable($argv[1])) {
echo 'Could not read the private key file:', $argv[1], "\n";
exit(255);
}
$pkeyFile = file_get_contents($argv[1]);
$private = openssl_get_privatekey($pkeyFile);
if ($private !== false) {
$pkey = '';
openssl_pkey_export($private, $pkey);
$phar->setSignatureAlgorithm(Phar::OPENSSL, $pkey);
/**
* Save the corresponding public key to the file
*/
if (!@is_readable($dist.'/random_compat.phar.pubkey')) {
$details = openssl_pkey_get_details($private);
file_put_contents(
$dist.'/random_compat.phar.pubkey',
$details['key']
);
}
} else {
echo 'An error occurred reading the private key from OpenSSL.', "\n";
exit(255);
}
}
/.idea
/vendor
/composer.lock
/composer.phar
/cache.properties
Git
Copyright (c) 2013-2016, Sebastian Bergmann <sebastian@phpunit.de>.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
* Neither the name of Sebastian Bergmann nor the names of his
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
# Git
Simple PHP wrapper for Git.
## Installation
To add this package as a local, per-project dependency to your project, simply add a dependency on `sebastian/git` to your project's `composer.json` file. Here is a minimal example of a `composer.json` file that just defines a dependency on Git 1.0:
{
"require": {
"sebastian/git": "1.0.*"
}
}
<?xml version="1.0" encoding="UTF-8"?>
<project name="git" default="build">
<target name="build" depends="prepare,lint,phpcs"/>
<target name="clean" description="Cleanup build artifacts">
</target>
<target name="prepare" depends="clean" description="Prepare for build">
</target>
<target name="lint">
<apply executable="php" failonerror="true">
<arg value="-l" />
<fileset dir="${basedir}/src">
<include name="**/*.php" />
<modified />
</fileset>
</apply>
</target>
<target name="phpcs" description="Find coding standard violations using PHP_CodeSniffer">
<exec executable="phpcs">
<arg value="--standard=PSR2" />
<arg value="--extensions=php" />
<arg path="${basedir}/src" />
</exec>
</target>
</project>
{
"name": "sebastian/git",
"description": "Simple wrapper for Git",
"keywords": ["git"],
"homepage": "http://www.github.com/sebastianbergmann/git",
"license": "BSD-3-Clause",
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
}
],
"require": {
"php": ">=5.3.3"
},
"autoload": {
"classmap": [
"src/"
]
},
"extra": {
"branch-alias": {
"dev-master": "2.1-dev"
}
}
}
<?php
/*
* This file is part of Git.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Git;
/**
*/
interface Exception
{
}
<?php
/*
* This file is part of Git.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Git;
/**
*/
class RuntimeException extends \RuntimeException implements Exception
{
}
<?php
/*
* This file is part of Git.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\Git;
use DateTime;
/**
*/
class Git
{
/**
* @var string
*/
private $repositoryPath;
/**
* @param string $repositoryPath
*/
public function __construct($repositoryPath)
{
if (!is_dir($repositoryPath)) {
throw new RuntimeException(
sprintf(
'Directory "%s" does not exist',
$repositoryPath
)
);
}
$this->repositoryPath = realpath($repositoryPath);
}
/**
* @param string $revision
*/
public function checkout($revision)
{
$this->execute(
'checkout --force --quiet ' . $revision . ' 2>&1'
);
}
/**
* @return string
*/
public function getCurrentBranch()
{
$output = $this->execute('symbolic-ref --short HEAD');
return $output[0];
}
/**
* @param string $from
* @param string $to
* @return string
*/
public function getDiff($from, $to)
{
$output = $this->execute(
'diff --no-ext-diff ' . $from . ' ' . $to
);
return implode("\n", $output);
}
/**
* @return array
*/
public function getRevisions()
{
$output = $this->execute(
'log --no-merges --date-order --reverse --format=medium'
);
$numLines = count($output);
$revisions = array();
for ($i = 0; $i < $numLines; $i++) {
$tmp = explode(' ', $output[$i]);
if ($tmp[0] == 'commit') {
$sha1 = $tmp[1];
} elseif ($tmp[0] == 'Author:') {
$author = implode(' ', array_slice($tmp, 1));
} elseif ($tmp[0] == 'Date:' && isset($author) && isset($sha1)) {
$revisions[] = array(
'author' => $author,
'date' => DateTime::createFromFormat(
'D M j H:i:s Y O',
implode(' ', array_slice($tmp, 3))
),
'sha1' => $sha1,
'message' => isset($output[$i+2]) ? trim($output[$i+2]) : ''
);
unset($author);
unset($sha1);
}
}
return $revisions;
}
/**
* @return bool
*/
public function isWorkingCopyClean()
{
$output = $this->execute('status');
return $output[count($output)-1] == 'nothing to commit, working directory clean';
}
/**
* @param string $command
* @throws RuntimeException
*/
protected function execute($command)
{
$command = 'git -C ' . escapeshellarg($this->repositoryPath) . ' ' . $command;
if (DIRECTORY_SEPARATOR == '/') {
$command = 'LC_ALL=en_US.UTF-8 ' . $command;
}
exec($command, $output, $returnValue);
if ($returnValue !== 0) {
throw new RuntimeException(implode("\r\n", $output));
}
return $output;
}
}
#!/bin/sh
#
# Configuration
#
# sass source
SASS_SOURCE_PATH="scss"
# sass options
SASS_OPTIONS="--source-map=true --style=nested"
# css target
CSS_TARGET_PATH="css-compiled"
#
# Check prerequisites
#
wtfile=$(command -v wt) || { echo "install wellington with 'brew install wellington"; exit 1; }
#
# Watch folder for changes
#
cd -P `pwd`
$wtfile compile "$SASS_SOURCE_PATH" -b "$CSS_TARGET_PATH" $SASS_OPTIONS
$wtfile watch "$SASS_SOURCE_PATH" -b "$CSS_TARGET_PATH" $SASS_OPTIONS
var webpack = require('webpack'),
path = require('path'),
exec = require('child_process').execSync,
UglifyJsPlugin = require('uglifyjs-webpack-plugin'),
isProd = process.env.NODE_ENV === 'production',
pwd = exec('pwd').toString(),
adminPath = path.resolve(pwd + '/../admin/themes/grav/app');
module.exports = {
entry: {
app: './app/main.js'
},
devtool: isProd ? false : 'eval-source-map',
target: 'web',
resolve: {
alias: {
admin: adminPath
}
},
output: {
path: path.resolve(__dirname, 'js'),
filename: '[name].js',
chunkFilename: 'vendor.js'
},
optimization: {
minimize: isProd,
minimizer: [
new UglifyJsPlugin({
uglifyOptions: {
compress: {
drop_console: true
},
dead_code: true
}
})
],
splitChunks: {
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: 1,
name: 'vendor',
enforce: true,
chunks: 'all'
}
}
}
},
plugins: [
new webpack.ProvidePlugin({
'fetch': 'imports-loader?this=>global!exports-loader?global.fetch!whatwg-fetch'
})
],
externals: {
jquery: 'jQuery',
'git-sync': 'GitSync',
'grav-config': 'GravAdmin'
},
module: {
rules: [
{enforce: 'pre', test: /\.json$/, loader: 'json-loader'},
{enforce: 'pre', test: /\.js$/, loader: 'eslint-loader', exclude: /node_modules/},
{test: /\.css$/, loader: 'style-loader!css-loader'},
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/,
query: {
presets: ['es2015', 'stage-3']
}
}
]
}
};
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment