',
+ '#suffix' => t('It is recommended to read the git diff before you commit.') . '
',
+ );
+
+ // Limit status to 1000 lines
+ $lines = explode("\n", $environment->git_status);
+ $count = count($lines);
+ if ($count > 100) {
+ $lines = array_slice($lines, 0, 100);
+ $lines[] = "# STATUS TRUNCATED. SHOWING 100 of $count LINES.";
+ }
+ $environment->git_status = implode("\n", $lines);
+
+ // Get git diff.
+ $environment->git_diff = trim(shell_exec("cd {$environment->repo_path}; git -c color.ui=always diff"));
+
+ $form['git_info']['status'] = array(
+ '#type' => 'markup',
+ '#markup' => theme('devshop_ascii', array(
+ 'output' => $environment->git_status
+ )),
+ );
+ $form['git_info']['diff'] = array(
+ '#type' => 'markup',
+ '#markup' => theme('devshop_ascii', array(
+ 'output' => $environment->git_diff
+ )),
+ );
+ $form['files'] = array(
+ '#description' => t('Check the boxes next to the files you wish to commit. If you leave all boxes unchecked, all new and changed files will be committed.'),
+ '#type' => 'checkboxes',
+ '#options' => $options,
+ '#element_validate' => array('aegir_commit_validate_files'),
+ );
+ $form['message'] = array(
+ '#title' => t('Message'),
+ '#rows' => 2,
+ '#type' => 'textarea',
+ '#description' => t('A commit message. A small automated timestamp will be added to indicate this was committed by devshop.'),
+ );
+ $form['push'] = array(
+ '#title' => t('Push code after committing.'),
+ '#type' => 'checkbox',
+ '#default_value' => 1,
+ );
+ $form['name'] = array(
+ '#type' => 'value',
+ '#value' => $user->name,
+ );
+ $form['mail'] = array(
+ '#type' => 'value',
+ '#value' => $user->mail,
+ );
+ return $form;
+}
+
+
+/**
+ * Converts list of files to json encode for hosting task arguments table.
+ * @param $element
+ * @param $form_state
+ */
+function aegir_commit_validate_files($element, &$form_state) {
+ if (!empty($element['#value'])) {
+ $values = json_encode(array_filter(array_values($element['#value'])));
+ form_set_value($element, $values, $form_state);
+ }
+}
+
+/**
+ * Implements hook_devshop_environment_menu().
+ *
+ * Defines the list of tasks that appear under the gear icon.
+ */
+function aegir_commit_devshop_environment_menu($environment) {
+
+ if ($environment->site && $environment->site_status == HOSTING_SITE_ENABLED) {
+ $items[] = 'commit';
+ }
+ return $items;
+}
+
+/**
+ * Implements drush_hook_pre_hosting_task()
+ *
+ * Runs before the command "hosting-task" is run. Passes "task_args" from
+ * the task node into "$task->options" which become drush options.
+ */
+function drush_aegir_commit_pre_hosting_task()
+{
+ $task =& drush_get_context('HOSTING_TASK');
+
+ if ($task->task_type != 'commit') {
+ return;
+ }
+ $task->options['files'] = $task->task_args['files'];
+ $task->options['message'] = $task->task_args['message'];
+ $task->options['push'] = $task->task_args['push'];
+ $task->options['name'] = $task->task_args['name'];
+ $task->options['mail'] = $task->task_args['mail'];
+}
+
diff --git a/modules/devshop/aegir_commit/hosting.feature.aegir_commit.inc b/modules/devshop/aegir_commit/hosting.feature.aegir_commit.inc
new file mode 100644
index 000000000..dae2e5b67
--- /dev/null
+++ b/modules/devshop/aegir_commit/hosting.feature.aegir_commit.inc
@@ -0,0 +1,16 @@
+ t('Commit Code'),
+ 'description' => t('Commit all or some of the changed files to the git repository.'),
+ 'status' => HOSTING_FEATURE_DISABLED,
+ 'module' => 'aegir_commit',
+ 'group' => 'advanced',
+ );
+ return $features;
+}
diff --git a/modules/devshop/aegir_download/aegir_download.info b/modules/devshop/aegir_download/aegir_download.info
new file mode 100644
index 000000000..6a3ff40ed
--- /dev/null
+++ b/modules/devshop/aegir_download/aegir_download.info
@@ -0,0 +1,4 @@
+name = Aegir Download
+description = Adds the ability to download packages to aegir hosted sites.
+core = 7.x
+dependencies[] = hosting_site
\ No newline at end of file
diff --git a/modules/devshop/aegir_download/aegir_download.module b/modules/devshop/aegir_download/aegir_download.module
new file mode 100644
index 000000000..9c2e6ada7
--- /dev/null
+++ b/modules/devshop/aegir_download/aegir_download.module
@@ -0,0 +1,84 @@
+ t('Download Modules'),
+ 'description' => t('Add modules or themes to your git repository.'),
+ 'dialog' => TRUE,
+ 'icon' => 'download'
+ );
+ return $tasks;
+}
+
+/**
+ * Implements hook_permission().
+ * @return array
+ */
+function aegir_download_permission() {
+ return array(
+ 'create download task' => array(
+ 'title' => t('create download task'),
+ 'description' => t('Create "download" task.'),
+ ),
+ );
+}
+
+/**
+ * @return mixed
+ */
+function hosting_task_download_form() {
+
+ $form['packages'] = array(
+ '#type' => 'textarea',
+ '#title' => t('Drupal modules or themes to download'),
+ '#description' => '
' . t('Enter the names of the drupal module or themes you would like to download to your project. The names must match the package system name: If you want the module from http://drupal.org/project/views, enter "views" into this field. Separate multiple packages with a space.') . '
' . t('If you enter the name of an existing module, it will overwrite your old version. This is a good way to update your modules. Run Update.php to ensure smooth deployment.') . '
',
+ );
+ $form['commit'] = array(
+ '#title' => t('Commit to git.'),
+ '#type' => 'checkbox',
+ '#default_value' => 1,
+ );
+ $form['message'] = array(
+ '#type' => 'textarea',
+ '#title' => t('Git Commit Message'),
+ '#description' => t('Enter a message to use in the git commit message.'),
+ );
+ $form['update'] = array(
+ '#title' => t('Run Database Updates'),
+ '#type' => 'checkbox',
+ '#default_value' => 1,
+ '#description' => t('If module updates occur, run update.php to update the database. Only use if you trust the modules you are downloading.'),
+ );
+ return $form;
+}
+
+/**
+ * Implements hook_devshop_environment_menu().
+ *
+ * Defines the list of tasks that appear under the gear icon.
+ */
+function aegir_download_devshop_environment_menu($environment) {
+
+ if ($environment->site && $environment->site_status == HOSTING_SITE_ENABLED) {
+ $items[] = 'download';
+ }
+ return $items;
+}
+
+function drush_aegir_download_pre_hosting_task()
+{
+ $task =& drush_get_context('HOSTING_TASK');
+ if ($task->task_type != 'download') {
+ return;
+ }
+
+ drush_log('[AEGIR] Download package enabled...', 'ok');
+
+ $task->options['packages'] = $task->task_args['packages'];
+ $task->options['commit'] = $task->task_args['commit'];
+ $task->options['message'] = $task->task_args['message'];
+ $task->options['update'] = $task->task_args['update'];
+}
diff --git a/modules/devshop/aegir_download/hosting.feature.aegir_download.inc b/modules/devshop/aegir_download/hosting.feature.aegir_download.inc
new file mode 100644
index 000000000..c5d501920
--- /dev/null
+++ b/modules/devshop/aegir_download/hosting.feature.aegir_download.inc
@@ -0,0 +1,16 @@
+ t('Download Modules'),
+ 'description' => t('Add modules or themes to your git repository.'),
+ 'status' => HOSTING_FEATURE_ENABLED,
+ 'module' => 'aegir_download',
+ 'group' => 'advanced',
+ );
+ return $features;
+}
diff --git a/modules/devshop/aegir_update/aegir_update.info b/modules/devshop/aegir_update/aegir_update.info
new file mode 100644
index 000000000..7482630bc
--- /dev/null
+++ b/modules/devshop/aegir_update/aegir_update.info
@@ -0,0 +1,4 @@
+name = Aegir Update
+description = Adds the ability to update your Drupal site using Aegir.
+core = 7.x
+dependencies[] = hosting_site
\ No newline at end of file
diff --git a/modules/devshop/aegir_update/aegir_update.module b/modules/devshop/aegir_update/aegir_update.module
new file mode 100644
index 000000000..70a4bc6d7
--- /dev/null
+++ b/modules/devshop/aegir_update/aegir_update.module
@@ -0,0 +1,84 @@
+ t('Update Drupal'),
+ 'description' => t('Upgrades drupal core and contrib to the latest versions.'),
+ 'dialog' => TRUE,
+ 'icon' => 'wrench'
+ );
+ return $tasks;
+}
+
+/**
+ * Implements hook_permission().
+ * @return array
+ */
+function aegir_update_permission() {
+ return array(
+ 'create update_drupal task' => array(
+ 'title' => t('create update_drupal task'),
+ 'description' => t('Create "Update Drupal" task.'),
+ ),
+ );
+}
+
+/**
+ * @return mixed
+ */
+function hosting_task_update_drupal_form() {
+ $form = array();
+ $form['note'] = array(
+ '#markup' => t('This will run the command drush pm-update on your Drupal site.'),
+ '#prefix' => '
',
+ '#suffix' => '
',
+ );
+ $form['warning'] = array(
+ '#markup' => t('Running this task may have unexpected consequences. It is not recommended to run on a production site.'),
+ '#prefix' => '
';
+ }
+ }
+
+ return system_settings_form($form);
+}
diff --git a/modules/devshop/devshop_cloud/drush/LICENSE b/modules/devshop/devshop_cloud/drush/LICENSE
deleted file mode 100644
index d6a93266f..000000000
--- a/modules/devshop/devshop_cloud/drush/LICENSE
+++ /dev/null
@@ -1,340 +0,0 @@
-GNU GENERAL PUBLIC LICENSE
- Version 2, June 1991
-
- Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
-
- Preamble
-
- The licenses for most software are designed to take away your
-freedom to share and change it. By contrast, the GNU General Public
-License is intended to guarantee your freedom to share and change free
-software--to make sure the software is free for all its users. This
-General Public License applies to most of the Free Software
-Foundation's software and to any other program whose authors commit to
-using it. (Some other Free Software Foundation software is covered by
-the GNU Lesser General Public License instead.) You can apply it to
-your programs, too.
-
- When we speak of free software, we are referring to freedom, not
-price. Our General Public Licenses are designed to make sure that you
-have the freedom to distribute copies of free software (and charge for
-this service if you wish), that you receive source code or can get it
-if you want it, that you can change the software or use pieces of it
-in new free programs; and that you know you can do these things.
-
- To protect your rights, we need to make restrictions that forbid
-anyone to deny you these rights or to ask you to surrender the rights.
-These restrictions translate to certain responsibilities for you if you
-distribute copies of the software, or if you modify it.
-
- For example, if you distribute copies of such a program, whether
-gratis or for a fee, you must give the recipients all the rights that
-you have. You must make sure that they, too, receive or can get the
-source code. And you must show them these terms so they know their
-rights.
-
- We protect your rights with two steps: (1) copyright the software, and
-(2) offer you this license which gives you legal permission to copy,
-distribute and/or modify the software.
-
- Also, for each author's protection and ours, we want to make certain
-that everyone understands that there is no warranty for this free
-software. If the software is modified by someone else and passed on, we
-want its recipients to know that what they have is not the original, so
-that any problems introduced by others will not reflect on the original
-authors' reputations.
-
- Finally, any free program is threatened constantly by software
-patents. We wish to avoid the danger that redistributors of a free
-program will individually obtain patent licenses, in effect making the
-program proprietary. To prevent this, we have made it clear that any
-patent must be licensed for everyone's free use or not licensed at all.
-
- The precise terms and conditions for copying, distribution and
-modification follow.
-
- GNU GENERAL PUBLIC LICENSE
- TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
-
- 0. This License applies to any program or other work which contains
-a notice placed by the copyright holder saying it may be distributed
-under the terms of this General Public License. The "Program", below,
-refers to any such program or work, and a "work based on the Program"
-means either the Program or any derivative work under copyright law:
-that is to say, a work containing the Program or a portion of it,
-either verbatim or with modifications and/or translated into another
-language. (Hereinafter, translation is included without limitation in
-the term "modification".) Each licensee is addressed as "you".
-
-Activities other than copying, distribution and modification are not
-covered by this License; they are outside its scope. The act of
-running the Program is not restricted, and the output from the Program
-is covered only if its contents constitute a work based on the
-Program (independent of having been made by running the Program).
-Whether that is true depends on what the Program does.
-
- 1. You may copy and distribute verbatim copies of the Program's
-source code as you receive it, in any medium, provided that you
-conspicuously and appropriately publish on each copy an appropriate
-copyright notice and disclaimer of warranty; keep intact all the
-notices that refer to this License and to the absence of any warranty;
-and give any other recipients of the Program a copy of this License
-along with the Program.
-
-You may charge a fee for the physical act of transferring a copy, and
-you may at your option offer warranty protection in exchange for a fee.
-
- 2. You may modify your copy or copies of the Program or any portion
-of it, thus forming a work based on the Program, and copy and
-distribute such modifications or work under the terms of Section 1
-above, provided that you also meet all of these conditions:
-
- a) You must cause the modified files to carry prominent notices
- stating that you changed the files and the date of any change.
-
- b) You must cause any work that you distribute or publish, that in
- whole or in part contains or is derived from the Program or any
- part thereof, to be licensed as a whole at no charge to all third
- parties under the terms of this License.
-
- c) If the modified program normally reads commands interactively
- when run, you must cause it, when started running for such
- interactive use in the most ordinary way, to print or display an
- announcement including an appropriate copyright notice and a
- notice that there is no warranty (or else, saying that you provide
- a warranty) and that users may redistribute the program under
- these conditions, and telling the user how to view a copy of this
- License. (Exception: if the Program itself is interactive but
- does not normally print such an announcement, your work based on
- the Program is not required to print an announcement.)
-
-These requirements apply to the modified work as a whole. If
-identifiable sections of that work are not derived from the Program,
-and can be reasonably considered independent and separate works in
-themselves, then this License, and its terms, do not apply to those
-sections when you distribute them as separate works. But when you
-distribute the same sections as part of a whole which is a work based
-on the Program, the distribution of the whole must be on the terms of
-this License, whose permissions for other licensees extend to the
-entire whole, and thus to each and every part regardless of who wrote it.
-
-Thus, it is not the intent of this section to claim rights or contest
-your rights to work written entirely by you; rather, the intent is to
-exercise the right to control the distribution of derivative or
-collective works based on the Program.
-
-In addition, mere aggregation of another work not based on the Program
-with the Program (or with a work based on the Program) on a volume of
-a storage or distribution medium does not bring the other work under
-the scope of this License.
-
- 3. You may copy and distribute the Program (or a work based on it,
-under Section 2) in object code or executable form under the terms of
-Sections 1 and 2 above provided that you also do one of the following:
-
- a) Accompany it with the complete corresponding machine-readable
- source code, which must be distributed under the terms of Sections
- 1 and 2 above on a medium customarily used for software interchange; or,
-
- b) Accompany it with a written offer, valid for at least three
- years, to give any third party, for a charge no more than your
- cost of physically performing source distribution, a complete
- machine-readable copy of the corresponding source code, to be
- distributed under the terms of Sections 1 and 2 above on a medium
- customarily used for software interchange; or,
-
- c) Accompany it with the information you received as to the offer
- to distribute corresponding source code. (This alternative is
- allowed only for noncommercial distribution and only if you
- received the program in object code or executable form with such
- an offer, in accord with Subsection b above.)
-
-The source code for a work means the preferred form of the work for
-making modifications to it. For an executable work, complete source
-code means all the source code for all modules it contains, plus any
-associated interface definition files, plus the scripts used to
-control compilation and installation of the executable. However, as a
-special exception, the source code distributed need not include
-anything that is normally distributed (in either source or binary
-form) with the major components (compiler, kernel, and so on) of the
-operating system on which the executable runs, unless that component
-itself accompanies the executable.
-
-If distribution of executable or object code is made by offering
-access to copy from a designated place, then offering equivalent
-access to copy the source code from the same place counts as
-distribution of the source code, even though third parties are not
-compelled to copy the source along with the object code.
-
- 4. You may not copy, modify, sublicense, or distribute the Program
-except as expressly provided under this License. Any attempt
-otherwise to copy, modify, sublicense or distribute the Program is
-void, and will automatically terminate your rights under this License.
-However, parties who have received copies, or rights, from you under
-this License will not have their licenses terminated so long as such
-parties remain in full compliance.
-
- 5. You are not required to accept this License, since you have not
-signed it. However, nothing else grants you permission to modify or
-distribute the Program or its derivative works. These actions are
-prohibited by law if you do not accept this License. Therefore, by
-modifying or distributing the Program (or any work based on the
-Program), you indicate your acceptance of this License to do so, and
-all its terms and conditions for copying, distributing or modifying
-the Program or works based on it.
-
- 6. Each time you redistribute the Program (or any work based on the
-Program), the recipient automatically receives a license from the
-original licensor to copy, distribute or modify the Program subject to
-these terms and conditions. You may not impose any further
-restrictions on the recipients' exercise of the rights granted herein.
-You are not responsible for enforcing compliance by third parties to
-this License.
-
- 7. If, as a consequence of a court judgment or allegation of patent
-infringement or for any other reason (not limited to patent issues),
-conditions are imposed on you (whether by court order, agreement or
-otherwise) that contradict the conditions of this License, they do not
-excuse you from the conditions of this License. If you cannot
-distribute so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you
-may not distribute the Program at all. For example, if a patent
-license would not permit royalty-free redistribution of the Program by
-all those who receive copies directly or indirectly through you, then
-the only way you could satisfy both it and this License would be to
-refrain entirely from distribution of the Program.
-
-If any portion of this section is held invalid or unenforceable under
-any particular circumstance, the balance of the section is intended to
-apply and the section as a whole is intended to apply in other
-circumstances.
-
-It is not the purpose of this section to induce you to infringe any
-patents or other property right claims or to contest validity of any
-such claims; this section has the sole purpose of protecting the
-integrity of the free software distribution system, which is
-implemented by public license practices. Many people have made
-generous contributions to the wide range of software distributed
-through that system in reliance on consistent application of that
-system; it is up to the author/donor to decide if he or she is willing
-to distribute software through any other system and a licensee cannot
-impose that choice.
-
-This section is intended to make thoroughly clear what is believed to
-be a consequence of the rest of this License.
-
- 8. If the distribution and/or use of the Program is restricted in
-certain countries either by patents or by copyrighted interfaces, the
-original copyright holder who places the Program under this License
-may add an explicit geographical distribution limitation excluding
-those countries, so that distribution is permitted only in or among
-countries not thus excluded. In such case, this License incorporates
-the limitation as if written in the body of this License.
-
- 9. The Free Software Foundation may publish revised and/or new versions
-of the General Public License from time to time. Such new versions will
-be similar in spirit to the present version, but may differ in detail to
-address new problems or concerns.
-
-Each version is given a distinguishing version number. If the Program
-specifies a version number of this License which applies to it and "any
-later version", you have the option of following the terms and conditions
-either of that version or of any later version published by the Free
-Software Foundation. If the Program does not specify a version number of
-this License, you may choose any version ever published by the Free Software
-Foundation.
-
- 10. If you wish to incorporate parts of the Program into other free
-programs whose distribution conditions are different, write to the author
-to ask for permission. For software which is copyrighted by the Free
-Software Foundation, write to the Free Software Foundation; we sometimes
-make exceptions for this. Our decision will be guided by the two goals
-of preserving the free status of all derivatives of our free software and
-of promoting the sharing and reuse of software generally.
-
- NO WARRANTY
-
- 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
-FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
-OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
-PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
-OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
-MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
-TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
-PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
-REPAIR OR CORRECTION.
-
- 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
-WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
-REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
-INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
-OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
-TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
-YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
-PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
-POSSIBILITY OF SUCH DAMAGES.
-
- END OF TERMS AND CONDITIONS
-
- How to Apply These Terms to Your New Programs
-
- If you develop a new program, and you want it to be of the greatest
-possible use to the public, the best way to achieve this is to make it
-free software which everyone can redistribute and change under these terms.
-
- To do so, attach the following notices to the program. It is safest
-to attach them to the start of each source file to most effectively
-convey the exclusion of warranty; and each file should have at least
-the "copyright" line and a pointer to where the full notice is found.
-
- {description}
- Copyright (C) {year} {fullname}
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along
- with this program; if not, write to the Free Software Foundation, Inc.,
- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-
-Also add information on how to contact you by electronic and paper mail.
-
-If the program is interactive, make it output a short notice like this
-when it starts in an interactive mode:
-
- Gnomovision version 69, Copyright (C) year name of author
- Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
- This is free software, and you are welcome to redistribute it
- under certain conditions; type `show c' for details.
-
-The hypothetical commands `show w' and `show c' should show the appropriate
-parts of the General Public License. Of course, the commands you use may
-be called something other than `show w' and `show c'; they could even be
-mouse-clicks or menu items--whatever suits your program.
-
-You should also get your employer (if you work as a programmer) or your
-school, if any, to sign a "copyright disclaimer" for the program, if
-necessary. Here is a sample; alter the names:
-
- Yoyodyne, Inc., hereby disclaims all copyright interest in the program
- `Gnomovision' (which makes passes at compilers) written by James Hacker.
-
- {signature of Ty Coon}, 1 April 1989
- Ty Coon, President of Vice
-
-This General Public License does not permit incorporating your program into
-proprietary programs. If your program is a subroutine library, you may
-consider it more useful to permit linking proprietary applications with the
-library. If this is what you want to do, use the GNU Lesser General
-Public License instead of this License.
-
diff --git a/modules/devshop/devshop_cloud/drush/Provision/Service/provider.php b/modules/devshop/devshop_cloud/drush/Provision/Service/provider.php
deleted file mode 100644
index d22ffecb4..000000000
--- a/modules/devshop/devshop_cloud/drush/Provision/Service/provider.php
+++ /dev/null
@@ -1,50 +0,0 @@
-setProperty('provider');
- $context->setProperty('provider_options');
- $context->setProperty('provider_data');
- $context->setProperty('provider_server_identifier');
- }
-
- /**
- * Stub for init_server();
- *
- * Call from child classes:
- * parent::init_server();
- *
- * This function is called many times during a server verify.
- * Use sparingly.
- */
- function init_server() {
- }
-
- /**
- * Saves server options to drush options so they will be picked up by
- * devshop_cloud_post_hosting_verify_task()
- */
- function verify_server_cmd() {
- drush_set_option('provider_data', $this->server->provider_data);
- drush_set_option('provider_server_identifier', $this->server->provider_server_identifier);
-
- if (!empty($this->server->ip_addresses)) {
- drush_set_option('ip_addresses', $this->server->ip_addresses);
- }
- }
-
- static function option_documentation() {
- return array(
- '--provider' => 'The provider of this server. Must match an available Provision_Service_provider',
- '--provider_options' => 'An array of options to send to the provider.',
- );
- }
-}
diff --git a/modules/devshop/devshop_cloud/drush/Provision/Service/provider/SoftLayer/softlayer-api-php-client/.gitignore b/modules/devshop/devshop_cloud/drush/Provision/Service/provider/SoftLayer/softlayer-api-php-client/.gitignore
deleted file mode 100644
index be7384552..000000000
--- a/modules/devshop/devshop_cloud/drush/Provision/Service/provider/SoftLayer/softlayer-api-php-client/.gitignore
+++ /dev/null
@@ -1,5 +0,0 @@
-.DS_Store
-*.swp
-Thumbs.db
-.svn
-._*
diff --git a/modules/devshop/devshop_cloud/drush/Provision/Service/provider/SoftLayer/softlayer-api-php-client/LICENSE.textile b/modules/devshop/devshop_cloud/drush/Provision/Service/provider/SoftLayer/softlayer-api-php-client/LICENSE.textile
deleted file mode 100644
index cd0f96d85..000000000
--- a/modules/devshop/devshop_cloud/drush/Provision/Service/provider/SoftLayer/softlayer-api-php-client/LICENSE.textile
+++ /dev/null
@@ -1,9 +0,0 @@
-Copyright (c) 2009 - 2010, "SoftLayer Technologies, Inc.":http://www.softlayer.com/ 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 SoftLayer Technologies, Inc. nor the names of its 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.
diff --git a/modules/devshop/devshop_cloud/drush/Provision/Service/provider/SoftLayer/softlayer-api-php-client/README.textile b/modules/devshop/devshop_cloud/drush/Provision/Service/provider/SoftLayer/softlayer-api-php-client/README.textile
deleted file mode 100644
index b3f1851af..000000000
--- a/modules/devshop/devshop_cloud/drush/Provision/Service/provider/SoftLayer/softlayer-api-php-client/README.textile
+++ /dev/null
@@ -1,98 +0,0 @@
-h1. A SoftLayer API PHP client.
-
-h2. Overview
-
-The SoftLayer API PHP client classes provide a simple method for connecting to and making calls from the SoftLayer API and provides support for many of the SoftLayer API's features. Method calls and client management are handled by the PHP SOAP and XML-RPC extensions.
-
-Making API calls using the SoftLayer_SoapClient or SoftLayer_XmlrpcClient classes is done in the following steps:
-
-# Instantiate a new SoftLayer_SoapClient or SoftLayer_XmlrpcClient object using the SoftLayer_SoapClient::getClient() or SoftLayer_XmlrpcClient::getClient() methods. Provide the name of the service that you wish to query, an optional id number of the object that you wish to instantiate, your SoftLayer API username, your SoftLayer API key, and an optional API endpoint base URL. The client classes default to connect over the public Internet. Enter SoftLayer_SoapClient::API_PRIVATE_ENDPOINT or SoftLayer_XmlrpcClient::API_PRIVATE_ENDPOINT to connect to the API over SoftLayer's private network. The system making API calls must be connected to SoftLayer's private network (eg. purchased from SoftLayer or connected via VPN) in order to use the private network API endpoints.
-# Define and add optional headers to the client, such as object masks and result limits.
-# Call the API method you wish to call as if it were local to your client object. This class throws exceptions if it's unable to execute a query, so it's best to place API method calls in try / catch statements for proper error handling.
-
-Once your method is executed you may continue using the same client if you need to connect to the same service or define another client object if you wish to work with multiple services at once.
-
-The most up to date version of this library can be found on the SoftLayer github public repositories: "http://github.com/softlayer/":http://github.com/softlayer/ . Please post to the SoftLayer forums <"http://forums.softlayer.com/":http://forums.softlayer.com/> or open a support ticket in the SoftLayer customer portal if you have any questions regarding use of this library.
-
-h2. System Requirements
-
-The SoftLayer_SoapClient class requires at least PHP 5.2.3 and the PHP SOAP enxtension installed. The SoftLayer_Xmlrpc class requires PHP at least PHP 5 and the PHP XML-RPC extension installed.
-
-A valid API username and key are required to call the SoftLayer API. A connection to the SoftLayer private network is required to connect to SoftLayer's private network API endpopints.
-
-h2. Installation
-
-Download and copy the SoftLayer API client to a directory local to your PHP project or a path within your PHP installation's include_path.
-
-h2. Usage
-
-These examples use the SoftLayer_SoapClient class. If you wish to use the XML-RPC API then replace mentions of SoapClient.class.php with XmlrpcClient.class.php and SoftLayer_SoapClient with SoftLayer_XmlrpcClient.
-
-Here's a simple usage example that retrieves account information by calling the "getObject()":http://sldn.softlayer.com/reference/services/SoftLayer_Account/getObject method in the "SoftLayer_Account":http://sldn.softlayer.com/reference/services/SoftLayer_Account service:
-
-
-
-For a more complex example we'll retrieve a support ticket with id 123456 along with the ticket's updates, the user it's assigned to, the servers attached to it, and the datacenter those servers are in. We'll retrieve our extra information using a nested object mask. After we have the ticket we'll update it with the text 'Hello!'.
-
-
-updates;
-$objectMask->assignedUser;
-$objectMask->attachedHardware->datacenter;
-$client->setObjectMask($objectMask);
-
-// Retrieve the ticket record
-try {
- $ticket = $client->getObject();
-} catch (Exception $e) {
- die('Unable to retrieve ticket record: ' . $e->getMessage());
-}
-
-// Update the ticket
-$update = new stdClass();
-$update->entry = 'Hello!';
-
-try {
- $update = $client->addUpdate($update);
- echo "Updated ticket 123456. The new update's id is " . $update[0]->id . '.');
-} catch (Exception $e) {
- die('Unable to update ticket: ' . $e->getMessage());
-}
-
-
-h2. Author
-
-This software is written by the SoftLayer Development Team <"sldn@softlayer.com":mailto:sldn@softlayer.com>.
-
-h2. Copyright
-
-This software is Copyright (c) 2009 - 2010 "SoftLayer Technologies, Inc":http://www.softlayer.com/. See the bundled LICENSE.textile file for more information.
diff --git a/modules/devshop/devshop_cloud/drush/Provision/Service/provider/SoftLayer/softlayer-api-php-client/SoftLayer/Common/ObjectMask.class.php b/modules/devshop/devshop_cloud/drush/Provision/Service/provider/SoftLayer/softlayer-api-php-client/SoftLayer/Common/ObjectMask.class.php
deleted file mode 100644
index b470c6df2..000000000
--- a/modules/devshop/devshop_cloud/drush/Provision/Service/provider/SoftLayer/softlayer-api-php-client/SoftLayer/Common/ObjectMask.class.php
+++ /dev/null
@@ -1,84 +0,0 @@
-datacenter = new StdClass();
- * $objectMask->serverRoom = new StdClass();
- * $objectMask->provisionDate = new StdClass();
- * $objectMask->softwareComponents = new StdClass();
- * $objectMask->softwareComponents->passwords = new StdClass();
- *
- * Building an object mask using SoftLayer_ObjectMask is a bit easier to
- * type:
- *
- * $objectMask = new SoftLayer_ObjectMask();
- * $objectMask->datacenter;
- * $objectMask->serverRoom;
- * $objectMask->provisionDate;
- * $objectMask->sofwareComponents->passwords;
- *
- * Use SoftLayer_SoapClient::setObjectMask() to set these object masks before
- * making your SoftLayer API calls.
- *
- * For more on object mask usage in the SoftLayer API please see
- * http://sldn.softlayer.com/article/Using_Object_Masks_in_the_SoftLayer_API .
- *
- * The most up to date version of this library can be found on the SoftLayer
- * github public repositories: http://github.com/softlayer/ . Please post to
- * the SoftLayer forums or open a support ticket
- * in the SoftLayer customer portal if you have any questions regarding use of
- * this library.
- *
- * @author SoftLayer Technologies, Inc.
- * @copyright Copyright (c) 2009 - 2010, Softlayer Technologies, Inc
- * @license http://sldn.softlayer.com/article/License
- * @see SoftLayer_SoapClient::setObjectMask()
- * @see SoftLayer_XmlrpcClient::setObjectMask()
- */
-class SoftLayer_ObjectMask
-{
- /**
- * Define an object mask value
- *
- * @param string $var
- */
- public function __get($var)
- {
- $this->{$var} = new SoftLayer_ObjectMask();
-
- return $this->{$var};
- }
-}
diff --git a/modules/devshop/devshop_cloud/drush/Provision/Service/provider/SoftLayer/softlayer-api-php-client/SoftLayer/SoapClient.class.php b/modules/devshop/devshop_cloud/drush/Provision/Service/provider/SoftLayer/softlayer-api-php-client/SoftLayer/SoapClient.class.php
deleted file mode 100644
index 68b3c9700..000000000
--- a/modules/devshop/devshop_cloud/drush/Provision/Service/provider/SoftLayer/softlayer-api-php-client/SoftLayer/SoapClient.class.php
+++ /dev/null
@@ -1,503 +0,0 @@
- or open a support ticket
- * in the SoftLayer customer portal if you have any questions regarding use of
- * this library.
- *
- * @author SoftLayer Technologies, Inc.
- * @copyright Copyright (c) 2009 - 2010, Softlayer Technologies, Inc
- * @license http://sldn.softlayer.com/article/License
- * @link http://sldn.softlayer.com/article/The_SoftLayer_API The SoftLayer API
- * @see SoftLayer_SoapClient_AsynchronousAction
- */
-class Softlayer_SoapClient extends SoapClient
-{
- /**
- * Your SoftLayer API username. You may overide this value when calling
- * getClient().
- *
- * @var string
- */
- const API_USER = 'set me';
-
- /**
- * Your SoftLayer API user's authentication key. You may overide this value
- * when calling getClient().
- *
- * @link https://manage.softlayer.com/Administrative/apiKeychain API key management in the SoftLayer customer portal
- * @var string
- */
- const API_KEY = 'set me';
-
- /**
- * The base URL of the SoftLayer SOAP API's WSDL files over the public
- * Internet.
- *
- * @var string
- */
- const API_PUBLIC_ENDPOINT = 'https://api.softlayer.com/soap/v3/';
-
- /**
- * The base URL of the SoftLayer SOAP API's WSDL files over SoftLayer's
- * private network.
- *
- * @var string
- */
- const API_PRIVATE_ENDPOINT = 'http://api.service.softlayer.com/soap/v3/';
-
- /**
- * The namespace to use for calls to the API
- *
- * $var string
- */
- const DEFAULT_NAMESPACE = 'http://api.service.softlayer.com/soap/v3/';
-
-
- /**
- * The API endpoint base URL used by the client.
- *
- * @var string
- */
- const API_BASE_URL = SoftLayer_SoapClient::API_PUBLIC_ENDPOINT;
-
- /**
- * An optional SOAP timeout if you want to set a timeout independent of
- * PHP's socket timeout.
- *
- * @var int
- */
- const SOAP_TIMEOUT = null;
-
- /**
- * The SOAP headers to send along with a SoftLayer API call
- *
- * @var array
- */
- protected $_headers = array();
-
- /**
- * The name of the SoftLayer API service you wish to query.
- *
- * @link http://sldn.softlayer.com/reference/services A list of SoftLayer API services
- * @var string
- */
- protected $_serviceName;
-
- /**
- * The base URL to the SoftLayer API's WSDL files being used by this
- * client.
- *
- * @var string
- */
- protected $_endpointUrl;
-
- /**
- * Whether or not the current call is an asynchronous call.
- *
- * @var bool
- */
- protected $_asynchronous = false;
-
- /**
- * The object that handles asynchronous calls if the current call is an
- * asynchronous call.
- *
- * @var SoftLayer_SoapClient_AsynchronousAction
- */
- private $_asyncAction = null;
-
- /**
- * If making an asynchronous call, then this is the name of the function
- * we're calling.
- *
- * @var string
- */
- public $asyncFunctionName = null;
-
- /**
- * If making an asynchronous call, then this is the result of an
- * asynchronous call as retuned from the
- * SoftLayer_SoapClient_AsynchronousAction class.
- *
- * @var object
- */
- private $_asyncResult = null;
-
- /**
- * Used when making asynchronous calls.
- *
- * @var bool
- */
- public $oneWay;
-
- /**
- * Execute a SoftLayer API method
- *
- * @return object
- */
- public function __call($functionName, $arguments = null)
- {
- // Determine if we shoud be making an asynchronous call. If so strip
- // "Async" from the end of the method name.
- if ($this->_asyncResult == null) {
- $this->_asynchronous = false;
- $this->_asyncAction = null;
-
- if (preg_match('/Async$/', $functionName) == 1) {
- $this->_asynchronous = true;
- $functionName = str_replace('Async', '', $functionName);
-
- $this->asyncFunctionName = $functionName;
- }
- }
-
- try {
- $result = parent::__call($functionName, $arguments, null, $this->_headers, null);
- } catch (SoapFault $e) {
- throw new Exception('There was an error querying the SoftLayer API: ' . $e->getMessage());
- }
-
- if ($this->_asynchronous == true) {
- return $this->_asyncAction;
- }
-
- // remove the resultLimit header if they set it
- $this->removeHeader('resultLimit');
-
- return $result;
- }
-
- /**
- * Create a SoftLayer API SOAP Client
- *
- * Retrieve a new SoftLayer_SoapClient object for a specific SoftLayer API
- * service using either the class' constants API_USER and API_KEY or a
- * custom username and API key for authentication. Provide an optional id
- * value if you wish to instantiate a particular SoftLayer API object.
- *
- * @param string $serviceName The name of the SoftLayer API service you wish to query
- * @param int $id An optional object id if you're instantiating a particular SoftLayer API object. Setting an id defines this client's initialization parameter header.
- * @param string $username An optional API username if you wish to bypass SoftLayer_SoapClient's built-in username.
- * @param string $username An optional API key if you wish to bypass SoftLayer_SoapClient's built-in API key.
- * @param string $endpointUrl The API endpoint base URL you wish to connect to. Set this to SoftLayer_SoapClient::API_PRIVATE_ENDPOINT to connect via SoftLayer's private network.
- * @return SoftLayer_SoapClient
- */
- public static function getClient($serviceName, $id = null, $username = null, $apiKey = null, $endpointUrl = null)
- {
- $serviceName = trim($serviceName);
-
- if ($serviceName == null) {
- throw new Exception('Please provide a SoftLayer API service name.');
- }
-
- /*
- * Default to use the public network API endpoint, otherwise use the
- * endpoint defined in API_PUBLIC_ENDPOINT, otherwise use the one
- * provided by the user.
- */
- if (isset($endpointUrl)) {
- $endpointUrl = trim($endpointUrl);
-
- if ($endpointUrl == null) {
- throw new Exception('Please provide a valid API endpoint.');
- }
- } elseif (self::API_BASE_URL != null) {
- $endpointUrl = self::API_BASE_URL;
- } else {
- $endpointUrl = SoftLayer_SoapClient::API_PUBLIC_ENDPOINT;
- }
-
- if (is_null(self::SOAP_TIMEOUT)) {
- $soapClient = new SoftLayer_SoapClient($endpointUrl . $serviceName . '?wsdl');
- } else {
- $soapClient = new SoftLayer_SoapClient($endpointUrl . $serviceName . '?wsdl', array('connection_timeout' => self::SOAP_TIMEOUT));
- }
-
- $soapClient->_serviceName = $serviceName;
- $soapClient->_endpointUrl = $endpointUrl;
-
- if ($username != null && $apiKey != null) {
- $soapClient->setAuthentication($username, $apiKey);
- } else {
- $soapClient->setAuthentication(self::API_USER, self::API_KEY);
- }
-
- if ($id !== null) {
- $soapClient->setInitParameter($id);
- }
-
- return $soapClient;
- }
-
- /**
- * Set a SoftLayer API call header
- *
- * Every header defines a customization specific to an SoftLayer API call.
- * Most API calls require authentication and initialization parameter
- * headers, but can also include optional headers such as object masks and
- * result limits if they're supported by the API method you're calling.
- *
- * @see removeHeader()
- * @param string $name The name of the header you wish to set
- * @param object $value The object you wish to set in this header
- * @return SoftLayer_SoapClient
- */
- public function addHeader($name, $value)
- {
- $this->_headers[$name] = new SoapHeader(self::DEFAULT_NAMESPACE, $name, $value);
- return $this;
- }
-
- /**
- * Remove a SoftLayer API call header
- *
- * Removing headers may cause API queries to fail.
- *
- * @see addHeader()
- * @param string $name The name of the header you wish to remove
- * @return SoftLayer_SoapClient
- */
- public function removeHeader($name)
- {
- unset($this->_headers[$name]);
- return $this;
- }
-
- /**
- * Set a user and key to authenticate a SoftLayer API call
- *
- * Use this method if you wish to bypass the API_USER and API_KEY class
- * constants and set custom authentication per API call.
- *
- * @link https://manage.softlayer.com/Administrative/apiKeychain API key management in the SoftLayer customer portal
- * @param string $username
- * @param string $apiKey
- * @return SoftLayer_SoapClient
- */
- public function setAuthentication($username, $apiKey)
- {
- $username = trim($username);
- $apiKey = trim($apiKey);
-
- if ($username == null) {
- throw new Exception('Please provide a SoftLayer API username.');
- }
-
- if ($apiKey == null) {
- throw new Exception('Please provide a SoftLayer API key.');
- }
-
- $header = new stdClass();
- $header->username = $username;
- $header->apiKey = $apiKey;
-
- $this->addHeader('authenticate', $header);
- return $this;
- }
-
-
- /**
- * Set an initialization parameter header on a SoftLayer API call
- *
- * Initialization parameters instantiate a SoftLayer API service object to
- * act upon during your API method call. For instance, if your account has a
- * server with id number 1234, then setting an initialization parameter of
- * 1234 in the SoftLayer_Hardware_Server Service instructs the API to act on
- * server record 1234 in your method calls.
- *
- * @link http://sldn.softlayer.com/article/Using_Initialization_Parameters_in_the_SoftLayer_API Using Initialization Parameters in the SoftLayer API
- * @param int $id The ID number of the SoftLayer API object you wish to instantiate.
- * @return SoftLayer_SoapClient
- */
- public function setInitParameter($id)
- {
- $id = trim($id);
-
- if (!is_null($id)) {
- $initParameters = new stdClass();
- $initParameters->id = $id;
- $this->addHeader($this->_serviceName . 'InitParameters', $initParameters);
- }
-
- return $this;
- }
-
- /**
- * Set an object mask to a SoftLayer API call
- *
- * Use an object mask to retrieve data related your API call's result.
- * Object masks are skeleton objects or strings that define nested relational
- * properties to retrieve along with an object's local properties.
- *
- * @see SoftLayer_ObjectMask
- * @link http://sldn.softlayer.com/article/Using-Object-Masks-SoftLayer-API Using object masks in the SoftLayer API
- * @param object $mask The object mask you wish to define
- * @return SoftLayer_SoapClient
- */
- public function setObjectMask($mask)
- {
- if (!is_null($mask)) {
- $header = 'SoftLayer_ObjectMask';
-
- if ($mask instanceof SoftLayer_ObjectMask) {
- $header = sprintf('%sObjectMask', $this->_serviceName);
- }
-
- $objectMask = new stdClass();
- $objectMask->mask = $mask;
- $this->addHeader($header, $objectMask);
- }
-
- return $this;
- }
-
- /**
- * Set an object filter to a SoftLayer API call
- *
- * Use an object filter to limit what data you get back
- * from the API. Very similar to objectMasks
- *
- * @see SoftLayer_ObjectMask
- * @param object $filter The object filter you wish to define
- * @return SoftLayer_SoapClient
- */
- public function setObjectFilter($objectFilter)
- {
- if (!is_null($objectFilter)) {
- $header = sprintf('%sObjectFilter', $this->_serviceName);
- $this->addHeader($header, $objectFilter);
- }
- return $this;
- }
- /**
- * Set a result limit on a SoftLayer API call
- *
- * Many SoftLayer API methods return a group of results. These methods
- * support a way to limit the number of results retrieved from the SoftLayer
- * API in a way akin to an SQL LIMIT statement.
- *
- * @link http://sldn.softlayer.com/article/Using_Result_Limits_in_the_SoftLayer_API Using Result Limits in the SoftLayer API
- * @param int $limit The number of results to limit your SoftLayer API call to.
- * @param int $offset An optional offset to begin your SoftLayer API call's returned result set at.
- * @return SoftLayer_SoapClient
- */
- public function setResultLimit($limit, $offset = 0)
- {
- $resultLimit = new stdClass();
- $resultLimit->limit = intval($limit);
- $resultLimit->offset = intval($offset);
-
- $this->addHeader('resultLimit', $resultLimit);
- return $this;
- }
-
- /**
- * Process a SOAP request
- *
- * We've overwritten the PHP SoapClient's __doRequest() to allow processing
- * asynchronous SOAP calls. If an asynchronous call was deected in the
- * __call() method then send processing to the
- * SoftLayer_SoapClient_AsynchronousAction class. Otherwise use the
- * SoapClient's built-in __doRequest() method. The results of this method
- * are sent back to __call() for post-processing. Asynchronous calls use
- * handleAsyncResult() to send he results of the call back to __call().
- *
- * @return object
- */
- public function __doRequest($request, $location, $action, $version, $one_way = false)
- {
- // Don't make a call if we already have an asynchronous result.
- if ($this->_asyncResult != null) {
- $result = $this->_asyncResult;
- unset($this->_asyncResult);
-
- return $result;
- }
-
- if ($this->oneWay == true) {
- $one_way = true;
- $this->oneWay = false;
- }
-
- // Use either the SoapClient or SoftLayer_SoapClient_AsynchronousAction
- // class to handle the call.
- if ($this->_asynchronous == false) {
- $result = parent::__doRequest($request, $location, $action, $version, $one_way);
-
- return $result;
- } else {
- $this->_asyncAction = new SoftLayer_SoapClient_AsynchronousAction($this, $this->asyncFunctionName, $request, $location, $action);
- return '';
- }
- }
-
- /**
- * Process the results of an asynchronous call.
- *
- * The SoftLayer_SoapClient_AsynchronousAction class uses
- * handleAsyncResult() to return it's call resuls back to this classes'
- * __call() method for post-pocessing.
- *
- * @param string $functionName The name of the SOAP method called.
- * @param string $result The raw SOAP XML output from a SOAP call
- * @return object
- */
- public function handleAsyncResult($functionName, $result)
- {
- $this->_asynchronous = false;
- $this->_asyncResult = $result;
-
- return $this->__call($functionName, array());
- }
-}
diff --git a/modules/devshop/devshop_cloud/drush/Provision/Service/provider/SoftLayer/softlayer-api-php-client/SoftLayer/SoapClient/AsynchronousAction.class.php b/modules/devshop/devshop_cloud/drush/Provision/Service/provider/SoftLayer/softlayer-api-php-client/SoftLayer/SoapClient/AsynchronousAction.class.php
deleted file mode 100644
index 416ad3a24..000000000
--- a/modules/devshop/devshop_cloud/drush/Provision/Service/provider/SoftLayer/softlayer-api-php-client/SoftLayer/SoapClient/AsynchronousAction.class.php
+++ /dev/null
@@ -1,224 +0,0 @@
- receive style of transmission. Response
- * time for a call is dependent on the latency between the SOAP client and SOAP
- * server and the time required by the server to process the SOAP call. Sending
- * multiple SOAP calls in serial over the public Internet can be a time
- * consuming process.
- *
- * Asynchronous calls allow you to send multiple SOAP calls in parallel to the
- * SoftLayer API. Parallel calls reduce the latency involved handling multiple
- * calls to the time it takes for the longest SOAP call to execute, dramatically
- * reducing the time it takes to send multiple SOAP calls in most cases.
- *
- * Asynchronous calls are handled identically to standard API calls with two
- * differences:
- *
- * 1) The SoftLayer_SoapClient class knows to make an asynchronous call when the
- * method called ends with "Async". For example, to make a standard call to the
- * method getObject() you would execute $client->geObject(). It's asynchronous
- * counterpart is execued with the code $client->getObjectAsync(). Once the
- * asynchronous call is made the results of your API command are sent to this
- * classes' socket property.
- *
- * 2) The results of an asynchronous method call are stored in a
- * SoftLayer_SoapClient_AsynchronousAction object. Use the wait() method to
- * retrieve data off the internal socket and return the result back to the
- * SoftLayer_SoapClient for processing. For example if you wish to retrieve the
- * results of the method getObject() execute the following statements:
- *
- * $result = $client->getObjectAsync(); // Make the call and start geting data back.
- * $result = $result->wait(); // Return the results of the API call
- *
- * To chain multiple asynchronous requests together call multiple Async requests
- * in succession then call their associated wait() methods in succession.
- *
- * Here's a simple usage example that retrieves account information, a PDF of an
- * account's next invoice and enables VLAN spanning on that same account by
- * calling three methods in the SoftLayer_Account service in parallel:
- *
- * ----------
- *
- * // Initialize an API client for the SoftLayer_Account service.
- * $client = SoftLayer_SoapClient::getClient('SoftLayer_Account');
- *
- * try {
- * // Request our account information.
- * $account = $client->getObjectAsync();
- *
- * // Request a PDF of our next invoice. This can take much longer than
- * // getting simple account information.
- * $nextInvoicePdf = $client->getNextInvoicePdfAsync();
- *
- * // While we're at it we'll enable VLAN spanning on our account.
- * $vlanSpanResult = $client->setVlanSpanAsync(true);
- *
- * // The three requests are now processing in parallel. Use the wait()
- * // method to retrieve the resuls of our requests. The wait time involved
- * // is roughly the same time as the longest API call.
- * $account = $account->wait();
- * $nextInvoicePdf = $nextInvoicePdf->wait();
- * $vlanSpanResult = $vlanSpanResult->wait();
- *
- * // Finally, display our results.
- * var_dump($account);
- * var_dump($nextInvoicePdf);
- * var_dump($vlanSpanResult);
- * } catch (Exception $e) {
- * die('Unable to retrieve account information: ' . $e->getMessage());
- * }
- *
- * ----------
- *
- * The most up to date version of this library can be found on the SoftLayer
- * github public repositories: http://github.com/softlayer/ . Please post to
- * the SoftLayer forums or open a support ticket
- * in the SoftLayer customer portal if you have any questions regarding use of
- * this library.
- *
- * @author SoftLayer Technologies, Inc.
- * @copyright Copyright (c) 2009 - 2010, Softlayer Technologies, Inc
- * @license http://sldn.softlayer.com/article/License
- * @see SoftLayer_SoapClient
- */
-class SoftLayer_SoapClient_AsynchronousAction
-{
- /**
- * The SoftLayer SOAP client making an asynchronous call
- *
- * @var SoftLayer_SoapClient
- */
- protected $_soapClient;
-
- /**
- * The name of the function we're calling
- *
- * @var string
- */
- protected $_functionName;
-
- /**
- * A socket connection to the SoftLayer SOAP API
- *
- * @var resource
- */
- protected $_socket;
-
- /**
- * Perform an asynchgronous SoftLayer SOAP call
- *
- * Create a raw socket connection to the URL specified by the
- * SoftLayer_SoapClient class and send SOAP HTTP headers and request XML to
- * that socket. Throw exceptions if we're unable to make the socket
- * connection or send data to that socket.
- *
- * @param SoftLayer_SoapClient $soapClient The SoftLayer SOAP client making the asynchronous call.
- * @param string $functionName The name of the function we're calling.
- * @param string $request The full XML SOAP request we wish to make.
- * @param string $location The URL of the web service we wish to call.
- * @param string $action The value of the HTTP SOAPAction header in our SOAP call.
- */
- public function __construct($soapClient, $functionName, $request, $location, $action)
- {
- preg_match('%^(http(?:s)?)://(.*?)(/.*?)$%', $location, $matches);
-
- $this->_soapClient = $soapClient;
- $this->_functionName = $functionName;
-
- $protocol = $matches[1];
- $host = $matches[2];
- $endpoint = $matches[3];
-
- $headers = array(
- 'POST ' . $endpoint . ' HTTP/1.1',
- 'Host: ' . $host,
- 'User-Agent: PHP-SOAP/' . phpversion(),
- 'Content-Type: text/xml; charset=utf-8',
- 'SOAPAction: "' . $action . '"',
- 'Content-Length: ' . strlen($request),
- 'Connection: close',
- );
-
- if ($protocol == 'https') {
- $host = 'ssl://' . $host;
- $port = 443;
- } else {
- $port = 80;
- }
-
- $data = implode("\r\n", $headers) . "\r\n\r\n" . $request . "\r\n";
- $this->_socket = fsockopen($host, $port, $errorNumber, $errorMessage);
-
- if ($this->_socket === false) {
- $this->_socket = null;
- throw new Exception('Unable to make an asynchronous SoftLayer API call: ' . $errorNumber . ': ' . $errorMessage);
- }
-
- if (fwrite($this->_socket, $data) === false) {
- throw new Exception('Unable to write data to an asynchronous SoftLayer API call.');
- }
- }
-
- /**
- * Process and return the results of an asyncrhonous SoftLayer API call
- *
- * Read data from our socket and process the raw SOAP result from the
- * SoftLayer_SoapClient instance that made the asynchronous call. wait()
- * *must* be called in order to recieve the results from your API call.
- *
- * @return object
- */
- public function wait()
- {
- $soapResult = '';
-
- while (!feof($this->_socket)) {
- $soapResult .= fread($this->_socket, 8192);
- }
-
- // separate the SOAP result into headers and data.
- list($headers, $data) = explode("\r\n\r\n", $soapResult);
-
- return $this->_soapClient->handleAsyncResult($this->_functionName, $data);
- }
-
- /**
- * Close the socket created when the SOAP request was created.
- */
- public function __destruct()
- {
- if ($this->_socket != null) {
- fclose($this->_socket);
- }
- }
-}
diff --git a/modules/devshop/devshop_cloud/drush/Provision/Service/provider/SoftLayer/softlayer-api-php-client/SoftLayer/XmlrpcClient.class.php b/modules/devshop/devshop_cloud/drush/Provision/Service/provider/SoftLayer/softlayer-api-php-client/SoftLayer/XmlrpcClient.class.php
deleted file mode 100644
index 31db1313e..000000000
--- a/modules/devshop/devshop_cloud/drush/Provision/Service/provider/SoftLayer/softlayer-api-php-client/SoftLayer/XmlrpcClient.class.php
+++ /dev/null
@@ -1,437 +0,0 @@
- or open a support ticket
- * in the SoftLayer customer portal if you have any questions regarding use of
- * this library.
- *
- * @author SoftLayer Technologies, Inc.
- * @copyright Copyright (c) 2009 - 2010, Softlayer Technologies, Inc
- * @license http://sldn.softlayer.com/article/License
- * @link http://sldn.softlayer.com/article/The_SoftLayer_API The SoftLayer API
- */
-class Softlayer_XmlrpcClient
-{
- /**
- * Your SoftLayer API username. You may overide this value when calling
- * getClient().
- *
- * @var string
- */
- const API_USER = 'set me';
-
- /**
- * Your SoftLayer API user's authentication key. You may overide this value
- * when calling getClient().
- *
- * @link https://manage.softlayer.com/Administrative/apiKeychain API key management in the SoftLayer customer portal
- * @var string
- */
- const API_KEY = 'set me';
-
- /**
- * The base URL of SoftLayer XML-RPC API's public network endpoints.
- *
- * @var string
- */
- const API_PUBLIC_ENDPOINT = 'https://api.softlayer.com/xmlrpc/v3/';
-
- /**
- * The base URL of SoftLayer XML-RPC API's private network endpoints.
- *
- * @var string
- */
- const API_PRIVATE_ENDPOINT = 'http://api.service.softlayer.com/xmlrpc/v3/';
-
- /**
- * The API endpoint base URL used by the client.
- *
- * @var string
- */
- const API_BASE_URL = SoftLayer_XmlrpcClient::API_PUBLIC_ENDPOINT;
-
- /**
- * The headers to send along with a SoftLayer API call
- *
- * @var array
- */
- protected $_headers = array();
-
- /**
- * The name of the SoftLayer API service you wish to query.
- *
- * @link http://sldn.softlayer.com/reference/services A list of SoftLayer API services
- * @var string
- */
- protected $_serviceName;
-
- /**
- * The base URL of SoftLayer XML-RPC API's endpoints used by this client.
- *
- * @var string
- */
- protected $_endpointUrl;
-
- /**
- * Execute a SoftLayer API method
- *
- * @return object
- */
- public function __call($functionName, $arguments = null)
- {
- $request = array();
- $request[0] = array('headers' => $this->_headers);
- $request = array_merge($request, $arguments);
-
- try {
- $encodedRequest = xmlrpc_encode_request($functionName, $request);
-
- // Making the XML-RPC call and interpreting the response is adapted
- // from the PHP manual:
- // http://www.php.net/manual/en/function.xmlrpc-encode-request.php
- $context = stream_context_create(array(
- 'http' => array(
- 'method' => 'POST',
- 'header' => 'Content-Type: text/xml',
- 'content' => $encodedRequest
- )));
-
- $file = file_get_contents($this->_endpointUrl . $this->_serviceName, false, $context);
-
- if ($file === false) {
- throw new Exception('Unable to contact the SoftLayer API at ' . $this->_endpointUrl . $serviceName . '.');
- }
-
- $result = xmlrpc_decode($file);
- } catch (Exception $e) {
- throw new Exception('There was an error querying the SoftLayer API: ' . $e->getMessage());
- }
-
- if (is_array($result) && xmlrpc_is_fault($result)) {
- throw new Exception('There was an error querying the SoftLayer API: ' . $result['faultString']);
- }
-
- // remove the resultLimit header if they set it
- $this->removeHeader('resultLimit');
-
- return self::_convertToObject(self::_convertXmlrpcTypes($result));
- }
-
- /**
- * Create a SoftLayer API XML-RPC Client
- *
- * Retrieve a new SoftLayer_XmlrpcClient object for a specific SoftLayer API
- * service using either the class' constants API_USER and API_KEY or a
- * custom username and API key for authentication. Provide an optional id
- * value if you wish to instantiate a particular SoftLayer API object.
- *
- * @param string $serviceName The name of the SoftLayer API service you wish to query
- * @param int $id An optional object id if you're instantiating a particular SoftLayer API object. Setting an id defines this client's initialization parameter header.
- * @param string $username An optional API username if you wish to bypass SoftLayer_XmlrpcClient's built-in username.
- * @param string $username An optional API key if you wish to bypass SoftLayer_XmlrpcClient's built-in API key.
- * @param string $endpointUrl The API endpoint base URL you wish to connect to. Set this to SoftLayer_XmlrpcClient::API_PRIVATE_ENDPOINT to connect via SoftLayer's private network.
- * @return SoftLayer_XmlrpcClient
- */
- public static function getClient($serviceName, $id = null, $username = null, $apiKey = null, $endpointUrl = null)
- {
- $serviceName = trim($serviceName);
- $id = trim($id);
- $username = trim($username);
- $apiKey = trim($apiKey);
-
- if ($serviceName == null) {
- throw new Exception('Please provide a SoftLayer API service name.');
- }
-
- $client = new Softlayer_XmlrpcClient();
-
- /*
- * Default to use the public network API endpoint, otherwise use the
- * endpoint defined in API_PUBLIC_ENDPOINT, otherwise use the one
- * provided by the user.
- */
- if (isset($endpointUrl)) {
- $endpointUrl = trim($endpointUrl);
-
- if ($endpointUrl == null) {
- throw new Exception('Please provide a valid API endpoint.');
- }
-
- $client->_endpointUrl = $endpointUrl;
- } elseif (self::API_BASE_URL != null) {
- $client->_endpointUrl = self::API_BASE_URL;
- } else {
- $client->_endpointUrl = SoftLayer_XmlrpcClient::API_PUBLIC_ENDPOINT;
- }
-
- if ($username != null && $apiKey != null) {
- $client->setAuthentication($username, $apiKey);
- } else {
- $client->setAuthentication(self::API_USER, self::API_KEY);
- }
-
- $client->_serviceName = $serviceName;
-
- if ($id != null) {
- $client->setInitParameter($id);
- }
-
- return $client;
- }
-
- /**
- * Set a SoftLayer API call header
- *
- * Every header defines a customization specific to an SoftLayer API call.
- * Most API calls require authentication and initialization parameter
- * headers, but can also include optional headers such as object masks and
- * result limits if they're supported by the API method you're calling.
- *
- * @see removeHeader()
- * @param string $name The name of the header you wish to set
- * @param object $value The object you wish to set in this header
- * @return SoftLayer_XmlrpcClient
- */
- public function addHeader($name, $value)
- {
- if (is_object($value)) {
- $value = (array)$value;
- }
-
- $this->_headers[$name] = $value;
- return $this;
- }
-
- /**
- * Remove a SoftLayer API call header
- *
- * Removing headers may cause API queries to fail.
- *
- * @see addHeader()
- * @param string $name The name of the header you wish to remove
- * @return SoftLayer_XmlrpcClient
- */
- public function removeHeader($name)
- {
- unset($this->_headers[$name]);
- return $this;
- }
-
- /**
- * Set a user and key to authenticate a SoftLayer API call
- *
- * Use this method if you wish to bypass the API_USER and API_KEY class
- * constants and set custom authentication per API call.
- *
- * @link https://manage.softlayer.com/Administrative/apiKeychain API key management in the SoftLayer customer portal
- * @param string $username
- * @param string $apiKey
- * @return SoftLayer_XmlrpcClient
- */
- public function setAuthentication($username, $apiKey)
- {
- $username = trim($username);
- $apiKey = trim($apiKey);
-
- if ($username == null) {
- throw new Exception('Please provide a SoftLayer API username.');
- }
-
- if ($apiKey == null) {
- throw new Exception('Please provide a SoftLayer API key.');
- }
-
- $header = new stdClass();
- $header->username = $username;
- $header->apiKey = $apiKey;
-
- $this->addHeader('authenticate', $header);
- return $this;
- }
-
- /**
- * Set an initialization parameter header on a SoftLayer API call
- *
- * Initialization parameters instantiate a SoftLayer API service object to
- * act upon during your API method call. For instance, if your account has a
- * server with id number 1234, then setting an initialization parameter of
- * 1234 in the SoftLayer_Hardware_Server Service instructs the API to act on
- * server record 1234 in your method calls.
- *
- * @link http://sldn.softlayer.com/article/Using_Initialization_Parameters_in_the_SoftLayer_API Using Initialization Parameters in the SoftLayer API
- * @param int $id The ID number of the SoftLayer API object you wish to instantiate.
- * @return SoftLayer_XmlrpcClient
- */
- public function setInitParameter($id)
- {
- $id = trim($id);
-
- if (!is_null($id)) {
- $initParameters = new stdClass();
- $initParameters->id = $id;
- $this->addHeader($this->_serviceName . 'InitParameters', $initParameters);
- }
-
- return $this;
- }
-
- /**
- * Set an object mask to a SoftLayer API call
- *
- * Use an object mask to retrieve data related your API call's result.
- * Object masks are skeleton objects or strings that define nested relational
- * properties to retrieve along with an object's local properties.
- *
- * @see SoftLayer_ObjectMask
- * @link http://sldn.softlayer.com/article/Using-Object-Masks-SoftLayer-API Using object masks in the SoftLayer API
- * @param object $mask The object mask you wish to define
- * @return SoftLayer_SoapClient
- */
- public function setObjectMask($mask)
- {
- if (!is_null($mask)) {
- $header = 'SoftLayer_ObjectMask';
-
- if ($mask instanceof SoftLayer_ObjectMask) {
- $header = sprintf('%sObjectMask', $this->_serviceName);
- }
-
- $objectMask = new stdClass();
- $objectMask->mask = $mask;
- $this->addHeader($header, $objectMask);
- }
-
- return $this;
- }
-
- /**
- * Set a result limit on a SoftLayer API call
- *
- * Many SoftLayer API methods return a group of results. These methods
- * support a way to limit the number of results retrieved from the SoftLayer
- * API in a way akin to an SQL LIMIT statement.
- *
- * @link http://sldn.softlayer.com/article/Using_Result_Limits_in_the_SoftLayer_API Using Result Limits in the SoftLayer API
- * @param int $limit The number of results to limit your SoftLayer API call to.
- * @param int $offset An optional offset to begin your SoftLayer API call's returned result set at.
- * @return SoftLayer_XmlrpcClient
- */
- public function setResultLimit($limit, $offset = 0)
- {
- $resultLimit = new stdClass();
- $resultLimit->limit = intval($limit);
- $resultLimit->offset = intval($offset);
-
- $this->addHeader('resultLimit', $resultLimit);
- return $this;
- }
-
- /**
- * Remove PHP xmlrpc type definition structures from a decoded request array
- *
- * Certain xmlrpc types like base64 are decoded in PHP to a stdClass with a
- * scalar property containing the decoded value of the xmlrpc member and an
- * xmlrpc_type property describing which xmlrpc type is being described. This
- * function removes xmlrpc_type data and moves the scalar value into the root of
- * the xmlrpc value for known xmlrpc types.
- *
- * @param mixed $result The decoded xmlrpc request to process
- * @return mixed
- */
- private static function _convertXmlrpcTypes($result) {
- if (is_array($result)) {
-
- // Return case 1: The result is an empty array. Return the empty
- // array.
- if (count($result) == 0) {
- return $result;
- } else {
-
- // Return case 2: The result is a non-empty array. Loop through
- // array elements and recursively translate every element.
- // Return the fully translated array.
- foreach ($result as $key => $value) {
- $result[$key] = self::_convertXmlrpcTypes($value);
- }
-
- return $result;
- }
-
- // Return case 3: The result is an xmlrpc scalar. Convert it to a normal
- // variable and return it.
- } elseif (is_object($result) && $result->scalar != null && $result->xmlrpc_type != null) {
-
- // Convert known xmlrpc types, otherwise unset the value.
- switch ($result->xmlrpc_type) {
- case 'base64':
- return $result->scalar;
- break;
- default:
- return null;
- break;
- }
-
- // Return case 4: Otherwise the result is a non-array and non xml-rpc
- // scalar variable. Return it unmolested.
- } else {
- return $result;
- }
- }
-
- /**
- * Recursively convert an array to an object
- *
- * Since xmlrpc_decode_result returns an array, but we want an object
- * result, so cast all array parts in our result set as objects.
- *
- * @param mixed $result A result or portion of a result to convert
- * @return mixed
- */
- private static function _convertToObject($result) {
- return is_array($result) ? (object) array_map('SoftLayer_XmlrpcClient::_convertToObject', $result) : $result;
- }
-}
diff --git a/modules/devshop/devshop_cloud/drush/Provision/Service/provider/SoftLayer/softlayer-api-php-client/example.php b/modules/devshop/devshop_cloud/drush/Provision/Service/provider/SoftLayer/softlayer-api-php-client/example.php
deleted file mode 100644
index 9c4665257..000000000
--- a/modules/devshop/devshop_cloud/drush/Provision/Service/provider/SoftLayer/softlayer-api-php-client/example.php
+++ /dev/null
@@ -1,113 +0,0 @@
-, [username], [API key]);
- *
- * API Service: The name of the API service you wish to connect to.
- * id: An optional id to initialize your API service with, if you're
- * interacting with a specific object. If you don't need to specify
- * an id then pass null to the client.
- * username: Your SoftLayer API username.
- * API key: Your SoftLayer API key,
- */
-$client = SoftLayer_SoapClient::getClient('SoftLayer_Account', null, $apiUsername, $apiKey);
-
-/**
- * Once your client object is created you can call API methods for that service
- * directly against your client object. A call may throw an exception on error,
- * so it's best to try your call and catch exceptions.
- *
- * This example calls the getObject() method in the SoftLayer_Account API
- * service.
- * It retrieves basic account information, and is a great way to test your API
- * account and connectivity.
- */
-try {
- print_r($client->getObject());
-} catch (Exception $e) {
- die($e->getMessage());
-}
-
-/**
- * For a more complex example we’ll retrieve a support ticket with id 123456
- * along with the ticket’s updates, the user it’s assigned to, the servers
- * attached to it, and the datacenter those servers are in. We’ll retrieve our
- * extra information using a nested object mask. After we have the ticket we’ll
- * update it with the text ‘Hello!’.
- */
-
-// Declare an API client to connect to the SoftLayer_Ticket API service.
-$client = SoftLayer_SoapClient::getClient('SoftLayer_Ticket', 123456, $apiUsername, $apiKey);
-
-// Assign an object mask to our API client:
-$objectMask = new SoftLayer_ObjectMask();
-$objectMask->updates;
-$objectMask->assignedUser;
-$objectMask->attachedHardware->datacenter;
-$client->setObjectMask($objectMask);
-
-// Retrieve the ticket record.
-try {
- $ticket = $client->getObject();
- print_r($ticket);
-} catch (Exception $e) {
- die('Unable to retrieve ticket record: ' . $e->getMessage());
-}
-
-// Now update the ticket.
-$update = new stdClass();
-$update->entry = 'Hello!';
-
-try {
- $update = $client->addUpdate($update);
- echo "Updated ticket 123456. The new update's id is " . $update[0]->id . '.';
-} catch (Exception $e) {
- die('Unable to update ticket: ' . $e->getMessage());
-}
diff --git a/modules/devshop/devshop_cloud/drush/Provision/Service/provider/digitalocean.php b/modules/devshop/devshop_cloud/drush/Provision/Service/provider/digitalocean.php
deleted file mode 100644
index 48921b85b..000000000
--- a/modules/devshop/devshop_cloud/drush/Provision/Service/provider/digitalocean.php
+++ /dev/null
@@ -1,42 +0,0 @@
-server: Provision_Context_server
- */
- function save_server() {
-
- // Look for provider_server_identifier
- $server_identifier = $this->server->provider_server_identifier;
-
- // If server ID is already found, move on.
- if (!empty($server_identifier)) {
- drush_log('[DEVSHOP] Server Identifier Found: ' . $server_identifier . ' Not creating new server.', 'ok');
- }
- // If there is no server ID, create the server.
- else {
-
- drush_log('[DEVSHOP] Server Identifier not found. Creating new server!', 'ok');
-
- // Faking our provider_data response.
- $this->server->setProperty('provider_data', array(
- 'hello' => 'do',
- 'fake data' => 'from digitalocean',
- ));
-
- // Faking our provider server identifier.
- $this->server->setProperty('provider_server_identifier', '123456789');
-
- $this->server->setProperty('ip_addresses', array(
- '1.2.3.4'
- ));
-
- drush_log('[DEVSHOP] Server Identifier found: 123456789. Assumed server was created.', 'ok');
- }
- }
-}
\ No newline at end of file
diff --git a/modules/devshop/devshop_cloud/drush/Provision/Service/provider/rackspace.php b/modules/devshop/devshop_cloud/drush/Provision/Service/provider/rackspace.php
deleted file mode 100644
index e4641db63..000000000
--- a/modules/devshop/devshop_cloud/drush/Provision/Service/provider/rackspace.php
+++ /dev/null
@@ -1,137 +0,0 @@
-server->rackspace_id = drush_get_option('rackspace_id', '');
-// $this->server->rackspace_image = drush_get_option('rackspace_image', '');
-// $this->server->rackspace_flavor = drush_get_option('rackspace_flavor', '');
-// $this->server->attributes_json = drush_get_option('attributes_json', '');
-// $this->server->role = drush_get_option('role', '');
-//
-// }
-//
-// /**
-// * This is run immediately after provision saves the server config files.
-// *
-// *
-// * Provision client home path /var/aegir/clients is writable.
-// * [DEVUDO] Verifying Server anotherfaker
-// */
-// function verify_server_cmd() {
-// drush_log('[DEVUDO] Verifying Server ' . d()->remote_host, 'ok');
-//
-// $server_fqdn = d()->remote_host;
-// $role = $this->server->role;
-// $rackspace_flavor = $this->server->rackspace_flavor; // 2
-// $rackspace_image = $this->server->rackspace_image;
-// $rackspace_id = $this->server->rackspace_id;
-// $attributes = $this->server->attributes_json;
-//
-// $ips = array();
-//
-// // Look for this chef node on Chef Server
-// drush_log("[DEVUDO] Looking for chef node $server_fqdn on chef server", 'ok');
-// $chef_node = shop_get_server($server_fqdn);
-//
-// // If shop_get_server() returns a string, knife node show didn't work.
-// if (is_string($chef_node)) {
-//
-// // If the error is NOT object not found, there was a more serious error
-// if (strpos($chef_node, 'ERROR: The object you are looking for could not be found') !== 0){
-// return drush_set_error(DRUSH_DEVUDO_ERROR, '[DEVUDO] knife failed: ' . $chef_node);
-// }
-// // Otherwise, we just don't have a chef node of that name yet.
-// // So, create a new server.
-// drush_log("[DEVUDO] Chef Node not found. Creating server...", 'ok');
-//
-// drush_log("[DEVUDO] Running: drush server-create $server_fqdn --role=$role --rackspace_flavor=$rackspace_flavor --rackspace_image=$rackspace_image --attributes=$attributes", 'ok');
-//
-// drush_set_option('rackspace_flavor', $rackspace_flavor);
-// drush_set_option('rackspace_image', $rackspace_image);
-// drush_set_option('role', $role);
-// drush_set_option('attributes', $attributes);
-//
-// $data = drush_shop_provision_server_create($server_fqdn);
-// $ips[] = $data['Public IP Address'];
-// $ips[] = $data['Private IP Address'];
-//
-// // Save for shop_hosting_post_hosting_verify_task()
-// drush_set_option('rackspace_id', $data['Instance ID']);
-// drush_set_option('ip_addresses', $ips);
-// }
-// // If we got a server node... run chef-client to update it.
-// else {
-// $ip = $chef_node->automatic->ipaddress;
-// drush_log("[DEVUDO] Chef node found with name:$server_fqdn ip:$ip Preparing attributes...", 'ok');
-//
-// // @TODO: Copy the attributes file and run chef-client again.
-// // Save new json data to file
-//
-// $json_path = "/tmp/$server_fqdn.json";
-// $attributes_json = $attributes;
-// file_put_contents($json_path, $attributes_json);
-//
-// // Sync file to server
-// // Use IP in case something is wrong with DNS
-// if (!empty($ip)){
-// $host = $ip;
-// }
-// else {
-// $host = $server_fqdn;
-// }
-//
-// // @TODO: This line implies that aegir already has ssh access to devudo@host
-// shop_exec("scp $json_path devudo@$host:~/attributes.json");
-//
-// // Run chef-client to update the server itself.
-// $chef_client_cmd = "sudo /usr/bin/chef-client -j attributes.json";
-// $chef_client_cmd_exec = escapeshellarg($chef_client_cmd);
-// drush_log("[DEVUDO] Running chef-client on $server_fqdn:", 'ok');
-// shop_exec("knife ssh name:$server_fqdn -x devudo $chef_client_cmd_exec -a ipaddress");
-// }
-// parent::verify_server_cmd();
-// }
-//
-//
-//
-// function config_data($config = null, $class = null) {
-// $data = parent::config_data($config, $class);
-// $data['rackspace_id'] = $this->server->rackspace_id;
-// $data['rackspace_image'] = $this->server->rackspace_image;
-// $data['rackspace_flavor'] = $this->server->rackspace_flavor;
-// $data['role'] = $this->server->role;
-// $data['attributes_json'] = $this->server->attributes_json;
-// return $data;
-// }
-//
-// static function option_documentation() {
-// return array(
-// '--rackspace_id' => 'The unique rackspace server ID.',
-// '--rackspace_image' => 'The rackspace server image.',
-// '--rackspace_flavor' => 'The rackspace server flavor.',
-// '--role' => 'The chef role.',
-// '--attributes_json' => 'JSON encoded attributes.',
-// );
-// }
-//
-// /**
-// * Ask the web server to check for and load configuration changes.
-// */
-// function parse_configs() {
-// return TRUE;
-// }
-//}
diff --git a/modules/devshop/devshop_cloud/drush/Provision/Service/provider/softlayer.php b/modules/devshop/devshop_cloud/drush/Provision/Service/provider/softlayer.php
deleted file mode 100644
index 0782615a9..000000000
--- a/modules/devshop/devshop_cloud/drush/Provision/Service/provider/softlayer.php
+++ /dev/null
@@ -1,88 +0,0 @@
-server->provider_server_identifier;
-
- // If server ID is already found, move on.
- if ($server_identifier) {
- drush_log('[DEVSHOP] Server Identifier Found. Not creating new server.', 'ok');
- }
- // If there is no server ID, create the server.
- else {
-
- drush_log('[DEVSHOP] Server Identifier not found. Creating new server!', 'ok');
-
- $server_fqdn = d()->remote_host;
-
- drush_log('[DEVSHOP|softlayer] Creating Server ' . $server_fqdn . '...', 'notice');
-
- // Initialize an API client for the SoftLayer_Account service.
- $virtual_guest = $this->softlayer_client('SoftLayer_Virtual_Guest');
- $provider_options = $this->prepare_provider_options();
-
- // Retrieve our account record
- try {
-
- // @TODO: Add more robust simulation.
- //$server = array(
- // 'id' => '00000',
- // 'stuff' => 'from softlayer',
- //);
- $server = (array) $virtual_guest->createObject($provider_options);
-
- drush_log('[DEVSHOP|softlayer] Created server in softlayer: ' . $server['id'], 'ok');
- } catch (Exception $e) {
- return drush_set_error('DEVSHOP_CLOUD_API_ACCESS_DENIED', $e->getMessage());
- }
-
- $provider_data = (array) $server;
- $this->server->setProperty('provider_data', $provider_data);
- $this->server->setProperty('provider_server_identifier', $provider_data['id']);
- }
- }
-
- function prepare_provider_options() {
- $devshop_cloud_provider_options = (object) drush_get_option('provider_options', '');
-
- // Break up title into hostname (subdomain) and domain.
- $provider_options = new stdClass();
- $domain = explode('.', d()->remote_host);
- $provider_options->hostname = $domain[0];
- $provider_options->domain = implode('.', array_slice($domain, 1));
- $provider_options->startCpus = $devshop_cloud_provider_options->processors;
- $provider_options->maxMemory = $devshop_cloud_provider_options->memory;
- $provider_options->hourlyBillingFlag = TRUE;
- $provider_options->localDiskFlag = TRUE;
- $provider_options->dedicatedAccountHostOnlyFlag = FALSE;
- $provider_options->operatingSystemReferenceCode = $devshop_cloud_provider_options->operatingSystems;
-
- $provider_options->datacenter = new stdClass();
- $provider_options->datacenter->name = $devshop_cloud_provider_options->datacenter;
-
- return $provider_options;
- }
-
- /**
- * Helper for getting a softlayer client.
- * @param $service
- * @return \Softlayer_SoapClient
- */
- private function softlayer_client($service, $id = null) {
- $api_key = drush_get_option('softlayer_api_key');
- $username = drush_get_option('softlayer_api_username');
-
- // Initialize an API client for the SoftLayer_Account service.
- $client = SoftLayer_SoapClient::getClient($service, $id, $username, $api_key);
- return $client;
- }
-}
\ No newline at end of file
diff --git a/modules/devshop/devshop_cloud/drush/README.md b/modules/devshop/devshop_cloud/drush/README.md
deleted file mode 100644
index 591c74eb7..000000000
--- a/modules/devshop/devshop_cloud/drush/README.md
+++ /dev/null
@@ -1,2 +0,0 @@
-# devshop_cloud
-Cloud server provisioning for devshop.
diff --git a/modules/devshop/devshop_cloud/drush/devshop_cloud.drush.inc b/modules/devshop/devshop_cloud/drush/devshop_cloud.drush.inc
deleted file mode 100644
index e44fe5fb5..000000000
--- a/modules/devshop/devshop_cloud/drush/devshop_cloud.drush.inc
+++ /dev/null
@@ -1,43 +0,0 @@
- NULL);
-}
diff --git a/modules/devshop/devshop_cloud/drush/tools.inc b/modules/devshop/devshop_cloud/drush/tools.inc
deleted file mode 100644
index bce0161d3..000000000
--- a/modules/devshop/devshop_cloud/drush/tools.inc
+++ /dev/null
@@ -1,45 +0,0 @@
- 'devshop_dothooks'));
+}
diff --git a/modules/devshop/devshop_dothooks/devshop_dothooks.module b/modules/devshop/devshop_dothooks/devshop_dothooks.module
new file mode 100644
index 000000000..a837d33b7
--- /dev/null
+++ b/modules/devshop/devshop_dothooks/devshop_dothooks.module
@@ -0,0 +1,144 @@
+settings->deploy['dothooks']) || empty($environment->settings->deploy['dothooks'])){
+ return;
+ }
+
+ if (file_exists($environment->repo_path . '/.hooks.yaml')) {
+ $hooks_file = '.hooks.yaml';
+ $hooks_path = $environment->repo_path . '/.hooks.yaml';
+ }
+ elseif (file_exists($environment->repo_path . '/.hooks.yml')) {
+ $hooks_file = '.hooks.yml';
+ $hooks_path = $environment->repo_path . '/.hooks.yml';
+ }
+ elseif (file_exists($environment->repo_path . '/.hooks')) {
+ $hooks_file = '.hooks';
+ $hooks_path = $environment->repo_path . '/.hooks';
+ }
+
+ $environment->dothooks_file_name = $hooks_file;
+ $environment->dothooks_file_path = $hooks_path;
+
+ // Attempt to parse
+ if (!empty($environment->dothooks_file_name)) {
+ try {
+ $environment->dothooks = $yaml->parse(file_get_contents($hooks_path));
+ } catch (\Symfony\Component\Yaml\Exception\ParseException $e) {
+ $environment->warnings[] = array(
+ 'text' => t('Invalid YAML in !file: !message', array(
+ '!file' => $hooks_path,
+ '!message' => $e->getMessage(),
+ )),
+ 'type' => 'error',
+ );
+ }
+ }
+}
+
+/**
+ * Implements hook_help()
+ * @param $path
+ * @param $arg
+ * @return string
+ */
+function devshop_dothooks_help($path, $arg)
+{
+ switch ($path) {
+ // Main module help for the block module
+ case 'admin/help#devshop_dothooks':
+ $note = t(
+ 'You can control what happens on deploy through a .hooks file in your repository.'
+ );
+
+ return <<$note
+
+# Fires after an environment is installed.
+install: |
+ drush {{alias}} vset site_name "Hooks Hooks Hooks"
+
+# Fires after code is deployed. A "deployment" happens when you push to your
+# git repository or select a new branch or tag for your environment.
+deploy: |
+ drush {{alias}} updb -y
+ drush {{alias}} cc all
+
+# Fires after "verify" task.
+verify: |
+ drush {{alias}} status
+
+# Fires after "Run Tests" task.
+test: |
+ drush {{alias}} uli
+
+
+# Fires after "Deploy Data (Sync)" task.
+sync: |
+ drush {{alias}} en devel -y
+
+
+HTML;
+
+ }
+}
+
+
+/**
+ * Runs a hook for a task.
+ * @param $hook
+ * @param $task
+ */
+function devshop_dothooks_run_hook($hook, $environment) {
+
+ // Respect drush option, but default to environment settings.
+ if (!drush_get_option('dothooks', $environment->settings->deploy['dothooks'])) {
+ drush_log('[.hooks] Environment not configured to run .hooks commands.', 'info');
+ return;
+ }
+
+ // If no dothooks file is found, throw an error.
+ if (empty($environment->dothooks)) {
+ drush_log(dt('Hook file not found, but the project is configured to use them. Create a .hooks file or turn off "Run deploy commands in the .hooks file".'), 'notice');
+ return;
+ }
+
+ drush_log('[.hooks] Hook file found: ' . $environment->dothooks_file_name, 'ok');
+
+ // Allow big string or lists of commands.
+ if (is_array($environment->dothooks[$hook])) {
+ $hooks = array_filter($environment->dothooks[$hook]);
+ }
+ else {
+ $hooks = array_filter(explode("\n", $environment->dothooks[$hook]));
+ }
+
+ // Prepare and run each command.
+ foreach ($hooks as $hook_line) {
+ $hook_line = strtr($hook_line, array(
+ '{{alias}}' => $environment->system_alias,
+ ));
+ devshop_process($hook_line, $environment->repo_path, 'DevShop .hooks.yml');
+ }
+}
+
diff --git a/modules/devshop/devshop_dothooks/hooks.php b/modules/devshop/devshop_dothooks/hooks.php
new file mode 100644
index 000000000..ccc9830ce
--- /dev/null
+++ b/modules/devshop/devshop_dothooks/hooks.php
@@ -0,0 +1,53 @@
+ref->type == 'site') {
+ devshop_dothooks_run_hook('verify', $task->ref->environment);
+ }
+}
+
+/**
+ * Implementation of hook_post_hosting_TASK_TYPE_task()
+ * for DevShop Deploy tasks.
+ *
+ * Runs the "deploy" dotHook
+ */
+function devshop_dothooks_post_hosting_devshop_deploy_task($task, $data) {
+ devshop_dothooks_run_hook('deploy', $task->ref->environment);
+}
+
+/**
+ * Implementation of hook_post_hosting_TASK_TYPE_task()
+ * for Run Tests tasks.
+ *
+ * Runs the "test" dotHook
+ */
+function devshop_dothooks_post_hosting_test_task($task, $data) {
+ devshop_dothooks_run_hook('test', $task->ref->environment);
+}
+
+/**
+ * Implementation of hook_post_hosting_TASK_TYPE_task()
+ * for Sync tasks.
+ *
+ * Runs the "sync" dotHook
+ */
+function devshop_dothooks_post_hosting_sync_task($task, $data) {
+ devshop_dothooks_run_hook('sync', $task->ref->environment);
+}
+
+/**
+ * Implementation of hook_post_hosting_TASK_TYPE_task()
+ * for Install tasks.
+ *
+ * Runs the "install" dotHook
+ */
+function devshop_dothooks_post_hosting_install_task($task, $data) {
+ devshop_dothooks_run_hook('install', $task->ref->environment);
+}
\ No newline at end of file
diff --git a/modules/devshop/devshop_extra_users/README.md b/modules/devshop/devshop_extra_users/README.md
new file mode 100644
index 000000000..9abb25762
--- /dev/null
+++ b/modules/devshop/devshop_extra_users/README.md
@@ -0,0 +1,18 @@
+DevShop Extras: Users
+=====================
+
+This module provides an example of how to use the DevShop front-end to take action
+after an environment is installed.
+
+Enable it, then visit the "Create Environment" form for a project.
+
+You will see a form field for "Manager Email". This field gets saved into the environment settings automatically.
+
+Then, in `devshop_extras_users.drush.inc` during the `hook_post_hosting_TASKTYPE_task`
+hook, this module creates a new user in your drupal site immediately after the
+installation.
+
+Nothing else happens here. Typically you would want to set a role or send an
+email.
+
+Use this code as an example for extending your own environments.
\ No newline at end of file
diff --git a/modules/devshop/devshop_extra_users/devshop_extra_users.drush.inc b/modules/devshop/devshop_extra_users/devshop_extra_users.drush.inc
new file mode 100644
index 000000000..9b9b75e27
--- /dev/null
+++ b/modules/devshop/devshop_extra_users/devshop_extra_users.drush.inc
@@ -0,0 +1,34 @@
+ref->type == 'site' && isset($task->ref->environment->settings->manager_email)) {
+
+ // Create an extra user.
+ $email = $task->ref->environment->settings->manager_email;
+ $password = provision_password();
+ $arguments = array('name' => $email);
+ $data = array(
+ 'mail' => $email,
+ 'password' => $password,
+ );
+
+ drush_log('Manager email is found! running user-create...', 'ok');
+ provision_backend_invoke($task->ref->title, 'user-create', $arguments, $data);
+
+ drush_log(dt('User !user has been created with password !password', array(
+ '!user' => $email,
+ '!password' => $password,
+ )), 'ok');
+
+ drush_log('No email has been sent! Please notify your new user.');
+ }
+}
diff --git a/modules/devshop/devshop_extra_users/devshop_extra_users.info b/modules/devshop/devshop_extra_users/devshop_extra_users.info
new file mode 100644
index 000000000..bcc11ca1d
--- /dev/null
+++ b/modules/devshop/devshop_extra_users/devshop_extra_users.info
@@ -0,0 +1,5 @@
+name = DevShop Extra Install
+description = Example module for loading extra info for an install profile.
+core = 7.x
+package = DevShop
+
diff --git a/modules/devshop/devshop_extra_users/devshop_extra_users.module b/modules/devshop/devshop_extra_users/devshop_extra_users.module
new file mode 100644
index 000000000..e1d13d86a
--- /dev/null
+++ b/modules/devshop/devshop_extra_users/devshop_extra_users.module
@@ -0,0 +1,15 @@
+ 'textfield',
+ '#title' => t('Manager Email'),
+ '#description' => t('Enter an email address and a user will be created.'),
+ );
+ }
+}
diff --git a/modules/devshop/devshop_github/.gitignore b/modules/devshop/devshop_github/.gitignore
new file mode 100644
index 000000000..e69de29bb
diff --git a/modules/devshop/devshop_github/composer.json b/modules/devshop/devshop_github/composer.json
new file mode 100644
index 000000000..56e09bfdb
--- /dev/null
+++ b/modules/devshop/devshop_github/composer.json
@@ -0,0 +1,5 @@
+{
+ "require": {
+ "knplabs/github-api": "~1.2"
+ }
+}
\ No newline at end of file
diff --git a/modules/devshop/devshop_github/composer.lock b/modules/devshop/devshop_github/composer.lock
new file mode 100644
index 000000000..4883279ed
--- /dev/null
+++ b/modules/devshop/devshop_github/composer.lock
@@ -0,0 +1,233 @@
+{
+ "_readme": [
+ "This file locks the dependencies of your project to a known state",
+ "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
+ "This file is @generated automatically"
+ ],
+ "hash": "a7ca813c3521e13be685f99e073d2773",
+ "packages": [
+ {
+ "name": "guzzle/guzzle",
+ "version": "v3.9.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/guzzle3.git",
+ "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/guzzle3/zipball/0645b70d953bc1c067bbc8d5bc53194706b628d9",
+ "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9",
+ "shasum": ""
+ },
+ "require": {
+ "ext-curl": "*",
+ "php": ">=5.3.3",
+ "symfony/event-dispatcher": "~2.1"
+ },
+ "replace": {
+ "guzzle/batch": "self.version",
+ "guzzle/cache": "self.version",
+ "guzzle/common": "self.version",
+ "guzzle/http": "self.version",
+ "guzzle/inflection": "self.version",
+ "guzzle/iterator": "self.version",
+ "guzzle/log": "self.version",
+ "guzzle/parser": "self.version",
+ "guzzle/plugin": "self.version",
+ "guzzle/plugin-async": "self.version",
+ "guzzle/plugin-backoff": "self.version",
+ "guzzle/plugin-cache": "self.version",
+ "guzzle/plugin-cookie": "self.version",
+ "guzzle/plugin-curlauth": "self.version",
+ "guzzle/plugin-error-response": "self.version",
+ "guzzle/plugin-history": "self.version",
+ "guzzle/plugin-log": "self.version",
+ "guzzle/plugin-md5": "self.version",
+ "guzzle/plugin-mock": "self.version",
+ "guzzle/plugin-oauth": "self.version",
+ "guzzle/service": "self.version",
+ "guzzle/stream": "self.version"
+ },
+ "require-dev": {
+ "doctrine/cache": "~1.3",
+ "monolog/monolog": "~1.0",
+ "phpunit/phpunit": "3.7.*",
+ "psr/log": "~1.0",
+ "symfony/class-loader": "~2.1",
+ "zendframework/zend-cache": "2.*,<2.3",
+ "zendframework/zend-log": "2.*,<2.3"
+ },
+ "suggest": {
+ "guzzlehttp/guzzle": "Guzzle 5 has moved to a new package name. The package you have installed, Guzzle 3, is deprecated."
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.9-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "Guzzle": "src/",
+ "Guzzle\\Tests": "tests/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ },
+ {
+ "name": "Guzzle Community",
+ "homepage": "https://github.com/guzzle/guzzle/contributors"
+ }
+ ],
+ "description": "PHP HTTP client. This library is deprecated in favor of https://packagist.org/packages/guzzlehttp/guzzle",
+ "homepage": "http://guzzlephp.org/",
+ "keywords": [
+ "client",
+ "curl",
+ "framework",
+ "http",
+ "http client",
+ "rest",
+ "web service"
+ ],
+ "time": "2015-03-18 18:23:50"
+ },
+ {
+ "name": "knplabs/github-api",
+ "version": "1.4.7",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/KnpLabs/php-github-api.git",
+ "reference": "d2729cf6b9d5b6fa340b1ff001dd89e319741baa"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/KnpLabs/php-github-api/zipball/d2729cf6b9d5b6fa340b1ff001dd89e319741baa",
+ "reference": "d2729cf6b9d5b6fa340b1ff001dd89e319741baa",
+ "shasum": ""
+ },
+ "require": {
+ "ext-curl": "*",
+ "guzzle/guzzle": "~3.7",
+ "php": ">=5.3.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.0"
+ },
+ "suggest": {
+ "knplabs/gaufrette": "Needed for optional Gaufrette cache"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.4.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "Github\\": "lib/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Thibault Duplessis",
+ "email": "thibault.duplessis@gmail.com",
+ "homepage": "http://ornicar.github.com"
+ },
+ {
+ "name": "KnpLabs Team",
+ "homepage": "http://knplabs.com"
+ }
+ ],
+ "description": "GitHub API v3 client",
+ "homepage": "https://github.com/KnpLabs/php-github-api",
+ "keywords": [
+ "api",
+ "gh",
+ "gist",
+ "github"
+ ],
+ "time": "2015-04-07 20:24:33"
+ },
+ {
+ "name": "symfony/event-dispatcher",
+ "version": "v2.6.6",
+ "target-dir": "Symfony/Component/EventDispatcher",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/EventDispatcher.git",
+ "reference": "70f7c8478739ad21e3deef0d977b38c77f1fb284"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/EventDispatcher/zipball/70f7c8478739ad21e3deef0d977b38c77f1fb284",
+ "reference": "70f7c8478739ad21e3deef0d977b38c77f1fb284",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "psr/log": "~1.0",
+ "symfony/config": "~2.0,>=2.0.5",
+ "symfony/dependency-injection": "~2.6",
+ "symfony/expression-language": "~2.6",
+ "symfony/phpunit-bridge": "~2.7",
+ "symfony/stopwatch": "~2.3"
+ },
+ "suggest": {
+ "symfony/dependency-injection": "",
+ "symfony/http-kernel": ""
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.6-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "Symfony\\Component\\EventDispatcher\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Symfony Community",
+ "homepage": "http://symfony.com/contributors"
+ },
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ }
+ ],
+ "description": "Symfony EventDispatcher Component",
+ "homepage": "http://symfony.com",
+ "time": "2015-03-13 17:37:22"
+ }
+ ],
+ "packages-dev": [],
+ "aliases": [],
+ "minimum-stability": "stable",
+ "stability-flags": [],
+ "prefer-stable": false,
+ "prefer-lowest": false,
+ "platform": [],
+ "platform-dev": []
+}
diff --git a/modules/devshop/devshop_github/devshop_github.drush.inc b/modules/devshop/devshop_github/devshop_github.drush.inc
new file mode 100644
index 000000000..7e493e98d
--- /dev/null
+++ b/modules/devshop/devshop_github/devshop_github.drush.inc
@@ -0,0 +1,120 @@
+task_type, $types) && isset($task->ref->project) && !empty($task->ref->project) && isset($task->ref->environment) && !empty($task->ref->environment)) {
+ $project = $task->ref->project;
+ $environment = $task->ref->environment;
+ }
+ else {
+ return;
+ }
+
+ // If a pull request object is available...
+ if (isset($environment->github_pull_request->pull_request_object->deployment)) {
+
+ // If project is configured to reinstall every time, only react on "install" tasks. Otherwise, we get two github deployments because both a "deploy" (git pull) and a "install" task are run on each git push.
+ if ($project->settings->github['pull_request_reinstall'] && $task->task_type == 'devshop-deploy') {
+ return;
+ }
+
+ // Create a deployment status
+ $owner = $project->github_owner;
+ $repo = $project->github_repo;
+ $deployment_id = $environment->github_pull_request->pull_request_object->deployment->id;
+
+ try {
+ $token = variable_get('devshop_github_token', '');
+ $client = new \Github\Client();
+ $client->authenticate($token, Github\Client::AUTH_HTTP_TOKEN);
+
+ $params = new stdClass();
+ if ($status == HOSTING_TASK_SUCCESS || $status == HOSTING_TASK_WARNING) {
+ $params->state = $state = 'success';
+ }
+ else {
+ $params->state = $state = 'failure';
+ }
+
+ // If task is a test run, only submit a commit status for devshop/tests context.
+ if ($task->task_type == 'test') {
+ $sha = $environment->github_pull_request->pull_request_object->head->sha;
+
+ $params = new stdClass();
+ $params->state = $state;
+ $params->target_url = url("node/{$task->nid}/view", array('absolute' => TRUE));
+
+ if ($status == HOSTING_TASK_WARNING) {
+ $params->description = t('DevShop: Tests passed with warnings');
+ }
+ else {
+ $params->description = t('DevShop: Tests !status!', array('!status' => $state));
+ }
+ $params->context = 'devshop/tests';
+
+ $status = $client->getHttpClient()->post("/repos/$owner/$repo/statuses/$sha", json_encode($params));
+ drush_log('Commit status created for devshop/tests!', 'success');
+ }
+ // Otherwise we create a deployment and a commit status.
+ else {
+
+ $params->target_url = $environment->url;
+ $params->description = t('Visit !url', array('!url' => $task->ref->environment->url));
+ $post_url = "/repos/$owner/$repo/deployments/{$deployment_id}/statuses";
+
+ drush_log('Attempting to create github deployment status: ' . $post_url, 'success');
+
+ $deployment_status = $client->getHttpClient()->post($post_url, json_encode($params));
+ drush_log('Deployment status created!', 'success');
+
+
+ // Update Status API
+
+ // Create a status
+ $sha = $environment->github_pull_request->pull_request_object->head->sha;
+
+ $params = new stdClass();
+ $params->state = $state;
+ $params->target_url = url("node/{$task->nid}", array('absolute' => TRUE));;
+
+ if ($status == HOSTING_TASK_WARNING) {
+ $params->description = t('DevShop: Deploy success with warnings. [!url]', array(
+ '!url' => $environment->url,
+ ));
+ }
+ else {
+ $params->description = t('DevShop: Deploy !status [!url]', array(
+ '!status' => $state,
+ '!url' => $environment->url,
+ ));
+ }
+ $params->context = 'devshop/deploy';
+
+ $deployment_status = $client->getHttpClient()->post("/repos/$owner/$repo/statuses/$sha", json_encode($params));
+ drush_log('Commit status created!', 'success');
+
+ // If deploy task fails, tests won't run.
+ if ($environment->settings->deploy['test'] && $status == HOSTING_TASK_ERROR) {
+
+ $params = new stdClass();
+ $params->state = $state;
+ $params->description = t('DevShop: Tests not run due to Deploy Fail');
+ $params->context = 'devshop/tests';
+
+ $deployment_status = $client->getHttpClient()->post("/repos/$owner/$repo/statuses/$sha", json_encode($params));
+ drush_log('Commit status created for devshop/tests', 'success');
+ }
+ }
+ } catch (Github\Exception\RuntimeException $e) {
+ drush_log('GitHub API Error: ' . $e->getMessage(), 'error');
+ }
+ }
+}
diff --git a/modules/devshop/devshop_github/devshop_github.info b/modules/devshop/devshop_github/devshop_github.info
new file mode 100644
index 000000000..6900a4293
--- /dev/null
+++ b/modules/devshop/devshop_github/devshop_github.info
@@ -0,0 +1,7 @@
+name = DevShop GitHub
+description = Integration with GitHub
+core = 7.x
+package = DevShop
+files[] = includes/add-key.inc
+files[] = includes/admin.inc
+dependencies[] = devshop_projects
diff --git a/modules/devshop/devshop_github/devshop_github.install b/modules/devshop/devshop_github/devshop_github.install
new file mode 100644
index 000000000..23b3482ec
--- /dev/null
+++ b/modules/devshop/devshop_github/devshop_github.install
@@ -0,0 +1,94 @@
+ array(
+ 'id' => array(
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'description' => 'Pull Request ID',
+ ),
+ 'number' => array(
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'description' => 'Pull Request Number',
+ ),
+ 'project_nid' => array(
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'description' => "The project's Node ID.",
+ ),
+ 'environment_name' => array(
+ 'type' => 'varchar',
+ 'not null' => TRUE,
+ 'length' => 64,
+ 'default' => '',
+ 'description' => 'Environment name for this pull request environment.',
+ ),
+ 'pull_request_object' => array(
+ 'type' => 'text',
+ 'not null' => FALSE,
+ 'size' => 'big',
+ 'description' => 'A serialized array of settings for this environment.',
+ ),
+ ),
+ 'primary key' => array('id'),
+ );
+ return $schema;
+}
+
+/**
+ * Implements hook_install().
+ */
+function devshop_github_install() {
+
+ // Push devshop_github's system weight to 1.
+ db_update('system')
+ ->fields(array(
+ 'weight' => 2
+ ))
+ ->condition('name', 'devshop_github')
+ ->execute();
+
+ // Display a message about setting a github personal token.
+ drupal_set_message(t('DevShop GitHub module has been enabled. You must add an access token to enable full functionality at !link.', array(
+ '!link' => l(t('the settings page'), 'admin/devshop/github'),
+ )));
+}
+
+/**
+ * Set a weight higher than devshop_project so our form doesn't get obliterated by
+ * devshop_projects_form_project_node_form_alter()
+ */
+function devshop_github_update_7000() {
+ db_update('system')
+ ->fields(array(
+ 'weight' => 1
+ ))
+ ->condition('name', 'devshop_github')
+ ->execute();
+}
+
+/**
+ * Set a weight higher than devshop_project module.
+ */
+function devshop_github_update_7001() {
+ db_update('system')
+ ->fields(array(
+ 'weight' => 2
+ ))
+ ->condition('name', 'devshop_github')
+ ->execute();
+}
\ No newline at end of file
diff --git a/modules/devshop/devshop_github/devshop_github.module b/modules/devshop/devshop_github/devshop_github.module
new file mode 100644
index 000000000..ce42ee6c9
--- /dev/null
+++ b/modules/devshop/devshop_github/devshop_github.module
@@ -0,0 +1,928 @@
+ 'GitHub',
+ 'description' => 'DevShop GitHub Integration Settings',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('devshop_github_settings_form'),
+ 'access arguments' => array('administer projects'),
+ 'file' => 'admin.inc',
+ 'file path' => drupal_get_path('module', 'devshop_github') . '/includes',
+ 'type' => MENU_LOCAL_TASK,
+ );
+ $items['admin/devshop/github/add-key'] = array(
+ 'title' => 'Add public key to GitHub Account',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('devshop_github_add_key_to_account'),
+ 'access arguments' => array('administer projects'),
+ 'file' => 'add-key.inc',
+ 'file path' => drupal_get_path('module', 'devshop_github') . '/includes',
+ 'type' => MENU_CALLBACK,
+ );
+ return $items;
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter() for project_node_form().
+ */
+function devshop_github_form_project_node_form_alter(&$form, &$form_state, $form_id) {
+ $node = $form['#node'];
+
+ if ($node->project->git_provider != 'github') {
+ return;
+ }
+
+ //All settings git pull in project page
+ $form['project']['settings']['github'] = array(
+ '#type' => 'fieldset',
+ '#group' => 'project_settings',
+ '#collapsible' => TRUE,
+ '#collapsed' => arg(1) != $node->nid,
+ '#title' => t('GitHub Integration'),
+ );
+
+ // Pull Requests create environments?
+ // $form['github']['pull_request_environments'] = array(
+ $form['project']['settings']['github']['pull_request_environments'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Create Environments for Pull Requests'),
+ '#default_value' => isset($node->project->settings->github) ? $node->project->settings->github['pull_request_environments'] : FALSE,
+ '#description' => t('If using GitHub, create a new environment when a new Pull Request is created.'),
+ );
+
+ // Delete Pull Request environments?
+ // $form['github']['pull_request_environments_delete'] = array(
+ $form['project']['settings']['github']['pull_request_environments_delete'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Delete Pull Request Environments'),
+ '#default_value' => isset($node->project->settings->github) ? $node->project->settings->github['pull_request_environments_delete'] : FALSE,
+ '#description' => t('When Pull Requests are closed, delete the environment.'),
+ '#states' => array(
+ 'visible' => array(
+ ':input[name="project[settings][github][pull_request_environments]"]' => array('checked' => TRUE),
+ ),
+ ),
+ );
+
+ // Pull Request Environment method.
+ // $form['github']['pull_request_environments_method'] = array(
+
+ $environments = array_keys($node->project->environments);
+ $options = array(
+ t('Install Drupal') => array(
+ 'devshop__github__install' => empty($node->project->install_profile)? t('Default install profile.'): $node->project->install_profile,
+ ),
+ t('Clone another environment') => array(),
+ );
+
+ if (empty($environments)) {
+ $options[t('Clone another environment')][] = t('No environments available. Check Project settings when you have one.');
+ }
+ else {
+ $options[t('Clone another environment')] = array_combine($environments, $environments);
+ }
+ $form['project']['settings']['github']['pull_request_environments_method'] = array(
+ '#type' => 'select',
+ '#title' => t('Pull Request Environment Creation Method'),
+ '#default_value' => isset($node->project->settings->github) ?
+ $node->project->settings->github['pull_request_environments_method'] : 'devshop__github__install',
+ '#description' => t('Select the method for creating the pull request environments.'),
+ '#options' => $options,
+ '#states' => array(
+ 'visible' => array(
+ ':input[name="project[settings][github][pull_request_environments]"]' => array('checked' => TRUE),
+ ),
+ ),
+ );
+ $form['project']['settings']['github']['pull_request_reinstall'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Reinstall Pull Request Environments on every git push.'),
+ '#default_value' => isset($node->project->settings->github) ?
+ $node->project->settings->github['pull_request_reinstall'] : 0,
+ '#description' => t('Destroy and reinstall Pull Request environments on every code push. All data in environments created via Pull Request will be destroyed.'),
+ '#states' => array(
+ 'visible' => array(
+ ':input[name="project[settings][github][pull_request_environments]"]' => array('checked' => TRUE),
+ ),
+ ),
+ );
+}
+
+/**
+ * Implements hook_form_alter().
+ */
+function devshop_github_form_devshop_project_create_step_git_alter(&$form, &$form_state, $form_id) {
+
+ // Look for Token
+ $token = variable_get('devshop_github_token', '');
+
+ if (empty($token)) {
+ $form['connect']['#description'] = '
' . t('GitHub API Token was not found.') . ' ' . l(t('Configure DevShop GitHub Settings'), 'admin/devshop/github') . '
';
+ }
+ }
+
+ return system_settings_form($form);
+}
diff --git a/modules/devshop/devshop_github/vendor/autoload.php b/modules/devshop/devshop_github/vendor/autoload.php
new file mode 100644
index 000000000..6bba741a6
--- /dev/null
+++ b/modules/devshop/devshop_github/vendor/autoload.php
@@ -0,0 +1,7 @@
+
+ * Jordi Boggiano
+ *
+ * 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 class loader
+ *
+ * See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md
+ *
+ * $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
+ * @author Jordi Boggiano
+ */
+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;
+
+ 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-0 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;
+ }
+
+ /**
+ * 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)
+ {
+ // work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731
+ if ('\\' == $class[0]) {
+ $class = substr($class, 1);
+ }
+
+ // class map lookup
+ if (isset($this->classMap[$class])) {
+ return $this->classMap[$class];
+ }
+ if ($this->classMapAuthoritative) {
+ return false;
+ }
+
+ $file = $this->findFileWithExtension($class, '.php');
+
+ // Search for Hack files if we are running on HHVM
+ if ($file === null && defined('HHVM_VERSION')) {
+ $file = $this->findFileWithExtension($class, '.hh');
+ }
+
+ if ($file === null) {
+ // Remember that this class does not exist.
+ return $this->classMap[$class] = false;
+ }
+
+ 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;
+ }
+ }
+}
+
+/**
+ * Scope isolated include.
+ *
+ * Prevents access to $this/self from included files.
+ */
+function includeFile($file)
+{
+ include $file;
+}
diff --git a/modules/devshop/devshop_github/vendor/composer/autoload_classmap.php b/modules/devshop/devshop_github/vendor/composer/autoload_classmap.php
new file mode 100644
index 000000000..7a91153b0
--- /dev/null
+++ b/modules/devshop/devshop_github/vendor/composer/autoload_classmap.php
@@ -0,0 +1,9 @@
+ array($vendorDir . '/symfony/event-dispatcher'),
+ 'Guzzle\\Tests' => array($vendorDir . '/guzzle/guzzle/tests'),
+ 'Guzzle' => array($vendorDir . '/guzzle/guzzle/src'),
+ 'Github\\' => array($vendorDir . '/knplabs/github-api/lib'),
+);
diff --git a/modules/devshop/devshop_github/vendor/composer/autoload_psr4.php b/modules/devshop/devshop_github/vendor/composer/autoload_psr4.php
new file mode 100644
index 000000000..b265c64a2
--- /dev/null
+++ b/modules/devshop/devshop_github/vendor/composer/autoload_psr4.php
@@ -0,0 +1,9 @@
+ $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);
+
+ return $loader;
+ }
+}
+
+function composerRequirec023ec1f16bdfa3119c161ea844cfa9e($file)
+{
+ require $file;
+}
diff --git a/modules/devshop/devshop_github/vendor/composer/installed.json b/modules/devshop/devshop_github/vendor/composer/installed.json
new file mode 100644
index 000000000..0dc9fd353
--- /dev/null
+++ b/modules/devshop/devshop_github/vendor/composer/installed.json
@@ -0,0 +1,223 @@
+[
+ {
+ "name": "symfony/event-dispatcher",
+ "version": "v2.6.6",
+ "version_normalized": "2.6.6.0",
+ "target-dir": "Symfony/Component/EventDispatcher",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/EventDispatcher.git",
+ "reference": "70f7c8478739ad21e3deef0d977b38c77f1fb284"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/EventDispatcher/zipball/70f7c8478739ad21e3deef0d977b38c77f1fb284",
+ "reference": "70f7c8478739ad21e3deef0d977b38c77f1fb284",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "psr/log": "~1.0",
+ "symfony/config": "~2.0,>=2.0.5",
+ "symfony/dependency-injection": "~2.6",
+ "symfony/expression-language": "~2.6",
+ "symfony/phpunit-bridge": "~2.7",
+ "symfony/stopwatch": "~2.3"
+ },
+ "suggest": {
+ "symfony/dependency-injection": "",
+ "symfony/http-kernel": ""
+ },
+ "time": "2015-03-13 17:37:22",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.6-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-0": {
+ "Symfony\\Component\\EventDispatcher\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Symfony Community",
+ "homepage": "http://symfony.com/contributors"
+ },
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ }
+ ],
+ "description": "Symfony EventDispatcher Component",
+ "homepage": "http://symfony.com"
+ },
+ {
+ "name": "guzzle/guzzle",
+ "version": "v3.9.3",
+ "version_normalized": "3.9.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/guzzle3.git",
+ "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/guzzle3/zipball/0645b70d953bc1c067bbc8d5bc53194706b628d9",
+ "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9",
+ "shasum": ""
+ },
+ "require": {
+ "ext-curl": "*",
+ "php": ">=5.3.3",
+ "symfony/event-dispatcher": "~2.1"
+ },
+ "replace": {
+ "guzzle/batch": "self.version",
+ "guzzle/cache": "self.version",
+ "guzzle/common": "self.version",
+ "guzzle/http": "self.version",
+ "guzzle/inflection": "self.version",
+ "guzzle/iterator": "self.version",
+ "guzzle/log": "self.version",
+ "guzzle/parser": "self.version",
+ "guzzle/plugin": "self.version",
+ "guzzle/plugin-async": "self.version",
+ "guzzle/plugin-backoff": "self.version",
+ "guzzle/plugin-cache": "self.version",
+ "guzzle/plugin-cookie": "self.version",
+ "guzzle/plugin-curlauth": "self.version",
+ "guzzle/plugin-error-response": "self.version",
+ "guzzle/plugin-history": "self.version",
+ "guzzle/plugin-log": "self.version",
+ "guzzle/plugin-md5": "self.version",
+ "guzzle/plugin-mock": "self.version",
+ "guzzle/plugin-oauth": "self.version",
+ "guzzle/service": "self.version",
+ "guzzle/stream": "self.version"
+ },
+ "require-dev": {
+ "doctrine/cache": "~1.3",
+ "monolog/monolog": "~1.0",
+ "phpunit/phpunit": "3.7.*",
+ "psr/log": "~1.0",
+ "symfony/class-loader": "~2.1",
+ "zendframework/zend-cache": "2.*,<2.3",
+ "zendframework/zend-log": "2.*,<2.3"
+ },
+ "suggest": {
+ "guzzlehttp/guzzle": "Guzzle 5 has moved to a new package name. The package you have installed, Guzzle 3, is deprecated."
+ },
+ "time": "2015-03-18 18:23:50",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.9-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-0": {
+ "Guzzle": "src/",
+ "Guzzle\\Tests": "tests/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ },
+ {
+ "name": "Guzzle Community",
+ "homepage": "https://github.com/guzzle/guzzle/contributors"
+ }
+ ],
+ "description": "PHP HTTP client. This library is deprecated in favor of https://packagist.org/packages/guzzlehttp/guzzle",
+ "homepage": "http://guzzlephp.org/",
+ "keywords": [
+ "client",
+ "curl",
+ "framework",
+ "http",
+ "http client",
+ "rest",
+ "web service"
+ ]
+ },
+ {
+ "name": "knplabs/github-api",
+ "version": "1.4.7",
+ "version_normalized": "1.4.7.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/KnpLabs/php-github-api.git",
+ "reference": "d2729cf6b9d5b6fa340b1ff001dd89e319741baa"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/KnpLabs/php-github-api/zipball/d2729cf6b9d5b6fa340b1ff001dd89e319741baa",
+ "reference": "d2729cf6b9d5b6fa340b1ff001dd89e319741baa",
+ "shasum": ""
+ },
+ "require": {
+ "ext-curl": "*",
+ "guzzle/guzzle": "~3.7",
+ "php": ">=5.3.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.0"
+ },
+ "suggest": {
+ "knplabs/gaufrette": "Needed for optional Gaufrette cache"
+ },
+ "time": "2015-04-07 20:24:33",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.4.x-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-0": {
+ "Github\\": "lib/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Thibault Duplessis",
+ "email": "thibault.duplessis@gmail.com",
+ "homepage": "http://ornicar.github.com"
+ },
+ {
+ "name": "KnpLabs Team",
+ "homepage": "http://knplabs.com"
+ }
+ ],
+ "description": "GitHub API v3 client",
+ "homepage": "https://github.com/KnpLabs/php-github-api",
+ "keywords": [
+ "api",
+ "gh",
+ "gist",
+ "github"
+ ]
+ }
+]
diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/.gitignore b/modules/devshop/devshop_github/vendor/guzzle/guzzle/.gitignore
new file mode 100644
index 000000000..893035d5b
--- /dev/null
+++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/.gitignore
@@ -0,0 +1,27 @@
+# Ingore common cruft
+.DS_STORE
+coverage
+.idea
+
+# Ignore binary files
+guzzle.phar
+guzzle-min.phar
+
+# Ignore potentially sensitive phpunit file
+phpunit.xml
+
+# Ignore composer generated files
+composer.phar
+composer.lock
+composer-test.lock
+vendor/
+
+# Ignore build files
+build/
+phing/build.properties
+
+# Ignore subsplit working directory
+.subsplit
+
+docs/_build
+docs/*.pyc
diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/.travis.yml b/modules/devshop/devshop_github/vendor/guzzle/guzzle/.travis.yml
new file mode 100644
index 000000000..209e05cd6
--- /dev/null
+++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/.travis.yml
@@ -0,0 +1,17 @@
+language: php
+
+php:
+ - 5.3
+ - 5.4
+ - 5.5
+ - 5.6
+ - hhvm
+
+before_script:
+ - curl --version
+ - pecl install uri_template-beta || echo "pecl uri_template not available"
+ - composer self-update
+ - composer install --no-interaction --prefer-source --dev
+ - ~/.nvm/nvm.sh install v0.6.14
+
+script: composer test
diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/CHANGELOG.md b/modules/devshop/devshop_github/vendor/guzzle/guzzle/CHANGELOG.md
new file mode 100644
index 000000000..f0dc5444a
--- /dev/null
+++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/CHANGELOG.md
@@ -0,0 +1,751 @@
+# CHANGELOG
+
+## 3.9.3 - 2015-03-18
+
+* Ensuring Content-Length is not stripped from a request when it is `0`.
+* Added more information to stream wrapper exceptions.
+* Message parser will no longer throw warnings for malformed messages.
+* Giving a valid cache TTL when max-age is 0.
+
+## 3.9.2 - 2014-09-10
+
+* Retrying "Connection died, retrying a fresh connect" curl errors.
+* Automatically extracting the cacert from the phar in client constructor.
+* Added EntityBody support for OPTIONS requests.
+
+## 3.9.1 - 2014-05-07
+
+* Added a fix to ReadLimitEntityBody to ensure it doesn't infinitely loop.
+* Added a fix to the stream checksum function so that when the first read
+ returns a falsey value, it still continues to consume the stream until EOF.
+
+## 3.9.0 - 2014-04-23
+
+* `null`, `false`, and `"_guzzle_blank_"` all now serialize as an empty value
+ with no trailing "=". See dc1d824277.
+* No longer performing an MD5 check on the cacert each time the phar is used,
+ but rather copying the cacert to the temp directory.
+* `"0"` can now be added as a URL path
+* Deleting cookies that are set to empty
+* If-Modified-Since is no longer unnecessarily added to the CachePlugin
+* Cookie path matching now follows RFC 6265 s5.1.4
+* Updated service descriptions are now added to a service client's composite
+ factory.
+* MockPlugin now throws an exception if the queue is empty.
+* Properly parsing URLs that start with "http" but are not absolute
+* Added the ability to configure the curl_multi_select timeout setting
+* OAuth parameters are now sorted using lexicographical byte value ordering
+* Fixing invalid usage of an out of range PHP feature in the ErrorResponsePlugin
+
+## 3.8.1 -2014-01-28
+
+* Bug: Always using GET requests when redirecting from a 303 response
+* Bug: CURLOPT_SSL_VERIFYHOST is now correctly set to false when setting `$certificateAuthority` to false in
+ `Guzzle\Http\ClientInterface::setSslVerification()`
+* Bug: RedirectPlugin now uses strict RFC 3986 compliance when combining a base URL with a relative URL
+* Bug: The body of a request can now be set to `"0"`
+* Sending PHP stream requests no longer forces `HTTP/1.0`
+* Adding more information to ExceptionCollection exceptions so that users have more context, including a stack trace of
+ each sub-exception
+* Updated the `$ref` attribute in service descriptions to merge over any existing parameters of a schema (rather than
+ clobbering everything).
+* Merging URLs will now use the query string object from the relative URL (thus allowing custom query aggregators)
+* Query strings are now parsed in a way that they do no convert empty keys with no value to have a dangling `=`.
+ For example `foo&bar=baz` is now correctly parsed and recognized as `foo&bar=baz` rather than `foo=&bar=baz`.
+* Now properly escaping the regular expression delimiter when matching Cookie domains.
+* Network access is now disabled when loading XML documents
+
+## 3.8.0 - 2013-12-05
+
+* Added the ability to define a POST name for a file
+* JSON response parsing now properly walks additionalProperties
+* cURL error code 18 is now retried automatically in the BackoffPlugin
+* Fixed a cURL error when URLs contain fragments
+* Fixed an issue in the BackoffPlugin retry event where it was trying to access all exceptions as if they were
+ CurlExceptions
+* CURLOPT_PROGRESS function fix for PHP 5.5 (69fcc1e)
+* Added the ability for Guzzle to work with older versions of cURL that do not support `CURLOPT_TIMEOUT_MS`
+* Fixed a bug that was encountered when parsing empty header parameters
+* UriTemplate now has a `setRegex()` method to match the docs
+* The `debug` request parameter now checks if it is truthy rather than if it exists
+* Setting the `debug` request parameter to true shows verbose cURL output instead of using the LogPlugin
+* Added the ability to combine URLs using strict RFC 3986 compliance
+* Command objects can now return the validation errors encountered by the command
+* Various fixes to cache revalidation (#437 and 29797e5)
+* Various fixes to the AsyncPlugin
+* Cleaned up build scripts
+
+## 3.7.4 - 2013-10-02
+
+* Bug fix: 0 is now an allowed value in a description parameter that has a default value (#430)
+* Bug fix: SchemaFormatter now returns an integer when formatting to a Unix timestamp
+ (see https://github.com/aws/aws-sdk-php/issues/147)
+* Bug fix: Cleaned up and fixed URL dot segment removal to properly resolve internal dots
+* Minimum PHP version is now properly specified as 5.3.3 (up from 5.3.2) (#420)
+* Updated the bundled cacert.pem (#419)
+* OauthPlugin now supports adding authentication to headers or query string (#425)
+
+## 3.7.3 - 2013-09-08
+
+* Added the ability to get the exception associated with a request/command when using `MultiTransferException` and
+ `CommandTransferException`.
+* Setting `additionalParameters` of a response to false is now honored when parsing responses with a service description
+* Schemas are only injected into response models when explicitly configured.
+* No longer guessing Content-Type based on the path of a request. Content-Type is now only guessed based on the path of
+ an EntityBody.
+* Bug fix: ChunkedIterator can now properly chunk a \Traversable as well as an \Iterator.
+* Bug fix: FilterIterator now relies on `\Iterator` instead of `\Traversable`.
+* Bug fix: Gracefully handling malformed responses in RequestMediator::writeResponseBody()
+* Bug fix: Replaced call to canCache with canCacheRequest in the CallbackCanCacheStrategy of the CachePlugin
+* Bug fix: Visiting XML attributes first before visting XML children when serializing requests
+* Bug fix: Properly parsing headers that contain commas contained in quotes
+* Bug fix: mimetype guessing based on a filename is now case-insensitive
+
+## 3.7.2 - 2013-08-02
+
+* Bug fix: Properly URL encoding paths when using the PHP-only version of the UriTemplate expander
+ See https://github.com/guzzle/guzzle/issues/371
+* Bug fix: Cookie domains are now matched correctly according to RFC 6265
+ See https://github.com/guzzle/guzzle/issues/377
+* Bug fix: GET parameters are now used when calculating an OAuth signature
+* Bug fix: Fixed an issue with cache revalidation where the If-None-Match header was being double quoted
+* `Guzzle\Common\AbstractHasDispatcher::dispatch()` now returns the event that was dispatched
+* `Guzzle\Http\QueryString::factory()` now guesses the most appropriate query aggregator to used based on the input.
+ See https://github.com/guzzle/guzzle/issues/379
+* Added a way to add custom domain objects to service description parsing using the `operation.parse_class` event. See
+ https://github.com/guzzle/guzzle/pull/380
+* cURL multi cleanup and optimizations
+
+## 3.7.1 - 2013-07-05
+
+* Bug fix: Setting default options on a client now works
+* Bug fix: Setting options on HEAD requests now works. See #352
+* Bug fix: Moving stream factory before send event to before building the stream. See #353
+* Bug fix: Cookies no longer match on IP addresses per RFC 6265
+* Bug fix: Correctly parsing header parameters that are in `<>` and quotes
+* Added `cert` and `ssl_key` as request options
+* `Host` header can now diverge from the host part of a URL if the header is set manually
+* `Guzzle\Service\Command\LocationVisitor\Request\XmlVisitor` was rewritten to change from using SimpleXML to XMLWriter
+* OAuth parameters are only added via the plugin if they aren't already set
+* Exceptions are now thrown when a URL cannot be parsed
+* Returning `false` if `Guzzle\Http\EntityBody::getContentMd5()` fails
+* Not setting a `Content-MD5` on a command if calculating the Content-MD5 fails via the CommandContentMd5Plugin
+
+## 3.7.0 - 2013-06-10
+
+* See UPGRADING.md for more information on how to upgrade.
+* Requests now support the ability to specify an array of $options when creating a request to more easily modify a
+ request. You can pass a 'request.options' configuration setting to a client to apply default request options to
+ every request created by a client (e.g. default query string variables, headers, curl options, etc).
+* Added a static facade class that allows you to use Guzzle with static methods and mount the class to `\Guzzle`.
+ See `Guzzle\Http\StaticClient::mount`.
+* Added `command.request_options` to `Guzzle\Service\Command\AbstractCommand` to pass request options to requests
+ created by a command (e.g. custom headers, query string variables, timeout settings, etc).
+* Stream size in `Guzzle\Stream\PhpStreamRequestFactory` will now be set if Content-Length is returned in the
+ headers of a response
+* Added `Guzzle\Common\Collection::setPath($path, $value)` to set a value into an array using a nested key
+ (e.g. `$collection->setPath('foo/baz/bar', 'test'); echo $collection['foo']['bar']['bar'];`)
+* ServiceBuilders now support storing and retrieving arbitrary data
+* CachePlugin can now purge all resources for a given URI
+* CachePlugin can automatically purge matching cached items when a non-idempotent request is sent to a resource
+* CachePlugin now uses the Vary header to determine if a resource is a cache hit
+* `Guzzle\Http\Message\Response` now implements `\Serializable`
+* Added `Guzzle\Cache\CacheAdapterFactory::fromCache()` to more easily create cache adapters
+* `Guzzle\Service\ClientInterface::execute()` now accepts an array, single command, or Traversable
+* Fixed a bug in `Guzzle\Http\Message\Header\Link::addLink()`
+* Better handling of calculating the size of a stream in `Guzzle\Stream\Stream` using fstat() and caching the size
+* `Guzzle\Common\Exception\ExceptionCollection` now creates a more readable exception message
+* Fixing BC break: Added back the MonologLogAdapter implementation rather than extending from PsrLog so that older
+ Symfony users can still use the old version of Monolog.
+* Fixing BC break: Added the implementation back in for `Guzzle\Http\Message\AbstractMessage::getTokenizedHeader()`.
+ Now triggering an E_USER_DEPRECATED warning when used. Use `$message->getHeader()->parseParams()`.
+* Several performance improvements to `Guzzle\Common\Collection`
+* Added an `$options` argument to the end of the following methods of `Guzzle\Http\ClientInterface`:
+ createRequest, head, delete, put, patch, post, options, prepareRequest
+* Added an `$options` argument to the end of `Guzzle\Http\Message\Request\RequestFactoryInterface::createRequest()`
+* Added an `applyOptions()` method to `Guzzle\Http\Message\Request\RequestFactoryInterface`
+* Changed `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $body = null)` to
+ `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $options = array())`. You can still pass in a
+ resource, string, or EntityBody into the $options parameter to specify the download location of the response.
+* Changed `Guzzle\Common\Collection::__construct($data)` to no longer accepts a null value for `$data` but a
+ default `array()`
+* Added `Guzzle\Stream\StreamInterface::isRepeatable`
+* Removed `Guzzle\Http\ClientInterface::setDefaultHeaders(). Use
+ $client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`. or
+ $client->getConfig()->setPath('request.options/headers', array('header_name' => 'value'))`.
+* Removed `Guzzle\Http\ClientInterface::getDefaultHeaders(). Use $client->getConfig()->getPath('request.options/headers')`.
+* Removed `Guzzle\Http\ClientInterface::expandTemplate()`
+* Removed `Guzzle\Http\ClientInterface::setRequestFactory()`
+* Removed `Guzzle\Http\ClientInterface::getCurlMulti()`
+* Removed `Guzzle\Http\Message\RequestInterface::canCache`
+* Removed `Guzzle\Http\Message\RequestInterface::setIsRedirect`
+* Removed `Guzzle\Http\Message\RequestInterface::isRedirect`
+* Made `Guzzle\Http\Client::expandTemplate` and `getUriTemplate` protected methods.
+* You can now enable E_USER_DEPRECATED warnings to see if you are using a deprecated method by setting
+ `Guzzle\Common\Version::$emitWarnings` to true.
+* Marked `Guzzle\Http\Message\Request::isResponseBodyRepeatable()` as deprecated. Use
+ `$request->getResponseBody()->isRepeatable()` instead.
+* Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use
+ `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead.
+* Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use
+ `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead.
+* Marked `Guzzle\Http\Message\Request::setIsRedirect()` as deprecated. Use the HistoryPlugin instead.
+* Marked `Guzzle\Http\Message\Request::isRedirect()` as deprecated. Use the HistoryPlugin instead.
+* Marked `Guzzle\Cache\CacheAdapterFactory::factory()` as deprecated
+* Marked 'command.headers', 'command.response_body' and 'command.on_complete' as deprecated for AbstractCommand.
+ These will work through Guzzle 4.0
+* Marked 'request.params' for `Guzzle\Http\Client` as deprecated. Use [request.options][params].
+* Marked `Guzzle\Service\Client::enableMagicMethods()` as deprecated. Magic methods can no longer be disabled on a Guzzle\Service\Client.
+* Marked `Guzzle\Service\Client::getDefaultHeaders()` as deprecated. Use $client->getConfig()->getPath('request.options/headers')`.
+* Marked `Guzzle\Service\Client::setDefaultHeaders()` as deprecated. Use $client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`.
+* Marked `Guzzle\Parser\Url\UrlParser` as deprecated. Just use PHP's `parse_url()` and percent encode your UTF-8.
+* Marked `Guzzle\Common\Collection::inject()` as deprecated.
+* Marked `Guzzle\Plugin\CurlAuth\CurlAuthPlugin` as deprecated. Use `$client->getConfig()->setPath('request.options/auth', array('user', 'pass', 'Basic|Digest');`
+* CacheKeyProviderInterface and DefaultCacheKeyProvider are no longer used. All of this logic is handled in a
+ CacheStorageInterface. These two objects and interface will be removed in a future version.
+* Always setting X-cache headers on cached responses
+* Default cache TTLs are now handled by the CacheStorageInterface of a CachePlugin
+* `CacheStorageInterface::cache($key, Response $response, $ttl = null)` has changed to `cache(RequestInterface
+ $request, Response $response);`
+* `CacheStorageInterface::fetch($key)` has changed to `fetch(RequestInterface $request);`
+* `CacheStorageInterface::delete($key)` has changed to `delete(RequestInterface $request);`
+* Added `CacheStorageInterface::purge($url)`
+* `DefaultRevalidation::__construct(CacheKeyProviderInterface $cacheKey, CacheStorageInterface $cache, CachePlugin
+ $plugin)` has changed to `DefaultRevalidation::__construct(CacheStorageInterface $cache,
+ CanCacheStrategyInterface $canCache = null)`
+* Added `RevalidationInterface::shouldRevalidate(RequestInterface $request, Response $response)`
+
+## 3.6.0 - 2013-05-29
+
+* ServiceDescription now implements ToArrayInterface
+* Added command.hidden_params to blacklist certain headers from being treated as additionalParameters
+* Guzzle can now correctly parse incomplete URLs
+* Mixed casing of headers are now forced to be a single consistent casing across all values for that header.
+* Messages internally use a HeaderCollection object to delegate handling case-insensitive header resolution
+* Removed the whole changedHeader() function system of messages because all header changes now go through addHeader().
+* Specific header implementations can be created for complex headers. When a message creates a header, it uses a
+ HeaderFactory which can map specific headers to specific header classes. There is now a Link header and
+ CacheControl header implementation.
+* Removed from interface: Guzzle\Http\ClientInterface::setUriTemplate
+* Removed from interface: Guzzle\Http\ClientInterface::setCurlMulti()
+* Removed Guzzle\Http\Message\Request::receivedRequestHeader() and implemented this functionality in
+ Guzzle\Http\Curl\RequestMediator
+* Removed the optional $asString parameter from MessageInterface::getHeader(). Just cast the header to a string.
+* Removed the optional $tryChunkedTransfer option from Guzzle\Http\Message\EntityEnclosingRequestInterface
+* Removed the $asObjects argument from Guzzle\Http\Message\MessageInterface::getHeaders()
+* Removed Guzzle\Parser\ParserRegister::get(). Use getParser()
+* Removed Guzzle\Parser\ParserRegister::set(). Use registerParser().
+* All response header helper functions return a string rather than mixing Header objects and strings inconsistently
+* Removed cURL blacklist support. This is no longer necessary now that Expect, Accept, etc are managed by Guzzle
+ directly via interfaces
+* Removed the injecting of a request object onto a response object. The methods to get and set a request still exist
+ but are a no-op until removed.
+* Most classes that used to require a ``Guzzle\Service\Command\CommandInterface` typehint now request a
+ `Guzzle\Service\Command\ArrayCommandInterface`.
+* Added `Guzzle\Http\Message\RequestInterface::startResponse()` to the RequestInterface to handle injecting a response
+ on a request while the request is still being transferred
+* The ability to case-insensitively search for header values
+* Guzzle\Http\Message\Header::hasExactHeader
+* Guzzle\Http\Message\Header::raw. Use getAll()
+* Deprecated cache control specific methods on Guzzle\Http\Message\AbstractMessage. Use the CacheControl header object
+ instead.
+* `Guzzle\Service\Command\CommandInterface` now extends from ToArrayInterface and ArrayAccess
+* Added the ability to cast Model objects to a string to view debug information.
+
+## 3.5.0 - 2013-05-13
+
+* Bug: Fixed a regression so that request responses are parsed only once per oncomplete event rather than multiple times
+* Bug: Better cleanup of one-time events accross the board (when an event is meant to fire once, it will now remove
+ itself from the EventDispatcher)
+* Bug: `Guzzle\Log\MessageFormatter` now properly writes "total_time" and "connect_time" values
+* Bug: Cloning an EntityEnclosingRequest now clones the EntityBody too
+* Bug: Fixed an undefined index error when parsing nested JSON responses with a sentAs parameter that reference a
+ non-existent key
+* Bug: All __call() method arguments are now required (helps with mocking frameworks)
+* Deprecating Response::getRequest() and now using a shallow clone of a request object to remove a circular reference
+ to help with refcount based garbage collection of resources created by sending a request
+* Deprecating ZF1 cache and log adapters. These will be removed in the next major version.
+* Deprecating `Response::getPreviousResponse()` (method signature still exists, but it'sdeprecated). Use the
+ HistoryPlugin for a history.
+* Added a `responseBody` alias for the `response_body` location
+* Refactored internals to no longer rely on Response::getRequest()
+* HistoryPlugin can now be cast to a string
+* HistoryPlugin now logs transactions rather than requests and responses to more accurately keep track of the requests
+ and responses that are sent over the wire
+* Added `getEffectiveUrl()` and `getRedirectCount()` to Response objects
+
+## 3.4.3 - 2013-04-30
+
+* Bug fix: Fixing bug introduced in 3.4.2 where redirect responses are duplicated on the final redirected response
+* Added a check to re-extract the temp cacert bundle from the phar before sending each request
+
+## 3.4.2 - 2013-04-29
+
+* Bug fix: Stream objects now work correctly with "a" and "a+" modes
+* Bug fix: Removing `Transfer-Encoding: chunked` header when a Content-Length is present
+* Bug fix: AsyncPlugin no longer forces HEAD requests
+* Bug fix: DateTime timezones are now properly handled when using the service description schema formatter
+* Bug fix: CachePlugin now properly handles stale-if-error directives when a request to the origin server fails
+* Setting a response on a request will write to the custom request body from the response body if one is specified
+* LogPlugin now writes to php://output when STDERR is undefined
+* Added the ability to set multiple POST files for the same key in a single call
+* application/x-www-form-urlencoded POSTs now use the utf-8 charset by default
+* Added the ability to queue CurlExceptions to the MockPlugin
+* Cleaned up how manual responses are queued on requests (removed "queued_response" and now using request.before_send)
+* Configuration loading now allows remote files
+
+## 3.4.1 - 2013-04-16
+
+* Large refactoring to how CurlMulti handles work. There is now a proxy that sits in front of a pool of CurlMulti
+ handles. This greatly simplifies the implementation, fixes a couple bugs, and provides a small performance boost.
+* Exceptions are now properly grouped when sending requests in parallel
+* Redirects are now properly aggregated when a multi transaction fails
+* Redirects now set the response on the original object even in the event of a failure
+* Bug fix: Model names are now properly set even when using $refs
+* Added support for PHP 5.5's CurlFile to prevent warnings with the deprecated @ syntax
+* Added support for oauth_callback in OAuth signatures
+* Added support for oauth_verifier in OAuth signatures
+* Added support to attempt to retrieve a command first literally, then ucfirst, the with inflection
+
+## 3.4.0 - 2013-04-11
+
+* Bug fix: URLs are now resolved correctly based on http://tools.ietf.org/html/rfc3986#section-5.2. #289
+* Bug fix: Absolute URLs with a path in a service description will now properly override the base URL. #289
+* Bug fix: Parsing a query string with a single PHP array value will now result in an array. #263
+* Bug fix: Better normalization of the User-Agent header to prevent duplicate headers. #264.
+* Bug fix: Added `number` type to service descriptions.
+* Bug fix: empty parameters are removed from an OAuth signature
+* Bug fix: Revalidating a cache entry prefers the Last-Modified over the Date header
+* Bug fix: Fixed "array to string" error when validating a union of types in a service description
+* Bug fix: Removed code that attempted to determine the size of a stream when data is written to the stream
+* Bug fix: Not including an `oauth_token` if the value is null in the OauthPlugin.
+* Bug fix: Now correctly aggregating successful requests and failed requests in CurlMulti when a redirect occurs.
+* The new default CURLOPT_TIMEOUT setting has been increased to 150 seconds so that Guzzle works on poor connections.
+* Added a feature to EntityEnclosingRequest::setBody() that will automatically set the Content-Type of the request if
+ the Content-Type can be determined based on the entity body or the path of the request.
+* Added the ability to overwrite configuration settings in a client when grabbing a throwaway client from a builder.
+* Added support for a PSR-3 LogAdapter.
+* Added a `command.after_prepare` event
+* Added `oauth_callback` parameter to the OauthPlugin
+* Added the ability to create a custom stream class when using a stream factory
+* Added a CachingEntityBody decorator
+* Added support for `additionalParameters` in service descriptions to define how custom parameters are serialized.
+* The bundled SSL certificate is now provided in the phar file and extracted when running Guzzle from a phar.
+* You can now send any EntityEnclosingRequest with POST fields or POST files and cURL will handle creating bodies
+* POST requests using a custom entity body are now treated exactly like PUT requests but with a custom cURL method. This
+ means that the redirect behavior of POST requests with custom bodies will not be the same as POST requests that use
+ POST fields or files (the latter is only used when emulating a form POST in the browser).
+* Lots of cleanup to CurlHandle::factory and RequestFactory::createRequest
+
+## 3.3.1 - 2013-03-10
+
+* Added the ability to create PHP streaming responses from HTTP requests
+* Bug fix: Running any filters when parsing response headers with service descriptions
+* Bug fix: OauthPlugin fixes to allow for multi-dimensional array signing, and sorting parameters before signing
+* Bug fix: Removed the adding of default empty arrays and false Booleans to responses in order to be consistent across
+ response location visitors.
+* Bug fix: Removed the possibility of creating configuration files with circular dependencies
+* RequestFactory::create() now uses the key of a POST file when setting the POST file name
+* Added xmlAllowEmpty to serialize an XML body even if no XML specific parameters are set
+
+## 3.3.0 - 2013-03-03
+
+* A large number of performance optimizations have been made
+* Bug fix: Added 'wb' as a valid write mode for streams
+* Bug fix: `Guzzle\Http\Message\Response::json()` now allows scalar values to be returned
+* Bug fix: Fixed bug in `Guzzle\Http\Message\Response` where wrapping quotes were stripped from `getEtag()`
+* BC: Removed `Guzzle\Http\Utils` class
+* BC: Setting a service description on a client will no longer modify the client's command factories.
+* BC: Emitting IO events from a RequestMediator is now a parameter that must be set in a request's curl options using
+ the 'emit_io' key. This was previously set under a request's parameters using 'curl.emit_io'
+* BC: `Guzzle\Stream\Stream::getWrapper()` and `Guzzle\Stream\Stream::getSteamType()` are no longer converted to
+ lowercase
+* Operation parameter objects are now lazy loaded internally
+* Added ErrorResponsePlugin that can throw errors for responses defined in service description operations' errorResponses
+* Added support for instantiating responseType=class responseClass classes. Classes must implement
+ `Guzzle\Service\Command\ResponseClassInterface`
+* Added support for additionalProperties for top-level parameters in responseType=model responseClasses. These
+ additional properties also support locations and can be used to parse JSON responses where the outermost part of the
+ JSON is an array
+* Added support for nested renaming of JSON models (rename sentAs to name)
+* CachePlugin
+ * Added support for stale-if-error so that the CachePlugin can now serve stale content from the cache on error
+ * Debug headers can now added to cached response in the CachePlugin
+
+## 3.2.0 - 2013-02-14
+
+* CurlMulti is no longer reused globally. A new multi object is created per-client. This helps to isolate clients.
+* URLs with no path no longer contain a "/" by default
+* Guzzle\Http\QueryString does no longer manages the leading "?". This is now handled in Guzzle\Http\Url.
+* BadResponseException no longer includes the full request and response message
+* Adding setData() to Guzzle\Service\Description\ServiceDescriptionInterface
+* Adding getResponseBody() to Guzzle\Http\Message\RequestInterface
+* Various updates to classes to use ServiceDescriptionInterface type hints rather than ServiceDescription
+* Header values can now be normalized into distinct values when multiple headers are combined with a comma separated list
+* xmlEncoding can now be customized for the XML declaration of a XML service description operation
+* Guzzle\Http\QueryString now uses Guzzle\Http\QueryAggregator\QueryAggregatorInterface objects to add custom value
+ aggregation and no longer uses callbacks
+* The URL encoding implementation of Guzzle\Http\QueryString can now be customized
+* Bug fix: Filters were not always invoked for array service description parameters
+* Bug fix: Redirects now use a target response body rather than a temporary response body
+* Bug fix: The default exponential backoff BackoffPlugin was not giving when the request threshold was exceeded
+* Bug fix: Guzzle now takes the first found value when grabbing Cache-Control directives
+
+## 3.1.2 - 2013-01-27
+
+* Refactored how operation responses are parsed. Visitors now include a before() method responsible for parsing the
+ response body. For example, the XmlVisitor now parses the XML response into an array in the before() method.
+* Fixed an issue where cURL would not automatically decompress responses when the Accept-Encoding header was sent
+* CURLOPT_SSL_VERIFYHOST is never set to 1 because it is deprecated (see 5e0ff2ef20f839e19d1eeb298f90ba3598784444)
+* Fixed a bug where redirect responses were not chained correctly using getPreviousResponse()
+* Setting default headers on a client after setting the user-agent will not erase the user-agent setting
+
+## 3.1.1 - 2013-01-20
+
+* Adding wildcard support to Guzzle\Common\Collection::getPath()
+* Adding alias support to ServiceBuilder configs
+* Adding Guzzle\Service\Resource\CompositeResourceIteratorFactory and cleaning up factory interface
+
+## 3.1.0 - 2013-01-12
+
+* BC: CurlException now extends from RequestException rather than BadResponseException
+* BC: Renamed Guzzle\Plugin\Cache\CanCacheStrategyInterface::canCache() to canCacheRequest() and added CanCacheResponse()
+* Added getData to ServiceDescriptionInterface
+* Added context array to RequestInterface::setState()
+* Bug: Removing hard dependency on the BackoffPlugin from Guzzle\Http
+* Bug: Adding required content-type when JSON request visitor adds JSON to a command
+* Bug: Fixing the serialization of a service description with custom data
+* Made it easier to deal with exceptions thrown when transferring commands or requests in parallel by providing
+ an array of successful and failed responses
+* Moved getPath from Guzzle\Service\Resource\Model to Guzzle\Common\Collection
+* Added Guzzle\Http\IoEmittingEntityBody
+* Moved command filtration from validators to location visitors
+* Added `extends` attributes to service description parameters
+* Added getModels to ServiceDescriptionInterface
+
+## 3.0.7 - 2012-12-19
+
+* Fixing phar detection when forcing a cacert to system if null or true
+* Allowing filename to be passed to `Guzzle\Http\Message\Request::setResponseBody()`
+* Cleaning up `Guzzle\Common\Collection::inject` method
+* Adding a response_body location to service descriptions
+
+## 3.0.6 - 2012-12-09
+
+* CurlMulti performance improvements
+* Adding setErrorResponses() to Operation
+* composer.json tweaks
+
+## 3.0.5 - 2012-11-18
+
+* Bug: Fixing an infinite recursion bug caused from revalidating with the CachePlugin
+* Bug: Response body can now be a string containing "0"
+* Bug: Using Guzzle inside of a phar uses system by default but now allows for a custom cacert
+* Bug: QueryString::fromString now properly parses query string parameters that contain equal signs
+* Added support for XML attributes in service description responses
+* DefaultRequestSerializer now supports array URI parameter values for URI template expansion
+* Added better mimetype guessing to requests and post files
+
+## 3.0.4 - 2012-11-11
+
+* Bug: Fixed a bug when adding multiple cookies to a request to use the correct glue value
+* Bug: Cookies can now be added that have a name, domain, or value set to "0"
+* Bug: Using the system cacert bundle when using the Phar
+* Added json and xml methods to Response to make it easier to parse JSON and XML response data into data structures
+* Enhanced cookie jar de-duplication
+* Added the ability to enable strict cookie jars that throw exceptions when invalid cookies are added
+* Added setStream to StreamInterface to actually make it possible to implement custom rewind behavior for entity bodies
+* Added the ability to create any sort of hash for a stream rather than just an MD5 hash
+
+## 3.0.3 - 2012-11-04
+
+* Implementing redirects in PHP rather than cURL
+* Added PECL URI template extension and using as default parser if available
+* Bug: Fixed Content-Length parsing of Response factory
+* Adding rewind() method to entity bodies and streams. Allows for custom rewinding of non-repeatable streams.
+* Adding ToArrayInterface throughout library
+* Fixing OauthPlugin to create unique nonce values per request
+
+## 3.0.2 - 2012-10-25
+
+* Magic methods are enabled by default on clients
+* Magic methods return the result of a command
+* Service clients no longer require a base_url option in the factory
+* Bug: Fixed an issue with URI templates where null template variables were being expanded
+
+## 3.0.1 - 2012-10-22
+
+* Models can now be used like regular collection objects by calling filter, map, etc
+* Models no longer require a Parameter structure or initial data in the constructor
+* Added a custom AppendIterator to get around a PHP bug with the `\AppendIterator`
+
+## 3.0.0 - 2012-10-15
+
+* Rewrote service description format to be based on Swagger
+ * Now based on JSON schema
+ * Added nested input structures and nested response models
+ * Support for JSON and XML input and output models
+ * Renamed `commands` to `operations`
+ * Removed dot class notation
+ * Removed custom types
+* Broke the project into smaller top-level namespaces to be more component friendly
+* Removed support for XML configs and descriptions. Use arrays or JSON files.
+* Removed the Validation component and Inspector
+* Moved all cookie code to Guzzle\Plugin\Cookie
+* Magic methods on a Guzzle\Service\Client now return the command un-executed.
+* Calling getResult() or getResponse() on a command will lazily execute the command if needed.
+* Now shipping with cURL's CA certs and using it by default
+* Added previousResponse() method to response objects
+* No longer sending Accept and Accept-Encoding headers on every request
+* Only sending an Expect header by default when a payload is greater than 1MB
+* Added/moved client options:
+ * curl.blacklist to curl.option.blacklist
+ * Added ssl.certificate_authority
+* Added a Guzzle\Iterator component
+* Moved plugins from Guzzle\Http\Plugin to Guzzle\Plugin
+* Added a more robust backoff retry strategy (replaced the ExponentialBackoffPlugin)
+* Added a more robust caching plugin
+* Added setBody to response objects
+* Updating LogPlugin to use a more flexible MessageFormatter
+* Added a completely revamped build process
+* Cleaning up Collection class and removing default values from the get method
+* Fixed ZF2 cache adapters
+
+## 2.8.8 - 2012-10-15
+
+* Bug: Fixed a cookie issue that caused dot prefixed domains to not match where popular browsers did
+
+## 2.8.7 - 2012-09-30
+
+* Bug: Fixed config file aliases for JSON includes
+* Bug: Fixed cookie bug on a request object by using CookieParser to parse cookies on requests
+* Bug: Removing the path to a file when sending a Content-Disposition header on a POST upload
+* Bug: Hardening request and response parsing to account for missing parts
+* Bug: Fixed PEAR packaging
+* Bug: Fixed Request::getInfo
+* Bug: Fixed cases where CURLM_CALL_MULTI_PERFORM return codes were causing curl transactions to fail
+* Adding the ability for the namespace Iterator factory to look in multiple directories
+* Added more getters/setters/removers from service descriptions
+* Added the ability to remove POST fields from OAuth signatures
+* OAuth plugin now supports 2-legged OAuth
+
+## 2.8.6 - 2012-09-05
+
+* Added the ability to modify and build service descriptions
+* Added the use of visitors to apply parameters to locations in service descriptions using the dynamic command
+* Added a `json` parameter location
+* Now allowing dot notation for classes in the CacheAdapterFactory
+* Using the union of two arrays rather than an array_merge when extending service builder services and service params
+* Ensuring that a service is a string before doing strpos() checks on it when substituting services for references
+ in service builder config files.
+* Services defined in two different config files that include one another will by default replace the previously
+ defined service, but you can now create services that extend themselves and merge their settings over the previous
+* The JsonLoader now supports aliasing filenames with different filenames. This allows you to alias something like
+ '_default' with a default JSON configuration file.
+
+## 2.8.5 - 2012-08-29
+
+* Bug: Suppressed empty arrays from URI templates
+* Bug: Added the missing $options argument from ServiceDescription::factory to enable caching
+* Added support for HTTP responses that do not contain a reason phrase in the start-line
+* AbstractCommand commands are now invokable
+* Added a way to get the data used when signing an Oauth request before a request is sent
+
+## 2.8.4 - 2012-08-15
+
+* Bug: Custom delay time calculations are no longer ignored in the ExponentialBackoffPlugin
+* Added the ability to transfer entity bodies as a string rather than streamed. This gets around curl error 65. Set `body_as_string` in a request's curl options to enable.
+* Added a StreamInterface, EntityBodyInterface, and added ftell() to Guzzle\Common\Stream
+* Added an AbstractEntityBodyDecorator and a ReadLimitEntityBody decorator to transfer only a subset of a decorated stream
+* Stream and EntityBody objects will now return the file position to the previous position after a read required operation (e.g. getContentMd5())
+* Added additional response status codes
+* Removed SSL information from the default User-Agent header
+* DELETE requests can now send an entity body
+* Added an EventDispatcher to the ExponentialBackoffPlugin and added an ExponentialBackoffLogger to log backoff retries
+* Added the ability of the MockPlugin to consume mocked request bodies
+* LogPlugin now exposes request and response objects in the extras array
+
+## 2.8.3 - 2012-07-30
+
+* Bug: Fixed a case where empty POST requests were sent as GET requests
+* Bug: Fixed a bug in ExponentialBackoffPlugin that caused fatal errors when retrying an EntityEnclosingRequest that does not have a body
+* Bug: Setting the response body of a request to null after completing a request, not when setting the state of a request to new
+* Added multiple inheritance to service description commands
+* Added an ApiCommandInterface and added ``getParamNames()`` and ``hasParam()``
+* Removed the default 2mb size cutoff from the Md5ValidatorPlugin so that it now defaults to validating everything
+* Changed CurlMulti::perform to pass a smaller timeout to CurlMulti::executeHandles
+
+## 2.8.2 - 2012-07-24
+
+* Bug: Query string values set to 0 are no longer dropped from the query string
+* Bug: A Collection object is no longer created each time a call is made to ``Guzzle\Service\Command\AbstractCommand::getRequestHeaders()``
+* Bug: ``+`` is now treated as an encoded space when parsing query strings
+* QueryString and Collection performance improvements
+* Allowing dot notation for class paths in filters attribute of a service descriptions
+
+## 2.8.1 - 2012-07-16
+
+* Loosening Event Dispatcher dependency
+* POST redirects can now be customized using CURLOPT_POSTREDIR
+
+## 2.8.0 - 2012-07-15
+
+* BC: Guzzle\Http\Query
+ * Query strings with empty variables will always show an equal sign unless the variable is set to QueryString::BLANK (e.g. ?acl= vs ?acl)
+ * Changed isEncodingValues() and isEncodingFields() to isUrlEncoding()
+ * Changed setEncodeValues(bool) and setEncodeFields(bool) to useUrlEncoding(bool)
+ * Changed the aggregation functions of QueryString to be static methods
+ * Can now use fromString() with querystrings that have a leading ?
+* cURL configuration values can be specified in service descriptions using ``curl.`` prefixed parameters
+* Content-Length is set to 0 before emitting the request.before_send event when sending an empty request body
+* Cookies are no longer URL decoded by default
+* Bug: URI template variables set to null are no longer expanded
+
+## 2.7.2 - 2012-07-02
+
+* BC: Moving things to get ready for subtree splits. Moving Inflection into Common. Moving Guzzle\Http\Parser to Guzzle\Parser.
+* BC: Removing Guzzle\Common\Batch\Batch::count() and replacing it with isEmpty()
+* CachePlugin now allows for a custom request parameter function to check if a request can be cached
+* Bug fix: CachePlugin now only caches GET and HEAD requests by default
+* Bug fix: Using header glue when transferring headers over the wire
+* Allowing deeply nested arrays for composite variables in URI templates
+* Batch divisors can now return iterators or arrays
+
+## 2.7.1 - 2012-06-26
+
+* Minor patch to update version number in UA string
+* Updating build process
+
+## 2.7.0 - 2012-06-25
+
+* BC: Inflection classes moved to Guzzle\Inflection. No longer static methods. Can now inject custom inflectors into classes.
+* BC: Removed magic setX methods from commands
+* BC: Magic methods mapped to service description commands are now inflected in the command factory rather than the client __call() method
+* Verbose cURL options are no longer enabled by default. Set curl.debug to true on a client to enable.
+* Bug: Now allowing colons in a response start-line (e.g. HTTP/1.1 503 Service Unavailable: Back-end server is at capacity)
+* Guzzle\Service\Resource\ResourceIteratorApplyBatched now internally uses the Guzzle\Common\Batch namespace
+* Added Guzzle\Service\Plugin namespace and a PluginCollectionPlugin
+* Added the ability to set POST fields and files in a service description
+* Guzzle\Http\EntityBody::factory() now accepts objects with a __toString() method
+* Adding a command.before_prepare event to clients
+* Added BatchClosureTransfer and BatchClosureDivisor
+* BatchTransferException now includes references to the batch divisor and transfer strategies
+* Fixed some tests so that they pass more reliably
+* Added Guzzle\Common\Log\ArrayLogAdapter
+
+## 2.6.6 - 2012-06-10
+
+* BC: Removing Guzzle\Http\Plugin\BatchQueuePlugin
+* BC: Removing Guzzle\Service\Command\CommandSet
+* Adding generic batching system (replaces the batch queue plugin and command set)
+* Updating ZF cache and log adapters and now using ZF's composer repository
+* Bug: Setting the name of each ApiParam when creating through an ApiCommand
+* Adding result_type, result_doc, deprecated, and doc_url to service descriptions
+* Bug: Changed the default cookie header casing back to 'Cookie'
+
+## 2.6.5 - 2012-06-03
+
+* BC: Renaming Guzzle\Http\Message\RequestInterface::getResourceUri() to getResource()
+* BC: Removing unused AUTH_BASIC and AUTH_DIGEST constants from
+* BC: Guzzle\Http\Cookie is now used to manage Set-Cookie data, not Cookie data
+* BC: Renaming methods in the CookieJarInterface
+* Moving almost all cookie logic out of the CookiePlugin and into the Cookie or CookieJar implementations
+* Making the default glue for HTTP headers ';' instead of ','
+* Adding a removeValue to Guzzle\Http\Message\Header
+* Adding getCookies() to request interface.
+* Making it easier to add event subscribers to HasDispatcherInterface classes. Can now directly call addSubscriber()
+
+## 2.6.4 - 2012-05-30
+
+* BC: Cleaning up how POST files are stored in EntityEnclosingRequest objects. Adding PostFile class.
+* BC: Moving ApiCommand specific functionality from the Inspector and on to the ApiCommand
+* Bug: Fixing magic method command calls on clients
+* Bug: Email constraint only validates strings
+* Bug: Aggregate POST fields when POST files are present in curl handle
+* Bug: Fixing default User-Agent header
+* Bug: Only appending or prepending parameters in commands if they are specified
+* Bug: Not requiring response reason phrases or status codes to match a predefined list of codes
+* Allowing the use of dot notation for class namespaces when using instance_of constraint
+* Added any_match validation constraint
+* Added an AsyncPlugin
+* Passing request object to the calculateWait method of the ExponentialBackoffPlugin
+* Allowing the result of a command object to be changed
+* Parsing location and type sub values when instantiating a service description rather than over and over at runtime
+
+## 2.6.3 - 2012-05-23
+
+* [BC] Guzzle\Common\FromConfigInterface no longer requires any config options.
+* [BC] Refactoring how POST files are stored on an EntityEnclosingRequest. They are now separate from POST fields.
+* You can now use an array of data when creating PUT request bodies in the request factory.
+* Removing the requirement that HTTPS requests needed a Cache-Control: public directive to be cacheable.
+* [Http] Adding support for Content-Type in multipart POST uploads per upload
+* [Http] Added support for uploading multiple files using the same name (foo[0], foo[1])
+* Adding more POST data operations for easier manipulation of POST data.
+* You can now set empty POST fields.
+* The body of a request is only shown on EntityEnclosingRequest objects that do not use POST files.
+* Split the Guzzle\Service\Inspector::validateConfig method into two methods. One to initialize when a command is created, and one to validate.
+* CS updates
+
+## 2.6.2 - 2012-05-19
+
+* [Http] Better handling of nested scope requests in CurlMulti. Requests are now always prepares in the send() method rather than the addRequest() method.
+
+## 2.6.1 - 2012-05-19
+
+* [BC] Removing 'path' support in service descriptions. Use 'uri'.
+* [BC] Guzzle\Service\Inspector::parseDocBlock is now protected. Adding getApiParamsForClass() with cache.
+* [BC] Removing Guzzle\Common\NullObject. Use https://github.com/mtdowling/NullObject if you need it.
+* [BC] Removing Guzzle\Common\XmlElement.
+* All commands, both dynamic and concrete, have ApiCommand objects.
+* Adding a fix for CurlMulti so that if all of the connections encounter some sort of curl error, then the loop exits.
+* Adding checks to EntityEnclosingRequest so that empty POST files and fields are ignored.
+* Making the method signature of Guzzle\Service\Builder\ServiceBuilder::factory more flexible.
+
+## 2.6.0 - 2012-05-15
+
+* [BC] Moving Guzzle\Service\Builder to Guzzle\Service\Builder\ServiceBuilder
+* [BC] Executing a Command returns the result of the command rather than the command
+* [BC] Moving all HTTP parsing logic to Guzzle\Http\Parsers. Allows for faster C implementations if needed.
+* [BC] Changing the Guzzle\Http\Message\Response::setProtocol() method to accept a protocol and version in separate args.
+* [BC] Moving ResourceIterator* to Guzzle\Service\Resource
+* [BC] Completely refactored ResourceIterators to iterate over a cloned command object
+* [BC] Moved Guzzle\Http\UriTemplate to Guzzle\Http\Parser\UriTemplate\UriTemplate
+* [BC] Guzzle\Guzzle is now deprecated
+* Moving Guzzle\Common\Guzzle::inject to Guzzle\Common\Collection::inject
+* Adding Guzzle\Version class to give version information about Guzzle
+* Adding Guzzle\Http\Utils class to provide getDefaultUserAgent() and getHttpDate()
+* Adding Guzzle\Curl\CurlVersion to manage caching curl_version() data
+* ServiceDescription and ServiceBuilder are now cacheable using similar configs
+* Changing the format of XML and JSON service builder configs. Backwards compatible.
+* Cleaned up Cookie parsing
+* Trimming the default Guzzle User-Agent header
+* Adding a setOnComplete() method to Commands that is called when a command completes
+* Keeping track of requests that were mocked in the MockPlugin
+* Fixed a caching bug in the CacheAdapterFactory
+* Inspector objects can be injected into a Command object
+* Refactoring a lot of code and tests to be case insensitive when dealing with headers
+* Adding Guzzle\Http\Message\HeaderComparison for easy comparison of HTTP headers using a DSL
+* Adding the ability to set global option overrides to service builder configs
+* Adding the ability to include other service builder config files from within XML and JSON files
+* Moving the parseQuery method out of Url and on to QueryString::fromString() as a static factory method.
+
+## 2.5.0 - 2012-05-08
+
+* Major performance improvements
+* [BC] Simplifying Guzzle\Common\Collection. Please check to see if you are using features that are now deprecated.
+* [BC] Using a custom validation system that allows a flyweight implementation for much faster validation. No longer using Symfony2 Validation component.
+* [BC] No longer supporting "{{ }}" for injecting into command or UriTemplates. Use "{}"
+* Added the ability to passed parameters to all requests created by a client
+* Added callback functionality to the ExponentialBackoffPlugin
+* Using microtime in ExponentialBackoffPlugin to allow more granular backoff strategies.
+* Rewinding request stream bodies when retrying requests
+* Exception is thrown when JSON response body cannot be decoded
+* Added configurable magic method calls to clients and commands. This is off by default.
+* Fixed a defect that added a hash to every parsed URL part
+* Fixed duplicate none generation for OauthPlugin.
+* Emitting an event each time a client is generated by a ServiceBuilder
+* Using an ApiParams object instead of a Collection for parameters of an ApiCommand
+* cache.* request parameters should be renamed to params.cache.*
+* Added the ability to set arbitrary curl options on requests (disable_wire, progress, etc). See CurlHandle.
+* Added the ability to disable type validation of service descriptions
+* ServiceDescriptions and ServiceBuilders are now Serializable
diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/LICENSE b/modules/devshop/devshop_github/vendor/guzzle/guzzle/LICENSE
new file mode 100644
index 000000000..d51aa6986
--- /dev/null
+++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2011 Michael Dowling, https://github.com/mtdowling
+
+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.
diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/README.md b/modules/devshop/devshop_github/vendor/guzzle/guzzle/README.md
new file mode 100644
index 000000000..6be06bf47
--- /dev/null
+++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/README.md
@@ -0,0 +1,57 @@
+Guzzle, PHP HTTP client and webservice framework
+================================================
+
+# This is an old version of Guzzle
+
+This repository is for Guzzle 3.x. Guzzle 5.x, the new version of Guzzle, has
+been released and is available at
+[https://github.com/guzzle/guzzle](https://github.com/guzzle/guzzle). The
+documentation for Guzzle version 5+ can be found at
+[http://guzzlephp.org](http://guzzlephp.org).
+
+Guzzle 3 is only maintained for bug and security fixes. Guzzle 3 will be EOL
+at some point in late 2015.
+
+### About Guzzle 3
+
+[![Composer Downloads](https://poser.pugx.org/guzzle/guzzle/d/total.png)](https://packagist.org/packages/guzzle/guzzle)
+ [![Build Status](https://secure.travis-ci.org/guzzle/guzzle3.png?branch=master)](http://travis-ci.org/guzzle/guzzle3)
+
+- Extremely powerful API provides all the power of cURL with a simple interface.
+- Truly take advantage of HTTP/1.1 with persistent connections, connection pooling, and parallel requests.
+- Service description DSL allows you build awesome web service clients faster.
+- Symfony2 event-based plugin system allows you to completely modify the behavior of a request.
+
+Get answers with: [Documentation](http://guzzle3.readthedocs.org/en/latest/), [Forums](https://groups.google.com/forum/?hl=en#!forum/guzzle), IRC ([#guzzlephp](irc://irc.freenode.net/#guzzlephp) @ irc.freenode.net)
+
+### Installing via Composer
+
+The recommended way to install Guzzle is through [Composer](http://getcomposer.org).
+
+```bash
+# Install Composer
+curl -sS https://getcomposer.org/installer | php
+
+# Add Guzzle as a dependency
+php composer.phar require guzzle/guzzle:~3.9
+```
+
+After installing, you need to require Composer's autoloader:
+
+```php
+require 'vendor/autoload.php';
+```
+## Known Issues
+
+1. Problem following a specific redirect: https://github.com/guzzle/guzzle/issues/385.
+ This has been fixed in Guzzle 4/5.
+2. Root XML attributes not serialized in a service description: https://github.com/guzzle/guzzle3/issues/5.
+ This has been fixed in Guzzle 4/5.
+3. Accept-Encoding not preserved when following redirect: https://github.com/guzzle/guzzle3/issues/9
+ Fixed in Guzzle 4/5.
+4. String "Array" Transmitted w/ PostFiles and Duplicate Aggregator: https://github.com/guzzle/guzzle3/issues/10
+ Fixed in Guzzle 4/5.
+5. Recursive model references with array items: https://github.com/guzzle/guzzle3/issues/13
+ Fixed in Guzzle 4/5
+6. String "Array" Transmitted w/ PostFiles and Duplicate Aggregator: https://github.com/guzzle/guzzle3/issues/10
+ Fixed in Guzzle 4/5.
diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/UPGRADING.md b/modules/devshop/devshop_github/vendor/guzzle/guzzle/UPGRADING.md
new file mode 100644
index 000000000..f58bf1171
--- /dev/null
+++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/UPGRADING.md
@@ -0,0 +1,537 @@
+Guzzle Upgrade Guide
+====================
+
+3.6 to 3.7
+----------
+
+### Deprecations
+
+- You can now enable E_USER_DEPRECATED warnings to see if you are using any deprecated methods.:
+
+```php
+\Guzzle\Common\Version::$emitWarnings = true;
+```
+
+The following APIs and options have been marked as deprecated:
+
+- Marked `Guzzle\Http\Message\Request::isResponseBodyRepeatable()` as deprecated. Use `$request->getResponseBody()->isRepeatable()` instead.
+- Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead.
+- Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead.
+- Marked `Guzzle\Http\Message\Request::setIsRedirect()` as deprecated. Use the HistoryPlugin instead.
+- Marked `Guzzle\Http\Message\Request::isRedirect()` as deprecated. Use the HistoryPlugin instead.
+- Marked `Guzzle\Cache\CacheAdapterFactory::factory()` as deprecated
+- Marked `Guzzle\Service\Client::enableMagicMethods()` as deprecated. Magic methods can no longer be disabled on a Guzzle\Service\Client.
+- Marked `Guzzle\Parser\Url\UrlParser` as deprecated. Just use PHP's `parse_url()` and percent encode your UTF-8.
+- Marked `Guzzle\Common\Collection::inject()` as deprecated.
+- Marked `Guzzle\Plugin\CurlAuth\CurlAuthPlugin` as deprecated. Use
+ `$client->getConfig()->setPath('request.options/auth', array('user', 'pass', 'Basic|Digest|NTLM|Any'));` or
+ `$client->setDefaultOption('auth', array('user', 'pass', 'Basic|Digest|NTLM|Any'));`
+
+3.7 introduces `request.options` as a parameter for a client configuration and as an optional argument to all creational
+request methods. When paired with a client's configuration settings, these options allow you to specify default settings
+for various aspects of a request. Because these options make other previous configuration options redundant, several
+configuration options and methods of a client and AbstractCommand have been deprecated.
+
+- Marked `Guzzle\Service\Client::getDefaultHeaders()` as deprecated. Use `$client->getDefaultOption('headers')`.
+- Marked `Guzzle\Service\Client::setDefaultHeaders()` as deprecated. Use `$client->setDefaultOption('headers/{header_name}', 'value')`.
+- Marked 'request.params' for `Guzzle\Http\Client` as deprecated. Use `$client->setDefaultOption('params/{param_name}', 'value')`
+- Marked 'command.headers', 'command.response_body' and 'command.on_complete' as deprecated for AbstractCommand. These will work through Guzzle 4.0
+
+ $command = $client->getCommand('foo', array(
+ 'command.headers' => array('Test' => '123'),
+ 'command.response_body' => '/path/to/file'
+ ));
+
+ // Should be changed to:
+
+ $command = $client->getCommand('foo', array(
+ 'command.request_options' => array(
+ 'headers' => array('Test' => '123'),
+ 'save_as' => '/path/to/file'
+ )
+ ));
+
+### Interface changes
+
+Additions and changes (you will need to update any implementations or subclasses you may have created):
+
+- Added an `$options` argument to the end of the following methods of `Guzzle\Http\ClientInterface`:
+ createRequest, head, delete, put, patch, post, options, prepareRequest
+- Added an `$options` argument to the end of `Guzzle\Http\Message\Request\RequestFactoryInterface::createRequest()`
+- Added an `applyOptions()` method to `Guzzle\Http\Message\Request\RequestFactoryInterface`
+- Changed `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $body = null)` to
+ `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $options = array())`. You can still pass in a
+ resource, string, or EntityBody into the $options parameter to specify the download location of the response.
+- Changed `Guzzle\Common\Collection::__construct($data)` to no longer accepts a null value for `$data` but a
+ default `array()`
+- Added `Guzzle\Stream\StreamInterface::isRepeatable`
+- Made `Guzzle\Http\Client::expandTemplate` and `getUriTemplate` protected methods.
+
+The following methods were removed from interfaces. All of these methods are still available in the concrete classes
+that implement them, but you should update your code to use alternative methods:
+
+- Removed `Guzzle\Http\ClientInterface::setDefaultHeaders(). Use
+ `$client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`. or
+ `$client->getConfig()->setPath('request.options/headers', array('header_name' => 'value'))` or
+ `$client->setDefaultOption('headers/{header_name}', 'value')`. or
+ `$client->setDefaultOption('headers', array('header_name' => 'value'))`.
+- Removed `Guzzle\Http\ClientInterface::getDefaultHeaders(). Use `$client->getConfig()->getPath('request.options/headers')`.
+- Removed `Guzzle\Http\ClientInterface::expandTemplate()`. This is an implementation detail.
+- Removed `Guzzle\Http\ClientInterface::setRequestFactory()`. This is an implementation detail.
+- Removed `Guzzle\Http\ClientInterface::getCurlMulti()`. This is a very specific implementation detail.
+- Removed `Guzzle\Http\Message\RequestInterface::canCache`. Use the CachePlugin.
+- Removed `Guzzle\Http\Message\RequestInterface::setIsRedirect`. Use the HistoryPlugin.
+- Removed `Guzzle\Http\Message\RequestInterface::isRedirect`. Use the HistoryPlugin.
+
+### Cache plugin breaking changes
+
+- CacheKeyProviderInterface and DefaultCacheKeyProvider are no longer used. All of this logic is handled in a
+ CacheStorageInterface. These two objects and interface will be removed in a future version.
+- Always setting X-cache headers on cached responses
+- Default cache TTLs are now handled by the CacheStorageInterface of a CachePlugin
+- `CacheStorageInterface::cache($key, Response $response, $ttl = null)` has changed to `cache(RequestInterface
+ $request, Response $response);`
+- `CacheStorageInterface::fetch($key)` has changed to `fetch(RequestInterface $request);`
+- `CacheStorageInterface::delete($key)` has changed to `delete(RequestInterface $request);`
+- Added `CacheStorageInterface::purge($url)`
+- `DefaultRevalidation::__construct(CacheKeyProviderInterface $cacheKey, CacheStorageInterface $cache, CachePlugin
+ $plugin)` has changed to `DefaultRevalidation::__construct(CacheStorageInterface $cache,
+ CanCacheStrategyInterface $canCache = null)`
+- Added `RevalidationInterface::shouldRevalidate(RequestInterface $request, Response $response)`
+
+3.5 to 3.6
+----------
+
+* Mixed casing of headers are now forced to be a single consistent casing across all values for that header.
+* Messages internally use a HeaderCollection object to delegate handling case-insensitive header resolution
+* Removed the whole changedHeader() function system of messages because all header changes now go through addHeader().
+ For example, setHeader() first removes the header using unset on a HeaderCollection and then calls addHeader().
+ Keeping the Host header and URL host in sync is now handled by overriding the addHeader method in Request.
+* Specific header implementations can be created for complex headers. When a message creates a header, it uses a
+ HeaderFactory which can map specific headers to specific header classes. There is now a Link header and
+ CacheControl header implementation.
+* Moved getLinks() from Response to just be used on a Link header object.
+
+If you previously relied on Guzzle\Http\Message\Header::raw(), then you will need to update your code to use the
+HeaderInterface (e.g. toArray(), getAll(), etc).
+
+### Interface changes
+
+* Removed from interface: Guzzle\Http\ClientInterface::setUriTemplate
+* Removed from interface: Guzzle\Http\ClientInterface::setCurlMulti()
+* Removed Guzzle\Http\Message\Request::receivedRequestHeader() and implemented this functionality in
+ Guzzle\Http\Curl\RequestMediator
+* Removed the optional $asString parameter from MessageInterface::getHeader(). Just cast the header to a string.
+* Removed the optional $tryChunkedTransfer option from Guzzle\Http\Message\EntityEnclosingRequestInterface
+* Removed the $asObjects argument from Guzzle\Http\Message\MessageInterface::getHeaders()
+
+### Removed deprecated functions
+
+* Removed Guzzle\Parser\ParserRegister::get(). Use getParser()
+* Removed Guzzle\Parser\ParserRegister::set(). Use registerParser().
+
+### Deprecations
+
+* The ability to case-insensitively search for header values
+* Guzzle\Http\Message\Header::hasExactHeader
+* Guzzle\Http\Message\Header::raw. Use getAll()
+* Deprecated cache control specific methods on Guzzle\Http\Message\AbstractMessage. Use the CacheControl header object
+ instead.
+
+### Other changes
+
+* All response header helper functions return a string rather than mixing Header objects and strings inconsistently
+* Removed cURL blacklist support. This is no longer necessary now that Expect, Accept, etc are managed by Guzzle
+ directly via interfaces
+* Removed the injecting of a request object onto a response object. The methods to get and set a request still exist
+ but are a no-op until removed.
+* Most classes that used to require a ``Guzzle\Service\Command\CommandInterface` typehint now request a
+ `Guzzle\Service\Command\ArrayCommandInterface`.
+* Added `Guzzle\Http\Message\RequestInterface::startResponse()` to the RequestInterface to handle injecting a response
+ on a request while the request is still being transferred
+* `Guzzle\Service\Command\CommandInterface` now extends from ToArrayInterface and ArrayAccess
+
+3.3 to 3.4
+----------
+
+Base URLs of a client now follow the rules of http://tools.ietf.org/html/rfc3986#section-5.2.2 when merging URLs.
+
+3.2 to 3.3
+----------
+
+### Response::getEtag() quote stripping removed
+
+`Guzzle\Http\Message\Response::getEtag()` no longer strips quotes around the ETag response header
+
+### Removed `Guzzle\Http\Utils`
+
+The `Guzzle\Http\Utils` class was removed. This class was only used for testing.
+
+### Stream wrapper and type
+
+`Guzzle\Stream\Stream::getWrapper()` and `Guzzle\Stream\Stream::getSteamType()` are no longer converted to lowercase.
+
+### curl.emit_io became emit_io
+
+Emitting IO events from a RequestMediator is now a parameter that must be set in a request's curl options using the
+'emit_io' key. This was previously set under a request's parameters using 'curl.emit_io'
+
+3.1 to 3.2
+----------
+
+### CurlMulti is no longer reused globally
+
+Before 3.2, the same CurlMulti object was reused globally for each client. This can cause issue where plugins added
+to a single client can pollute requests dispatched from other clients.
+
+If you still wish to reuse the same CurlMulti object with each client, then you can add a listener to the
+ServiceBuilder's `service_builder.create_client` event to inject a custom CurlMulti object into each client as it is
+created.
+
+```php
+$multi = new Guzzle\Http\Curl\CurlMulti();
+$builder = Guzzle\Service\Builder\ServiceBuilder::factory('/path/to/config.json');
+$builder->addListener('service_builder.create_client', function ($event) use ($multi) {
+ $event['client']->setCurlMulti($multi);
+}
+});
+```
+
+### No default path
+
+URLs no longer have a default path value of '/' if no path was specified.
+
+Before:
+
+```php
+$request = $client->get('http://www.foo.com');
+echo $request->getUrl();
+// >> http://www.foo.com/
+```
+
+After:
+
+```php
+$request = $client->get('http://www.foo.com');
+echo $request->getUrl();
+// >> http://www.foo.com
+```
+
+### Less verbose BadResponseException
+
+The exception message for `Guzzle\Http\Exception\BadResponseException` no longer contains the full HTTP request and
+response information. You can, however, get access to the request and response object by calling `getRequest()` or
+`getResponse()` on the exception object.
+
+### Query parameter aggregation
+
+Multi-valued query parameters are no longer aggregated using a callback function. `Guzzle\Http\Query` now has a
+setAggregator() method that accepts a `Guzzle\Http\QueryAggregator\QueryAggregatorInterface` object. This object is
+responsible for handling the aggregation of multi-valued query string variables into a flattened hash.
+
+2.8 to 3.x
+----------
+
+### Guzzle\Service\Inspector
+
+Change `\Guzzle\Service\Inspector::fromConfig` to `\Guzzle\Common\Collection::fromConfig`
+
+**Before**
+
+```php
+use Guzzle\Service\Inspector;
+
+class YourClient extends \Guzzle\Service\Client
+{
+ public static function factory($config = array())
+ {
+ $default = array();
+ $required = array('base_url', 'username', 'api_key');
+ $config = Inspector::fromConfig($config, $default, $required);
+
+ $client = new self(
+ $config->get('base_url'),
+ $config->get('username'),
+ $config->get('api_key')
+ );
+ $client->setConfig($config);
+
+ $client->setDescription(ServiceDescription::factory(__DIR__ . DIRECTORY_SEPARATOR . 'client.json'));
+
+ return $client;
+ }
+```
+
+**After**
+
+```php
+use Guzzle\Common\Collection;
+
+class YourClient extends \Guzzle\Service\Client
+{
+ public static function factory($config = array())
+ {
+ $default = array();
+ $required = array('base_url', 'username', 'api_key');
+ $config = Collection::fromConfig($config, $default, $required);
+
+ $client = new self(
+ $config->get('base_url'),
+ $config->get('username'),
+ $config->get('api_key')
+ );
+ $client->setConfig($config);
+
+ $client->setDescription(ServiceDescription::factory(__DIR__ . DIRECTORY_SEPARATOR . 'client.json'));
+
+ return $client;
+ }
+```
+
+### Convert XML Service Descriptions to JSON
+
+**Before**
+
+```xml
+
+
+
+
+
+ Get a list of groups
+
+
+ Uses a search query to get a list of groups
+
+
+
+ Create a group
+
+
+
+
+ Delete a group by ID
+
+
+
+
+
+
+ Update a group
+
+
+
+
+
+
+```
+
+**After**
+
+```json
+{
+ "name": "Zendesk REST API v2",
+ "apiVersion": "2012-12-31",
+ "description":"Provides access to Zendesk views, groups, tickets, ticket fields, and users",
+ "operations": {
+ "list_groups": {
+ "httpMethod":"GET",
+ "uri": "groups.json",
+ "summary": "Get a list of groups"
+ },
+ "search_groups":{
+ "httpMethod":"GET",
+ "uri": "search.json?query=\"{query} type:group\"",
+ "summary": "Uses a search query to get a list of groups",
+ "parameters":{
+ "query":{
+ "location": "uri",
+ "description":"Zendesk Search Query",
+ "type": "string",
+ "required": true
+ }
+ }
+ },
+ "create_group": {
+ "httpMethod":"POST",
+ "uri": "groups.json",
+ "summary": "Create a group",
+ "parameters":{
+ "data": {
+ "type": "array",
+ "location": "body",
+ "description":"Group JSON",
+ "filters": "json_encode",
+ "required": true
+ },
+ "Content-Type":{
+ "type": "string",
+ "location":"header",
+ "static": "application/json"
+ }
+ }
+ },
+ "delete_group": {
+ "httpMethod":"DELETE",
+ "uri": "groups/{id}.json",
+ "summary": "Delete a group",
+ "parameters":{
+ "id":{
+ "location": "uri",
+ "description":"Group to delete by ID",
+ "type": "integer",
+ "required": true
+ }
+ }
+ },
+ "get_group": {
+ "httpMethod":"GET",
+ "uri": "groups/{id}.json",
+ "summary": "Get a ticket",
+ "parameters":{
+ "id":{
+ "location": "uri",
+ "description":"Group to get by ID",
+ "type": "integer",
+ "required": true
+ }
+ }
+ },
+ "update_group": {
+ "httpMethod":"PUT",
+ "uri": "groups/{id}.json",
+ "summary": "Update a group",
+ "parameters":{
+ "id": {
+ "location": "uri",
+ "description":"Group to update by ID",
+ "type": "integer",
+ "required": true
+ },
+ "data": {
+ "type": "array",
+ "location": "body",
+ "description":"Group JSON",
+ "filters": "json_encode",
+ "required": true
+ },
+ "Content-Type":{
+ "type": "string",
+ "location":"header",
+ "static": "application/json"
+ }
+ }
+ }
+}
+```
+
+### Guzzle\Service\Description\ServiceDescription
+
+Commands are now called Operations
+
+**Before**
+
+```php
+use Guzzle\Service\Description\ServiceDescription;
+
+$sd = new ServiceDescription();
+$sd->getCommands(); // @returns ApiCommandInterface[]
+$sd->hasCommand($name);
+$sd->getCommand($name); // @returns ApiCommandInterface|null
+$sd->addCommand($command); // @param ApiCommandInterface $command
+```
+
+**After**
+
+```php
+use Guzzle\Service\Description\ServiceDescription;
+
+$sd = new ServiceDescription();
+$sd->getOperations(); // @returns OperationInterface[]
+$sd->hasOperation($name);
+$sd->getOperation($name); // @returns OperationInterface|null
+$sd->addOperation($operation); // @param OperationInterface $operation
+```
+
+### Guzzle\Common\Inflection\Inflector
+
+Namespace is now `Guzzle\Inflection\Inflector`
+
+### Guzzle\Http\Plugin
+
+Namespace is now `Guzzle\Plugin`. Many other changes occur within this namespace and are detailed in their own sections below.
+
+### Guzzle\Http\Plugin\LogPlugin and Guzzle\Common\Log
+
+Now `Guzzle\Plugin\Log\LogPlugin` and `Guzzle\Log` respectively.
+
+**Before**
+
+```php
+use Guzzle\Common\Log\ClosureLogAdapter;
+use Guzzle\Http\Plugin\LogPlugin;
+
+/** @var \Guzzle\Http\Client */
+$client;
+
+// $verbosity is an integer indicating desired message verbosity level
+$client->addSubscriber(new LogPlugin(new ClosureLogAdapter(function($m) { echo $m; }, $verbosity = LogPlugin::LOG_VERBOSE);
+```
+
+**After**
+
+```php
+use Guzzle\Log\ClosureLogAdapter;
+use Guzzle\Log\MessageFormatter;
+use Guzzle\Plugin\Log\LogPlugin;
+
+/** @var \Guzzle\Http\Client */
+$client;
+
+// $format is a string indicating desired message format -- @see MessageFormatter
+$client->addSubscriber(new LogPlugin(new ClosureLogAdapter(function($m) { echo $m; }, $format = MessageFormatter::DEBUG_FORMAT);
+```
+
+### Guzzle\Http\Plugin\CurlAuthPlugin
+
+Now `Guzzle\Plugin\CurlAuth\CurlAuthPlugin`.
+
+### Guzzle\Http\Plugin\ExponentialBackoffPlugin
+
+Now `Guzzle\Plugin\Backoff\BackoffPlugin`, and other changes.
+
+**Before**
+
+```php
+use Guzzle\Http\Plugin\ExponentialBackoffPlugin;
+
+$backoffPlugin = new ExponentialBackoffPlugin($maxRetries, array_merge(
+ ExponentialBackoffPlugin::getDefaultFailureCodes(), array(429)
+ ));
+
+$client->addSubscriber($backoffPlugin);
+```
+
+**After**
+
+```php
+use Guzzle\Plugin\Backoff\BackoffPlugin;
+use Guzzle\Plugin\Backoff\HttpBackoffStrategy;
+
+// Use convenient factory method instead -- see implementation for ideas of what
+// you can do with chaining backoff strategies
+$backoffPlugin = BackoffPlugin::getExponentialBackoff($maxRetries, array_merge(
+ HttpBackoffStrategy::getDefaultFailureCodes(), array(429)
+ ));
+$client->addSubscriber($backoffPlugin);
+```
+
+### Known Issues
+
+#### [BUG] Accept-Encoding header behavior changed unintentionally.
+
+(See #217) (Fixed in 09daeb8c666fb44499a0646d655a8ae36456575e)
+
+In version 2.8 setting the `Accept-Encoding` header would set the CURLOPT_ENCODING option, which permitted cURL to
+properly handle gzip/deflate compressed responses from the server. In versions affected by this bug this does not happen.
+See issue #217 for a workaround, or use a version containing the fix.
diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/build.xml b/modules/devshop/devshop_github/vendor/guzzle/guzzle/build.xml
new file mode 100644
index 000000000..2aa62ba9a
--- /dev/null
+++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/build.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/composer.json b/modules/devshop/devshop_github/vendor/guzzle/guzzle/composer.json
new file mode 100644
index 000000000..59424b39b
--- /dev/null
+++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/composer.json
@@ -0,0 +1,82 @@
+{
+ "name": "guzzle/guzzle",
+ "type": "library",
+ "description": "PHP HTTP client. This library is deprecated in favor of https://packagist.org/packages/guzzlehttp/guzzle",
+ "keywords": ["framework", "http", "rest", "web service", "curl", "client", "HTTP client"],
+ "homepage": "http://guzzlephp.org/",
+ "license": "MIT",
+
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ },
+ {
+ "name": "Guzzle Community",
+ "homepage": "https://github.com/guzzle/guzzle/contributors"
+ }
+ ],
+
+ "replace": {
+ "guzzle/batch": "self.version",
+ "guzzle/cache": "self.version",
+ "guzzle/common": "self.version",
+ "guzzle/http": "self.version",
+ "guzzle/inflection": "self.version",
+ "guzzle/iterator": "self.version",
+ "guzzle/log": "self.version",
+ "guzzle/parser": "self.version",
+ "guzzle/plugin": "self.version",
+ "guzzle/plugin-async": "self.version",
+ "guzzle/plugin-backoff": "self.version",
+ "guzzle/plugin-cache": "self.version",
+ "guzzle/plugin-cookie": "self.version",
+ "guzzle/plugin-curlauth": "self.version",
+ "guzzle/plugin-error-response": "self.version",
+ "guzzle/plugin-history": "self.version",
+ "guzzle/plugin-log": "self.version",
+ "guzzle/plugin-md5": "self.version",
+ "guzzle/plugin-mock": "self.version",
+ "guzzle/plugin-oauth": "self.version",
+ "guzzle/service": "self.version",
+ "guzzle/stream": "self.version"
+ },
+
+ "require": {
+ "php": ">=5.3.3",
+ "ext-curl": "*",
+ "symfony/event-dispatcher": "~2.1"
+ },
+
+ "autoload": {
+ "psr-0": {
+ "Guzzle": "src/",
+ "Guzzle\\Tests": "tests/"
+ }
+ },
+
+ "suggest": {
+ "guzzlehttp/guzzle": "Guzzle 5 has moved to a new package name. The package you have installed, Guzzle 3, is deprecated."
+ },
+
+ "scripts": {
+ "test": "phpunit"
+ },
+
+ "require-dev": {
+ "doctrine/cache": "~1.3",
+ "symfony/class-loader": "~2.1",
+ "monolog/monolog": "~1.0",
+ "psr/log": "~1.0",
+ "zendframework/zend-cache": "2.*,<2.3",
+ "zendframework/zend-log": "2.*,<2.3",
+ "phpunit/phpunit": "3.7.*"
+ },
+
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.9-dev"
+ }
+ }
+}
diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/Makefile b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/Makefile
new file mode 100644
index 000000000..d92e03f95
--- /dev/null
+++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/Makefile
@@ -0,0 +1,153 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS =
+SPHINXBUILD = sphinx-build
+PAPER =
+BUILDDIR = _build
+
+# Internal variables.
+PAPEROPT_a4 = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+# the i18n builder cannot share the environment and doctrees with the others
+I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
+
+help:
+ @echo "Please use \`make ' where is one of"
+ @echo " html to make standalone HTML files"
+ @echo " dirhtml to make HTML files named index.html in directories"
+ @echo " singlehtml to make a single large HTML file"
+ @echo " pickle to make pickle files"
+ @echo " json to make JSON files"
+ @echo " htmlhelp to make HTML files and a HTML help project"
+ @echo " qthelp to make HTML files and a qthelp project"
+ @echo " devhelp to make HTML files and a Devhelp project"
+ @echo " epub to make an epub"
+ @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+ @echo " latexpdf to make LaTeX files and run them through pdflatex"
+ @echo " text to make text files"
+ @echo " man to make manual pages"
+ @echo " texinfo to make Texinfo files"
+ @echo " info to make Texinfo files and run them through makeinfo"
+ @echo " gettext to make PO message catalogs"
+ @echo " changes to make an overview of all changed/added/deprecated items"
+ @echo " linkcheck to check all external links for integrity"
+ @echo " doctest to run all doctests embedded in the documentation (if enabled)"
+
+clean:
+ -rm -rf $(BUILDDIR)/*
+
+html:
+ $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+ $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+singlehtml:
+ $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
+ @echo
+ @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
+
+pickle:
+ $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+ @echo
+ @echo "Build finished; now you can process the pickle files."
+
+json:
+ $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+ @echo
+ @echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+ $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+ @echo
+ @echo "Build finished; now you can run HTML Help Workshop with the" \
+ ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+ $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+ @echo
+ @echo "Build finished; now you can run "qcollectiongenerator" with the" \
+ ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+ @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Guzzle.qhcp"
+ @echo "To view the help file:"
+ @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Guzzle.qhc"
+
+devhelp:
+ $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
+ @echo
+ @echo "Build finished."
+ @echo "To view the help file:"
+ @echo "# mkdir -p $$HOME/.local/share/devhelp/Guzzle"
+ @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Guzzle"
+ @echo "# devhelp"
+
+epub:
+ $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
+ @echo
+ @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
+
+latex:
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+ @echo
+ @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+ @echo "Run \`make' in that directory to run these through (pdf)latex" \
+ "(use \`make latexpdf' here to do that automatically)."
+
+latexpdf:
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+ @echo "Running LaTeX files through pdflatex..."
+ $(MAKE) -C $(BUILDDIR)/latex all-pdf
+ @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+text:
+ $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
+ @echo
+ @echo "Build finished. The text files are in $(BUILDDIR)/text."
+
+man:
+ $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
+ @echo
+ @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
+
+texinfo:
+ $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+ @echo
+ @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
+ @echo "Run \`make' in that directory to run these through makeinfo" \
+ "(use \`make info' here to do that automatically)."
+
+info:
+ $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+ @echo "Running Texinfo files through makeinfo..."
+ make -C $(BUILDDIR)/texinfo info
+ @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
+
+gettext:
+ $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
+ @echo
+ @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
+
+changes:
+ $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+ @echo
+ @echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+ $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+ @echo
+ @echo "Link check complete; look for any errors in the above output " \
+ "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+ $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+ @echo "Testing of doctests in the sources finished, look at the " \
+ "results in $(BUILDDIR)/doctest/output.txt."
diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/_downloads/guzzle-schema-1.0.json b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/_downloads/guzzle-schema-1.0.json
new file mode 100644
index 000000000..81683026b
--- /dev/null
+++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/_downloads/guzzle-schema-1.0.json
@@ -0,0 +1,176 @@
+{
+ "additionalProperties": true,
+ "name": {
+ "type": "string",
+ "description": "Name of the web service"
+ },
+ "apiVersion": {
+ "type": ["string", "number"],
+ "description": "Version identifier that the service description is compatible with"
+ },
+ "baseUrl": {
+ "type": "string",
+ "description": "Base URL of the web service. Any relative URI specified in an operation will be merged with the baseUrl using the process defined in RFC 2396"
+ },
+ "basePath": {
+ "type": "string",
+ "description": "Alias of baseUrl"
+ },
+ "_description": {
+ "type": "string",
+ "description": "Short summary of the web service. This is actually called 'description' but this JSON schema wont validate using just description."
+ },
+ "operations": {
+ "description": "Operations of the web service",
+ "type": "object",
+ "properties": {
+ "extends": {
+ "type": "string",
+ "description": "Extend from another operation by name. The parent operation must be defined before the child."
+ },
+ "httpMethod": {
+ "type": "string",
+ "description": "HTTP method used with the operation (e.g. GET, POST, PUT, DELETE, PATCH, etc)"
+ },
+ "uri": {
+ "type": "string",
+ "description": "URI of the operation. The uri attribute can contain URI templates. The variables of the URI template are parameters of the operation with a location value of uri"
+ },
+ "summary": {
+ "type": "string",
+ "description": "Short summary of what the operation does"
+ },
+ "class": {
+ "type": "string",
+ "description": "Custom class to instantiate instead of the default Guzzle\\Service\\Command\\OperationCommand"
+ },
+ "responseClass": {
+ "type": "string",
+ "description": "This is what is returned from the method. Can be a primitive, class name, or model name."
+ },
+ "responseNotes": {
+ "type": "string",
+ "description": "A description of the response returned by the operation"
+ },
+ "responseType": {
+ "type": "string",
+ "description": "The type of response that the operation creates. If not specified, this value will be automatically inferred based on whether or not there is a model matching the name, if a matching class name is found, or set to 'primitive' by default.",
+ "enum": [ "primitive", "class", "model", "documentation" ]
+ },
+ "deprecated": {
+ "type": "boolean",
+ "description": "Whether or not the operation is deprecated"
+ },
+ "errorResponses": {
+ "description": "Errors that could occur while executing the operation",
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "code": {
+ "type": "number",
+ "description": "HTTP response status code of the error"
+ },
+ "reason": {
+ "type": "string",
+ "description": "Response reason phrase or description of the error"
+ },
+ "class": {
+ "type": "string",
+ "description": "A custom exception class that would be thrown if the error is encountered"
+ }
+ }
+ }
+ },
+ "data": {
+ "type": "object",
+ "additionalProperties": "true"
+ },
+ "parameters": {
+ "$ref": "parameters",
+ "description": "Parameters of the operation. Parameters are used to define how input data is serialized into a HTTP request."
+ },
+ "additionalParameters": {
+ "$ref": "parameters",
+ "description": "Validation and serialization rules for any parameter supplied to the operation that was not explicitly defined."
+ }
+ }
+ },
+ "models": {
+ "description": "Schema models that can be referenced throughout the service description. Models can be used to define how an HTTP response is parsed into a Guzzle\\Service\\Resource\\Model object.",
+ "type": "object",
+ "properties": {
+ "$ref": "parameters",
+ "description": "Parameters of the model. When a model is referenced in a responseClass attribute of an operation, parameters define how a HTTP response message is parsed into a Guzzle\\Service\\Resource\\Model."
+ }
+ },
+ "includes": {
+ "description": "Service description files to include and extend from (can be a .json, .js, or .php file)",
+ "type": "array",
+ "items": {
+ "type": "string",
+ "pattern": ".+\\.(js|json|php)$"
+ }
+ },
+ "definitions": {
+ "parameters": {
+ "extends": "http://json-schema.org/schema",
+ "id": "parameters",
+ "name": {
+ "type": "string",
+ "description": "Unique name of the parameter"
+ },
+ "type": {
+ "type": ["string", "array"],
+ "description": "Type of variable (string, number, integer, boolean, object, array, numeric, null, any). Types are using for validation and determining the structure of a parameter. You can use a union type by providing an array of simple types. If one of the union types matches the provided value, then the value is valid."
+ },
+ "instanceOf": {
+ "type": "string",
+ "description": "When the type is an object, you can specify the class that the object must implement"
+ },
+ "required": {
+ "type": "boolean",
+ "description": "Whether or not the parameter is required"
+ },
+ "default": {
+ "description": "Default value to use if no value is supplied"
+ },
+ "static": {
+ "type": "bool",
+ "description": "Set to true to specify that the parameter value cannot be changed from the default setting"
+ },
+ "description": {
+ "type": "string",
+ "description": "Documentation of the parameter"
+ },
+ "location": {
+ "type": "string",
+ "description": "The location of a request used to apply a parameter. Custom locations can be registered with a command, but the defaults are uri, query, statusCode, reasonPhrase, header, body, json, xml, postField, postFile, responseBody"
+ },
+ "sentAs": {
+ "type": "string",
+ "description": "Specifies how the data being modeled is sent over the wire. For example, you may wish to include certain headers in a response model that have a normalized casing of FooBar, but the actual header is x-foo-bar. In this case, sentAs would be set to x-foo-bar."
+ },
+ "filters": {
+ "type": "array",
+ "description": "Array of static method names to to run a parameter value through. Each value in the array must be a string containing the full class path to a static method or an array of complex filter information. You can specify static methods of classes using the full namespace class name followed by ‘::’ (e.g. FooBar::baz()). Some filters require arguments in order to properly filter a value. For complex filters, use a hash containing a ‘method’ key pointing to a static method, and an ‘args’ key containing an array of positional arguments to pass to the method. Arguments can contain keywords that are replaced when filtering a value: '@value‘ is replaced with the value being validated, '@api‘ is replaced with the Parameter object.",
+ "items": {
+ "type": ["string", {
+ "object": {
+ "properties": {
+ "method": {
+ "type": "string",
+ "description": "PHP function to call",
+ "required": true
+ },
+ "args": {
+ "type": "array"
+ }
+ }
+ }
+ }]
+ }
+ }
+ }
+ }
+}
diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/_static/guzzle-icon.png b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/_static/guzzle-icon.png
new file mode 100644
index 000000000..f1017f7e6
Binary files /dev/null and b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/_static/guzzle-icon.png differ
diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/_static/homepage.css b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/_static/homepage.css
new file mode 100644
index 000000000..70c46d8d5
--- /dev/null
+++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/_static/homepage.css
@@ -0,0 +1,122 @@
+/* Hero unit on homepage */
+
+.hero-unit h1 {
+ font-size: 49px;
+ margin-bottom: 12px;
+}
+
+.hero-unit {
+ padding: 40px;
+}
+
+.hero-unit p {
+ font-size: 17px;
+}
+
+.masthead img {
+ float: left;
+ margin-right: 17px;
+}
+
+.hero-unit ul li {
+ margin-left: 220px;
+}
+
+.hero-unit .buttons {
+ text-align: center;
+}
+
+.jumbotron {
+ position: relative;
+ padding: 40px 0;
+ color: #fff;
+ text-shadow: 0 1px 3px rgba(0,0,0,.4), 0 0 30px rgba(0,0,0,.075);
+ background: #00312F;
+ background: -moz-linear-gradient(45deg, #002F31 0%, #335A6D 100%);
+ background: -webkit-gradient(linear, left bottom, right top, color-stop(0%,#00312D), color-stop(100%,#33566D));
+ background: -webkit-linear-gradient(45deg, #020031 0%,#334F6D 100%);
+ background: -o-linear-gradient(45deg, #002D31 0%,#334D6D 100%);
+ background: -ms-linear-gradient(45deg, #002F31 0%,#33516D 100%);
+ background: linear-gradient(45deg, #020031 0%,#33516D 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#020031', endColorstr='#6d3353',GradientType=1 );
+ -webkit-box-shadow: inset 0 3px 7px rgba(0, 0, 0, .2), inset 0 -3px 7px rgba(0, 0, 0, .2);
+ -moz-box-shadow: inset 0 3px 7px rgba(0,0,0,.2), inset 0 -3px 7px rgba(0,0,0,.2);
+ box-shadow: inset 0 3px 7px rgba(0, 0, 0, .2), inset 0 -3px 7px rgba(0, 0, 0, .2);
+}
+
+.jumbotron h1 {
+ font-size: 80px;
+ font-weight: bold;
+ letter-spacing: -1px;
+ line-height: 1;
+}
+
+.jumbotron p {
+ font-size: 24px;
+ font-weight: 300;
+ line-height: 1.25;
+ margin-bottom: 30px;
+}
+
+.masthead {
+ padding: 40px 0 30px;
+ margin-bottom: 0;
+ color: #fff;
+ margin-top: -19px;
+}
+
+.masthead h1 {
+ display: none;
+}
+
+.masthead p {
+ font-size: 40px;
+ font-weight: 200;
+ line-height: 1.25;
+ margin: 12px 0 0 0;
+}
+
+.masthead .btn {
+ padding: 19px 24px;
+ font-size: 24px;
+ font-weight: 200;
+ border: 0;
+}
+
+/* Social bar on homepage */
+
+.social {
+ padding: 2px 0;
+ text-align: center;
+ background-color: #f5f5f5;
+ border-top: 1px solid #fff;
+ border-bottom: 1px solid #ddd;
+ margin: 0 0 20px 0;
+}
+
+.social ul {
+ margin-top: 0;
+}
+
+.social-buttons {
+ margin-left: 0;
+ margin-bottom: 0;
+ padding-left: 0;
+ list-style: none;
+}
+
+.social-buttons li {
+ display: inline-block;
+ padding: 5px 8px;
+ line-height: 1;
+ *display: inline;
+ *zoom: 1;
+}
+
+.center-announcement {
+ padding: 10px;
+ background-color: rgb(238, 243, 255);
+ border-radius: 8px;
+ text-align: center;
+ margin: 24px 0;
+}
diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/_static/logo.png b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/_static/logo.png
new file mode 100644
index 000000000..965a4ef41
Binary files /dev/null and b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/_static/logo.png differ
diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/_static/prettify.css b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/_static/prettify.css
new file mode 100644
index 000000000..4d410b12e
--- /dev/null
+++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/_static/prettify.css
@@ -0,0 +1,41 @@
+.com {
+ color: #93A1A1;
+}
+.lit {
+ color: #195F91;
+}
+.pun, .opn, .clo {
+ color: #93A1A1;
+}
+.fun {
+ color: #DC322F;
+}
+.str, .atv {
+ color: #DD1144;
+}
+.kwd, .linenums .tag {
+ color: #1E347B;
+}
+.typ, .atn, .dec, .var {
+ color: teal;
+}
+.pln {
+ color: #48484C;
+}
+.prettyprint {
+ background-color: #F7F7F9;
+ border: 1px solid #E1E1E8;
+ padding: 8px;
+}
+.prettyprint.linenums {
+ box-shadow: 40px 0 0 #FBFBFC inset, 41px 0 0 #ECECF0 inset;
+}
+ol.linenums {
+ margin: 0 0 0 33px;
+}
+ol.linenums li {
+ color: #BEBEC5;
+ line-height: 18px;
+ padding-left: 12px;
+ text-shadow: 0 1px 0 #FFFFFF;
+}
diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/_static/prettify.js b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/_static/prettify.js
new file mode 100644
index 000000000..eef5ad7e6
--- /dev/null
+++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/_static/prettify.js
@@ -0,0 +1,28 @@
+var q=null;window.PR_SHOULD_USE_CONTINUATION=!0;
+(function(){function L(a){function m(a){var f=a.charCodeAt(0);if(f!==92)return f;var b=a.charAt(1);return(f=r[b])?f:"0"<=b&&b<="7"?parseInt(a.substring(1),8):b==="u"||b==="x"?parseInt(a.substring(2),16):a.charCodeAt(1)}function e(a){if(a<32)return(a<16?"\\x0":"\\x")+a.toString(16);a=String.fromCharCode(a);if(a==="\\"||a==="-"||a==="["||a==="]")a="\\"+a;return a}function h(a){for(var f=a.substring(1,a.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),a=
+[],b=[],o=f[0]==="^",c=o?1:0,i=f.length;c122||(d<65||j>90||b.push([Math.max(65,j)|32,Math.min(d,90)|32]),d<97||j>122||b.push([Math.max(97,j)&-33,Math.min(d,122)&-33]))}}b.sort(function(a,f){return a[0]-f[0]||f[1]-a[1]});f=[];j=[NaN,NaN];for(c=0;ci[0]&&(i[1]+1>i[0]&&b.push("-"),b.push(e(i[1])));b.push("]");return b.join("")}function y(a){for(var f=a.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),b=f.length,d=[],c=0,i=0;c=2&&a==="["?f[c]=h(j):a!=="\\"&&(f[c]=j.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return f.join("")}for(var t=0,s=!1,l=!1,p=0,d=a.length;p=5&&"lang-"===b.substring(0,5))&&!(o&&typeof o[1]==="string"))c=!1,b="src";c||(r[f]=b)}i=d;d+=f.length;if(c){c=o[1];var j=f.indexOf(c),k=j+c.length;o[2]&&(k=f.length-o[2].length,j=k-c.length);b=b.substring(5);B(l+i,f.substring(0,j),e,p);B(l+i+j,c,C(b,c),p);B(l+i+k,f.substring(k),e,p)}else p.push(l+i,b)}a.e=p}var h={},y;(function(){for(var e=a.concat(m),
+l=[],p={},d=0,g=e.length;d=0;)h[n.charAt(k)]=r;r=r[1];n=""+r;p.hasOwnProperty(n)||(l.push(r),p[n]=q)}l.push(/[\S\s]/);y=L(l)})();var t=m.length;return e}function u(a){var m=[],e=[];a.tripleQuotedStrings?m.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?m.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/,
+q,"'\"`"]):m.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&e.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var h=a.hashComments;h&&(a.cStyleComments?(h>1?m.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):m.push(["com",/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),e.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,q])):m.push(["com",/^#[^\n\r]*/,
+q,"#"]));a.cStyleComments&&(e.push(["com",/^\/\/[^\n\r]*/,q]),e.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));a.regexLiterals&&e.push(["lang-regex",/^(?:^^\.?|[!+-]|!=|!==|#|%|%=|&|&&|&&=|&=|\(|\*|\*=|\+=|,|-=|->|\/|\/=|:|::|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|[?@[^]|\^=|\^\^|\^\^=|{|\||\|=|\|\||\|\|=|~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\s*(\/(?=[^*/])(?:[^/[\\]|\\[\S\s]|\[(?:[^\\\]]|\\[\S\s])*(?:]|$))+\/)/]);(h=a.types)&&e.push(["typ",h]);a=(""+a.keywords).replace(/^ | $/g,
+"");a.length&&e.push(["kwd",RegExp("^(?:"+a.replace(/[\s,]+/g,"|")+")\\b"),q]);m.push(["pln",/^\s+/,q," \r\n\t\xa0"]);e.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/,q],["pun",/^.[^\s\w"-$'./@\\`]*/,q]);return x(m,e)}function D(a,m){function e(a){switch(a.nodeType){case 1:if(k.test(a.className))break;if("BR"===a.nodeName)h(a),
+a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)e(a);break;case 3:case 4:if(p){var b=a.nodeValue,d=b.match(t);if(d){var c=b.substring(0,d.index);a.nodeValue=c;(b=b.substring(d.index+d[0].length))&&a.parentNode.insertBefore(s.createTextNode(b),a.nextSibling);h(a);c||a.parentNode.removeChild(a)}}}}function h(a){function b(a,d){var e=d?a.cloneNode(!1):a,f=a.parentNode;if(f){var f=b(f,1),g=a.nextSibling;f.appendChild(e);for(var h=g;h;h=g)g=h.nextSibling,f.appendChild(h)}return e}
+for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),e;(e=a.parentNode)&&e.nodeType===1;)a=e;d.push(a)}var k=/(?:^|\s)nocode(?:\s|$)/,t=/\r\n?|\n/,s=a.ownerDocument,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=s.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);for(l=s.createElement("LI");a.firstChild;)l.appendChild(a.firstChild);for(var d=[l],g=0;g=0;){var h=m[e];A.hasOwnProperty(h)?window.console&&console.warn("cannot override language handler %s",h):A[h]=a}}function C(a,m){if(!a||!A.hasOwnProperty(a))a=/^\s*=o&&(h+=2);e>=c&&(a+=2)}}catch(w){"console"in window&&console.log(w&&w.stack?w.stack:w)}}var v=["break,continue,do,else,for,if,return,while"],w=[[v,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"],
+"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],F=[w,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],G=[w,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"],
+H=[G,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"],w=[w,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],I=[v,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"],
+J=[v,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],v=[v,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],K=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/,N=/\S/,O=u({keywords:[F,H,w,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END"+
+I,J,v],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),A={};k(O,["default-code"]);k(x([],[["pln",/^[^]+/],["dec",/^]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^
+
+
+
+
+
+
+
Guzzle
+
Guzzle is a PHP HTTP client & framework for building RESTful web service clients.
Guzzle takes the pain out of sending HTTP requests and the redundancy out of creating web service clients. It's
+ a framework that includes the tools needed to create a robust web service client, including:
+ Service descriptions for defining the inputs and outputs of an API, resource iterators for traversing
+ paginated resources, batching for sending a large number of requests as efficiently as possible.
+ Guzzle is now part of Drupal 8 core and powers the official AWS SDK for PHP
+
+
+
GitHub Example
+
+
<?php
+require_once 'vendor/autoload.php';
+use Guzzle\Http\Client;
+
+// Create a client and provide a base URL
+$client = new Client('https://api.github.com');
+// Create a request with basic Auth
+$request = $client->get('/user')->setAuth('user', 'pass');
+// Send the request and get the response
+$response = $request->send();
+echo $response->getBody();
+// >>> {"type":"User", ...
+echo $response->getHeader('Content-Length');
+// >>> 792
+
+
+
Twitter Example
+
<?php
+// Create a client to work with the Twitter API
+$client = new Client('https://api.twitter.com/{version}', array(
+ 'version' => '1.1'
+));
+
+// Sign all requests with the OauthPlugin
+$client->addSubscriber(new Guzzle\Plugin\Oauth\OauthPlugin(array(
+ 'consumer_key' => '***',
+ 'consumer_secret' => '***',
+ 'token' => '***',
+ 'token_secret' => '***'
+)));
+
+echo $client->get('statuses/user_timeline.json')->send()->getBody();
+// >>> {"public_gists":6,"type":"User" ...
+
+// Create a tweet using POST
+$request = $client->post('statuses/update.json', null, array(
+ 'status' => 'Tweeted with Guzzle, http://guzzlephp.org'
+));
+
+// Send the request and parse the JSON response into an array
+$data = $request->send()->json();
+echo $data['text'];
+// >>> Tweeted with Guzzle, http://t.co/kngJMfRk
+
+
+
+
diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/_templates/leftbar.html b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/_templates/leftbar.html
new file mode 100644
index 000000000..e69de29bb
diff --git a/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/_templates/nav_links.html b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/_templates/nav_links.html
new file mode 100644
index 000000000..d4f2165bb
--- /dev/null
+++ b/modules/devshop/devshop_github/vendor/guzzle/guzzle/docs/_templates/nav_links.html
@@ -0,0 +1,5 @@
+
+
+
+HTML;
+
+}
+
+/**
+ * Replacement for hosting_get_tasks() that will load tasks for the platform and the site, as well as clone targets.
+ * @param $environment
+ * The environment object.
+ *
+ * @param null $type
+ * The task type you wish to retrieve.
+ *
+ * @param null $key
+ *
+ * If $key is specified, the return array will be re-ordered, with the key of
+ * the array being set to $task_node->$key.
+ *
+ * For example, if you want all tasks for an environment, sorted by date, use
+ * 'vid' for the $key parameter:
+ *
+ * $tasks = devshop_get_tasks($environment, NULL, 'vid');
+ *
+ * @return array
+ *
+ * By default, an array is returned with Task types as the keys. Each element
+ * in the array is another array, keyed by VID.
+ *
+ */
+function devshop_get_tasks($environment, $type = NULL, $key = NULL, $limit = 20) {
+
+ // Load tasks from hosting_task table for both site and platform.
+ $args = array();
+ if ($environment->site) {
+ if ($type) {
+ $where = "(t.rid = :site OR t.rid = :platform) AND t.task_type = :type";
+ $args = array(
+ ':site' => $environment->site,
+ ':platform' => $environment->platform,
+ ':type' => $type
+ );
+ }
+ else {
+ $where = "(t.rid = :site OR t.rid = :platform)";
+ $args = array(
+ ':site' => $environment->site,
+ ':platform' => $environment->platform,
+ );
+ }
+ }
+ else {
+ if ($type) {
+ $where = "(t.rid = :platform AND t.task_type = :type)";
+ $args = array(
+ ':platform' => $environment->platform,
+ ':type' => $type
+ );
+ }
+ else {
+ $where = "(t.rid = :platform)";
+ $args = array(
+ ':platform' => $environment->platform,
+ );
+ }
+ }
+
+ // Ensure limit is safe.
+ if (!is_int($limit) || $limit <= 0) {
+ $limit = 20;
+ }
+ $results = db_query("
+ SELECT t.nid, t.task_type
+ FROM {node} tn
+ INNER JOIN {hosting_task} t ON tn.nid = t.nid
+ WHERE
+ $where
+ ORDER BY nid DESC
+ LIMIT $limit
+ ",
+ $args
+ );
+ $tasks = array();
+ foreach ($results as $result) {
+ $tasks[$result->task_type][$result->nid] = node_load($result->nid);
+ }
+
+ // Load "Clone" tasks
+ if ($type == NULL || $type == 'clone') {
+ $results = db_query(
+ "
+ SELECT ta.nid
+ FROM {hosting_task_arguments} ta
+ INNER JOIN {hosting_task} t ON ta.nid = t.nid
+ WHERE
+ name = :name AND value = :platform
+ LIMIT 1
+ ",
+ array(
+ ':platform' => $environment->platform,
+ ':name' => 'target_platform',
+ )
+ );
+ foreach ($results as $result) {
+ $task_node = node_load($result->nid);
+ $tasks[$task_node->task_type][$task_node->nid] = $task_node;
+ }
+ }
+
+ // If key is requested...
+ if ($key) {
+ foreach ($tasks as $task_type => $task_list) {
+ foreach ($task_list as $task) {
+ $new_task_list[$task->{$key}] = $task;
+ }
+ }
+ if ($key == 'nid') {
+ krsort($new_task_list);
+ }
+ else {
+ ksort($new_task_list);
+ }
+ return $new_task_list;
+ }
+ else {
+ ksort($tasks);
+ return $tasks;
+ }
+}
diff --git a/modules/devshop/devshop_permissions/devshop_permissions.features.user_permission.inc b/modules/devshop/devshop_permissions/devshop_permissions.features.user_permission.inc
new file mode 100644
index 000000000..95e9199b1
--- /dev/null
+++ b/modules/devshop/devshop_permissions/devshop_permissions.features.user_permission.inc
@@ -0,0 +1,749 @@
+ 'access content',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: access disabled sites
+ $permissions['access disabled sites'] = array(
+ 'name' => 'access disabled sites',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'anonymous user',
+ '2' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: access filemanager
+ $permissions['access filemanager'] = array(
+ 'name' => 'access filemanager',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: access hosting logs
+ $permissions['access hosting logs'] = array(
+ 'name' => 'access hosting logs',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: access hosting wizard
+ $permissions['access hosting wizard'] = array(
+ 'name' => 'access hosting wizard',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: access project logs
+ $permissions['access project logs'] = array(
+ 'name' => 'access project logs',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: access task logs
+ $permissions['access task logs'] = array(
+ 'name' => 'access task logs',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: administer SSH public keys
+ $permissions['administer SSH public keys'] = array(
+ 'name' => 'administer SSH public keys',
+ 'roles' => array(
+ '0' => 'administrator',
+ ),
+ );
+
+ // Exported permission: administer clients
+ $permissions['administer clients'] = array(
+ 'name' => 'administer clients',
+ 'roles' => array(
+ '0' => 'administrator',
+ ),
+ );
+
+ // Exported permission: administer hosting
+ $permissions['administer hosting'] = array(
+ 'name' => 'administer hosting',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: administer hosting aliases
+ $permissions['administer hosting aliases'] = array(
+ 'name' => 'administer hosting aliases',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: administer hosting backup queue
+ $permissions['administer hosting backup queue'] = array(
+ 'name' => 'administer hosting backup queue',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: administer hosting features
+ $permissions['administer hosting features'] = array(
+ 'name' => 'administer hosting features',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: administer hosting queues
+ $permissions['administer hosting queues'] = array(
+ 'name' => 'administer hosting queues',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: administer hosting settings
+ $permissions['administer hosting settings'] = array(
+ 'name' => 'administer hosting settings',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: administer hosting site backup manager
+ $permissions['administer hosting site backup manager'] = array(
+ 'name' => 'administer hosting site backup manager',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: administer platforms
+ $permissions['administer platforms'] = array(
+ 'name' => 'administer platforms',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: administer projects
+ $permissions['administer projects'] = array(
+ 'name' => 'administer projects',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: administer servers
+ $permissions['administer servers'] = array(
+ 'name' => 'administer servers',
+ 'roles' => array(
+ '0' => 'administrator',
+ ),
+ );
+
+ // Exported permission: administer sites
+ $permissions['administer sites'] = array(
+ 'name' => 'administer sites',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: administer tasks
+ $permissions['administer tasks'] = array(
+ 'name' => 'administer tasks',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: cancel own tasks
+ $permissions['cancel own tasks'] = array(
+ 'name' => 'cancel own tasks',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: configure devshop pull
+ $permissions['configure devshop pull'] = array(
+ 'name' => 'configure devshop pull',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: create backup schedules
+ $permissions['create backup schedules'] = array(
+ 'name' => 'create backup schedules',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: create backup task
+ $permissions['create backup task'] = array(
+ 'name' => 'create backup task',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: create backup-delete task
+ $permissions['create backup-delete task'] = array(
+ 'name' => 'create backup-delete task',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: create client
+ $permissions['create client'] = array(
+ 'name' => 'create client',
+ 'roles' => array(
+ '0' => 'administrator',
+ ),
+ );
+
+ // Exported permission: create clone task
+ $permissions['create clone task'] = array(
+ 'name' => 'create clone task',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: create delete task
+ $permissions['create delete task'] = array(
+ 'name' => 'create delete task',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: create devshop-delete task
+ $permissions['create devshop-delete task'] = array(
+ 'name' => 'create devshop-delete task',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: create devshop-deploy task
+ $permissions['create devshop-deploy task'] = array(
+ 'name' => 'create devshop-deploy task',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: create disable task
+ $permissions['create disable task'] = array(
+ 'name' => 'create disable task',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: create download task
+ $permissions['create download task'] = array(
+ 'name' => 'create download task',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: create enable task
+ $permissions['create enable task'] = array(
+ 'name' => 'create enable task',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: create flush_cache task
+ $permissions['create flush_cache task'] = array(
+ 'name' => 'create flush_cache task',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: create flush_drush_cache task
+ $permissions['create flush_drush_cache task'] = array(
+ 'name' => 'create flush_drush_cache task',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: create lock task
+ $permissions['create lock task'] = array(
+ 'name' => 'create lock task',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: create login-reset task
+ $permissions['create login-reset task'] = array(
+ 'name' => 'create login-reset task',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: create migrate task
+ $permissions['create migrate task'] = array(
+ 'name' => 'create migrate task',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: create package
+ $permissions['create package'] = array(
+ 'name' => 'create package',
+ 'roles' => array(
+ '0' => 'administrator',
+ ),
+ );
+
+ // Exported permission: create platform
+ $permissions['create platform'] = array(
+ 'name' => 'create platform',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: create project
+ $permissions['create project'] = array(
+ 'name' => 'create project',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: create rebuild_registry task
+ $permissions['create rebuild_registry task'] = array(
+ 'name' => 'create rebuild_registry task',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: create restore task
+ $permissions['create restore task'] = array(
+ 'name' => 'create restore task',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: create run_cron task
+ $permissions['create run_cron task'] = array(
+ 'name' => 'create run_cron task',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: create server
+ $permissions['create server'] = array(
+ 'name' => 'create server',
+ 'roles' => array(
+ '0' => 'administrator',
+ ),
+ );
+
+ // Exported permission: create site
+ $permissions['create site'] = array(
+ 'name' => 'create site',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: create site aliases
+ $permissions['create site aliases'] = array(
+ 'name' => 'create site aliases',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: create sites on locked platforms
+ $permissions['create sites on locked platforms'] = array(
+ 'name' => 'create sites on locked platforms',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: create sync task
+ $permissions['create sync task'] = array(
+ 'name' => 'create sync task',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: create unlock task
+ $permissions['create unlock task'] = array(
+ 'name' => 'create unlock task',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: create update task
+ $permissions['create update task'] = array(
+ 'name' => 'create update task',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: create verify task
+ $permissions['create verify task'] = array(
+ 'name' => 'create verify task',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: delete own client
+ $permissions['delete own client'] = array(
+ 'name' => 'delete own client',
+ 'roles' => array(
+ '0' => 'administrator',
+ ),
+ );
+
+ // Exported permission: delete package
+ $permissions['delete package'] = array(
+ 'name' => 'delete package',
+ 'roles' => array(
+ '0' => 'administrator',
+ ),
+ );
+
+ // Exported permission: delete platform
+ $permissions['delete platform'] = array(
+ 'name' => 'delete platform',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: delete projects
+ $permissions['delete projects'] = array(
+ 'name' => 'delete projects',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: delete server
+ $permissions['delete server'] = array(
+ 'name' => 'delete server',
+ 'roles' => array(
+ '0' => 'administrator',
+ ),
+ );
+
+ // Exported permission: delete site
+ $permissions['delete site'] = array(
+ 'name' => 'delete site',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: edit client uname
+ $permissions['edit client uname'] = array(
+ 'name' => 'edit client uname',
+ 'roles' => array(
+ '0' => 'administrator',
+ ),
+ );
+
+ // Exported permission: edit client users
+ $permissions['edit client users'] = array(
+ 'name' => 'edit client users',
+ 'roles' => array(
+ '0' => 'administrator',
+ ),
+ );
+
+ // Exported permission: edit own client
+ $permissions['edit own client'] = array(
+ 'name' => 'edit own client',
+ 'roles' => array(
+ '0' => 'administrator',
+ ),
+ );
+
+ // Exported permission: edit package
+ $permissions['edit package'] = array(
+ 'name' => 'edit package',
+ 'roles' => array(
+ '0' => 'administrator',
+ ),
+ );
+
+ // Exported permission: edit platform
+ $permissions['edit platform'] = array(
+ 'name' => 'edit platform',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: edit project
+ $permissions['edit project'] = array(
+ 'name' => 'edit project',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: edit server
+ $permissions['edit server'] = array(
+ 'name' => 'edit server',
+ 'roles' => array(
+ '0' => 'administrator',
+ ),
+ );
+
+ // Exported permission: edit site
+ $permissions['edit site'] = array(
+ 'name' => 'edit site',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: manage any SSH public keys
+ $permissions['manage any SSH public keys'] = array(
+ 'name' => 'manage any SSH public keys',
+ 'roles' => array(
+ '0' => 'administrator',
+ ),
+ );
+
+ // Exported permission: manage own SSH public keys
+ $permissions['manage own SSH public keys'] = array(
+ 'name' => 'manage own SSH public keys',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: retry failed tasks
+ $permissions['retry failed tasks'] = array(
+ 'name' => 'retry failed tasks',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: update status of tasks
+ $permissions['update status of tasks'] = array(
+ 'name' => 'update status of tasks',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: view any SSH public keys
+ $permissions['view any SSH public keys'] = array(
+ 'name' => 'view any SSH public keys',
+ 'roles' => array(
+ '0' => 'administrator',
+ ),
+ );
+
+ // Exported permission: view client
+ $permissions['view client'] = array(
+ 'name' => 'view client',
+ 'roles' => array(
+ '0' => 'administrator',
+ ),
+ );
+
+ // Exported permission: view git commit logs
+ $permissions['view git commit logs'] = array(
+ 'name' => 'view git commit logs',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: view locked platforms
+ $permissions['view locked platforms'] = array(
+ 'name' => 'view locked platforms',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: view own SSH public keys
+ $permissions['view own SSH public keys'] = array(
+ 'name' => 'view own SSH public keys',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: view own tasks
+ $permissions['view own tasks'] = array(
+ 'name' => 'view own tasks',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: view package
+ $permissions['view package'] = array(
+ 'name' => 'view package',
+ 'roles' => array(
+ '0' => 'administrator',
+ ),
+ );
+
+ // Exported permission: view platform
+ $permissions['view platform'] = array(
+ 'name' => 'view platform',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: view project
+ $permissions['view project'] = array(
+ 'name' => 'view project',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: view projects
+ $permissions['view projects'] = array(
+ 'name' => 'view projects',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: view server
+ $permissions['view server'] = array(
+ 'name' => 'view server',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: view site
+ $permissions['view site'] = array(
+ 'name' => 'view site',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ // Exported permission: view task
+ $permissions['view task'] = array(
+ 'name' => 'view task',
+ 'roles' => array(
+ '0' => 'administrator',
+ '1' => 'authenticated user',
+ ),
+ );
+
+ return $permissions;
+}
diff --git a/modules/devshop/devshop_permissions/devshop_permissions.info b/modules/devshop/devshop_permissions/devshop_permissions.info
new file mode 100644
index 000000000..bf72bb6ac
--- /dev/null
+++ b/modules/devshop/devshop_permissions/devshop_permissions.info
@@ -0,0 +1,90 @@
+name = "DevShop Permissions"
+description = "Default user permissions for devshop."
+core = 7.x
+package = "Features"
+dependencies[] = "devshop_projects"
+dependencies[] = "features"
+features[user_permission][] = "access content"
+features[user_permission][] = "access disabled sites"
+features[user_permission][] = "access filemanager"
+features[user_permission][] = "access hosting logs"
+features[user_permission][] = "access hosting wizard"
+features[user_permission][] = "access project logs"
+features[user_permission][] = "access task logs"
+features[user_permission][] = "administer SSH public keys"
+features[user_permission][] = "administer clients"
+features[user_permission][] = "administer hosting"
+features[user_permission][] = "administer hosting aliases"
+features[user_permission][] = "administer hosting backup queue"
+features[user_permission][] = "administer hosting features"
+features[user_permission][] = "administer hosting queues"
+features[user_permission][] = "administer hosting settings"
+features[user_permission][] = "administer hosting site backup manager"
+features[user_permission][] = "administer platforms"
+features[user_permission][] = "administer projects"
+features[user_permission][] = "administer servers"
+features[user_permission][] = "administer sites"
+features[user_permission][] = "administer tasks"
+features[user_permission][] = "cancel own tasks"
+features[user_permission][] = "configure devshop pull"
+features[user_permission][] = "create backup schedules"
+features[user_permission][] = "create backup task"
+features[user_permission][] = "create backup-delete task"
+features[user_permission][] = "create client"
+features[user_permission][] = "create clone task"
+features[user_permission][] = "create delete task"
+features[user_permission][] = "create devshop-delete task"
+features[user_permission][] = "create devshop-deploy task"
+features[user_permission][] = "create disable task"
+features[user_permission][] = "create download task"
+features[user_permission][] = "create enable task"
+features[user_permission][] = "create flush_cache task"
+features[user_permission][] = "create flush_drush_cache task"
+features[user_permission][] = "create lock task"
+features[user_permission][] = "create login-reset task"
+features[user_permission][] = "create migrate task"
+features[user_permission][] = "create package"
+features[user_permission][] = "create platform"
+features[user_permission][] = "create project"
+features[user_permission][] = "create rebuild_registry task"
+features[user_permission][] = "create restore task"
+features[user_permission][] = "create run_cron task"
+features[user_permission][] = "create server"
+features[user_permission][] = "create site"
+features[user_permission][] = "create site aliases"
+features[user_permission][] = "create sites on locked platforms"
+features[user_permission][] = "create sync task"
+features[user_permission][] = "create unlock task"
+features[user_permission][] = "create update task"
+features[user_permission][] = "create verify task"
+features[user_permission][] = "delete own client"
+features[user_permission][] = "delete package"
+features[user_permission][] = "delete platform"
+features[user_permission][] = "delete projects"
+features[user_permission][] = "delete server"
+features[user_permission][] = "delete site"
+features[user_permission][] = "edit client uname"
+features[user_permission][] = "edit client users"
+features[user_permission][] = "edit own client"
+features[user_permission][] = "edit package"
+features[user_permission][] = "edit platform"
+features[user_permission][] = "edit project"
+features[user_permission][] = "edit server"
+features[user_permission][] = "edit site"
+features[user_permission][] = "manage any SSH public keys"
+features[user_permission][] = "manage own SSH public keys"
+features[user_permission][] = "retry failed tasks"
+features[user_permission][] = "update status of tasks"
+features[user_permission][] = "view any SSH public keys"
+features[user_permission][] = "view client"
+features[user_permission][] = "view git commit logs"
+features[user_permission][] = "view locked platforms"
+features[user_permission][] = "view own SSH public keys"
+features[user_permission][] = "view own tasks"
+features[user_permission][] = "view package"
+features[user_permission][] = "view platform"
+features[user_permission][] = "view project"
+features[user_permission][] = "view projects"
+features[user_permission][] = "view server"
+features[user_permission][] = "view site"
+features[user_permission][] = "view task"
diff --git a/modules/devshop/devshop_permissions/devshop_permissions.module b/modules/devshop/devshop_permissions/devshop_permissions.module
new file mode 100644
index 000000000..5f0757208
--- /dev/null
+++ b/modules/devshop/devshop_permissions/devshop_permissions.module
@@ -0,0 +1,3 @@
+tagline = t('Hosted by DevShop');
+}
\ No newline at end of file
diff --git a/modules/devshop/devshop_projects/devshop_projects.drush.inc b/modules/devshop/devshop_projects/devshop_projects.drush.inc
new file mode 100644
index 000000000..1bae652d0
--- /dev/null
+++ b/modules/devshop/devshop_projects/devshop_projects.drush.inc
@@ -0,0 +1,587 @@
+ref;
+ if (($ref->type == 'site' || $ref->type == 'platform') && isset($ref->environment)) {
+ $ref->environment->last_task = $task->nid;
+
+ // @TODO: Look for environment_target on clone task, so we can update that environment's last task as well.
+
+ // Prevent "Verify" and "reset password" tasks from cluttering up the environment status indicator.
+ // If last task is a successful verify or reset password, load the task before that as the "last task".
+ if (($task->task_type == 'verify' || $task->task_type == 'login-reset') && $status == HOSTING_TASK_SUCCESS) {
+
+ // If this object is a platform, lookup the latest tasks from the site node.
+ $tasks_list = hosting_get_tasks('rid', $ref->nid);
+
+ // The first item in $tasks should be this task.
+ // If so, use the next task as the "last_task", because verify and reset password tasks are not important.
+ foreach ($tasks_list as &$task_list_item) {
+ // If the first task is this task, we want to skip it.
+ if ($task_list_item->nid == $task->nid && $task_list_item->vid == $task->vid) {
+ continue;
+ }
+ // If the next task is also a login-reset or verify, skip it.
+ if ($task_list_item->task_type == 'verify' || $task_list_item->task_type == 'login-reset') {
+ continue;
+ }
+
+ // Grab the first task that isn't this one or another skippable task
+ $ref->environment->last_task = $task_list_item->nid;
+
+ // Stop the loop through the tasks.
+ break;
+ }
+ }
+
+ devshop_environment_save_last_task($ref->environment);
+ }
+}
+
+/**
+ * Implements drush_HOOK_pre_COMMAND()
+ *
+ * This runs for each task during the command
+ * drush @hostmaster hosting-tasks
+ *
+ * NOTE: This ONLY runs when being called from a hostmaster task.
+ * This hook should ONLY be used to pass options from a hostmaster task form to
+ * the $task object, or if you don't need this functionality from the command
+ * line.
+ */
+function drush_devshop_projects_pre_hosting_task()
+{
+
+ $task =& drush_get_context('HOSTING_TASK');
+
+ // For all tasks
+ if ($task->ref->type == 'site' || $task->ref->type == 'platform') {
+ $drush_aliases = new Provision_Config_ProjectAliases($task->ref->project->name, $task->ref->project);
+ }
+ elseif ($task->ref->type == 'project') {
+ $drush_aliases = new Provision_Config_ProjectAliases(d($task->ref->hosting_name)->name, d($task->ref->hosting_name)->project);
+ }
+
+ // Only write drush aliases if project name was found.
+ if (!empty($drush_aliases->data['name'])) {
+ $drush_aliases->write();
+ drush_log(dt('Drush aliases written for %name project.', [
+ '%name' => $drush_aliases->data['name'],
+ ]), 'devshop_log');
+ }
+
+
+ // Passing options for Download task. This should go in aegir_download.drush.inc but I can't get that hook working right now.
+ if ($task->task_type == 'download') {
+ drush_log('[AEGIR DEVSHOP_PROJECTS] Download package enabled...', 'ok');
+
+ $task->options['packages'] = $task->task_args['packages'];
+ $task->options['commit'] = $task->task_args['update'];
+ $task->options['message'] = $task->task_args['message'];
+ $task->options['update'] = $task->task_args['update'];
+ }
+
+ // Verify Platform
+ // Here is where we hook in and clone the site
+ // @TODO: This can be removed once hosting_git migration is complete.
+// if ($task->ref->type == 'platform' && $task->task_type == 'verify' && !empty($task->ref->project->git_url)) {
+// drush_devshop_platform_verify();
+// }
+
+ // Pull
+ if ($task->ref->type == 'project' && $task->task_type == 'devshop-pull') {
+ $task->args['environments'] = $task->task_args['environments'];
+ $task->options['update'] = $task->task_args['update'];
+ $task->options['revert'] = !empty($task->task_args['revert']);
+ $task->options['cache'] = $task->task_args['cache'];
+ $task->options['force'] = FALSE;
+ }
+
+ // Deploy
+ if ($task->ref->type == 'site' && $task->task_type == 'devshop-deploy') {
+
+ // The git_ref is the only argument
+ $task->args['git_ref'] = $task->task_args['git_ref'];
+
+ // Just load in all of the task_args as options(they come from the task form.)
+ foreach ($task->task_args as $arg => $value) {
+ $task->options[$arg] = $value;
+ }
+ }
+
+ // Run tests
+ if ($task->ref->type == 'site' && $task->task_type == 'test') {
+
+ $task->options['tests-to-run'] = $task->task_args['tests_to_run'];
+ $task->options['test-type'] = $task->task_args['test_type'];
+ }
+
+ // On every task triggered by devshop, set this option.
+ $task->options['is-devshop'] = TRUE;
+}
+
+/**
+ * Implementation of hook_post_hosting_TASK_TYPE_task()
+ * for Verify tasks.
+ *
+ * Provides the "Fork environment" feature: Checks out a new branch and pushes it.
+ */
+function devshop_projects_post_hosting_verify_task($task, $data) {
+ if ($task->ref->type == 'platform') {
+
+ // Fork Environment:
+ // If this is the first verification, and branch needs to be created (for "fork environment")
+ $platform = $task->ref;
+ if (!$platform->verified && !empty($platform->environment->settings->branch_to_fork)) {
+ devshop_process("git checkout -b {$platform->environment->git_ref}", $platform->repo_path, dt('Creating a new branch.'));
+
+ // Push the branch
+ devshop_process("git push -u origin {$platform->environment->git_ref}", $platform->repo_path, dt('Pushing new branch.'));
+ }
+// // Run 'composer install' if configured to do so.
+// if ($platform->environment->settings->deploy['composer'] || $platform->project->settings->deploy['default_hooks']['composer']) {
+//
+// if (file_exists($platform->git['repo_path'] . '/composer.json')) {
+// $cwd = $platform->git['repo_path'];
+// }
+// elseif (file_exists($platform->publish_path . '/composer.json')) {
+// $cwd = $platform->publish_path;
+// }
+// else {
+// // If composer.json was not found in the drupal root or repo root, do not run.
+// drush_log(dt('composer.json not found, even though "composer install" hook is enabled.'), 'warning');
+// return;
+// }
+////
+//// devshop_process("composer install", $cwd, dt('composer.json detected, running composer install in !folder', array(
+//// '!folder' => $cwd,
+//// )));
+//
+// devshop_process("git status", $platform->git['repo_path'], dt('Running git status'));
+// }
+ }
+}
+
+// @TODO: Remove this function for hosting_git migration.
+/**
+ * Pre hosting task hook for "verify platform" task.
+ * - Clones the repository on first run, checks out the selected after that.
+ *
+ * Not a hook! Called from drush_devshop_projects_pre_hosting_task
+ */
+//function drush_devshop_platform_verify() {
+
+// drush_log(dt("[DEVSHOP] Hello. Preparing aegir platform codebase..."));
+//
+ // Verify Platform
+// $task = &drush_get_context('HOSTING_TASK');
+
+// $platform = $task->ref;
+
+ // If for some reason we still do not have an environment... bail.
+// if (empty($platform->environment)) {
+// return drush_set_error('DEVSHOP_ERROR', dt('Unable to determine environment. Please contact your administrator.'));
+// }
+
+// $root = $platform->publish_path;
+// $git_ref = $platform->environment->git_ref;
+//
+// $git_remote = $platform->project->git_url;
+// $drupal_path = $platform->project->drupal_path;
+//
+// Remove drupal_path to clone.
+// if ($drupal_path) {
+// $root = str_replace($drupal_path, '', $root);
+// }
+
+ // Check if the platform code exists. If it doesn't, clone it.
+// if (!is_dir($root)) {
+//
+// // Clone the repo.
+// devshop_drush_process("git clone --recursive $git_remote $root", NULL, dt('Cloning git repository'));
+
+ // Skip errors about submodule
+
+
+ // If there is "site_to_clone", checkout that site's git ref.
+ // This is done for Pull Request Environments. When a PR comes in, we want
+ // to clone the site to an identical platform first. This will always succeed.
+ // Then, we trigger a deployment to the new branch.
+ //
+ // This ensures that initial pull request environments run the same "deploy"
+ // hooks. It means we don't have to hack into the "clone" task to run deploy
+ // hooks anymore.
+// if ($platform->environment->settings->site_to_clone) {
+// $site = node_load($platform->environment->settings->site_to_clone);
+// $git_ref = $site->environment->git_ref;
+// }
+
+ // If there is a git ref, check it out.
+ // We don't do this on git clone, because it could be a tag.
+ // @TODO: Won't the platform ALWAYS have a git_branch?
+// if ($git_ref) {
+// devshop_drush_process("git checkout $git_ref", $root, dt('Checking out specified version'));
+// }
+// }
+//
+// // If this is the first verification, and branch needs to be created (for "fork environment")
+// if (!$platform->verified && !empty($platform->environment->settings->branch_to_fork)) {
+// devshop_drush_process("git checkout -b {$platform->environment->git_ref}", $root, dt('Creating a new branch.'));
+//
+// // Push the branch
+// devshop_drush_process("git push -u origin {$platform->environment->git_ref}", $root, dt('Pushing new branch.'));
+// }
+//
+// // Detect a bad path_to_drupal
+// if (!file_exists($platform->environment->root . '/index.php')) {
+// return drush_set_error('DEVSHOP_ERROR', dt($platform->environment->root . '/index.php file not found. Check "Path to Drupal" in Project Settings.'));
+// }
+//
+// // When verifying the platform for the first time, run 'composer install' if configured to do so.
+// if ($platform->verified && $platform->environment->settings->deploy['composer'] || $platform->project->settings->deploy['default_hooks']['composer']) {
+//
+// if (file_exists($platform->environment->repo_root . '/composer.json')) {
+// $cwd = $platform->environment->repo_root;
+// }
+// elseif (file_exists($root . '/composer.json')) {
+// $cwd = $root;
+// }
+// else {
+// // If composer.json was not found in the drupal root or repo root, do not run.
+// drush_log(dt('composer.json not found, even though "composer install" hook is enabled.'), 'warning');
+// return;
+// }
+//
+// devshop_drush_process("composer install", $cwd, dt('composer.json detected, running composer install in !folder', array(
+// '!folder' => $cwd,
+// )));
+//
+// devshop_drush_process("git status", $platform->environment->repo_root, dt('Running git status'));
+// }
+//}
+
+/**
+ * Implements hook_post_hosting_TASK_TYPE_task() for delete tasks().
+ */
+function devshop_projects_post_hosting_delete_task($task, $data) {
+
+ // When a project is deleted...
+ if ($task->ref->type == 'project') {
+ // Queue site deletion for each environment.
+ $project = $task->ref->project;
+ foreach ($project->environments as $environment) {
+ // If site exists, trigger deletion.
+ if ($environment->site) {
+ hosting_add_task($environment->site, 'delete');
+ }
+ // If platform exists, trigger deletion.
+ elseif ($environment->platform) {
+ hosting_add_task($environment->platform, 'delete', array('force' => 1));
+ }
+ }
+
+ // @TODO: Should probably add our own status column
+ // Unpublish the project node.
+ $task->ref->status = 0;
+ $task->ref->no_verify = TRUE;
+ node_save($task->ref);
+ }
+
+ // When a site is deleted (if it is in a project, delete the platform it is on.
+ // @TODO: Check for another site on this platform?
+ if ($task->ref->type == 'site' && !empty($task->ref->project)) {
+ // We trigger platform deletion here.
+ hosting_add_task($task->ref->platform, 'delete');
+ }
+
+ // When a platform is deleted (if it is in a project), delete the environment record.
+ if ($task->ref->type == 'platform' && !empty($task->ref->project)) {
+ db_delete('hosting_devshop_project_environment')
+ ->condition('project_nid', $task->ref->project->nid)
+ ->condition('platform', $task->ref->platform)
+ ->execute();
+
+ // If drupal root is not repo root, delete folder at code_path.
+ $repo_path = str_replace($task->ref->project->drupal_path, '', $task->ref->publish_path);
+ if (file_exists($repo_path) ) {
+ _provision_recursive_delete($repo_path);
+ }
+ }
+
+ // When a platform is deleted, if it is the last in the project,
+ // and the project has been unpublished, delete the directory.
+ if ($task->ref->type == 'platform' && !empty($task->ref->project)) {
+ if ($task->ref->project->status == 0 && count($task->ref->project->environments) == 1) {
+ // Delete the project folder.
+ exec("rm -rf {$project->code_path}");
+ }
+ }
+}
+
+/**
+ * Implements hook_post_hosting_TASK_TYPE_task() for install.
+ */
+function devshop_projects_post_hosting_install_task($task, $data) {
+
+ // Queue up a verification of the site.
+ hosting_add_task($task->ref->nid, 'verify');
+
+}
+
+ /**
+ * Implements hook_post_hosting_TASK_TYPE_task() for devshop deploy tasks().
+ */
+function devshop_projects_post_hosting_devshop_deploy_task($task, $data) {
+
+ // Save the deployed git ref to the environment record.
+ // Doing this post deploy ensures it was actually checked out.
+ drush_log('[DEVSHOP] Environment Deployed. Saving new git ref to project settings.');
+
+ $git_ref = trim(str_replace('refs/heads/', '', shell_exec("cd {$task->ref->environment->repo_path}; git describe --tags --exact-match || git symbolic-ref -q HEAD")));;
+
+ $args = array(
+ ':git' => $git_ref,
+ ':site' => $task->ref->environment->site,
+ );
+
+ if (db_query('UPDATE {hosting_git} SET git_ref = :git WHERE nid = :site', $args)) {
+ drush_log("[DEVSHOP] Git reference {$git_ref} saved to {$task->ref->environment->name} environment record.");
+ }
+ else {
+ return drush_set_error('[DEVSHOP] Environment update failed! Unable to save git ref to hosting_devshop_project_environment table.');
+ }
+
+ // Queue up a verification of the platform.
+ hosting_add_task($task->ref->environment->platform, 'verify');
+
+ // Queue up a verification of the site.
+ hosting_add_task($task->ref->nid, 'verify');
+}
+
+// @TODO: Remove this function. This is not needed now that we create site nodes at environment create time.
+///**
+// * Implements hook_post_hosting_TASK_TYPE_task().
+// *
+// * Acts after the successful verification of platforms.
+// * This is where the sites get automatically created.
+// */
+//function devshop_projects_post_hosting_verify_task($task, $data) {
+//
+ // If this is a new platform created by a project, we will create a site here.
+// if ($task->ref->type == 'platform') {
+// drush_log('[DevShop] Codebase Clone Complete... Preparing next steps.', 'devshop_log');
+
+// // If this platform isn't in a project, bail.
+// $platform = $task->ref;
+// if (!isset($platform->project)) {
+// $project = $task->ref->project;
+//
+// // @TODO: On first verify, "environment" can be missing. Not sure why, yet.
+// if (empty($platform->environment) && isset($project->environments[$task->project->environment])) {
+// $platform->environment = $project->environments[$task->project->environment];
+// }
+//
+// drush_log('[DEVSHOP] No project found for this platform.', 'notice');
+// return;
+// }
+//
+// // If the project doesn't have an install profile chosen yet, bail.
+// if (empty($platform->project->install_profile)) {
+// drush_log('[DEVSHOP] No install profile found for this project. Not doing anything.', 'ok');
+// return;
+// }
+//
+// // If the platform has a site already, trigger verification, then bail.
+// if (!empty($platform->environment->site)){
+// drush_log('[DEVSHOP] Platform already has a site.', 'notice');
+// // hosting_add_task($platform->environment->site, 'verify');
+// return;
+// }
+// else {
+//
+// // Check for site to clone
+// if (!empty($platform->environment->settings->site_to_clone)) {
+// $site = node_load($platform->environment->settings->site_to_clone);
+// $args['target_platform'] = $platform->nid;
+//
+// // Ensure new URI doesn't contain underscores or other invalid characters.
+// $args['new_uri'] = str_replace('_', '-', $platform->environment->name) . '.' . $platform->project->base_url;
+//
+// // Use the default database server for the project.
+// $args['new_db_server'] = $site->db_server;
+// $args['environment_target'] = $platform->environment->name;
+// drush_log('[DEVSHOP] Cloning environment: ' . $platform->environment->name);
+//
+// // Queue the Cloning of the site.
+// // @see devshop_projects_post_hosting_import_task().
+//
+// // If, for some reason, we are still missing names, abort.
+// if (empty($site->environment->name) || empty($platform->environment->name)) {
+// return drush_set_error('DEVSHOP_ERROR', 'Source or target environment not found. Contact your systems administrator.');
+// }
+//
+// $task = hosting_add_task($platform->environment->settings->site_to_clone, 'clone', $args);
+//
+// $link = l(t('View Logs'), "node/{$task->nid}");
+// drush_log("[DevShop] Environment '{$site->environment->name}' is scheduled to be cloned to '{$platform->environment->name}'. $link", 'devshop_log');
+// }
+// else {
+//
+// // Determine what DB server to use
+// if (isset($platform->environment->settings->db_server)) {
+// $db_server = $platform->environment->settings->db_server;
+// }
+// else {
+// $db_server = $platform->project->settings->default_environment['db_server'];
+// }
+//
+// if (empty($platform->environment->name)) {
+// return drush_set_error('DEVSHOP_ERROR', 'Missing environment name. Unable to create new site.');
+// }
+// else {
+// $new_site = devshop_projects_create_site($platform->project, $platform, $platform->environment->name, $db_server);
+// drush_log("[DevShop] Environment '{$platform->environment->name}' is scheduled to be installed. Please wait...", 'devshop_log');
+// }
+// }
+// }
+// }
+//}
+
+///**
+// * Implements hook_post_hosting_TASK_TYPE_task().
+// */
+//function devshop_projects_post_hosting_install_task($task, $data) {
+//
+// // Now that environment data is stored in project data we need to verify projects a lot.
+// if ($task->ref->type == 'site' && isset($task->ref->project->nid)) {
+// hosting_add_task($task->ref->project->nid, 'verify');
+// }
+//}
+
+// @TODO: Remove this. We no longer use Clone task.
+///**
+// * Implements hook_post_hosting_TASK_TYPE_task().
+// */
+//function devshop_projects_post_hosting_clone_task($task, $data) {
+//
+// // Now that environment data is stored in project data we need to verify projects a lot.
+// if ($task->ref->type == 'site' && isset($task->ref->project->nid)) {
+// hosting_add_task($task->ref->project->nid, 'verify');
+// }
+//}
+
+///**
+// * Implements hook_post_hosting_TASK_TYPE_task().
+// *
+// * Ensures that cloned sites that are on project platforms inherit the project.
+// */
+//function devshop_projects_post_hosting_import_task($task, $data) {
+//
+// // Now that environment data is stored in project data we need to verify projects a lot.
+// if ($task->ref->type == 'site' && isset($task->ref->project->nid)) {
+// hosting_add_task($task->ref->project->nid, 'verify');
+// }
+//
+// // If this is a new platform created by a project, we will create a site here.
+// if ($task->ref->type == 'site') {
+// $site = $task->ref;
+// $platform = node_load($site->platform);
+//
+// if (isset($platform->environment) && !empty($platform->environment->name)) {
+//
+// // Save environment record.
+// drush_log('[DevShop Environment] Saving environment data...', 'devshop_command');
+// db_update('hosting_devshop_project_environment')
+// ->fields(array(
+// 'site' => $site->nid,
+// ))
+// ->condition('project_nid', $platform->project->nid)
+// ->condition('name', $platform->environment->name)
+// ->execute();
+// drush_log("Saved {$platform->environment->name} in project {$platform->project->name}.", 'devshop_ok');
+//
+// // Save aliases if needed.
+// if ($platform->project->settings->live['environment_aliases'] && !empty($platform->project->settings->live['live_domain'])) {
+// $domain = $platform->environment->name . '.' . $platform->project->settings->live['live_domain'];
+//
+// $site->aliases[] = $domain;
+// drush_log('[DevShop Environment] Saving environment data', 'devshop_command');
+// $site->no_verify = TRUE;
+// node_save($site);
+//
+// }
+//
+// // If we have been cloned from another environment, run a deploy task.
+// if (!empty($platform->environment->settings->clone_source)) {
+//
+// // Trigger a verify of the site, so the alias has project information.
+// hosting_add_task($site->nid, 'verify');
+//
+// // Trigger a deploy using the project's default hooks.
+// $args = $platform->project->settings->deploy['default_hooks'];
+// $args['git_ref'] = $platform->environment->git_ref;
+//
+// // Handle a Pull Request. If there is one, we want to deploy to the PR branch.
+// if (!empty($platform->environment->github_pull_request->pull_request_object->head->ref)) {
+// $args['git_ref'] = $platform->environment->github_pull_request->pull_request_object->head->ref;
+// }
+//
+// hosting_add_task($site->nid, 'devshop-deploy', $args);
+//
+// } else {
+// // Trigger a verify of the site, so the alias has project information.
+// hosting_add_task($site->nid, 'verify');
+// }
+// }
+// }
+//}
+//
+///**
+// * Utility for execute git commands.
+// */
+//function _devshop_projects_git_execute($commands, $root = '') {
+// // Execute
+// $return = '';
+// if (is_string($commands)) {
+// $commands = array($commands);
+// }
+// foreach ($commands as $command) {
+// if (!empty($command)) {
+// drush_log("[DEVSHOP] Running Command in $root: $command");
+//
+// // @TODO: Create a d()->server->shell_cd_and_exec() function
+// // server->shell_exec() uses escapeshellcmd(), so we cannot cd_and_exec!
+// // So instead, this is the code from d()->server->shell_exec
+// // d()->server->shell_exec($cmd);
+// if (provision_is_local_host(d()->server->remote_host)) {
+//
+// // Run in $root if it exists.
+// if (file_exists($root)) {
+// drush_shell_cd_and_exec($root, escapeshellcmd($command));
+// }
+// else {
+// drush_shell_exec(escapeshellcmd($command));
+// }
+// }
+// else {
+// drush_shell_cd_and_exec($root, 'ssh ' . drush_get_option('ssh-options', '-o PasswordAuthentication=no') . ' %s %s', $this->script_user . '@' . d()->server->remote_host, escapeshellcmd($command));
+// }
+//
+// $output = drush_shell_exec_output();
+// drush_log('Shell Output: ' . implode("\n", $output), 'ok');
+// $return .= implode("\n", $output);
+// }
+// }
+// return $return;
+//}
diff --git a/modules/devshop/devshop_projects/devshop_projects.info b/modules/devshop/devshop_projects/devshop_projects.info
new file mode 100644
index 000000000..38f30d85a
--- /dev/null
+++ b/modules/devshop/devshop_projects/devshop_projects.info
@@ -0,0 +1,29 @@
+name = DevShop Projects
+description = A DevShop module to sites/platforms together into projects.
+core = 7.x
+package = DevShop
+
+files[] = inc/create/create.inc
+files[] = inc/create/step-1.inc
+files[] = inc/create/step-2.inc
+files[] = inc/create/step-3.inc
+files[] = inc/create/step-4.inc
+files[] = inc/admin.inc
+files[] = inc/create-wizard.inc
+files[] = inc/forms.inc
+files[] = inc/logs.inc
+files[] = inc/nodes.inc
+files[] = inc/queue.inc
+files[] = inc/tasks-ajax.inc
+files[] = inc/ui.inc
+files[] = tasks/commit.inc
+files[] = tasks/create.inc
+files[] = tasks/deploy.inc
+files[] = tasks/pull.inc
+files[] = tasks/tasks.inc
+dependencies[] = hosting
+dependencies[] = hosting_alias
+dependencies[] = hosting_package
+dependencies[] = hosting_git
+dependencies[] = hosting_git_pull
+dependencies[] = ctools
diff --git a/modules/devshop/devshop_projects/devshop_projects.install b/modules/devshop/devshop_projects/devshop_projects.install
new file mode 100644
index 000000000..896c59ae0
--- /dev/null
+++ b/modules/devshop/devshop_projects/devshop_projects.install
@@ -0,0 +1,478 @@
+ 7300,
+ );
+ return $dependencies;
+}
+
+/**
+ * Implements hook_install()
+ */
+function devshop_projects_install() {
+
+ // Push devshop_projects's system weight to 1.
+ db_update('system')
+ ->fields(array(
+ 'weight' => 1
+ ))
+ ->condition('name', 'devshop_projects')
+ ->execute();
+}
+
+/**
+ * Implements hook_schema().
+ */
+function devshop_projects_schema() {
+ $schema['hosting_devshop_project'] = array(
+ 'fields' => array(
+ 'nid' => array(
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'description' => 'Project/Node ID.',
+ ),
+ 'git_url' => array(
+ 'type' => 'text',
+ 'size' => 'big',
+ 'not null' => FALSE,
+ ),
+ 'code_path' => array(
+ 'type' => 'text',
+ 'size' => 'big',
+ 'not null' => FALSE,
+ ),
+ 'drupal_path' => array(
+ 'type' => 'text',
+ 'size' => 'big',
+ 'not null' => FALSE,
+ ),
+ 'base_url' => array(
+ 'type' => 'text',
+ 'size' => 'big',
+ 'not null' => FALSE,
+ ),
+ 'install_profile' => array(
+ 'type' => 'text',
+ 'size' => 'big',
+ 'not null' => FALSE,
+ ),
+ 'settings' => array(
+ 'type' => 'text',
+ 'not null' => FALSE,
+ 'size' => 'big',
+ 'description' => 'A serialized array of settings for this project.',
+ ),
+ ),
+ 'primary key' => array('nid'),
+ );
+
+ // Table for tracking environments
+ $schema['hosting_devshop_project_environment'] = array(
+ 'fields' => array(
+ 'project_nid' => array(
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'description' => "The project's Node ID.",
+ ),
+ 'name' => array(
+ 'type' => 'varchar',
+ 'not null' => TRUE,
+ 'length' => 64,
+ 'default' => '',
+ 'description' => 'Environment name.',
+ ),
+ 'site' => array(
+ 'type' => 'int',
+ 'not null' => FALSE,
+ 'default' => 0,
+ 'unsigned' => TRUE,
+ 'description' => 'The node ID of the site for this environment.',
+ ),
+ 'platform' => array(
+ 'type' => 'int',
+ 'not null' => FALSE,
+ 'default' => 0,
+ 'unsigned' => TRUE,
+ 'description' => 'The node ID of the platform for this environment.',
+ ),
+ 'settings' => array(
+ 'type' => 'text',
+ 'not null' => FALSE,
+ 'size' => 'big',
+ 'description' => 'A serialized array of settings for this environment.',
+ ),
+ 'last_task' => array(
+ 'type' => 'text',
+ 'not null' => FALSE,
+ 'size' => 'big',
+ 'description' => 'The NID of the last task that should be displayed on an environment.',
+ ),
+ ),
+ 'indexes' => array(
+ 'project_environment' => array('project_nid', 'name'),
+ ),
+ );
+ return $schema;
+}
+
+/**
+ * Add the "data" column to projects.
+ */
+function devshop_projects_update_1() {
+ $ret = array();
+ $ret[] = update_sql("ALTER TABLE {hosting_devshop_project} " .
+ "ADD COLUMN data longtext NOT NULL default ''");
+ return $ret;
+}
+
+/*
+ * Update 2: Delete rows in the hosting_devshop_project_objects table
+ * that the node pointed to by object nid no longer exists.
+ */
+function devshop_projects_update_2() {
+ $ret = array();
+ $query = db_query("SELECT object_nid " .
+ "FROM {hosting_devshop_project_object}");
+
+ while($proj = db_fetch_object($query)) {
+ $count = db_result(db_query("SELECT COUNT(*) FROM {node} " .
+ "WHERE nid = %d", $proj->object_nid));
+ if ($count != 1) {
+ $ret[] = update_sql('DELETE FROM {hosting_devshop_project_object} ' .
+ 'WHERE object_nid = %d', $proj->object_nid);
+ }
+ }
+
+ return $ret;
+}
+
+/**
+ * Adds git_branch column to hosting_devshop_project_objects table.
+ */
+function devshop_projects_update_3() {
+ $ret = array();
+ db_add_field($ret, 'hosting_devshop_project_object', 'git_branch', array('type' => 'varchar', 'length' => 16, 'not null' => FALSE));
+ return $ret;
+}
+
+/**
+ * Changes env_type to environment in {hosting_devshop_project_object}.
+ */
+function devshop_projects_update_4() {
+ $ret = array();
+ $schema = devshop_projects_schema();
+ $spec = $schema['hosting_devshop_project_object']['fields']['environment'];
+ db_change_field($ret, 'hosting_devshop_project_object', 'env_type', 'environment', $spec);
+ return $ret;
+}
+
+/**
+ * Makes "git_branch" field larger.
+ */
+function devshop_projects_update_5() {
+ $ret = array();
+ $ret[] = update_sql("ALTER TABLE {hosting_devshop_project_object} CHANGE git_branch git_branch VARCHAR(128) NULL");
+ return $ret;
+}
+
+/**
+ * Adds drupal_path column to hosting_devshop_project table.
+ */
+function devshop_projects_update_6() {
+ $ret = array();
+ db_add_field($ret, 'hosting_devshop_project', 'drupal_path', array('type' => 'text', 'size' => 'big', 'not null' => FALSE));
+ return $ret;
+}
+
+/**
+ * Adds drupal_path column to hosting_devshop_project table.
+ */
+function devshop_projects_update_7() {
+ $ret = array();
+ db_add_field($ret, 'hosting_devshop_project_object', 'drupal_path', array('type' => 'text', 'size' => 'big', 'not null' => FALSE));
+ return $ret;
+}
+
+/**
+ * Makes "environment" field larger.
+ */
+function devshop_projects_update_8() {
+ $ret = array();
+ $ret[] = update_sql("ALTER TABLE {hosting_devshop_project_object} CHANGE environment environment VARCHAR(64) NOT NULL");
+
+ return $ret;
+}
+
+/**
+ * Add clone_nid column to hosting_devshop_project_object.
+ */
+function devshop_projects_update_9() {
+ $ret = array();
+ $ret[] = db_add_field($ret, 'hosting_devshop_project_object', 'clone_nid', array('type' => 'int', 'not null' => FALSE, 'default' => 0, 'unsigned' => TRUE));
+
+ return $ret;
+}
+
+/**
+ * 6.x-1.9-beta4 -^
+ *
+ * 6.x-1.9-rc1 -v (FUTURE RELEASE)
+ */
+
+/**
+ * Remove live_domain from hosting_devshop_project schema.
+ */
+function devshop_projects_update_6000() {
+ $ret = array();
+ if (db_column_exists('hosting_devshop_project', 'live_domain')){
+ $ret[] = db_drop_field($ret, 'hosting_devshop_project', 'live_domain');
+ }
+ return $ret;
+}
+
+/**
+ * Create new hosting_devshop_environment table.
+ */
+function devshop_projects_update_6001() {
+ // Create only the new table
+ // Clone of drupal_install_schema('devshop_projects');
+ $module = 'devshop_projects';
+ $name = 'hosting_devshop_project_environment';
+
+ $schema = drupal_get_schema_unprocessed($module);
+ _drupal_initialize_schema($module, $schema);
+
+ $ret = array();
+ db_create_table($ret, $name, $schema['hosting_devshop_project_environment']);
+ return $ret;
+}
+
+/**
+ * Enable hosting_alias module to allow custom domains on sites.
+ */
+function devshop_projects_update_6002() {
+ module_enable(array('hosting_alias'));
+ return array();
+}
+
+/**
+ * Remove legacy `hosting_devshop_project_object` table.
+ */
+function devshop_projects_update_6003(){
+ $ret = array();
+ db_drop_table($ret, 'hosting_devshop_project_object');
+ return $ret;
+}
+
+/**
+ * Change "data" column to "settings" column.
+ */
+function devshop_projects_update_6004(){
+ $ret = array();
+ db_change_column($ret, 'hosting_devshop_project', 'data', 'settings', 'text', array(
+ 'not null' => FALSE,
+ 'size' => 'big',
+ 'description' => 'A serialized array of settings for this project.',
+ ));
+ return $ret;
+}
+
+/**
+ * Turn on the new Boots theme.
+ * Uncomment once the theme is ready.
+function devshop_projects_update_600X() {
+ variable_set('theme_default','boots');
+ return array();
+}
+ */
+
+/**
+ * Add "last_task" column for environments.
+ */
+function devshop_projects_update_6008(){
+ $schema = devshop_projects_schema();
+ $ret = array();
+ $ret[] = db_add_field(
+ $ret,
+ 'hosting_devshop_project_environment',
+ 'last_task', $schema['hosting_devshop_project_environment']['fields']['last_task']
+ );
+ return $ret;
+}
+
+/**
+ * Update all environments with their last_task();
+ */
+function devshop_projects_update_6009 () {
+
+ // Lookup all environments
+ $query = db_query('SELECT * FROM {hosting_devshop_project_environment}');
+ while ($result = db_fetch_object($query)) {
+ $site = node_load($result->site);
+ $result = db_fetch_object(db_query("SELECT t.nid, t.vid FROM hosting_task t INNER JOIN node n ON n.vid = t.vid WHERE rid = %d GROUP BY t.nid ORDER BY vid DESC", $site->nid));
+
+ $task = node_load($result->nid);
+ $site->environment->last_task = array(
+ 'status' => $task->task_status,
+ 'type' => $task->task_type,
+ 'nid' => $task->nid,
+ );
+ devshop_environment_save_last_task($site->environment);
+ }
+}
+
+
+/**
+ * Reset all environment's "last task" to the NID.
+ */
+function devshop_projects_update_6010 () {
+
+ // Lookup all environments
+ $query = db_query('SELECT * FROM {hosting_devshop_project_environment}');
+ while ($result = db_fetch_object($query)) {
+ $site = node_load($result->site);
+ $result = db_fetch_object(db_query("SELECT t.nid, t.vid FROM hosting_task t INNER JOIN node n ON n.vid = t.vid WHERE rid = %d GROUP BY t.nid ORDER BY vid DESC", $site->nid));
+ $site->environment->last_task = $result->nid;
+ devshop_environment_save_last_task($site->environment);
+ }
+}
+
+/**
+ * Reset all environment's "last task" to the NID, again.
+ */
+function devshop_projects_update_6011 () {
+ devshop_reset_last_tasks();
+}
+
+/**
+ * Remove the menu form from the project node form.
+ */
+function devshop_projects_update_7000 () {
+ variable_set('menu_options_project', array());
+}
+
+/**
+ * Remove the menu form from the site node form.
+ */
+function devshop_projects_update_7001 () {
+ variable_set('menu_options_site', array());
+}
+
+// @TODO: This breaks upgrades. Figure out why and in include it in an upcoming release.
+///**
+// * Enable devshop_testing.
+// */
+//function devshop_projects_update_7002 () {
+// module_enable(array(
+// 'devshop_testing',
+// ));
+//}
+
+/**
+ * Fully resetting project and site node menu settings.
+ */
+function devshop_projects_update_7003 () {
+ variable_set('menu_options_site', '');
+ variable_set('menu_options_project', '');
+}
+
+/**
+ * Enable Hosting Git and Hosting Git Checkout.
+ */
+function devshop_projects_update_7004 () {
+ module_enable(array(
+ 'hosting_git',
+ 'hosting_git_pull',
+ ));
+}
+
+/**
+ * Save git_ref data to hosting_git table and drop the devshop environment's git_ref field.
+ */
+function devshop_projects_update_7005 () {
+ $environments = db_query('SELECT * FROM {hosting_devshop_project_environment} e LEFT JOIN {hosting_devshop_project} p ON p.nid = e.project_nid');
+ foreach ($environments as $environment) {
+ if ($environment->platform) {
+ db_merge('hosting_git')
+ ->key(array('nid' => $environment->platform))
+ ->fields(array(
+ 'repo_url' => $environment->git_url,
+ 'git_ref' => $environment->git_ref,
+ ))
+ ->execute();
+ drupal_set_message(t("Updated Platform $environment->platform with $environment->git_url and $environment->git_ref"));
+ }
+ else {
+ drupal_set_message(t("No Platform found for environment $environment->name for site $environment->site."), 'warning');
+ }
+ }
+
+ if (db_drop_field('hosting_devshop_project_environment', 'git_ref')) {
+ drupal_set_message(t("Git ref information has been migrated and the field has been removed."));
+ }
+ else {
+ drupal_set_message(t("Unable to remove git_ref field from hosting_devshop_project_environment table."), 'error');
+ }
+}
+
+/**
+ * Reduce the character limit for bootstrap to turn form element descriptions into tooltips.
+ */
+function devshop_projects_update_7007 () {
+ // Get the settings
+ $settings = variable_get('theme_boots_settings', array());
+
+ // Set the variable
+ $settings['bootstrap_forms_smart_descriptions_limit'] = 40;
+
+ // Save our settings
+ variable_set('theme_boots_settings', $settings);
+}
+
+/**
+ * Set weight of devshop_projects module to 1 so it loads alters after hosting_git.
+ */
+function devshop_projects_update_7008 () {
+
+ // Push devshop_projects's system weight to 1.
+ db_update('system')
+ ->fields(array(
+ 'weight' => 1
+ ))
+ ->condition('name', 'devshop_projects')
+ ->execute();
+
+}
+
+/**
+ * Update hosting contexts for project node to ensure they all begin with "project_".
+ */
+function devshop_projects_update_7009 () {
+ $query = "UPDATE hosting_context c JOIN node n ON n.nid=c.nid SET c.name = CONCAT('project_', name) WHERE n.type='project' AND c.name NOT LIKE 'project_'";
+ if (!db_query($query)) {
+ drupal_set_message(t('Something went wrong when running a query to update hosting_context table to ensure all projects are prefixed with "project_":') . $query);
+ }
+}
\ No newline at end of file
diff --git a/modules/devshop/devshop_projects/devshop_projects.module b/modules/devshop/devshop_projects/devshop_projects.module
new file mode 100644
index 000000000..ac9ab1d3c
--- /dev/null
+++ b/modules/devshop/devshop_projects/devshop_projects.module
@@ -0,0 +1,903 @@
+step)) {
+
+ // Make sure we don't show this message twice.
+ $message = t('You have an unfinished project. You should !link.', array(
+ '!link' => l(t('finish creating your project'), 'projects/add'),
+ ));
+
+ $show_message = TRUE;
+ $messages = drupal_get_messages('status');
+
+ if (isset($messages['status'])) {
+ foreach ($messages['status'] as $m) {
+ if (strpos($m, $message) !== FALSE) {
+ $show_message = FALSE;
+ }
+ else {
+ drupal_set_message($m);
+ }
+ }
+ }
+
+ if ($show_message) {
+ drupal_set_message($message);
+ }
+ }
+ }
+
+ // If on the projects page, or a project or site node...
+ if (arg(0) == 'node' && is_numeric(arg(1))) {
+ $node = node_load(arg(1));
+ if ($node->type == 'project' || $node->type == 'site' || $node->type == 'task') {
+ drupal_add_js(drupal_get_path('module', 'devshop_projects') . '/inc/task-ajax.js');
+
+ if ($node->type == 'project') {
+ drupal_add_js(array('devshopProject' => arg(1)), 'setting');
+ }
+ if ($node->type == 'task') {
+ drupal_add_js(array('devshopTask' => arg(1)), 'setting');
+ }
+ }
+ }
+ // If on the projects list page...
+ else {
+ drupal_add_js(drupal_get_path('module', 'devshop_projects') . '/inc/task-ajax.js');
+ }
+}
+
+/**
+ * Implements hook_permission().
+ *
+ * Since we have a node type, "create project content permission is
+ * automatically added by Drupal
+ */
+function devshop_projects_permission() {
+ return array(
+ 'view projects' => array(
+ 'title' => t('view projects'),
+ 'description' => t('Access the projects page.'),
+ ),
+ 'create devshop-delete task' => array(
+ 'title' => t('create devshop-delete task'),
+ 'description' => t('Create "devshop-delete" task.'),
+ ),
+ 'create devshop-deploy task' => array(
+ 'title' => t('create devshop-deploy task'),
+ 'description' => t('Create "devshop-deploy" task.'),
+ ),
+ 'create project' => array(
+ 'title' => t('create project'),
+ 'description' => t('Create new DevShop projects.'),
+ ),
+ 'view project' => array(
+ 'title' => t('view project'),
+ 'description' => t('Access DevShop project dashboards.'),
+ ),
+ 'edit project' => array(
+ 'title' => t('edit project'),
+ 'description' => t('Edit DevShop projects.'),
+ ),
+ 'delete projects' => array(
+ 'title' => t('delete projects'),
+ 'description' => t('Delete DevShop projects.'),
+ ),
+ 'administer projects' => array(
+ 'title' => t('administer projects'),
+ 'description' => t('Administer DevShop projects.'),
+ ),
+ 'change site domain name' => array(
+ 'title' => t("Change a site's primary domain name."),
+ 'description' => t('Alter the main domain name the site responds to.'),
+ ),
+ );
+}
+
+/**
+ * Implements hook_devshop_environment_menu().
+ *
+ * Defines the list of tasks that appear under the gear icon.
+ */
+function devshop_environment_menu_first_items($environment, $tasks) {
+ $site = null;
+ $platform = null;
+ $items = array();
+ $aegir_items = array();
+
+ global $user;
+
+ // Show for every site, even disabled ones.
+ if ($environment->site) {
+ $site = node_load($environment->site);
+ if (node_access('update', $site)) {
+ $items[] = l(' '.t('Environment Settings'),
+ "node/{$environment->site}/edit",
+ array(
+ 'html' => TRUE,
+ ));
+ $items[] = '';
+
+ }
+
+ if (user_access('create git-tag task') && $environment->platform) {
+ $items[] = l(' '.t('Create a tag'),
+ "hosting_confirm/{$environment->platform}/platform_git-tag",
+ array(
+ 'html' => TRUE,
+ ));
+ $items[] = '';
+ }
+
+ if (user_access('change site domain names') && user_access('create migrate task') ) {
+ $href = "hosting_confirm/{$environment->site}/site_migrate";
+ $items[] = l(' '.t('Change Domain Name'),
+ $href, array(
+ 'query' => array(
+ 'rename' => 1,
+ 'token' => drupal_get_token($user->uid),
+ ),
+ 'html' => TRUE,
+ ));
+ }
+ if (node_access('view', $site)) {
+ $aegir_items[] = l(' '.t('Aegir Site'),
+ "node/{$environment->site}",
+ array(
+ 'html' => TRUE,
+ ));
+ }
+ }
+
+ // If platform exists
+ if ($environment->platform) {
+ $platform = node_load($environment->platform);
+ if (node_access('view', $platform)) {
+ $aegir_items[] = l(' '.t('Aegir Platform'),
+ "node/{$environment->platform}",
+ array(
+ 'query' => array(
+ 'rename' => 1,
+ 'token' => drupal_get_token($user->uid),
+ ),
+ 'html' => TRUE,
+ ));
+ }
+ }
+
+ // Load test task, if it exists
+ if ($tasks['test']) {
+ $href = "hosting_confirm/{$environment->site}/site_test";
+ $url = url($href, array(
+ 'query' => array(
+ 'token' => drupal_get_token($user->uid),
+ )
+ ));
+ $title = $tasks['test']['title'];
+ if (isset($tasks['test']['icon'])) {
+ $icon = '';
+ }
+ else {
+ $icon = '';
+ }
+ $items[] = <<
+ $icon $title
+
+HTML;
+ $items[] = '';
+ }
+
+ // Add aegir items to items
+ if (!empty($aegir_items)) {
+ $items = array_merge($items, $aegir_items);
+
+ if (!empty($actions)) {
+ $items[] = '';
+ }
+ }
+
+ // Site specific actions.
+ if ($environment->site && $environment->site_status == HOSTING_SITE_ENABLED) {
+ if (node_access('create', 'site')) {
+ $items[] = l(' '.t('Clone Environment'),
+ "node/add/site/{$environment->project_name}/clone/{$environment->name}",
+ array(
+ 'html' => TRUE,
+ )
+ );
+
+// Removing "Fork Environment" because it's a decent amount of work to re-do now
+// now that the site node form is being used. It was confusing to users , and we will
+// have a new beta shortly after this one anyway.
+// @TODO: Un-deprecate Fork Environment.
+// $items[] = l(' '.t('Fork Environment'),
+// "node/add/site/{$environment->project_name}/fork/{$environment->name}",
+// array(
+// 'html' => TRUE,
+// )
+// );
+ }
+
+ // Add disable or delete task based on hosting variable.
+ if (!variable_get('hosting_require_disable_before_delete', TRUE)) {
+ $items[] = 'disable';
+ $items[] = 'delete';
+ } else {
+
+ if ($environment->site_status == HOSTING_SITE_DISABLED) {
+ $items[] = 'enable';
+ $items[] = 'delete';
+ } elseif (empty($environment->settings->locked)) {
+ $items[] = 'disable';
+ }
+ }
+
+ $items[] = '';
+
+ // Add export and import config buttons to Drupal 8 sites.
+ if (module_exists('aegir_config') && strpos($environment->version, '8') === 0) {
+ $items[] = 'config_export';
+ $items[] = 'config_import';
+ $items[] = '';
+ }
+ elseif (module_exists('aegir_features') && strpos($environment->version, '7') === 0) {
+ $items[] = 'features_update';
+ $items[] = 'features_revert_all';
+ $items[] = '';
+ }
+ }
+ elseif ($environment->site && $environment->site_status == HOSTING_SITE_DISABLED) {
+ $items[] = 'enable';
+ $items[] = 'delete';
+ }
+
+ return $items;
+}
+
+/**
+ * Implements hook_node_access().
+ */
+function devshop_projects_node_access($node, $op, $account) {
+ switch ($op) {
+ case 'create':
+ return user_access('create project', $account);
+ break;
+ case 'update':
+ return user_access('edit project', $account);
+ break;
+ case 'delete':
+ return user_access('delete projects', $account);
+ break;
+ case 'view':
+ return user_access('view project', $account);
+ break;
+ }
+}
+
+/**
+ * Implements hook_menu().
+ */
+function devshop_projects_menu() {
+ //Settings page.
+ $items['admin/devshop'] = array(
+ 'title' => 'DevShop Settings',
+ 'description' => 'Default values for use in creation project',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('devshop_projects_settings_form'),
+ 'access arguments' => array('administer projects'),
+ 'file' => 'admin.inc',
+ 'file path' => drupal_get_path('module', 'devshop_projects') . '/inc',
+ 'type' => MENU_NORMAL_ITEM,
+ );
+ $items['admin/devshop/settings'] = array(
+ 'title' => t('DevShop Settings'),
+ 'type' => MENU_DEFAULT_LOCAL_TASK,
+ );
+
+ $items['projects'] = array(
+ 'title' => t('Projects'),
+ 'description' => 'Display a list of all projects',
+ 'page callback' => 'devshop_projects_projects_page',
+ 'access arguments' => array('view projects'),
+ 'menu_name' => 'main-menu',
+ 'type' => MENU_NORMAL_ITEM,
+ 'weight' => 1,
+ );
+ $items['projects/list'] = array(
+ 'title' => t('All Projects'),
+ 'description' => '',
+ 'type' => MENU_DEFAULT_LOCAL_TASK,
+ );
+ $items['projects/add'] = array(
+ 'title' => 'Start New Project',
+ 'type' => MENU_LOCAL_TASK,
+ 'title' => t('Start a new Project'),
+ 'title callback' => 'check_plain',
+ 'page callback' => 'devshop_projects_add',
+ 'page arguments' => array(2),
+ 'access arguments' => array('create project'),
+ 'description' => 'Start a new Drupal website project.',
+ 'file' => 'create.inc',
+ 'file path' => drupal_get_path('module', 'devshop_projects') . '/inc/create',
+ );
+
+ // Ajax endpoint for reloads
+ $items['projects/add/status'] = array(
+ 'page callback' => 'devshop_projects_add_status',
+ 'access callback' => 'node_access',
+ 'access arguments' => array('create', 'project'),
+ 'file' => 'create.inc',
+ 'file path' => drupal_get_path('module', 'devshop_projects') . '/inc/create',
+ );
+
+ // hosting tasks ajax pages.
+ $tasks = hosting_available_tasks('project') ;
+ if (!empty($tasks)) {
+ foreach (hosting_available_tasks('project') as $task => $info) {
+ $path = 'hosting_confirm/%/project_' . $task;
+ $items[$path] = array(
+ 'title' => $info['title'],
+ 'description' => $info['description'],
+ 'page callback' => 'devshop_projects_hosting_task_confirm_form_page',
+ 'page arguments' => array(1, $task),
+ 'access callback' => 'hosting_task_menu_access_csrf',
+ 'access arguments' => array(1, $task),
+ 'type' => MENU_CALLBACK,
+ );
+ $items[$path] = array_merge($items[$path], $info);
+ }
+ }
+
+ // Drush aliases download.
+ $items['node/%node/aliases'] = array(
+ 'title' => 'Drush Aliases',
+ 'description' => '',
+ 'page callback' => 'devshop_project_drush_aliases_page',
+ 'page arguments' => array(1),
+ 'access callback' => 'node_access',
+ 'access arguments' => array('update', 1),
+ 'weight' => 1,
+ 'file' => 'inc/drush.inc',
+ 'type' => MENU_CALLBACK,
+ );
+
+// // Add Environment page
+// $items['project/%/add-environment'] = array(
+// 'title' => 'Add Environment',
+// 'type' => MENU_CALLBACK,
+// 'page callback' => 'devshop_projects_environment_add_page',
+// 'page arguments' => array(1),
+// 'access callback' => 'devshop_projects_environment_add_access',
+// 'access arguments' => array(1),
+// );
+
+ // Ajax endpoint for reloads
+ $items['devshop/tasks'] = array(
+ 'page callback' => 'devshop_projects_tasks_status_json',
+ 'page arguments' => array(2),
+ 'description' => '',
+ 'access arguments' => array('access task logs'),
+ 'file' => 'tasks-ajax.inc',
+ 'file path' => drupal_get_path('module', 'devshop_projects') . '/inc',
+ );
+
+ return $items;
+}
+
+/**
+ * Get the bootstrap class from hosting task status.
+ */
+function devshop_task_status_class($status) {
+ $codes = array(
+ HOSTING_TASK_SUCCESS => 'success',
+ HOSTING_TASK_QUEUED => 'queued',
+ HOSTING_TASK_ERROR => 'danger',
+ HOSTING_TASK_PROCESSING => 'processing',
+ HOSTING_TASK_WARNING => 'warning',
+ );
+ return $codes[$status];
+}
+
+/**
+ * Get the fontawesome icon from hosting task status.
+ */
+function devshop_task_status_icon($status) {
+ $icons = array(
+ HOSTING_TASK_SUCCESS => 'check',
+ HOSTING_TASK_QUEUED => 'cog',
+ HOSTING_TASK_ERROR => 'exclamation-circle',
+ HOSTING_TASK_PROCESSING => 'cog fa-spin',
+ HOSTING_TASK_WARNING => 'warning',
+ );
+ return $icons[$status];
+}
+
+function devshop_project_logs_access() {
+ return TRUE;
+}
+
+/**
+ * Implements hook_block_info_alter()
+ *
+ * Disables Aegir's queue and "supporting aegir" block.
+ *
+ * @param $blocks
+ * @param $theme
+ * @param $code_blocks
+ */
+function devshop_projects_block_info_alter(&$blocks, $theme, $code_blocks) {
+ $blocks['hosting']['hosting_queues']['status'] = 0;
+ $blocks['hosting']['hosting_queues_summary']['status'] = 0;
+ $blocks['hosting']['hosting_supporting_aegir']['status'] = 0;
+}
+
+/**
+ * Implements hook_block_info().
+ */
+function devshop_projects_block_info() {
+ $blocks['project_nav'] = array(
+ 'info' => t('DevShop Project Nav'),
+ 'weight' => '-10',
+ 'cache' => DRUPAL_CACHE_GLOBAL,
+ 'status' => 1,
+ 'region' => 'header',
+ );
+ $blocks['project_create'] = array(
+ 'info' => t('DevShop Create Project'),
+ 'weight' => '-10',
+ 'cache' => DRUPAL_CACHE_PER_USER,
+ 'status' => 1,
+ 'region' => 'sidebar_first',
+ );
+ return $blocks;
+}
+
+/**
+ * Implements hook_block_view().
+ */
+function devshop_projects_block_view($delta) {
+ if ($delta == 'project_nav' && (
+ arg(0) == 'node' || arg(0) == 'hosting_confirm'
+ )) {
+
+ if (is_numeric(arg(1))) {
+ $node = node_load(arg(1));
+ }
+ elseif (arg(1) == 'add') {
+ $node = devshop_projects_load_by_name(arg(3));
+ }
+
+ if (node_access('view', $node) && ($node->type == 'project' || !empty($node->project))) {
+
+ // If viewed node is project, use that.
+ if (is_numeric($node->project)) {
+ if ($project_node = node_load($node->project)) {
+ $project = $project_node->project;
+ }
+ }
+ elseif (!empty($node->project)) {
+ $project = $node->project;
+ }
+ else {
+ return '';
+ }
+
+ $block['subject'] = '';
+ $block['content'] = theme('devshop_project_nav', array('project' => $project));
+ }
+ }
+ elseif ($delta == 'project_create') {
+ if (arg(0) == 'projects' && arg(1) == 'add') {
+ ctools_include('object-cache');
+ $project = ctools_object_cache_get('project', 'devshop_project');
+ $block['subject'] = '';
+ $block['content'] = theme('devshop_project_add_status', array('project' => $project));
+ }
+ }
+ if (isset($block)) {
+ return $block;
+ }
+}
+
+/**
+ * Page Callback for projects/add
+ */
+function devshop_projects_add($step = NULL) {
+ if ($step == NULL) {
+ // Check to see if this project is still in the wizard
+ ctools_include('object-cache');
+ $project_wizard_cache = ctools_object_cache_get('project', 'devshop_project');
+ if (!empty($project_wizard_cache->step)) {
+ drupal_goto('projects/add/' . $project_wizard_cache->step);
+ }
+ }
+ return devshop_projects_create_wizard($step);
+}
+
+/**
+ * Replacement for hosting_task_confirm_form()
+ *
+ * @TODO: Remove once http://drupal.org/node/1861898 is committed.
+ */
+function devshop_projects_hosting_task_confirm_form_page($nid, $task) {
+ $node = node_load($nid);
+ return drupal_get_form('hosting_task_confirm_form', $node, $task);
+}
+
+/**
+ * Implements hook_menu_alter().
+ *
+ * Replaces node/add/project with a ctools wizard.
+ */
+function devshop_projects_menu_alter(&$items) {
+ $items['node/add/project']['page callback'] = 'devshop_projects_create_wizard';
+ $items['node/add/project']['page arguments'] = array(3);
+ $items['node/add/project']['file'] = 'create-wizard.inc';
+ $items['node/add/project']['file path'] = drupal_get_path('module', 'devshop_projects') . '/inc';
+
+ // Make project node pages more user-friendly.
+ $items['node/%node/view']['title callback'] = 'devshop_hosting_project_tab_title';
+ $items['node/%node/view']['title arguments'] = array('View', 1);
+
+ $items['node/%node/edit']['title callback'] = 'devshop_hosting_project_tab_title';
+ $items['node/%node/edit']['title arguments'] = array('Edit', 1);
+
+ $items['node/%node/edit']['access callback'] = 'devshop_hosting_project_tab_access';
+
+
+
+ $items['node/%node/view']['weight'] = -10;
+ $items['node/%node/edit']['weight'] = -9;
+
+ // Hosting now hides all but migrate and clone tasks.
+ $path = sprintf("hosting_confirm/%hosting_%s_wildcard/site_devshop-deploy", 'deploy', 'deploy', 'devshop-deploy');
+ $items[$path]['access callback'] = 'devshop_hosting_task_menu_access_csrf';
+
+ // Redirect hosting/task/% to the node
+ $items['hosting/task/%'] = array(
+ 'page callback' => 'drupal_goto_nid',
+ 'page arguments' => array(2),
+ 'access callback' => TRUE,
+ );
+}
+
+/**
+ * Helper to redirect to a node page.
+ * @param $nid
+ */
+function drupal_goto_nid($nid) {
+ drupal_goto("node/{$nid}");
+}
+
+/**
+ * Access callback helper for hosting task menu items.
+ * A copy of hosting_task_menu_access() except we allow devshop-deploy tasks.
+ * @see hosting_task_menu_access()
+ */
+function devshop_hosting_task_menu_access_csrf($node, $task) {
+ global $user;
+
+ $interactive_tasks = array('migrate', 'clone', 'devshop-deploy');
+
+ // To prevent CSRF attacks, a unique token based upon user is used. Deny
+ // access if the token is missing or invalid. We only do this on
+ // non-interactive tasks.
+ if (!in_array($task, $interactive_tasks) && (!isset($_GET['token']) || !drupal_valid_token($_GET['token'], $user->uid))) {
+ return FALSE;
+ }
+ // Call the main menu access handler.
+ return hosting_task_menu_access($node, $task);
+}
+
+/**
+ * Tab title replacer
+ */
+function devshop_hosting_project_tab_title($default, $node) {
+ if ($default == 'View' && $node->type == 'project') {
+ return t('Project Dashboard');
+ }
+ if ($default == 'Edit' && $node->type == 'project') {
+ return t('Project Settings');
+ }
+ if ($default == 'View' && $node->type == 'site' && isset($node->project)) {
+ return t('Environment Dashboard');
+ }
+ if ($default == 'Edit' && $node->type == 'site' && isset($node->project)) {
+ return t('Environment Settings');
+ }
+ if ($node->type == 'task') {
+ if ($default == 'View') {
+ $tasks = hosting_available_tasks('site');
+ $label = drupal_ucfirst($tasks[$node->task_type]['title']);
+ return t('Task: !type', array('!type' => $label));
+ }
+ }
+
+ // Otherwise, just return the page text
+ return t($default);
+}
+
+/**
+ * replacement for node_access
+ */
+function devshop_hosting_project_tab_access($type, $node) {
+ if ($node->type == 'task') {
+ return FALSE;
+ }
+ return node_access($type, $node);
+}
+
+/**
+ * Access Callback for Aegir Tasks
+ *
+ * This function defines access to the various aegir tasks.
+ *
+ * @arg $node object
+ * The node object is running the task. (Site, Platform, Server)
+ *
+ * @arg $task string
+ * The type of the task that is running.
+ *
+ * @see hosting_task_menu_access()
+ *
+ * @TODO: This NEVER runs for verify! Only for devshop-xyz tasks.
+ * project verify task is defined in devshop_projects_hosting_tasks() in
+ * inc/tasks.inc, and has this function as it's access callback. But this
+ * function seems to never run.
+ */
+function devshop_hosting_task_menu_access($node, $task) {
+ // If we are passed the nid by mistake
+ if (!isset($node->nid)) {
+ $node = node_load($node);
+ }
+
+ if ($node->type != 'project' && $task != 'delete' && !$node->status) {
+ return FALSE;
+ }
+
+ if (user_access("create " . $task . " task")) {
+ return TRUE;
+ }
+}
+
+
+
+/**
+ * Implements hook_hosting_drush_aliases_name().
+ *
+ * See http://drupal.org/project/hosting_drush_aliases
+ */
+function devshop_projects_hosting_drush_aliases_name($node) {
+ if (isset($node->project_name)) {
+ return $node->project_name . "." . $node->project_environment;
+ }
+}
+
+/**
+ * Helper function to create a site in a project.
+ * Used by Wizard & "Create Platform" > Post Verify
+ */
+function devshop_projects_create_site($project, $platform_node, $environment_name, $db_server = NULL) {
+ global $user;
+
+ // Create the site nodes
+ $node = new stdClass();
+ $node->type = 'site';
+ $node->status = 1;
+ $node->uid = $user->uid;
+ $node->title = devshop_environment_url($project, $environment_name);
+
+ // Aegir properties
+ // @TODO: better client & DB support
+ $node->client = HOSTING_DEFAULT_CLIENT;
+ $servers = hosting_get_servers('db');
+ $server = $db_server ? $db_server : key($servers);
+ $node->db_server = $server;
+
+ $node->platform = $platform_node->nid;
+
+ // Lookup this platforms install profile
+ $node->profile = db_query('SELECT nid FROM {hosting_package} WHERE short_name = :short_name', array(':short_name' => $project->install_profile))->fetchField();
+
+ // @TODO: Improve site language handling?
+ $node->site_language = !empty($user->language) ? $user->language : 'en';
+
+ // Subdomain alias, if configured.
+ if ($project->settings->live['environment_aliases'] && !empty($project->settings->live['live_domain'])) {
+ $node->aliases = array($environment_name . '.' . $project->settings->live['live_domain']);
+ }
+
+ // Save the site node
+ if ($node = node_submit($node)) {
+ node_save($node);
+ }
+
+ // Lookup install task to use it for last task.
+ $tasks = hosting_get_tasks('rid', $node->nid);
+
+ // Update environment with our site
+ db_update('hosting_devshop_project_environment')
+ ->fields(array(
+ 'site' => $node->nid,
+ 'last_task' => current($tasks)->nid,
+ ))
+ ->condition('project_nid', $project->nid)
+ ->condition('name', $environment_name)
+ ->execute();
+
+ return $node;
+}
+
+/**
+ * Helper to get select #options for git ref.
+ */
+function devshop_projects_git_ref_options($project, $current_ref = '') {
+
+ // Build branch options
+ if (is_array($project->settings->git['branches']) && !empty($project->settings->git['branches'])) {
+ $options = array(
+ 'Branches' => array_combine($project->settings->git['branches'], $project->settings->git['branches']),
+ );
+
+ // If there are tags...
+ if (is_array($project->settings->git['tags']) && !empty($project->settings->git['tags'])) {
+ $options['Tags'] = array_combine($project->settings->git['tags'], $project->settings->git['tags']);
+ }
+ }
+
+ // If there are none...
+ if (!isset($options)) {
+ $options = array(t('No branches or tags! Re-validate the project.'));
+ }
+
+ // Set "current" label.
+ if (isset($options['Branches'][$current_ref])) {
+ $options['Branches'][$current_ref] .= ' (' . t('current') . ')';
+ }
+ elseif (isset($options['Tags'][$current_ref])) {
+ $options['Tags'][$current_ref] .= ' (' . t('current') . ')';
+ }
+
+
+ return $options;
+}
+
+/**
+ * Check if a site has features diff enabled.
+ */
+function _devshop_projects_site_has_module($nid, $module) {
+ if (is_object($nid) && isset($nid->nid)) {
+ $nid = $nid->nid;
+ }
+
+ $query = db_select('hosting_package_instance', 'i')
+ ->fields('i', array('status'));
+ $query->leftJoin('hosting_package', 'p', 'i.package_id = p.nid');
+
+ $query->condition('p.short_name', $module);
+ $query->condition('rid', $nid);
+
+ $result = $query->execute()->fetchField();
+ return $result;
+}
+
+/**
+ * Check if a site has features diff enabled.
+ */
+function _devshop_projects_project_has_module($node, $module) {
+ $environment = key($node->project->environments);
+
+ if (isset($node->project->environments[$environment]->site)) {
+ return _devshop_projects_site_has_module($node->project->environments[$environment]->site, $module);
+ }
+}
+
+/**
+ * Reset all environment's "last task" to the NID.
+ */
+function devshop_reset_last_tasks () {
+
+ // Lookup all environments
+ $results = db_query('SELECT * FROM {hosting_devshop_project_environment}');
+ foreach ($results as $result) {
+ $site = node_load($result->site);
+ $nid = db_query("SELECT t.nid FROM hosting_task t INNER JOIN node n ON n.nid = t.nid WHERE rid = :site", array(
+ ':site' => $site->nid
+ ))->fetchField();
+
+ $site->environment->last_task = $nid;
+ devshop_environment_save_last_task($site->environment);
+ }
+}
+
+/**
+ * Helper to get a project node by name.
+ */
+function devshop_load_project_by_name($name) {
+ $nid = db_select('node', 'n')
+ ->fields('n', array('nid'))
+ ->condition('type', 'project')
+ ->condition('title', $name)
+ ->condition('status', 1)
+ ->execute()
+ ->fetchField()
+ ;
+ $node = node_load($nid);
+ return $node;
+}
+
+/**
+ * Generate an environment's URL from project and environment name using the domain name pattern.
+ *
+ * @param $project
+ * @param $environment_name
+ * @return string
+ */
+function devshop_environment_url($project, $environment_name) {
+
+ // Generate field prefix and suffix using domain name pattern.
+ if (variable_get('devshop_projects_allow_custom_base_url')) {
+ $pattern = $project->base_url;
+ }
+ else {
+ $pattern = variable_get('devshop_project_environment_url_pattern', '@project.@environment.@hostname');
+ }
+ $domain = strtr($pattern, array(
+ '@environment' => $environment_name,
+ '@project' => $project->name,
+ '@hostname' => $_SERVER['SERVER_NAME'],
+ ));
+ return $domain;
+}
+
+/**
+ * Retrieve a project node by name.
+ *
+ * @param $name
+ * @return bool|mixed
+ */
+function devshop_projects_load_by_name($name) {
+ $nid = db_select('node', 'n')
+ ->fields('n', array('nid'))
+ ->condition('title', $name)
+ ->condition('type', 'project')
+ ->condition('status', 1)
+ ->execute()
+ ->fetchField();
+
+ return node_load($nid);
+}
\ No newline at end of file
diff --git a/modules/devshop/devshop_projects/drush/Provision/Config/ProjectAliases.php b/modules/devshop/devshop_projects/drush/Provision/Config/ProjectAliases.php
new file mode 100644
index 000000000..555c244b9
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/Provision/Config/ProjectAliases.php
@@ -0,0 +1,31 @@
+data = array(
+ 'name' => strtr($context, array('@project_' => '')),
+ 'project' => $project,
+ );
+ }
+
+ function filename() {
+ return drush_server_home() . '/.drush/project_aliases/' . $this->data['name'] . '.aliases.drushrc.php';
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/Provision/Config/project_aliases.tpl.php b/modules/devshop/devshop_projects/drush/Provision/Config/project_aliases.tpl.php
new file mode 100644
index 000000000..19202aae2
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/Provision/Config/project_aliases.tpl.php
@@ -0,0 +1,39 @@
+environments as $name => $environment) {
+
+ if ($environment->site_status != 1) {
+ continue;
+ }
+ // Tell drush to inherit from the provision site alias record.
+ $alias = array(
+ 'parent' => '@' . $environment->uri
+ );
+
+ // If web server is not server master, add "remote host and user.
+ if (d($environment->drush_alias)->platform->web_server->name != '@server_master') {
+ $alias['remote-host'] = d($environment->drush_alias)->platform->web_server->remote_host;
+ $alias['remote-user'] = d($environment->drush_alias)->platform->web_server->script_user;
+ }
+
+ $export = var_export($alias, TRUE);
+ ?>
+
+$aliases[''] = ;
+
+ settings->aliases as $name => $remote_alias) {
+ $export = var_export($remote_alias, TRUE);
+ ?>
+
+$aliases[''] = ;
+
+
diff --git a/modules/devshop/devshop_projects/drush/Provision/Context/project.php b/modules/devshop/devshop_projects/drush/Provision/Context/project.php
new file mode 100644
index 000000000..abd16a6f1
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/Provision/Context/project.php
@@ -0,0 +1,46 @@
+project['environments'][$name])) {
+ return (object) $this->project['environments'][$name];
+ }
+ else {
+ return drush_set_error('DEVSHOP_PROJECT_ERROR', dt('Environment %name not found.', array(
+ '%name' => $name,
+ )));
+ }
+ }
+
+ static function option_documentation() {
+ return array(
+ '--project_name' => 'Project: The codename for this project.',
+ '--project' => 'Project: JSON encoded data about the project.',
+
+ //@TODO: Document this!
+// '--code_path' => 'Project: The path to the project codebases. (NOT the Drupal root)',
+// '--drupal_path' => 'Project: The path to the drupal root.',
+// '--git_url' => 'Project: The Git URL for this project.',
+// '--git_branches' => 'Project: The available Git branches in the remote repository for this project.',
+// '--git_tags' => 'Project: The available Git tags in the remote repository for this project.',
+// '--base_url' => 'Project: the base URL that the dev/test/live subdomains will be attached to.',
+// '--server' => 'Project: The server hosting this project. (Default is @server_master)',
+// '--install_profile' => 'Project: The desired installation profile for all sites.',
+ );
+ }
+
+ function init_project() {
+ $this->setProperty('project_name');
+ $this->setProperty('project');
+ }
+
+ function verify() {
+ $this->type_invoke('verify');
+ drush_log('[DEVSHOP] verify()', 'ok');
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/Provision/Service/Process.php b/modules/devshop/devshop_projects/drush/Provision/Service/Process.php
new file mode 100644
index 000000000..9a9654881
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/Provision/Service/Process.php
@@ -0,0 +1,26 @@
+server->remote_host)) {
+ return devshop_process($command, $cwd, $label, $env, $log_output);
+ }
+ else {
+ return devshop_process('ssh ' . drush_get_option('ssh-options', '-o PasswordAuthentication=no') . ' ' . $this->server->script_user . '@' . $this->server->remote_host . ' ' . $command, $cwd, $label, $env);
+ }
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/Provision/Service/project.php b/modules/devshop/devshop_projects/drush/Provision/Service/project.php
new file mode 100644
index 000000000..b20d70be7
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/Provision/Service/project.php
@@ -0,0 +1,26 @@
+setProperty('environment');
+ $context->setProperty('environment_settings');
+ $context->setProperty('project');
+ }
+
+ /**
+ * Add environment, project, and git ref to platform aliases.
+ */
+ static function subscribe_platform($context) {
+ $context->setProperty('environment');
+ $context->setProperty('project');
+ }
+
+}
diff --git a/modules/devshop/devshop_projects/drush/acquia/README.txt b/modules/devshop/devshop_projects/drush/acquia/README.txt
new file mode 100644
index 000000000..be1b71e65
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/acquia/README.txt
@@ -0,0 +1,9 @@
+Acquia Cloud Hooks Integration
+==============================
+
+DevShop now supports firing Acquia Cloud Hooks when deploying to environments.
+
+The code in acquia.drush.inc will detect an acquia repo and trigger your hooks
+to run.
+
+Currently, only the `post-code-update` hook is supported. More to come.
\ No newline at end of file
diff --git a/modules/devshop/devshop_projects/drush/acquia/acquia.drush.inc b/modules/devshop/devshop_projects/drush/acquia/acquia.drush.inc
new file mode 100644
index 000000000..59346a401
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/acquia/acquia.drush.inc
@@ -0,0 +1,76 @@
+type == 'site'){
+
+ $project_name = d()->project;
+ $project = (object) d("@project_{$project_name}")->project;
+ $environment = (object) $project->environments[d()->environment];
+
+ // Respect environment settings.
+ if ($environment->settings['deploy']['acquia_hooks'] != 1) {
+ return;
+ }
+
+ // If project has no path to drupal, we know it's not acquia.
+ if ($project->drupal_path != 'docroot') {
+ drush_log('[DEVSHOP] Acquia Cloud skipped.', 'ok');
+ return;
+ }
+
+ // Detect hook files.
+ $cloud_hooks_path = $environment->repo_path . '/hooks';
+ if (file_exists($cloud_hooks_path)) {
+ drush_log('[DEVSHOP] Acquia Cloud Hooks detected...', 'ok');
+ }
+
+ // Collect scripts to run.
+ // Common scripts.
+ $files = scandir($cloud_hooks_path . '/common/post-code-update');
+ if (empty($files)) $files = array();
+ $scripts = array_diff($files, array('..', '.'));
+ foreach ($scripts as &$script) {
+ $script = realpath($cloud_hooks_path . '/common/post-code-update/' . $script);
+ }
+
+ // Environment scripts: Post Code Update
+ if (file_exists($cloud_hooks_path . '/' . $environment->name)) {
+ $files = scandir($cloud_hooks_path . '/' . $environment->name . '/post-code-update');
+ if (empty($files)) $files = array();
+ $scripts += array_diff($files, array('..', '.'));
+ foreach ($scripts as &$script) {
+ $script = realpath($cloud_hooks_path . '/' . $environment->name . '/post-code-update/' . $script);
+ }
+ }
+
+ // @TODO: Post Code Deploy & Post DB Copy
+
+ drush_log(implode("\n", $scripts), 'ok');
+
+ // Run Scripts
+ // Usage: post-code-deploy site target-env source-branch deployed-tag repo-url repo-type
+ foreach ($scripts as $file) {
+ drush_log('[DEVSHOP] Running Acquia Cloud Hook: ' . $file, 'ok');
+
+ if (drush_shell_exec("sh $file {$project_name} {$environment->name} old_branch {$branch} repo_url repo_type devshop $environment->url") !== 0) {
+ $output = drush_shell_exec_output();
+ drush_log(implode("\n", $output), 'ok');
+ }
+ else {
+ return drush_set_error(DRUSH_FRAMEWORK_ERROR, 'The last cloud hook returned a non-zero exit code. Remaining hooks skipped.');
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/devshop/devshop_projects/drush/acquia/example.slack.sh b/modules/devshop/devshop_projects/drush/acquia/example.slack.sh
new file mode 100644
index 000000000..3c4f848d1
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/acquia/example.slack.sh
@@ -0,0 +1,39 @@
+#!/bin/sh
+#
+# Altered from https://github.com/acquia/cloud-hooks/tree/master/samples/slack
+# Requires a /var/aegir/slack_settings file that contains:
+#
+# SLACK_WEBHOOK_URL=https://example.slack.com/services/hooks/incoming-webhook?token=TOKEN
+#
+# Cloud Hook: post-code-deploy
+#
+# The post-code-deploy hook is run whenever you use the Workflow page to
+# deploy new code to an environment, either via drag-drop or by selecting
+# an existing branch or tag from the Code drop-down list. See
+# ../README.md for details.
+#
+# Usage: post-code-deploy site target-env source-branch deployed-tag repo-url
+# repo-type
+
+site="$1"
+target_env="$2"
+source_branch="$3"
+deployed_tag="$4"
+repo_url="$5"
+repo_type="$6"
+source="$7"
+site_url="$8"
+
+# Load the Slack webhook URL (which is not stored in this repo).
+. $HOME/slack_settings
+
+if [ $source = 'devshop' ]; then
+ source="DevShop"
+ image="https://www.drupal.org/files/project-images/devshop-icon.png"
+else
+ source="Acquia Cloud"
+ image="https://pbs.twimg.com/profile_images/1901642489/cloud_icon_150.png"
+fi
+
+# Post deployment notice to Slack
+curl -X POST --data-urlencode "payload={\"channel\": \"#dev-engageny\", \"username\": \"$source\", \"text\": \"Git branch \`$deployed_tag\` updated on *$target_env*: $site_url.\", \"icon_url\": \"$image\"}" $SLACK_WEBHOOK_URL
diff --git a/modules/devshop/devshop_projects/drush/aegir_extras/aegir_commit.drush.inc b/modules/devshop/devshop_projects/drush/aegir_extras/aegir_commit.drush.inc
new file mode 100644
index 000000000..74f22f4cf
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/aegir_extras/aegir_commit.drush.inc
@@ -0,0 +1,118 @@
+ 'Commits code to git.',
+ 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
+ 'options' => array(
+ 'message' => 'The commit message to use.',
+ 'push' => 'Push the code after committing.',
+ 'files' => 'A list of files, if desired.',
+ 'name' => 'The name of the user to commit as.',
+ 'mail' => 'The email of the user to commit as.',
+ ),
+ );
+ return $items;
+}
+
+/**
+ * Implements the provision-fua command.
+ */
+function drush_aegir_commit_provision_commit() {
+
+ if (d()->type == 'site') {
+ $git_url = d()->platform->repo_url;
+ $git_path = d()->platform->repo_path;
+
+ $message = trim(drush_get_option('message', ''));
+ $files = json_decode(drush_get_option('files', '[]'));
+ $push = drush_get_option('push', FALSE);
+ $name = drush_get_option('name', FALSE);
+ $mail = drush_get_option('mail', FALSE);
+
+ // Git Add
+ try {
+ $wrapper = new GitWrapper();
+ $wrapper->streamOutput();
+
+ $git = $wrapper->workingCopy($git_path);
+ $status = $git->getStatus();
+
+ if (empty($status)) {
+ drush_log('[Git Status] Working Directory is clean. Nothing to commit.', 'devshop_log');
+ }
+ else {
+ devshop_process('git status', $git_path, dt('Git Status'));
+ $cmds = array();
+
+ // If files option was detected
+ if ($files) {
+ $files_list = '';
+
+ foreach ($files as $file) {
+ $file = $git_path . '/' . $file;
+ if (file_exists($file)) {
+ $files_list .= ' ' . $file;
+ }
+ }
+
+ // If files lists is not empty, add "git add" command to queue.
+ if (!empty($files_list)) {
+ $cmds[] = "git add {$files_list}";
+ }
+ }
+ else {
+ $cmds[] = 'git add -A';
+ }
+
+ foreach ($cmds as $cmd) {
+ devshop_process($cmd, $git_path, 'Adding files to local repository...');
+ }
+
+ devshop_process('git status', $git_path, dt('Git Status'));
+
+ $hostmaster_uri = d('hostmaster')->uri;
+ $message = escapeshellarg("$message
+--------------
+Committed by OpenDevShop via http://$hostmaster_uri");
+
+ devshop_process("git commit -m {$message}", $git_path, 'Committing changes to local repository...', array(
+ 'GIT_AUTHOR_NAME' => $name,
+ 'GIT_AUTHOR_EMAIL' => $mail,
+ 'GIT_COMMITTER_NAME' => $name,
+ 'GIT_COMMITTER_EMAIL' => $mail,
+ ));
+
+ devshop_process('git status', $git_path, dt('Git Status'));
+ }
+
+ // Push the result, if desired.
+ if ($push) {
+
+ // Do a little pre-check to ensure we don't hang at http password.
+ $git_url = parse_url($git_url);
+ if (($git_url['scheme'] == 'http' || $git_url['scheme'] == 'https') && empty($git_url['pass'])) {
+ drush_log('http git url detected, with no password. Unable to push to this repository.', 'warning');
+ return;
+ }
+ else {
+ devshop_process("git push", $git_path, 'Pushing code to remote repository...');
+ devshop_process('git status', $git_path, dt('Git Status'));
+ }
+ }
+ }
+ catch (GitException $e) {
+ drush_log('Git Error', 'devshop_command');
+ drush_log("Unable to load git repository at path " . $git_path, 'devshop_error');
+ drush_set_error($e->getMessage());
+ }
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/aegir_extras/aegir_download.drush.inc b/modules/devshop/devshop_projects/drush/aegir_extras/aegir_download.drush.inc
new file mode 100644
index 000000000..2965e3984
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/aegir_extras/aegir_download.drush.inc
@@ -0,0 +1,130 @@
+ 'Downloads drupal modules and themes, and optionally commits them to git.',
+ 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
+ 'options' => array(
+ 'packages' => 'This list of modules and themes to download.',
+ 'commit' => 'Commit the downloaded code to git.',
+ 'message' => 'The message to use when committing.',
+ 'update' => 'Run update.php after the download.',
+ 'test' => 'Queue a test run after the download.',
+ ),
+ 'arguments' => array(
+ ),
+ 'examples' => array(
+ 'drush @env.project.domain.com provision-download views ctools --update --commit --message="the usuals"' => 'Downloads views & ctools, commits them to the repo with a message, and run drush updb.',
+ ),
+ 'aliases' => array('pdl'),
+ );
+ return $items;
+}
+
+/**
+ * Implements the provision-git-pull command.
+ */
+function drush_aegir_download_provision_download() {
+ $packages = drush_get_option('packages');
+
+ if (is_array($packages)) {
+ $packages = implode($packages);
+ }
+
+ // No modules, no more.
+ if (empty($packages)) {
+ return drush_set_error('No packages defined.');
+ }
+
+ // Run `drush dl $modules`
+ $target = d()->name;
+ $cmd = "drush $target dl $packages --yes --strict=0";
+
+ // Pass through options to the drush dl command.
+ if (drush_get_option('commit', FALSE)) {
+ $cmd .= ' --commit ';
+ }
+ if (drush_get_option('message', '')) {
+ $message = drush_get_option('message', '');
+ $cmd .= " --message='$message' ";
+ }
+ if (drush_get_option('update', FALSE)) {
+ $cmd .= " --update";
+ }
+
+ drush_log($cmd, 'devshop_command');
+
+ $process = new Process($cmd);
+ $process->setTimeout(null);
+ $exit_code = $process->run(
+ function ($type, $buffer) {
+ if (Process::ERR === $type) {
+ drush_log($buffer, 'devshop_ok');
+ } else {
+ drush_log($buffer, 'devshop_ok');
+ }
+ }
+ );
+
+ // check exit code
+ if ($exit_code === 0) {
+ drush_log("Aegir Download: OK", 'ok');
+ } else {
+ drush_log("Aegir Download Failed!", 'error');
+ drush_log(dt('Downloading modules failed. Check the logs and try again.'), 'devshop_error');
+ return;
+ }
+}
+
+/**
+ * Implements hook_drush_pm_post_download()
+ *
+ * Runs after a project has been downloaded.
+ *
+ * This is needed for devshop because we want to commit the new module we just
+ * downloaded, and only this hook knows about the path.
+ *
+ * @param $drupal_project
+ * @param $release
+ */
+function aegir_download_drush_pm_post_download($drupal_project, $release) {
+
+ $project_name = d()->project;
+ $project = (object) d("@project_{$project_name}")->project;
+ $environment = (object) $project->environments[d()->environment];
+
+ if (d()->environment && drush_get_option('commit', FALSE)) {
+ drush_log(dt('[DEVSHOP] Committing new module...'), 'ok');
+
+ $message = drush_get_option('message', dt('Committed by DevShop: ') . $drupal_project['name']);
+
+ $wrapper = new GitWrapper();
+ $wrapper->streamOutput();
+
+ try {
+ $git = $wrapper->workingCopy($environment->repo_path);
+
+ $download_path = $drupal_project['full_project_path'];
+ $git
+ ->add($download_path)
+ ->commit($message)
+ ->push();
+ }
+ catch (GitException $e) {
+ return drush_set_error('DRUSH_ERROR', "Git Exception: " . $e->getMessage());
+ }
+ }
+ else {
+ drush_log(dt('Skipping Commit. Make sure you commit or delete this module at some point.'), 'notice');
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/aegir_extras/aegir_features.drush.inc b/modules/devshop/devshop_projects/drush/aegir_extras/aegir_features.drush.inc
new file mode 100644
index 000000000..332cb991e
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/aegir_extras/aegir_features.drush.inc
@@ -0,0 +1,60 @@
+ 'Runs drush features-update-all.',
+ 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
+ 'options' => array(
+ 'features' => 'The list of features to update.',
+ 'revert' => 'Whether or not to revert the features afterwards.',
+ ),
+ );
+ return $items;
+}
+
+/**
+ * Implements the provision-fua command.
+ */
+function drush_aegir_features_provision_features_update() {
+
+ if (d()->type == 'site') {
+
+ $project_name = d()->project;
+ $project = (object) d("@project_{$project_name}")->project;
+ $environment = (object) $project->environments[d()->environment];
+
+ $features = trim(drush_get_option('features', ''));
+ $revert = drush_get_option('revert', FALSE);
+
+ // If features were specified...
+ if (!empty($features)) {
+ devshop_process("drush {$environment->drush_alias} features-update {$features} -y", NULL, dt('Update Features'));
+ }
+ else {
+ devshop_process("drush {$environment->drush_alias} features-update-all -y", NULL, dt('Update All Features'));
+ }
+
+ // Show git status
+ devshop_process("git status", $environment->repo_path, NULL, dt('Checking git status'));
+
+ // Revert force revert the features if the user asked.
+ if ($revert) {
+ if (!empty($features)) {
+ devshop_process("drush {$environment->drush_alias} fr {$features} --force -y", NULL, dt('Revert Features'));
+ }
+ else {
+ devshop_process("drush {$environment->drush_alias} fra --force -y", NULL, dt('Revert Features'));
+ }
+ }
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/composer.json b/modules/devshop/devshop_projects/drush/composer.json
new file mode 100644
index 000000000..be849d2d6
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/composer.json
@@ -0,0 +1,7 @@
+{
+ "require": {
+ "symfony/process": "^2.7",
+ "cpliakas/git-wrapper": "~1.0",
+ "symfony/class-loader": "^3.0"
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/composer.lock b/modules/devshop/devshop_projects/drush/composer.lock
new file mode 100644
index 000000000..a06c03025
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/composer.lock
@@ -0,0 +1,239 @@
+{
+ "_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"
+ ],
+ "hash": "99d65f6a12dd5b91c0eaf59137155905",
+ "content-hash": "90ec948563783f502e22b7dc6254cf67",
+ "packages": [
+ {
+ "name": "cpliakas/git-wrapper",
+ "version": "1.6.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/cpliakas/git-wrapper.git",
+ "reference": "5b89e9c5b4598c110e2d93d1245b4e26df3855a5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/cpliakas/git-wrapper/zipball/5b89e9c5b4598c110e2d93d1245b4e26df3855a5",
+ "reference": "5b89e9c5b4598c110e2d93d1245b4e26df3855a5",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0",
+ "symfony/event-dispatcher": "~2.3|~3.0",
+ "symfony/process": "~2.3|~3.0"
+ },
+ "require-dev": {
+ "pdepend/pdepend": "~1.0",
+ "phploc/phploc": "~2.0",
+ "phpmd/phpmd": "~1.0",
+ "phpunit/phpunit": "~3.0",
+ "psr/log": "~1.0",
+ "scrutinizer/ocular": "~1.0",
+ "sebastian/phpcpd": "~2.0",
+ "symfony/filesystem": "~2.0"
+ },
+ "suggest": {
+ "monolog/monolog": "Enables logging of executed git commands"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-0": {
+ "GitWrapper": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Chris Pliakas",
+ "email": "opensource@chrispliakas.com"
+ }
+ ],
+ "description": "A PHP wrapper around the Git command line utility.",
+ "homepage": "https://github.com/cpliakas/git-wrapper",
+ "keywords": [
+ "git"
+ ],
+ "time": "2015-11-11 15:45:43"
+ },
+ {
+ "name": "symfony/class-loader",
+ "version": "v3.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/class-loader.git",
+ "reference": "92e7cf1af2bc1695daabb4ac972db169606e9030"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/class-loader/zipball/92e7cf1af2bc1695daabb4ac972db169606e9030",
+ "reference": "92e7cf1af2bc1695daabb4ac972db169606e9030",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.5.9"
+ },
+ "require-dev": {
+ "symfony/finder": "~2.8|~3.0",
+ "symfony/polyfill-apcu": "~1.1"
+ },
+ "suggest": {
+ "symfony/polyfill-apcu": "For using ApcClassLoader on HHVM"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\ClassLoader\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony ClassLoader Component",
+ "homepage": "https://symfony.com",
+ "time": "2016-02-03 09:33:23"
+ },
+ {
+ "name": "symfony/event-dispatcher",
+ "version": "v3.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/event-dispatcher.git",
+ "reference": "d36355e026905fa5229e1ed7b4e9eda2e67adfcf"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/d36355e026905fa5229e1ed7b4e9eda2e67adfcf",
+ "reference": "d36355e026905fa5229e1ed7b4e9eda2e67adfcf",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.5.9"
+ },
+ "require-dev": {
+ "psr/log": "~1.0",
+ "symfony/config": "~2.8|~3.0",
+ "symfony/dependency-injection": "~2.8|~3.0",
+ "symfony/expression-language": "~2.8|~3.0",
+ "symfony/stopwatch": "~2.8|~3.0"
+ },
+ "suggest": {
+ "symfony/dependency-injection": "",
+ "symfony/http-kernel": ""
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\EventDispatcher\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony EventDispatcher Component",
+ "homepage": "https://symfony.com",
+ "time": "2015-10-30 23:35:59"
+ },
+ {
+ "name": "symfony/process",
+ "version": "v2.8.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/process.git",
+ "reference": "1b988a88e3551102f3c2d9e1d47a18c3a78d6312"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/process/zipball/1b988a88e3551102f3c2d9e1d47a18c3a78d6312",
+ "reference": "1b988a88e3551102f3c2d9e1d47a18c3a78d6312",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.9"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.8-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Process\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Process Component",
+ "homepage": "https://symfony.com",
+ "time": "2015-11-30 12:35:10"
+ }
+ ],
+ "packages-dev": [],
+ "aliases": [],
+ "minimum-stability": "stable",
+ "stability-flags": [],
+ "prefer-stable": false,
+ "prefer-lowest": false,
+ "platform": [],
+ "platform-dev": []
+}
diff --git a/modules/devshop/devshop_projects/drush/contexts.inc b/modules/devshop/devshop_projects/drush/contexts.inc
new file mode 100644
index 000000000..5227e37fa
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/contexts.inc
@@ -0,0 +1,144 @@
+ref->project->git_url);
+
+ // If something went wrong connecting to the git repo, don't wipe out our branches.
+ if (!empty($branches['branches'])) {
+ $task->ref->project->settings->git['branches'] = $branches['branches'];
+ $task->ref->project->settings->git['tags'] = $branches['tags'];
+ $task->ref->project->settings->git['refs'] = $branches['refs'];
+
+ // Save the project node now that we have branches and tags.
+ // Don't verify again, this is the verification process.
+ $task->ref->no_verify = TRUE;
+ node_save($task->ref);
+ }
+
+ // Save project object to drush alias (aegir context).
+ if (isset($task->ref->project)) {
+ $task->context_options['server'] = '@server_master';
+ $task->context_options['project_name'] = $task->ref->title;
+ $task->context_options['project'] = $task->ref->project;
+ }
+}
+
+/**
+ * Implements hook_hosting_site_context_options().
+ *
+ * Runs on verify task. Passes data to the drush alias.
+ * Save environment name, project name, and git ref to site aliases.
+ */
+function devshop_projects_hosting_site_context_options(&$task) {
+
+ if (isset($task->ref->environment)) {
+ $task->context_options['environment'] = $task->ref->environment->name;
+ $task->context_options['environment_settings'] = $task->ref->environment->settings;
+ $task->context_options['project'] = $task->ref->project->name;
+ $task->context_options['git_ref'] = $task->ref->environment->git_ref;
+ }
+
+ // If install task is set to force-reinstall, pass to drush options.
+ if ($task->task_type == 'install' && $task->task_args['force-reinstall']) {
+ $task->options['force-reinstall'] = 1;
+ }
+
+}
+
+/**
+ * Implements hook_hosting_site_context_options().
+ *
+ * Runs on verify task. Passes data to the drush alias.
+ * Save environment name, project name, and git ref to site aliases.
+ */
+function devshop_projects_hosting_platform_context_options(&$task) {
+
+ if (isset($task->ref->environment)) {
+ $task->context_options['environment'] = $task->ref->environment->name;
+ $task->context_options['project'] = $task->ref->project->name;
+ $task->context_options['git_ref'] = $task->ref->environment->git_ref;
+ }
+}
+
+/**
+ * Implements hook_drush_context_import().
+ *
+ * This allows project nodes to be created from contexts (aliases)
+ */
+function devshop_projects_drush_context_import($context, &$node) {
+ if ($context->type == 'Project') {
+ $node->title = $context->project_name;
+ $node->type = 'project';
+ $node->project = $context->project;
+ }
+}
+
+/**
+ * Helpfer for getting branches and tags from a git URL
+ */
+function getBranchesAndTags($git_url = NULL) {
+ if (is_null($git_url)) {
+ $git_url = drush_get_option('git_url');
+ }
+ $command = "git ls-remote {$git_url}";
+ $output = devshop_process($command);
+ $lines = explode("\n", $output);
+
+ // Build tag and branch list
+ $branches = array();
+ $tags = array();
+ $refs = array();
+
+ foreach ($lines as $line_string) {
+
+ // "annotated" tags come with an extra row and these characters at the end.
+ // See http://stackoverflow.com/questions/15472107/when-listing-git-ls-remote-why-theres-after-the-tag-name
+ if (substr($line_string, -3, 3) == '^{}') {
+ continue;
+ }
+
+ // Example remote line:
+ // 9fc5727c0823d8d3300ba5aae3328d5998033e45 refs/heads/master
+ // 9fc5727c0823d8d3300ba5aae3328d5998033e45 refs/tags/1.0
+ $line = trim(substr($line_string, 40));
+ if (empty($line) || $line == 'HEAD') {
+ continue;
+ }
+
+ // If branch
+ if (strpos($line, 'refs/heads/') === 0) {
+ $git_ref = str_replace('refs/heads/', '', $line);
+ $branches[] = $git_ref;
+ $refs[$git_ref] = 'branch';
+ }
+ // else if tag
+ elseif (strpos($line, 'refs/tags/') === 0) {
+ $git_ref = str_replace('refs/tags/', '', $line);
+ $tags[] = $git_ref;
+ $refs[$git_ref] = 'tag';
+ }
+ // If not a tag or a head, continue.
+ // @TODO: Should we store alternative types? GitHub Pull Requests use this.
+ else {
+ continue;
+ }
+ }
+ drush_log(dt('Found !count branches: !list', array(
+ '!count' => count($branches),
+ '!list' => implode(', ', $branches),
+ )), 'devshop_log');
+ drush_log(dt('Found !count tags: !list', array(
+ '!count' => count($tags),
+ '!list' => implode(', ', $tags),
+ )), 'devshop_log');
+
+ return array('branches' => $branches, 'tags' => $tags, 'refs' => $refs);
+}
diff --git a/modules/devshop/devshop_projects/drush/deploy.devshop.provision.inc b/modules/devshop/devshop_projects/drush/deploy.devshop.provision.inc
new file mode 100644
index 000000000..fb104304b
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/deploy.devshop.provision.inc
@@ -0,0 +1,149 @@
+type == 'site') {
+ if (empty(d()->environment)) {
+ return drush_set_error('DEVSHOP_FRAMEWORK_ERROR', 'This site is not a part of a project. You cannot use this command.');
+ }
+ }
+ else {
+ return drush_set_error('DEVSHOP_FRAMEWORK_ERROR', 'provision-devshop-deploy must be run on a site context.');
+ }
+
+ // Verify that the branch or tag exists
+ if (empty($git_ref)) {
+ return drush_set_error('DEVSHOP_FRAMEWORK_ERROR', 'You must specify a valid branch or tag.');
+ }
+
+ $project_alias = '@project_' . d()->project;
+ $project = (object) d($project_alias)->project;
+
+ if (!isset($project->settings['git']['refs'][$git_ref])) {
+ $drush_command = "drush $project_alias provision-verify";
+ return drush_set_error('DEVSHOP_FRAMEWORK_ERROR', "Branch or tag '$git_ref' not found. Try running '$drush_command' to fetch new remote branches or tags.");
+ }
+}
+
+/**
+ * Implements the provision-devshop-deploy command.
+ */
+function drush_devshop_provision_provision_devshop_deploy($git_ref = '')
+{
+ $project_name = d()->project;
+ $project = (object) d("@project_{$project_name}")->project;
+ $environment = (object) $project->environments[d()->environment];
+ $desired_ref_type = $project->settings['git']['refs'][$git_ref];
+
+ drush_log('[Current Working Directory]' . d()->platform->repo_path, 'devshop_log');
+
+ // Stash any changes? Not sure if we should do this anymore...
+ // $git->command('stash');
+
+ // Fetch
+ devshop_process("git fetch --all", d()->platform->repo_path, dt('DevShop Deploy'));
+
+ // Checkout the chosen ref
+ $git_checkout_output = devshop_process("git checkout {$git_ref}", d()->platform->repo_path, dt('DevShop Deploy'));
+ $git_checkout_output_lines = explode("\n", $git_checkout_output);
+
+ // Run Git Pull, if on a branch.
+ if ($desired_ref_type == 'branch') {
+ $git_pull_output = devshop_process("git pull", d()->platform->repo_path, dt('DevShop Deploy'));
+ }
+
+ // Run a submodule update and init.
+ devshop_process("git submodule update --init --recursive --force ", d()->platform->repo_path, dt('DevShop Deploy'));
+
+ devshop_process("git status", d()->platform->repo_path, dt('DevShop Deploy'));
+}
+
+/**
+ * Post provision-devshop-deploy
+ */
+function drush_devshop_provision_post_provision_devshop_deploy($git_ref = '') {
+
+ // Get post deploy options
+ $revert = drush_get_option('revert');
+ $update = drush_get_option('update');
+ $cache = drush_get_option('cache');
+ $composer = drush_get_option('composer', TRUE);
+
+ $project_name = d()->project;
+ $project = (object) d("@project_{$project_name}")->project;
+ $environment = (object) $project->environments[d()->environment];
+ $desired_ref_type = $project->settings['git']['refs'][$git_ref];
+
+ // Ensure drush_alias exists. Not sure why it was missing for me.
+ if (empty($environment->drush_alias)) {
+ $environment->drush_alias = d()->name;
+ }
+
+ $commands = array();
+
+ drush_log("[{$project_name}] {$environment->name}: " . dt('Running deploy hooks.'), 'notice');
+
+ // Built in Hooks
+ if ($composer) {
+ if (file_exists(d()->platform->repo_path . '/composer.json')) {
+ $commands[] = "composer install";
+ }
+ elseif (file_exists($environment->root . '/composer.json')) {
+ $commands[] = "cd {$environment->root} && composer install";
+ }
+ }
+ else {
+ drush_log(dt('[DEVSHOP] Skipped running composer install...'), 'ok');
+ }
+
+ // Built in Hooks
+ if ($update) {
+ $commands[] = "drush {$environment->drush_alias} updatedb --yes";
+ }
+ else {
+ drush_log(dt('[DEVSHOP] Skipped updating database...'), 'ok');
+ }
+
+ // Clear the whole cache, unless option is false
+ if ($cache) {
+ if (drush_drupal_major_version(d()->root) == 8) {
+ $commands[] = "drush {$environment->drush_alias} cache-rebuild";
+ }
+ else {
+ $commands[] = "drush {$environment->drush_alias} cache-clear all";
+ }
+ }
+ else {
+ drush_log(dt('[DEVSHOP] Skipped clearing all caches...'), 'ok');
+ }
+
+ // Revert All Features, unless option is false
+ if ($revert) {
+ $commands[] = "drush {$environment->drush_alias} features-revert-all --yes";
+ }
+ else {
+ drush_log(dt('[DEVSHOP] Skipped reverting all features...'), 'ok');
+ }
+
+ foreach ($commands as $command) {
+ $output = devshop_process($command, d()->platform->repo_path, 'DevShop Deploy Hook');
+
+ // Detect common errors and help the user.
+ // Composer install with incorrect PHP version.
+ if (strpos($command, 'composer install') !== FALSE && strpos($output, 'your PHP version') !== FALSE && strpos($output, 'does not satisfy that requirement') !== FALSE) {
+ drush_log(dt('Composer indicated that it could not install packages because the site codebase requires a higher version of PHP than what is running on the server.'), 'devshop_log');
+ drush_log(dt('You can either upgrade your version of PHP on this server or set the maximum PHP version in your composer.json file. See !link for instructions for how to modify your composer.json file, then make sure to run `composer update` on your codebase to pin your packages to a lower PHP version.', [
+ '!link' => 'https://getcomposer.org/doc/06-config.md#platform',
+ ]), 'devshop_log');
+ }
+
+ }
+}
+
diff --git a/modules/devshop/devshop_projects/drush/deploy_hooks_example/devshop.drush.inc b/modules/devshop/devshop_projects/drush/deploy_hooks_example/devshop.drush.inc
new file mode 100644
index 000000000..bb6fceb5e
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/deploy_hooks_example/devshop.drush.inc
@@ -0,0 +1,39 @@
+type == 'site'){
+
+ $project_name = d()->project;
+ $project = (object) d("@project_{$project_name}")->project;
+ $environment = (object) $project->environments[d()->environment];
+
+ drush_log("[CUSTOM] Successfully checked out $branch to environment $environment->name in project $project_name.", 'ok');
+
+// provision_backend_invoke(d()->name, 'status');
+
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/devshop-dev.make b/modules/devshop/devshop_projects/drush/devshop-dev.make
new file mode 100644
index 000000000..b9808342b
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/devshop-dev.make
@@ -0,0 +1,19 @@
+;
+; This makefile is used by the DevShop standalone installer to build devmaster.
+;
+
+core = 6.x
+api = 2
+
+projects[drupal][type] = "core"
+
+; DEVELOPMENT MODE:
+; When in development, use this:
+projects[devmaster][type] = "profile"
+projects[devmaster][download][type] = "git"
+projects[devmaster][download][url] = "git@git.drupal.org:project/devmaster.git"
+projects[devmaster][download][branch] = "6.x-1.x"
+
+; RELEASE:
+; When releasing, lock in the devmaster version.
+;projects[devmaster][version] = "6.x-1.0"
\ No newline at end of file
diff --git a/modules/devshop/devshop_projects/drush/devshop_provision.drush.inc b/modules/devshop/devshop_projects/drush/devshop_provision.drush.inc
new file mode 100644
index 000000000..a7b32753f
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/devshop_provision.drush.inc
@@ -0,0 +1,596 @@
+ 'Deploys a tag or branch to an environment and (optionally) run update.php, clear cache, and revert features.',
+ 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
+ 'options' => array(
+ 'update' => 'Run update.php after code pull.',
+ 'revert' => 'Revert all features after code pull.',
+ 'cache' => 'Clear all caches after code pull.',
+ 'reset' => 'Runs "git reset --hard" before pulling.',
+ 'force' => "Runs always update,revert and cache options, even if files don't change.",
+ 'test' => 'Queue a test run after the deploy. (Only works from Hostmaster)',
+ ),
+ 'arguments' => array(
+ 'git_ref' => 'The git branch or tag to deploy.',
+ ),
+ 'examples' => array(
+ 'drush @env.project.domain.com provision-devshop-deploy master --cache --update' => 'Triggers a git checkout & pull of branch master to the dev environment, clearing caches and running updates.',
+ ),
+ 'aliases' => array('deploy'),
+ );
+ $items['provision-test'] = array(
+ 'description' => 'Run a set of tests.',
+ 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
+ 'options' => array(
+ 'tests-to-run' => 'The list of tests to run, separated by comma.',
+ 'test-type' => 'The type of tests to run. simpletest, behat',
+ 'behat-folder-path' => 'The path to this sites behat tests.',
+ 'behat-bin-path' => 'The path to the behat executable within behat-folder-path',
+ 'output-path' => 'The path to a folder to store results in. Will be created if it doesn\'t exist',
+ ),
+ 'aliases' => array('test'),
+ );
+ return $items;
+}
+
+/**
+ * Function for checking if this is a project and we have a repo.
+ *
+ * Used in pre drush command hooks
+ */
+function devshop_provision_pre_flight($platform_name = NULL){
+ if (d()->type != 'Project'){
+ return drush_set_error(DEVSHOP_FRAMEWORK_ERROR, 'All provision-devshop-* commands must be run on a project alias.');
+ }
+}
+
+/**
+ * Append PHP code to Drupal's settings.php file.
+ *
+ * To use templating, return an include statement for the template.
+ *
+ * @param $uri
+ * URI for the site.
+ * @param $data
+ * Associative array of data from provisionConfig_drupal_settings::data.
+ *
+ * @return
+ * Lines to add to the site's settings.php file.
+ *
+ * @see provisionConfig_drupal_settings
+ */
+function devshop_provision_provision_drupal_config($uri, $data, $config = NULL) {
+
+ $environment = d()->environment;
+ $project = d()->project;
+
+ return << NULL,
+ 'Process' => 'Process',
+ );
+}
+
+/**
+ * Implements hook_provision_apache_vhost_config()
+ *
+ * Adds "devshop_project" and "devshop_environment" server variables.
+ *
+ * @param $uri
+ * URI for the site.
+ * @param $data
+ * Associative array of data from Provision_Config_Apache_Site::data.
+ *
+ * @return
+ * Lines to add to the configuration file.
+ *
+ * @see Provision_Config_Apache_Site
+ * provision_logs_provision_apache_vhost_config
+ */
+function devshop_provision_provision_apache_vhost_config($uri, $data) {
+
+ $environment = d()->environment;
+ $project = d()->project;
+
+ if (empty($environment) || empty($project)) {
+ return;
+ }
+
+ return <<environment;
+ $project = d()->project;
+
+ if (empty($environment) || empty($project)) {
+ return;
+ }
+
+ return <<project) && empty(d()->environment)) {
+ drush_log('[DEVSHOP] New environment detected. Rebuilding Registry.', 'ok');
+ drush_invoke('registry-rebuild');
+ }
+}
+
+/**
+ * Run a Symfony Process saving to drush logs.
+ */
+function devshop_process($command, $cwd = null, $label = 'Process', $env = array(), $log_output = TRUE, $error_message = 'Process Failed') {
+ drush_log("[$label] $command", 'devshop_command');
+
+ // Merge in env vars, inheriting the CLI's
+ if (is_array($env)) {
+ $env = array_merge($_SERVER, $env);
+ }
+ else {
+ $env = $_SERVER;
+ }
+
+ // Make sure colors always come through
+ $env['TERM'] = 'xterm';
+
+ $process = new Process(escapeshellcmd($command), $cwd, $env);
+ $process->setTimeout(NULL);
+ if ($log_output) {
+ $exit_code = $process->run(function ($type, $buffer) {
+ if (drush_get_option('is-devshop', FALSE)) {
+ drush_log($buffer, 'devshop_info');
+ }
+ else {
+ echo $buffer;
+ }
+
+// if (Process::ERR === $type) {
+// drush_log($buffer, 'devshop_info');
+// } else {
+// drush_log($buffer, 'devshop_info');
+// }
+ });
+ }
+ else {
+ $exit_code = $process->run();
+ }
+
+ // check exit code
+ if ($exit_code === 0) {
+ drush_log('', 'devshop_ok');
+ return $process->getOutput();
+ }
+ else {
+ drush_log('', 'devshop_error');
+ drush_set_error('DEVSHOP_PROCESS_ERROR', dt($error_message));
+ return $process->getErrorOutput();
+ }
+}
+
+/**
+ * Find the username of the current running procses
+ *
+ * This will return the username of the current running user (as seen
+ * from posix_geteuid()) and should be used instead of
+ * get_current_user() (which looks at the file owner instead).
+ *
+ * @see get_current_user()
+ * @see posix_geteuid()
+ *
+ * @return
+ * String. The username.
+ */
+function devshop_current_user() {
+ return devshop_posix_username(posix_geteuid());
+}
+
+/**
+ * Check whether a user is a member of a group.
+ *
+ * @param user
+ * username or user id of user.
+ * @param group
+ * groupname or group id of group.
+ *
+ * @return
+ * Boolean. True if user does belong to group,
+ * and FALSE if the user does not belong to the group, or either the user or group do not exist.
+ */
+function devshop_user_in_group($user, $group) {
+ // TODO: make these singletons with static variables for caching.
+ $user = devshop_posix_username($user);
+ $group = devshop_posix_groupname($group);
+ if ($user && $group) {
+ $info = posix_getgrnam($group);
+ if (in_array($user, $info['members'])) {
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+/**
+ * Return the valid system username for $user.
+ *
+ * @return
+ * Returns the username if found, otherwise returns FALSE
+ */
+function devshop_posix_username($user) {
+ // TODO: make these singletons with static variables for caching.
+ // we do this both ways, so that the function returns NULL if no such user was found.
+ if (is_numeric($user)) {
+ $info = posix_getpwuid($user);
+ $user = $info['name'];
+ }
+ else {
+ $info = posix_getpwnam($user);
+ $user = $info['name'];
+ }
+ return $user;
+}
+
+/**
+ * Return the valid system groupname for $group.
+ *
+ * @return
+ * Returns the groupname if found, otherwise returns FALSE
+ */
+function devshop_posix_groupname($group) {
+ // TODO: make these singletons with static variables for caching.
+ // we do this both ways, so that the function returns NULL if no such user was found.
+ if (is_numeric($group)) {
+ $info = posix_getgrgid($group);
+ $group = $info['name'];
+ }
+ else {
+ $info = posix_getgrnam($group);
+ $group = $info['name'];
+ }
+ return $group;
+}
+
+/**
+ * Generate a random alphanumeric password.
+ *
+ * This is a copy of Drupal core's user_password() function. We keep it
+ * here in case we need this and don't have a bootstrapped Drupal
+ * around.
+ *
+ * @see user_password()
+ */
+function devshop_password($length = 10) {
+ // This variable contains the list of allowable characters for the
+ // password. Note that the number 0 and the letter 'O' have been
+ // removed to avoid confusion between the two. The same is true
+ // of 'I', 1, and 'l'.
+ $allowable_characters = 'abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789';
+
+ // Zero-based count of characters in the allowable list:
+ $len = strlen($allowable_characters) - 1;
+
+ // Declare the password as a blank string.
+ $pass = '';
+
+ // Loop the number of times specified by $length.
+ for ($i = 0; $i < $length; $i++) {
+
+ // Each iteration, pick a random character from the
+ // allowable string and append it to the password:
+ $pass .= $allowable_characters[mt_rand(0, $len)];
+ }
+
+ return $pass;
+}
+
+function devshop_default_web_group() {
+ $info = posix_getgrgid(posix_getgid());
+ $common_groups = array(
+ 'www-data',
+ 'apache',
+ 'nginx',
+ 'www',
+ '_www',
+ 'webservd',
+ 'httpd',
+ 'nogroup',
+ 'nobody',
+ $info['name']);
+
+ foreach ($common_groups as $group) {
+ if (devshop_posix_groupname($group)) {
+ return $group;
+ break;
+ }
+ }
+ return NULL;
+}
+
+/**
+ * return the FQDN of the machine or provided host
+ *
+ * this replicates hostname -f, which is not portable
+ */
+function devshop_fqdn($host = NULL) {
+ if (is_null($host)) {
+ $host = php_uname('n');
+ }
+ return strtolower(gethostbyaddr(gethostbyname($host)));
+}
+
+//
+//if (!function_exists('provision_autoload_register_prefix')) {
+// /**
+// * Register a PECL style prefix with the provision autoloader.
+// *
+// * @param string $prefix
+// * The class prefix to register.
+// * @param string $dir
+// * The directory to search for the classes in.
+// * @param bool $prepend
+// * If the directory should be searched first for the classes in the given
+// * prefix, set this to TRUE, otherwise, the default, FALSE, is fine.
+// */
+// function provision_autoload_register_prefix($prefix, $dir, $prepend = FALSE) {
+//
+// // Get any current directories set for this prefix.
+// $current_prefixes = provision_autoload()->getPrefixes();
+// if (isset($current_prefixes[$prefix])) {
+// $dirs = $current_prefixes[$prefix];
+// }
+// else {
+// $dirs = array();
+// }
+//
+// // Now add the new one.
+// if ($prepend) {
+// array_unshift($dirs, $dir);
+// }
+// else {
+// array_push($dirs, $dir);
+// }
+//
+// // Set the prefixes.
+// provision_autoload()->addPrefix($prefix, $dirs);
+// }
+//}
+//
+//if (!function_exists('provision_autoload')) {
+// /**
+// * Return an instance of the provision autoloader.
+// *
+// * This will instiatate an instance if it needs to.
+// */
+// function provision_autoload() {
+// static $instance = NULL;
+//
+// if (is_null($instance)) {
+// $instance = new ClassLoader();
+// // Activate the autoloader.
+// $instance->register();
+// }
+//
+// return $instance;
+// }
+//}
+
+/**
+ * Implementation of drush_hook_provision_pre_COMMAND()
+ * Calls drush_devshop_provision_pre_provision_verify()
+ */
+function drush_devshop_provision_pre_provision_deploy() {
+ drush_devshop_provision_pre_provision_verify();
+}
+
+/**
+ * Implementation of drush_hook_provision_pre_COMMAND()
+ * for Verify tasks: Writes easier to use drush aliases for each project.
+ */
+function drush_devshop_provision_pre_provision_verify() {
+ if (d()->type === 'project' || (!empty(d()->project) && d()->type === 'site' || d()->type === 'platform')) {
+
+ }
+}
+
+/**
+ * Implementation of drush_hook_post_provision_install()
+ *
+ * This function handles all of the alternative environment install methods.
+ */
+function drush_devshop_provision_post_provision_install() {
+
+ // Install Method: Clone
+ // Sync from a drush alias.
+ drush_log('Installing environment with method: ' . d()->install_method , 'devshop_log');
+ if (d()->install_method == 'clone') {
+ drush_log(dt('Environment Clone initiated...'), 'devshop_log');
+ $clone_source = d()->environment_settings['install_method']['clone_source'];
+
+ // If "clone_source" is set to "_other", use the drush alias in "clone_source_drush".
+ if ($clone_source == '_other') {
+ $source = d()->environment_settings['install_method']['clone_source_drush'];
+ }
+ elseif (!empty($clone_source)) {
+ $source = d()->environment_settings['install_method']['clone_source'];
+ }
+ else {
+ return drush_set_error('DEVSHOP_ERROR', dt('Install_method"clone_source" was not set.'));
+ }
+
+ $destination = d()->name;
+ $command = "drush provision-sync $source $destination --database --files --update-uri --disable-rollback-backup --updatedb --cache-clear --registry-rebuild";
+ devshop_process($command, NULL, 'Cloning site from ' . d($source)->uri);
+ devshop_process('drush cc drush', NULL, 'Clearing drush caches');
+// devshop_drush_process_cache_clear($destination);
+ }
+
+ // Install Method: import
+ elseif (d()->install_method == 'import') {
+
+
+ // If import source is a mysql URL:
+ $path_or_url = d()->environment_settings['install_method']['import'];
+ $url = parse_url($path_or_url);
+ if ($url['scheme'] == 'mysql') {
+
+ // Dump the file
+ $temp_file_name = tempnam('/tmp', 'devshop_remote_db.sql.');
+ $db_name = ltrim($url['path'], '/');
+ $command = "mysqldump -u{$url['user']} -p{$url['pass']} -h{$url['host']} {$db_name} --result-file={$temp_file_name}";
+ devshop_process($command, NULL, dt('Saving database file...'));
+
+ $file_to_import = $temp_file_name;
+ }
+
+ // If file exists but is not readable, alert the user.
+ elseif (file_exists($path_or_url) && !is_readable($path_or_url)){
+ return drush_set_error('DEVSHOP_IMPORT_ERROR', dt('Unable to import SQL file @path: not readable.'));
+ }
+
+ // If file exists and is readable, set it as the file to import.
+ elseif (file_exists($path_or_url) && is_readable($path_or_url)){
+ $file_to_import = $path_or_url;
+ }
+
+ // Import the file
+ $alias = d()->name;
+ $command = "drush {$alias} sqlq 'SOURCE {$file_to_import}'";
+ devshop_process($command, NULL, dt('Importing database file...'));
+ devshop_process('drush cc drush', NULL, 'Clearing drush caches');
+ devshop_drush_process_cache_clear($alias);
+ devshop_process("drush {$alias} sqlq 'SHOW TABLES'", NULL, dt('Show Tables'));
+ }
+}
+
+/**
+ * Runs drush cache-clear or drush cache-rebuild on the chosen alias.
+ *
+ * @param $alias
+ * A drush alias.
+ */
+function devshop_drush_process_cache_clear($alias) {
+
+ // Clear all caches.
+ if (drush_drupal_major_version(d()->root) == 8) {
+ devshop_process("drush {$alias} cache-rebuild", NULL, dt('Clear all Caches'));
+ }
+ else {
+ devshop_process("drush {$alias} cache-clear all", NULL, dt('Clear all Caches'));
+ }
+}
\ No newline at end of file
diff --git a/modules/devshop/devshop_projects/drush/install.devshop.inc b/modules/devshop/devshop_projects/drush/install.devshop.inc
new file mode 100644
index 000000000..e1e09b454
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/install.devshop.inc
@@ -0,0 +1,289 @@
+ join("\n", drush_shell_exec_output()))));
+ }
+}
+
+function drush_provision_devshop_install_validate($site = NULL) {
+ // set defaults for this whole script
+ // those are settings that are not prompted to the user but still overridable
+ drush_set_default('version', provision_version());
+ drush_set_default('profile', 'devmaster');
+
+ // Get values needed to set other defaults
+ $version = drush_get_option('version');
+ $aegir_root = drush_set_default('aegir_root', drush_server_home());
+ $profile = drush_get_option('profile');
+
+ drush_set_default('root', $aegir_root . '/' . $profile . '-' . $version);
+ drush_set_default('r', drush_get_option('root'));
+ drush_set_default('script_user', devshop_current_user());
+ drush_set_default('web_group', _provision_default_web_group());
+ drush_set_default('http_service_type', 'apache');
+ drush_set_default('http_port', '80');
+ drush_set_default('aegir_db_user', 'root');
+ drush_set_default('aegir_db_port', '3306');
+ drush_set_default('client_name', 'admin');
+ $aegir_db_user = drush_get_option('aegir_db_user');
+
+ // Generate "makefile" message only if there is one set.
+ $root = drush_get_option(array('r', 'root'));
+ if (is_dir($root) && !drush_get_option('makefile', FALSE)) {
+ // Don't assume we know the makefile used to build an existing platform
+ $makefile_msg = '';
+ }
+ else {
+ drush_set_default('makefile', dirname(__FILE__) . '/devshop-dev.make');
+ $makefile_msg = dt("Aegir makefile: !makefile\n", array('!makefile' => drush_get_option('makefile')));
+ }
+
+ // Generate "profile" message only if there is one set.
+ if (!drush_get_option('profile', FALSE)) {
+ $profile_msg = '';
+ }
+ else {
+ $profile_msg = dt("Aegir install profile: !profile\n", array('!profile' => $profile));
+ }
+
+ drush_print("Aegir $version automated install script");
+ drush_print("==============================================================================");
+
+ if (!$site || !drush_get_option('aegir_host', NULL) || !drush_get_option('aegir_db_pass', NULL) || filter_var(drush_get_option('client_email'), FILTER_VALIDATE_EMAIL)) {
+ drush_print("Some settings have not been provided and will now be prompted.
+Don't worry: you will get to review those settings after the final install");
+ }
+ // now we prompt the user for settings if not provided or not sane
+ if (!$site) {
+ $site = drush_prompt(dt("Aegir frontend URL"), get_fqdn());
+ }
+ drush_set_option('site', $site);
+
+ drush_set_default('aegir_host', get_fqdn());
+ drush_set_default('aegir_db_host', 'localhost');
+
+ if (is_null(drush_get_option('aegir_db_pass', NULL))) {
+ // XXX: may not be portable everywhere?
+ system('stty -echo');
+ drush_set_option('aegir_db_pass', drush_prompt(dt('MySQL privileged user ("!root") password', array('!root' => $aegir_db_user))));
+ system('stty echo');
+ print "\n"; // add a newline since the user's didn't print
+ }
+
+ if (drush_get_option('aegir_host') == 'localhost') {
+ $default_email = 'webmaster@example.com';
+ } else {
+ $default_email = 'webmaster@' . drush_get_option('aegir_host');
+ }
+ drush_set_default('client_email', $default_email);
+ while (!filter_var(drush_get_option('client_email'), FILTER_VALIDATE_EMAIL) && !drush_get_context('DRUSH_AFFIRMATIVE')) {
+ drush_set_option('client_email', drush_prompt(dt("Admin user e-mail"), $default_email));
+ }
+
+ drush_print(dt('
+This script will operate the following changes in your system:
+
+1. Create server-level configuration directories
+2. Create the Hostmaster frontend platform
+3. Install the frontend site
+4. Setup the dispatcher (a user cron job)
+
+We are making the following assumptions:
+ * you have read and are following the install instructions at:
+ http://community.aegirproject.org/installing
+ * the FQDN of this machine is valid and resolves
+ * you are executing this script as your "aegir" user
+
+The following settings will be used:
+ Aegir frontend URL: !site
+ Master server FQDN: !fqdn
+ Aegir root: !home
+ Aegir user: !user
+ Web group: !web
+ Web server: !web_server
+ Web server port: !web_server_port
+ Aegir DB host: !db_host
+ Aegir DB user: !db_user
+ Aegir DB password: !db_pass
+ Aegir DB port: !db_port
+ Aegir version: !version
+ Aegir platform path: !root
+ Admin email: !email
+ !makefile !profile',
+ array(
+ '!site' => $site,
+ '!fqdn' => drush_get_option('aegir_host'),
+ '!home' => drush_get_option('aegir_root'),
+ '!user' => drush_get_option('script_user'),
+ '!web' => drush_get_option('web_group'),
+ '!web_server' => drush_get_option('http_service_type'),
+ '!web_server_port' => drush_get_option('http_port'),
+ '!db_host' => drush_get_option('aegir_db_host'),
+ '!db_user' => drush_get_option('aegir_db_user'),
+ '!db_pass' => is_null(drush_get_option('aegir_db_pass', NULL, 'process')) ? '' : '',
+ '!db_port' => drush_get_option('aegir_db_port'),
+ '!version' => drush_get_option('version'),
+ '!root' => $root,
+ '!makefile' => $makefile_msg,
+ '!profile' => $profile_msg,
+ '!email' => drush_get_option('client_email'),
+ )));
+
+ if (!drush_confirm(dt('Do you really want to proceed with the install'))) {
+ return drush_set_error('PROVISION_CANCEL_INSTALL', dt('Installation aborted'));
+ }
+
+ return TRUE;
+}
+
+/**
+ * Drush command to install hostmaster.
+ */
+function drush_provision_devshop_install($site = NULL) {
+ $version = drush_get_option('version');
+ $site = drush_get_option('site', get_fqdn());
+ $aegir_root = drush_get_option('aegir_root');
+ $platform = drush_get_option(array('r', 'root'));
+
+ $aegir_http_host = drush_get_option('aegir_host');
+ $aegir_http_port = drush_get_option('http_port');
+ $aegir_db_user = drush_get_option('aegir_db_user');
+ $aegir_db_pass = drush_get_option('aegir_db_pass');
+ $aegir_db_port = drush_get_option('aegir_db_port');
+ $aegir_db_host = drush_get_option('aegir_db_host');
+
+ $server = '@server_master';
+ $master_context = array(
+ 'context_type' => 'server',
+ // files
+ 'remote_host' => $aegir_http_host,
+ 'aegir_root' => $aegir_root,
+ 'script_user' => drush_get_option('script_user'),
+ // apache or nginx or..
+ 'http_service_type' => drush_get_option('http_service_type'),
+ 'http_port' => $aegir_http_port,
+ 'web_group' => drush_get_option('web_group'),
+ 'master_url' => "http://" . $site,
+ 'db_port' => $aegir_db_port,
+ );
+
+ $master_db = sprintf("mysql://%s:%s@%s:%s", urlencode($aegir_db_user), urlencode($aegir_db_pass), $aegir_db_host, $aegir_db_port);
+ if ($aegir_http_host == $aegir_db_host) {
+ $master_context['db_service_type'] = 'mysql';
+ $master_context['master_db'] = $master_db;
+ $dbserver = $server;
+ } else {
+ $dbserver = '@server_' . $aegir_db_host;
+ $dbserver_context = array(
+ 'remote_host' => $aegir_db_host,
+ 'context_type' => 'server',
+ 'db_service_type' => 'mysql',
+ 'master_db' => $master_db,
+ 'db_port' => $aegir_db_port,
+ );
+ drush_invoke_process('@none', "provision-save", array($dbserver), $dbserver_context);
+ provision_backend_invoke($dbserver, 'provision-verify');
+ }
+ drush_invoke_process('@none', "provision-save", array($server), $master_context);
+ provision_backend_invoke($server, 'provision-verify');
+
+ // exit if an error has occured.
+ if (drush_get_error()) {
+ return false;
+ }
+
+ if (drush_get_option('backend-only')) {
+ return;
+ }
+
+ $platform_name = '@platform_hostmaster';
+ drush_invoke_process('@none', "provision-save", array($platform_name), array(
+ 'context_type' => 'platform',
+ 'server' => $server,
+ 'web_server' => $server,
+ 'root' => $platform,
+ 'makefile' => drush_get_option('makefile'),
+ ));
+ // propagate working-copy args downward
+ $options = array();
+ if (drush_get_option('working-copy')) {
+ $options['working-copy'] = 1;
+ }
+ provision_backend_invoke($platform_name, 'provision-verify', array(), $options);
+
+ // exit if an error has occured.
+ if (drush_get_error()) {
+ return false;
+ }
+
+ drush_set_default('profile', 'devmaster');
+ $profile = drush_get_option('profile');
+
+ $site_name = '@hostmaster';
+ drush_invoke_process('@none', "provision-save", array($site_name), array(
+ 'context_type' => 'site',
+ 'platform' => $platform_name,
+ 'db_server' => $dbserver,
+ 'uri' => $site,
+ 'client_name' => drush_get_option('client_name'),
+ 'profile' => $profile,
+ ));
+ $data = provision_backend_invoke($site_name, 'provision-install', array(), array('client_email' => drush_get_option('client_email')));
+ provision_backend_invoke($site_name, 'provision-verify');
+
+ // exit if an error has occured.
+ if (drush_get_error()) {
+ return false;
+ }
+
+
+ drush_print(dt("Initializing the hosting system"));
+ drush_invoke_process('@none', 'cache-clear', array('drush'));
+ provision_backend_invoke($site_name, 'hosting-setup');
+
+ drush_print("");
+ drush_print("==============================================================================");
+ drush_print("");
+ drush_print("");
+ drush_print(dt("Congratulations, Aegir has now been installed."));
+ drush_print("");
+ drush_print(dt("You should now log in to the Aegir frontend by opening the following link in your web browser:"));
+ drush_print("");
+ drush_print($data['context']['login_link']);
+ drush_print("");
+ drush_print("");
+ drush_print("==============================================================================");
+ drush_print("");
+}
+
+/**
+ * Implements drush_hook_post_hostmaster_install().
+ */
+function drush_provision_post_devshop_install() {
+ $backend_only = drush_get_option('backend-only');
+ if (empty($backend_only)) {
+ drush_invoke_process('@hostmaster', 'cache-clear', array('drush'));
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/pull.devshop.provision.inc b/modules/devshop/devshop_projects/drush/pull.devshop.provision.inc
new file mode 100644
index 000000000..2d2e7fdc4
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/pull.devshop.provision.inc
@@ -0,0 +1,123 @@
+project_name;
+ $platform_alias = '@' . implode('_', array('platform', $project_name, $platform_name));
+ $platform = d($platform_alias);
+
+ $site_alias = '@' . $platform_name . '.' . d()->project['base_url'];
+ $site = d($site_alias);
+
+ // Find repo path
+ $repo_path = $platform->root;
+
+ // Ensure it's a git repo.
+ provision_git_is_repo($repo_path);
+
+ // Determine revert setting from project settings (or if set as option, always)
+ $reset = drush_get_option('reset') ? drush_get_option('reset') : FALSE;
+
+ // If reset is true, do a git reset --hard first.
+ if ($reset) {
+ drush_shell_cd_and_exec($repo_path, 'git reset --hard');
+ drush_log(dt('[DEVSHOP] Git repository reset.'), 'ok');
+ }
+
+ // Pull latest version of site
+ // Execute git pull --rebase
+ if (drush_shell_cd_and_exec($repo_path, 'git pull --rebase')) {
+ drush_log(dt('[DEVSHOP] Git repository pulled.', array('!path' => $repo_path)), 'ok');
+ $output = drush_shell_exec_output();
+ drush_log(implode("\n", drush_shell_exec_output()), 'ok');
+ }
+ else {
+ drush_set_error('DRUSH_PROVISION_GIT_PULL_FAILED', dt("Git pull failed in !path.\nThe specific errors are below:\n!errors", array('!path' => $repo_path, '!errors' => implode("\n", drush_shell_exec_output()))));
+ continue;
+ }
+
+ // If no new files were detected... and force is false then skip out.
+ if (count($output) == 1 && !$force) {
+ drush_log('[DEVSHOP] No changes detected. Nothing else needs to be done', 'ok');
+ continue;
+ }
+
+ // Verify the platform.
+ provision_backend_invoke($platform_alias, 'provision-verify');
+
+ // update db, unless option is false.
+ if ($update){
+ drush_log(dt('[DEVSHOP] Updating database...'), 'ok');
+ provision_backend_invoke($site_alias, 'updb');
+ }
+ else {
+ drush_log(dt('[DEVSHOP] Skipped updating database...'), 'ok');
+ }
+
+ // Revert All Features, unless option is false
+ if ($revert){
+ drush_log(dt('[DEVSHOP] Reverting all features...'), 'ok');
+ provision_backend_invoke($site_alias, 'features-revert-all');
+ }
+ else {
+ drush_log(dt('[DEVSHOP] Skipped reverting all features...'), 'ok');
+ }
+
+ // Clear the whole cache, unless option is false
+ // Seriously, lets do this twice. Go Drupal!
+ if ($cache){
+ drush_log(dt('[DEVSHOP] Clearing all caches...'), 'ok');
+ provision_backend_invoke($site_alias, 'cc all');
+ provision_backend_invoke($site_alias, 'cc all');
+ }
+ else {
+ drush_log(dt('[DEVSHOP] Skipped clearing all caches...'), 'ok');
+ }
+ }
+}
+
diff --git a/modules/devshop/devshop_projects/drush/test.devshop.provision.inc b/modules/devshop/devshop_projects/drush/test.devshop.provision.inc
new file mode 100644
index 000000000..e69de29bb
diff --git a/modules/devshop/devshop_projects/drush/test.provision.inc b/modules/devshop/devshop_projects/drush/test.provision.inc
new file mode 100644
index 000000000..84713906e
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/test.provision.inc
@@ -0,0 +1,184 @@
+project;
+ $project = (object) d("@project_{$project_name}")->project;
+ $environment = (object) $project->environments[d()->environment];
+
+ drush_log(dt('Provision DevShop Run Tests started...'), 'status');
+
+ // Get tests to run
+ if (!empty($project->settings['testing']['test_type'])) {
+ $type = $project->settings['testing']['test_type'];
+ }
+ else {
+ $type = drush_get_option('test-type', NULL);
+ }
+
+ if ($project->settings['testing']['tests_to_run'] === NULL) {
+ $tests = array();
+ }
+ else {
+ $tests = array_filter($project->settings['testing']['tests_to_run']);
+ }
+ $tests_to_run = drush_get_option('tests-to-run', $tests);
+
+ // Run Simpletest
+ if ($type == 'simpletest') {
+ drush_log(dt("Running $type tests $tests_to_run"), 'ok');
+ provision_backend_invoke('@self', 'en simpletest');
+ provision_backend_invoke('@self', 'test-run', array($tests_to_run));
+ }
+ elseif ($type == 'behat') {
+ // Get paths from options or site context.
+ $repo_path = d()->platform->repo_path? d()->platform->repo_path: d()->platform->root;
+ $behat_folder_path = drush_get_option('behat-folder-path', $project->settings['testing']['behat_folder_path']);
+
+ // If no repo at that path, error.
+ if (!file_exists($repo_path)){
+ return drush_set_error('DEVSHOP_MISSING_FILE', dt('repo_path does not exist.'));
+ }
+ // If no file at behat bin path, error.
+ $tests_folder = $repo_path . '/' .$behat_folder_path;
+ $yml_path = $repo_path . '/' .$behat_folder_path . '/behat.yml';
+ if (!file_exists($yml_path)){
+
+ $message = << Testing.
+ 2. Copy the files from https://github.com/opendevshop/devmaster/tree/1.x/modules/devshop/devshop_testing/tests_example
+ 3. Write more tests!
+
+TXT;
+
+
+ drush_log(dt($message, array('!path' => $behat_folder_path)), 'error');
+ return drush_set_error('DEVSHOP_MISSING_FILE', dt('Your project is not yet ready to run Behat tests. Please follow the instructions and try again.'));
+ }
+
+ // Prepare path and command
+ $full_behat_folder_path = $repo_path . '/' . $behat_folder_path;
+ $full_behat_bin_path = $repo_path . '/' . $behat_folder_path . '/bin/behat';
+
+ // Load the behat.yml from behat_folder_path.
+ if (file_exists($full_behat_folder_path . '/behat.yml')) {
+ $behat_yml = file_get_contents($full_behat_folder_path . '/behat.yml');
+ }
+ elseif (file_exists($full_behat_folder_path . '/config/behat.yml')) {
+ $behat_yml = file_get_contents($full_behat_folder_path . '/config/behat.yml');
+ }
+ else {
+ return drush_set_error('DEVSHOP_MISSING_FILE', dt('behat.yml file not found in behat_folder_path: ') . $behat_folder_path);
+ }
+
+ // Run composer install.
+ devshop_process('composer install', $full_behat_folder_path, 'DevShop Testing');
+
+ // Write custom behat.yml to temporary folder.
+ $environment_name = $environment->name;
+ $alias = d()->name;
+ $root = d()->root;
+
+ $username = d()->http_basic_auth_username;
+ $password = d()->http_basic_auth_password;
+ $uri = d()->uri;
+ $url = d()->ssl_enabled?
+ "https://$uri":
+ "http://$uri";
+
+ if (!empty($username)) {
+ $url = d()->ssl_enabled?
+ "https://$username:$password@$uri":
+ "http://$username:$password@$uri";
+ }
+
+ $behat_yml = << $behat_yml_file,
+ )), 'ok');
+
+ // Run behat tests for each feature.
+ $no_errors = TRUE;
+ $test_result = '';
+
+ // Fill an empty item if empty so we run all the tests.
+ if (empty($tests_to_run)) {
+ $tests_to_run[] = '';
+ }
+
+ // Foreach test to run...
+ $i = 0;
+ foreach ($tests_to_run as $feature) {
+ $i++;
+
+ // Check for path.
+ if (substr($feature, 0, 1) !== '/') {
+ $feature = '/'.$feature;
+ }
+ $feature_path = empty($feature) ? '' : "features{$feature}";
+
+ // Create Command
+ // The extra profile is used so we can dynamically set the URL and drush alias of the behat.yml.
+ // the "colors" option is to force it to output ANSI colors. "format-settings" "expand: true" is so
+ // Behat will output all of the steps when using "Scenario Outlines".
+ $cmd = "$full_behat_bin_path $feature_path --config $behat_yml_file --profile devshop --colors --strict --format-settings='{\"expand\": true}'";
+
+ // Run behat command
+ devshop_process($cmd, $full_behat_folder_path, 'DevShop Testing', array(), TRUE, 'Test Run Failed');
+ }
+ }
+}
+
diff --git a/modules/devshop/devshop_projects/drush/vendor/autoload.php b/modules/devshop/devshop_projects/drush/vendor/autoload.php
new file mode 100644
index 000000000..dd11eed60
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/autoload.php
@@ -0,0 +1,7 @@
+
+ * Jordi Boggiano
+ *
+ * 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
+ * @author Jordi Boggiano
+ * @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;
+
+ 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;
+ }
+
+ /**
+ * 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)
+ {
+ // work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731
+ if ('\\' == $class[0]) {
+ $class = substr($class, 1);
+ }
+
+ // class map lookup
+ if (isset($this->classMap[$class])) {
+ return $this->classMap[$class];
+ }
+ if ($this->classMapAuthoritative) {
+ return false;
+ }
+
+ $file = $this->findFileWithExtension($class, '.php');
+
+ // Search for Hack files if we are running on HHVM
+ if ($file === null && defined('HHVM_VERSION')) {
+ $file = $this->findFileWithExtension($class, '.hh');
+ }
+
+ if ($file === null) {
+ // Remember that this class does not exist.
+ return $this->classMap[$class] = false;
+ }
+
+ 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;
+ }
+ }
+}
+
+/**
+ * Scope isolated include.
+ *
+ * Prevents access to $this/self from included files.
+ */
+function includeFile($file)
+{
+ include $file;
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/composer/LICENSE b/modules/devshop/devshop_projects/drush/vendor/composer/LICENSE
new file mode 100644
index 000000000..c8d57af8b
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/composer/LICENSE
@@ -0,0 +1,21 @@
+
+Copyright (c) 2015 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.
+
diff --git a/modules/devshop/devshop_projects/drush/vendor/composer/autoload_classmap.php b/modules/devshop/devshop_projects/drush/vendor/composer/autoload_classmap.php
new file mode 100644
index 000000000..7a91153b0
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/composer/autoload_classmap.php
@@ -0,0 +1,9 @@
+ array($vendorDir . '/cpliakas/git-wrapper/src'),
+);
diff --git a/modules/devshop/devshop_projects/drush/vendor/composer/autoload_psr4.php b/modules/devshop/devshop_projects/drush/vendor/composer/autoload_psr4.php
new file mode 100644
index 000000000..a9a7dea0a
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/composer/autoload_psr4.php
@@ -0,0 +1,12 @@
+ array($vendorDir . '/symfony/process'),
+ 'Symfony\\Component\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher'),
+ 'Symfony\\Component\\ClassLoader\\' => array($vendorDir . '/symfony/class-loader'),
+);
diff --git a/modules/devshop/devshop_projects/drush/vendor/composer/autoload_real.php b/modules/devshop/devshop_projects/drush/vendor/composer/autoload_real.php
new file mode 100644
index 000000000..f8acedade
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/composer/autoload_real.php
@@ -0,0 +1,45 @@
+ $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);
+
+ return $loader;
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/composer/installed.json b/modules/devshop/devshop_projects/drush/vendor/composer/installed.json
new file mode 100644
index 000000000..f21f15b0d
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/composer/installed.json
@@ -0,0 +1,230 @@
+[
+ {
+ "name": "symfony/process",
+ "version": "v2.8.0",
+ "version_normalized": "2.8.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/process.git",
+ "reference": "1b988a88e3551102f3c2d9e1d47a18c3a78d6312"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/process/zipball/1b988a88e3551102f3c2d9e1d47a18c3a78d6312",
+ "reference": "1b988a88e3551102f3c2d9e1d47a18c3a78d6312",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.9"
+ },
+ "time": "2015-11-30 12:35:10",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.8-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Process\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Process Component",
+ "homepage": "https://symfony.com"
+ },
+ {
+ "name": "symfony/event-dispatcher",
+ "version": "v3.0.0",
+ "version_normalized": "3.0.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/event-dispatcher.git",
+ "reference": "d36355e026905fa5229e1ed7b4e9eda2e67adfcf"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/d36355e026905fa5229e1ed7b4e9eda2e67adfcf",
+ "reference": "d36355e026905fa5229e1ed7b4e9eda2e67adfcf",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.5.9"
+ },
+ "require-dev": {
+ "psr/log": "~1.0",
+ "symfony/config": "~2.8|~3.0",
+ "symfony/dependency-injection": "~2.8|~3.0",
+ "symfony/expression-language": "~2.8|~3.0",
+ "symfony/stopwatch": "~2.8|~3.0"
+ },
+ "suggest": {
+ "symfony/dependency-injection": "",
+ "symfony/http-kernel": ""
+ },
+ "time": "2015-10-30 23:35:59",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\EventDispatcher\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony EventDispatcher Component",
+ "homepage": "https://symfony.com"
+ },
+ {
+ "name": "cpliakas/git-wrapper",
+ "version": "1.6.1",
+ "version_normalized": "1.6.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/cpliakas/git-wrapper.git",
+ "reference": "5b89e9c5b4598c110e2d93d1245b4e26df3855a5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/cpliakas/git-wrapper/zipball/5b89e9c5b4598c110e2d93d1245b4e26df3855a5",
+ "reference": "5b89e9c5b4598c110e2d93d1245b4e26df3855a5",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0",
+ "symfony/event-dispatcher": "~2.3|~3.0",
+ "symfony/process": "~2.3|~3.0"
+ },
+ "require-dev": {
+ "pdepend/pdepend": "~1.0",
+ "phploc/phploc": "~2.0",
+ "phpmd/phpmd": "~1.0",
+ "phpunit/phpunit": "~3.0",
+ "psr/log": "~1.0",
+ "scrutinizer/ocular": "~1.0",
+ "sebastian/phpcpd": "~2.0",
+ "symfony/filesystem": "~2.0"
+ },
+ "suggest": {
+ "monolog/monolog": "Enables logging of executed git commands"
+ },
+ "time": "2015-11-11 15:45:43",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-0": {
+ "GitWrapper": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Chris Pliakas",
+ "email": "opensource@chrispliakas.com"
+ }
+ ],
+ "description": "A PHP wrapper around the Git command line utility.",
+ "homepage": "https://github.com/cpliakas/git-wrapper",
+ "keywords": [
+ "git"
+ ]
+ },
+ {
+ "name": "symfony/class-loader",
+ "version": "v3.0.2",
+ "version_normalized": "3.0.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/class-loader.git",
+ "reference": "92e7cf1af2bc1695daabb4ac972db169606e9030"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/class-loader/zipball/92e7cf1af2bc1695daabb4ac972db169606e9030",
+ "reference": "92e7cf1af2bc1695daabb4ac972db169606e9030",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.5.9"
+ },
+ "require-dev": {
+ "symfony/finder": "~2.8|~3.0",
+ "symfony/polyfill-apcu": "~1.1"
+ },
+ "suggest": {
+ "symfony/polyfill-apcu": "For using ApcClassLoader on HHVM"
+ },
+ "time": "2016-02-03 09:33:23",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\ClassLoader\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony ClassLoader Component",
+ "homepage": "https://symfony.com"
+ }
+]
diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/.editorconfig b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/.editorconfig
new file mode 100644
index 000000000..153cf3ef5
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/.editorconfig
@@ -0,0 +1,10 @@
+; top-most EditorConfig file
+root = true
+
+; Unix-style newlines
+[*]
+end_of_line = LF
+
+[*.php]
+indent_style = space
+indent_size = 4
diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/.gitignore b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/.gitignore
new file mode 100644
index 000000000..a13670229
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/.gitignore
@@ -0,0 +1,4 @@
+build/
+composer.lock
+nbproject
+vendor/
diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/.scrutinizer.yml b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/.scrutinizer.yml
new file mode 100644
index 000000000..543119899
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/.scrutinizer.yml
@@ -0,0 +1,22 @@
+checks:
+ php:
+ code_rating: true
+ duplication: true
+
+filter:
+ paths:
+ - src/*
+
+tools:
+ php_sim: true
+ php_pdepend: true
+ php_analyzer: true
+ php_mess_detector:
+ config:
+ ruleset: ./phpmd.xml
+ php_cs_fixer:
+ config: { level: psr2 }
+ external_code_coverage:
+ runs: 4
+ timeout: 600
+
diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/.travis.yml b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/.travis.yml
new file mode 100644
index 000000000..47a99c84e
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/.travis.yml
@@ -0,0 +1,14 @@
+language: php
+
+php:
+ - "5.6"
+ - "5.5"
+ - "5.4"
+ - "5.3"
+
+before_script:
+ - composer install --prefer-dist --dev
+
+after_script:
+ - vendor/bin/ocular code-coverage:upload --format=php-clover build/logs/clover.xml
+
diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/LICENSE b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/LICENSE
new file mode 100644
index 000000000..5ef03e0a1
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2013 Acquia, Inc.
+
+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.
diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/README.md b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/README.md
new file mode 100644
index 000000000..71904b66f
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/README.md
@@ -0,0 +1,186 @@
+# Overview
+
+[![Build Status](https://travis-ci.org/cpliakas/git-wrapper.svg?branch=master)](https://travis-ci.org/cpliakas/git-wrapper)
+[![Code Coverage](https://scrutinizer-ci.com/g/cpliakas/git-wrapper/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/cpliakas/git-wrapper/?branch=master)
+[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/cpliakas/git-wrapper/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/cpliakas/git-wrapper/?branch=master)
+[![Total Downloads](https://img.shields.io/packagist/dt/cpliakas/git-wrapper.svg)](https://packagist.org/packages/cpliakas/git-wrapper)
+[![Latest Stable Version](https://img.shields.io/packagist/v/cpliakas/git-wrapper.svg)](https://packagist.org/packages/cpliakas/git-wrapper)
+[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/cpliakas/git-wrapper/master/LICENSE)
+
+This library is a PHP wrapper around the Git command line tool.
+
+Its purpose is to provide a readable API that abstracts some of the challenges
+of executing Git commands from within a PHP process. Specifically, this library
+builds upon the Symfony framework's Process component to execute the Git command
+in a way that works across platforms and uses the best-in-breed techniques
+available to PHP. This library also provides an SSH wrapper script and API
+method for developers to easily specify a private key other than one of the
+defaults by using the technique in [this thread on StackOverflow](http://stackoverflow.com/a/3500308/870667).
+Finally, various commands are expected to be executed in the directory
+containing the working copy. Although this a fairly simple challenge to
+overcome, the library handles this transparently so the developer doesn't have
+to think about it.
+
+## Usage
+
+```php
+use GitWrapper\GitWrapper;
+
+// Initialize the library. If the path to the Git binary is not passed as
+// the first argument when instantiating GitWrapper, it is auto-discovered.
+require_once 'vendor/autoload.php';
+$wrapper = new GitWrapper();
+
+// Optionally specify a private key other than one of the defaults.
+$wrapper->setPrivateKey('/path/to/private/key');
+
+// Clone a repo into `/path/to/working/copy`, get a working copy object.
+$git = $wrapper->clone('git://github.com/cpliakas/git-wrapper.git', '/path/to/working/copy');
+
+// Create a file in the working copy.
+touch('/path/to/working/copy/text.txt');
+
+// Add it, commit it, and push the change.
+$git
+ ->add('test.txt')
+ ->commit('Added the test.txt file as per the examples.')
+ ->push();
+
+// Render the output.
+print $git->getOutput();
+
+// Stream output of subsequent Git commands in real time to STDOUT and STDERR.
+$wrapper->streamOutput();
+
+// Execute an arbitrary git command.
+// The following is synonymous with `git config -l`
+$wrapper->git('config -l');
+```
+
+All command methods adhere to the following paradigm:
+
+```php
+$git->command($arg1, $arg2, ..., $options);
+```
+
+Replace `command` with the Git command being executed, e.g. `checkout`, `push`,
+etc. The `$arg*` parameters are a variable number of arguments as they would be
+passed to the Git command line tool. `$options` is an optional array of command
+line options in the following format:
+
+```php
+$options = array(
+ 'verbose' => true, // Passes the "--verbose" flag.
+ 't' => 'my-branch', // Passes the "-t my-branch" option.
+);
+```
+
+#### Logging
+
+Use the logger listener with [PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md)
+compatible loggers such as [Monolog](https://github.com/Seldaek/monolog) to log
+commands that are executed.
+
+```php
+
+use GitWrapper\Event\GitLoggerListener;
+use Monolog\Logger;
+use Monolog\Handler\StreamHandler;
+
+// Log to a file named "git.log"
+$log = new Logger('git');
+$log->pushHandler(new StreamHandler('git.log', Logger::DEBUG));
+
+// Instantiate the listener, add the logger to it, and register it.
+$listener = new GitLoggerListener($log);
+$wrapper->addLoggerListener($listener);
+
+$git = $wrapper->clone('git://github.com/cpliakas/git-wrapper.git', '/path/to/working/copy');
+
+// The "git.log" file now has info about the command that was executed above.
+
+```
+
+## Installation
+
+Git Wrapper can be installed with [Composer](http://getcomposer.org) by adding
+the library as a dependency to your composer.json file.
+
+```json
+{
+ "require": {
+ "cpliakas/git-wrapper": "~1.0"
+ }
+}
+```
+
+Please refer to [Composer's documentation](https://github.com/composer/composer/blob/master/doc/00-intro.md#introduction)
+for installation and usage instructions.
+
+## Gotchas
+
+There are a few "gotchas" that are out of scope for this library to solve but
+might prevent a successful implementation of running Git via PHP. The following
+is an incomplete list of challenges that are often encountered when executing
+Git from PHP.
+
+### Missing HOME Environment Variable
+
+Sometimes the `HOME` environment variable is not set in the Git process that is
+spawned by PHP. This will cause many Git operations to fail. It is advisable to
+set the `HOME` environment variable to a path outside of the document root that
+the web server has write access to. Note that this environment variable is only
+set for the process running Git and NOT the PHP process that is spawns it.
+
+```php
+$wrapper->setEnvVar('HOME', '/path/to/a/private/writable/dir');
+```
+
+It is important that the storage is persistent as the ~/.gitconfig file will be
+written to this location. See the following "gotcha" for why this is important.
+
+### Missing Identity And Configurations
+
+Many repositories require that a name and email address are specified. This data
+is set by running `git config [name] [value]` on the command line, and the
+configurations are usually stored in the `~/.gitconfig file`. When executing Git
+via PHP, however, the process might have a different home directory than the
+user who normally runs git via the command line. Therefore no identity is sent
+to the repository, and it will likely throw an error.
+
+```php
+// Set configuration options globally.
+$wrapper->git('config --global user.name "User name"');
+$wrapper->git('config --global user.email user@example.com');
+
+// Set configuration options per repository.
+$git
+ ->config('user.name', 'User name')
+ ->config('user.email', 'user@example.com');
+```
+
+### Commits To Repositories With No Changes
+
+Running `git commit` on a repository with no changes returns no output but exits
+with a status of 1. Therefore the library will throw a `GitException` since it
+correctly detected an error. It is advisable to check whether a working copy has
+any changes prior to running the commit operation in order to prevent unwanted
+exceptions.
+
+```php
+if ($git->hasChanges()) {
+ $git->commit('Committed the changes.');
+}
+```
+
+### Permissions Of The GIT_SSH Wrapper Script
+
+On checkout, the bin/git-ssh-wrapper.sh script should be executable. If it is
+not, git commands with fail if a non-default private key is specified.
+
+ $> chmod 0755 ./bin/git-ssh-wrapper.sh
+
+## For Developers
+
+Refer to [PHP Project Starter's documentation](https://github.com/cpliakas/php-project-starter#using-apache-ant)
+for the Apache Ant targets supported by this project.
diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/bin/git-ssh-wrapper.sh b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/bin/git-ssh-wrapper.sh
new file mode 100755
index 000000000..7782562eb
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/bin/git-ssh-wrapper.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+ssh -i $GIT_SSH_KEY -p $GIT_SSH_PORT -o StrictHostKeyChecking=no -o IdentitiesOnly=yes "$@"
diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/build.xml b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/build.xml
new file mode 100644
index 000000000..2e67de86a
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/build.xml
@@ -0,0 +1,118 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/composer.json b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/composer.json
new file mode 100644
index 000000000..09508032a
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/composer.json
@@ -0,0 +1,37 @@
+{
+ "name": "cpliakas/git-wrapper",
+ "type": "library",
+ "description": "A PHP wrapper around the Git command line utility.",
+ "keywords": ["git"],
+ "homepage": "https://github.com/cpliakas/git-wrapper",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Chris Pliakas",
+ "email": "opensource@chrispliakas.com"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.0",
+ "symfony/process": "~2.3|~3.0",
+ "symfony/event-dispatcher": "~2.3|~3.0"
+ },
+ "require-dev": {
+ "pdepend/pdepend": "~1.0",
+ "phploc/phploc": "~2.0",
+ "phpmd/phpmd": "~1.0",
+ "phpunit/phpunit": "~3.0",
+ "psr/log": "~1.0",
+ "scrutinizer/ocular": "~1.0",
+ "sebastian/phpcpd": "~2.0",
+ "symfony/filesystem": "~2.0"
+ },
+ "suggest": {
+ "monolog/monolog": "Enables logging of executed git commands"
+ },
+ "autoload": {
+ "psr-0": {
+ "GitWrapper": "src/"
+ }
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/phpmd.xml b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/phpmd.xml
new file mode 100644
index 000000000..ac63555ed
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/phpmd.xml
@@ -0,0 +1,21 @@
+
+
+
+ PHP Project Starter Rulset: Adopted From Jenkins for Symfony 2
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/phpunit.xml b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/phpunit.xml
new file mode 100644
index 000000000..a030f91eb
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/phpunit.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+ test
+
+
+
+
+
+ src
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/src/GitWrapper/Event/GitEvent.php b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/src/GitWrapper/Event/GitEvent.php
new file mode 100644
index 000000000..5c61c2306
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/src/GitWrapper/Event/GitEvent.php
@@ -0,0 +1,82 @@
+wrapper = $wrapper;
+ $this->process = $process;
+ $this->command = $command;
+ }
+
+ /**
+ * Gets the GitWrapper object that likely instantiated this class.
+ *
+ * @return \GitWrapper\GitWrapper
+ */
+ public function getWrapper()
+ {
+ return $this->wrapper;
+ }
+
+ /**
+ * Gets the Process object being run.
+ *
+ * @return \Symfony\Component\Process\Process
+ */
+ public function getProcess()
+ {
+ return $this->process;
+ }
+
+ /**
+ * Gets the GitCommand object being executed.
+ *
+ * @return \GitWrapper\GitCommand
+ */
+ public function getCommand()
+ {
+ return $this->command;
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/src/GitWrapper/Event/GitEvents.php b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/src/GitWrapper/Event/GitEvents.php
new file mode 100644
index 000000000..6e477784c
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/src/GitWrapper/Event/GitEvents.php
@@ -0,0 +1,53 @@
+ LogLevel::INFO,
+ GitEvents::GIT_OUTPUT => LogLevel::DEBUG,
+ GitEvents::GIT_SUCCESS => LogLevel::INFO,
+ GitEvents::GIT_ERROR => LogLevel::ERROR,
+ GitEvents::GIT_BYPASS => LogLevel::INFO,
+ );
+
+ /**
+ * @param \Psr\Log\LoggerInterface $logger
+ */
+ public function __construct(LoggerInterface $logger)
+ {
+ $this->setLogger($logger);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function setLogger(LoggerInterface $logger)
+ {
+ $this->logger = $logger;
+ }
+
+ /**
+ * @return \Psr\Log\LoggerInterface
+ */
+ public function getLogger()
+ {
+ return $this->logger;
+ }
+
+ /**
+ * Sets the log level mapping for an event.
+ *
+ * @param string $eventName
+ * @param string|false $logLevel
+ *
+ * @return \GitWrapper\Event\GitLoggerListener
+ */
+ public function setLogLevelMapping($eventName, $logLevel)
+ {
+ $this->logLevelMappings[$eventName] = $logLevel;
+ return $this;
+ }
+
+ /**
+ * Returns the log level mapping for an event.
+ *
+ * @param string $eventName
+ *
+ * @return string
+ *
+ * @throws \DomainException
+ */
+ public function getLogLevelMapping($eventName)
+ {
+ if (!isset($this->logLevelMappings[$eventName])) {
+ throw new \DomainException('Unknown event: ' . $eventName);
+ }
+
+ return $this->logLevelMappings[$eventName];
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public static function getSubscribedEvents()
+ {
+ return array(
+ GitEvents::GIT_PREPARE => array('onPrepare', 0),
+ GitEvents::GIT_OUTPUT => array('handleOutput', 0),
+ GitEvents::GIT_SUCCESS => array('onSuccess', 0),
+ GitEvents::GIT_ERROR => array('onError', 0),
+ GitEvents::GIT_BYPASS => array('onBypass', 0),
+ );
+ }
+
+ /**
+ * Adds a logg message using the level defined in the mappings.
+ *
+ * @param \GitWrapper\Event\GitEvent $event
+ * @param string $message
+ * @param array $context
+ *
+ * @throws \DomainException
+ */
+ public function log(GitEvent $event, $message, array $context = array())
+ {
+ $method = $this->getLogLevelMapping($event->getName());
+ if ($method !== false) {
+ $context += array('command' => $event->getProcess()->getCommandLine());
+ $this->logger->$method($message, $context);
+ }
+ }
+
+ public function onPrepare(GitEvent $event)
+ {
+ $this->log($event, 'Git command preparing to run');
+ }
+
+ public function handleOutput(GitOutputEvent $event)
+ {
+ $context = array('error' => $event->isError() ? true : false);
+ $this->log($event, $event->getBuffer(), $context);
+ }
+
+ public function onSuccess(GitEvent $event)
+ {
+ $this->log($event, 'Git command successfully run');
+ }
+
+ public function onError(GitEvent $event)
+ {
+ $this->log($event, 'Error running Git command');
+ }
+
+ public function onBypass(GitEvent $event)
+ {
+ $this->log($event, 'Git command bypassed');
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/src/GitWrapper/Event/GitOutputEvent.php b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/src/GitWrapper/Event/GitOutputEvent.php
new file mode 100644
index 000000000..d1eaa84a9
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/src/GitWrapper/Event/GitOutputEvent.php
@@ -0,0 +1,64 @@
+type = $type;
+ $this->buffer = $buffer;
+ }
+
+ /**
+ * @return string
+ */
+ public function getType()
+ {
+ return $this->type;
+ }
+
+ /**
+ * @return string
+ */
+ public function getBuffer()
+ {
+ return $this->buffer;
+ }
+
+ /**
+ * Tests wheter the buffer was captured from STDERR.
+ */
+ public function isError()
+ {
+ return (Process::ERR == $this->type);
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/src/GitWrapper/Event/GitOutputListenerInterface.php b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/src/GitWrapper/Event/GitOutputListenerInterface.php
new file mode 100644
index 000000000..f8c85c1ce
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/src/GitWrapper/Event/GitOutputListenerInterface.php
@@ -0,0 +1,11 @@
+isError() ? STDERR : STDOUT;
+ fputs($handler, $event->getBuffer());
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/src/GitWrapper/GitBranches.php b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/src/GitWrapper/GitBranches.php
new file mode 100644
index 000000000..9b27d5b3e
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/src/GitWrapper/GitBranches.php
@@ -0,0 +1,105 @@
+git = clone $git;
+ $output = (string) $git->branch(array('a' => true));
+ }
+
+ /**
+ * Fetches the branches via the `git branch` command.
+ *
+ * @param boolean $onlyRemote
+ * Whether to fetch only remote branches, defaults to false which returns
+ * all branches.
+ *
+ * @return array
+ */
+ public function fetchBranches($onlyRemote = false)
+ {
+ $this->git->clearOutput();
+ $options = ($onlyRemote) ? array('r' => true) : array('a' => true);
+ $output = (string) $this->git->branch($options);
+ $branches = preg_split("/\r\n|\n|\r/", rtrim($output));
+ return array_map(array($this, 'trimBranch'), $branches);
+ }
+
+ /**
+ * Strips unwanted characters from the branch.
+ *
+ * @param string $branch
+ * The raw branch returned in the output of the Git command.
+ *
+ * @return string
+ * The processed branch name.
+ */
+ public function trimBranch($branch)
+ {
+ return ltrim($branch, ' *');
+ }
+
+ /**
+ * Implements \IteratorAggregate::getIterator().
+ */
+ public function getIterator()
+ {
+ $branches = $this->all();
+ return new \ArrayIterator($branches);
+ }
+
+ /**
+ * Returns all branches.
+ *
+ * @return array
+ */
+ public function all()
+ {
+ return $this->fetchBranches();
+ }
+
+ /**
+ * Returns only remote branches.
+ *
+ * @return array
+ */
+ public function remote()
+ {
+ return $this->fetchBranches(true);
+ }
+
+ /**
+ * Returns currently active branch (HEAD) of the working copy.
+ *
+ * @return string
+ */
+ public function head()
+ {
+ return trim((string) $this->git->run(array('rev-parse --abbrev-ref HEAD')));
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/src/GitWrapper/GitCommand.php b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/src/GitWrapper/GitCommand.php
new file mode 100644
index 000000000..05719e607
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/src/GitWrapper/GitCommand.php
@@ -0,0 +1,298 @@
+command = array_shift($args);
+
+ // If the last element is an array, set it as the options.
+ $options = end($args);
+ if (is_array($options)) {
+ $this->setOptions($options);
+ array_pop($args);
+ }
+
+ // Pass all other method arguments as the Git command arguments.
+ foreach ($args as $arg) {
+ $this->addArgument($arg);
+ }
+ }
+ }
+
+ /**
+ * Constructs a GitCommand object.
+ *
+ * Accepts a variable number of arguments to model the arguments passed to
+ * the Git command line utility. If the last argument is an array, it is
+ * passed as the command options.
+ *
+ * @param string $command
+ * The Git command being run, e.g. "clone", "commit", etc.
+ * @param string ...
+ * Zero or more arguments passed to the Git command.
+ * @param array $options
+ * An optional array of arguments to pass to the command.
+ *
+ * @return \GitWrapper\GitCommand
+ */
+ public static function getInstance()
+ {
+ $args = func_get_args();
+ return new static($args);
+ }
+
+ /**
+ * Returns Git command being run, e.g. "clone", "commit", etc.
+ *
+ * @return string
+ */
+ public function getCommand()
+ {
+ return $this->command;
+ }
+
+ /**
+ * Sets the path to the directory containing the working copy.
+ *
+ * @param string $directory
+ * The path to the directory containing the working copy.
+ *
+ * @return \GitWrapper\GitCommand
+ */
+ public function setDirectory($directory)
+ {
+ $this->directory = $directory;
+ return $this;
+ }
+
+ /**
+ * Gets the path to the directory containing the working copy.
+ *
+ * @return string|null
+ * The path, null if no path is set.
+ */
+ public function getDirectory()
+ {
+ return $this->directory;
+ }
+
+ /**
+ * A boolean flagging whether to skip running the command.
+ *
+ * @param boolean $bypass
+ * Whether to bypass execution of the command. The parameter defaults to
+ * true for code readability, however the default behavior of this class
+ * is to run the command.
+ *
+ * @return \GitWrapper\GitCommand
+ */
+ public function bypass($bypass = true)
+ {
+ $this->bypass = (bool) $bypass;
+ return $this;
+ }
+
+ /**
+ * Returns true if the Git command should be run.
+ *
+ * The return value is the boolean opposite $this->bypass. Although this
+ * seems complex, it makes the code more readable when checking whether the
+ * command should be run or not.
+ *
+ * @return boolean
+ * If true, the command should be run.
+ */
+ public function notBypassed()
+ {
+ return !$this->bypass;
+ }
+
+ /**
+ * Builds the command line options for use in the Git command.
+ *
+ * @return string
+ */
+ public function buildOptions()
+ {
+ $options = array();
+ foreach ($this->options as $option => $values) {
+ foreach ((array) $values as $value) {
+ $prefix = (strlen($option) != 1) ? '--' : '-';
+ $rendered = $prefix . $option;
+ if ($value !== true) {
+ $rendered .= ('--' == $prefix) ? '=' : ' ';
+ $rendered .= ProcessUtils::escapeArgument($value);
+ }
+ $options[] = $rendered;
+ }
+ }
+ return join(' ', $options);
+ }
+
+ /**
+ * Sets a command line option.
+ *
+ * Option names are passed as-is to the command line, whereas the values are
+ * escaped using \Symfony\Component\Process\ProcessUtils.
+ *
+ * @param string $option
+ * The option name, e.g. "branch", "q".
+ * @param string|true $value
+ * The option's value, pass true if the options is a flag.
+ *
+ * @reutrn \GitWrapper\GitCommand
+ */
+ public function setOption($option, $value)
+ {
+ $this->options[$option] = $value;
+ return $this;
+ }
+
+ /**
+ * Sets multiple command line options.
+ *
+ * @param array $options
+ * An associative array of command line options.
+ *
+ * @reutrn \GitWrapper\GitCommand
+ */
+ public function setOptions(array $options)
+ {
+ foreach ($options as $option => $value) {
+ $this->setOption($option, $value);
+ }
+ return $this;
+ }
+
+ /**
+ * Sets a command line flag.
+ *
+ * @param string $flag
+ * The flag name, e.g. "q", "a".
+ *
+ * @reutrn \GitWrapper\GitCommand
+ *
+ * @see \GitWrapper\GitCommand::setOption()
+ */
+ public function setFlag($option)
+ {
+ return $this->setOption($option, true);
+ }
+
+ /**
+ * Gets a command line option.
+ *
+ * @param string $option
+ * The option name, e.g. "branch", "q".
+ * @param mixed $default
+ * Value that is returned if the option is not set, defaults to null.
+ *
+ * @return mixed
+ */
+ public function getOption($option, $default = null)
+ {
+ return (isset($this->options[$option])) ? $this->options[$option] : $default;
+ }
+
+ /**
+ * Unsets a command line option.
+ *
+ * @param string $option
+ * The option name, e.g. "branch", "q".
+ *
+ * @return \GitWrapper\GitCommand
+ */
+ public function unsetOption($option)
+ {
+ unset($this->options[$option]);
+ return $this;
+ }
+
+ /**
+ * Adds a command line argument passed to the Git command.
+ *
+ * @param string $arg
+ * The argument, e.g. the repo URL, directory, etc.
+ *
+ * @return \GitWrapper\GitCommand
+ */
+ public function addArgument($arg)
+ {
+ $this->args[] = $arg;
+ return $this;
+ }
+
+ /**
+ * Renders the arguments and options for the Git command.
+ *
+ * @return string
+ *
+ * @see GitCommand::getCommand()
+ * @see GitCommand::buildOptions()
+ */
+ public function getCommandLine()
+ {
+ $command = array(
+ $this->getCommand(),
+ $this->buildOptions(),
+ join(' ', array_map(array('\Symfony\Component\Process\ProcessUtils', 'escapeArgument'), $this->args)),
+ );
+ return join(' ', array_filter($command));
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/src/GitWrapper/GitException.php b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/src/GitWrapper/GitException.php
new file mode 100644
index 000000000..2557aa387
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/src/GitWrapper/GitException.php
@@ -0,0 +1,8 @@
+git = $git;
+ $this->command = $command;
+
+ // Build the command line options, flags, and arguments.
+ $binary = ProcessUtils::escapeArgument($git->getGitBinary());
+ $commandLine = rtrim($binary . ' ' . $command->getCommandLine());
+
+ // Resolve the working directory of the Git process. Use the directory
+ // in the command object if it exists.
+ if (null === $cwd) {
+ if (null !== $directory = $command->getDirectory()) {
+ if (!$cwd = realpath($directory)) {
+ throw new GitException('Path to working directory could not be resolved: ' . $directory);
+ }
+ }
+ }
+
+ // Finalize the environment variables, an empty array is converted
+ // to null which enherits the environment of the PHP process.
+ $env = $git->getEnvVars();
+ if (!$env) {
+ $env = null;
+ }
+
+ parent::__construct($commandLine, $cwd, $env, null, $git->getTimeout(), $git->getProcOptions());
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function run($callback = null)
+ {
+ $event = new Event\GitEvent($this->git, $this, $this->command);
+ $dispatcher = $this->git->getDispatcher();
+
+ try {
+
+ // Throw the "git.command.prepare" event prior to executing.
+ $dispatcher->dispatch(Event\GitEvents::GIT_PREPARE, $event);
+
+ // Execute command if it is not flagged to be bypassed and throw the
+ // "git.command.success" event, otherwise do not execute the comamnd
+ // and throw the "git.command.bypass" event.
+ if ($this->command->notBypassed()) {
+ parent::run($callback);
+
+ if ($this->isSuccessful()) {
+ $dispatcher->dispatch(Event\GitEvents::GIT_SUCCESS, $event);
+ } else {
+ $output = $this->getErrorOutput();
+
+ if(trim($output) == '') {
+ $output = $this->getOutput();
+ }
+
+ throw new \RuntimeException($output);
+ }
+ } else {
+ $dispatcher->dispatch(Event\GitEvents::GIT_BYPASS, $event);
+ }
+
+ } catch (\RuntimeException $e) {
+ $dispatcher->dispatch(Event\GitEvents::GIT_ERROR, $event);
+ throw new GitException($e->getMessage());
+ }
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/src/GitWrapper/GitWorkingCopy.php b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/src/GitWrapper/GitWorkingCopy.php
new file mode 100644
index 000000000..519a2d4ab
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/src/GitWrapper/GitWorkingCopy.php
@@ -0,0 +1,1119 @@
+wrapper = $wrapper;
+ $this->directory = $directory;
+ }
+
+ /**
+ * Returns the GitWrapper object that likely instantiated this class.
+ *
+ * @return \GitWrapper\GitWrapper
+ */
+ public function getWrapper()
+ {
+ return $this->wrapper;
+ }
+
+ /**
+ * Gets the path to the directory containing the working copy.
+ *
+ * @return string
+ */
+ public function getDirectory()
+ {
+ return $this->directory;
+ }
+
+ /**
+ * Gets the output captured by the last run Git commnd(s).
+ *
+ * @return string
+ */
+ public function getOutput()
+ {
+ $output = $this->output;
+ $this->output = '';
+ return $output;
+ }
+
+ /**
+ * Clears the stored output captured by the last run Git command(s).
+ *
+ * @return \GitWrapper\GitWorkingCopy
+ */
+ public function clearOutput()
+ {
+ $this->output = '';
+ return $this;
+ }
+
+ /**
+ * Manually sets the cloned flag.
+ *
+ * @param boolean $cloned
+ * Whether the repository is cloned into the directory or not.
+ *
+ * @return \GitWrapper\GitWorkingCopy
+ */
+ public function setCloned($cloned)
+ {
+ $this->cloned = (bool) $cloned;
+ return $this;
+ }
+
+ /**
+ * Checks whether a repository has already been cloned to this directory.
+ *
+ * If the flag is not set, test if it looks like we're at a git directory.
+ *
+ * @return boolean
+ */
+ public function isCloned()
+ {
+ if (!isset($this->cloned)) {
+ $gitDir = $this->directory;
+ if (is_dir($gitDir . '/.git')) {
+ $gitDir .= '/.git';
+ };
+ $this->cloned = (is_dir($gitDir . '/objects') && is_dir($gitDir . '/refs') && is_file($gitDir . '/HEAD'));
+ }
+ return $this->cloned;
+ }
+
+ /**
+ * Runs a Git command and captures the output.
+ *
+ * @param array $args
+ * The arguments passed to the command method.
+ * @param boolean $setDirectory
+ * Set the working directory, defaults to true.
+ *
+ * @return \GitWrapper\GitWorkingCopy
+ *
+ * @throws \GitWrapper\GitException
+ *
+ * @see GitWrapper::run()
+ */
+ public function run($args, $setDirectory = true)
+ {
+ $command = call_user_func_array(array('GitWrapper\GitCommand', 'getInstance'), $args);
+ if ($setDirectory) {
+ $command->setDirectory($this->directory);
+ }
+ $this->output .= $this->wrapper->run($command);
+ return $this;
+ }
+
+ /**
+ * @defgroup command_helpers Git Command Helpers
+ *
+ * Helper methods that wrap common Git commands.
+ *
+ * @{
+ */
+
+ /**
+ * Returns the output of a `git status -s` command.
+ *
+ * @return string
+ *
+ * @throws \GitWrapper\GitException
+ */
+ public function getStatus()
+ {
+ return $this->wrapper->git('status -s', $this->directory);
+ }
+
+ /**
+ * Returns true if there are changes to commit.
+ *
+ * @return bool
+ *
+ * @throws \GitWrapper\GitException
+ */
+ public function hasChanges()
+ {
+ $output = $this->getStatus();
+ return !empty($output);
+ }
+
+ /**
+ * Returns whether HEAD has a remote tracking branch.
+ *
+ * @return bool
+ */
+ public function isTracking()
+ {
+ try {
+ $this->run(array('rev-parse @{u}'));
+ } catch (GitException $e) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Returns whether HEAD is up-to-date with its remote tracking branch.
+ *
+ * @return bool
+ *
+ * @throws \GitWrapper\GitException
+ * Thrown when HEAD does not have a remote tracking branch.
+ */
+ public function isUpToDate()
+ {
+ if (!$this->isTracking()) {
+ throw new GitException('Error: HEAD does not have a remote tracking branch. Cannot check if it is up-to-date.');
+ }
+ $this->clearOutput();
+ $merge_base = (string) $this->run(array('merge-base @ @{u}'));
+ $remote_sha = (string) $this->run(array('rev-parse @{u}'));
+ return $merge_base === $remote_sha;
+ }
+
+ /**
+ * Returns whether HEAD is ahead of its remote tracking branch.
+ *
+ * If this returns true it means that commits are present locally which have
+ * not yet been pushed to the remote.
+ *
+ * @return bool
+ *
+ * @throws \GitWrapper\GitException
+ * Thrown when HEAD does not have a remote tracking branch.
+ */
+ public function isAhead()
+ {
+ if (!$this->isTracking()) {
+ throw new GitException('Error: HEAD does not have a remote tracking branch. Cannot check if it is ahead.');
+ }
+ $this->clearOutput();
+ $merge_base = (string) $this->run(array('merge-base @ @{u}'));
+ $local_sha = (string) $this->run(array('rev-parse @'));
+ $remote_sha = (string) $this->run(array('rev-parse @{u}'));
+ return $merge_base === $remote_sha && $local_sha !== $remote_sha;
+ }
+
+ /**
+ * Returns whether HEAD is behind its remote tracking branch.
+ *
+ * If this returns true it means that a pull is needed to bring the branch
+ * up-to-date with the remote.
+ *
+ * @return bool
+ *
+ * @throws \GitWrapper\GitException
+ * Thrown when HEAD does not have a remote tracking branch.
+ */
+ public function isBehind()
+ {
+ if (!$this->isTracking()) {
+ throw new GitException('Error: HEAD does not have a remote tracking branch. Cannot check if it is behind.');
+ }
+ $this->clearOutput();
+ $merge_base = (string) $this->run(array('merge-base @ @{u}'));
+ $local_sha = (string) $this->run(array('rev-parse @'));
+ $remote_sha = (string) $this->run(array('rev-parse @{u}'));
+ return $merge_base === $local_sha && $local_sha !== $remote_sha;
+ }
+
+ /**
+ * Returns whether HEAD needs to be merged with its remote tracking branch.
+ *
+ * If this returns true it means that HEAD has diverged from its remote
+ * tracking branch; new commits are present locally as well as on the
+ * remote.
+ *
+ * @return bool
+ * true if HEAD needs to be merged with the remote, false otherwise.
+ *
+ * @throws \GitWrapper\GitException
+ * Thrown when HEAD does not have a remote tracking branch.
+ */
+ public function needsMerge()
+ {
+ if (!$this->isTracking()) {
+ throw new GitException('Error: HEAD does not have a remote tracking branch. Cannot check if it is behind.');
+ }
+ $this->clearOutput();
+ $merge_base = (string) $this->run(array('merge-base @ @{u}'));
+ $local_sha = (string) $this->run(array('rev-parse @'));
+ $remote_sha = (string) $this->run(array('rev-parse @{u}'));
+ return $merge_base !== $local_sha && $merge_base !== $remote_sha;
+ }
+
+ /**
+ * Returns a GitBranches object containing information on the repository's
+ * branches.
+ *
+ * @return GitBranches
+ */
+ public function getBranches()
+ {
+ return new GitBranches($this);
+ }
+
+ /**
+ * Helper method that pushes a tag to a repository.
+ *
+ * This is synonymous with `git push origin tag v1.2.3`.
+ *
+ * @param string $tag
+ * The tag being pushed.
+ * @param string $repository
+ * The destination of the push operation, which is either a URL or name of
+ * the remote. Defaults to "origin".
+ * @param array $options
+ * (optional) An associative array of command line options.
+ *
+ * @see GitWorkingCopy::push()
+ */
+ public function pushTag($tag, $repository = 'origin', array $options = array())
+ {
+ return $this->push($repository, 'tag', $tag, $options);
+ }
+
+ /**
+ * Helper method that pushes all tags to a repository.
+ *
+ * This is synonymous with `git push --tags origin`.
+ *
+ * @param string $repository
+ * The destination of the push operation, which is either a URL or name of
+ * the remote. Defaults to "origin".
+ * @param array $options
+ * (optional) An associative array of command line options.
+ *
+ * @see GitWorkingCopy::push()
+ */
+ public function pushTags($repository = 'origin', array $options = array())
+ {
+ $options['tags'] = true;
+ return $this->push($repository, $options);
+ }
+
+ /**
+ * Fetches all remotes.
+ *
+ * This is synonymous with `git fetch --all`.
+ *
+ * @param array $options
+ * (optional) An associative array of command line options.
+ *
+ * @see GitWorkingCopy::fetch()
+ */
+ public function fetchAll(array $options = array())
+ {
+ $options['all'] = true;
+ return $this->fetch($options);
+ }
+
+ /**
+ * Create a new branch and check it out.
+ *
+ * This is synonymous with `git checkout -b`.
+ *
+ * @param string $branch
+ * The new branch being created.
+ *
+ * @see GitWorkingCopy::checkout()
+ */
+ public function checkoutNewBranch($branch, array $options = array())
+ {
+ $options['b'] = true;
+ return $this->checkout($branch, $options);
+ }
+
+ /**
+ * @} End of "defgroup command_helpers".
+ */
+
+ /**
+ * @defgroup commands Git Commands
+ *
+ * All methods in this group correspond with Git commands, for example
+ * "git add", "git commit", "git push", etc.
+ *
+ * @{
+ */
+
+ /**
+ * Executes a `git add` command.
+ *
+ * Add file contents to the index.
+ *
+ * @code
+ * $git->add('some/file.txt');
+ * @endcode
+ *
+ * @param string $filepattern
+ * Files to add content from. Fileglobs (e.g. *.c) can be given to add
+ * all matching files. Also a leading directory name (e.g. dir to add
+ * dir/file1 and dir/file2) can be given to add all files in the
+ * directory, recursively.
+ * @param array $options
+ * An optional array of command line options.
+ *
+ * @return \GitWrapper\GitWorkingCopy
+ *
+ * @throws \GitWrapper\GitException
+ */
+ public function add($filepattern, array $options = array())
+ {
+ $args = array(
+ 'add',
+ $filepattern,
+ $options,
+ );
+ return $this->run($args);
+ }
+
+ /**
+ * Executes a `git apply` command.
+ *
+ * Apply a patch to files and/or to the index
+ *
+ * @code
+ * $git->apply('the/file/to/read/the/patch/from');
+ * @endcode
+ *
+ * @param string ...
+ * (optional) Additional command line arguments.
+ * @param array $options
+ * (optional) An associative array of command line options.
+ *
+ * @return GitWorkingCopy
+ *
+ * @throws GitException
+ */
+ public function apply()
+ {
+ $args = func_get_args();
+ array_unshift($args, 'apply');
+ return $this->run($args);
+ }
+
+ /**
+ * Executes a `git bisect` command.
+ *
+ * Find by binary search the change that introduced a bug.
+ *
+ * @code
+ * $git->bisect('good', '2.6.13-rc2');
+ * $git->bisect('view', array('stat' => true));
+ * @endcode
+ *
+ * @param string $sub_command
+ * The subcommand passed to `git bisect`.
+ * @param string ...
+ * (optional) Additional command line arguments.
+ * @param array $options
+ * (optional) An associative array of command line options.
+ *
+ * @return \GitWrapper\GitWorkingCopy
+ *
+ * @throws \GitWrapper\GitException
+ */
+ public function bisect($sub_command)
+ {
+ $args = func_get_args();
+ $args[0] = 'bisect ' . ProcessUtils::escapeArgument($sub_command);
+ return $this->run($args);
+ }
+
+ /**
+ * Executes a `git branch` command.
+ *
+ * List, create, or delete branches.
+ *
+ * @code
+ * $git->branch('my2.6.14', 'v2.6.14');
+ * $git->branch('origin/html', 'origin/man', array('d' => true, 'r' => 'origin/todo'));
+ * @endcode
+ *
+ * @param string ...
+ * (optional) Additional command line arguments.
+ * @param array $options
+ * (optional) An associative array of command line options.
+ *
+ * @return \GitWrapper\GitWorkingCopy
+ *
+ * @throws \GitWrapper\GitException
+ */
+ public function branch()
+ {
+ $args = func_get_args();
+ array_unshift($args, 'branch');
+ return $this->run($args);
+ }
+
+ /**
+ * Executes a `git checkout` command.
+ *
+ * Checkout a branch or paths to the working tree.
+ *
+ * @code
+ * $git->checkout('new-branch', array('b' => true));
+ * @endcode
+ *
+ * @param string ...
+ * (optional) Additional command line arguments.
+ * @param array $options
+ * (optional) An associative array of command line options.
+ *
+ * @return \GitWrapper\GitWorkingCopy
+ *
+ * @throws \GitWrapper\GitException
+ */
+ public function checkout()
+ {
+ $args = func_get_args();
+ array_unshift($args, 'checkout');
+ return $this->run($args);
+ }
+
+ /**
+ * Executes a `git clone` command.
+ *
+ * Clone a repository into a new directory. Use GitWorkingCopy::clone()
+ * instead for more readable code.
+ *
+ * @code
+ * $git->clone('git://github.com/cpliakas/git-wrapper.git');
+ * @endcode
+ *
+ * @param string $repository
+ * The Git URL of the repository being cloned.
+ * @param array $options
+ * (optional) An associative array of command line options.
+ *
+ * @param string $repository
+ * The URL of the repository being cloned.
+ *
+ * @return \GitWrapper\GitWorkingCopy
+ *
+ * @throws \GitWrapper\GitException
+ */
+ public function cloneRepository($repository, $options = array())
+ {
+ $args = array(
+ 'clone',
+ $repository,
+ $this->directory,
+ $options,
+ );
+ return $this->run($args, false);
+ }
+
+ /**
+ * Executes a `git commit` command.
+ *
+ * Record changes to the repository. If only one argument is passed, it is
+ * assumed to be the commit message. Therefore `$git->commit('Message');`
+ * yields a `git commit -am "Message"` command.
+ *
+ * @code
+ * $git->commit('My commit message');
+ * $git->commit('Makefile', array('m' => 'My commit message'));
+ * @endcode
+ *
+ * @param string ...
+ * (optional) Additional command line arguments.
+ * @param array $options
+ * (optional) An associative array of command line options.
+ *
+ * @return \GitWrapper\GitWorkingCopy
+ *
+ * @throws \GitWrapper\GitException
+ */
+ public function commit()
+ {
+ $args = func_get_args();
+ if (isset($args[0]) && is_string($args[0]) && !isset($args[1])) {
+ $args[0] = array(
+ 'm' => $args[0],
+ 'a' => true,
+ );
+ }
+ array_unshift($args, 'commit');
+ return $this->run($args);
+ }
+
+ /**
+ * Executes a `git config` command.
+ *
+ * Get and set repository options.
+ *
+ * @code
+ * $git->config('user.email', 'opensource@chrispliakas.com');
+ * $git->config('user.name', 'Chris Pliakas');
+ * @endcode
+ *
+ * @param string ...
+ * (optional) Additional command line arguments.
+ * @param array $options
+ * (optional) An associative array of command line options.
+ *
+ * @return \GitWrapper\GitWorkingCopy
+ *
+ * @throws \GitWrapper\GitException
+ */
+ public function config()
+ {
+ $args = func_get_args();
+ array_unshift($args, 'config');
+ return $this->run($args);
+ }
+
+ /**
+ * Executes a `git diff` command.
+ *
+ * Show changes between commits, commit and working tree, etc.
+ *
+ * @code
+ * $git->diff();
+ * $git->diff('topic', 'master');
+ * @endcode
+ *
+ * @param string ...
+ * (optional) Additional command line arguments.
+ * @param array $options
+ * (optional) An associative array of command line options.
+ *
+ * @return \GitWrapper\GitWorkingCopy
+ *
+ * @throws \GitWrapper\GitException
+ */
+ public function diff()
+ {
+ $args = func_get_args();
+ array_unshift($args, 'diff');
+ return $this->run($args);
+ }
+
+ /**
+ * Executes a `git fetch` command.
+ *
+ * Download objects and refs from another repository.
+ *
+ * @code
+ * $git->fetch('origin');
+ * $git->fetch(array('all' => true));
+ * @endcode
+ *
+ * @param string ...
+ * (optional) Additional command line arguments.
+ * @param array $options
+ * (optional) An associative array of command line options.
+ *
+ * @return \GitWrapper\GitWorkingCopy
+ *
+ * @throws \GitWrapper\GitException
+ */
+ public function fetch()
+ {
+ $args = func_get_args();
+ array_unshift($args, 'fetch');
+ return $this->run($args);
+ }
+
+ /**
+ * Executes a `git grep` command.
+ *
+ * Print lines matching a pattern.
+ *
+ * @code
+ * $git->grep('time_t', '--', '*.[ch]');
+ * @endcode
+ *
+ * @param string ...
+ * (optional) Additional command line arguments.
+ * @param array $options
+ * (optional) An associative array of command line options.
+ *
+ * @return \GitWrapper\GitWorkingCopy
+ *
+ * @throws \GitWrapper\GitException
+ */
+ public function grep()
+ {
+ $args = func_get_args();
+ array_unshift($args, 'grep');
+ return $this->run($args);
+ }
+
+ /**
+ * Executes a `git init` command.
+ *
+ * Create an empty git repository or reinitialize an existing one.
+ *
+ * @code
+ * $git->init(array('bare' => true));
+ * @endcode
+ *
+ * @param array $options
+ * (optional) An associative array of command line options.
+ *
+ * @return \GitWrapper\GitWorkingCopy
+ *
+ * @throws \GitWrapper\GitException
+ */
+ public function init(array $options = array())
+ {
+ $args = array(
+ 'init',
+ $this->directory,
+ $options,
+ );
+ return $this->run($args, false);
+ }
+
+ /**
+ * Executes a `git log` command.
+ *
+ * Show commit logs.
+ *
+ * @code
+ * $git->log(array('no-merges' => true));
+ * $git->log('v2.6.12..', 'include/scsi', 'drivers/scsi');
+ * @endcode
+ *
+ * @param string ...
+ * (optional) Additional command line arguments.
+ * @param array $options
+ * (optional) An associative array of command line options.
+ *
+ * @return \GitWrapper\GitWorkingCopy
+ *
+ * @throws \GitWrapper\GitException
+ */
+ public function log()
+ {
+ $args = func_get_args();
+ array_unshift($args, 'log');
+ return $this->run($args);
+ }
+
+ /**
+ * Executes a `git merge` command.
+ *
+ * Join two or more development histories together.
+ *
+ * @code
+ * $git->merge('fixes', 'enhancements');
+ * @endcode
+ *
+ * @param string ...
+ * (optional) Additional command line arguments.
+ * @param array $options
+ * (optional) An associative array of command line options.
+ *
+ * @return \GitWrapper\GitWorkingCopy
+ *
+ * @throws \GitWrapper\GitException
+ */
+ public function merge()
+ {
+ $args = func_get_args();
+ array_unshift($args, 'merge');
+ return $this->run($args);
+ }
+
+ /**
+ * Executes a `git mv` command.
+ *
+ * Move or rename a file, a directory, or a symlink.
+ *
+ * @code
+ * $git->mv('orig.txt', 'dest.txt');
+ * @endcode
+ *
+ * @param string $source
+ * The file / directory being moved.
+ * @param string $destination
+ * The target file / directory that the source is being move to.
+ * @param array $options
+ * (optional) An associative array of command line options.
+ *
+ * @return \GitWrapper\GitWorkingCopy
+ *
+ * @throws \GitWrapper\GitException
+ */
+ public function mv($source, $destination, array $options = array())
+ {
+ $args = array(
+ 'mv',
+ $source,
+ $destination,
+ $options,
+ );
+ return $this->run($args);
+ }
+
+ /**
+ * Executes a `git pull` command.
+ *
+ * Fetch from and merge with another repository or a local branch.
+ *
+ * @code
+ * $git->pull('upstream', 'master');
+ * @endcode
+ *
+ * @param string ...
+ * (optional) Additional command line arguments.
+ * @param array $options
+ * (optional) An associative array of command line options.
+ *
+ * @return \GitWrapper\GitWorkingCopy
+ *
+ * @throws \GitWrapper\GitException
+ */
+ public function pull()
+ {
+ $args = func_get_args();
+ array_unshift($args, 'pull');
+ return $this->run($args);
+ }
+
+ /**
+ * Executes a `git push` command.
+ *
+ * Update remote refs along with associated objects.
+ *
+ * @code
+ * $git->push('upstream', 'master');
+ * @endcode
+ *
+ * @param string ...
+ * (optional) Additional command line arguments.
+ * @param array $options
+ * (optional) An associative array of command line options.
+ *
+ * @return \GitWrapper\GitWorkingCopy
+ *
+ * @throws \GitWrapper\GitException
+ */
+ public function push()
+ {
+ $args = func_get_args();
+ array_unshift($args, 'push');
+ return $this->run($args);
+ }
+
+ /**
+ * Executes a `git rebase` command.
+ *
+ * Forward-port local commits to the updated upstream head.
+ *
+ * @code
+ * $git->rebase('subsystem@{1}', array('onto' => 'subsystem'));
+ * @endcode
+ *
+ * @param string ...
+ * (optional) Additional command line arguments.
+ * @param array $options
+ * (optional) An associative array of command line options.
+ *
+ * @return \GitWrapper\GitWorkingCopy
+ *
+ * @throws \GitWrapper\GitException
+ */
+ public function rebase()
+ {
+ $args = func_get_args();
+ array_unshift($args, 'rebase');
+ return $this->run($args);
+ }
+
+ /**
+ * Executes a `git remote` command.
+ *
+ * Manage the set of repositories ("remotes") whose branches you track.
+ *
+ * @code
+ * $git->remote('add', 'upstream', 'git://github.com/cpliakas/git-wrapper.git');
+ * @endcode
+ *
+ * @param string ...
+ * (optional) Additional command line arguments.
+ * @param array $options
+ * (optional) An associative array of command line options.
+ *
+ * @return \GitWrapper\GitWorkingCopy
+ *
+ * @throws \GitWrapper\GitException
+ */
+ public function remote()
+ {
+ $args = func_get_args();
+ array_unshift($args, 'remote');
+ return $this->run($args);
+ }
+
+ /**
+ * Executes a `git reset` command.
+ *
+ * Reset current HEAD to the specified state.
+ *
+ * @code
+ * $git->reset(array('hard' => true));
+ * @endcode
+ *
+ * @param string ...
+ * (optional) Additional command line arguments.
+ * @param array $options
+ * (optional) An associative array of command line options.
+ *
+ * @return \GitWrapper\GitWorkingCopy
+ *
+ * @throws \GitWrapper\GitException
+ */
+ public function reset()
+ {
+ $args = func_get_args();
+ array_unshift($args, 'reset');
+ return $this->run($args);
+ }
+
+ /**
+ * Executes a `git rm` command.
+ *
+ * Remove files from the working tree and from the index.
+ *
+ * @code
+ * $git->rm('oldfile.txt');
+ * @endcode
+ *
+ * @param string $filepattern
+ * Files to remove from version control. Fileglobs (e.g. *.c) can be
+ * given to add all matching files. Also a leading directory name (e.g.
+ * dir to add dir/file1 and dir/file2) can be given to add all files in
+ * the directory, recursively.
+ * @param array $options
+ * (optional) An associative array of command line options.
+ *
+ * @return \GitWrapper\GitWorkingCopy
+ *
+ * @throws \GitWrapper\GitException
+ */
+ public function rm($filepattern, array $options = array())
+ {
+ $args = array(
+ 'rm',
+ $filepattern,
+ $options,
+ );
+ return $this->run($args);
+ }
+
+ /**
+ * Executes a `git show` command.
+ *
+ * Show various types of objects.
+ *
+ * @code
+ * $git->show('v1.0.0');
+ * @endcode
+ *
+ * @param string $object
+ * The names of objects to show. For a more complete list of ways to spell
+ * object names, see "SPECIFYING REVISIONS" section in gitrevisions(7).
+ * @param array $options
+ * (optional) An associative array of command line options.
+ *
+ * @return \GitWrapper\GitWorkingCopy
+ *
+ * @throws \GitWrapper\GitException
+ */
+ public function show($object, array $options = array())
+ {
+ $args = array('show', $object, $options);
+ return $this->run($args);
+ }
+
+ /**
+ * Executes a `git status` command.
+ *
+ * Show the working tree status.
+ *
+ * @code
+ * $git->status(array('s' => true));
+ * @endcode
+ *
+ * @param string ...
+ * (optional) Additional command line arguments.
+ * @param array $options
+ * (optional) An associative array of command line options.
+ *
+ * @return \GitWrapper\GitWorkingCopy
+ *
+ * @throws \GitWrapper\GitException
+ */
+ public function status()
+ {
+ $args = func_get_args();
+ array_unshift($args, 'status');
+ return $this->run($args);
+ }
+
+ /**
+ * Executes a `git tag` command.
+ *
+ * Create, list, delete or verify a tag object signed with GPG.
+
+ * @code
+ * $git->tag('v1.0.0');
+ * @endcode
+ *
+ * @param string ...
+ * (optional) Additional command line arguments.
+ * @param array $options
+ * (optional) An associative array of command line options.
+ *
+ * @return \GitWrapper\GitWorkingCopy
+ *
+ * @throws \GitWrapper\GitException
+ */
+ public function tag()
+ {
+ $args = func_get_args();
+ array_unshift($args, 'tag');
+ return $this->run($args);
+ }
+
+ /**
+ * Executes a `git clean` command.
+ *
+ * Remove untracked files from the working tree
+ *
+ * @code
+ * $git->clean('-d', '-f');
+ * @endcode
+ *
+ * @param string ...
+ * (optional) Additional command line arguments.
+ * @param array $options
+ * (optional) An associative array of command line options.
+ *
+ * @return \GitWrapper\GitWorkingCopy
+ *
+ * @throws \GitWrapper\GitException
+ */
+ public function clean()
+ {
+ $args = func_get_args();
+ array_unshift($args, 'clean');
+ return $this->run($args);
+ }
+
+ /**
+ * Executes a `git archive` command.
+ *
+ * Create an archive of files from a named tree
+ *
+ * @code
+ * $git->archive('HEAD', array('o' => '/path/to/archive'));
+ * @endcode
+ *
+ * @param string ...
+ * (optional) Additional command line arguments.
+ * @param array $options
+ * (optional) An associative array of command line options.
+ *
+ * @return \GitWrapper\GitWorkingCopy
+ *
+ * @throws \GitWrapper\GitException
+ */
+ public function archive()
+ {
+ $args = func_get_args();
+ array_unshift($args, 'archive');
+ return $this->run($args);
+ }
+
+ /**
+ * @} End of "defgroup command".
+ */
+
+ /**
+ * Hackish, allows us to use "clone" as a method name.
+ *
+ * $throws \BadMethodCallException
+ * @throws \GitWrapper\GitException
+ */
+ public function __call($method, $args)
+ {
+ if ('clone' == $method) {
+ return call_user_func_array(array($this, 'cloneRepository'), $args);
+ } else {
+ $class = get_called_class();
+ $message = "Call to undefined method $class::$method()";
+ throw new \BadMethodCallException($message);
+ }
+ }
+
+ /**
+ * Gets the output captured by the last run Git commnd(s).
+ *
+ * @return string
+ *
+ * @see GitWorkingCopy::getOutput()
+ */
+ public function __toString()
+ {
+ return $this->getOutput();
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/src/GitWrapper/GitWrapper.php b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/src/GitWrapper/GitWrapper.php
new file mode 100644
index 000000000..a14b25440
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/src/GitWrapper/GitWrapper.php
@@ -0,0 +1,555 @@
+find('git');
+ if (!$gitBinary) {
+ throw new GitException('Unable to find the Git executable.');
+ }
+ // @codeCoverageIgnoreEnd
+ }
+
+ $this->setGitBinary($gitBinary);
+ }
+
+ /**
+ * Gets the dispatcher used by this library to dispatch events.
+ *
+ * @return \Symfony\Component\EventDispatcher\EventDispatcherInterface
+ */
+ public function getDispatcher()
+ {
+ if (!isset($this->dispatcher)) {
+ $this->dispatcher = new EventDispatcher();
+ }
+ return $this->dispatcher;
+ }
+
+ /**
+ * Sets the dispatcher used by this library to dispatch events.
+ *
+ * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher
+ * The Symfony event dispatcher object.
+ *
+ * @return \GitWrapper\GitWrapper
+ */
+ public function setDispatcher(EventDispatcherInterface $dispatcher)
+ {
+ $this->dispatcher = $dispatcher;
+ return $this;
+ }
+
+ /**
+ * Sets the path to the Git binary.
+ *
+ * @param string $gitBinary
+ * Path to the Git binary.
+ *
+ * @return \GitWrapper\GitWrapper
+ */
+ public function setGitBinary($gitBinary)
+ {
+ $this->gitBinary = $gitBinary;
+ return $this;
+ }
+
+ /**
+ * Returns the path to the Git binary.
+ *
+ * @return string
+ */
+ public function getGitBinary()
+ {
+ return $this->gitBinary;
+ }
+
+ /**
+ * Sets an environment variable that is defined only in the scope of the Git
+ * command.
+ *
+ * @param string $var
+ * The name of the environment variable, e.g. "HOME", "GIT_SSH".
+ * @param mixed $default
+ * The value of the environment variable is not set, defaults to null.
+ *
+ * @return \GitWrapper\GitWrapper
+ */
+ public function setEnvVar($var, $value)
+ {
+ $this->env[$var] = $value;
+ return $this;
+ }
+
+ /**
+ * Unsets an environment variable that is defined only in the scope of the
+ * Git command.
+ *
+ * @param string $var
+ * The name of the environment variable, e.g. "HOME", "GIT_SSH".
+ *
+ * @return \GitWrapper\GitWrapper
+ */
+ public function unsetEnvVar($var)
+ {
+ unset($this->env[$var]);
+ return $this;
+ }
+
+ /**
+ * Returns an environment variable that is defined only in the scope of the
+ * Git command.
+ *
+ * @param string $var
+ * The name of the environment variable, e.g. "HOME", "GIT_SSH".
+ * @param mixed $default
+ * The value returned if the environment variable is not set, defaults to
+ * null.
+ *
+ * @return mixed
+ */
+ public function getEnvVar($var, $default = null)
+ {
+ return isset($this->env[$var]) ? $this->env[$var] : $default;
+ }
+
+ /**
+ * Returns the associative array of environment variables that are defined
+ * only in the scope of the Git command.
+ *
+ * @return array
+ */
+ public function getEnvVars()
+ {
+ return $this->env;
+ }
+
+ /**
+ * Sets the timeout of the Git command.
+ *
+ * @param int $timeout
+ * The timeout in seconds.
+ *
+ * @return \GitWrapper\GitWrapper
+ */
+ public function setTimeout($timeout)
+ {
+ $this->timeout = (int) $timeout;
+ return $this;
+ }
+
+ /**
+ * Gets the timeout of the Git command.
+ *
+ * @return int
+ * The timeout in seconds.
+ */
+ public function getTimeout()
+ {
+ return $this->timeout;
+ }
+
+ /**
+ * Sets the options passed to proc_open() when executing the Git command.
+ *
+ * @param array $timeout
+ * The options passed to proc_open().
+ *
+ * @return \GitWrapper\GitWrapper
+ */
+ public function setProcOptions(array $options)
+ {
+ $this->procOptions = $options;
+ return $this;
+ }
+
+ /**
+ * Gets the options passed to proc_open() when executing the Git command.
+ *
+ * @return array
+ */
+ public function getProcOptions()
+ {
+ return $this->procOptions;
+ }
+
+ /**
+ * Set an alternate private key used to connect to the repository.
+ *
+ * This method sets the GIT_SSH environment variable to use the wrapper
+ * script included with this library. It also sets the custom GIT_SSH_KEY
+ * and GIT_SSH_PORT environment variables that are used by the script.
+ *
+ * @param string $privateKey
+ * Path to the private key.
+ * @param int $port
+ * Port that the SSH server being connected to listens on, defaults to 22.
+ * @param string|null $wrapper
+ * Path the the GIT_SSH wrapper script, defaults to null which uses the
+ * script included with this library.
+ *
+ * @return \GitWrapper\GitWrapper
+ *
+ * @throws \GitWrapper\GitException
+ * Thrown when any of the paths cannot be resolved.
+ */
+ public function setPrivateKey($privateKey, $port = 22, $wrapper = null)
+ {
+ if (null === $wrapper) {
+ $wrapper = __DIR__ . '/../../bin/git-ssh-wrapper.sh';
+ }
+ if (!$wrapperPath = realpath($wrapper)) {
+ throw new GitException('Path to GIT_SSH wrapper script could not be resolved: ' . $wrapper);
+ }
+ if (!$privateKeyPath = realpath($privateKey)) {
+ throw new GitException('Path private key could not be resolved: ' . $privateKey);
+ }
+
+ return $this
+ ->setEnvVar('GIT_SSH', $wrapperPath)
+ ->setEnvVar('GIT_SSH_KEY', $privateKeyPath)
+ ->setEnvVar('GIT_SSH_PORT', (int) $port)
+ ;
+ }
+
+ /**
+ * Unsets the private key by removing the appropriate environment variables.
+ *
+ * @return \GitWrapper\GitWrapper
+ */
+ public function unsetPrivateKey()
+ {
+ return $this
+ ->unsetEnvVar('GIT_SSH')
+ ->unsetEnvVar('GIT_SSH_KEY')
+ ->unsetEnvVar('GIT_SSH_PORT')
+ ;
+ }
+
+ /**
+ * Adds output listener.
+ *
+ * @param \GitWrapper\Event\GitOutputListenerInterface $listener
+ *
+ * @return \GitWrapper\GitWrapper
+ */
+ public function addOutputListener(Event\GitOutputListenerInterface $listener)
+ {
+ $this
+ ->getDispatcher()
+ ->addListener(Event\GitEvents::GIT_OUTPUT, array($listener, 'handleOutput'))
+ ;
+ return $this;
+ }
+
+ /**
+ * Adds logger listener listener.
+ *
+ * @param Event\GitLoggerListener $listener
+ *
+ * @return GitWrapper
+ */
+ public function addLoggerListener(Event\GitLoggerListener $listener)
+ {
+ $this
+ ->getDispatcher()
+ ->addSubscriber($listener)
+ ;
+ return $this;
+ }
+
+ /**
+ * Removes an output listener.
+ *
+ * @param \GitWrapper\Event\GitOutputListenerInterface $listener
+ *
+ * @return \GitWrapper\GitWrapper
+ */
+ public function removeOutputListener(Event\GitOutputListenerInterface $listener)
+ {
+ $this
+ ->getDispatcher()
+ ->removeListener(Event\GitEvents::GIT_OUTPUT, array($listener, 'handleOutput'))
+ ;
+ return $this;
+ }
+
+ /**
+ * Set whether or not to stream real-time output to STDOUT and STDERR.
+ *
+ * @param boolean $streamOutput
+ *
+ * @return \GitWrapper\GitWrapper
+ */
+ public function streamOutput($streamOutput = true)
+ {
+ if ($streamOutput && !isset($this->streamListener)) {
+ $this->streamListener = new Event\GitOutputStreamListener();
+ $this->addOutputListener($this->streamListener);
+ }
+
+ if (!$streamOutput && isset($this->streamListener)) {
+ $this->removeOutputListener($this->streamListener);
+ unset($this->streamListener);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns an object that interacts with a working copy.
+ *
+ * @param string $directory
+ * Path to the directory containing the working copy.
+ *
+ * @return GitWorkingCopy
+ */
+ public function workingCopy($directory)
+ {
+ return new GitWorkingCopy($this, $directory);
+ }
+
+ /**
+ * Returns the version of the installed Git client.
+ *
+ * @return string
+ *
+ * @throws \GitWrapper\GitException
+ */
+ public function version()
+ {
+ return $this->git('--version');
+ }
+
+ /**
+ * Parses name of the repository from the path.
+ *
+ * For example, passing the "git@github.com:cpliakas/git-wrapper.git"
+ * repository would return "git-wrapper".
+ *
+ * @param string $repository
+ * The repository URL.
+ *
+ * @return string
+ */
+ public static function parseRepositoryName($repository)
+ {
+ $scheme = parse_url($repository, PHP_URL_SCHEME);
+
+ if (null === $scheme) {
+ $parts = explode('/', $repository);
+ $path = end($parts);
+ } else {
+ $strpos = strpos($repository, ':');
+ $path = substr($repository, $strpos + 1);
+ }
+
+ return basename($path, '.git');
+ }
+
+ /**
+ * Executes a `git init` command.
+ *
+ * Create an empty git repository or reinitialize an existing one.
+ *
+ * @param string $directory
+ * The directory being initialized.
+ * @param array $options
+ * (optional) An associative array of command line options.
+ *
+ * @return \GitWrapper\GitWorkingCopy
+ *
+ * @throws \GitWrapper\GitException
+ *
+ * @see GitWorkingCopy::cloneRepository()
+ *
+ * @ingroup commands
+ */
+ public function init($directory, array $options = array())
+ {
+ $git = $this->workingCopy($directory);
+ $git->init($options);
+ $git->setCloned(true);
+ return $git;
+ }
+
+ /**
+ * Executes a `git clone` command and returns a working copy object.
+ *
+ * Clone a repository into a new directory. Use GitWorkingCopy::clone()
+ * instead for more readable code.
+ *
+ * @param string $repository
+ * The Git URL of the repository being cloned.
+ * @param string $directory
+ * The directory that the repository will be cloned into. If null is
+ * passed, the directory will automatically be generated from the URL via
+ * the GitWrapper::parseRepositoryName() method.
+ * @param array $options
+ * (optional) An associative array of command line options.
+ *
+ * @return \GitWrapper\GitWorkingCopy
+ *
+ * @throws \GitWrapper\GitException
+ *
+ * @see GitWorkingCopy::cloneRepository()
+ *
+ * @ingroup commands
+ */
+ public function cloneRepository($repository, $directory = null, array $options = array())
+ {
+ if (null === $directory) {
+ $directory = self::parseRepositoryName($repository);
+ }
+ $git = $this->workingCopy($directory);
+ $git->clone($repository, $options);
+ $git->setCloned(true);
+ return $git;
+ }
+
+ /**
+ * Runs an arbitrary Git command.
+ *
+ * The command is simply a raw command line entry for everything after the
+ * Git binary. For example, a `git config -l` command would be passed as
+ * `config -l` via the first argument of this method.
+ *
+ * Note that no events are thrown by this method.
+ *
+ * @param string $commandLine
+ * The raw command containing the Git options and arguments. The Git
+ * binary should not be in the command, for example `git config -l` would
+ * translate to "config -l".
+ * @param string|null $cwd
+ * The working directory of the Git process. Defaults to null which uses
+ * the current working directory of the PHP process.
+ *
+ * @return string
+ * The STDOUT returned by the Git command.
+ *
+ * @throws \GitWrapper\GitException
+ *
+ * @see GitWrapper::run()
+ */
+ public function git($commandLine, $cwd = null)
+ {
+ $command = GitCommand::getInstance($commandLine);
+ $command->setDirectory($cwd);
+ return $this->run($command);
+ }
+
+ /**
+ * Runs a Git command.
+ *
+ * @param \GitWrapper\GitCommand $command
+ * The Git command being executed.
+ * @param string|null $cwd
+ * Explicitly specify the working directory of the Git process. Defaults
+ * to null which automatically sets the working directory based on the
+ * command being executed relative to the working copy.
+ *
+ * @return string
+ * The STDOUT returned by the Git command.
+ *
+ * @throws \GitWrapper\GitException
+ *
+ * @see Process
+ */
+ public function run(GitCommand $command, $cwd = null)
+ {
+ $wrapper = $this;
+ $process = new GitProcess($this, $command, $cwd);
+ $process->run(function ($type, $buffer) use ($wrapper, $process, $command) {
+ $event = new Event\GitOutputEvent($wrapper, $process, $command, $type, $buffer);
+ $wrapper->getDispatcher()->dispatch(Event\GitEvents::GIT_OUTPUT, $event);
+ });
+ return $command->notBypassed() ? $process->getOutput() : '';
+ }
+
+ /**
+ * Hackish, allows us to use "clone" as a method name.
+ *
+ * $throws \BadMethodCallException
+ * @throws \GitWrapper\GitException
+ */
+ public function __call($method, $args)
+ {
+ if ('clone' == $method) {
+ return call_user_func_array(array($this, 'cloneRepository'), $args);
+ } else {
+ $class = get_called_class();
+ $message = "Call to undefined method $class::$method()";
+ throw new \BadMethodCallException($message);
+ }
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/Event/TestBypassListener.php b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/Event/TestBypassListener.php
new file mode 100644
index 000000000..22e7f20fd
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/Event/TestBypassListener.php
@@ -0,0 +1,13 @@
+getCommand()->bypass();
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/Event/TestDispatcher.php b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/Event/TestDispatcher.php
new file mode 100644
index 000000000..57c2e6ba8
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/Event/TestDispatcher.php
@@ -0,0 +1,7 @@
+methods);
+ }
+
+ /**
+ * @return \GitWrapper\Event\GitEvent
+ */
+ public function getEvent()
+ {
+ return $this->event;
+ }
+
+ public function onPrepare(GitEvent $event)
+ {
+ $this->methods[] = 'onPrepare';
+ $this->event = $event;
+ }
+
+ public function onSuccess(GitEvent $event)
+ {
+ $this->methods[] = 'onSuccess';
+ }
+
+ public function onError(GitEvent $event)
+ {
+ $this->methods[] = 'onError';
+ }
+
+ public function onBypass(GitEvent $event)
+ {
+ $this->methods[] = 'onBypass';
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/Event/TestOutputListener.php b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/Event/TestOutputListener.php
new file mode 100644
index 000000000..be67b536f
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/Event/TestOutputListener.php
@@ -0,0 +1,27 @@
+event;
+ }
+
+ public function handleOutput(GitOutputEvent $event)
+ {
+ $this->event = $event;
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/GitCommandTest.php b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/GitCommandTest.php
new file mode 100644
index 000000000..0decf63f0
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/GitCommandTest.php
@@ -0,0 +1,55 @@
+randomString();
+ $argument = $this->randomString();
+ $flag = $this->randomString();
+ $optionName = $this->randomString();
+ $optionValue = $this->randomString();
+
+ $git = GitCommand::getInstance($command)
+ ->addArgument($argument)
+ ->setFlag($flag)
+ ->setOption($optionName, $optionValue);
+
+ $expected = "$command --$flag --$optionName='$optionValue' '$argument'";
+ $commandLine = $git->getCommandLine();
+
+ $this->assertEquals($expected, $commandLine);
+ }
+
+ public function testOption()
+ {
+ $optionName = $this->randomString();
+ $optionValue = $this->randomString();
+
+ $git = GitCommand::getInstance()
+ ->setOption($optionName, $optionValue);
+
+ $this->assertEquals($optionValue, $git->getOption($optionName));
+
+ $git->unsetOption($optionName);
+ $this->assertNull($git->getOption($optionName));
+ }
+
+ /**
+ * @see https://github.com/cpliakas/git-wrapper/issues/50
+ */
+ public function testMultiOption()
+ {
+ $git = GitCommand::getInstance('test-command')
+ ->setOption('test-arg', array(true, true));
+
+ $expected = 'test-command --test-arg --test-arg';
+ $commandLine = $git->getCommandLine();
+
+ $this->assertEquals($expected, $commandLine);
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/GitListenerTest.php b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/GitListenerTest.php
new file mode 100644
index 000000000..50ea6a3fe
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/GitListenerTest.php
@@ -0,0 +1,58 @@
+addListener();
+ $this->wrapper->version();
+
+ $this->assertTrue($listener->methodCalled('onPrepare'));
+ $this->assertTrue($listener->methodCalled('onSuccess'));
+ $this->assertFalse($listener->methodCalled('onError'));
+ $this->assertFalse($listener->methodCalled('onBypass'));
+ }
+
+ public function testListenerError()
+ {
+ $listener = $this->addListener();
+ $this->runBadCommand(true);
+
+ $this->assertTrue($listener->methodCalled('onPrepare'));
+ $this->assertFalse($listener->methodCalled('onSuccess'));
+ $this->assertTrue($listener->methodCalled('onError'));
+ $this->assertFalse($listener->methodCalled('onBypass'));
+ }
+
+ public function testGitBypass()
+ {
+ $this->addBypassListener();
+ $listener = $this->addListener();
+
+ $output = $this->wrapper->version();
+
+ $this->assertTrue($listener->methodCalled('onPrepare'));
+ $this->assertFalse($listener->methodCalled('onSuccess'));
+ $this->assertFalse($listener->methodCalled('onError'));
+ $this->assertTrue($listener->methodCalled('onBypass'));
+
+ $this->assertEmpty($output);
+ }
+
+ public function testEvent()
+ {
+ $process = new Process('');
+ $command = GitCommand::getInstance();
+ $event = new GitEvent($this->wrapper, $process, $command);
+
+ $this->assertEquals($this->wrapper, $event->getWrapper());
+ $this->assertEquals($process, $event->getProcess());
+ $this->assertEquals($command, $event->getCommand());
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/GitLoggerListenerTest.php b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/GitLoggerListenerTest.php
new file mode 100644
index 000000000..0a0674b2c
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/GitLoggerListenerTest.php
@@ -0,0 +1,89 @@
+assertEquals($log, $listener->getLogger());
+ }
+
+ public function testSetLogLevelMapping()
+ {
+ $listener = new GitLoggerListener(new NullLogger());
+ $listener->setLogLevelMapping('test.event', 'test-level');
+ $this->assertEquals('test-level', $listener->getLogLevelMapping('test.event'));
+ }
+
+ /**
+ * @expectedException \DomainException
+ */
+ public function testGetInvalidLogLevelMapping()
+ {
+ $listener = new GitLoggerListener(new NullLogger());
+ $listener->getLogLevelMapping('bad.event');
+ }
+
+ public function testRegisterLogger()
+ {
+ $logger = new TestLogger();
+ $this->wrapper->addLoggerListener(new GitLoggerListener($logger));
+ $git = $this->wrapper->init(self::REPO_DIR, array('bare' => true));
+
+ $this->assertEquals('Git command preparing to run', $logger->messages[0]);
+ $this->assertEquals('Initialized empty Git repository in ' . realpath(self::REPO_DIR) . "/\n", $logger->messages[1]);
+ $this->assertEquals('Git command successfully run', $logger->messages[2]);
+
+ $this->assertArrayHasKey('command', $logger->contexts[0]);
+ $this->assertArrayHasKey('command', $logger->contexts[1]);
+ $this->assertArrayHasKey('error', $logger->contexts[1]);
+ $this->assertArrayHasKey('command', $logger->contexts[2]);
+
+ $this->assertEquals(LogLevel::INFO, $logger->levels[0]);
+ $this->assertEquals(LogLevel::DEBUG, $logger->levels[1]);
+ $this->assertEquals(LogLevel::INFO, $logger->levels[2]);
+
+ try {
+ $logger->clearMessages();
+ $git->commit('fatal: This operation must be run in a work tree');
+ } catch (\Exception $e) {
+ // Nothing to do, this is expected.
+ }
+
+ $this->assertEquals('Error running Git command', $logger->messages[2]);
+ $this->assertArrayHasKey('command', $logger->contexts[2]);
+ $this->assertEquals(LogLevel::ERROR, $logger->levels[2]);
+ }
+
+ public function testLogBypassedCommand()
+ {
+ $logger = new TestLogger();
+ $this->wrapper->addLoggerListener(new GitLoggerListener($logger));
+
+ $command = GitCommand::getInstance('status', array('s' => true));
+ $command->bypass();
+
+ $this->wrapper->run($command);
+
+ $this->assertEquals('Git command bypassed', $logger->messages[1]);
+ $this->assertArrayHasKey('command', $logger->contexts[1]);
+ $this->assertEquals(LogLevel::INFO, $logger->levels[1]);
+ }
+
+ public function tearDown()
+ {
+ parent::tearDown();
+
+ if (is_dir(self::REPO_DIR)) {
+ $this->filesystem->remove(self::REPO_DIR);
+ }
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/GitWorkingCopyTest.php b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/GitWorkingCopyTest.php
new file mode 100644
index 000000000..7e9d86218
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/GitWorkingCopyTest.php
@@ -0,0 +1,558 @@
+wrapper->init(self::REPO_DIR, array('bare' => true));
+
+ // Clone the local repository.
+ $directory = 'build/test/wc_init';
+ $git = $this->wrapper->clone('file://' . realpath(self::REPO_DIR), $directory);
+ $git->config('user.email', self::CONFIG_EMAIL);
+ $git->config('user.name', self::CONFIG_NAME);
+
+ // Create the initial structure.
+ file_put_contents($directory . '/change.me', "unchanged\n");
+ $this->filesystem->touch($directory . '/move.me');
+ $this->filesystem->mkdir($directory . '/a.directory', 0755);
+ $this->filesystem->touch($directory . '/a.directory/remove.me');
+
+ // Initial commit.
+ $git
+ ->add('*')
+ ->commit('Initial commit.')
+ ->push('origin', 'master', array('u' => true))
+ ;
+
+ // Create a branch, add a file.
+ $branch = 'test-branch';
+ file_put_contents($directory . '/branch.txt', "$branch\n");
+ $git
+ ->checkoutNewBranch($branch)
+ ->add('branch.txt')
+ ->commit('Committed testing branch.')
+ ->push('origin', $branch, array('u' => true))
+ ;
+
+ // Create a tag of the branch.
+ $git
+ ->tag('test-tag')
+ ->pushTags()
+ ;
+
+ $this->filesystem->remove($directory);
+ }
+
+ /**
+ * Removes the local repository.
+ */
+ public function tearDown()
+ {
+ parent::tearDown();
+
+ $this->filesystem->remove(self::REPO_DIR);
+
+ if (is_dir(self::WORKING_DIR)) {
+ $this->filesystem->remove(self::WORKING_DIR);
+ }
+ }
+
+ /**
+ * Clones the local repo and returns an initialized GitWorkingCopy object.
+ *
+ * @param string $directory
+ * The directory that the repository is being cloned to, defaults to
+ * "test/wc".
+ *
+ * @return \GitWrapper\GitWorkingCopy
+ */
+ public function getWorkingCopy($directory = self::WORKING_DIR)
+ {
+ $git = $this->wrapper->workingCopy($directory);
+ $git
+ ->cloneRepository('file://' . realpath(self::REPO_DIR))
+ ->config('user.email', self::CONFIG_EMAIL)
+ ->config('user.name', self::CONFIG_NAME)
+ ->clearOutput()
+ ;
+ return $git;
+ }
+
+ /**
+ * @expectedException \BadMethodCallException
+ */
+ public function testCallError()
+ {
+ $git = $this->getWorkingCopy();
+ $git->badMethod();
+ }
+
+ public function testIsCloned()
+ {
+ $git = $this->getWorkingCopy();
+ $this->assertTrue($git->isCloned());
+ }
+
+ public function testGetOutput()
+ {
+ $git = $this->getWorkingCopy();
+
+ // Test getting output of a simple status command.
+ $output = (string) $git->status();
+ $this->assertTrue(strpos($output, 'nothing to commit') !== false);
+
+ // Getting output should clear the buffer.
+ $cleared = (string) $git;
+ $this->assertEmpty($cleared);
+ }
+
+ public function testClearOutput()
+ {
+ $git = $this->getWorkingCopy();
+
+ // Put stuff in the output buffer.
+ $git->status();
+
+ $git->clearOutput();
+ $output = $git->getOutput();
+ $this->assertEmpty($output);
+ }
+
+ public function testHasChanges()
+ {
+ $git = $this->getWorkingCopy();
+ $this->assertFalse($git->hasChanges());
+
+ file_put_contents(self::WORKING_DIR . '/change.me', "changed\n");
+ $this->assertTrue($git->hasChanges());
+ }
+
+ public function testGetBranches()
+ {
+ $git = $this->getWorkingCopy();
+ $branches = $git->getBranches();
+
+ $this->assertTrue($branches instanceof \GitWrapper\GitBranches);
+
+ // Dumb count checks. Is there a better way to do this?
+ $allBranches = 0;
+ foreach ($branches as $branch) {
+ $allBranches++;
+ }
+ $this->assertEquals($allBranches, 4);
+
+ $remoteBranches = $branches->remote();
+ $this->assertEquals(count($remoteBranches), 3);
+ }
+
+ public function testFetchAll()
+ {
+ $git = $this->getWorkingCopy();
+
+ $output = rtrim((string) $git->fetchAll());
+
+ $this->assertEquals('Fetching origin', $output);
+ }
+
+ public function testGitAdd()
+ {
+ $git = $this->getWorkingCopy();
+ $this->filesystem->touch(self::WORKING_DIR . '/add.me');
+
+ $git->add('add.me');
+
+ $match = (bool) preg_match('@A\\s+add\\.me@s', $git->getStatus());
+ $this->assertTrue($match);
+ }
+
+ public function testGitApply()
+ {
+ $git = $this->getWorkingCopy();
+
+ $patch = <<apply('patch.txt');
+ $this->assertRegExp('@\?\?\\s+FileCreatedByPatch\\.txt@s', $git->getStatus());
+ $this->assertEquals("contents\n", file_get_contents(self::WORKING_DIR . '/FileCreatedByPatch.txt'));
+ }
+
+ public function testGitRm()
+ {
+ $git = $this->getWorkingCopy();
+ $git->rm('a.directory/remove.me');
+ $this->assertFalse(is_file(self::WORKING_DIR . '/a.directory/remove.me'));
+ }
+
+ public function testGitMv()
+ {
+ $git = $this->getWorkingCopy();
+ $git->mv('move.me', 'moved');
+
+ $this->assertFalse(is_file(self::WORKING_DIR . '/move.me'));
+ $this->assertTrue(is_file(self::WORKING_DIR . '/moved'));
+ }
+
+ public function testGitBranch()
+ {
+ $branchName = $this->randomString();
+
+ // Create the branch.
+ $git = $this->getWorkingCopy();
+ $git->branch($branchName);
+
+ // Get list of local branches.
+ $branches = (string) $git->branch();
+
+ // Check that our branch is there.
+ $this->assertTrue(strpos($branches, $branchName) !== false);
+ }
+
+ public function testGitLog()
+ {
+ $git = $this->getWorkingCopy();
+ $output = (string) $git->log();
+ return $this->assertTrue(strpos($output, 'Initial commit.') !== false);
+ }
+
+ public function testGitConfig()
+ {
+ $git = $this->getWorkingCopy();
+ $email = rtrim((string) $git->config('user.email'));
+ $this->assertEquals('opensource@chrispliakas.com', $email);
+ }
+
+ public function testGitTag()
+ {
+ $tag = $this->randomString();
+
+ $git = $this->getWorkingCopy();
+ $git
+ ->tag($tag)
+ ->pushTag($tag)
+ ;
+
+ $tags = (string) $git->tag();
+ $this->assertTrue(strpos($tags, $tag) !== false);
+ }
+
+ public function testGitClean()
+ {
+ $git = $this->getWorkingCopy();
+
+ file_put_contents(self::WORKING_DIR . '/untracked.file', "untracked\n");
+
+ $result = $git
+ ->clean('-d', '-f')
+ ;
+
+ $this->assertSame($git, $result);
+ $this->assertFileNotExists(self::WORKING_DIR . '/untracked.file');
+ }
+
+ public function testGitReset()
+ {
+ $git = $this->getWorkingCopy();
+ file_put_contents(self::WORKING_DIR . '/change.me', "changed\n");
+
+ $this->assertTrue($git->hasChanges());
+ $git->reset(array('hard' => true));
+ $this->assertFalse($git->hasChanges());
+ }
+
+ public function testGitStatus()
+ {
+ $git = $this->getWorkingCopy();
+ file_put_contents(self::WORKING_DIR . '/change.me', "changed\n");
+ $output = (string) $git->status(array('s' => true));
+ $this->assertEquals(" M change.me\n", $output);
+ }
+
+ public function testGitPull()
+ {
+ $git = $this->getWorkingCopy();
+ $output = (string) $git->pull();
+ $this->assertEquals("Already up-to-date.\n", $output);
+ }
+
+ public function testGitArchive()
+ {
+ $archiveName = uniqid().'.tar';
+ $archivePath = '/tmp/'.$archiveName;
+ $git = $this->getWorkingCopy();
+ $output = (string) $git->archive('HEAD', array('o' => $archivePath));
+ $this->assertEquals("", $output);
+ $this->assertFileExists($archivePath);
+ }
+
+ /**
+ * This tests an odd case where sometimes even though a command fails and an exception is thrown
+ * the result of Process::getErrorOutput() is empty because the output is sent to STDOUT instead of STDERR. So
+ * there's a code path in GitProcess::run() to check the output from Process::getErrorOutput() and if it's empty use
+ * the result from Process::getOutput() instead
+ */
+ public function testGitPullErrorWithEmptyErrorOutput()
+ {
+ $git = $this->getWorkingCopy();
+
+ try {
+ $git->commit('Nothing to commit so generates an error / not error');
+ } catch(GitException $exception) {
+ $errorOutput = $exception->getMessage();
+ }
+
+ $this->assertTrue(strpos($errorOutput, "Your branch is up-to-date with 'origin/master'.") !== false);
+ }
+
+ public function testGitDiff()
+ {
+ $git = $this->getWorkingCopy();
+ file_put_contents(self::WORKING_DIR . '/change.me', "changed\n");
+ $output = (string) $git->diff();
+ $this->assertTrue(strpos($output, 'diff --git a/change.me b/change.me') === 0);
+ }
+
+ public function testGitGrep()
+ {
+ $git = $this->getWorkingCopy();
+ $output = (string) $git->grep('changed', '--', '*.me');
+ $this->assertTrue(strpos($output, 'change.me') === 0);
+ }
+
+ public function testGitShow()
+ {
+ $git = $this->getWorkingCopy();
+ $output = (string) $git->show('test-tag');
+ $this->assertTrue(strpos($output, 'commit ') === 0);
+ }
+
+ public function testGitBisect()
+ {
+ $git = $this->getWorkingCopy();
+ $output = (string) $git->bisect('help');
+ $this->assertTrue(stripos($output, 'usage: git bisect') === 0);
+ }
+
+ public function testGitRemote()
+ {
+ $git = $this->getWorkingCopy();
+ $output = (string) $git->remote();
+ $this->assertEquals(rtrim($output), 'origin');
+ }
+
+ public function testRebase()
+ {
+ $git = $this->getWorkingCopy();
+ $git
+ ->checkout('test-branch')
+ ->clearOutput()
+ ;
+
+ $output = (string) $git->rebase('test-branch', 'master');
+ $this->assertTrue(strpos($output, 'First, rewinding head') === 0);
+ }
+
+ public function testMerge()
+ {
+ $git = $this->getWorkingCopy();
+ $git
+ ->checkout('test-branch')
+ ->checkout('master')
+ ->clearOutput()
+ ;
+
+ $output = (string) $git->merge('test-branch');
+ $this->assertTrue(strpos($output, 'Updating ') === 0);
+ }
+
+ public function testOutputListener()
+ {
+ $git = $this->getWorkingCopy();
+
+ $listener = new Event\TestOutputListener();
+ $git->getWrapper()->addOutputListener($listener);
+
+ $git->status();
+ $event = $listener->getLastEvent();
+
+ $expectedType = Process::OUT;
+ $this->assertEquals($expectedType, $event->getType());
+
+ $this->assertTrue(stripos($event->getBuffer(), 'nothing to commit') !== false);
+ }
+
+ public function testLiveOutput()
+ {
+ $git = $this->getWorkingCopy();
+
+ // Capture output written to STDOUT and use echo so we can suppress and
+ // capture it using normal output buffering.
+ stream_filter_register('suppress', '\GitWrapper\Test\StreamSuppressFilter');
+ $stdoutSuppress = stream_filter_append(STDOUT, 'suppress');
+
+ $git->getWrapper()->streamOutput(true);
+ ob_start();
+ $git->status();
+ $contents = ob_get_contents();
+ ob_end_clean();
+
+ $this->assertTrue(stripos($contents, 'nothing to commit') !== false);
+
+ $git->clearOutput();
+ $git->getWrapper()->streamOutput(false);
+ ob_start();
+ $git->status();
+ $empty = ob_get_contents();
+ ob_end_clean();
+
+ $this->assertEmpty($empty);
+
+ stream_filter_remove($stdoutSuppress);
+ }
+
+ public function testCommitWithAuthor()
+ {
+ $git = $this->getWorkingCopy();
+ file_put_contents(self::WORKING_DIR . '/commit.txt', "created\n");
+
+ $this->assertTrue($git->hasChanges());
+
+ $git
+ ->add('commit.txt')
+ ->commit(array(
+ 'm' => 'Committed testing branch.',
+ 'a' => true,
+ 'author' => 'test '
+ ))
+ ;
+
+ $output = (string) $git->log();
+ $this->assertContains('Committed testing branch', $output);
+ $this->assertContains('Author: test ', $output);
+ }
+
+ public function testIsTracking()
+ {
+ $git = $this->getWorkingCopy();
+
+ // The master branch is a remote tracking branch.
+ $this->assertTrue($git->isTracking());
+
+ // Create a new branch without pushing it, so it does not have a remote.
+ $git->checkoutNewBranch('non-tracking-branch');
+ $this->assertFalse($git->isTracking());
+ }
+
+ public function testIsUpToDate()
+ {
+ $git = $this->getWorkingCopy();
+
+ // The default test branch is up-to-date with its remote.
+ $git->checkout('test-branch');
+ $this->assertTrue($git->isUpToDate());
+
+ // If we create a new commit, we are still up-to-date.
+ file_put_contents(self::WORKING_DIR . '/commit.txt', "created\n");
+ $git
+ ->add('commit.txt')
+ ->commit(array(
+ 'm' => '1 commit ahead. Still up-to-date.',
+ 'a' => true,
+ ))
+ ;
+ $this->assertTrue($git->isUpToDate());
+
+ // Reset the branch to its first commit, so that it is 1 commit behind.
+ $git->reset(
+ 'HEAD~2',
+ array('hard' => true)
+ );
+
+ $this->assertFalse($git->isUpToDate());
+ }
+
+ public function testIsAhead()
+ {
+ $git = $this->getWorkingCopy();
+
+ // The default master branch is not ahead of the remote.
+ $this->assertFalse($git->isAhead());
+
+ // Create a new commit, so that the branch is 1 commit ahead.
+ file_put_contents(self::WORKING_DIR . '/commit.txt', "created\n");
+ $git
+ ->add('commit.txt')
+ ->commit(array('m' => '1 commit ahead.'))
+ ;
+
+ $this->assertTrue($git->isAhead());
+ }
+
+ public function testIsBehind()
+ {
+ $git = $this->getWorkingCopy();
+
+ // The default test branch is not behind the remote.
+ $git->checkout('test-branch');
+ $this->assertFalse($git->isBehind());
+
+ // Reset the branch to its parent commit, so that it is 1 commit behind.
+ $git->reset(
+ 'HEAD^',
+ array('hard' => true)
+ );
+
+ $this->assertTrue($git->isBehind());
+ }
+
+ public function testNeedsMerge()
+ {
+ $git = $this->getWorkingCopy();
+
+ // The default test branch does not need to be merged with the remote.
+ $git->checkout('test-branch');
+ $this->assertFalse($git->needsMerge());
+
+ // Reset the branch to its parent commit, so that it is 1 commit behind.
+ // This does not require the branches to be merged.
+ $git->reset(
+ 'HEAD^',
+ array('hard' => true)
+ );
+ $this->assertFalse($git->needsMerge());
+
+ // Create a new commit, so that the branch is also 1 commit ahead. Now a
+ // merge is needed.
+ file_put_contents(self::WORKING_DIR . '/commit.txt', "created\n");
+ $git
+ ->add('commit.txt')
+ ->commit(array('m' => '1 commit ahead.'))
+ ;
+ $this->assertTrue($git->needsMerge());
+
+ // Merge the remote, so that we are no longer behind, but only ahead. A
+ // merge should then no longer be needed.
+ $git->merge('@{u}');
+ $this->assertFalse($git->needsMerge());
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/GitWrapperTest.php b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/GitWrapperTest.php
new file mode 100644
index 000000000..f21ce54ec
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/GitWrapperTest.php
@@ -0,0 +1,199 @@
+wrapper->setGitBinary($binary);
+ $this->assertEquals($binary, $this->wrapper->getGitBinary());
+ }
+
+ public function testSetDispatcher()
+ {
+ $dispatcher = new TestDispatcher();
+ $this->wrapper->setDispatcher($dispatcher);
+ $this->assertEquals($dispatcher, $this->wrapper->getDispatcher());
+ }
+
+ public function testSetTimeout()
+ {
+ $timeout = mt_rand(1, 60);
+ $this->wrapper->setTimeout($timeout);
+ $this->assertEquals($timeout, $this->wrapper->getTimeout());
+ }
+
+ public function testEnvVar()
+ {
+ $var = $this->randomString();
+ $value = $this->randomString();
+
+ $this->wrapper->setEnvVar($var, $value);
+ $this->assertEquals($value, $this->wrapper->getEnvVar($var));
+
+ $envvars = $this->wrapper->getEnvVars();
+ $this->assertEquals($value, $envvars[$var]);
+
+ $this->wrapper->unsetEnvVar($var);
+ $this->assertNull($this->wrapper->getEnvVar($var));
+ }
+
+ public function testEnvVarDefault()
+ {
+ $var = $this->randomString();
+ $default = $this->randomString();
+ $this->assertEquals($default, $this->wrapper->getEnvVar($var, $default));
+ }
+
+ public function testProcOptions()
+ {
+ $value = (bool) mt_rand(0, 1);
+ $options = array('suppress_errors' => $value);
+ $this->wrapper->setProcOptions($options);
+ $this->assertEquals($options, $this->wrapper->getProcOptions());
+ }
+
+ public function testGitVersion()
+ {
+ $version = $this->wrapper->version();
+ $this->assertGitVersion($version);
+ }
+
+ public function testSetPrivateKey()
+ {
+ $key = './test/id_rsa';
+ $keyExpected = realpath($key);
+ $sshWrapperExpected = realpath(__DIR__ . '/../../../bin/git-ssh-wrapper.sh');
+
+ $this->wrapper->setPrivateKey($key);
+ $this->assertEquals($keyExpected, $this->wrapper->getEnvVar('GIT_SSH_KEY'));
+ $this->assertEquals(22, $this->wrapper->getEnvVar('GIT_SSH_PORT'));
+ $this->assertEquals($sshWrapperExpected, $this->wrapper->getEnvVar('GIT_SSH'));
+ }
+
+ public function testSetPrivateKeyPort()
+ {
+ $port = mt_rand(1024, 10000);
+ $this->wrapper->setPrivateKey('./test/id_rsa', $port);
+ $this->assertEquals($port, $this->wrapper->getEnvVar('GIT_SSH_PORT'));
+ }
+
+ public function testSetPrivateKeyWrapper()
+ {
+ $sshWrapper = './test/dummy-wrapper.sh';
+ $sshWrapperExpected = realpath($sshWrapper);
+ $this->wrapper->setPrivateKey('./test/id_rsa', 22, $sshWrapper);
+ $this->assertEquals($sshWrapperExpected, $this->wrapper->getEnvVar('GIT_SSH'));
+ }
+
+ /**
+ * @expectedException \GitWrapper\GitException
+ */
+ public function testSetPrivateKeyError()
+ {
+ $badKey = './test/id_rsa_bad';
+ $this->wrapper->setPrivateKey($badKey);
+ }
+
+ /**
+ * @expectedException \GitWrapper\GitException
+ */
+ public function testSetPrivateKeyWrapperError()
+ {
+ $badWrapper = './test/dummy-wrapper-bad.sh';
+ $this->wrapper->setPrivateKey('./test/id_rsa', 22, $badWrapper);
+ }
+
+ public function testUnsetPrivateKey()
+ {
+ // Set and unset the private key.
+ $key = './test/id_rsa';
+ $sshWrapper = './test/dummy-wrapper.sh';
+ $this->wrapper->setPrivateKey($key, 22, $sshWrapper);
+ $this->wrapper->unsetPrivateKey();
+
+ $this->assertNull($this->wrapper->getEnvVar('GIT_SSH_KEY'));
+ $this->assertNull($this->wrapper->getEnvVar('GIT_SSH_PORT'));
+ $this->assertNull($this->wrapper->getEnvVar('GIT_SSH'));
+ }
+
+ public function testGitCommand()
+ {
+ $version = $this->wrapper->git('--version');
+ $this->assertGitVersion($version);
+ }
+
+ /**
+ * @expectedException \GitWrapper\GitException
+ */
+ public function testGitCommandError()
+ {
+ $this->runBadCommand();
+ }
+
+ public function testGitRun()
+ {
+ $command = GitCommand::getInstance();
+ $command->setFlag('version');
+ $command->setDirectory('./test'); // Directory just has to exist.
+ $version = $this->wrapper->run($command);
+ $this->assertGitVersion($version);
+ }
+
+ /**
+ * @expectedException \GitWrapper\GitException
+ */
+ public function testGitRunDirectoryError()
+ {
+ $command = GitCommand::getInstance();
+ $command->setFlag('version');
+ $command->setDirectory('/some/bad/directory');
+ $this->wrapper->run($command);
+ }
+
+ public function testWrapperExecutable()
+ {
+ $sshWrapper = realpath(__DIR__ . '/../../../bin/git-ssh-wrapper.sh');
+ $this->assertTrue(is_executable($sshWrapper));
+ }
+
+ /**
+ * @expectedException \BadMethodCallException
+ */
+ public function testCallError()
+ {
+ $this->wrapper->badMethod();
+ }
+
+ public function testWorkingCopy()
+ {
+ $directory = './' . $this->randomString();
+ $git = $this->wrapper->workingCopy($directory);
+
+ $this->assertTrue($git instanceof GitWorkingCopy);
+ $this->assertEquals($directory, $git->getDirectory());
+ $this->assertEquals($this->wrapper, $git->getWrapper());
+ }
+
+ public function testParseRepositoryName()
+ {
+ $nameGit = GitWrapper::parseRepositoryName('git@github.com:cpliakas/git-wrapper.git');
+ $this->assertEquals($nameGit, 'git-wrapper');
+
+ $nameHttps = GitWrapper::parseRepositoryName('https://github.com/cpliakas/git-wrapper.git');
+ $this->assertEquals($nameHttps, 'git-wrapper');
+ }
+
+ public function testCloneWothoutDirectory()
+ {
+ $this->addBypassListener();
+ $this->wrapper->clone('file:///' . $this->randomString());
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/GitWrapperTestCase.php b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/GitWrapperTestCase.php
new file mode 100644
index 000000000..e567de67b
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/GitWrapperTestCase.php
@@ -0,0 +1,119 @@
+filesystem = new Filesystem();
+ $this->wrapper = new GitWrapper();
+ }
+
+ /**
+ * Generates a random string.
+ *
+ * @param type $length
+ * The string length, defaults to 8 characters.
+ *
+ * @return string
+ *
+ * @see http://api.drupal.org/api/drupal/modules%21simpletest%21drupal_web_test_case.php/function/DrupalTestCase%3A%3ArandomName/7
+ */
+ public function randomString($length = 8)
+ {
+ $values = array_merge(range(65, 90), range(97, 122), range(48, 57));
+ $max = count($values) - 1;
+ $str = chr(mt_rand(97, 122));
+ for ($i = 1; $i < $length; $i++) {
+ $str .= chr($values[mt_rand(0, $max)]);
+ }
+ return $str;
+ }
+
+ /**
+ * Adds the test listener for all events, returns the listener.
+ *
+ * @return \GitWrapper\Test\Event\TestListener
+ */
+ public function addListener()
+ {
+ $dispatcher = $this->wrapper->getDispatcher();
+ $listener = new TestListener();
+
+ $dispatcher->addListener(GitEvents::GIT_PREPARE, array($listener, 'onPrepare'));
+ $dispatcher->addListener(GitEvents::GIT_SUCCESS, array($listener, 'onSuccess'));
+ $dispatcher->addListener(GitEvents::GIT_ERROR, array($listener, 'onError'));
+ $dispatcher->addListener(GitEvents::GIT_BYPASS, array($listener, 'onBypass'));
+
+ return $listener;
+ }
+
+ /**
+ * Adds the bypass listener so that Git commands are not run.
+ *
+ * @return \GitWrapper\Test\Event\TestBypassListener
+ */
+ public function addBypassListener()
+ {
+ $listener = new TestBypassListener();
+ $dispatcher = $this->wrapper->getDispatcher();
+ $dispatcher->addListener(GitEvents::GIT_PREPARE, array($listener, 'onPrepare'), -5);
+ return $listener;
+ }
+
+ /**
+ * Asserts a correct Git version string was returned.
+ *
+ * @param type $version
+ * The version returned by the `git --version` command.
+ */
+ public function assertGitVersion($version)
+ {
+ $match = preg_match('/^git version [.0-9]+/', $version);
+ $this->assertNotEmpty($match);
+ }
+
+ /**
+ * Executes a bad command.
+ *
+ * @param bool $catchException
+ * Whether to catch the exception to continue script execution, defaults
+ * to false.
+ */
+ public function runBadCommand($catchException = false)
+ {
+ try {
+ $this->wrapper->git('a-bad-command');
+ } catch (GitException $e) {
+ if (!$catchException) {
+ throw $e;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/StreamSuppressFilter.php b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/StreamSuppressFilter.php
new file mode 100644
index 000000000..c8ef3def5
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/StreamSuppressFilter.php
@@ -0,0 +1,21 @@
+data;
+ $bucket->data = '';
+ $consumed += $bucket->datalen;
+ stream_bucket_append($out, $bucket);
+ }
+ return PSFS_PASS_ON;
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/TestLogger.php b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/TestLogger.php
new file mode 100644
index 000000000..32785d0db
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/GitWrapper/Test/TestLogger.php
@@ -0,0 +1,28 @@
+messages[] = $message;
+ $this->levels[] = $level;
+ $this->contexts[] = $context;
+ }
+
+ public function clearMessages()
+ {
+ $this->messages = $this->levels = $this->contexts = array();
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/bootstrap.php b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/bootstrap.php
new file mode 100644
index 000000000..d2d2fd7a8
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/bootstrap.php
@@ -0,0 +1,11 @@
+add('GitWrapper\Test', 'test');
+$loader->register();
diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/dummy-wrapper.sh b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/dummy-wrapper.sh
new file mode 100644
index 000000000..e69de29bb
diff --git a/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/id_rsa b/modules/devshop/devshop_projects/drush/vendor/cpliakas/git-wrapper/test/id_rsa
new file mode 100644
index 000000000..e69de29bb
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/.gitignore b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/.gitignore
new file mode 100644
index 000000000..c49a5d8df
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/.gitignore
@@ -0,0 +1,3 @@
+vendor/
+composer.lock
+phpunit.xml
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/ApcClassLoader.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/ApcClassLoader.php
new file mode 100644
index 000000000..843f2cc83
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/ApcClassLoader.php
@@ -0,0 +1,139 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ClassLoader;
+
+/**
+ * ApcClassLoader implements a wrapping autoloader cached in APC for PHP 5.3.
+ *
+ * It expects an object implementing a findFile method to find the file. This
+ * allows using it as a wrapper around the other loaders of the component (the
+ * ClassLoader for instance) but also around any other autoloaders following
+ * this convention (the Composer one for instance).
+ *
+ * // with a Symfony autoloader
+ * use Symfony\Component\ClassLoader\ClassLoader;
+ *
+ * $loader = new ClassLoader();
+ * $loader->addPrefix('Symfony\Component', __DIR__.'/component');
+ * $loader->addPrefix('Symfony', __DIR__.'/framework');
+ *
+ * // or with a Composer autoloader
+ * use Composer\Autoload\ClassLoader;
+ *
+ * $loader = new ClassLoader();
+ * $loader->add('Symfony\Component', __DIR__.'/component');
+ * $loader->add('Symfony', __DIR__.'/framework');
+ *
+ * $cachedLoader = new ApcClassLoader('my_prefix', $loader);
+ *
+ * // activate the cached autoloader
+ * $cachedLoader->register();
+ *
+ * // eventually deactivate the non-cached loader if it was registered previously
+ * // to be sure to use the cached one.
+ * $loader->unregister();
+ *
+ * @author Fabien Potencier
+ * @author Kris Wallsmith
+ */
+class ApcClassLoader
+{
+ private $prefix;
+
+ /**
+ * A class loader object that implements the findFile() method.
+ *
+ * @var object
+ */
+ protected $decorated;
+
+ /**
+ * Constructor.
+ *
+ * @param string $prefix The APC namespace prefix to use.
+ * @param object $decorated A class loader object that implements the findFile() method.
+ *
+ * @throws \RuntimeException
+ * @throws \InvalidArgumentException
+ */
+ public function __construct($prefix, $decorated)
+ {
+ if (!function_exists('apcu_fetch')) {
+ throw new \RuntimeException('Unable to use ApcClassLoader as APC is not installed.');
+ }
+
+ if (!method_exists($decorated, 'findFile')) {
+ throw new \InvalidArgumentException('The class finder must implement a "findFile" method.');
+ }
+
+ $this->prefix = $prefix;
+ $this->decorated = $decorated;
+ }
+
+ /**
+ * 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
+ */
+ public function loadClass($class)
+ {
+ if ($file = $this->findFile($class)) {
+ require $file;
+
+ return true;
+ }
+ }
+
+ /**
+ * Finds a file by class name while caching lookups to APC.
+ *
+ * @param string $class A class name to resolve to file
+ *
+ * @return string|null
+ */
+ public function findFile($class)
+ {
+ if (false === $file = apcu_fetch($this->prefix.$class)) {
+ apcu_store($this->prefix.$class, $file = $this->decorated->findFile($class));
+ }
+
+ return $file;
+ }
+
+ /**
+ * Passes through all unknown calls onto the decorated object.
+ */
+ public function __call($method, $args)
+ {
+ return call_user_func_array(array($this->decorated, $method), $args);
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/CHANGELOG.md b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/CHANGELOG.md
new file mode 100644
index 000000000..64ef8d9c9
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/CHANGELOG.md
@@ -0,0 +1,36 @@
+CHANGELOG
+=========
+
+3.0.0
+-----
+
+ * The DebugClassLoader class has been removed
+ * The DebugUniversalClassLoader class has been removed
+ * The UniversalClassLoader class has been removed
+ * The ApcUniversalClassLoader class has been removed
+
+2.4.0
+-----
+
+ * deprecated the UniversalClassLoader in favor of the ClassLoader class instead
+ * deprecated the ApcUniversalClassLoader in favor of the ApcClassLoader class instead
+ * deprecated the DebugUniversalClassLoader in favor of the DebugClassLoader class from the Debug component
+ * deprecated the DebugClassLoader as it has been moved to the Debug component instead
+
+2.3.0
+-----
+
+ * added a WinCacheClassLoader for WinCache
+
+2.1.0
+-----
+
+ * added a DebugClassLoader able to wrap any autoloader providing a findFile
+ method
+ * added a new ApcClassLoader and XcacheClassLoader using composition to wrap
+ other loaders
+ * added a new ClassLoader which does not distinguish between namespaced and
+ pear-like classes (as the PEAR convention is a subset of PSR-0) and
+ supports using Composer's namespace maps
+ * added a class map generator
+ * added support for loading globally-installed PEAR packages
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/ClassCollectionLoader.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/ClassCollectionLoader.php
new file mode 100644
index 000000000..19d450b93
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/ClassCollectionLoader.php
@@ -0,0 +1,374 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ClassLoader;
+
+/**
+ * ClassCollectionLoader.
+ *
+ * @author Fabien Potencier
+ */
+class ClassCollectionLoader
+{
+ private static $loaded;
+ private static $seen;
+ private static $useTokenizer = true;
+
+ /**
+ * Loads a list of classes and caches them in one big file.
+ *
+ * @param array $classes An array of classes to load
+ * @param string $cacheDir A cache directory
+ * @param string $name The cache name prefix
+ * @param bool $autoReload Whether to flush the cache when the cache is stale or not
+ * @param bool $adaptive Whether to remove already declared classes or not
+ * @param string $extension File extension of the resulting file
+ *
+ * @throws \InvalidArgumentException When class can't be loaded
+ */
+ public static function load($classes, $cacheDir, $name, $autoReload, $adaptive = false, $extension = '.php')
+ {
+ // each $name can only be loaded once per PHP process
+ if (isset(self::$loaded[$name])) {
+ return;
+ }
+
+ self::$loaded[$name] = true;
+
+ $declared = array_merge(get_declared_classes(), get_declared_interfaces(), get_declared_traits());
+
+ if ($adaptive) {
+ // don't include already declared classes
+ $classes = array_diff($classes, $declared);
+
+ // the cache is different depending on which classes are already declared
+ $name = $name.'-'.substr(hash('sha256', implode('|', $classes)), 0, 5);
+ }
+
+ $classes = array_unique($classes);
+
+ $cache = $cacheDir.'/'.$name.$extension;
+
+ // auto-reload
+ $reload = false;
+ if ($autoReload) {
+ $metadata = $cache.'.meta';
+ if (!is_file($metadata) || !is_file($cache)) {
+ $reload = true;
+ } else {
+ $time = filemtime($cache);
+ $meta = unserialize(file_get_contents($metadata));
+
+ sort($meta[1]);
+ sort($classes);
+
+ if ($meta[1] != $classes) {
+ $reload = true;
+ } else {
+ foreach ($meta[0] as $resource) {
+ if (!is_file($resource) || filemtime($resource) > $time) {
+ $reload = true;
+
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ if (!$reload && is_file($cache)) {
+ require_once $cache;
+
+ return;
+ }
+
+ $files = array();
+ $content = '';
+ foreach (self::getOrderedClasses($classes) as $class) {
+ if (in_array($class->getName(), $declared)) {
+ continue;
+ }
+
+ $files[] = $class->getFileName();
+
+ $c = preg_replace(array('/^\s*<\?php/', '/\?>\s*$/'), '', file_get_contents($class->getFileName()));
+
+ // fakes namespace declaration for global code
+ if (!$class->inNamespace()) {
+ $c = "\nnamespace\n{\n".$c."\n}\n";
+ }
+
+ $c = self::fixNamespaceDeclarations('= 70000) {
+ // PHP 7 memory manager will not release after token_get_all(), see https://bugs.php.net/70098
+ unset($tokens, $rawChunk);
+ gc_mem_caches();
+ }
+
+ return $output;
+ }
+
+ /**
+ * This method is only useful for testing.
+ */
+ public static function enableTokenizer($bool)
+ {
+ self::$useTokenizer = (bool) $bool;
+ }
+
+ /**
+ * Strips leading & trailing ws, multiple EOL, multiple ws.
+ *
+ * @param string $code Original PHP code
+ *
+ * @return string compressed code
+ */
+ private static function compressCode($code)
+ {
+ return preg_replace(
+ array('/^\s+/m', '/\s+$/m', '/([\n\r]+ *[\n\r]+)+/', '/[ \t]+/'),
+ array('', '', "\n", ' '),
+ $code
+ );
+ }
+
+ /**
+ * Writes a cache file.
+ *
+ * @param string $file Filename
+ * @param string $content Temporary file content
+ *
+ * @throws \RuntimeException when a cache file cannot be written
+ */
+ private static function writeCacheFile($file, $content)
+ {
+ $tmpFile = tempnam(dirname($file), basename($file));
+ if (false !== @file_put_contents($tmpFile, $content) && @rename($tmpFile, $file)) {
+ @chmod($file, 0666 & ~umask());
+
+ return;
+ }
+
+ throw new \RuntimeException(sprintf('Failed to write cache file "%s".', $file));
+ }
+
+ /**
+ * Gets an ordered array of passed classes including all their dependencies.
+ *
+ * @param array $classes
+ *
+ * @return \ReflectionClass[] An array of sorted \ReflectionClass instances (dependencies added if needed)
+ *
+ * @throws \InvalidArgumentException When a class can't be loaded
+ */
+ private static function getOrderedClasses(array $classes)
+ {
+ $map = array();
+ self::$seen = array();
+ foreach ($classes as $class) {
+ try {
+ $reflectionClass = new \ReflectionClass($class);
+ } catch (\ReflectionException $e) {
+ throw new \InvalidArgumentException(sprintf('Unable to load class "%s"', $class));
+ }
+
+ $map = array_merge($map, self::getClassHierarchy($reflectionClass));
+ }
+
+ return $map;
+ }
+
+ private static function getClassHierarchy(\ReflectionClass $class)
+ {
+ if (isset(self::$seen[$class->getName()])) {
+ return array();
+ }
+
+ self::$seen[$class->getName()] = true;
+
+ $classes = array($class);
+ $parent = $class;
+ while (($parent = $parent->getParentClass()) && $parent->isUserDefined() && !isset(self::$seen[$parent->getName()])) {
+ self::$seen[$parent->getName()] = true;
+
+ array_unshift($classes, $parent);
+ }
+
+ $traits = array();
+
+ foreach ($classes as $c) {
+ foreach (self::resolveDependencies(self::computeTraitDeps($c), $c) as $trait) {
+ if ($trait !== $c) {
+ $traits[] = $trait;
+ }
+ }
+ }
+
+ return array_merge(self::getInterfaces($class), $traits, $classes);
+ }
+
+ private static function getInterfaces(\ReflectionClass $class)
+ {
+ $classes = array();
+
+ foreach ($class->getInterfaces() as $interface) {
+ $classes = array_merge($classes, self::getInterfaces($interface));
+ }
+
+ if ($class->isUserDefined() && $class->isInterface() && !isset(self::$seen[$class->getName()])) {
+ self::$seen[$class->getName()] = true;
+
+ $classes[] = $class;
+ }
+
+ return $classes;
+ }
+
+ private static function computeTraitDeps(\ReflectionClass $class)
+ {
+ $traits = $class->getTraits();
+ $deps = array($class->getName() => $traits);
+ while ($trait = array_pop($traits)) {
+ if ($trait->isUserDefined() && !isset(self::$seen[$trait->getName()])) {
+ self::$seen[$trait->getName()] = true;
+ $traitDeps = $trait->getTraits();
+ $deps[$trait->getName()] = $traitDeps;
+ $traits = array_merge($traits, $traitDeps);
+ }
+ }
+
+ return $deps;
+ }
+
+ /**
+ * Dependencies resolution.
+ *
+ * This function does not check for circular dependencies as it should never
+ * occur with PHP traits.
+ *
+ * @param array $tree The dependency tree
+ * @param \ReflectionClass $node The node
+ * @param \ArrayObject $resolved An array of already resolved dependencies
+ * @param \ArrayObject $unresolved An array of dependencies to be resolved
+ *
+ * @return \ArrayObject The dependencies for the given node
+ *
+ * @throws \RuntimeException if a circular dependency is detected
+ */
+ private static function resolveDependencies(array $tree, $node, \ArrayObject $resolved = null, \ArrayObject $unresolved = null)
+ {
+ if (null === $resolved) {
+ $resolved = new \ArrayObject();
+ }
+ if (null === $unresolved) {
+ $unresolved = new \ArrayObject();
+ }
+ $nodeName = $node->getName();
+
+ if (isset($tree[$nodeName])) {
+ $unresolved[$nodeName] = $node;
+ foreach ($tree[$nodeName] as $dependency) {
+ if (!$resolved->offsetExists($dependency->getName())) {
+ self::resolveDependencies($tree, $dependency, $resolved, $unresolved);
+ }
+ }
+ $resolved[$nodeName] = $node;
+ unset($unresolved[$nodeName]);
+ }
+
+ return $resolved;
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/ClassLoader.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/ClassLoader.php
new file mode 100644
index 000000000..a506dc094
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/ClassLoader.php
@@ -0,0 +1,203 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ClassLoader;
+
+/**
+ * ClassLoader implements an PSR-0 class loader.
+ *
+ * See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md
+ *
+ * $loader = new ClassLoader();
+ *
+ * // register classes with namespaces
+ * $loader->addPrefix('Symfony\Component', __DIR__.'/component');
+ * $loader->addPrefix('Symfony', __DIR__.'/framework');
+ *
+ * // activate the autoloader
+ * $loader->register();
+ *
+ * // to enable searching the include path (e.g. 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.
+ *
+ * @author Fabien Potencier
+ * @author Jordi Boggiano
+ */
+class ClassLoader
+{
+ private $prefixes = array();
+ private $fallbackDirs = array();
+ private $useIncludePath = false;
+
+ /**
+ * Returns prefixes.
+ *
+ * @return array
+ */
+ public function getPrefixes()
+ {
+ return $this->prefixes;
+ }
+
+ /**
+ * Returns fallback directories.
+ *
+ * @return array
+ */
+ public function getFallbackDirs()
+ {
+ return $this->fallbackDirs;
+ }
+
+ /**
+ * Adds prefixes.
+ *
+ * @param array $prefixes Prefixes to add
+ */
+ public function addPrefixes(array $prefixes)
+ {
+ foreach ($prefixes as $prefix => $path) {
+ $this->addPrefix($prefix, $path);
+ }
+ }
+
+ /**
+ * Registers a set of classes.
+ *
+ * @param string $prefix The classes prefix
+ * @param array|string $paths The location(s) of the classes
+ */
+ public function addPrefix($prefix, $paths)
+ {
+ if (!$prefix) {
+ foreach ((array) $paths as $path) {
+ $this->fallbackDirs[] = $path;
+ }
+
+ return;
+ }
+ if (isset($this->prefixes[$prefix])) {
+ if (is_array($paths)) {
+ $this->prefixes[$prefix] = array_unique(array_merge(
+ $this->prefixes[$prefix],
+ $paths
+ ));
+ } elseif (!in_array($paths, $this->prefixes[$prefix])) {
+ $this->prefixes[$prefix][] = $paths;
+ }
+ } else {
+ $this->prefixes[$prefix] = array_unique((array) $paths);
+ }
+ }
+
+ /**
+ * Turns on searching the include for class files.
+ *
+ * @param bool $useIncludePath
+ */
+ public function setUseIncludePath($useIncludePath)
+ {
+ $this->useIncludePath = (bool) $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;
+ }
+
+ /**
+ * 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
+ */
+ public function loadClass($class)
+ {
+ if ($file = $this->findFile($class)) {
+ require $file;
+
+ return true;
+ }
+ }
+
+ /**
+ * Finds the path to the file where the class is defined.
+ *
+ * @param string $class The name of the class
+ *
+ * @return string|null The path, if found
+ */
+ public function findFile($class)
+ {
+ if (false !== $pos = strrpos($class, '\\')) {
+ // namespaced class name
+ $classPath = str_replace('\\', DIRECTORY_SEPARATOR, substr($class, 0, $pos)).DIRECTORY_SEPARATOR;
+ $className = substr($class, $pos + 1);
+ } else {
+ // PEAR-like class name
+ $classPath = null;
+ $className = $class;
+ }
+
+ $classPath .= str_replace('_', DIRECTORY_SEPARATOR, $className).'.php';
+
+ foreach ($this->prefixes as $prefix => $dirs) {
+ if ($class === strstr($class, $prefix)) {
+ foreach ($dirs as $dir) {
+ if (file_exists($dir.DIRECTORY_SEPARATOR.$classPath)) {
+ return $dir.DIRECTORY_SEPARATOR.$classPath;
+ }
+ }
+ }
+ }
+
+ foreach ($this->fallbackDirs as $dir) {
+ if (file_exists($dir.DIRECTORY_SEPARATOR.$classPath)) {
+ return $dir.DIRECTORY_SEPARATOR.$classPath;
+ }
+ }
+
+ if ($this->useIncludePath && $file = stream_resolve_include_path($classPath)) {
+ return $file;
+ }
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/ClassMapGenerator.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/ClassMapGenerator.php
new file mode 100644
index 000000000..17e95c0bc
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/ClassMapGenerator.php
@@ -0,0 +1,156 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ClassLoader;
+
+/**
+ * ClassMapGenerator.
+ *
+ * @author Gyula Sallai
+ */
+class ClassMapGenerator
+{
+ /**
+ * Generate a class map file.
+ *
+ * @param array|string $dirs Directories or a single path to search in
+ * @param string $file The name of the class map file
+ */
+ public static function dump($dirs, $file)
+ {
+ $dirs = (array) $dirs;
+ $maps = array();
+
+ foreach ($dirs as $dir) {
+ $maps = array_merge($maps, static::createMap($dir));
+ }
+
+ file_put_contents($file, sprintf('isFile()) {
+ continue;
+ }
+
+ $path = $file->getRealPath();
+
+ if (pathinfo($path, PATHINFO_EXTENSION) !== 'php') {
+ continue;
+ }
+
+ $classes = self::findClasses($path);
+
+ if (PHP_VERSION_ID >= 70000) {
+ // PHP 7 memory manager will not release after token_get_all(), see https://bugs.php.net/70098
+ gc_mem_caches();
+ }
+
+ foreach ($classes as $class) {
+ $map[$class] = $path;
+ }
+ }
+
+ return $map;
+ }
+
+ /**
+ * Extract the classes in the given file.
+ *
+ * @param string $path The file to check
+ *
+ * @return array The found classes
+ */
+ private static function findClasses($path)
+ {
+ $contents = file_get_contents($path);
+ $tokens = token_get_all($contents);
+
+ $classes = array();
+
+ $namespace = '';
+ for ($i = 0; isset($tokens[$i]); ++$i) {
+ $token = $tokens[$i];
+
+ if (!isset($token[1])) {
+ continue;
+ }
+
+ $class = '';
+
+ switch ($token[0]) {
+ case T_NAMESPACE:
+ $namespace = '';
+ // If there is a namespace, extract it
+ while (isset($tokens[++$i][1])) {
+ if (in_array($tokens[$i][0], array(T_STRING, T_NS_SEPARATOR))) {
+ $namespace .= $tokens[$i][1];
+ }
+ }
+ $namespace .= '\\';
+ break;
+ case T_CLASS:
+ case T_INTERFACE:
+ case T_TRAIT:
+ // Skip usage of ::class constant
+ $isClassConstant = false;
+ for ($j = $i - 1; $j > 0; --$j) {
+ if (!isset($tokens[$j][1])) {
+ break;
+ }
+
+ if (T_DOUBLE_COLON === $tokens[$j][0]) {
+ $isClassConstant = true;
+ break;
+ } elseif (!in_array($tokens[$j][0], array(T_WHITESPACE, T_DOC_COMMENT, T_COMMENT))) {
+ break;
+ }
+ }
+
+ if ($isClassConstant) {
+ break;
+ }
+
+ // Find the classname
+ while (isset($tokens[++$i][1])) {
+ $t = $tokens[$i];
+ if (T_STRING === $t[0]) {
+ $class .= $t[1];
+ } elseif ('' !== $class && T_WHITESPACE === $t[0]) {
+ break;
+ }
+ }
+
+ $classes[] = ltrim($namespace.$class, '\\');
+ break;
+ default:
+ break;
+ }
+ }
+
+ return $classes;
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/LICENSE b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/LICENSE
new file mode 100644
index 000000000..12a74531e
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2004-2016 Fabien Potencier
+
+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.
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/MapClassLoader.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/MapClassLoader.php
new file mode 100644
index 000000000..1d8bcc2a0
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/MapClassLoader.php
@@ -0,0 +1,68 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ClassLoader;
+
+/**
+ * A class loader that uses a mapping file to look up paths.
+ *
+ * @author Fabien Potencier
+ */
+class MapClassLoader
+{
+ private $map = array();
+
+ /**
+ * Constructor.
+ *
+ * @param array $map A map where keys are classes and values the absolute file path
+ */
+ public function __construct(array $map)
+ {
+ $this->map = $map;
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * Loads the given class or interface.
+ *
+ * @param string $class The name of the class
+ */
+ public function loadClass($class)
+ {
+ if (isset($this->map[$class])) {
+ require $this->map[$class];
+ }
+ }
+
+ /**
+ * Finds the path to the file where the class is defined.
+ *
+ * @param string $class The name of the class
+ *
+ * @return string|null The path, if found
+ */
+ public function findFile($class)
+ {
+ if (isset($this->map[$class])) {
+ return $this->map[$class];
+ }
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Psr4ClassLoader.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Psr4ClassLoader.php
new file mode 100644
index 000000000..84647b758
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Psr4ClassLoader.php
@@ -0,0 +1,93 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ClassLoader;
+
+/**
+ * A PSR-4 compatible class loader.
+ *
+ * See http://www.php-fig.org/psr/psr-4/
+ *
+ * @author Alexander M. Turek
+ */
+class Psr4ClassLoader
+{
+ /**
+ * @var array
+ */
+ private $prefixes = array();
+
+ /**
+ * @param string $prefix
+ * @param string $baseDir
+ */
+ public function addPrefix($prefix, $baseDir)
+ {
+ $prefix = trim($prefix, '\\').'\\';
+ $baseDir = rtrim($baseDir, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR;
+ $this->prefixes[] = array($prefix, $baseDir);
+ }
+
+ /**
+ * @param string $class
+ *
+ * @return string|null
+ */
+ public function findFile($class)
+ {
+ $class = ltrim($class, '\\');
+
+ foreach ($this->prefixes as list($currentPrefix, $currentBaseDir)) {
+ if (0 === strpos($class, $currentPrefix)) {
+ $classWithoutPrefix = substr($class, strlen($currentPrefix));
+ $file = $currentBaseDir.str_replace('\\', DIRECTORY_SEPARATOR, $classWithoutPrefix).'.php';
+ if (file_exists($file)) {
+ return $file;
+ }
+ }
+ }
+ }
+
+ /**
+ * @param string $class
+ *
+ * @return bool
+ */
+ public function loadClass($class)
+ {
+ $file = $this->findFile($class);
+ if (null !== $file) {
+ require $file;
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Registers this instance as an autoloader.
+ *
+ * @param bool $prepend
+ */
+ public function register($prepend = false)
+ {
+ spl_autoload_register(array($this, 'loadClass'), true, $prepend);
+ }
+
+ /**
+ * Removes this instance from the registered autoloaders.
+ */
+ public function unregister()
+ {
+ spl_autoload_unregister(array($this, 'loadClass'));
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/README.md b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/README.md
new file mode 100644
index 000000000..45093c5df
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/README.md
@@ -0,0 +1,85 @@
+ClassLoader Component
+=====================
+
+ClassLoader loads your project classes automatically if they follow some
+standard PHP conventions.
+
+The ClassLoader object is able to autoload classes that implement the PSR-0
+standard or the PEAR naming convention.
+
+First, register the autoloader:
+
+```php
+require_once __DIR__.'/src/Symfony/Component/ClassLoader/ClassLoader.php';
+
+use Symfony\Component\ClassLoader\ClassLoader;
+
+$loader = new ClassLoader();
+$loader->register();
+```
+
+Then, register some namespaces with the `addPrefix()` method:
+
+```php
+$loader->addPrefix('Symfony', __DIR__.'/src');
+$loader->addPrefix('Monolog', __DIR__.'/vendor/monolog/src');
+```
+
+The `addPrefix()` method takes a namespace prefix and a path where to look for
+the classes as arguments.
+
+You can also register a sub-namespaces:
+
+```php
+$loader->addPrefix('Doctrine\\Common', __DIR__.'/vendor/doctrine-common/lib');
+```
+
+The order of registration is significant and the first registered namespace
+takes precedence over later registered one.
+
+You can also register more than one path for a given namespace:
+
+```php
+$loader->addPrefix('Symfony', array(__DIR__.'/src', __DIR__.'/symfony/src'));
+```
+
+Alternatively, you can use the `addPrefixes()` method to register more
+than one namespace at once:
+
+```php
+$loader->addPrefixes(array(
+ 'Symfony' => array(__DIR__.'/src', __DIR__.'/symfony/src'),
+ 'Doctrine\\Common' => __DIR__.'/vendor/doctrine-common/lib',
+ 'Doctrine' => __DIR__.'/vendor/doctrine/lib',
+ 'Monolog' => __DIR__.'/vendor/monolog/src',
+));
+```
+
+For better performance, you can use the APC class loader:
+
+```php
+require_once __DIR__.'/src/Symfony/Component/ClassLoader/ClassLoader.php';
+require_once __DIR__.'/src/Symfony/Component/ClassLoader/ApcClassLoader.php';
+
+use Symfony\Component\ClassLoader\ClassLoader;
+use Symfony\Component\ClassLoader\ApcClassLoader;
+
+$loader = new ClassLoader();
+$loader->addPrefix('Symfony', __DIR__.'/src');
+
+$loader = new ApcClassLoader('apc.prefix.', $loader);
+$loader->register();
+```
+
+Furthermore, the component provides tools to aggregate classes into a single
+file, which is especially useful to improve performance on servers that do not
+provide byte caches.
+
+Resources
+---------
+
+You can run the unit tests with the following command:
+
+ $ cd path/to/Symfony/Component/ClassLoader/
+ $ composer install
+ $ phpunit
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/ApcClassLoaderTest.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/ApcClassLoaderTest.php
new file mode 100644
index 000000000..e96ba9540
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/ApcClassLoaderTest.php
@@ -0,0 +1,196 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ClassLoader\Tests;
+
+use Symfony\Component\ClassLoader\ApcClassLoader;
+use Symfony\Component\ClassLoader\ClassLoader;
+
+class ApcClassLoaderTest extends \PHPUnit_Framework_TestCase
+{
+ protected function setUp()
+ {
+ if (!(ini_get('apc.enabled') && ini_get('apc.enable_cli'))) {
+ $this->markTestSkipped('The apc extension is not enabled.');
+ } else {
+ apcu_clear_cache();
+ }
+ }
+
+ protected function tearDown()
+ {
+ if (ini_get('apc.enabled') && ini_get('apc.enable_cli')) {
+ apcu_clear_cache();
+ }
+ }
+
+ public function testConstructor()
+ {
+ $loader = new ClassLoader();
+ $loader->addPrefix('Apc\Namespaced', __DIR__.DIRECTORY_SEPARATOR.'Fixtures');
+
+ $loader = new ApcClassLoader('test.prefix.', $loader);
+
+ $this->assertEquals($loader->findFile('\Apc\Namespaced\FooBar'), apcu_fetch('test.prefix.\Apc\Namespaced\FooBar'), '__construct() takes a prefix as its first argument');
+ }
+
+ /**
+ * @dataProvider getLoadClassTests
+ */
+ public function testLoadClass($className, $testClassName, $message)
+ {
+ $loader = new ClassLoader();
+ $loader->addPrefix('Apc\Namespaced', __DIR__.DIRECTORY_SEPARATOR.'Fixtures');
+ $loader->addPrefix('Apc_Pearlike_', __DIR__.DIRECTORY_SEPARATOR.'Fixtures');
+
+ $loader = new ApcClassLoader('test.prefix.', $loader);
+ $loader->loadClass($testClassName);
+ $this->assertTrue(class_exists($className), $message);
+ }
+
+ public function getLoadClassTests()
+ {
+ return array(
+ array('\\Apc\\Namespaced\\Foo', 'Apc\\Namespaced\\Foo', '->loadClass() loads Apc\Namespaced\Foo class'),
+ array('Apc_Pearlike_Foo', 'Apc_Pearlike_Foo', '->loadClass() loads Apc_Pearlike_Foo class'),
+ );
+ }
+
+ /**
+ * @dataProvider getLoadClassFromFallbackTests
+ */
+ public function testLoadClassFromFallback($className, $testClassName, $message)
+ {
+ $loader = new ClassLoader();
+ $loader->addPrefix('Apc\Namespaced', __DIR__.DIRECTORY_SEPARATOR.'Fixtures');
+ $loader->addPrefix('Apc_Pearlike_', __DIR__.DIRECTORY_SEPARATOR.'Fixtures');
+ $loader->addPrefix('', array(__DIR__.DIRECTORY_SEPARATOR.'Fixtures/Apc/fallback'));
+
+ $loader = new ApcClassLoader('test.prefix.fallback', $loader);
+ $loader->loadClass($testClassName);
+
+ $this->assertTrue(class_exists($className), $message);
+ }
+
+ public function getLoadClassFromFallbackTests()
+ {
+ return array(
+ array('\\Apc\\Namespaced\\Baz', 'Apc\\Namespaced\\Baz', '->loadClass() loads Apc\Namespaced\Baz class'),
+ array('Apc_Pearlike_Baz', 'Apc_Pearlike_Baz', '->loadClass() loads Apc_Pearlike_Baz class'),
+ array('\\Apc\\Namespaced\\FooBar', 'Apc\\Namespaced\\FooBar', '->loadClass() loads Apc\Namespaced\Baz class from fallback dir'),
+ array('Apc_Pearlike_FooBar', 'Apc_Pearlike_FooBar', '->loadClass() loads Apc_Pearlike_Baz class from fallback dir'),
+ );
+ }
+
+ /**
+ * @dataProvider getLoadClassNamespaceCollisionTests
+ */
+ public function testLoadClassNamespaceCollision($namespaces, $className, $message)
+ {
+ $loader = new ClassLoader();
+ $loader->addPrefixes($namespaces);
+
+ $loader = new ApcClassLoader('test.prefix.collision.', $loader);
+ $loader->loadClass($className);
+
+ $this->assertTrue(class_exists($className), $message);
+ }
+
+ public function getLoadClassNamespaceCollisionTests()
+ {
+ return array(
+ array(
+ array(
+ 'Apc\\NamespaceCollision\\A' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/Apc/alpha',
+ 'Apc\\NamespaceCollision\\A\\B' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/Apc/beta',
+ ),
+ 'Apc\NamespaceCollision\A\Foo',
+ '->loadClass() loads NamespaceCollision\A\Foo from alpha.',
+ ),
+ array(
+ array(
+ 'Apc\\NamespaceCollision\\A\\B' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/Apc/beta',
+ 'Apc\\NamespaceCollision\\A' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/Apc/alpha',
+ ),
+ 'Apc\NamespaceCollision\A\Bar',
+ '->loadClass() loads NamespaceCollision\A\Bar from alpha.',
+ ),
+ array(
+ array(
+ 'Apc\\NamespaceCollision\\A' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/Apc/alpha',
+ 'Apc\\NamespaceCollision\\A\\B' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/Apc/beta',
+ ),
+ 'Apc\NamespaceCollision\A\B\Foo',
+ '->loadClass() loads NamespaceCollision\A\B\Foo from beta.',
+ ),
+ array(
+ array(
+ 'Apc\\NamespaceCollision\\A\\B' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/Apc/beta',
+ 'Apc\\NamespaceCollision\\A' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/Apc/alpha',
+ ),
+ 'Apc\NamespaceCollision\A\B\Bar',
+ '->loadClass() loads NamespaceCollision\A\B\Bar from beta.',
+ ),
+ );
+ }
+
+ /**
+ * @dataProvider getLoadClassPrefixCollisionTests
+ */
+ public function testLoadClassPrefixCollision($prefixes, $className, $message)
+ {
+ $loader = new ClassLoader();
+ $loader->addPrefixes($prefixes);
+
+ $loader = new ApcClassLoader('test.prefix.collision.', $loader);
+ $loader->loadClass($className);
+
+ $this->assertTrue(class_exists($className), $message);
+ }
+
+ public function getLoadClassPrefixCollisionTests()
+ {
+ return array(
+ array(
+ array(
+ 'ApcPrefixCollision_A_' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/Apc/alpha/Apc',
+ 'ApcPrefixCollision_A_B_' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/Apc/beta/Apc',
+ ),
+ 'ApcPrefixCollision_A_Foo',
+ '->loadClass() loads ApcPrefixCollision_A_Foo from alpha.',
+ ),
+ array(
+ array(
+ 'ApcPrefixCollision_A_B_' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/Apc/beta/Apc',
+ 'ApcPrefixCollision_A_' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/Apc/alpha/Apc',
+ ),
+ 'ApcPrefixCollision_A_Bar',
+ '->loadClass() loads ApcPrefixCollision_A_Bar from alpha.',
+ ),
+ array(
+ array(
+ 'ApcPrefixCollision_A_' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/Apc/alpha/Apc',
+ 'ApcPrefixCollision_A_B_' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/Apc/beta/Apc',
+ ),
+ 'ApcPrefixCollision_A_B_Foo',
+ '->loadClass() loads ApcPrefixCollision_A_B_Foo from beta.',
+ ),
+ array(
+ array(
+ 'ApcPrefixCollision_A_B_' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/Apc/beta/Apc',
+ 'ApcPrefixCollision_A_' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/Apc/alpha/Apc',
+ ),
+ 'ApcPrefixCollision_A_B_Bar',
+ '->loadClass() loads ApcPrefixCollision_A_B_Bar from beta.',
+ ),
+ );
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/ClassCollectionLoaderTest.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/ClassCollectionLoaderTest.php
new file mode 100644
index 000000000..906e74941
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/ClassCollectionLoaderTest.php
@@ -0,0 +1,274 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ClassLoader\Tests;
+
+use Symfony\Component\ClassLoader\ClassCollectionLoader;
+
+require_once __DIR__.'/Fixtures/ClassesWithParents/GInterface.php';
+require_once __DIR__.'/Fixtures/ClassesWithParents/CInterface.php';
+require_once __DIR__.'/Fixtures/ClassesWithParents/B.php';
+require_once __DIR__.'/Fixtures/ClassesWithParents/A.php';
+
+class ClassCollectionLoaderTest extends \PHPUnit_Framework_TestCase
+{
+ public function testTraitDependencies()
+ {
+ require_once __DIR__.'/Fixtures/deps/traits.php';
+
+ $r = new \ReflectionClass('Symfony\Component\ClassLoader\ClassCollectionLoader');
+ $m = $r->getMethod('getOrderedClasses');
+ $m->setAccessible(true);
+
+ $ordered = $m->invoke('Symfony\Component\ClassLoader\ClassCollectionLoader', array('CTFoo'));
+
+ $this->assertEquals(
+ array('TD', 'TC', 'TB', 'TA', 'TZ', 'CTFoo'),
+ array_map(function ($class) { return $class->getName(); }, $ordered)
+ );
+
+ $ordered = $m->invoke('Symfony\Component\ClassLoader\ClassCollectionLoader', array('CTBar'));
+
+ $this->assertEquals(
+ array('TD', 'TZ', 'TC', 'TB', 'TA', 'CTBar'),
+ array_map(function ($class) { return $class->getName(); }, $ordered)
+ );
+ }
+
+ /**
+ * @dataProvider getDifferentOrders
+ */
+ public function testClassReordering(array $classes)
+ {
+ $expected = array(
+ 'ClassesWithParents\\GInterface',
+ 'ClassesWithParents\\CInterface',
+ 'ClassesWithParents\\B',
+ 'ClassesWithParents\\A',
+ );
+
+ $r = new \ReflectionClass('Symfony\Component\ClassLoader\ClassCollectionLoader');
+ $m = $r->getMethod('getOrderedClasses');
+ $m->setAccessible(true);
+
+ $ordered = $m->invoke('Symfony\Component\ClassLoader\ClassCollectionLoader', $classes);
+
+ $this->assertEquals($expected, array_map(function ($class) { return $class->getName(); }, $ordered));
+ }
+
+ public function getDifferentOrders()
+ {
+ return array(
+ array(array(
+ 'ClassesWithParents\\A',
+ 'ClassesWithParents\\CInterface',
+ 'ClassesWithParents\\GInterface',
+ 'ClassesWithParents\\B',
+ )),
+ array(array(
+ 'ClassesWithParents\\B',
+ 'ClassesWithParents\\A',
+ 'ClassesWithParents\\CInterface',
+ )),
+ array(array(
+ 'ClassesWithParents\\CInterface',
+ 'ClassesWithParents\\B',
+ 'ClassesWithParents\\A',
+ )),
+ array(array(
+ 'ClassesWithParents\\A',
+ )),
+ );
+ }
+
+ /**
+ * @dataProvider getDifferentOrdersForTraits
+ */
+ public function testClassWithTraitsReordering(array $classes)
+ {
+ require_once __DIR__.'/Fixtures/ClassesWithParents/ATrait.php';
+ require_once __DIR__.'/Fixtures/ClassesWithParents/BTrait.php';
+ require_once __DIR__.'/Fixtures/ClassesWithParents/CTrait.php';
+ require_once __DIR__.'/Fixtures/ClassesWithParents/D.php';
+ require_once __DIR__.'/Fixtures/ClassesWithParents/E.php';
+
+ $expected = array(
+ 'ClassesWithParents\\GInterface',
+ 'ClassesWithParents\\CInterface',
+ 'ClassesWithParents\\ATrait',
+ 'ClassesWithParents\\BTrait',
+ 'ClassesWithParents\\CTrait',
+ 'ClassesWithParents\\B',
+ 'ClassesWithParents\\A',
+ 'ClassesWithParents\\D',
+ 'ClassesWithParents\\E',
+ );
+
+ $r = new \ReflectionClass('Symfony\Component\ClassLoader\ClassCollectionLoader');
+ $m = $r->getMethod('getOrderedClasses');
+ $m->setAccessible(true);
+
+ $ordered = $m->invoke('Symfony\Component\ClassLoader\ClassCollectionLoader', $classes);
+
+ $this->assertEquals($expected, array_map(function ($class) { return $class->getName(); }, $ordered));
+ }
+
+ public function getDifferentOrdersForTraits()
+ {
+ return array(
+ array(array(
+ 'ClassesWithParents\\E',
+ 'ClassesWithParents\\ATrait',
+ )),
+ array(array(
+ 'ClassesWithParents\\E',
+ )),
+ );
+ }
+
+ public function testFixClassWithTraitsOrdering()
+ {
+ require_once __DIR__.'/Fixtures/ClassesWithParents/CTrait.php';
+ require_once __DIR__.'/Fixtures/ClassesWithParents/F.php';
+ require_once __DIR__.'/Fixtures/ClassesWithParents/G.php';
+
+ $classes = array(
+ 'ClassesWithParents\\F',
+ 'ClassesWithParents\\G',
+ );
+
+ $expected = array(
+ 'ClassesWithParents\\CTrait',
+ 'ClassesWithParents\\F',
+ 'ClassesWithParents\\G',
+ );
+
+ $r = new \ReflectionClass('Symfony\Component\ClassLoader\ClassCollectionLoader');
+ $m = $r->getMethod('getOrderedClasses');
+ $m->setAccessible(true);
+
+ $ordered = $m->invoke('Symfony\Component\ClassLoader\ClassCollectionLoader', $classes);
+
+ $this->assertEquals($expected, array_map(function ($class) { return $class->getName(); }, $ordered));
+ }
+
+ /**
+ * @dataProvider getFixNamespaceDeclarationsData
+ */
+ public function testFixNamespaceDeclarations($source, $expected)
+ {
+ $this->assertEquals('assertEquals('assertEquals(<<<'EOF'
+namespace Namespaced
+{
+class WithComments
+{
+public static $loaded = true;
+}
+$string ='string should not be modified {$string}';
+$heredoc = (<<
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ClassLoader\Tests;
+
+use Symfony\Component\ClassLoader\ClassLoader;
+
+class ClassLoaderTest extends \PHPUnit_Framework_TestCase
+{
+ public function testGetPrefixes()
+ {
+ $loader = new ClassLoader();
+ $loader->addPrefix('Foo', __DIR__.DIRECTORY_SEPARATOR.'Fixtures');
+ $loader->addPrefix('Bar', __DIR__.DIRECTORY_SEPARATOR.'Fixtures');
+ $loader->addPrefix('Bas', __DIR__.DIRECTORY_SEPARATOR.'Fixtures');
+ $prefixes = $loader->getPrefixes();
+ $this->assertArrayHasKey('Foo', $prefixes);
+ $this->assertArrayNotHasKey('Foo1', $prefixes);
+ $this->assertArrayHasKey('Bar', $prefixes);
+ $this->assertArrayHasKey('Bas', $prefixes);
+ }
+
+ public function testGetFallbackDirs()
+ {
+ $loader = new ClassLoader();
+ $loader->addPrefix(null, __DIR__.DIRECTORY_SEPARATOR.'Fixtures');
+ $loader->addPrefix(null, __DIR__.DIRECTORY_SEPARATOR.'Fixtures');
+ $fallback_dirs = $loader->getFallbackDirs();
+ $this->assertCount(2, $fallback_dirs);
+ }
+
+ /**
+ * @dataProvider getLoadClassTests
+ */
+ public function testLoadClass($className, $testClassName, $message)
+ {
+ $loader = new ClassLoader();
+ $loader->addPrefix('Namespaced2\\', __DIR__.DIRECTORY_SEPARATOR.'Fixtures');
+ $loader->addPrefix('Pearlike2_', __DIR__.DIRECTORY_SEPARATOR.'Fixtures');
+ $loader->loadClass($testClassName);
+ $this->assertTrue(class_exists($className), $message);
+ }
+
+ public function getLoadClassTests()
+ {
+ return array(
+ array('\\Namespaced2\\Foo', 'Namespaced2\\Foo', '->loadClass() loads Namespaced2\Foo class'),
+ array('\\Pearlike2_Foo', 'Pearlike2_Foo', '->loadClass() loads Pearlike2_Foo class'),
+ );
+ }
+
+ /**
+ * @dataProvider getLoadNonexistentClassTests
+ */
+ public function testLoadNonexistentClass($className, $testClassName, $message)
+ {
+ $loader = new ClassLoader();
+ $loader->addPrefix('Namespaced2\\', __DIR__.DIRECTORY_SEPARATOR.'Fixtures');
+ $loader->addPrefix('Pearlike2_', __DIR__.DIRECTORY_SEPARATOR.'Fixtures');
+ $loader->loadClass($testClassName);
+ $this->assertFalse(class_exists($className), $message);
+ }
+
+ public function getLoadNonexistentClassTests()
+ {
+ return array(
+ array('\\Pearlike3_Bar', '\\Pearlike3_Bar', '->loadClass() loads non existing Pearlike3_Bar class with a leading slash'),
+ );
+ }
+
+ public function testAddPrefixSingle()
+ {
+ $loader = new ClassLoader();
+ $loader->addPrefix('Foo', __DIR__.DIRECTORY_SEPARATOR.'Fixtures');
+ $loader->addPrefix('Foo', __DIR__.DIRECTORY_SEPARATOR.'Fixtures');
+ $prefixes = $loader->getPrefixes();
+ $this->assertArrayHasKey('Foo', $prefixes);
+ $this->assertCount(1, $prefixes['Foo']);
+ }
+
+ public function testAddPrefixesSingle()
+ {
+ $loader = new ClassLoader();
+ $loader->addPrefixes(array('Foo' => array('foo', 'foo')));
+ $loader->addPrefixes(array('Foo' => array('foo')));
+ $prefixes = $loader->getPrefixes();
+ $this->assertArrayHasKey('Foo', $prefixes);
+ $this->assertCount(1, $prefixes['Foo'], print_r($prefixes, true));
+ }
+
+ public function testAddPrefixMulti()
+ {
+ $loader = new ClassLoader();
+ $loader->addPrefix('Foo', 'foo');
+ $loader->addPrefix('Foo', 'bar');
+ $prefixes = $loader->getPrefixes();
+ $this->assertArrayHasKey('Foo', $prefixes);
+ $this->assertCount(2, $prefixes['Foo']);
+ $this->assertContains('foo', $prefixes['Foo']);
+ $this->assertContains('bar', $prefixes['Foo']);
+ }
+
+ public function testUseIncludePath()
+ {
+ $loader = new ClassLoader();
+ $this->assertFalse($loader->getUseIncludePath());
+
+ $this->assertNull($loader->findFile('Foo'));
+
+ $includePath = get_include_path();
+
+ $loader->setUseIncludePath(true);
+ $this->assertTrue($loader->getUseIncludePath());
+
+ set_include_path(__DIR__.'/Fixtures/includepath'.PATH_SEPARATOR.$includePath);
+
+ $this->assertEquals(__DIR__.DIRECTORY_SEPARATOR.'Fixtures'.DIRECTORY_SEPARATOR.'includepath'.DIRECTORY_SEPARATOR.'Foo.php', $loader->findFile('Foo'));
+
+ set_include_path($includePath);
+ }
+
+ /**
+ * @dataProvider getLoadClassFromFallbackTests
+ */
+ public function testLoadClassFromFallback($className, $testClassName, $message)
+ {
+ $loader = new ClassLoader();
+ $loader->addPrefix('Namespaced2\\', __DIR__.DIRECTORY_SEPARATOR.'Fixtures');
+ $loader->addPrefix('Pearlike2_', __DIR__.DIRECTORY_SEPARATOR.'Fixtures');
+ $loader->addPrefix('', array(__DIR__.DIRECTORY_SEPARATOR.'Fixtures/fallback'));
+ $loader->loadClass($testClassName);
+ $this->assertTrue(class_exists($className), $message);
+ }
+
+ public function getLoadClassFromFallbackTests()
+ {
+ return array(
+ array('\\Namespaced2\\Baz', 'Namespaced2\\Baz', '->loadClass() loads Namespaced2\Baz class'),
+ array('\\Pearlike2_Baz', 'Pearlike2_Baz', '->loadClass() loads Pearlike2_Baz class'),
+ array('\\Namespaced2\\FooBar', 'Namespaced2\\FooBar', '->loadClass() loads Namespaced2\Baz class from fallback dir'),
+ array('\\Pearlike2_FooBar', 'Pearlike2_FooBar', '->loadClass() loads Pearlike2_Baz class from fallback dir'),
+ );
+ }
+
+ /**
+ * @dataProvider getLoadClassNamespaceCollisionTests
+ */
+ public function testLoadClassNamespaceCollision($namespaces, $className, $message)
+ {
+ $loader = new ClassLoader();
+ $loader->addPrefixes($namespaces);
+
+ $loader->loadClass($className);
+ $this->assertTrue(class_exists($className), $message);
+ }
+
+ public function getLoadClassNamespaceCollisionTests()
+ {
+ return array(
+ array(
+ array(
+ 'NamespaceCollision\\C' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/alpha',
+ 'NamespaceCollision\\C\\B' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/beta',
+ ),
+ 'NamespaceCollision\C\Foo',
+ '->loadClass() loads NamespaceCollision\C\Foo from alpha.',
+ ),
+ array(
+ array(
+ 'NamespaceCollision\\C\\B' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/beta',
+ 'NamespaceCollision\\C' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/alpha',
+ ),
+ 'NamespaceCollision\C\Bar',
+ '->loadClass() loads NamespaceCollision\C\Bar from alpha.',
+ ),
+ array(
+ array(
+ 'NamespaceCollision\\C' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/alpha',
+ 'NamespaceCollision\\C\\B' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/beta',
+ ),
+ 'NamespaceCollision\C\B\Foo',
+ '->loadClass() loads NamespaceCollision\C\B\Foo from beta.',
+ ),
+ array(
+ array(
+ 'NamespaceCollision\\C\\B' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/beta',
+ 'NamespaceCollision\\C' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/alpha',
+ ),
+ 'NamespaceCollision\C\B\Bar',
+ '->loadClass() loads NamespaceCollision\C\B\Bar from beta.',
+ ),
+ array(
+ array(
+ 'PrefixCollision_C_' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/alpha',
+ 'PrefixCollision_C_B_' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/beta',
+ ),
+ 'PrefixCollision_C_Foo',
+ '->loadClass() loads PrefixCollision_C_Foo from alpha.',
+ ),
+ array(
+ array(
+ 'PrefixCollision_C_B_' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/beta',
+ 'PrefixCollision_C_' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/alpha',
+ ),
+ 'PrefixCollision_C_Bar',
+ '->loadClass() loads PrefixCollision_C_Bar from alpha.',
+ ),
+ array(
+ array(
+ 'PrefixCollision_C_' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/alpha',
+ 'PrefixCollision_C_B_' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/beta',
+ ),
+ 'PrefixCollision_C_B_Foo',
+ '->loadClass() loads PrefixCollision_C_B_Foo from beta.',
+ ),
+ array(
+ array(
+ 'PrefixCollision_C_B_' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/beta',
+ 'PrefixCollision_C_' => __DIR__.DIRECTORY_SEPARATOR.'Fixtures/alpha',
+ ),
+ 'PrefixCollision_C_B_Bar',
+ '->loadClass() loads PrefixCollision_C_B_Bar from beta.',
+ ),
+ );
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/ClassMapGeneratorTest.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/ClassMapGeneratorTest.php
new file mode 100644
index 000000000..6aed6efa3
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/ClassMapGeneratorTest.php
@@ -0,0 +1,144 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ClassLoader\Tests;
+
+use Symfony\Component\ClassLoader\ClassMapGenerator;
+
+class ClassMapGeneratorTest extends \PHPUnit_Framework_TestCase
+{
+ /**
+ * @var string|null
+ */
+ private $workspace = null;
+
+ public function prepare_workspace()
+ {
+ $this->workspace = rtrim(sys_get_temp_dir(), DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR.time().mt_rand(0, 1000);
+ mkdir($this->workspace, 0777, true);
+ $this->workspace = realpath($this->workspace);
+ }
+
+ /**
+ * @param string $file
+ */
+ private function clean($file)
+ {
+ if (is_dir($file) && !is_link($file)) {
+ $dir = new \FilesystemIterator($file);
+ foreach ($dir as $childFile) {
+ $this->clean($childFile);
+ }
+
+ rmdir($file);
+ } else {
+ unlink($file);
+ }
+ }
+
+ /**
+ * @dataProvider getTestCreateMapTests
+ */
+ public function testDump($directory)
+ {
+ $this->prepare_workspace();
+
+ $file = $this->workspace.'/file';
+
+ $generator = new ClassMapGenerator();
+ $generator->dump($directory, $file);
+ $this->assertFileExists($file);
+
+ $this->clean($this->workspace);
+ }
+
+ /**
+ * @dataProvider getTestCreateMapTests
+ */
+ public function testCreateMap($directory, $expected)
+ {
+ $this->assertEqualsNormalized($expected, ClassMapGenerator::createMap($directory));
+ }
+
+ public function getTestCreateMapTests()
+ {
+ $data = array(
+ array(__DIR__.'/Fixtures/Namespaced', array(
+ 'Namespaced\\Bar' => realpath(__DIR__).'/Fixtures/Namespaced/Bar.php',
+ 'Namespaced\\Foo' => realpath(__DIR__).'/Fixtures/Namespaced/Foo.php',
+ 'Namespaced\\Baz' => realpath(__DIR__).'/Fixtures/Namespaced/Baz.php',
+ 'Namespaced\\WithComments' => realpath(__DIR__).'/Fixtures/Namespaced/WithComments.php',
+ ),
+ ),
+ array(__DIR__.'/Fixtures/beta/NamespaceCollision', array(
+ 'NamespaceCollision\\A\\B\\Bar' => realpath(__DIR__).'/Fixtures/beta/NamespaceCollision/A/B/Bar.php',
+ 'NamespaceCollision\\A\\B\\Foo' => realpath(__DIR__).'/Fixtures/beta/NamespaceCollision/A/B/Foo.php',
+ 'NamespaceCollision\\C\\B\\Bar' => realpath(__DIR__).'/Fixtures/beta/NamespaceCollision/C/B/Bar.php',
+ 'NamespaceCollision\\C\\B\\Foo' => realpath(__DIR__).'/Fixtures/beta/NamespaceCollision/C/B/Foo.php',
+ )),
+ array(__DIR__.'/Fixtures/Pearlike', array(
+ 'Pearlike_Foo' => realpath(__DIR__).'/Fixtures/Pearlike/Foo.php',
+ 'Pearlike_Bar' => realpath(__DIR__).'/Fixtures/Pearlike/Bar.php',
+ 'Pearlike_Baz' => realpath(__DIR__).'/Fixtures/Pearlike/Baz.php',
+ 'Pearlike_WithComments' => realpath(__DIR__).'/Fixtures/Pearlike/WithComments.php',
+ )),
+ array(__DIR__.'/Fixtures/classmap', array(
+ 'Foo\\Bar\\A' => realpath(__DIR__).'/Fixtures/classmap/sameNsMultipleClasses.php',
+ 'Foo\\Bar\\B' => realpath(__DIR__).'/Fixtures/classmap/sameNsMultipleClasses.php',
+ 'A' => realpath(__DIR__).'/Fixtures/classmap/multipleNs.php',
+ 'Alpha\\A' => realpath(__DIR__).'/Fixtures/classmap/multipleNs.php',
+ 'Alpha\\B' => realpath(__DIR__).'/Fixtures/classmap/multipleNs.php',
+ 'Beta\\A' => realpath(__DIR__).'/Fixtures/classmap/multipleNs.php',
+ 'Beta\\B' => realpath(__DIR__).'/Fixtures/classmap/multipleNs.php',
+ 'ClassMap\\SomeInterface' => realpath(__DIR__).'/Fixtures/classmap/SomeInterface.php',
+ 'ClassMap\\SomeParent' => realpath(__DIR__).'/Fixtures/classmap/SomeParent.php',
+ 'ClassMap\\SomeClass' => realpath(__DIR__).'/Fixtures/classmap/SomeClass.php',
+ )),
+ array(__DIR__.'/Fixtures/php5.4', array(
+ 'TFoo' => __DIR__.'/Fixtures/php5.4/traits.php',
+ 'CFoo' => __DIR__.'/Fixtures/php5.4/traits.php',
+ 'Foo\\TBar' => __DIR__.'/Fixtures/php5.4/traits.php',
+ 'Foo\\IBar' => __DIR__.'/Fixtures/php5.4/traits.php',
+ 'Foo\\TFooBar' => __DIR__.'/Fixtures/php5.4/traits.php',
+ 'Foo\\CBar' => __DIR__.'/Fixtures/php5.4/traits.php',
+ )),
+ array(__DIR__.'/Fixtures/php5.5', array(
+ 'ClassCons\\Foo' => __DIR__.'/Fixtures/php5.5/class_cons.php',
+ )),
+ );
+
+ return $data;
+ }
+
+ public function testCreateMapFinderSupport()
+ {
+ $finder = new \Symfony\Component\Finder\Finder();
+ $finder->files()->in(__DIR__.'/Fixtures/beta/NamespaceCollision');
+
+ $this->assertEqualsNormalized(array(
+ 'NamespaceCollision\\A\\B\\Bar' => realpath(__DIR__).'/Fixtures/beta/NamespaceCollision/A/B/Bar.php',
+ 'NamespaceCollision\\A\\B\\Foo' => realpath(__DIR__).'/Fixtures/beta/NamespaceCollision/A/B/Foo.php',
+ 'NamespaceCollision\\C\\B\\Bar' => realpath(__DIR__).'/Fixtures/beta/NamespaceCollision/C/B/Bar.php',
+ 'NamespaceCollision\\C\\B\\Foo' => realpath(__DIR__).'/Fixtures/beta/NamespaceCollision/C/B/Foo.php',
+ ), ClassMapGenerator::createMap($finder));
+ }
+
+ protected function assertEqualsNormalized($expected, $actual, $message = null)
+ {
+ foreach ($expected as $ns => $path) {
+ $expected[$ns] = str_replace('\\', '/', $path);
+ }
+ foreach ($actual as $ns => $path) {
+ $actual[$ns] = str_replace('\\', '/', $path);
+ }
+ $this->assertEquals($expected, $actual, $message);
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Apc/Namespaced/Bar.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Apc/Namespaced/Bar.php
new file mode 100644
index 000000000..4259f1451
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Apc/Namespaced/Bar.php
@@ -0,0 +1,17 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Apc\Namespaced;
+
+class Bar
+{
+ public static $loaded = true;
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Apc/Namespaced/Baz.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Apc/Namespaced/Baz.php
new file mode 100644
index 000000000..3ddb595e2
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Apc/Namespaced/Baz.php
@@ -0,0 +1,17 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Apc\Namespaced;
+
+class Baz
+{
+ public static $loaded = true;
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Apc/Namespaced/Foo.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Apc/Namespaced/Foo.php
new file mode 100644
index 000000000..cf0a4b741
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Apc/Namespaced/Foo.php
@@ -0,0 +1,17 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Apc\Namespaced;
+
+class Foo
+{
+ public static $loaded = true;
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Apc/Namespaced/FooBar.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Apc/Namespaced/FooBar.php
new file mode 100644
index 000000000..bbbc81515
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Apc/Namespaced/FooBar.php
@@ -0,0 +1,17 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Apc\Namespaced;
+
+class FooBar
+{
+ public static $loaded = true;
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Apc/Pearlike/Bar.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Apc/Pearlike/Bar.php
new file mode 100644
index 000000000..e774cb9bf
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Apc/Pearlike/Bar.php
@@ -0,0 +1,6 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Apc\NamespaceCollision\A;
+
+class Bar
+{
+ public static $loaded = true;
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Apc/alpha/Apc/NamespaceCollision/A/Foo.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Apc/alpha/Apc/NamespaceCollision/A/Foo.php
new file mode 100644
index 000000000..184a1b1da
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Apc/alpha/Apc/NamespaceCollision/A/Foo.php
@@ -0,0 +1,17 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Apc\NamespaceCollision\A;
+
+class Foo
+{
+ public static $loaded = true;
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Apc/beta/Apc/ApcPrefixCollision/A/B/Bar.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Apc/beta/Apc/ApcPrefixCollision/A/B/Bar.php
new file mode 100644
index 000000000..3892f7068
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Apc/beta/Apc/ApcPrefixCollision/A/B/Bar.php
@@ -0,0 +1,6 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Apc\NamespaceCollision\A\B;
+
+class Bar
+{
+ public static $loaded = true;
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Apc/beta/Apc/NamespaceCollision/A/B/Foo.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Apc/beta/Apc/NamespaceCollision/A/B/Foo.php
new file mode 100644
index 000000000..450eeb50b
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Apc/beta/Apc/NamespaceCollision/A/B/Foo.php
@@ -0,0 +1,17 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Apc\NamespaceCollision\A\B;
+
+class Foo
+{
+ public static $loaded = true;
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Apc/fallback/Apc/Pearlike/FooBar.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Apc/fallback/Apc/Pearlike/FooBar.php
new file mode 100644
index 000000000..96f2f76c6
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Apc/fallback/Apc/Pearlike/FooBar.php
@@ -0,0 +1,6 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Apc\Namespaced;
+
+class FooBar
+{
+ public static $loaded = true;
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/ClassesWithParents/A.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/ClassesWithParents/A.php
new file mode 100644
index 000000000..b0f942595
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/ClassesWithParents/A.php
@@ -0,0 +1,7 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Namespaced;
+
+class Bar
+{
+ public static $loaded = true;
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Namespaced/Baz.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Namespaced/Baz.php
new file mode 100644
index 000000000..0b0bbd057
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Namespaced/Baz.php
@@ -0,0 +1,17 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Namespaced;
+
+class Baz
+{
+ public static $loaded = true;
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Namespaced/Foo.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Namespaced/Foo.php
new file mode 100644
index 000000000..df5e1f4ce
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Namespaced/Foo.php
@@ -0,0 +1,17 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Namespaced;
+
+class Foo
+{
+ public static $loaded = true;
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Namespaced/WithComments.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Namespaced/WithComments.php
new file mode 100644
index 000000000..361e53de1
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Namespaced/WithComments.php
@@ -0,0 +1,37 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Namespaced;
+
+class WithComments
+{
+ /** @Boolean */
+ public static $loaded = true;
+}
+
+$string = 'string should not be modified {$string}';
+
+$heredoc = (<<
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+class Pearlike_WithComments
+{
+ /** @Boolean */
+ public static $loaded = true;
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Pearlike2/Bar.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Pearlike2/Bar.php
new file mode 100644
index 000000000..7f5f79773
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/Pearlike2/Bar.php
@@ -0,0 +1,6 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace NamespaceCollision\A;
+
+class Bar
+{
+ public static $loaded = true;
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/alpha/NamespaceCollision/A/Foo.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/alpha/NamespaceCollision/A/Foo.php
new file mode 100644
index 000000000..aee6a080d
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/alpha/NamespaceCollision/A/Foo.php
@@ -0,0 +1,17 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace NamespaceCollision\A;
+
+class Foo
+{
+ public static $loaded = true;
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/alpha/NamespaceCollision/C/Bar.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/alpha/NamespaceCollision/C/Bar.php
new file mode 100644
index 000000000..c1b8dd65d
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/alpha/NamespaceCollision/C/Bar.php
@@ -0,0 +1,8 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace NamespaceCollision\A\B;
+
+class Bar
+{
+ public static $loaded = true;
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/beta/NamespaceCollision/A/B/Foo.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/beta/NamespaceCollision/A/B/Foo.php
new file mode 100644
index 000000000..f5f2d727e
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/beta/NamespaceCollision/A/B/Foo.php
@@ -0,0 +1,17 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace NamespaceCollision\A\B;
+
+class Foo
+{
+ public static $loaded = true;
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/beta/NamespaceCollision/C/B/Bar.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/beta/NamespaceCollision/C/B/Bar.php
new file mode 100644
index 000000000..4bb03dc7f
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/beta/NamespaceCollision/C/B/Bar.php
@@ -0,0 +1,8 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace ClassMap;
+
+class SomeClass extends SomeParent implements SomeInterface
+{
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/classmap/SomeInterface.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/classmap/SomeInterface.php
new file mode 100644
index 000000000..1fe5e09aa
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/classmap/SomeInterface.php
@@ -0,0 +1,16 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace ClassMap;
+
+interface SomeInterface
+{
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/classmap/SomeParent.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/classmap/SomeParent.php
new file mode 100644
index 000000000..ce2f9fc6c
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/classmap/SomeParent.php
@@ -0,0 +1,16 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace ClassMap;
+
+abstract class SomeParent
+{
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/classmap/multipleNs.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/classmap/multipleNs.php
new file mode 100644
index 000000000..c7cec646f
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/classmap/multipleNs.php
@@ -0,0 +1,25 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Foo\Bar;
+
+class A
+{
+}
+class B
+{
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/deps/traits.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/deps/traits.php
new file mode 100644
index 000000000..82b30a6f9
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/deps/traits.php
@@ -0,0 +1,37 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Namespaced;
+
+class FooBar
+{
+ public static $loaded = true;
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/fallback/Namespaced2/FooBar.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/fallback/Namespaced2/FooBar.php
new file mode 100644
index 000000000..1036d4359
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/Tests/Fixtures/fallback/Namespaced2/FooBar.php
@@ -0,0 +1,8 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ClassLoader\Tests;
+
+use Symfony\Component\ClassLoader\Psr4ClassLoader;
+
+class Psr4ClassLoaderTest extends \PHPUnit_Framework_TestCase
+{
+ /**
+ * @param string $className
+ * @dataProvider getLoadClassTests
+ */
+ public function testLoadClass($className)
+ {
+ $loader = new Psr4ClassLoader();
+ $loader->addPrefix(
+ 'Acme\\DemoLib',
+ __DIR__.DIRECTORY_SEPARATOR.'Fixtures'.DIRECTORY_SEPARATOR.'psr-4'
+ );
+ $loader->loadClass($className);
+ $this->assertTrue(class_exists($className), sprintf('loadClass() should load %s', $className));
+ }
+
+ /**
+ * @return array
+ */
+ public function getLoadClassTests()
+ {
+ return array(
+ array('Acme\\DemoLib\\Foo'),
+ array('Acme\\DemoLib\\Class_With_Underscores'),
+ array('Acme\\DemoLib\\Lets\\Go\\Deeper\\Foo'),
+ array('Acme\\DemoLib\\Lets\\Go\\Deeper\\Class_With_Underscores'),
+ );
+ }
+
+ /**
+ * @param string $className
+ * @dataProvider getLoadNonexistentClassTests
+ */
+ public function testLoadNonexistentClass($className)
+ {
+ $loader = new Psr4ClassLoader();
+ $loader->addPrefix(
+ 'Acme\\DemoLib',
+ __DIR__.DIRECTORY_SEPARATOR.'Fixtures'.DIRECTORY_SEPARATOR.'psr-4'
+ );
+ $loader->loadClass($className);
+ $this->assertFalse(class_exists($className), sprintf('loadClass() should not load %s', $className));
+ }
+
+ /**
+ * @return array
+ */
+ public function getLoadNonexistentClassTests()
+ {
+ return array(
+ array('Acme\\DemoLib\\I_Do_Not_Exist'),
+ array('UnknownVendor\\SomeLib\\I_Do_Not_Exist'),
+ );
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/WinCacheClassLoader.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/WinCacheClassLoader.php
new file mode 100644
index 000000000..7ba0d9df5
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/WinCacheClassLoader.php
@@ -0,0 +1,138 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ClassLoader;
+
+/**
+ * WinCacheClassLoader implements a wrapping autoloader cached in WinCache.
+ *
+ * It expects an object implementing a findFile method to find the file. This
+ * allow using it as a wrapper around the other loaders of the component (the
+ * ClassLoader for instance) but also around any other autoloaders following
+ * this convention (the Composer one for instance).
+ *
+ * // with a Symfony autoloader
+ * $loader = new ClassLoader();
+ * $loader->addPrefix('Symfony\Component', __DIR__.'/component');
+ * $loader->addPrefix('Symfony', __DIR__.'/framework');
+ *
+ * // or with a Composer autoloader
+ * use Composer\Autoload\ClassLoader;
+ *
+ * $loader = new ClassLoader();
+ * $loader->add('Symfony\Component', __DIR__.'/component');
+ * $loader->add('Symfony', __DIR__.'/framework');
+ *
+ * $cachedLoader = new WinCacheClassLoader('my_prefix', $loader);
+ *
+ * // activate the cached autoloader
+ * $cachedLoader->register();
+ *
+ * // eventually deactivate the non-cached loader if it was registered previously
+ * // to be sure to use the cached one.
+ * $loader->unregister();
+ *
+ * @author Fabien Potencier
+ * @author Kris Wallsmith
+ * @author Artem Ryzhkov
+ */
+class WinCacheClassLoader
+{
+ private $prefix;
+
+ /**
+ * A class loader object that implements the findFile() method.
+ *
+ * @var object
+ */
+ protected $decorated;
+
+ /**
+ * Constructor.
+ *
+ * @param string $prefix The WinCache namespace prefix to use.
+ * @param object $decorated A class loader object that implements the findFile() method.
+ *
+ * @throws \RuntimeException
+ * @throws \InvalidArgumentException
+ */
+ public function __construct($prefix, $decorated)
+ {
+ if (!extension_loaded('wincache')) {
+ throw new \RuntimeException('Unable to use WinCacheClassLoader as WinCache is not enabled.');
+ }
+
+ if (!method_exists($decorated, 'findFile')) {
+ throw new \InvalidArgumentException('The class finder must implement a "findFile" method.');
+ }
+
+ $this->prefix = $prefix;
+ $this->decorated = $decorated;
+ }
+
+ /**
+ * 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
+ */
+ public function loadClass($class)
+ {
+ if ($file = $this->findFile($class)) {
+ require $file;
+
+ return true;
+ }
+ }
+
+ /**
+ * Finds a file by class name while caching lookups to WinCache.
+ *
+ * @param string $class A class name to resolve to file
+ *
+ * @return string|null
+ */
+ public function findFile($class)
+ {
+ if (false === $file = wincache_ucache_get($this->prefix.$class)) {
+ wincache_ucache_set($this->prefix.$class, $file = $this->decorated->findFile($class), 0);
+ }
+
+ return $file;
+ }
+
+ /**
+ * Passes through all unknown calls onto the decorated object.
+ */
+ public function __call($method, $args)
+ {
+ return call_user_func_array(array($this->decorated, $method), $args);
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/XcacheClassLoader.php b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/XcacheClassLoader.php
new file mode 100644
index 000000000..5aacac69e
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/XcacheClassLoader.php
@@ -0,0 +1,141 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ClassLoader;
+
+/**
+ * XcacheClassLoader implements a wrapping autoloader cached in XCache for PHP 5.3.
+ *
+ * It expects an object implementing a findFile method to find the file. This
+ * allows using it as a wrapper around the other loaders of the component (the
+ * ClassLoader for instance) but also around any other autoloaders following
+ * this convention (the Composer one for instance).
+ *
+ * // with a Symfony autoloader
+ * $loader = new ClassLoader();
+ * $loader->addPrefix('Symfony\Component', __DIR__.'/component');
+ * $loader->addPrefix('Symfony', __DIR__.'/framework');
+ *
+ * // or with a Composer autoloader
+ * use Composer\Autoload\ClassLoader;
+ *
+ * $loader = new ClassLoader();
+ * $loader->add('Symfony\Component', __DIR__.'/component');
+ * $loader->add('Symfony', __DIR__.'/framework');
+ *
+ * $cachedLoader = new XcacheClassLoader('my_prefix', $loader);
+ *
+ * // activate the cached autoloader
+ * $cachedLoader->register();
+ *
+ * // eventually deactivate the non-cached loader if it was registered previously
+ * // to be sure to use the cached one.
+ * $loader->unregister();
+ *
+ * @author Fabien Potencier
+ * @author Kris Wallsmith
+ * @author Kim Hemsø Rasmussen
+ */
+class XcacheClassLoader
+{
+ private $prefix;
+
+ /**
+ * A class loader object that implements the findFile() method.
+ *
+ * @var object
+ */
+ private $decorated;
+
+ /**
+ * Constructor.
+ *
+ * @param string $prefix The XCache namespace prefix to use.
+ * @param object $decorated A class loader object that implements the findFile() method.
+ *
+ * @throws \RuntimeException
+ * @throws \InvalidArgumentException
+ */
+ public function __construct($prefix, $decorated)
+ {
+ if (!extension_loaded('xcache')) {
+ throw new \RuntimeException('Unable to use XcacheClassLoader as XCache is not enabled.');
+ }
+
+ if (!method_exists($decorated, 'findFile')) {
+ throw new \InvalidArgumentException('The class finder must implement a "findFile" method.');
+ }
+
+ $this->prefix = $prefix;
+ $this->decorated = $decorated;
+ }
+
+ /**
+ * 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
+ */
+ public function loadClass($class)
+ {
+ if ($file = $this->findFile($class)) {
+ require $file;
+
+ return true;
+ }
+ }
+
+ /**
+ * Finds a file by class name while caching lookups to Xcache.
+ *
+ * @param string $class A class name to resolve to file
+ *
+ * @return string|null
+ */
+ public function findFile($class)
+ {
+ if (xcache_isset($this->prefix.$class)) {
+ $file = xcache_get($this->prefix.$class);
+ } else {
+ $file = $this->decorated->findFile($class);
+ xcache_set($this->prefix.$class, $file);
+ }
+
+ return $file;
+ }
+
+ /**
+ * Passes through all unknown calls onto the decorated object.
+ */
+ public function __call($method, $args)
+ {
+ return call_user_func_array(array($this->decorated, $method), $args);
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/composer.json b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/composer.json
new file mode 100644
index 000000000..0089e5fba
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/composer.json
@@ -0,0 +1,40 @@
+{
+ "name": "symfony/class-loader",
+ "type": "library",
+ "description": "Symfony ClassLoader Component",
+ "keywords": [],
+ "homepage": "https://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "minimum-stability": "dev",
+ "require": {
+ "php": ">=5.5.9"
+ },
+ "require-dev": {
+ "symfony/finder": "~2.8|~3.0",
+ "symfony/polyfill-apcu": "~1.1"
+ },
+ "suggest": {
+ "symfony/polyfill-apcu": "For using ApcClassLoader on HHVM"
+ },
+ "autoload": {
+ "psr-4": { "Symfony\\Component\\ClassLoader\\": "" },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0-dev"
+ }
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/phpunit.xml.dist b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/phpunit.xml.dist
new file mode 100644
index 000000000..4856db5be
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/class-loader/phpunit.xml.dist
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+ ./Tests/
+
+
+
+
+
+ ./
+
+ ./Resources
+ ./Tests
+ ./vendor
+
+
+
+
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/.gitignore b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/.gitignore
new file mode 100644
index 000000000..c49a5d8df
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/.gitignore
@@ -0,0 +1,3 @@
+vendor/
+composer.lock
+phpunit.xml
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/CHANGELOG.md b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/CHANGELOG.md
new file mode 100644
index 000000000..8feda35d5
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/CHANGELOG.md
@@ -0,0 +1,32 @@
+CHANGELOG
+=========
+
+3.0.0
+-----
+
+ * The method `getListenerPriority($eventName, $listener)` has been added to the
+ `EventDispatcherInterface`.
+ * The methods `Event::setDispatcher()`, `Event::getDispatcher()`, `Event::setName()`
+ and `Event::getName()` have been removed.
+ The event dispatcher and the event name are passed to the listener call.
+
+2.5.0
+-----
+
+ * added Debug\TraceableEventDispatcher (originally in HttpKernel)
+ * changed Debug\TraceableEventDispatcherInterface to extend EventDispatcherInterface
+ * added RegisterListenersPass (originally in HttpKernel)
+
+2.1.0
+-----
+
+ * added TraceableEventDispatcherInterface
+ * added ContainerAwareEventDispatcher
+ * added a reference to the EventDispatcher on the Event
+ * added a reference to the Event name on the event
+ * added fluid interface to the dispatch() method which now returns the Event
+ object
+ * added GenericEvent event class
+ * added the possibility for subscribers to subscribe several times for the
+ same event
+ * added ImmutableEventDispatcher
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/ContainerAwareEventDispatcher.php b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/ContainerAwareEventDispatcher.php
new file mode 100644
index 000000000..5982b85f3
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/ContainerAwareEventDispatcher.php
@@ -0,0 +1,195 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher;
+
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Lazily loads listeners and subscribers from the dependency injection
+ * container.
+ *
+ * @author Fabien Potencier
+ * @author Bernhard Schussek
+ * @author Jordan Alliot
+ */
+class ContainerAwareEventDispatcher extends EventDispatcher
+{
+ /**
+ * The container from where services are loaded.
+ *
+ * @var ContainerInterface
+ */
+ private $container;
+
+ /**
+ * The service IDs of the event listeners and subscribers.
+ *
+ * @var array
+ */
+ private $listenerIds = array();
+
+ /**
+ * The services registered as listeners.
+ *
+ * @var array
+ */
+ private $listeners = array();
+
+ /**
+ * Constructor.
+ *
+ * @param ContainerInterface $container A ContainerInterface instance
+ */
+ public function __construct(ContainerInterface $container)
+ {
+ $this->container = $container;
+ }
+
+ /**
+ * Adds a service as event listener.
+ *
+ * @param string $eventName Event for which the listener is added
+ * @param array $callback The service ID of the listener service & the method
+ * name that has to be called
+ * @param int $priority The higher this value, the earlier an event listener
+ * will be triggered in the chain.
+ * Defaults to 0.
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function addListenerService($eventName, $callback, $priority = 0)
+ {
+ if (!is_array($callback) || 2 !== count($callback)) {
+ throw new \InvalidArgumentException('Expected an array("service", "method") argument');
+ }
+
+ $this->listenerIds[$eventName][] = array($callback[0], $callback[1], $priority);
+ }
+
+ public function removeListener($eventName, $listener)
+ {
+ $this->lazyLoad($eventName);
+
+ if (isset($this->listenerIds[$eventName])) {
+ foreach ($this->listenerIds[$eventName] as $i => list($serviceId, $method, $priority)) {
+ $key = $serviceId.'.'.$method;
+ if (isset($this->listeners[$eventName][$key]) && $listener === array($this->listeners[$eventName][$key], $method)) {
+ unset($this->listeners[$eventName][$key]);
+ if (empty($this->listeners[$eventName])) {
+ unset($this->listeners[$eventName]);
+ }
+ unset($this->listenerIds[$eventName][$i]);
+ if (empty($this->listenerIds[$eventName])) {
+ unset($this->listenerIds[$eventName]);
+ }
+ }
+ }
+ }
+
+ parent::removeListener($eventName, $listener);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasListeners($eventName = null)
+ {
+ if (null === $eventName) {
+ return (bool) count($this->listenerIds) || (bool) count($this->listeners);
+ }
+
+ if (isset($this->listenerIds[$eventName])) {
+ return true;
+ }
+
+ return parent::hasListeners($eventName);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getListeners($eventName = null)
+ {
+ if (null === $eventName) {
+ foreach ($this->listenerIds as $serviceEventName => $args) {
+ $this->lazyLoad($serviceEventName);
+ }
+ } else {
+ $this->lazyLoad($eventName);
+ }
+
+ return parent::getListeners($eventName);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getListenerPriority($eventName, $listener)
+ {
+ $this->lazyLoad($eventName);
+
+ return parent::getListenerPriority($eventName, $listener);
+ }
+
+ /**
+ * Adds a service as event subscriber.
+ *
+ * @param string $serviceId The service ID of the subscriber service
+ * @param string $class The service's class name (which must implement EventSubscriberInterface)
+ */
+ public function addSubscriberService($serviceId, $class)
+ {
+ foreach ($class::getSubscribedEvents() as $eventName => $params) {
+ if (is_string($params)) {
+ $this->listenerIds[$eventName][] = array($serviceId, $params, 0);
+ } elseif (is_string($params[0])) {
+ $this->listenerIds[$eventName][] = array($serviceId, $params[0], isset($params[1]) ? $params[1] : 0);
+ } else {
+ foreach ($params as $listener) {
+ $this->listenerIds[$eventName][] = array($serviceId, $listener[0], isset($listener[1]) ? $listener[1] : 0);
+ }
+ }
+ }
+ }
+
+ public function getContainer()
+ {
+ return $this->container;
+ }
+
+ /**
+ * Lazily loads listeners for this event from the dependency injection
+ * container.
+ *
+ * @param string $eventName The name of the event to dispatch. The name of
+ * the event is the name of the method that is
+ * invoked on listeners.
+ */
+ protected function lazyLoad($eventName)
+ {
+ if (isset($this->listenerIds[$eventName])) {
+ foreach ($this->listenerIds[$eventName] as list($serviceId, $method, $priority)) {
+ $listener = $this->container->get($serviceId);
+
+ $key = $serviceId.'.'.$method;
+ if (!isset($this->listeners[$eventName][$key])) {
+ $this->addListener($eventName, array($listener, $method), $priority);
+ } elseif ($listener !== $this->listeners[$eventName][$key]) {
+ parent::removeListener($eventName, array($this->listeners[$eventName][$key], $method));
+ $this->addListener($eventName, array($listener, $method), $priority);
+ }
+
+ $this->listeners[$eventName][$key] = $listener;
+ }
+ }
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php
new file mode 100644
index 000000000..35df8162b
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php
@@ -0,0 +1,367 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher\Debug;
+
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\EventDispatcher\Event;
+use Symfony\Component\Stopwatch\Stopwatch;
+use Psr\Log\LoggerInterface;
+
+/**
+ * Collects some data about event listeners.
+ *
+ * This event dispatcher delegates the dispatching to another one.
+ *
+ * @author Fabien Potencier
+ */
+class TraceableEventDispatcher implements TraceableEventDispatcherInterface
+{
+ protected $logger;
+ protected $stopwatch;
+
+ private $called;
+ private $dispatcher;
+ private $wrappedListeners;
+
+ /**
+ * Constructor.
+ *
+ * @param EventDispatcherInterface $dispatcher An EventDispatcherInterface instance
+ * @param Stopwatch $stopwatch A Stopwatch instance
+ * @param LoggerInterface $logger A LoggerInterface instance
+ */
+ public function __construct(EventDispatcherInterface $dispatcher, Stopwatch $stopwatch, LoggerInterface $logger = null)
+ {
+ $this->dispatcher = $dispatcher;
+ $this->stopwatch = $stopwatch;
+ $this->logger = $logger;
+ $this->called = array();
+ $this->wrappedListeners = array();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addListener($eventName, $listener, $priority = 0)
+ {
+ $this->dispatcher->addListener($eventName, $listener, $priority);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addSubscriber(EventSubscriberInterface $subscriber)
+ {
+ $this->dispatcher->addSubscriber($subscriber);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeListener($eventName, $listener)
+ {
+ if (isset($this->wrappedListeners[$eventName])) {
+ foreach ($this->wrappedListeners[$eventName] as $index => $wrappedListener) {
+ if ($wrappedListener->getWrappedListener() === $listener) {
+ $listener = $wrappedListener;
+ unset($this->wrappedListeners[$eventName][$index]);
+ break;
+ }
+ }
+ }
+
+ return $this->dispatcher->removeListener($eventName, $listener);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeSubscriber(EventSubscriberInterface $subscriber)
+ {
+ return $this->dispatcher->removeSubscriber($subscriber);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getListeners($eventName = null)
+ {
+ return $this->dispatcher->getListeners($eventName);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getListenerPriority($eventName, $listener)
+ {
+ return $this->dispatcher->getListenerPriority($eventName, $listener);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasListeners($eventName = null)
+ {
+ return $this->dispatcher->hasListeners($eventName);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dispatch($eventName, Event $event = null)
+ {
+ if (null === $event) {
+ $event = new Event();
+ }
+
+ $this->preProcess($eventName);
+ $this->preDispatch($eventName, $event);
+
+ $e = $this->stopwatch->start($eventName, 'section');
+
+ $this->dispatcher->dispatch($eventName, $event);
+
+ if ($e->isStarted()) {
+ $e->stop();
+ }
+
+ $this->postDispatch($eventName, $event);
+ $this->postProcess($eventName);
+
+ return $event;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getCalledListeners()
+ {
+ $called = array();
+ foreach ($this->called as $eventName => $listeners) {
+ foreach ($listeners as $listener) {
+ $info = $this->getListenerInfo($listener->getWrappedListener(), $eventName);
+ $called[$eventName.'.'.$info['pretty']] = $info;
+ }
+ }
+
+ return $called;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getNotCalledListeners()
+ {
+ try {
+ $allListeners = $this->getListeners();
+ } catch (\Exception $e) {
+ if (null !== $this->logger) {
+ $this->logger->info('An exception was thrown while getting the uncalled listeners.', array('exception' => $e));
+ }
+
+ // unable to retrieve the uncalled listeners
+ return array();
+ }
+
+ $notCalled = array();
+ foreach ($allListeners as $eventName => $listeners) {
+ foreach ($listeners as $listener) {
+ $called = false;
+ if (isset($this->called[$eventName])) {
+ foreach ($this->called[$eventName] as $l) {
+ if ($l->getWrappedListener() === $listener) {
+ $called = true;
+
+ break;
+ }
+ }
+ }
+
+ if (!$called) {
+ $info = $this->getListenerInfo($listener, $eventName);
+ $notCalled[$eventName.'.'.$info['pretty']] = $info;
+ }
+ }
+ }
+
+ uasort($notCalled, array($this, 'sortListenersByPriority'));
+
+ return $notCalled;
+ }
+
+ /**
+ * Proxies all method calls to the original event dispatcher.
+ *
+ * @param string $method The method name
+ * @param array $arguments The method arguments
+ *
+ * @return mixed
+ */
+ public function __call($method, $arguments)
+ {
+ return call_user_func_array(array($this->dispatcher, $method), $arguments);
+ }
+
+ /**
+ * Called before dispatching the event.
+ *
+ * @param string $eventName The event name
+ * @param Event $event The event
+ */
+ protected function preDispatch($eventName, Event $event)
+ {
+ }
+
+ /**
+ * Called after dispatching the event.
+ *
+ * @param string $eventName The event name
+ * @param Event $event The event
+ */
+ protected function postDispatch($eventName, Event $event)
+ {
+ }
+
+ private function preProcess($eventName)
+ {
+ foreach ($this->dispatcher->getListeners($eventName) as $listener) {
+ $this->dispatcher->removeListener($eventName, $listener);
+ $info = $this->getListenerInfo($listener, $eventName);
+ $name = isset($info['class']) ? $info['class'] : $info['type'];
+ $wrappedListener = new WrappedListener($listener, $name, $this->stopwatch, $this);
+ $this->wrappedListeners[$eventName][] = $wrappedListener;
+ $this->dispatcher->addListener($eventName, $wrappedListener);
+ }
+ }
+
+ private function postProcess($eventName)
+ {
+ unset($this->wrappedListeners[$eventName]);
+ $skipped = false;
+ foreach ($this->dispatcher->getListeners($eventName) as $listener) {
+ if (!$listener instanceof WrappedListener) { // #12845: a new listener was added during dispatch.
+ continue;
+ }
+ // Unwrap listener
+ $this->dispatcher->removeListener($eventName, $listener);
+ $this->dispatcher->addListener($eventName, $listener->getWrappedListener());
+
+ $info = $this->getListenerInfo($listener->getWrappedListener(), $eventName);
+ if ($listener->wasCalled()) {
+ if (null !== $this->logger) {
+ $this->logger->debug(sprintf('Notified event "%s" to listener "%s".', $eventName, $info['pretty']));
+ }
+
+ if (!isset($this->called[$eventName])) {
+ $this->called[$eventName] = new \SplObjectStorage();
+ }
+
+ $this->called[$eventName]->attach($listener);
+ }
+
+ if (null !== $this->logger && $skipped) {
+ $this->logger->debug(sprintf('Listener "%s" was not called for event "%s".', $info['pretty'], $eventName));
+ }
+
+ if ($listener->stoppedPropagation()) {
+ if (null !== $this->logger) {
+ $this->logger->debug(sprintf('Listener "%s" stopped propagation of the event "%s".', $info['pretty'], $eventName));
+ }
+
+ $skipped = true;
+ }
+ }
+ }
+
+ /**
+ * Returns information about the listener.
+ *
+ * @param object $listener The listener
+ * @param string $eventName The event name
+ *
+ * @return array Information about the listener
+ */
+ private function getListenerInfo($listener, $eventName)
+ {
+ $info = array(
+ 'event' => $eventName,
+ 'priority' => $this->getListenerPriority($eventName, $listener),
+ );
+ if ($listener instanceof \Closure) {
+ $info += array(
+ 'type' => 'Closure',
+ 'pretty' => 'closure',
+ );
+ } elseif (is_string($listener)) {
+ try {
+ $r = new \ReflectionFunction($listener);
+ $file = $r->getFileName();
+ $line = $r->getStartLine();
+ } catch (\ReflectionException $e) {
+ $file = null;
+ $line = null;
+ }
+ $info += array(
+ 'type' => 'Function',
+ 'function' => $listener,
+ 'file' => $file,
+ 'line' => $line,
+ 'pretty' => $listener,
+ );
+ } elseif (is_array($listener) || (is_object($listener) && is_callable($listener))) {
+ if (!is_array($listener)) {
+ $listener = array($listener, '__invoke');
+ }
+ $class = is_object($listener[0]) ? get_class($listener[0]) : $listener[0];
+ try {
+ $r = new \ReflectionMethod($class, $listener[1]);
+ $file = $r->getFileName();
+ $line = $r->getStartLine();
+ } catch (\ReflectionException $e) {
+ $file = null;
+ $line = null;
+ }
+ $info += array(
+ 'type' => 'Method',
+ 'class' => $class,
+ 'method' => $listener[1],
+ 'file' => $file,
+ 'line' => $line,
+ 'pretty' => $class.'::'.$listener[1],
+ );
+ }
+
+ return $info;
+ }
+
+ private function sortListenersByPriority($a, $b)
+ {
+ if (is_int($a['priority']) && !is_int($b['priority'])) {
+ return 1;
+ }
+
+ if (!is_int($a['priority']) && is_int($b['priority'])) {
+ return -1;
+ }
+
+ if ($a['priority'] === $b['priority']) {
+ return 0;
+ }
+
+ if ($a['priority'] > $b['priority']) {
+ return -1;
+ }
+
+ return 1;
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php
new file mode 100644
index 000000000..5483e8150
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php
@@ -0,0 +1,34 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher\Debug;
+
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+
+/**
+ * @author Fabien Potencier
+ */
+interface TraceableEventDispatcherInterface extends EventDispatcherInterface
+{
+ /**
+ * Gets the called listeners.
+ *
+ * @return array An array of called listeners
+ */
+ public function getCalledListeners();
+
+ /**
+ * Gets the not called listeners.
+ *
+ * @return array An array of not called listeners
+ */
+ public function getNotCalledListeners();
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Debug/WrappedListener.php b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Debug/WrappedListener.php
new file mode 100644
index 000000000..e16627d6a
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Debug/WrappedListener.php
@@ -0,0 +1,71 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher\Debug;
+
+use Symfony\Component\Stopwatch\Stopwatch;
+use Symfony\Component\EventDispatcher\Event;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+
+/**
+ * @author Fabien Potencier
+ */
+class WrappedListener
+{
+ private $listener;
+ private $name;
+ private $called;
+ private $stoppedPropagation;
+ private $stopwatch;
+ private $dispatcher;
+
+ public function __construct($listener, $name, Stopwatch $stopwatch, EventDispatcherInterface $dispatcher = null)
+ {
+ $this->listener = $listener;
+ $this->name = $name;
+ $this->stopwatch = $stopwatch;
+ $this->dispatcher = $dispatcher;
+ $this->called = false;
+ $this->stoppedPropagation = false;
+ }
+
+ public function getWrappedListener()
+ {
+ return $this->listener;
+ }
+
+ public function wasCalled()
+ {
+ return $this->called;
+ }
+
+ public function stoppedPropagation()
+ {
+ return $this->stoppedPropagation;
+ }
+
+ public function __invoke(Event $event, $eventName, EventDispatcherInterface $dispatcher)
+ {
+ $this->called = true;
+
+ $e = $this->stopwatch->start($this->name, 'event_listener');
+
+ call_user_func($this->listener, $event, $eventName, $this->dispatcher ?: $dispatcher);
+
+ if ($e->isStarted()) {
+ $e->stop();
+ }
+
+ if ($event->isPropagationStopped()) {
+ $this->stoppedPropagation = true;
+ }
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php
new file mode 100644
index 000000000..7e74a37aa
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php
@@ -0,0 +1,110 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher\DependencyInjection;
+
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+
+/**
+ * Compiler pass to register tagged services for an event dispatcher.
+ */
+class RegisterListenersPass implements CompilerPassInterface
+{
+ /**
+ * @var string
+ */
+ protected $dispatcherService;
+
+ /**
+ * @var string
+ */
+ protected $listenerTag;
+
+ /**
+ * @var string
+ */
+ protected $subscriberTag;
+
+ /**
+ * Constructor.
+ *
+ * @param string $dispatcherService Service name of the event dispatcher in processed container
+ * @param string $listenerTag Tag name used for listener
+ * @param string $subscriberTag Tag name used for subscribers
+ */
+ public function __construct($dispatcherService = 'event_dispatcher', $listenerTag = 'kernel.event_listener', $subscriberTag = 'kernel.event_subscriber')
+ {
+ $this->dispatcherService = $dispatcherService;
+ $this->listenerTag = $listenerTag;
+ $this->subscriberTag = $subscriberTag;
+ }
+
+ public function process(ContainerBuilder $container)
+ {
+ if (!$container->hasDefinition($this->dispatcherService) && !$container->hasAlias($this->dispatcherService)) {
+ return;
+ }
+
+ $definition = $container->findDefinition($this->dispatcherService);
+
+ foreach ($container->findTaggedServiceIds($this->listenerTag) as $id => $events) {
+ $def = $container->getDefinition($id);
+ if (!$def->isPublic()) {
+ throw new \InvalidArgumentException(sprintf('The service "%s" must be public as event listeners are lazy-loaded.', $id));
+ }
+
+ if ($def->isAbstract()) {
+ throw new \InvalidArgumentException(sprintf('The service "%s" must not be abstract as event listeners are lazy-loaded.', $id));
+ }
+
+ foreach ($events as $event) {
+ $priority = isset($event['priority']) ? $event['priority'] : 0;
+
+ if (!isset($event['event'])) {
+ throw new \InvalidArgumentException(sprintf('Service "%s" must define the "event" attribute on "%s" tags.', $id, $this->listenerTag));
+ }
+
+ if (!isset($event['method'])) {
+ $event['method'] = 'on'.preg_replace_callback(array(
+ '/(?<=\b)[a-z]/i',
+ '/[^a-z0-9]/i',
+ ), function ($matches) { return strtoupper($matches[0]); }, $event['event']);
+ $event['method'] = preg_replace('/[^a-z0-9]/i', '', $event['method']);
+ }
+
+ $definition->addMethodCall('addListenerService', array($event['event'], array($id, $event['method']), $priority));
+ }
+ }
+
+ foreach ($container->findTaggedServiceIds($this->subscriberTag) as $id => $attributes) {
+ $def = $container->getDefinition($id);
+ if (!$def->isPublic()) {
+ throw new \InvalidArgumentException(sprintf('The service "%s" must be public as event subscribers are lazy-loaded.', $id));
+ }
+
+ if ($def->isAbstract()) {
+ throw new \InvalidArgumentException(sprintf('The service "%s" must not be abstract as event subscribers are lazy-loaded.', $id));
+ }
+
+ // We must assume that the class value has been correctly filled, even if the service is created by a factory
+ $class = $container->getParameterBag()->resolveValue($def->getClass());
+
+ $refClass = new \ReflectionClass($class);
+ $interface = 'Symfony\Component\EventDispatcher\EventSubscriberInterface';
+ if (!$refClass->implementsInterface($interface)) {
+ throw new \InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, $interface));
+ }
+
+ $definition->addMethodCall('addSubscriberService', array($id, $class));
+ }
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Event.php b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Event.php
new file mode 100644
index 000000000..8d49422cd
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Event.php
@@ -0,0 +1,58 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher;
+
+/**
+ * Event is the base class for classes containing event data.
+ *
+ * This class contains no event data. It is used by events that do not pass
+ * state information to an event handler when an event is raised.
+ *
+ * You can call the method stopPropagation() to abort the execution of
+ * further listeners in your event listener.
+ *
+ * @author Guilherme Blanco
+ * @author Jonathan Wage
+ * @author Roman Borschel
+ * @author Bernhard Schussek
+ */
+class Event
+{
+ /**
+ * @var bool Whether no further event listeners should be triggered
+ */
+ private $propagationStopped = false;
+
+ /**
+ * Returns whether further event listeners should be triggered.
+ *
+ * @see Event::stopPropagation()
+ *
+ * @return bool Whether propagation was already stopped for this event.
+ */
+ public function isPropagationStopped()
+ {
+ return $this->propagationStopped;
+ }
+
+ /**
+ * Stops the propagation of the event to further event listeners.
+ *
+ * If multiple event listeners are connected to the same event, no
+ * further event listener will be triggered once any trigger calls
+ * stopPropagation().
+ */
+ public function stopPropagation()
+ {
+ $this->propagationStopped = true;
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/EventDispatcher.php b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/EventDispatcher.php
new file mode 100644
index 000000000..e1882d232
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/EventDispatcher.php
@@ -0,0 +1,188 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher;
+
+/**
+ * The EventDispatcherInterface is the central point of Symfony's event listener system.
+ *
+ * Listeners are registered on the manager and events are dispatched through the
+ * manager.
+ *
+ * @author Guilherme Blanco
+ * @author Jonathan Wage
+ * @author Roman Borschel
+ * @author Bernhard Schussek
+ * @author Fabien Potencier
+ * @author Jordi Boggiano
+ * @author Jordan Alliot
+ */
+class EventDispatcher implements EventDispatcherInterface
+{
+ private $listeners = array();
+ private $sorted = array();
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dispatch($eventName, Event $event = null)
+ {
+ if (null === $event) {
+ $event = new Event();
+ }
+
+ if ($listeners = $this->getListeners($eventName)) {
+ $this->doDispatch($listeners, $eventName, $event);
+ }
+
+ return $event;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getListeners($eventName = null)
+ {
+ if (null !== $eventName) {
+ if (!isset($this->listeners[$eventName])) {
+ return array();
+ }
+
+ if (!isset($this->sorted[$eventName])) {
+ $this->sortListeners($eventName);
+ }
+
+ return $this->sorted[$eventName];
+ }
+
+ foreach ($this->listeners as $eventName => $eventListeners) {
+ if (!isset($this->sorted[$eventName])) {
+ $this->sortListeners($eventName);
+ }
+ }
+
+ return array_filter($this->sorted);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getListenerPriority($eventName, $listener)
+ {
+ if (!isset($this->listeners[$eventName])) {
+ return;
+ }
+
+ foreach ($this->listeners[$eventName] as $priority => $listeners) {
+ if (false !== ($key = array_search($listener, $listeners, true))) {
+ return $priority;
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasListeners($eventName = null)
+ {
+ return (bool) count($this->getListeners($eventName));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addListener($eventName, $listener, $priority = 0)
+ {
+ $this->listeners[$eventName][$priority][] = $listener;
+ unset($this->sorted[$eventName]);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeListener($eventName, $listener)
+ {
+ if (!isset($this->listeners[$eventName])) {
+ return;
+ }
+
+ foreach ($this->listeners[$eventName] as $priority => $listeners) {
+ if (false !== ($key = array_search($listener, $listeners, true))) {
+ unset($this->listeners[$eventName][$priority][$key], $this->sorted[$eventName]);
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addSubscriber(EventSubscriberInterface $subscriber)
+ {
+ foreach ($subscriber->getSubscribedEvents() as $eventName => $params) {
+ if (is_string($params)) {
+ $this->addListener($eventName, array($subscriber, $params));
+ } elseif (is_string($params[0])) {
+ $this->addListener($eventName, array($subscriber, $params[0]), isset($params[1]) ? $params[1] : 0);
+ } else {
+ foreach ($params as $listener) {
+ $this->addListener($eventName, array($subscriber, $listener[0]), isset($listener[1]) ? $listener[1] : 0);
+ }
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeSubscriber(EventSubscriberInterface $subscriber)
+ {
+ foreach ($subscriber->getSubscribedEvents() as $eventName => $params) {
+ if (is_array($params) && is_array($params[0])) {
+ foreach ($params as $listener) {
+ $this->removeListener($eventName, array($subscriber, $listener[0]));
+ }
+ } else {
+ $this->removeListener($eventName, array($subscriber, is_string($params) ? $params : $params[0]));
+ }
+ }
+ }
+
+ /**
+ * Triggers the listeners of an event.
+ *
+ * This method can be overridden to add functionality that is executed
+ * for each listener.
+ *
+ * @param callable[] $listeners The event listeners.
+ * @param string $eventName The name of the event to dispatch.
+ * @param Event $event The event object to pass to the event handlers/listeners.
+ */
+ protected function doDispatch($listeners, $eventName, Event $event)
+ {
+ foreach ($listeners as $listener) {
+ call_user_func($listener, $event, $eventName, $this);
+ if ($event->isPropagationStopped()) {
+ break;
+ }
+ }
+ }
+
+ /**
+ * Sorts the internal list of listeners for the given event by priority.
+ *
+ * @param string $eventName The name of the event.
+ */
+ private function sortListeners($eventName)
+ {
+ krsort($this->listeners[$eventName]);
+ $this->sorted[$eventName] = call_user_func_array('array_merge', $this->listeners[$eventName]);
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/EventDispatcherInterface.php b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/EventDispatcherInterface.php
new file mode 100644
index 000000000..e0ba65f95
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/EventDispatcherInterface.php
@@ -0,0 +1,100 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher;
+
+/**
+ * The EventDispatcherInterface is the central point of Symfony's event listener system.
+ * Listeners are registered on the manager and events are dispatched through the
+ * manager.
+ *
+ * @author Bernhard Schussek
+ */
+interface EventDispatcherInterface
+{
+ /**
+ * Dispatches an event to all registered listeners.
+ *
+ * @param string $eventName The name of the event to dispatch. The name of
+ * the event is the name of the method that is
+ * invoked on listeners.
+ * @param Event $event The event to pass to the event handlers/listeners.
+ * If not supplied, an empty Event instance is created.
+ *
+ * @return Event
+ */
+ public function dispatch($eventName, Event $event = null);
+
+ /**
+ * Adds an event listener that listens on the specified events.
+ *
+ * @param string $eventName The event to listen on
+ * @param callable $listener The listener
+ * @param int $priority The higher this value, the earlier an event
+ * listener will be triggered in the chain (defaults to 0)
+ */
+ public function addListener($eventName, $listener, $priority = 0);
+
+ /**
+ * Adds an event subscriber.
+ *
+ * The subscriber is asked for all the events he is
+ * interested in and added as a listener for these events.
+ *
+ * @param EventSubscriberInterface $subscriber The subscriber.
+ */
+ public function addSubscriber(EventSubscriberInterface $subscriber);
+
+ /**
+ * Removes an event listener from the specified events.
+ *
+ * @param string $eventName The event to remove a listener from
+ * @param callable $listener The listener to remove
+ */
+ public function removeListener($eventName, $listener);
+
+ /**
+ * Removes an event subscriber.
+ *
+ * @param EventSubscriberInterface $subscriber The subscriber
+ */
+ public function removeSubscriber(EventSubscriberInterface $subscriber);
+
+ /**
+ * Gets the listeners of a specific event or all listeners sorted by descending priority.
+ *
+ * @param string $eventName The name of the event
+ *
+ * @return array The event listeners for the specified event, or all event listeners by event name
+ */
+ public function getListeners($eventName = null);
+
+ /**
+ * Gets the listener priority for a specific event.
+ *
+ * Returns null if the event or the listener does not exist.
+ *
+ * @param string $eventName The name of the event
+ * @param callable $listener The listener
+ *
+ * @return int|null The event listener priority
+ */
+ public function getListenerPriority($eventName, $listener);
+
+ /**
+ * Checks whether an event has any registered listeners.
+ *
+ * @param string $eventName The name of the event
+ *
+ * @return bool true if the specified event has any listeners, false otherwise
+ */
+ public function hasListeners($eventName = null);
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/EventSubscriberInterface.php b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/EventSubscriberInterface.php
new file mode 100644
index 000000000..ec53e54e2
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/EventSubscriberInterface.php
@@ -0,0 +1,46 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher;
+
+/**
+ * An EventSubscriber knows himself what events he is interested in.
+ * If an EventSubscriber is added to an EventDispatcherInterface, the manager invokes
+ * {@link getSubscribedEvents} and registers the subscriber as a listener for all
+ * returned events.
+ *
+ * @author Guilherme Blanco
+ * @author Jonathan Wage
+ * @author Roman Borschel
+ * @author Bernhard Schussek
+ */
+interface EventSubscriberInterface
+{
+ /**
+ * Returns an array of event names this subscriber wants to listen to.
+ *
+ * The array keys are event names and the value can be:
+ *
+ * * The method name to call (priority defaults to 0)
+ * * An array composed of the method name to call and the priority
+ * * An array of arrays composed of the method names to call and respective
+ * priorities, or 0 if unset
+ *
+ * For instance:
+ *
+ * * array('eventName' => 'methodName')
+ * * array('eventName' => array('methodName', $priority))
+ * * array('eventName' => array(array('methodName1', $priority), array('methodName2'))
+ *
+ * @return array The event names to listen to
+ */
+ public static function getSubscribedEvents();
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/GenericEvent.php b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/GenericEvent.php
new file mode 100644
index 000000000..6458180a5
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/GenericEvent.php
@@ -0,0 +1,186 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher;
+
+/**
+ * Event encapsulation class.
+ *
+ * Encapsulates events thus decoupling the observer from the subject they encapsulate.
+ *
+ * @author Drak
+ */
+class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate
+{
+ /**
+ * Event subject.
+ *
+ * @var mixed usually object or callable
+ */
+ protected $subject;
+
+ /**
+ * Array of arguments.
+ *
+ * @var array
+ */
+ protected $arguments;
+
+ /**
+ * Encapsulate an event with $subject and $args.
+ *
+ * @param mixed $subject The subject of the event, usually an object.
+ * @param array $arguments Arguments to store in the event.
+ */
+ public function __construct($subject = null, array $arguments = array())
+ {
+ $this->subject = $subject;
+ $this->arguments = $arguments;
+ }
+
+ /**
+ * Getter for subject property.
+ *
+ * @return mixed $subject The observer subject.
+ */
+ public function getSubject()
+ {
+ return $this->subject;
+ }
+
+ /**
+ * Get argument by key.
+ *
+ * @param string $key Key.
+ *
+ * @throws \InvalidArgumentException If key is not found.
+ *
+ * @return mixed Contents of array key.
+ */
+ public function getArgument($key)
+ {
+ if ($this->hasArgument($key)) {
+ return $this->arguments[$key];
+ }
+
+ throw new \InvalidArgumentException(sprintf('Argument "%s" not found.', $key));
+ }
+
+ /**
+ * Add argument to event.
+ *
+ * @param string $key Argument name.
+ * @param mixed $value Value.
+ *
+ * @return GenericEvent
+ */
+ public function setArgument($key, $value)
+ {
+ $this->arguments[$key] = $value;
+
+ return $this;
+ }
+
+ /**
+ * Getter for all arguments.
+ *
+ * @return array
+ */
+ public function getArguments()
+ {
+ return $this->arguments;
+ }
+
+ /**
+ * Set args property.
+ *
+ * @param array $args Arguments.
+ *
+ * @return GenericEvent
+ */
+ public function setArguments(array $args = array())
+ {
+ $this->arguments = $args;
+
+ return $this;
+ }
+
+ /**
+ * Has argument.
+ *
+ * @param string $key Key of arguments array.
+ *
+ * @return bool
+ */
+ public function hasArgument($key)
+ {
+ return array_key_exists($key, $this->arguments);
+ }
+
+ /**
+ * ArrayAccess for argument getter.
+ *
+ * @param string $key Array key.
+ *
+ * @throws \InvalidArgumentException If key does not exist in $this->args.
+ *
+ * @return mixed
+ */
+ public function offsetGet($key)
+ {
+ return $this->getArgument($key);
+ }
+
+ /**
+ * ArrayAccess for argument setter.
+ *
+ * @param string $key Array key to set.
+ * @param mixed $value Value.
+ */
+ public function offsetSet($key, $value)
+ {
+ $this->setArgument($key, $value);
+ }
+
+ /**
+ * ArrayAccess for unset argument.
+ *
+ * @param string $key Array key.
+ */
+ public function offsetUnset($key)
+ {
+ if ($this->hasArgument($key)) {
+ unset($this->arguments[$key]);
+ }
+ }
+
+ /**
+ * ArrayAccess has argument.
+ *
+ * @param string $key Array key.
+ *
+ * @return bool
+ */
+ public function offsetExists($key)
+ {
+ return $this->hasArgument($key);
+ }
+
+ /**
+ * IteratorAggregate for iterating over the object like an array.
+ *
+ * @return \ArrayIterator
+ */
+ public function getIterator()
+ {
+ return new \ArrayIterator($this->arguments);
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php
new file mode 100644
index 000000000..13e8572ed
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php
@@ -0,0 +1,101 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher;
+
+/**
+ * A read-only proxy for an event dispatcher.
+ *
+ * @author Bernhard Schussek
+ */
+class ImmutableEventDispatcher implements EventDispatcherInterface
+{
+ /**
+ * The proxied dispatcher.
+ *
+ * @var EventDispatcherInterface
+ */
+ private $dispatcher;
+
+ /**
+ * Creates an unmodifiable proxy for an event dispatcher.
+ *
+ * @param EventDispatcherInterface $dispatcher The proxied event dispatcher.
+ */
+ public function __construct(EventDispatcherInterface $dispatcher)
+ {
+ $this->dispatcher = $dispatcher;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dispatch($eventName, Event $event = null)
+ {
+ return $this->dispatcher->dispatch($eventName, $event);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addListener($eventName, $listener, $priority = 0)
+ {
+ throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addSubscriber(EventSubscriberInterface $subscriber)
+ {
+ throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeListener($eventName, $listener)
+ {
+ throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeSubscriber(EventSubscriberInterface $subscriber)
+ {
+ throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getListeners($eventName = null)
+ {
+ return $this->dispatcher->getListeners($eventName);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getListenerPriority($eventName, $listener)
+ {
+ return $this->dispatcher->getListenerPriority($eventName, $listener);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasListeners($eventName = null)
+ {
+ return $this->dispatcher->hasListeners($eventName);
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/LICENSE b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/LICENSE
new file mode 100644
index 000000000..43028bc60
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2004-2015 Fabien Potencier
+
+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.
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/README.md b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/README.md
new file mode 100644
index 000000000..8031f4dd3
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/README.md
@@ -0,0 +1,27 @@
+EventDispatcher Component
+=========================
+
+The Symfony EventDispatcher component implements the Mediator pattern in a
+simple and effective way to make your projects truly extensible.
+
+```php
+use Symfony\Component\EventDispatcher\EventDispatcher;
+use Symfony\Component\EventDispatcher\Event;
+
+$dispatcher = new EventDispatcher();
+
+$dispatcher->addListener('event_name', function (Event $event) {
+ // ...
+});
+
+$dispatcher->dispatch('event_name');
+```
+
+Resources
+---------
+
+You can run the unit tests with the following command:
+
+ $ cd path/to/Symfony/Component/EventDispatcher/
+ $ composer install
+ $ phpunit
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Tests/AbstractEventDispatcherTest.php b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Tests/AbstractEventDispatcherTest.php
new file mode 100644
index 000000000..30429d3f7
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Tests/AbstractEventDispatcherTest.php
@@ -0,0 +1,373 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher\Tests;
+
+use Symfony\Component\EventDispatcher\Event;
+use Symfony\Component\EventDispatcher\EventDispatcher;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+abstract class AbstractEventDispatcherTest extends \PHPUnit_Framework_TestCase
+{
+ /* Some pseudo events */
+ const preFoo = 'pre.foo';
+ const postFoo = 'post.foo';
+ const preBar = 'pre.bar';
+ const postBar = 'post.bar';
+
+ /**
+ * @var EventDispatcher
+ */
+ private $dispatcher;
+
+ private $listener;
+
+ protected function setUp()
+ {
+ $this->dispatcher = $this->createEventDispatcher();
+ $this->listener = new TestEventListener();
+ }
+
+ protected function tearDown()
+ {
+ $this->dispatcher = null;
+ $this->listener = null;
+ }
+
+ abstract protected function createEventDispatcher();
+
+ public function testInitialState()
+ {
+ $this->assertEquals(array(), $this->dispatcher->getListeners());
+ $this->assertFalse($this->dispatcher->hasListeners(self::preFoo));
+ $this->assertFalse($this->dispatcher->hasListeners(self::postFoo));
+ }
+
+ public function testAddListener()
+ {
+ $this->dispatcher->addListener('pre.foo', array($this->listener, 'preFoo'));
+ $this->dispatcher->addListener('post.foo', array($this->listener, 'postFoo'));
+ $this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
+ $this->assertTrue($this->dispatcher->hasListeners(self::postFoo));
+ $this->assertCount(1, $this->dispatcher->getListeners(self::preFoo));
+ $this->assertCount(1, $this->dispatcher->getListeners(self::postFoo));
+ $this->assertCount(2, $this->dispatcher->getListeners());
+ }
+
+ public function testGetListenersSortsByPriority()
+ {
+ $listener1 = new TestEventListener();
+ $listener2 = new TestEventListener();
+ $listener3 = new TestEventListener();
+ $listener1->name = '1';
+ $listener2->name = '2';
+ $listener3->name = '3';
+
+ $this->dispatcher->addListener('pre.foo', array($listener1, 'preFoo'), -10);
+ $this->dispatcher->addListener('pre.foo', array($listener2, 'preFoo'), 10);
+ $this->dispatcher->addListener('pre.foo', array($listener3, 'preFoo'));
+
+ $expected = array(
+ array($listener2, 'preFoo'),
+ array($listener3, 'preFoo'),
+ array($listener1, 'preFoo'),
+ );
+
+ $this->assertSame($expected, $this->dispatcher->getListeners('pre.foo'));
+ }
+
+ public function testGetAllListenersSortsByPriority()
+ {
+ $listener1 = new TestEventListener();
+ $listener2 = new TestEventListener();
+ $listener3 = new TestEventListener();
+ $listener4 = new TestEventListener();
+ $listener5 = new TestEventListener();
+ $listener6 = new TestEventListener();
+
+ $this->dispatcher->addListener('pre.foo', $listener1, -10);
+ $this->dispatcher->addListener('pre.foo', $listener2);
+ $this->dispatcher->addListener('pre.foo', $listener3, 10);
+ $this->dispatcher->addListener('post.foo', $listener4, -10);
+ $this->dispatcher->addListener('post.foo', $listener5);
+ $this->dispatcher->addListener('post.foo', $listener6, 10);
+
+ $expected = array(
+ 'pre.foo' => array($listener3, $listener2, $listener1),
+ 'post.foo' => array($listener6, $listener5, $listener4),
+ );
+
+ $this->assertSame($expected, $this->dispatcher->getListeners());
+ }
+
+ public function testGetListenerPriority()
+ {
+ $listener1 = new TestEventListener();
+ $listener2 = new TestEventListener();
+
+ $this->dispatcher->addListener('pre.foo', $listener1, -10);
+ $this->dispatcher->addListener('pre.foo', $listener2);
+
+ $this->assertSame(-10, $this->dispatcher->getListenerPriority('pre.foo', $listener1));
+ $this->assertSame(0, $this->dispatcher->getListenerPriority('pre.foo', $listener2));
+ $this->assertNull($this->dispatcher->getListenerPriority('pre.bar', $listener2));
+ $this->assertNull($this->dispatcher->getListenerPriority('pre.foo', function () {}));
+ }
+
+ public function testDispatch()
+ {
+ $this->dispatcher->addListener('pre.foo', array($this->listener, 'preFoo'));
+ $this->dispatcher->addListener('post.foo', array($this->listener, 'postFoo'));
+ $this->dispatcher->dispatch(self::preFoo);
+ $this->assertTrue($this->listener->preFooInvoked);
+ $this->assertFalse($this->listener->postFooInvoked);
+ $this->assertInstanceOf('Symfony\Component\EventDispatcher\Event', $this->dispatcher->dispatch('noevent'));
+ $this->assertInstanceOf('Symfony\Component\EventDispatcher\Event', $this->dispatcher->dispatch(self::preFoo));
+ $event = new Event();
+ $return = $this->dispatcher->dispatch(self::preFoo, $event);
+ $this->assertSame($event, $return);
+ }
+
+ public function testDispatchForClosure()
+ {
+ $invoked = 0;
+ $listener = function () use (&$invoked) {
+ ++$invoked;
+ };
+ $this->dispatcher->addListener('pre.foo', $listener);
+ $this->dispatcher->addListener('post.foo', $listener);
+ $this->dispatcher->dispatch(self::preFoo);
+ $this->assertEquals(1, $invoked);
+ }
+
+ public function testStopEventPropagation()
+ {
+ $otherListener = new TestEventListener();
+
+ // postFoo() stops the propagation, so only one listener should
+ // be executed
+ // Manually set priority to enforce $this->listener to be called first
+ $this->dispatcher->addListener('post.foo', array($this->listener, 'postFoo'), 10);
+ $this->dispatcher->addListener('post.foo', array($otherListener, 'preFoo'));
+ $this->dispatcher->dispatch(self::postFoo);
+ $this->assertTrue($this->listener->postFooInvoked);
+ $this->assertFalse($otherListener->postFooInvoked);
+ }
+
+ public function testDispatchByPriority()
+ {
+ $invoked = array();
+ $listener1 = function () use (&$invoked) {
+ $invoked[] = '1';
+ };
+ $listener2 = function () use (&$invoked) {
+ $invoked[] = '2';
+ };
+ $listener3 = function () use (&$invoked) {
+ $invoked[] = '3';
+ };
+ $this->dispatcher->addListener('pre.foo', $listener1, -10);
+ $this->dispatcher->addListener('pre.foo', $listener2);
+ $this->dispatcher->addListener('pre.foo', $listener3, 10);
+ $this->dispatcher->dispatch(self::preFoo);
+ $this->assertEquals(array('3', '2', '1'), $invoked);
+ }
+
+ public function testRemoveListener()
+ {
+ $this->dispatcher->addListener('pre.bar', $this->listener);
+ $this->assertTrue($this->dispatcher->hasListeners(self::preBar));
+ $this->dispatcher->removeListener('pre.bar', $this->listener);
+ $this->assertFalse($this->dispatcher->hasListeners(self::preBar));
+ $this->dispatcher->removeListener('notExists', $this->listener);
+ }
+
+ public function testAddSubscriber()
+ {
+ $eventSubscriber = new TestEventSubscriber();
+ $this->dispatcher->addSubscriber($eventSubscriber);
+ $this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
+ $this->assertTrue($this->dispatcher->hasListeners(self::postFoo));
+ }
+
+ public function testAddSubscriberWithPriorities()
+ {
+ $eventSubscriber = new TestEventSubscriber();
+ $this->dispatcher->addSubscriber($eventSubscriber);
+
+ $eventSubscriber = new TestEventSubscriberWithPriorities();
+ $this->dispatcher->addSubscriber($eventSubscriber);
+
+ $listeners = $this->dispatcher->getListeners('pre.foo');
+ $this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
+ $this->assertCount(2, $listeners);
+ $this->assertInstanceOf('Symfony\Component\EventDispatcher\Tests\TestEventSubscriberWithPriorities', $listeners[0][0]);
+ }
+
+ public function testAddSubscriberWithMultipleListeners()
+ {
+ $eventSubscriber = new TestEventSubscriberWithMultipleListeners();
+ $this->dispatcher->addSubscriber($eventSubscriber);
+
+ $listeners = $this->dispatcher->getListeners('pre.foo');
+ $this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
+ $this->assertCount(2, $listeners);
+ $this->assertEquals('preFoo2', $listeners[0][1]);
+ }
+
+ public function testRemoveSubscriber()
+ {
+ $eventSubscriber = new TestEventSubscriber();
+ $this->dispatcher->addSubscriber($eventSubscriber);
+ $this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
+ $this->assertTrue($this->dispatcher->hasListeners(self::postFoo));
+ $this->dispatcher->removeSubscriber($eventSubscriber);
+ $this->assertFalse($this->dispatcher->hasListeners(self::preFoo));
+ $this->assertFalse($this->dispatcher->hasListeners(self::postFoo));
+ }
+
+ public function testRemoveSubscriberWithPriorities()
+ {
+ $eventSubscriber = new TestEventSubscriberWithPriorities();
+ $this->dispatcher->addSubscriber($eventSubscriber);
+ $this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
+ $this->dispatcher->removeSubscriber($eventSubscriber);
+ $this->assertFalse($this->dispatcher->hasListeners(self::preFoo));
+ }
+
+ public function testRemoveSubscriberWithMultipleListeners()
+ {
+ $eventSubscriber = new TestEventSubscriberWithMultipleListeners();
+ $this->dispatcher->addSubscriber($eventSubscriber);
+ $this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
+ $this->assertCount(2, $this->dispatcher->getListeners(self::preFoo));
+ $this->dispatcher->removeSubscriber($eventSubscriber);
+ $this->assertFalse($this->dispatcher->hasListeners(self::preFoo));
+ }
+
+ public function testEventReceivesTheDispatcherInstanceAsArgument()
+ {
+ $listener = new TestWithDispatcher();
+ $this->dispatcher->addListener('test', array($listener, 'foo'));
+ $this->assertNull($listener->name);
+ $this->assertNull($listener->dispatcher);
+ $this->dispatcher->dispatch('test');
+ $this->assertEquals('test', $listener->name);
+ $this->assertSame($this->dispatcher, $listener->dispatcher);
+ }
+
+ /**
+ * @see https://bugs.php.net/bug.php?id=62976
+ *
+ * This bug affects:
+ * - The PHP 5.3 branch for versions < 5.3.18
+ * - The PHP 5.4 branch for versions < 5.4.8
+ * - The PHP 5.5 branch is not affected
+ */
+ public function testWorkaroundForPhpBug62976()
+ {
+ $dispatcher = $this->createEventDispatcher();
+ $dispatcher->addListener('bug.62976', new CallableClass());
+ $dispatcher->removeListener('bug.62976', function () {});
+ $this->assertTrue($dispatcher->hasListeners('bug.62976'));
+ }
+
+ public function testHasListenersWhenAddedCallbackListenerIsRemoved()
+ {
+ $listener = function () {};
+ $this->dispatcher->addListener('foo', $listener);
+ $this->dispatcher->removeListener('foo', $listener);
+ $this->assertFalse($this->dispatcher->hasListeners());
+ }
+
+ public function testGetListenersWhenAddedCallbackListenerIsRemoved()
+ {
+ $listener = function () {};
+ $this->dispatcher->addListener('foo', $listener);
+ $this->dispatcher->removeListener('foo', $listener);
+ $this->assertSame(array(), $this->dispatcher->getListeners());
+ }
+
+ public function testHasListenersWithoutEventsReturnsFalseAfterHasListenersWithEventHasBeenCalled()
+ {
+ $this->assertFalse($this->dispatcher->hasListeners('foo'));
+ $this->assertFalse($this->dispatcher->hasListeners());
+ }
+}
+
+class CallableClass
+{
+ public function __invoke()
+ {
+ }
+}
+
+class TestEventListener
+{
+ public $preFooInvoked = false;
+ public $postFooInvoked = false;
+
+ /* Listener methods */
+
+ public function preFoo(Event $e)
+ {
+ $this->preFooInvoked = true;
+ }
+
+ public function postFoo(Event $e)
+ {
+ $this->postFooInvoked = true;
+
+ $e->stopPropagation();
+ }
+}
+
+class TestWithDispatcher
+{
+ public $name;
+ public $dispatcher;
+
+ public function foo(Event $e, $name, $dispatcher)
+ {
+ $this->name = $name;
+ $this->dispatcher = $dispatcher;
+ }
+}
+
+class TestEventSubscriber implements EventSubscriberInterface
+{
+ public static function getSubscribedEvents()
+ {
+ return array('pre.foo' => 'preFoo', 'post.foo' => 'postFoo');
+ }
+}
+
+class TestEventSubscriberWithPriorities implements EventSubscriberInterface
+{
+ public static function getSubscribedEvents()
+ {
+ return array(
+ 'pre.foo' => array('preFoo', 10),
+ 'post.foo' => array('postFoo'),
+ );
+ }
+}
+
+class TestEventSubscriberWithMultipleListeners implements EventSubscriberInterface
+{
+ public static function getSubscribedEvents()
+ {
+ return array('pre.foo' => array(
+ array('preFoo1'),
+ array('preFoo2', 10),
+ ));
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Tests/ContainerAwareEventDispatcherTest.php b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Tests/ContainerAwareEventDispatcherTest.php
new file mode 100644
index 000000000..c015a2e88
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Tests/ContainerAwareEventDispatcherTest.php
@@ -0,0 +1,183 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher\Tests;
+
+use Symfony\Component\DependencyInjection\Container;
+use Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher;
+use Symfony\Component\EventDispatcher\Event;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+class ContainerAwareEventDispatcherTest extends AbstractEventDispatcherTest
+{
+ protected function createEventDispatcher()
+ {
+ $container = new Container();
+
+ return new ContainerAwareEventDispatcher($container);
+ }
+
+ public function testAddAListenerService()
+ {
+ $event = new Event();
+
+ $service = $this->getMock('Symfony\Component\EventDispatcher\Tests\Service');
+
+ $service
+ ->expects($this->once())
+ ->method('onEvent')
+ ->with($event)
+ ;
+
+ $container = new Container();
+ $container->set('service.listener', $service);
+
+ $dispatcher = new ContainerAwareEventDispatcher($container);
+ $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'));
+
+ $dispatcher->dispatch('onEvent', $event);
+ }
+
+ public function testAddASubscriberService()
+ {
+ $event = new Event();
+
+ $service = $this->getMock('Symfony\Component\EventDispatcher\Tests\SubscriberService');
+
+ $service
+ ->expects($this->once())
+ ->method('onEvent')
+ ->with($event)
+ ;
+
+ $container = new Container();
+ $container->set('service.subscriber', $service);
+
+ $dispatcher = new ContainerAwareEventDispatcher($container);
+ $dispatcher->addSubscriberService('service.subscriber', 'Symfony\Component\EventDispatcher\Tests\SubscriberService');
+
+ $dispatcher->dispatch('onEvent', $event);
+ }
+
+ public function testPreventDuplicateListenerService()
+ {
+ $event = new Event();
+
+ $service = $this->getMock('Symfony\Component\EventDispatcher\Tests\Service');
+
+ $service
+ ->expects($this->once())
+ ->method('onEvent')
+ ->with($event)
+ ;
+
+ $container = new Container();
+ $container->set('service.listener', $service);
+
+ $dispatcher = new ContainerAwareEventDispatcher($container);
+ $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'), 5);
+ $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'), 10);
+
+ $dispatcher->dispatch('onEvent', $event);
+ }
+
+ public function testHasListenersOnLazyLoad()
+ {
+ $event = new Event();
+
+ $service = $this->getMock('Symfony\Component\EventDispatcher\Tests\Service');
+
+ $container = new Container();
+ $container->set('service.listener', $service);
+
+ $dispatcher = new ContainerAwareEventDispatcher($container);
+ $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'));
+
+ $service
+ ->expects($this->once())
+ ->method('onEvent')
+ ->with($event)
+ ;
+
+ $this->assertTrue($dispatcher->hasListeners());
+
+ if ($dispatcher->hasListeners('onEvent')) {
+ $dispatcher->dispatch('onEvent');
+ }
+ }
+
+ public function testGetListenersOnLazyLoad()
+ {
+ $service = $this->getMock('Symfony\Component\EventDispatcher\Tests\Service');
+
+ $container = new Container();
+ $container->set('service.listener', $service);
+
+ $dispatcher = new ContainerAwareEventDispatcher($container);
+ $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'));
+
+ $listeners = $dispatcher->getListeners();
+
+ $this->assertTrue(isset($listeners['onEvent']));
+
+ $this->assertCount(1, $dispatcher->getListeners('onEvent'));
+ }
+
+ public function testRemoveAfterDispatch()
+ {
+ $service = $this->getMock('Symfony\Component\EventDispatcher\Tests\Service');
+
+ $container = new Container();
+ $container->set('service.listener', $service);
+
+ $dispatcher = new ContainerAwareEventDispatcher($container);
+ $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'));
+
+ $dispatcher->dispatch('onEvent', new Event());
+ $dispatcher->removeListener('onEvent', array($container->get('service.listener'), 'onEvent'));
+ $this->assertFalse($dispatcher->hasListeners('onEvent'));
+ }
+
+ public function testRemoveBeforeDispatch()
+ {
+ $service = $this->getMock('Symfony\Component\EventDispatcher\Tests\Service');
+
+ $container = new Container();
+ $container->set('service.listener', $service);
+
+ $dispatcher = new ContainerAwareEventDispatcher($container);
+ $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'));
+
+ $dispatcher->removeListener('onEvent', array($container->get('service.listener'), 'onEvent'));
+ $this->assertFalse($dispatcher->hasListeners('onEvent'));
+ }
+}
+
+class Service
+{
+ public function onEvent(Event $e)
+ {
+ }
+}
+
+class SubscriberService implements EventSubscriberInterface
+{
+ public static function getSubscribedEvents()
+ {
+ return array(
+ 'onEvent' => array('onEvent'),
+ );
+ }
+
+ public function onEvent(Event $e)
+ {
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Tests/Debug/TraceableEventDispatcherTest.php b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Tests/Debug/TraceableEventDispatcherTest.php
new file mode 100644
index 000000000..1d4a8c850
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Tests/Debug/TraceableEventDispatcherTest.php
@@ -0,0 +1,199 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher\Tests\Debug;
+
+use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\EventDispatcher\EventDispatcher;
+use Symfony\Component\EventDispatcher\Event;
+use Symfony\Component\Stopwatch\Stopwatch;
+
+class TraceableEventDispatcherTest extends \PHPUnit_Framework_TestCase
+{
+ public function testAddRemoveListener()
+ {
+ $dispatcher = new EventDispatcher();
+ $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch());
+
+ $tdispatcher->addListener('foo', $listener = function () {});
+ $listeners = $dispatcher->getListeners('foo');
+ $this->assertCount(1, $listeners);
+ $this->assertSame($listener, $listeners[0]);
+
+ $tdispatcher->removeListener('foo', $listener);
+ $this->assertCount(0, $dispatcher->getListeners('foo'));
+ }
+
+ public function testGetListeners()
+ {
+ $dispatcher = new EventDispatcher();
+ $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch());
+
+ $tdispatcher->addListener('foo', $listener = function () {});
+ $this->assertSame($dispatcher->getListeners('foo'), $tdispatcher->getListeners('foo'));
+ }
+
+ public function testHasListeners()
+ {
+ $dispatcher = new EventDispatcher();
+ $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch());
+
+ $this->assertFalse($dispatcher->hasListeners('foo'));
+ $this->assertFalse($tdispatcher->hasListeners('foo'));
+
+ $tdispatcher->addListener('foo', $listener = function () {});
+ $this->assertTrue($dispatcher->hasListeners('foo'));
+ $this->assertTrue($tdispatcher->hasListeners('foo'));
+ }
+
+ public function testAddRemoveSubscriber()
+ {
+ $dispatcher = new EventDispatcher();
+ $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch());
+
+ $subscriber = new EventSubscriber();
+
+ $tdispatcher->addSubscriber($subscriber);
+ $listeners = $dispatcher->getListeners('foo');
+ $this->assertCount(1, $listeners);
+ $this->assertSame(array($subscriber, 'call'), $listeners[0]);
+
+ $tdispatcher->removeSubscriber($subscriber);
+ $this->assertCount(0, $dispatcher->getListeners('foo'));
+ }
+
+ public function testGetCalledListeners()
+ {
+ $dispatcher = new EventDispatcher();
+ $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch());
+ $tdispatcher->addListener('foo', $listener = function () {});
+
+ $this->assertEquals(array(), $tdispatcher->getCalledListeners());
+ $this->assertEquals(array('foo.closure' => array('event' => 'foo', 'type' => 'Closure', 'pretty' => 'closure', 'priority' => 0)), $tdispatcher->getNotCalledListeners());
+
+ $tdispatcher->dispatch('foo');
+
+ $this->assertEquals(array('foo.closure' => array('event' => 'foo', 'type' => 'Closure', 'pretty' => 'closure', 'priority' => null)), $tdispatcher->getCalledListeners());
+ $this->assertEquals(array(), $tdispatcher->getNotCalledListeners());
+ }
+
+ public function testGetCalledListenersNested()
+ {
+ $tdispatcher = null;
+ $dispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch());
+ $dispatcher->addListener('foo', function (Event $event, $eventName, $dispatcher) use (&$tdispatcher) {
+ $tdispatcher = $dispatcher;
+ $dispatcher->dispatch('bar');
+ });
+ $dispatcher->addListener('bar', function (Event $event) {});
+ $dispatcher->dispatch('foo');
+ $this->assertSame($dispatcher, $tdispatcher);
+ $this->assertCount(2, $dispatcher->getCalledListeners());
+ }
+
+ public function testLogger()
+ {
+ $logger = $this->getMock('Psr\Log\LoggerInterface');
+
+ $dispatcher = new EventDispatcher();
+ $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch(), $logger);
+ $tdispatcher->addListener('foo', $listener1 = function () {});
+ $tdispatcher->addListener('foo', $listener2 = function () {});
+
+ $logger->expects($this->at(0))->method('debug')->with('Notified event "foo" to listener "closure".');
+ $logger->expects($this->at(1))->method('debug')->with('Notified event "foo" to listener "closure".');
+
+ $tdispatcher->dispatch('foo');
+ }
+
+ public function testLoggerWithStoppedEvent()
+ {
+ $logger = $this->getMock('Psr\Log\LoggerInterface');
+
+ $dispatcher = new EventDispatcher();
+ $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch(), $logger);
+ $tdispatcher->addListener('foo', $listener1 = function (Event $event) { $event->stopPropagation(); });
+ $tdispatcher->addListener('foo', $listener2 = function () {});
+
+ $logger->expects($this->at(0))->method('debug')->with('Notified event "foo" to listener "closure".');
+ $logger->expects($this->at(1))->method('debug')->with('Listener "closure" stopped propagation of the event "foo".');
+ $logger->expects($this->at(2))->method('debug')->with('Listener "closure" was not called for event "foo".');
+
+ $tdispatcher->dispatch('foo');
+ }
+
+ public function testDispatchCallListeners()
+ {
+ $called = array();
+
+ $dispatcher = new EventDispatcher();
+ $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch());
+ $tdispatcher->addListener('foo', $listener1 = function () use (&$called) { $called[] = 'foo1'; });
+ $tdispatcher->addListener('foo', $listener2 = function () use (&$called) { $called[] = 'foo2'; });
+
+ $tdispatcher->dispatch('foo');
+
+ $this->assertEquals(array('foo1', 'foo2'), $called);
+ }
+
+ public function testDispatchNested()
+ {
+ $dispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch());
+ $loop = 1;
+ $dispatcher->addListener('foo', $listener1 = function () use ($dispatcher, &$loop) {
+ ++$loop;
+ if (2 == $loop) {
+ $dispatcher->dispatch('foo');
+ }
+ });
+
+ $dispatcher->dispatch('foo');
+ }
+
+ public function testDispatchReusedEventNested()
+ {
+ $nestedCall = false;
+ $dispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch());
+ $dispatcher->addListener('foo', function (Event $e) use ($dispatcher) {
+ $dispatcher->dispatch('bar', $e);
+ });
+ $dispatcher->addListener('bar', function (Event $e) use (&$nestedCall) {
+ $nestedCall = true;
+ });
+
+ $this->assertFalse($nestedCall);
+ $dispatcher->dispatch('foo');
+ $this->assertTrue($nestedCall);
+ }
+
+ public function testListenerCanRemoveItselfWhenExecuted()
+ {
+ $eventDispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch());
+ $listener1 = function ($event, $eventName, EventDispatcherInterface $dispatcher) use (&$listener1) {
+ $dispatcher->removeListener('foo', $listener1);
+ };
+ $eventDispatcher->addListener('foo', $listener1);
+ $eventDispatcher->addListener('foo', function () {});
+ $eventDispatcher->dispatch('foo');
+
+ $this->assertCount(1, $eventDispatcher->getListeners('foo'), 'expected listener1 to be removed');
+ }
+}
+
+class EventSubscriber implements EventSubscriberInterface
+{
+ public static function getSubscribedEvents()
+ {
+ return array('foo' => 'call');
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php
new file mode 100644
index 000000000..0fdd6372b
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php
@@ -0,0 +1,200 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher\Tests\DependencyInjection;
+
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
+
+class RegisterListenersPassTest extends \PHPUnit_Framework_TestCase
+{
+ /**
+ * Tests that event subscribers not implementing EventSubscriberInterface
+ * trigger an exception.
+ *
+ * @expectedException \InvalidArgumentException
+ */
+ public function testEventSubscriberWithoutInterface()
+ {
+ // one service, not implementing any interface
+ $services = array(
+ 'my_event_subscriber' => array(0 => array()),
+ );
+
+ $definition = $this->getMock('Symfony\Component\DependencyInjection\Definition');
+ $definition->expects($this->atLeastOnce())
+ ->method('isPublic')
+ ->will($this->returnValue(true));
+ $definition->expects($this->atLeastOnce())
+ ->method('getClass')
+ ->will($this->returnValue('stdClass'));
+
+ $builder = $this->getMock(
+ 'Symfony\Component\DependencyInjection\ContainerBuilder',
+ array('hasDefinition', 'findTaggedServiceIds', 'getDefinition')
+ );
+ $builder->expects($this->any())
+ ->method('hasDefinition')
+ ->will($this->returnValue(true));
+
+ // We don't test kernel.event_listener here
+ $builder->expects($this->atLeastOnce())
+ ->method('findTaggedServiceIds')
+ ->will($this->onConsecutiveCalls(array(), $services));
+
+ $builder->expects($this->atLeastOnce())
+ ->method('getDefinition')
+ ->will($this->returnValue($definition));
+
+ $registerListenersPass = new RegisterListenersPass();
+ $registerListenersPass->process($builder);
+ }
+
+ public function testValidEventSubscriber()
+ {
+ $services = array(
+ 'my_event_subscriber' => array(0 => array()),
+ );
+
+ $definition = $this->getMock('Symfony\Component\DependencyInjection\Definition');
+ $definition->expects($this->atLeastOnce())
+ ->method('isPublic')
+ ->will($this->returnValue(true));
+ $definition->expects($this->atLeastOnce())
+ ->method('getClass')
+ ->will($this->returnValue('Symfony\Component\EventDispatcher\Tests\DependencyInjection\SubscriberService'));
+
+ $builder = $this->getMock(
+ 'Symfony\Component\DependencyInjection\ContainerBuilder',
+ array('hasDefinition', 'findTaggedServiceIds', 'getDefinition', 'findDefinition')
+ );
+ $builder->expects($this->any())
+ ->method('hasDefinition')
+ ->will($this->returnValue(true));
+
+ // We don't test kernel.event_listener here
+ $builder->expects($this->atLeastOnce())
+ ->method('findTaggedServiceIds')
+ ->will($this->onConsecutiveCalls(array(), $services));
+
+ $builder->expects($this->atLeastOnce())
+ ->method('getDefinition')
+ ->will($this->returnValue($definition));
+
+ $builder->expects($this->atLeastOnce())
+ ->method('findDefinition')
+ ->will($this->returnValue($definition));
+
+ $registerListenersPass = new RegisterListenersPass();
+ $registerListenersPass->process($builder);
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage The service "foo" must be public as event listeners are lazy-loaded.
+ */
+ public function testPrivateEventListener()
+ {
+ $container = new ContainerBuilder();
+ $container->register('foo', 'stdClass')->setPublic(false)->addTag('kernel.event_listener', array());
+ $container->register('event_dispatcher', 'stdClass');
+
+ $registerListenersPass = new RegisterListenersPass();
+ $registerListenersPass->process($container);
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage The service "foo" must be public as event subscribers are lazy-loaded.
+ */
+ public function testPrivateEventSubscriber()
+ {
+ $container = new ContainerBuilder();
+ $container->register('foo', 'stdClass')->setPublic(false)->addTag('kernel.event_subscriber', array());
+ $container->register('event_dispatcher', 'stdClass');
+
+ $registerListenersPass = new RegisterListenersPass();
+ $registerListenersPass->process($container);
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage The service "foo" must not be abstract as event listeners are lazy-loaded.
+ */
+ public function testAbstractEventListener()
+ {
+ $container = new ContainerBuilder();
+ $container->register('foo', 'stdClass')->setAbstract(true)->addTag('kernel.event_listener', array());
+ $container->register('event_dispatcher', 'stdClass');
+
+ $registerListenersPass = new RegisterListenersPass();
+ $registerListenersPass->process($container);
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage The service "foo" must not be abstract as event subscribers are lazy-loaded.
+ */
+ public function testAbstractEventSubscriber()
+ {
+ $container = new ContainerBuilder();
+ $container->register('foo', 'stdClass')->setAbstract(true)->addTag('kernel.event_subscriber', array());
+ $container->register('event_dispatcher', 'stdClass');
+
+ $registerListenersPass = new RegisterListenersPass();
+ $registerListenersPass->process($container);
+ }
+
+ public function testEventSubscriberResolvableClassName()
+ {
+ $container = new ContainerBuilder();
+
+ $container->setParameter('subscriber.class', 'Symfony\Component\EventDispatcher\Tests\DependencyInjection\SubscriberService');
+ $container->register('foo', '%subscriber.class%')->addTag('kernel.event_subscriber', array());
+ $container->register('event_dispatcher', 'stdClass');
+
+ $registerListenersPass = new RegisterListenersPass();
+ $registerListenersPass->process($container);
+
+ $definition = $container->getDefinition('event_dispatcher');
+ $expected_calls = array(
+ array(
+ 'addSubscriberService',
+ array(
+ 'foo',
+ 'Symfony\Component\EventDispatcher\Tests\DependencyInjection\SubscriberService',
+ ),
+ ),
+ );
+ $this->assertSame($expected_calls, $definition->getMethodCalls());
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage You have requested a non-existent parameter "subscriber.class"
+ */
+ public function testEventSubscriberUnresolvableClassName()
+ {
+ $container = new ContainerBuilder();
+ $container->register('foo', '%subscriber.class%')->addTag('kernel.event_subscriber', array());
+ $container->register('event_dispatcher', 'stdClass');
+
+ $registerListenersPass = new RegisterListenersPass();
+ $registerListenersPass->process($container);
+ }
+}
+
+class SubscriberService implements \Symfony\Component\EventDispatcher\EventSubscriberInterface
+{
+ public static function getSubscribedEvents()
+ {
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Tests/EventDispatcherTest.php b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Tests/EventDispatcherTest.php
new file mode 100644
index 000000000..5faa5c8be
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Tests/EventDispatcherTest.php
@@ -0,0 +1,22 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher\Tests;
+
+use Symfony\Component\EventDispatcher\EventDispatcher;
+
+class EventDispatcherTest extends AbstractEventDispatcherTest
+{
+ protected function createEventDispatcher()
+ {
+ return new EventDispatcher();
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Tests/EventTest.php b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Tests/EventTest.php
new file mode 100644
index 000000000..1a6f4c4ae
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Tests/EventTest.php
@@ -0,0 +1,54 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher\Tests;
+
+use Symfony\Component\EventDispatcher\Event;
+
+/**
+ * Test class for Event.
+ */
+class EventTest extends \PHPUnit_Framework_TestCase
+{
+ /**
+ * @var \Symfony\Component\EventDispatcher\Event
+ */
+ protected $event;
+
+ /**
+ * Sets up the fixture, for example, opens a network connection.
+ * This method is called before a test is executed.
+ */
+ protected function setUp()
+ {
+ $this->event = new Event();
+ }
+
+ /**
+ * Tears down the fixture, for example, closes a network connection.
+ * This method is called after a test is executed.
+ */
+ protected function tearDown()
+ {
+ $this->event = null;
+ }
+
+ public function testIsPropagationStopped()
+ {
+ $this->assertFalse($this->event->isPropagationStopped());
+ }
+
+ public function testStopPropagationAndIsPropagationStopped()
+ {
+ $this->event->stopPropagation();
+ $this->assertTrue($this->event->isPropagationStopped());
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Tests/GenericEventTest.php b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Tests/GenericEventTest.php
new file mode 100644
index 000000000..aebd82dab
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Tests/GenericEventTest.php
@@ -0,0 +1,139 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher\Tests;
+
+use Symfony\Component\EventDispatcher\GenericEvent;
+
+/**
+ * Test class for Event.
+ */
+class GenericEventTest extends \PHPUnit_Framework_TestCase
+{
+ /**
+ * @var GenericEvent
+ */
+ private $event;
+
+ private $subject;
+
+ /**
+ * Prepares the environment before running a test.
+ */
+ protected function setUp()
+ {
+ parent::setUp();
+
+ $this->subject = new \stdClass();
+ $this->event = new GenericEvent($this->subject, array('name' => 'Event'));
+ }
+
+ /**
+ * Cleans up the environment after running a test.
+ */
+ protected function tearDown()
+ {
+ $this->subject = null;
+ $this->event = null;
+
+ parent::tearDown();
+ }
+
+ public function testConstruct()
+ {
+ $this->assertEquals($this->event, new GenericEvent($this->subject, array('name' => 'Event')));
+ }
+
+ /**
+ * Tests Event->getArgs().
+ */
+ public function testGetArguments()
+ {
+ // test getting all
+ $this->assertSame(array('name' => 'Event'), $this->event->getArguments());
+ }
+
+ public function testSetArguments()
+ {
+ $result = $this->event->setArguments(array('foo' => 'bar'));
+ $this->assertAttributeSame(array('foo' => 'bar'), 'arguments', $this->event);
+ $this->assertSame($this->event, $result);
+ }
+
+ public function testSetArgument()
+ {
+ $result = $this->event->setArgument('foo2', 'bar2');
+ $this->assertAttributeSame(array('name' => 'Event', 'foo2' => 'bar2'), 'arguments', $this->event);
+ $this->assertEquals($this->event, $result);
+ }
+
+ public function testGetArgument()
+ {
+ // test getting key
+ $this->assertEquals('Event', $this->event->getArgument('name'));
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testGetArgException()
+ {
+ $this->event->getArgument('nameNotExist');
+ }
+
+ public function testOffsetGet()
+ {
+ // test getting key
+ $this->assertEquals('Event', $this->event['name']);
+
+ // test getting invalid arg
+ $this->setExpectedException('InvalidArgumentException');
+ $this->assertFalse($this->event['nameNotExist']);
+ }
+
+ public function testOffsetSet()
+ {
+ $this->event['foo2'] = 'bar2';
+ $this->assertAttributeSame(array('name' => 'Event', 'foo2' => 'bar2'), 'arguments', $this->event);
+ }
+
+ public function testOffsetUnset()
+ {
+ unset($this->event['name']);
+ $this->assertAttributeSame(array(), 'arguments', $this->event);
+ }
+
+ public function testOffsetIsset()
+ {
+ $this->assertTrue(isset($this->event['name']));
+ $this->assertFalse(isset($this->event['nameNotExist']));
+ }
+
+ public function testHasArgument()
+ {
+ $this->assertTrue($this->event->hasArgument('name'));
+ $this->assertFalse($this->event->hasArgument('nameNotExist'));
+ }
+
+ public function testGetSubject()
+ {
+ $this->assertSame($this->subject, $this->event->getSubject());
+ }
+
+ public function testHasIterator()
+ {
+ $data = array();
+ foreach ($this->event as $key => $value) {
+ $data[$key] = $value;
+ }
+ $this->assertEquals(array('name' => 'Event'), $data);
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Tests/ImmutableEventDispatcherTest.php b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Tests/ImmutableEventDispatcherTest.php
new file mode 100644
index 000000000..80a7e43be
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/Tests/ImmutableEventDispatcherTest.php
@@ -0,0 +1,105 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\EventDispatcher\Tests;
+
+use Symfony\Component\EventDispatcher\Event;
+use Symfony\Component\EventDispatcher\ImmutableEventDispatcher;
+
+/**
+ * @author Bernhard Schussek
+ */
+class ImmutableEventDispatcherTest extends \PHPUnit_Framework_TestCase
+{
+ /**
+ * @var \PHPUnit_Framework_MockObject_MockObject
+ */
+ private $innerDispatcher;
+
+ /**
+ * @var ImmutableEventDispatcher
+ */
+ private $dispatcher;
+
+ protected function setUp()
+ {
+ $this->innerDispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
+ $this->dispatcher = new ImmutableEventDispatcher($this->innerDispatcher);
+ }
+
+ public function testDispatchDelegates()
+ {
+ $event = new Event();
+
+ $this->innerDispatcher->expects($this->once())
+ ->method('dispatch')
+ ->with('event', $event)
+ ->will($this->returnValue('result'));
+
+ $this->assertSame('result', $this->dispatcher->dispatch('event', $event));
+ }
+
+ public function testGetListenersDelegates()
+ {
+ $this->innerDispatcher->expects($this->once())
+ ->method('getListeners')
+ ->with('event')
+ ->will($this->returnValue('result'));
+
+ $this->assertSame('result', $this->dispatcher->getListeners('event'));
+ }
+
+ public function testHasListenersDelegates()
+ {
+ $this->innerDispatcher->expects($this->once())
+ ->method('hasListeners')
+ ->with('event')
+ ->will($this->returnValue('result'));
+
+ $this->assertSame('result', $this->dispatcher->hasListeners('event'));
+ }
+
+ /**
+ * @expectedException \BadMethodCallException
+ */
+ public function testAddListenerDisallowed()
+ {
+ $this->dispatcher->addListener('event', function () { return 'foo'; });
+ }
+
+ /**
+ * @expectedException \BadMethodCallException
+ */
+ public function testAddSubscriberDisallowed()
+ {
+ $subscriber = $this->getMock('Symfony\Component\EventDispatcher\EventSubscriberInterface');
+
+ $this->dispatcher->addSubscriber($subscriber);
+ }
+
+ /**
+ * @expectedException \BadMethodCallException
+ */
+ public function testRemoveListenerDisallowed()
+ {
+ $this->dispatcher->removeListener('event', function () { return 'foo'; });
+ }
+
+ /**
+ * @expectedException \BadMethodCallException
+ */
+ public function testRemoveSubscriberDisallowed()
+ {
+ $subscriber = $this->getMock('Symfony\Component\EventDispatcher\EventSubscriberInterface');
+
+ $this->dispatcher->removeSubscriber($subscriber);
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/composer.json b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/composer.json
new file mode 100644
index 000000000..12d91b926
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/composer.json
@@ -0,0 +1,44 @@
+{
+ "name": "symfony/event-dispatcher",
+ "type": "library",
+ "description": "Symfony EventDispatcher Component",
+ "keywords": [],
+ "homepage": "https://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "require": {
+ "php": ">=5.5.9"
+ },
+ "require-dev": {
+ "symfony/dependency-injection": "~2.8|~3.0",
+ "symfony/expression-language": "~2.8|~3.0",
+ "symfony/config": "~2.8|~3.0",
+ "symfony/stopwatch": "~2.8|~3.0",
+ "psr/log": "~1.0"
+ },
+ "suggest": {
+ "symfony/dependency-injection": "",
+ "symfony/http-kernel": ""
+ },
+ "autoload": {
+ "psr-4": { "Symfony\\Component\\EventDispatcher\\": "" },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "minimum-stability": "dev",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0-dev"
+ }
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/phpunit.xml.dist b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/phpunit.xml.dist
new file mode 100644
index 000000000..ae0586e0b
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/event-dispatcher/phpunit.xml.dist
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+ ./Tests/
+
+
+
+
+
+ ./
+
+ ./Resources
+ ./Tests
+ ./vendor
+
+
+
+
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/.gitignore b/modules/devshop/devshop_projects/drush/vendor/symfony/process/.gitignore
new file mode 100644
index 000000000..c49a5d8df
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/.gitignore
@@ -0,0 +1,3 @@
+vendor/
+composer.lock
+phpunit.xml
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/CHANGELOG.md b/modules/devshop/devshop_projects/drush/vendor/symfony/process/CHANGELOG.md
new file mode 100644
index 000000000..2f3c1beb7
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/CHANGELOG.md
@@ -0,0 +1,40 @@
+CHANGELOG
+=========
+
+2.5.0
+-----
+
+ * added support for PTY mode
+ * added the convenience method "mustRun"
+ * deprecation: Process::setStdin() is deprecated in favor of Process::setInput()
+ * deprecation: Process::getStdin() is deprecated in favor of Process::getInput()
+ * deprecation: Process::setInput() and ProcessBuilder::setInput() do not accept non-scalar types
+
+2.4.0
+-----
+
+ * added the ability to define an idle timeout
+
+2.3.0
+-----
+
+ * added ProcessUtils::escapeArgument() to fix the bug in escapeshellarg() function on Windows
+ * added Process::signal()
+ * added Process::getPid()
+ * added support for a TTY mode
+
+2.2.0
+-----
+
+ * added ProcessBuilder::setArguments() to reset the arguments on a builder
+ * added a way to retrieve the standard and error output incrementally
+ * added Process:restart()
+
+2.1.0
+-----
+
+ * added support for non-blocking processes (start(), wait(), isRunning(), stop())
+ * enhanced Windows compatibility
+ * added Process::getExitCodeText() that returns a string representation for
+ the exit code returned by the process
+ * added ProcessBuilder
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/Exception/ExceptionInterface.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Exception/ExceptionInterface.php
new file mode 100644
index 000000000..75c1c9e5d
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Exception/ExceptionInterface.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Exception;
+
+/**
+ * Marker Interface for the Process Component.
+ *
+ * @author Johannes M. Schmitt
+ */
+interface ExceptionInterface
+{
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/Exception/InvalidArgumentException.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Exception/InvalidArgumentException.php
new file mode 100644
index 000000000..926ee2118
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Exception/InvalidArgumentException.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Exception;
+
+/**
+ * InvalidArgumentException for the Process Component.
+ *
+ * @author Romain Neutron
+ */
+class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
+{
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/Exception/LogicException.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Exception/LogicException.php
new file mode 100644
index 000000000..be3d490dd
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Exception/LogicException.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Exception;
+
+/**
+ * LogicException for the Process Component.
+ *
+ * @author Romain Neutron
+ */
+class LogicException extends \LogicException implements ExceptionInterface
+{
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/Exception/ProcessFailedException.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Exception/ProcessFailedException.php
new file mode 100644
index 000000000..328acfde5
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Exception/ProcessFailedException.php
@@ -0,0 +1,54 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Exception;
+
+use Symfony\Component\Process\Process;
+
+/**
+ * Exception for failed processes.
+ *
+ * @author Johannes M. Schmitt
+ */
+class ProcessFailedException extends RuntimeException
+{
+ private $process;
+
+ public function __construct(Process $process)
+ {
+ if ($process->isSuccessful()) {
+ throw new InvalidArgumentException('Expected a failed process, but the given process was successful.');
+ }
+
+ $error = sprintf('The command "%s" failed.'."\n\nExit Code: %s(%s)\n\nWorking directory: %s",
+ $process->getCommandLine(),
+ $process->getExitCode(),
+ $process->getExitCodeText(),
+ $process->getWorkingDirectory()
+ );
+
+ if (!$process->isOutputDisabled()) {
+ $error .= sprintf("\n\nOutput:\n================\n%s\n\nError Output:\n================\n%s",
+ $process->getOutput(),
+ $process->getErrorOutput()
+ );
+ }
+
+ parent::__construct($error);
+
+ $this->process = $process;
+ }
+
+ public function getProcess()
+ {
+ return $this->process;
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/Exception/ProcessTimedOutException.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Exception/ProcessTimedOutException.php
new file mode 100644
index 000000000..d45114696
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Exception/ProcessTimedOutException.php
@@ -0,0 +1,69 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Exception;
+
+use Symfony\Component\Process\Process;
+
+/**
+ * Exception that is thrown when a process times out.
+ *
+ * @author Johannes M. Schmitt
+ */
+class ProcessTimedOutException extends RuntimeException
+{
+ const TYPE_GENERAL = 1;
+ const TYPE_IDLE = 2;
+
+ private $process;
+ private $timeoutType;
+
+ public function __construct(Process $process, $timeoutType)
+ {
+ $this->process = $process;
+ $this->timeoutType = $timeoutType;
+
+ parent::__construct(sprintf(
+ 'The process "%s" exceeded the timeout of %s seconds.',
+ $process->getCommandLine(),
+ $this->getExceededTimeout()
+ ));
+ }
+
+ public function getProcess()
+ {
+ return $this->process;
+ }
+
+ public function isGeneralTimeout()
+ {
+ return $this->timeoutType === self::TYPE_GENERAL;
+ }
+
+ public function isIdleTimeout()
+ {
+ return $this->timeoutType === self::TYPE_IDLE;
+ }
+
+ public function getExceededTimeout()
+ {
+ switch ($this->timeoutType) {
+ case self::TYPE_GENERAL:
+ return $this->process->getTimeout();
+
+ case self::TYPE_IDLE:
+ return $this->process->getIdleTimeout();
+
+ default:
+ throw new \LogicException(sprintf('Unknown timeout type "%d".', $this->timeoutType));
+ }
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/Exception/RuntimeException.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Exception/RuntimeException.php
new file mode 100644
index 000000000..adead2536
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Exception/RuntimeException.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Exception;
+
+/**
+ * RuntimeException for the Process Component.
+ *
+ * @author Johannes M. Schmitt
+ */
+class RuntimeException extends \RuntimeException implements ExceptionInterface
+{
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/ExecutableFinder.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/ExecutableFinder.php
new file mode 100644
index 000000000..fa11cb6e4
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/ExecutableFinder.php
@@ -0,0 +1,90 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process;
+
+/**
+ * Generic executable finder.
+ *
+ * @author Fabien Potencier
+ * @author Johannes M. Schmitt
+ */
+class ExecutableFinder
+{
+ private $suffixes = array('.exe', '.bat', '.cmd', '.com');
+
+ /**
+ * Replaces default suffixes of executable.
+ *
+ * @param array $suffixes
+ */
+ public function setSuffixes(array $suffixes)
+ {
+ $this->suffixes = $suffixes;
+ }
+
+ /**
+ * Adds new possible suffix to check for executable.
+ *
+ * @param string $suffix
+ */
+ public function addSuffix($suffix)
+ {
+ $this->suffixes[] = $suffix;
+ }
+
+ /**
+ * Finds an executable by name.
+ *
+ * @param string $name The executable name (without the extension)
+ * @param string $default The default to return if no executable is found
+ * @param array $extraDirs Additional dirs to check into
+ *
+ * @return string The executable path or default value
+ */
+ public function find($name, $default = null, array $extraDirs = array())
+ {
+ if (ini_get('open_basedir')) {
+ $searchPath = explode(PATH_SEPARATOR, ini_get('open_basedir'));
+ $dirs = array();
+ foreach ($searchPath as $path) {
+ // Silencing against https://bugs.php.net/69240
+ if (@is_dir($path)) {
+ $dirs[] = $path;
+ } else {
+ if (basename($path) == $name && is_executable($path)) {
+ return $path;
+ }
+ }
+ }
+ } else {
+ $dirs = array_merge(
+ explode(PATH_SEPARATOR, getenv('PATH') ?: getenv('Path')),
+ $extraDirs
+ );
+ }
+
+ $suffixes = array('');
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ $pathExt = getenv('PATHEXT');
+ $suffixes = $pathExt ? explode(PATH_SEPARATOR, $pathExt) : $this->suffixes;
+ }
+ foreach ($suffixes as $suffix) {
+ foreach ($dirs as $dir) {
+ if (is_file($file = $dir.DIRECTORY_SEPARATOR.$name.$suffix) && ('\\' === DIRECTORY_SEPARATOR || is_executable($file))) {
+ return $file;
+ }
+ }
+ }
+
+ return $default;
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/LICENSE b/modules/devshop/devshop_projects/drush/vendor/symfony/process/LICENSE
new file mode 100644
index 000000000..43028bc60
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2004-2015 Fabien Potencier
+
+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.
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/PhpExecutableFinder.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/PhpExecutableFinder.php
new file mode 100644
index 000000000..fb297825f
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/PhpExecutableFinder.php
@@ -0,0 +1,90 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process;
+
+/**
+ * An executable finder specifically designed for the PHP executable.
+ *
+ * @author Fabien Potencier
+ * @author Johannes M. Schmitt
+ */
+class PhpExecutableFinder
+{
+ private $executableFinder;
+
+ public function __construct()
+ {
+ $this->executableFinder = new ExecutableFinder();
+ }
+
+ /**
+ * Finds The PHP executable.
+ *
+ * @param bool $includeArgs Whether or not include command arguments
+ *
+ * @return string|false The PHP executable path or false if it cannot be found
+ */
+ public function find($includeArgs = true)
+ {
+ $args = $this->findArguments();
+ $args = $includeArgs && $args ? ' '.implode(' ', $args) : '';
+
+ // HHVM support
+ if (defined('HHVM_VERSION')) {
+ return (getenv('PHP_BINARY') ?: PHP_BINARY).$args;
+ }
+
+ // PHP_BINARY return the current sapi executable
+ if (defined('PHP_BINARY') && PHP_BINARY && in_array(PHP_SAPI, array('cli', 'cli-server', 'phpdbg')) && is_file(PHP_BINARY)) {
+ return PHP_BINARY.$args;
+ }
+
+ if ($php = getenv('PHP_PATH')) {
+ if (!is_executable($php)) {
+ return false;
+ }
+
+ return $php;
+ }
+
+ if ($php = getenv('PHP_PEAR_PHP_BIN')) {
+ if (is_executable($php)) {
+ return $php;
+ }
+ }
+
+ $dirs = array(PHP_BINDIR);
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ $dirs[] = 'C:\xampp\php\\';
+ }
+
+ return $this->executableFinder->find('php', false, $dirs);
+ }
+
+ /**
+ * Finds the PHP executable arguments.
+ *
+ * @return array The PHP executable arguments
+ */
+ public function findArguments()
+ {
+ $arguments = array();
+
+ if (defined('HHVM_VERSION')) {
+ $arguments[] = '--php';
+ } elseif ('phpdbg' === PHP_SAPI) {
+ $arguments[] = '-qrr';
+ }
+
+ return $arguments;
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/PhpProcess.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/PhpProcess.php
new file mode 100644
index 000000000..4a2a2625f
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/PhpProcess.php
@@ -0,0 +1,72 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process;
+
+use Symfony\Component\Process\Exception\RuntimeException;
+
+/**
+ * PhpProcess runs a PHP script in an independent process.
+ *
+ * $p = new PhpProcess('');
+ * $p->run();
+ * print $p->getOutput()."\n";
+ *
+ * @author Fabien Potencier
+ */
+class PhpProcess extends Process
+{
+ /**
+ * Constructor.
+ *
+ * @param string $script The PHP script to run (as a string)
+ * @param string|null $cwd The working directory or null to use the working dir of the current PHP process
+ * @param array|null $env The environment variables or null to use the same environment as the current PHP process
+ * @param int $timeout The timeout in seconds
+ * @param array $options An array of options for proc_open
+ */
+ public function __construct($script, $cwd = null, array $env = null, $timeout = 60, array $options = array())
+ {
+ $executableFinder = new PhpExecutableFinder();
+ if (false === $php = $executableFinder->find()) {
+ $php = null;
+ }
+ if ('phpdbg' === PHP_SAPI) {
+ $file = tempnam(sys_get_temp_dir(), 'dbg');
+ file_put_contents($file, $script);
+ register_shutdown_function('unlink', $file);
+ $php .= ' '.ProcessUtils::escapeArgument($file);
+ $script = null;
+ }
+
+ parent::__construct($php, $cwd, $env, $script, $timeout, $options);
+ }
+
+ /**
+ * Sets the path to the PHP binary to use.
+ */
+ public function setPhpBinary($php)
+ {
+ $this->setCommandLine($php);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function start($callback = null)
+ {
+ if (null === $this->getCommandLine()) {
+ throw new RuntimeException('Unable to find the PHP executable.');
+ }
+
+ parent::start($callback);
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/Pipes/AbstractPipes.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Pipes/AbstractPipes.php
new file mode 100644
index 000000000..1ca85739f
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Pipes/AbstractPipes.php
@@ -0,0 +1,74 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Pipes;
+
+/**
+ * @author Romain Neutron
+ *
+ * @internal
+ */
+abstract class AbstractPipes implements PipesInterface
+{
+ /** @var array */
+ public $pipes = array();
+
+ /** @var string */
+ protected $inputBuffer = '';
+ /** @var resource|null */
+ protected $input;
+
+ /** @var bool */
+ private $blocked = true;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function close()
+ {
+ foreach ($this->pipes as $pipe) {
+ fclose($pipe);
+ }
+ $this->pipes = array();
+ }
+
+ /**
+ * Returns true if a system call has been interrupted.
+ *
+ * @return bool
+ */
+ protected function hasSystemCallBeenInterrupted()
+ {
+ $lastError = error_get_last();
+
+ // stream_select returns false when the `select` system call is interrupted by an incoming signal
+ return isset($lastError['message']) && false !== stripos($lastError['message'], 'interrupted system call');
+ }
+
+ /**
+ * Unblocks streams.
+ */
+ protected function unblock()
+ {
+ if (!$this->blocked) {
+ return;
+ }
+
+ foreach ($this->pipes as $pipe) {
+ stream_set_blocking($pipe, 0);
+ }
+ if (null !== $this->input) {
+ stream_set_blocking($this->input, 0);
+ }
+
+ $this->blocked = false;
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/Pipes/PipesInterface.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Pipes/PipesInterface.php
new file mode 100644
index 000000000..09d3f61d6
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Pipes/PipesInterface.php
@@ -0,0 +1,60 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Pipes;
+
+/**
+ * PipesInterface manages descriptors and pipes for the use of proc_open.
+ *
+ * @author Romain Neutron
+ *
+ * @internal
+ */
+interface PipesInterface
+{
+ const CHUNK_SIZE = 16384;
+
+ /**
+ * Returns an array of descriptors for the use of proc_open.
+ *
+ * @return array
+ */
+ public function getDescriptors();
+
+ /**
+ * Returns an array of filenames indexed by their related stream in case these pipes use temporary files.
+ *
+ * @return string[]
+ */
+ public function getFiles();
+
+ /**
+ * Reads data in file handles and pipes.
+ *
+ * @param bool $blocking Whether to use blocking calls or not.
+ * @param bool $close Whether to close pipes if they've reached EOF.
+ *
+ * @return string[] An array of read data indexed by their fd.
+ */
+ public function readAndWrite($blocking, $close = false);
+
+ /**
+ * Returns if the current state has open file handles or pipes.
+ *
+ * @return bool
+ */
+ public function areOpen();
+
+ /**
+ * Closes file handles and pipes.
+ */
+ public function close();
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/Pipes/UnixPipes.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Pipes/UnixPipes.php
new file mode 100644
index 000000000..f8a0d1997
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Pipes/UnixPipes.php
@@ -0,0 +1,214 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Pipes;
+
+use Symfony\Component\Process\Process;
+
+/**
+ * UnixPipes implementation uses unix pipes as handles.
+ *
+ * @author Romain Neutron
+ *
+ * @internal
+ */
+class UnixPipes extends AbstractPipes
+{
+ /** @var bool */
+ private $ttyMode;
+ /** @var bool */
+ private $ptyMode;
+ /** @var bool */
+ private $disableOutput;
+
+ public function __construct($ttyMode, $ptyMode, $input, $disableOutput)
+ {
+ $this->ttyMode = (bool) $ttyMode;
+ $this->ptyMode = (bool) $ptyMode;
+ $this->disableOutput = (bool) $disableOutput;
+
+ if (is_resource($input)) {
+ $this->input = $input;
+ } else {
+ $this->inputBuffer = (string) $input;
+ }
+ }
+
+ public function __destruct()
+ {
+ $this->close();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getDescriptors()
+ {
+ if ($this->disableOutput) {
+ $nullstream = fopen('/dev/null', 'c');
+
+ return array(
+ array('pipe', 'r'),
+ $nullstream,
+ $nullstream,
+ );
+ }
+
+ if ($this->ttyMode) {
+ return array(
+ array('file', '/dev/tty', 'r'),
+ array('file', '/dev/tty', 'w'),
+ array('file', '/dev/tty', 'w'),
+ );
+ }
+
+ if ($this->ptyMode && Process::isPtySupported()) {
+ return array(
+ array('pty'),
+ array('pty'),
+ array('pty'),
+ );
+ }
+
+ return array(
+ array('pipe', 'r'),
+ array('pipe', 'w'), // stdout
+ array('pipe', 'w'), // stderr
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFiles()
+ {
+ return array();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function readAndWrite($blocking, $close = false)
+ {
+ // only stdin is left open, job has been done !
+ // we can now close it
+ if (1 === count($this->pipes) && array(0) === array_keys($this->pipes)) {
+ fclose($this->pipes[0]);
+ unset($this->pipes[0]);
+ }
+
+ if (empty($this->pipes)) {
+ return array();
+ }
+
+ $this->unblock();
+
+ $read = array();
+
+ if (null !== $this->input) {
+ // if input is a resource, let's add it to stream_select argument to
+ // fill a buffer
+ $r = array_merge($this->pipes, array('input' => $this->input));
+ } else {
+ $r = $this->pipes;
+ }
+ // discard read on stdin
+ unset($r[0]);
+
+ $w = isset($this->pipes[0]) ? array($this->pipes[0]) : null;
+ $e = null;
+
+ // let's have a look if something changed in streams
+ if (false === $n = @stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) {
+ // if a system call has been interrupted, forget about it, let's try again
+ // otherwise, an error occurred, let's reset pipes
+ if (!$this->hasSystemCallBeenInterrupted()) {
+ $this->pipes = array();
+ }
+
+ return $read;
+ }
+
+ // nothing has changed
+ if (0 === $n) {
+ return $read;
+ }
+
+ foreach ($r as $pipe) {
+ // prior PHP 5.4 the array passed to stream_select is modified and
+ // lose key association, we have to find back the key
+ $type = (false !== $found = array_search($pipe, $this->pipes)) ? $found : 'input';
+ $data = '';
+ while ('' !== $dataread = (string) fread($pipe, self::CHUNK_SIZE)) {
+ $data .= $dataread;
+ }
+
+ if ('' !== $data) {
+ if ($type === 'input') {
+ $this->inputBuffer .= $data;
+ } else {
+ $read[$type] = $data;
+ }
+ }
+
+ if (false === $data || (true === $close && feof($pipe) && '' === $data)) {
+ if ($type === 'input') {
+ // no more data to read on input resource
+ // use an empty buffer in the next reads
+ $this->input = null;
+ } else {
+ fclose($this->pipes[$type]);
+ unset($this->pipes[$type]);
+ }
+ }
+ }
+
+ if (null !== $w && 0 < count($w)) {
+ while (strlen($this->inputBuffer)) {
+ $written = fwrite($w[0], $this->inputBuffer, 2 << 18); // write 512k
+ if ($written > 0) {
+ $this->inputBuffer = (string) substr($this->inputBuffer, $written);
+ } else {
+ break;
+ }
+ }
+ }
+
+ // no input to read on resource, buffer is empty and stdin still open
+ if ('' === $this->inputBuffer && null === $this->input && isset($this->pipes[0])) {
+ fclose($this->pipes[0]);
+ unset($this->pipes[0]);
+ }
+
+ return $read;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function areOpen()
+ {
+ return (bool) $this->pipes;
+ }
+
+ /**
+ * Creates a new UnixPipes instance.
+ *
+ * @param Process $process
+ * @param string|resource $input
+ *
+ * @return UnixPipes
+ */
+ public static function create(Process $process, $input)
+ {
+ return new static($process->isTty(), $process->isPty(), $input, $process->isOutputDisabled());
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/Pipes/WindowsPipes.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Pipes/WindowsPipes.php
new file mode 100644
index 000000000..1472f8c6c
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Pipes/WindowsPipes.php
@@ -0,0 +1,253 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Pipes;
+
+use Symfony\Component\Process\Process;
+use Symfony\Component\Process\Exception\RuntimeException;
+
+/**
+ * WindowsPipes implementation uses temporary files as handles.
+ *
+ * @see https://bugs.php.net/bug.php?id=51800
+ * @see https://bugs.php.net/bug.php?id=65650
+ *
+ * @author Romain Neutron
+ *
+ * @internal
+ */
+class WindowsPipes extends AbstractPipes
+{
+ /** @var array */
+ private $files = array();
+ /** @var array */
+ private $fileHandles = array();
+ /** @var array */
+ private $readBytes = array(
+ Process::STDOUT => 0,
+ Process::STDERR => 0,
+ );
+ /** @var bool */
+ private $disableOutput;
+
+ public function __construct($disableOutput, $input)
+ {
+ $this->disableOutput = (bool) $disableOutput;
+
+ if (!$this->disableOutput) {
+ // Fix for PHP bug #51800: reading from STDOUT pipe hangs forever on Windows if the output is too big.
+ // Workaround for this problem is to use temporary files instead of pipes on Windows platform.
+ //
+ // @see https://bugs.php.net/bug.php?id=51800
+ $this->files = array(
+ Process::STDOUT => tempnam(sys_get_temp_dir(), 'out_sf_proc'),
+ Process::STDERR => tempnam(sys_get_temp_dir(), 'err_sf_proc'),
+ );
+ foreach ($this->files as $offset => $file) {
+ if (false === $file || false === $this->fileHandles[$offset] = fopen($file, 'rb')) {
+ throw new RuntimeException('A temporary file could not be opened to write the process output to, verify that your TEMP environment variable is writable');
+ }
+ }
+ }
+
+ if (is_resource($input)) {
+ $this->input = $input;
+ } else {
+ $this->inputBuffer = $input;
+ }
+ }
+
+ public function __destruct()
+ {
+ $this->close();
+ $this->removeFiles();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getDescriptors()
+ {
+ if ($this->disableOutput) {
+ $nullstream = fopen('NUL', 'c');
+
+ return array(
+ array('pipe', 'r'),
+ $nullstream,
+ $nullstream,
+ );
+ }
+
+ // We're not using pipe on Windows platform as it hangs (https://bugs.php.net/bug.php?id=51800)
+ // We're not using file handles as it can produce corrupted output https://bugs.php.net/bug.php?id=65650
+ // So we redirect output within the commandline and pass the nul device to the process
+ return array(
+ array('pipe', 'r'),
+ array('file', 'NUL', 'w'),
+ array('file', 'NUL', 'w'),
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFiles()
+ {
+ return $this->files;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function readAndWrite($blocking, $close = false)
+ {
+ $this->write($blocking, $close);
+
+ $read = array();
+ $fh = $this->fileHandles;
+ foreach ($fh as $type => $fileHandle) {
+ if (0 !== fseek($fileHandle, $this->readBytes[$type])) {
+ continue;
+ }
+ $data = '';
+ $dataread = null;
+ while (!feof($fileHandle)) {
+ if (false !== $dataread = fread($fileHandle, self::CHUNK_SIZE)) {
+ $data .= $dataread;
+ }
+ }
+ if (0 < $length = strlen($data)) {
+ $this->readBytes[$type] += $length;
+ $read[$type] = $data;
+ }
+
+ if (false === $dataread || (true === $close && feof($fileHandle) && '' === $data)) {
+ fclose($this->fileHandles[$type]);
+ unset($this->fileHandles[$type]);
+ }
+ }
+
+ return $read;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function areOpen()
+ {
+ return (bool) $this->pipes && (bool) $this->fileHandles;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function close()
+ {
+ parent::close();
+ foreach ($this->fileHandles as $handle) {
+ fclose($handle);
+ }
+ $this->fileHandles = array();
+ }
+
+ /**
+ * Creates a new WindowsPipes instance.
+ *
+ * @param Process $process The process
+ * @param $input
+ *
+ * @return WindowsPipes
+ */
+ public static function create(Process $process, $input)
+ {
+ return new static($process->isOutputDisabled(), $input);
+ }
+
+ /**
+ * Removes temporary files.
+ */
+ private function removeFiles()
+ {
+ foreach ($this->files as $filename) {
+ if (file_exists($filename)) {
+ @unlink($filename);
+ }
+ }
+ $this->files = array();
+ }
+
+ /**
+ * Writes input to stdin.
+ *
+ * @param bool $blocking
+ * @param bool $close
+ */
+ private function write($blocking, $close)
+ {
+ if (empty($this->pipes)) {
+ return;
+ }
+
+ $this->unblock();
+
+ $r = null !== $this->input ? array('input' => $this->input) : null;
+ $w = isset($this->pipes[0]) ? array($this->pipes[0]) : null;
+ $e = null;
+
+ // let's have a look if something changed in streams
+ if (false === $n = @stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) {
+ // if a system call has been interrupted, forget about it, let's try again
+ // otherwise, an error occurred, let's reset pipes
+ if (!$this->hasSystemCallBeenInterrupted()) {
+ $this->pipes = array();
+ }
+
+ return;
+ }
+
+ // nothing has changed
+ if (0 === $n) {
+ return;
+ }
+
+ if (null !== $w && 0 < count($r)) {
+ $data = '';
+ while ($dataread = fread($r['input'], self::CHUNK_SIZE)) {
+ $data .= $dataread;
+ }
+
+ $this->inputBuffer .= $data;
+
+ if (false === $data || (true === $close && feof($r['input']) && '' === $data)) {
+ // no more data to read on input resource
+ // use an empty buffer in the next reads
+ $this->input = null;
+ }
+ }
+
+ if (null !== $w && 0 < count($w)) {
+ while (strlen($this->inputBuffer)) {
+ $written = fwrite($w[0], $this->inputBuffer, 2 << 18);
+ if ($written > 0) {
+ $this->inputBuffer = (string) substr($this->inputBuffer, $written);
+ } else {
+ break;
+ }
+ }
+ }
+
+ // no input to read on resource, buffer is empty and stdin still open
+ if ('' === $this->inputBuffer && null === $this->input && isset($this->pipes[0])) {
+ fclose($this->pipes[0]);
+ unset($this->pipes[0]);
+ }
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/Process.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Process.php
new file mode 100644
index 000000000..c1e732170
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Process.php
@@ -0,0 +1,1515 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process;
+
+use Symfony\Component\Process\Exception\InvalidArgumentException;
+use Symfony\Component\Process\Exception\LogicException;
+use Symfony\Component\Process\Exception\ProcessFailedException;
+use Symfony\Component\Process\Exception\ProcessTimedOutException;
+use Symfony\Component\Process\Exception\RuntimeException;
+use Symfony\Component\Process\Pipes\PipesInterface;
+use Symfony\Component\Process\Pipes\UnixPipes;
+use Symfony\Component\Process\Pipes\WindowsPipes;
+
+/**
+ * Process is a thin wrapper around proc_* functions to easily
+ * start independent PHP processes.
+ *
+ * @author Fabien Potencier
+ * @author Romain Neutron
+ */
+class Process
+{
+ const ERR = 'err';
+ const OUT = 'out';
+
+ const STATUS_READY = 'ready';
+ const STATUS_STARTED = 'started';
+ const STATUS_TERMINATED = 'terminated';
+
+ const STDIN = 0;
+ const STDOUT = 1;
+ const STDERR = 2;
+
+ // Timeout Precision in seconds.
+ const TIMEOUT_PRECISION = 0.2;
+
+ private $callback;
+ private $commandline;
+ private $cwd;
+ private $env;
+ private $input;
+ private $starttime;
+ private $lastOutputTime;
+ private $timeout;
+ private $idleTimeout;
+ private $options;
+ private $exitcode;
+ private $fallbackExitcode;
+ private $processInformation;
+ private $outputDisabled = false;
+ private $stdout;
+ private $stderr;
+ private $enhanceWindowsCompatibility = true;
+ private $enhanceSigchildCompatibility;
+ private $process;
+ private $status = self::STATUS_READY;
+ private $incrementalOutputOffset = 0;
+ private $incrementalErrorOutputOffset = 0;
+ private $tty;
+ private $pty;
+
+ private $useFileHandles = false;
+ /** @var PipesInterface */
+ private $processPipes;
+
+ private $latestSignal;
+
+ private static $sigchild;
+
+ /**
+ * Exit codes translation table.
+ *
+ * User-defined errors must use exit codes in the 64-113 range.
+ *
+ * @var array
+ */
+ public static $exitCodes = array(
+ 0 => 'OK',
+ 1 => 'General error',
+ 2 => 'Misuse of shell builtins',
+
+ 126 => 'Invoked command cannot execute',
+ 127 => 'Command not found',
+ 128 => 'Invalid exit argument',
+
+ // signals
+ 129 => 'Hangup',
+ 130 => 'Interrupt',
+ 131 => 'Quit and dump core',
+ 132 => 'Illegal instruction',
+ 133 => 'Trace/breakpoint trap',
+ 134 => 'Process aborted',
+ 135 => 'Bus error: "access to undefined portion of memory object"',
+ 136 => 'Floating point exception: "erroneous arithmetic operation"',
+ 137 => 'Kill (terminate immediately)',
+ 138 => 'User-defined 1',
+ 139 => 'Segmentation violation',
+ 140 => 'User-defined 2',
+ 141 => 'Write to pipe with no one reading',
+ 142 => 'Signal raised by alarm',
+ 143 => 'Termination (request to terminate)',
+ // 144 - not defined
+ 145 => 'Child process terminated, stopped (or continued*)',
+ 146 => 'Continue if stopped',
+ 147 => 'Stop executing temporarily',
+ 148 => 'Terminal stop signal',
+ 149 => 'Background process attempting to read from tty ("in")',
+ 150 => 'Background process attempting to write to tty ("out")',
+ 151 => 'Urgent data available on socket',
+ 152 => 'CPU time limit exceeded',
+ 153 => 'File size limit exceeded',
+ 154 => 'Signal raised by timer counting virtual time: "virtual timer expired"',
+ 155 => 'Profiling timer expired',
+ // 156 - not defined
+ 157 => 'Pollable event',
+ // 158 - not defined
+ 159 => 'Bad syscall',
+ );
+
+ /**
+ * Constructor.
+ *
+ * @param string $commandline The command line to run
+ * @param string|null $cwd The working directory or null to use the working dir of the current PHP process
+ * @param array|null $env The environment variables or null to use the same environment as the current PHP process
+ * @param string|null $input The input
+ * @param int|float|null $timeout The timeout in seconds or null to disable
+ * @param array $options An array of options for proc_open
+ *
+ * @throws RuntimeException When proc_open is not installed
+ */
+ public function __construct($commandline, $cwd = null, array $env = null, $input = null, $timeout = 60, array $options = array())
+ {
+ if (!function_exists('proc_open')) {
+ throw new RuntimeException('The Process class relies on proc_open, which is not available on your PHP installation.');
+ }
+
+ $this->commandline = $commandline;
+ $this->cwd = $cwd;
+
+ // on Windows, if the cwd changed via chdir(), proc_open defaults to the dir where PHP was started
+ // on Gnu/Linux, PHP builds with --enable-maintainer-zts are also affected
+ // @see : https://bugs.php.net/bug.php?id=51800
+ // @see : https://bugs.php.net/bug.php?id=50524
+ if (null === $this->cwd && (defined('ZEND_THREAD_SAFE') || '\\' === DIRECTORY_SEPARATOR)) {
+ $this->cwd = getcwd();
+ }
+ if (null !== $env) {
+ $this->setEnv($env);
+ }
+
+ $this->input = $input;
+ $this->setTimeout($timeout);
+ $this->useFileHandles = '\\' === DIRECTORY_SEPARATOR;
+ $this->pty = false;
+ $this->enhanceWindowsCompatibility = true;
+ $this->enhanceSigchildCompatibility = '\\' !== DIRECTORY_SEPARATOR && $this->isSigchildEnabled();
+ $this->options = array_replace(array('suppress_errors' => true, 'binary_pipes' => true), $options);
+ }
+
+ public function __destruct()
+ {
+ // stop() will check if we have a process running.
+ $this->stop();
+ }
+
+ public function __clone()
+ {
+ $this->resetProcessData();
+ }
+
+ /**
+ * Runs the process.
+ *
+ * The callback receives the type of output (out or err) and
+ * some bytes from the output in real-time. It allows to have feedback
+ * from the independent process during execution.
+ *
+ * The STDOUT and STDERR are also available after the process is finished
+ * via the getOutput() and getErrorOutput() methods.
+ *
+ * @param callable|null $callback A PHP callback to run whenever there is some
+ * output available on STDOUT or STDERR
+ *
+ * @return int The exit status code
+ *
+ * @throws RuntimeException When process can't be launched
+ * @throws RuntimeException When process stopped after receiving signal
+ * @throws LogicException In case a callback is provided and output has been disabled
+ */
+ public function run($callback = null)
+ {
+ $this->start($callback);
+
+ return $this->wait();
+ }
+
+ /**
+ * Runs the process.
+ *
+ * This is identical to run() except that an exception is thrown if the process
+ * exits with a non-zero exit code.
+ *
+ * @param callable|null $callback
+ *
+ * @return self
+ *
+ * @throws RuntimeException if PHP was compiled with --enable-sigchild and the enhanced sigchild compatibility mode is not enabled
+ * @throws ProcessFailedException if the process didn't terminate successfully
+ */
+ public function mustRun($callback = null)
+ {
+ if ($this->isSigchildEnabled() && !$this->enhanceSigchildCompatibility) {
+ throw new RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.');
+ }
+
+ if (0 !== $this->run($callback)) {
+ throw new ProcessFailedException($this);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Starts the process and returns after writing the input to STDIN.
+ *
+ * This method blocks until all STDIN data is sent to the process then it
+ * returns while the process runs in the background.
+ *
+ * The termination of the process can be awaited with wait().
+ *
+ * The callback receives the type of output (out or err) and some bytes from
+ * the output in real-time while writing the standard input to the process.
+ * It allows to have feedback from the independent process during execution.
+ * If there is no callback passed, the wait() method can be called
+ * with true as a second parameter then the callback will get all data occurred
+ * in (and since) the start call.
+ *
+ * @param callable|null $callback A PHP callback to run whenever there is some
+ * output available on STDOUT or STDERR
+ *
+ * @throws RuntimeException When process can't be launched
+ * @throws RuntimeException When process is already running
+ * @throws LogicException In case a callback is provided and output has been disabled
+ */
+ public function start($callback = null)
+ {
+ if ($this->isRunning()) {
+ throw new RuntimeException('Process is already running');
+ }
+ if ($this->outputDisabled && null !== $callback) {
+ throw new LogicException('Output has been disabled, enable it to allow the use of a callback.');
+ }
+
+ $this->resetProcessData();
+ $this->starttime = $this->lastOutputTime = microtime(true);
+ $this->callback = $this->buildCallback($callback);
+ $descriptors = $this->getDescriptors();
+
+ $commandline = $this->commandline;
+
+ if ('\\' === DIRECTORY_SEPARATOR && $this->enhanceWindowsCompatibility) {
+ $commandline = 'cmd /V:ON /E:ON /D /C "('.$commandline.')';
+ foreach ($this->processPipes->getFiles() as $offset => $filename) {
+ $commandline .= ' '.$offset.'>'.ProcessUtils::escapeArgument($filename);
+ }
+ $commandline .= '"';
+
+ if (!isset($this->options['bypass_shell'])) {
+ $this->options['bypass_shell'] = true;
+ }
+ }
+
+ $ptsWorkaround = null;
+
+ if (!$this->useFileHandles && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
+ // Workaround for the bug, when PTS functionality is enabled.
+ // @see : https://bugs.php.net/69442
+ $ptsWorkaround = fopen(__FILE__, 'r');
+ }
+
+ $this->process = proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $this->env, $this->options);
+
+ if ($ptsWorkaround) {
+ fclose($ptsWorkaround);
+ }
+
+ if (!is_resource($this->process)) {
+ throw new RuntimeException('Unable to launch a new process.');
+ }
+ $this->status = self::STATUS_STARTED;
+
+ if ($this->tty) {
+ return;
+ }
+
+ $this->updateStatus(false);
+ $this->checkTimeout();
+ }
+
+ /**
+ * Restarts the process.
+ *
+ * Be warned that the process is cloned before being started.
+ *
+ * @param callable|null $callback A PHP callback to run whenever there is some
+ * output available on STDOUT or STDERR
+ *
+ * @return Process The new process
+ *
+ * @throws RuntimeException When process can't be launched
+ * @throws RuntimeException When process is already running
+ *
+ * @see start()
+ */
+ public function restart($callback = null)
+ {
+ if ($this->isRunning()) {
+ throw new RuntimeException('Process is already running');
+ }
+
+ $process = clone $this;
+ $process->start($callback);
+
+ return $process;
+ }
+
+ /**
+ * Waits for the process to terminate.
+ *
+ * The callback receives the type of output (out or err) and some bytes
+ * from the output in real-time while writing the standard input to the process.
+ * It allows to have feedback from the independent process during execution.
+ *
+ * @param callable|null $callback A valid PHP callback
+ *
+ * @return int The exitcode of the process
+ *
+ * @throws RuntimeException When process timed out
+ * @throws RuntimeException When process stopped after receiving signal
+ * @throws LogicException When process is not yet started
+ */
+ public function wait($callback = null)
+ {
+ $this->requireProcessIsStarted(__FUNCTION__);
+
+ $this->updateStatus(false);
+ if (null !== $callback) {
+ $this->callback = $this->buildCallback($callback);
+ }
+
+ do {
+ $this->checkTimeout();
+ $running = '\\' === DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen();
+ $close = '\\' !== DIRECTORY_SEPARATOR || !$running;
+ $this->readPipes(true, $close);
+ } while ($running);
+
+ while ($this->isRunning()) {
+ usleep(1000);
+ }
+
+ if ($this->processInformation['signaled'] && $this->processInformation['termsig'] !== $this->latestSignal) {
+ throw new RuntimeException(sprintf('The process has been signaled with signal "%s".', $this->processInformation['termsig']));
+ }
+
+ return $this->exitcode;
+ }
+
+ /**
+ * Returns the Pid (process identifier), if applicable.
+ *
+ * @return int|null The process id if running, null otherwise
+ *
+ * @throws RuntimeException In case --enable-sigchild is activated
+ */
+ public function getPid()
+ {
+ if ($this->isSigchildEnabled()) {
+ throw new RuntimeException('This PHP has been compiled with --enable-sigchild. The process identifier can not be retrieved.');
+ }
+
+ $this->updateStatus(false);
+
+ return $this->isRunning() ? $this->processInformation['pid'] : null;
+ }
+
+ /**
+ * Sends a POSIX signal to the process.
+ *
+ * @param int $signal A valid POSIX signal (see http://www.php.net/manual/en/pcntl.constants.php)
+ *
+ * @return Process
+ *
+ * @throws LogicException In case the process is not running
+ * @throws RuntimeException In case --enable-sigchild is activated
+ * @throws RuntimeException In case of failure
+ */
+ public function signal($signal)
+ {
+ $this->doSignal($signal, true);
+
+ return $this;
+ }
+
+ /**
+ * Disables fetching output and error output from the underlying process.
+ *
+ * @return Process
+ *
+ * @throws RuntimeException In case the process is already running
+ * @throws LogicException if an idle timeout is set
+ */
+ public function disableOutput()
+ {
+ if ($this->isRunning()) {
+ throw new RuntimeException('Disabling output while the process is running is not possible.');
+ }
+ if (null !== $this->idleTimeout) {
+ throw new LogicException('Output can not be disabled while an idle timeout is set.');
+ }
+
+ $this->outputDisabled = true;
+
+ return $this;
+ }
+
+ /**
+ * Enables fetching output and error output from the underlying process.
+ *
+ * @return Process
+ *
+ * @throws RuntimeException In case the process is already running
+ */
+ public function enableOutput()
+ {
+ if ($this->isRunning()) {
+ throw new RuntimeException('Enabling output while the process is running is not possible.');
+ }
+
+ $this->outputDisabled = false;
+
+ return $this;
+ }
+
+ /**
+ * Returns true in case the output is disabled, false otherwise.
+ *
+ * @return bool
+ */
+ public function isOutputDisabled()
+ {
+ return $this->outputDisabled;
+ }
+
+ /**
+ * Returns the current output of the process (STDOUT).
+ *
+ * @return string The process output
+ *
+ * @throws LogicException in case the output has been disabled
+ * @throws LogicException In case the process is not started
+ */
+ public function getOutput()
+ {
+ if ($this->outputDisabled) {
+ throw new LogicException('Output has been disabled.');
+ }
+
+ $this->requireProcessIsStarted(__FUNCTION__);
+
+ $this->readPipes(false, '\\' === DIRECTORY_SEPARATOR ? !$this->processInformation['running'] : true);
+
+ return $this->stdout;
+ }
+
+ /**
+ * Returns the output incrementally.
+ *
+ * In comparison with the getOutput method which always return the whole
+ * output, this one returns the new output since the last call.
+ *
+ * @throws LogicException in case the output has been disabled
+ * @throws LogicException In case the process is not started
+ *
+ * @return string The process output since the last call
+ */
+ public function getIncrementalOutput()
+ {
+ $this->requireProcessIsStarted(__FUNCTION__);
+
+ $data = $this->getOutput();
+
+ $latest = substr($data, $this->incrementalOutputOffset);
+
+ if (false === $latest) {
+ return '';
+ }
+
+ $this->incrementalOutputOffset = strlen($data);
+
+ return $latest;
+ }
+
+ /**
+ * Clears the process output.
+ *
+ * @return Process
+ */
+ public function clearOutput()
+ {
+ $this->stdout = '';
+ $this->incrementalOutputOffset = 0;
+
+ return $this;
+ }
+
+ /**
+ * Returns the current error output of the process (STDERR).
+ *
+ * @return string The process error output
+ *
+ * @throws LogicException in case the output has been disabled
+ * @throws LogicException In case the process is not started
+ */
+ public function getErrorOutput()
+ {
+ if ($this->outputDisabled) {
+ throw new LogicException('Output has been disabled.');
+ }
+
+ $this->requireProcessIsStarted(__FUNCTION__);
+
+ $this->readPipes(false, '\\' === DIRECTORY_SEPARATOR ? !$this->processInformation['running'] : true);
+
+ return $this->stderr;
+ }
+
+ /**
+ * Returns the errorOutput incrementally.
+ *
+ * In comparison with the getErrorOutput method which always return the
+ * whole error output, this one returns the new error output since the last
+ * call.
+ *
+ * @throws LogicException in case the output has been disabled
+ * @throws LogicException In case the process is not started
+ *
+ * @return string The process error output since the last call
+ */
+ public function getIncrementalErrorOutput()
+ {
+ $this->requireProcessIsStarted(__FUNCTION__);
+
+ $data = $this->getErrorOutput();
+
+ $latest = substr($data, $this->incrementalErrorOutputOffset);
+
+ if (false === $latest) {
+ return '';
+ }
+
+ $this->incrementalErrorOutputOffset = strlen($data);
+
+ return $latest;
+ }
+
+ /**
+ * Clears the process output.
+ *
+ * @return Process
+ */
+ public function clearErrorOutput()
+ {
+ $this->stderr = '';
+ $this->incrementalErrorOutputOffset = 0;
+
+ return $this;
+ }
+
+ /**
+ * Returns the exit code returned by the process.
+ *
+ * @return null|int The exit status code, null if the Process is not terminated
+ *
+ * @throws RuntimeException In case --enable-sigchild is activated and the sigchild compatibility mode is disabled
+ */
+ public function getExitCode()
+ {
+ if ($this->isSigchildEnabled() && !$this->enhanceSigchildCompatibility) {
+ throw new RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.');
+ }
+
+ $this->updateStatus(false);
+
+ return $this->exitcode;
+ }
+
+ /**
+ * Returns a string representation for the exit code returned by the process.
+ *
+ * This method relies on the Unix exit code status standardization
+ * and might not be relevant for other operating systems.
+ *
+ * @return null|string A string representation for the exit status code, null if the Process is not terminated.
+ *
+ * @throws RuntimeException In case --enable-sigchild is activated and the sigchild compatibility mode is disabled
+ *
+ * @see http://tldp.org/LDP/abs/html/exitcodes.html
+ * @see http://en.wikipedia.org/wiki/Unix_signal
+ */
+ public function getExitCodeText()
+ {
+ if (null === $exitcode = $this->getExitCode()) {
+ return;
+ }
+
+ return isset(self::$exitCodes[$exitcode]) ? self::$exitCodes[$exitcode] : 'Unknown error';
+ }
+
+ /**
+ * Checks if the process ended successfully.
+ *
+ * @return bool true if the process ended successfully, false otherwise
+ */
+ public function isSuccessful()
+ {
+ return 0 === $this->getExitCode();
+ }
+
+ /**
+ * Returns true if the child process has been terminated by an uncaught signal.
+ *
+ * It always returns false on Windows.
+ *
+ * @return bool
+ *
+ * @throws RuntimeException In case --enable-sigchild is activated
+ * @throws LogicException In case the process is not terminated
+ */
+ public function hasBeenSignaled()
+ {
+ $this->requireProcessIsTerminated(__FUNCTION__);
+
+ if ($this->isSigchildEnabled()) {
+ throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.');
+ }
+
+ $this->updateStatus(false);
+
+ return $this->processInformation['signaled'];
+ }
+
+ /**
+ * Returns the number of the signal that caused the child process to terminate its execution.
+ *
+ * It is only meaningful if hasBeenSignaled() returns true.
+ *
+ * @return int
+ *
+ * @throws RuntimeException In case --enable-sigchild is activated
+ * @throws LogicException In case the process is not terminated
+ */
+ public function getTermSignal()
+ {
+ $this->requireProcessIsTerminated(__FUNCTION__);
+
+ if ($this->isSigchildEnabled()) {
+ throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.');
+ }
+
+ $this->updateStatus(false);
+
+ return $this->processInformation['termsig'];
+ }
+
+ /**
+ * Returns true if the child process has been stopped by a signal.
+ *
+ * It always returns false on Windows.
+ *
+ * @return bool
+ *
+ * @throws LogicException In case the process is not terminated
+ */
+ public function hasBeenStopped()
+ {
+ $this->requireProcessIsTerminated(__FUNCTION__);
+
+ $this->updateStatus(false);
+
+ return $this->processInformation['stopped'];
+ }
+
+ /**
+ * Returns the number of the signal that caused the child process to stop its execution.
+ *
+ * It is only meaningful if hasBeenStopped() returns true.
+ *
+ * @return int
+ *
+ * @throws LogicException In case the process is not terminated
+ */
+ public function getStopSignal()
+ {
+ $this->requireProcessIsTerminated(__FUNCTION__);
+
+ $this->updateStatus(false);
+
+ return $this->processInformation['stopsig'];
+ }
+
+ /**
+ * Checks if the process is currently running.
+ *
+ * @return bool true if the process is currently running, false otherwise
+ */
+ public function isRunning()
+ {
+ if (self::STATUS_STARTED !== $this->status) {
+ return false;
+ }
+
+ $this->updateStatus(false);
+
+ return $this->processInformation['running'];
+ }
+
+ /**
+ * Checks if the process has been started with no regard to the current state.
+ *
+ * @return bool true if status is ready, false otherwise
+ */
+ public function isStarted()
+ {
+ return $this->status != self::STATUS_READY;
+ }
+
+ /**
+ * Checks if the process is terminated.
+ *
+ * @return bool true if process is terminated, false otherwise
+ */
+ public function isTerminated()
+ {
+ $this->updateStatus(false);
+
+ return $this->status == self::STATUS_TERMINATED;
+ }
+
+ /**
+ * Gets the process status.
+ *
+ * The status is one of: ready, started, terminated.
+ *
+ * @return string The current process status
+ */
+ public function getStatus()
+ {
+ $this->updateStatus(false);
+
+ return $this->status;
+ }
+
+ /**
+ * Stops the process.
+ *
+ * @param int|float $timeout The timeout in seconds
+ * @param int $signal A POSIX signal to send in case the process has not stop at timeout, default is SIGKILL (9)
+ *
+ * @return int The exit-code of the process
+ */
+ public function stop($timeout = 10, $signal = null)
+ {
+ $timeoutMicro = microtime(true) + $timeout;
+ if ($this->isRunning()) {
+ // given `SIGTERM` may not be defined and that `proc_terminate` uses the constant value and not the constant itself, we use the same here
+ $this->doSignal(15, false);
+ do {
+ usleep(1000);
+ } while ($this->isRunning() && microtime(true) < $timeoutMicro);
+
+ if ($this->isRunning() && !$this->isSigchildEnabled()) {
+ // Avoid exception here: process is supposed to be running, but it might have stopped just
+ // after this line. In any case, let's silently discard the error, we cannot do anything.
+ $this->doSignal($signal ?: 9, false);
+ }
+ }
+
+ $this->updateStatus(false);
+ if ($this->processInformation['running']) {
+ $this->close();
+ }
+
+ return $this->exitcode;
+ }
+
+ /**
+ * Adds a line to the STDOUT stream.
+ *
+ * @param string $line The line to append
+ */
+ public function addOutput($line)
+ {
+ $this->lastOutputTime = microtime(true);
+ $this->stdout .= $line;
+ }
+
+ /**
+ * Adds a line to the STDERR stream.
+ *
+ * @param string $line The line to append
+ */
+ public function addErrorOutput($line)
+ {
+ $this->lastOutputTime = microtime(true);
+ $this->stderr .= $line;
+ }
+
+ /**
+ * Gets the command line to be executed.
+ *
+ * @return string The command to execute
+ */
+ public function getCommandLine()
+ {
+ return $this->commandline;
+ }
+
+ /**
+ * Sets the command line to be executed.
+ *
+ * @param string $commandline The command to execute
+ *
+ * @return self The current Process instance
+ */
+ public function setCommandLine($commandline)
+ {
+ $this->commandline = $commandline;
+
+ return $this;
+ }
+
+ /**
+ * Gets the process timeout (max. runtime).
+ *
+ * @return float|null The timeout in seconds or null if it's disabled
+ */
+ public function getTimeout()
+ {
+ return $this->timeout;
+ }
+
+ /**
+ * Gets the process idle timeout (max. time since last output).
+ *
+ * @return float|null The timeout in seconds or null if it's disabled
+ */
+ public function getIdleTimeout()
+ {
+ return $this->idleTimeout;
+ }
+
+ /**
+ * Sets the process timeout (max. runtime).
+ *
+ * To disable the timeout, set this value to null.
+ *
+ * @param int|float|null $timeout The timeout in seconds
+ *
+ * @return self The current Process instance
+ *
+ * @throws InvalidArgumentException if the timeout is negative
+ */
+ public function setTimeout($timeout)
+ {
+ $this->timeout = $this->validateTimeout($timeout);
+
+ return $this;
+ }
+
+ /**
+ * Sets the process idle timeout (max. time since last output).
+ *
+ * To disable the timeout, set this value to null.
+ *
+ * @param int|float|null $timeout The timeout in seconds
+ *
+ * @return self The current Process instance.
+ *
+ * @throws LogicException if the output is disabled
+ * @throws InvalidArgumentException if the timeout is negative
+ */
+ public function setIdleTimeout($timeout)
+ {
+ if (null !== $timeout && $this->outputDisabled) {
+ throw new LogicException('Idle timeout can not be set while the output is disabled.');
+ }
+
+ $this->idleTimeout = $this->validateTimeout($timeout);
+
+ return $this;
+ }
+
+ /**
+ * Enables or disables the TTY mode.
+ *
+ * @param bool $tty True to enabled and false to disable
+ *
+ * @return self The current Process instance
+ *
+ * @throws RuntimeException In case the TTY mode is not supported
+ */
+ public function setTty($tty)
+ {
+ if ('\\' === DIRECTORY_SEPARATOR && $tty) {
+ throw new RuntimeException('TTY mode is not supported on Windows platform.');
+ }
+ if ($tty && (!file_exists('/dev/tty') || !is_readable('/dev/tty'))) {
+ throw new RuntimeException('TTY mode requires /dev/tty to be readable.');
+ }
+
+ $this->tty = (bool) $tty;
+
+ return $this;
+ }
+
+ /**
+ * Checks if the TTY mode is enabled.
+ *
+ * @return bool true if the TTY mode is enabled, false otherwise
+ */
+ public function isTty()
+ {
+ return $this->tty;
+ }
+
+ /**
+ * Sets PTY mode.
+ *
+ * @param bool $bool
+ *
+ * @return self
+ */
+ public function setPty($bool)
+ {
+ $this->pty = (bool) $bool;
+
+ return $this;
+ }
+
+ /**
+ * Returns PTY state.
+ *
+ * @return bool
+ */
+ public function isPty()
+ {
+ return $this->pty;
+ }
+
+ /**
+ * Gets the working directory.
+ *
+ * @return string|null The current working directory or null on failure
+ */
+ public function getWorkingDirectory()
+ {
+ if (null === $this->cwd) {
+ // getcwd() will return false if any one of the parent directories does not have
+ // the readable or search mode set, even if the current directory does
+ return getcwd() ?: null;
+ }
+
+ return $this->cwd;
+ }
+
+ /**
+ * Sets the current working directory.
+ *
+ * @param string $cwd The new working directory
+ *
+ * @return self The current Process instance
+ */
+ public function setWorkingDirectory($cwd)
+ {
+ $this->cwd = $cwd;
+
+ return $this;
+ }
+
+ /**
+ * Gets the environment variables.
+ *
+ * @return array The current environment variables
+ */
+ public function getEnv()
+ {
+ return $this->env;
+ }
+
+ /**
+ * Sets the environment variables.
+ *
+ * An environment variable value should be a string.
+ * If it is an array, the variable is ignored.
+ *
+ * That happens in PHP when 'argv' is registered into
+ * the $_ENV array for instance.
+ *
+ * @param array $env The new environment variables
+ *
+ * @return self The current Process instance
+ */
+ public function setEnv(array $env)
+ {
+ // Process can not handle env values that are arrays
+ $env = array_filter($env, function ($value) {
+ return !is_array($value);
+ });
+
+ $this->env = array();
+ foreach ($env as $key => $value) {
+ $this->env[$key] = (string) $value;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Gets the contents of STDIN.
+ *
+ * @return string|null The current contents
+ *
+ * @deprecated since version 2.5, to be removed in 3.0.
+ * Use setInput() instead.
+ * This method is deprecated in favor of getInput.
+ */
+ public function getStdin()
+ {
+ @trigger_error('The '.__METHOD__.' method is deprecated since version 2.5 and will be removed in 3.0. Use the getInput() method instead.', E_USER_DEPRECATED);
+
+ return $this->getInput();
+ }
+
+ /**
+ * Gets the Process input.
+ *
+ * @return null|string The Process input
+ */
+ public function getInput()
+ {
+ return $this->input;
+ }
+
+ /**
+ * Sets the contents of STDIN.
+ *
+ * @param string|null $stdin The new contents
+ *
+ * @return self The current Process instance
+ *
+ * @deprecated since version 2.5, to be removed in 3.0.
+ * Use setInput() instead.
+ *
+ * @throws LogicException In case the process is running
+ * @throws InvalidArgumentException In case the argument is invalid
+ */
+ public function setStdin($stdin)
+ {
+ @trigger_error('The '.__METHOD__.' method is deprecated since version 2.5 and will be removed in 3.0. Use the setInput() method instead.', E_USER_DEPRECATED);
+
+ return $this->setInput($stdin);
+ }
+
+ /**
+ * Sets the input.
+ *
+ * This content will be passed to the underlying process standard input.
+ *
+ * @param mixed $input The content
+ *
+ * @return self The current Process instance
+ *
+ * @throws LogicException In case the process is running
+ *
+ * Passing an object as an input is deprecated since version 2.5 and will be removed in 3.0.
+ */
+ public function setInput($input)
+ {
+ if ($this->isRunning()) {
+ throw new LogicException('Input can not be set while the process is running.');
+ }
+
+ $this->input = ProcessUtils::validateInput(sprintf('%s::%s', __CLASS__, __FUNCTION__), $input);
+
+ return $this;
+ }
+
+ /**
+ * Gets the options for proc_open.
+ *
+ * @return array The current options
+ */
+ public function getOptions()
+ {
+ return $this->options;
+ }
+
+ /**
+ * Sets the options for proc_open.
+ *
+ * @param array $options The new options
+ *
+ * @return self The current Process instance
+ */
+ public function setOptions(array $options)
+ {
+ $this->options = $options;
+
+ return $this;
+ }
+
+ /**
+ * Gets whether or not Windows compatibility is enabled.
+ *
+ * This is true by default.
+ *
+ * @return bool
+ */
+ public function getEnhanceWindowsCompatibility()
+ {
+ return $this->enhanceWindowsCompatibility;
+ }
+
+ /**
+ * Sets whether or not Windows compatibility is enabled.
+ *
+ * @param bool $enhance
+ *
+ * @return self The current Process instance
+ */
+ public function setEnhanceWindowsCompatibility($enhance)
+ {
+ $this->enhanceWindowsCompatibility = (bool) $enhance;
+
+ return $this;
+ }
+
+ /**
+ * Returns whether sigchild compatibility mode is activated or not.
+ *
+ * @return bool
+ */
+ public function getEnhanceSigchildCompatibility()
+ {
+ return $this->enhanceSigchildCompatibility;
+ }
+
+ /**
+ * Activates sigchild compatibility mode.
+ *
+ * Sigchild compatibility mode is required to get the exit code and
+ * determine the success of a process when PHP has been compiled with
+ * the --enable-sigchild option
+ *
+ * @param bool $enhance
+ *
+ * @return self The current Process instance
+ */
+ public function setEnhanceSigchildCompatibility($enhance)
+ {
+ $this->enhanceSigchildCompatibility = (bool) $enhance;
+
+ return $this;
+ }
+
+ /**
+ * Performs a check between the timeout definition and the time the process started.
+ *
+ * In case you run a background process (with the start method), you should
+ * trigger this method regularly to ensure the process timeout
+ *
+ * @throws ProcessTimedOutException In case the timeout was reached
+ */
+ public function checkTimeout()
+ {
+ if ($this->status !== self::STATUS_STARTED) {
+ return;
+ }
+
+ if (null !== $this->timeout && $this->timeout < microtime(true) - $this->starttime) {
+ $this->stop(0);
+
+ throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_GENERAL);
+ }
+
+ if (null !== $this->idleTimeout && $this->idleTimeout < microtime(true) - $this->lastOutputTime) {
+ $this->stop(0);
+
+ throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_IDLE);
+ }
+ }
+
+ /**
+ * Returns whether PTY is supported on the current operating system.
+ *
+ * @return bool
+ */
+ public static function isPtySupported()
+ {
+ static $result;
+
+ if (null !== $result) {
+ return $result;
+ }
+
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ return $result = false;
+ }
+
+ $proc = @proc_open('echo 1', array(array('pty'), array('pty'), array('pty')), $pipes);
+ if (is_resource($proc)) {
+ proc_close($proc);
+
+ return $result = true;
+ }
+
+ return $result = false;
+ }
+
+ /**
+ * Creates the descriptors needed by the proc_open.
+ *
+ * @return array
+ */
+ private function getDescriptors()
+ {
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ $this->processPipes = WindowsPipes::create($this, $this->input);
+ } else {
+ $this->processPipes = UnixPipes::create($this, $this->input);
+ }
+ $descriptors = $this->processPipes->getDescriptors($this->outputDisabled);
+
+ if (!$this->useFileHandles && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
+ // last exit code is output on the fourth pipe and caught to work around --enable-sigchild
+ $descriptors = array_merge($descriptors, array(array('pipe', 'w')));
+
+ $this->commandline = '('.$this->commandline.') 3>/dev/null; code=$?; echo $code >&3; exit $code';
+ }
+
+ return $descriptors;
+ }
+
+ /**
+ * Builds up the callback used by wait().
+ *
+ * The callbacks adds all occurred output to the specific buffer and calls
+ * the user callback (if present) with the received output.
+ *
+ * @param callable|null $callback The user defined PHP callback
+ *
+ * @return \Closure A PHP closure
+ */
+ protected function buildCallback($callback)
+ {
+ $that = $this;
+ $out = self::OUT;
+ $callback = function ($type, $data) use ($that, $callback, $out) {
+ if ($out == $type) {
+ $that->addOutput($data);
+ } else {
+ $that->addErrorOutput($data);
+ }
+
+ if (null !== $callback) {
+ call_user_func($callback, $type, $data);
+ }
+ };
+
+ return $callback;
+ }
+
+ /**
+ * Updates the status of the process, reads pipes.
+ *
+ * @param bool $blocking Whether to use a blocking read call.
+ */
+ protected function updateStatus($blocking)
+ {
+ if (self::STATUS_STARTED !== $this->status) {
+ return;
+ }
+
+ $this->processInformation = proc_get_status($this->process);
+ $this->captureExitCode();
+
+ $this->readPipes($blocking, '\\' === DIRECTORY_SEPARATOR ? !$this->processInformation['running'] : true);
+
+ if (!$this->processInformation['running']) {
+ $this->close();
+ }
+ }
+
+ /**
+ * Returns whether PHP has been compiled with the '--enable-sigchild' option or not.
+ *
+ * @return bool
+ */
+ protected function isSigchildEnabled()
+ {
+ if (null !== self::$sigchild) {
+ return self::$sigchild;
+ }
+
+ if (!function_exists('phpinfo')) {
+ return self::$sigchild = false;
+ }
+
+ ob_start();
+ phpinfo(INFO_GENERAL);
+
+ return self::$sigchild = false !== strpos(ob_get_clean(), '--enable-sigchild');
+ }
+
+ /**
+ * Validates and returns the filtered timeout.
+ *
+ * @param int|float|null $timeout
+ *
+ * @return float|null
+ *
+ * @throws InvalidArgumentException if the given timeout is a negative number
+ */
+ private function validateTimeout($timeout)
+ {
+ $timeout = (float) $timeout;
+
+ if (0.0 === $timeout) {
+ $timeout = null;
+ } elseif ($timeout < 0) {
+ throw new InvalidArgumentException('The timeout value must be a valid positive integer or float number.');
+ }
+
+ return $timeout;
+ }
+
+ /**
+ * Reads pipes, executes callback.
+ *
+ * @param bool $blocking Whether to use blocking calls or not.
+ * @param bool $close Whether to close file handles or not.
+ */
+ private function readPipes($blocking, $close)
+ {
+ $result = $this->processPipes->readAndWrite($blocking, $close);
+
+ $callback = $this->callback;
+ foreach ($result as $type => $data) {
+ if (3 == $type) {
+ $this->fallbackExitcode = (int) $data;
+ } else {
+ $callback($type === self::STDOUT ? self::OUT : self::ERR, $data);
+ }
+ }
+ }
+
+ /**
+ * Captures the exitcode if mentioned in the process information.
+ */
+ private function captureExitCode()
+ {
+ if (isset($this->processInformation['exitcode']) && -1 != $this->processInformation['exitcode']) {
+ $this->exitcode = $this->processInformation['exitcode'];
+ }
+ }
+
+ /**
+ * Closes process resource, closes file handles, sets the exitcode.
+ *
+ * @return int The exitcode
+ */
+ private function close()
+ {
+ $this->processPipes->close();
+ if (is_resource($this->process)) {
+ $exitcode = proc_close($this->process);
+ } else {
+ $exitcode = -1;
+ }
+
+ $this->exitcode = -1 !== $exitcode ? $exitcode : (null !== $this->exitcode ? $this->exitcode : -1);
+ $this->status = self::STATUS_TERMINATED;
+
+ if (-1 === $this->exitcode && null !== $this->fallbackExitcode) {
+ $this->exitcode = $this->fallbackExitcode;
+ } elseif (-1 === $this->exitcode && $this->processInformation['signaled'] && 0 < $this->processInformation['termsig']) {
+ // if process has been signaled, no exitcode but a valid termsig, apply Unix convention
+ $this->exitcode = 128 + $this->processInformation['termsig'];
+ }
+
+ return $this->exitcode;
+ }
+
+ /**
+ * Resets data related to the latest run of the process.
+ */
+ private function resetProcessData()
+ {
+ $this->starttime = null;
+ $this->callback = null;
+ $this->exitcode = null;
+ $this->fallbackExitcode = null;
+ $this->processInformation = null;
+ $this->stdout = null;
+ $this->stderr = null;
+ $this->process = null;
+ $this->latestSignal = null;
+ $this->status = self::STATUS_READY;
+ $this->incrementalOutputOffset = 0;
+ $this->incrementalErrorOutputOffset = 0;
+ }
+
+ /**
+ * Sends a POSIX signal to the process.
+ *
+ * @param int $signal A valid POSIX signal (see http://www.php.net/manual/en/pcntl.constants.php)
+ * @param bool $throwException Whether to throw exception in case signal failed
+ *
+ * @return bool True if the signal was sent successfully, false otherwise
+ *
+ * @throws LogicException In case the process is not running
+ * @throws RuntimeException In case --enable-sigchild is activated
+ * @throws RuntimeException In case of failure
+ */
+ private function doSignal($signal, $throwException)
+ {
+ if (!$this->isRunning()) {
+ if ($throwException) {
+ throw new LogicException('Can not send signal on a non running process.');
+ }
+
+ return false;
+ }
+
+ if ($this->isSigchildEnabled()) {
+ if ($throwException) {
+ throw new RuntimeException('This PHP has been compiled with --enable-sigchild. The process can not be signaled.');
+ }
+
+ return false;
+ }
+
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ exec(sprintf('taskkill /F /T /PID %d 2>&1', $this->getPid()), $output, $exitCode);
+ if ($exitCode) {
+ if ($throwException) {
+ throw new RuntimeException(sprintf('Unable to kill the process (%s).', implode(' ', $output)));
+ }
+
+ return false;
+ }
+ }
+
+ if (true !== @proc_terminate($this->process, $signal) && '\\' !== DIRECTORY_SEPARATOR) {
+ if ($throwException) {
+ throw new RuntimeException(sprintf('Error while sending signal `%s`.', $signal));
+ }
+
+ return false;
+ }
+
+ $this->latestSignal = $signal;
+
+ return true;
+ }
+
+ /**
+ * Ensures the process is running or terminated, throws a LogicException if the process has a not started.
+ *
+ * @param string $functionName The function name that was called.
+ *
+ * @throws LogicException If the process has not run.
+ */
+ private function requireProcessIsStarted($functionName)
+ {
+ if (!$this->isStarted()) {
+ throw new LogicException(sprintf('Process must be started before calling %s.', $functionName));
+ }
+ }
+
+ /**
+ * Ensures the process is terminated, throws a LogicException if the process has a status different than `terminated`.
+ *
+ * @param string $functionName The function name that was called.
+ *
+ * @throws LogicException If the process is not yet terminated.
+ */
+ private function requireProcessIsTerminated($functionName)
+ {
+ if (!$this->isTerminated()) {
+ throw new LogicException(sprintf('Process must be terminated before calling %s.', $functionName));
+ }
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/ProcessBuilder.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/ProcessBuilder.php
new file mode 100644
index 000000000..a782fd69e
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/ProcessBuilder.php
@@ -0,0 +1,287 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process;
+
+use Symfony\Component\Process\Exception\InvalidArgumentException;
+use Symfony\Component\Process\Exception\LogicException;
+
+/**
+ * Process builder.
+ *
+ * @author Kris Wallsmith
+ */
+class ProcessBuilder
+{
+ private $arguments;
+ private $cwd;
+ private $env = array();
+ private $input;
+ private $timeout = 60;
+ private $options = array();
+ private $inheritEnv = true;
+ private $prefix = array();
+ private $outputDisabled = false;
+
+ /**
+ * Constructor.
+ *
+ * @param string[] $arguments An array of arguments
+ */
+ public function __construct(array $arguments = array())
+ {
+ $this->arguments = $arguments;
+ }
+
+ /**
+ * Creates a process builder instance.
+ *
+ * @param string[] $arguments An array of arguments
+ *
+ * @return ProcessBuilder
+ */
+ public static function create(array $arguments = array())
+ {
+ return new static($arguments);
+ }
+
+ /**
+ * Adds an unescaped argument to the command string.
+ *
+ * @param string $argument A command argument
+ *
+ * @return ProcessBuilder
+ */
+ public function add($argument)
+ {
+ $this->arguments[] = $argument;
+
+ return $this;
+ }
+
+ /**
+ * Adds a prefix to the command string.
+ *
+ * The prefix is preserved when resetting arguments.
+ *
+ * @param string|array $prefix A command prefix or an array of command prefixes
+ *
+ * @return ProcessBuilder
+ */
+ public function setPrefix($prefix)
+ {
+ $this->prefix = is_array($prefix) ? $prefix : array($prefix);
+
+ return $this;
+ }
+
+ /**
+ * Sets the arguments of the process.
+ *
+ * Arguments must not be escaped.
+ * Previous arguments are removed.
+ *
+ * @param string[] $arguments
+ *
+ * @return ProcessBuilder
+ */
+ public function setArguments(array $arguments)
+ {
+ $this->arguments = $arguments;
+
+ return $this;
+ }
+
+ /**
+ * Sets the working directory.
+ *
+ * @param null|string $cwd The working directory
+ *
+ * @return ProcessBuilder
+ */
+ public function setWorkingDirectory($cwd)
+ {
+ $this->cwd = $cwd;
+
+ return $this;
+ }
+
+ /**
+ * Sets whether environment variables will be inherited or not.
+ *
+ * @param bool $inheritEnv
+ *
+ * @return ProcessBuilder
+ */
+ public function inheritEnvironmentVariables($inheritEnv = true)
+ {
+ $this->inheritEnv = $inheritEnv;
+
+ return $this;
+ }
+
+ /**
+ * Sets an environment variable.
+ *
+ * Setting a variable overrides its previous value. Use `null` to unset a
+ * defined environment variable.
+ *
+ * @param string $name The variable name
+ * @param null|string $value The variable value
+ *
+ * @return ProcessBuilder
+ */
+ public function setEnv($name, $value)
+ {
+ $this->env[$name] = $value;
+
+ return $this;
+ }
+
+ /**
+ * Adds a set of environment variables.
+ *
+ * Already existing environment variables with the same name will be
+ * overridden by the new values passed to this method. Pass `null` to unset
+ * a variable.
+ *
+ * @param array $variables The variables
+ *
+ * @return ProcessBuilder
+ */
+ public function addEnvironmentVariables(array $variables)
+ {
+ $this->env = array_replace($this->env, $variables);
+
+ return $this;
+ }
+
+ /**
+ * Sets the input of the process.
+ *
+ * @param mixed $input The input as a string
+ *
+ * @return ProcessBuilder
+ *
+ * @throws InvalidArgumentException In case the argument is invalid
+ *
+ * Passing an object as an input is deprecated since version 2.5 and will be removed in 3.0.
+ */
+ public function setInput($input)
+ {
+ $this->input = ProcessUtils::validateInput(sprintf('%s::%s', __CLASS__, __FUNCTION__), $input);
+
+ return $this;
+ }
+
+ /**
+ * Sets the process timeout.
+ *
+ * To disable the timeout, set this value to null.
+ *
+ * @param float|null $timeout
+ *
+ * @return ProcessBuilder
+ *
+ * @throws InvalidArgumentException
+ */
+ public function setTimeout($timeout)
+ {
+ if (null === $timeout) {
+ $this->timeout = null;
+
+ return $this;
+ }
+
+ $timeout = (float) $timeout;
+
+ if ($timeout < 0) {
+ throw new InvalidArgumentException('The timeout value must be a valid positive integer or float number.');
+ }
+
+ $this->timeout = $timeout;
+
+ return $this;
+ }
+
+ /**
+ * Adds a proc_open option.
+ *
+ * @param string $name The option name
+ * @param string $value The option value
+ *
+ * @return ProcessBuilder
+ */
+ public function setOption($name, $value)
+ {
+ $this->options[$name] = $value;
+
+ return $this;
+ }
+
+ /**
+ * Disables fetching output and error output from the underlying process.
+ *
+ * @return ProcessBuilder
+ */
+ public function disableOutput()
+ {
+ $this->outputDisabled = true;
+
+ return $this;
+ }
+
+ /**
+ * Enables fetching output and error output from the underlying process.
+ *
+ * @return ProcessBuilder
+ */
+ public function enableOutput()
+ {
+ $this->outputDisabled = false;
+
+ return $this;
+ }
+
+ /**
+ * Creates a Process instance and returns it.
+ *
+ * @return Process
+ *
+ * @throws LogicException In case no arguments have been provided
+ */
+ public function getProcess()
+ {
+ if (0 === count($this->prefix) && 0 === count($this->arguments)) {
+ throw new LogicException('You must add() command arguments before calling getProcess().');
+ }
+
+ $options = $this->options;
+
+ $arguments = array_merge($this->prefix, $this->arguments);
+ $script = implode(' ', array_map(array(__NAMESPACE__.'\\ProcessUtils', 'escapeArgument'), $arguments));
+
+ if ($this->inheritEnv) {
+ // include $_ENV for BC purposes
+ $env = array_replace($_ENV, $_SERVER, $this->env);
+ } else {
+ $env = $this->env;
+ }
+
+ $process = new Process($script, $this->cwd, $env, $this->input, $this->timeout, $options);
+
+ if ($this->outputDisabled) {
+ $process->disableOutput();
+ }
+
+ return $process;
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/ProcessUtils.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/ProcessUtils.php
new file mode 100644
index 000000000..4f30b630d
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/ProcessUtils.php
@@ -0,0 +1,115 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process;
+
+use Symfony\Component\Process\Exception\InvalidArgumentException;
+
+/**
+ * ProcessUtils is a bunch of utility methods.
+ *
+ * This class contains static methods only and is not meant to be instantiated.
+ *
+ * @author Martin Hasoň
+ */
+class ProcessUtils
+{
+ /**
+ * This class should not be instantiated.
+ */
+ private function __construct()
+ {
+ }
+
+ /**
+ * Escapes a string to be used as a shell argument.
+ *
+ * @param string $argument The argument that will be escaped
+ *
+ * @return string The escaped argument
+ */
+ public static function escapeArgument($argument)
+ {
+ //Fix for PHP bug #43784 escapeshellarg removes % from given string
+ //Fix for PHP bug #49446 escapeshellarg doesn't work on Windows
+ //@see https://bugs.php.net/bug.php?id=43784
+ //@see https://bugs.php.net/bug.php?id=49446
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ if ('' === $argument) {
+ return escapeshellarg($argument);
+ }
+
+ $escapedArgument = '';
+ $quote = false;
+ foreach (preg_split('/(")/', $argument, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE) as $part) {
+ if ('"' === $part) {
+ $escapedArgument .= '\\"';
+ } elseif (self::isSurroundedBy($part, '%')) {
+ // Avoid environment variable expansion
+ $escapedArgument .= '^%"'.substr($part, 1, -1).'"^%';
+ } else {
+ // escape trailing backslash
+ if ('\\' === substr($part, -1)) {
+ $part .= '\\';
+ }
+ $quote = true;
+ $escapedArgument .= $part;
+ }
+ }
+ if ($quote) {
+ $escapedArgument = '"'.$escapedArgument.'"';
+ }
+
+ return $escapedArgument;
+ }
+
+ return escapeshellarg($argument);
+ }
+
+ /**
+ * Validates and normalizes a Process input.
+ *
+ * @param string $caller The name of method call that validates the input
+ * @param mixed $input The input to validate
+ *
+ * @return string The validated input
+ *
+ * @throws InvalidArgumentException In case the input is not valid
+ *
+ * Passing an object as an input is deprecated since version 2.5 and will be removed in 3.0.
+ */
+ public static function validateInput($caller, $input)
+ {
+ if (null !== $input) {
+ if (is_resource($input)) {
+ return $input;
+ }
+ if (is_scalar($input)) {
+ return (string) $input;
+ }
+ // deprecated as of Symfony 2.5, to be removed in 3.0
+ if (is_object($input) && method_exists($input, '__toString')) {
+ @trigger_error('Passing an object as an input is deprecated since version 2.5 and will be removed in 3.0.', E_USER_DEPRECATED);
+
+ return (string) $input;
+ }
+
+ throw new InvalidArgumentException(sprintf('%s only accepts strings or stream resources.', $caller));
+ }
+
+ return $input;
+ }
+
+ private static function isSurroundedBy($arg, $char)
+ {
+ return 2 < strlen($arg) && $char === $arg[0] && $char === $arg[strlen($arg) - 1];
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/README.md b/modules/devshop/devshop_projects/drush/vendor/symfony/process/README.md
new file mode 100644
index 000000000..7222fe895
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/README.md
@@ -0,0 +1,65 @@
+Process Component
+=================
+
+Process executes commands in sub-processes.
+
+In this example, we run a simple directory listing and get the result back:
+
+```php
+use Symfony\Component\Process\Process;
+use Symfony\Component\Process\Exception\ProcessFailedException;
+
+$process = new Process('ls -lsa');
+$process->setTimeout(3600);
+$process->run();
+if (!$process->isSuccessful()) {
+ throw new ProcessFailedException($process);
+}
+
+print $process->getOutput();
+```
+
+You can think that this is easy to achieve with plain PHP but it's not especially
+if you want to take care of the subtle differences between the different platforms.
+
+You can simplify the code by using `mustRun()` instead of `run()`, which will
+throw a `ProcessFailedException` automatically in case of a problem:
+
+```php
+use Symfony\Component\Process\Process;
+
+$process = new Process('ls -lsa');
+$process->setTimeout(3600);
+$process->mustRun();
+
+print $process->getOutput();
+```
+
+And if you want to be able to get some feedback in real-time, just pass an
+anonymous function to the ``run()`` method and you will get the output buffer
+as it becomes available:
+
+```php
+use Symfony\Component\Process\Process;
+
+$process = new Process('ls -lsa');
+$process->run(function ($type, $buffer) {
+ if (Process::ERR === $type) {
+ echo 'ERR > '.$buffer;
+ } else {
+ echo 'OUT > '.$buffer;
+ }
+});
+```
+
+That's great if you want to execute a long running command (like rsync-ing files to a
+remote server) and give feedback to the user in real-time.
+
+Resources
+---------
+
+You can run the unit tests with the following command:
+
+ $ cd path/to/Symfony/Component/Process/
+ $ composer install
+ $ phpunit
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/AbstractProcessTest.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/AbstractProcessTest.php
new file mode 100644
index 000000000..fca3729be
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/AbstractProcessTest.php
@@ -0,0 +1,1196 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Tests;
+
+use Symfony\Component\Process\Exception\LogicException;
+use Symfony\Component\Process\Exception\ProcessTimedOutException;
+use Symfony\Component\Process\Exception\RuntimeException;
+use Symfony\Component\Process\PhpExecutableFinder;
+use Symfony\Component\Process\Pipes\PipesInterface;
+use Symfony\Component\Process\Process;
+
+/**
+ * @author Robert Schönthal
+ */
+abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
+{
+ protected static $phpBin;
+
+ public static function setUpBeforeClass()
+ {
+ $phpBin = new PhpExecutableFinder();
+ self::$phpBin = 'phpdbg' === PHP_SAPI ? 'php' : $phpBin->find();
+ }
+
+ public function testThatProcessDoesNotThrowWarningDuringRun()
+ {
+ @trigger_error('Test Error', E_USER_NOTICE);
+ $process = $this->getProcess(self::$phpBin." -r 'sleep(3)'");
+ $process->run();
+ $actualError = error_get_last();
+ $this->assertEquals('Test Error', $actualError['message']);
+ $this->assertEquals(E_USER_NOTICE, $actualError['type']);
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\InvalidArgumentException
+ */
+ public function testNegativeTimeoutFromConstructor()
+ {
+ $this->getProcess('', null, null, null, -1);
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\InvalidArgumentException
+ */
+ public function testNegativeTimeoutFromSetter()
+ {
+ $p = $this->getProcess('');
+ $p->setTimeout(-1);
+ }
+
+ public function testFloatAndNullTimeout()
+ {
+ $p = $this->getProcess('');
+
+ $p->setTimeout(10);
+ $this->assertSame(10.0, $p->getTimeout());
+
+ $p->setTimeout(null);
+ $this->assertNull($p->getTimeout());
+
+ $p->setTimeout(0.0);
+ $this->assertNull($p->getTimeout());
+ }
+
+ public function testStopWithTimeoutIsActuallyWorking()
+ {
+ if (!extension_loaded('pcntl')) {
+ $this->markTestSkipped('Extension pcntl is required.');
+ }
+
+ // exec is mandatory here since we send a signal to the process
+ // see https://github.com/symfony/symfony/issues/5030 about prepending
+ // command with exec
+ $p = $this->getProcess('exec '.self::$phpBin.' '.__DIR__.'/NonStopableProcess.php 3');
+ $p->start();
+ usleep(100000);
+ $start = microtime(true);
+ $p->stop(1.1, SIGKILL);
+ while ($p->isRunning()) {
+ usleep(1000);
+ }
+ $duration = microtime(true) - $start;
+
+ $this->assertLessThan(4, $duration);
+ }
+
+ public function testAllOutputIsActuallyReadOnTermination()
+ {
+ // this code will result in a maximum of 2 reads of 8192 bytes by calling
+ // start() and isRunning(). by the time getOutput() is called the process
+ // has terminated so the internal pipes array is already empty. normally
+ // the call to start() will not read any data as the process will not have
+ // generated output, but this is non-deterministic so we must count it as
+ // a possibility. therefore we need 2 * PipesInterface::CHUNK_SIZE plus
+ // another byte which will never be read.
+ $expectedOutputSize = PipesInterface::CHUNK_SIZE * 2 + 2;
+
+ $code = sprintf('echo str_repeat(\'*\', %d);', $expectedOutputSize);
+ $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg($code)));
+
+ $p->start();
+ // Let's wait enough time for process to finish...
+ // Here we don't call Process::run or Process::wait to avoid any read of pipes
+ usleep(500000);
+
+ if ($p->isRunning()) {
+ $this->markTestSkipped('Process execution did not complete in the required time frame');
+ }
+
+ $o = $p->getOutput();
+
+ $this->assertEquals($expectedOutputSize, strlen($o));
+ }
+
+ public function testCallbacksAreExecutedWithStart()
+ {
+ $data = '';
+
+ $process = $this->getProcess('echo foo && php -r "sleep(1);" && echo foo');
+ $process->start(function ($type, $buffer) use (&$data) {
+ $data .= $buffer;
+ });
+
+ while ($process->isRunning()) {
+ usleep(10000);
+ }
+
+ $this->assertEquals(2, preg_match_all('/foo/', $data, $matches));
+ }
+
+ /**
+ * tests results from sub processes.
+ *
+ * @dataProvider responsesCodeProvider
+ */
+ public function testProcessResponses($expected, $getter, $code)
+ {
+ $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg($code)));
+ $p->run();
+
+ $this->assertSame($expected, $p->$getter());
+ }
+
+ /**
+ * tests results from sub processes.
+ *
+ * @dataProvider pipesCodeProvider
+ */
+ public function testProcessPipes($code, $size)
+ {
+ $expected = str_repeat(str_repeat('*', 1024), $size).'!';
+ $expectedLength = (1024 * $size) + 1;
+
+ $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg($code)));
+ $p->setInput($expected);
+ $p->run();
+
+ $this->assertEquals($expectedLength, strlen($p->getOutput()));
+ $this->assertEquals($expectedLength, strlen($p->getErrorOutput()));
+ }
+
+ /**
+ * @dataProvider pipesCodeProvider
+ */
+ public function testSetStreamAsInput($code, $size)
+ {
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ $this->markTestIncomplete('This test fails with a timeout on Windows, can someone investigate please?');
+ }
+ $expected = str_repeat(str_repeat('*', 1024), $size).'!';
+ $expectedLength = (1024 * $size) + 1;
+
+ $stream = fopen('php://temporary', 'w+');
+ fwrite($stream, $expected);
+ rewind($stream);
+
+ $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg($code)), null, null, null, 5);
+ $p->setInput($stream);
+ $p->run();
+
+ fclose($stream);
+
+ $this->assertEquals($expectedLength, strlen($p->getOutput()));
+ $this->assertEquals($expectedLength, strlen($p->getErrorOutput()));
+ }
+
+ public function testSetInputWhileRunningThrowsAnException()
+ {
+ $process = $this->getProcess(self::$phpBin.' -r "usleep(500000);"');
+ $process->start();
+ try {
+ $process->setInput('foobar');
+ $process->stop();
+ $this->fail('A LogicException should have been raised.');
+ } catch (LogicException $e) {
+ $this->assertEquals('Input can not be set while the process is running.', $e->getMessage());
+ }
+ $process->stop();
+ }
+
+ /**
+ * @dataProvider provideInvalidInputValues
+ * @expectedException \Symfony\Component\Process\Exception\InvalidArgumentException
+ * @expectedExceptionMessage Symfony\Component\Process\Process::setInput only accepts strings or stream resources.
+ */
+ public function testInvalidInput($value)
+ {
+ $process = $this->getProcess(self::$phpBin.' -v');
+ $process->setInput($value);
+ }
+
+ public function provideInvalidInputValues()
+ {
+ return array(
+ array(array()),
+ array(new NonStringifiable()),
+ );
+ }
+
+ /**
+ * @dataProvider provideInputValues
+ */
+ public function testValidInput($expected, $value)
+ {
+ $process = $this->getProcess(self::$phpBin.' -v');
+ $process->setInput($value);
+ $this->assertSame($expected, $process->getInput());
+ }
+
+ public function provideInputValues()
+ {
+ return array(
+ array(null, null),
+ array('24.5', 24.5),
+ array('input data', 'input data'),
+ );
+ }
+
+ /**
+ * @dataProvider provideLegacyInputValues
+ * @group legacy
+ */
+ public function testLegacyValidInput($expected, $value)
+ {
+ $process = $this->getProcess(self::$phpBin.' -v');
+ $process->setInput($value);
+ $this->assertSame($expected, $process->getInput());
+ }
+
+ public function provideLegacyInputValues()
+ {
+ return array(
+ array('stringifiable', new Stringifiable()),
+ );
+ }
+
+ public function chainedCommandsOutputProvider()
+ {
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ return array(
+ array("2 \r\n2\r\n", '&&', '2'),
+ );
+ }
+
+ return array(
+ array("1\n1\n", ';', '1'),
+ array("2\n2\n", '&&', '2'),
+ );
+ }
+
+ /**
+ * @dataProvider chainedCommandsOutputProvider
+ */
+ public function testChainedCommandsOutput($expected, $operator, $input)
+ {
+ $process = $this->getProcess(sprintf('echo %s %s echo %s', $input, $operator, $input));
+ $process->run();
+ $this->assertEquals($expected, $process->getOutput());
+ }
+
+ public function testCallbackIsExecutedForOutput()
+ {
+ $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('echo \'foo\';')));
+
+ $called = false;
+ $p->run(function ($type, $buffer) use (&$called) {
+ $called = $buffer === 'foo';
+ });
+
+ $this->assertTrue($called, 'The callback should be executed with the output');
+ }
+
+ public function testGetErrorOutput()
+ {
+ $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('$n = 0; while ($n < 3) { file_put_contents(\'php://stderr\', \'ERROR\'); $n++; }')));
+
+ $p->run();
+ $this->assertEquals(3, preg_match_all('/ERROR/', $p->getErrorOutput(), $matches));
+ }
+
+ public function testGetIncrementalErrorOutput()
+ {
+ // use a lock file to toggle between writing ("W") and reading ("R") the
+ // error stream
+ $lock = tempnam(sys_get_temp_dir(), get_class($this).'Lock');
+ file_put_contents($lock, 'W');
+
+ $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('$n = 0; while ($n < 3) { if (\'W\' === file_get_contents('.var_export($lock, true).')) { file_put_contents(\'php://stderr\', \'ERROR\'); $n++; file_put_contents('.var_export($lock, true).', \'R\'); } usleep(100); }')));
+
+ $p->start();
+ while ($p->isRunning()) {
+ if ('R' === file_get_contents($lock)) {
+ $this->assertLessThanOrEqual(1, preg_match_all('/ERROR/', $p->getIncrementalErrorOutput(), $matches));
+ file_put_contents($lock, 'W');
+ }
+ usleep(100);
+ }
+
+ unlink($lock);
+ }
+
+ public function testFlushErrorOutput()
+ {
+ $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('$n = 0; while ($n < 3) { file_put_contents(\'php://stderr\', \'ERROR\'); $n++; }')));
+
+ $p->run();
+ $p->clearErrorOutput();
+ $this->assertEmpty($p->getErrorOutput());
+ }
+
+ public function testGetEmptyIncrementalErrorOutput()
+ {
+ // use a lock file to toggle between writing ("W") and reading ("R") the
+ // output stream
+ $lock = tempnam(sys_get_temp_dir(), get_class($this).'Lock');
+ file_put_contents($lock, 'W');
+
+ $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('$n = 0; while ($n < 3) { if (\'W\' === file_get_contents('.var_export($lock, true).')) { file_put_contents(\'php://stderr\', \'ERROR\'); $n++; file_put_contents('.var_export($lock, true).', \'R\'); } usleep(100); }')));
+
+ $p->start();
+
+ $shouldWrite = false;
+
+ while ($p->isRunning()) {
+ if ('R' === file_get_contents($lock)) {
+ if (!$shouldWrite) {
+ $this->assertLessThanOrEqual(1, preg_match_all('/ERROR/', $p->getIncrementalOutput(), $matches));
+ $shouldWrite = true;
+ } else {
+ $this->assertSame('', $p->getIncrementalOutput());
+
+ file_put_contents($lock, 'W');
+ $shouldWrite = false;
+ }
+ }
+ usleep(100);
+ }
+
+ unlink($lock);
+ }
+
+ public function testGetOutput()
+ {
+ $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('$n = 0; while ($n < 3) { echo \' foo \'; $n++; }')));
+
+ $p->run();
+ $this->assertEquals(3, preg_match_all('/foo/', $p->getOutput(), $matches));
+ }
+
+ public function testGetIncrementalOutput()
+ {
+ // use a lock file to toggle between writing ("W") and reading ("R") the
+ // output stream
+ $lock = tempnam(sys_get_temp_dir(), get_class($this).'Lock');
+ file_put_contents($lock, 'W');
+
+ $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('$n = 0; while ($n < 3) { if (\'W\' === file_get_contents('.var_export($lock, true).')) { echo \' foo \'; $n++; file_put_contents('.var_export($lock, true).', \'R\'); } usleep(100); }')));
+
+ $p->start();
+ while ($p->isRunning()) {
+ if ('R' === file_get_contents($lock)) {
+ $this->assertLessThanOrEqual(1, preg_match_all('/foo/', $p->getIncrementalOutput(), $matches));
+ file_put_contents($lock, 'W');
+ }
+ usleep(100);
+ }
+
+ unlink($lock);
+ }
+
+ public function testFlushOutput()
+ {
+ $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('$n=0;while ($n<3) {echo \' foo \';$n++;}')));
+
+ $p->run();
+ $p->clearOutput();
+ $this->assertEmpty($p->getOutput());
+ }
+
+ public function testGetEmptyIncrementalOutput()
+ {
+ // use a lock file to toggle between writing ("W") and reading ("R") the
+ // output stream
+ $lock = tempnam(sys_get_temp_dir(), get_class($this).'Lock');
+ file_put_contents($lock, 'W');
+
+ $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('$n = 0; while ($n < 3) { if (\'W\' === file_get_contents('.var_export($lock, true).')) { echo \' foo \'; $n++; file_put_contents('.var_export($lock, true).', \'R\'); } usleep(100); }')));
+
+ $p->start();
+
+ $shouldWrite = false;
+
+ while ($p->isRunning()) {
+ if ('R' === file_get_contents($lock)) {
+ if (!$shouldWrite) {
+ $this->assertLessThanOrEqual(1, preg_match_all('/foo/', $p->getIncrementalOutput(), $matches));
+ $shouldWrite = true;
+ } else {
+ $this->assertSame('', $p->getIncrementalOutput());
+
+ file_put_contents($lock, 'W');
+ $shouldWrite = false;
+ }
+ }
+ usleep(100);
+ }
+
+ unlink($lock);
+ }
+
+ public function testZeroAsOutput()
+ {
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ // see http://stackoverflow.com/questions/7105433/windows-batch-echo-without-new-line
+ $p = $this->getProcess('echo | set /p dummyName=0');
+ } else {
+ $p = $this->getProcess('printf 0');
+ }
+
+ $p->run();
+ $this->assertSame('0', $p->getOutput());
+ }
+
+ public function testExitCodeCommandFailed()
+ {
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Windows does not support POSIX exit code');
+ }
+
+ // such command run in bash return an exitcode 127
+ $process = $this->getProcess('nonexistingcommandIhopeneversomeonewouldnameacommandlikethis');
+ $process->run();
+
+ $this->assertGreaterThan(0, $process->getExitCode());
+ }
+
+ public function testTTYCommand()
+ {
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Windows does have /dev/tty support');
+ }
+
+ $process = $this->getProcess('echo "foo" >> /dev/null && '.self::$phpBin.' -r "usleep(100000);"');
+ $process->setTty(true);
+ $process->start();
+ $this->assertTrue($process->isRunning());
+ $process->wait();
+
+ $this->assertSame(Process::STATUS_TERMINATED, $process->getStatus());
+ }
+
+ public function testTTYCommandExitCode()
+ {
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Windows does have /dev/tty support');
+ }
+
+ $process = $this->getProcess('echo "foo" >> /dev/null');
+ $process->setTty(true);
+ $process->run();
+
+ $this->assertTrue($process->isSuccessful());
+ }
+
+ public function testTTYInWindowsEnvironment()
+ {
+ if ('\\' !== DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('This test is for Windows platform only');
+ }
+
+ $process = $this->getProcess('echo "foo" >> /dev/null');
+ $process->setTty(false);
+ $this->setExpectedException('Symfony\Component\Process\Exception\RuntimeException', 'TTY mode is not supported on Windows platform.');
+ $process->setTty(true);
+ }
+
+ public function testExitCodeTextIsNullWhenExitCodeIsNull()
+ {
+ $process = $this->getProcess('');
+ $this->assertNull($process->getExitCodeText());
+ }
+
+ public function testPTYCommand()
+ {
+ if (!Process::isPtySupported()) {
+ $this->markTestSkipped('PTY is not supported on this operating system.');
+ }
+
+ $process = $this->getProcess('echo "foo"');
+ $process->setPty(true);
+ $process->run();
+
+ $this->assertSame(Process::STATUS_TERMINATED, $process->getStatus());
+ $this->assertEquals("foo\r\n", $process->getOutput());
+ }
+
+ public function testMustRun()
+ {
+ $process = $this->getProcess('echo foo');
+
+ $this->assertSame($process, $process->mustRun());
+ $this->assertEquals('foo'.PHP_EOL, $process->getOutput());
+ }
+
+ public function testSuccessfulMustRunHasCorrectExitCode()
+ {
+ $process = $this->getProcess('echo foo')->mustRun();
+ $this->assertEquals(0, $process->getExitCode());
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\ProcessFailedException
+ */
+ public function testMustRunThrowsException()
+ {
+ $process = $this->getProcess('exit 1');
+ $process->mustRun();
+ }
+
+ public function testExitCodeText()
+ {
+ $process = $this->getProcess('');
+ $r = new \ReflectionObject($process);
+ $p = $r->getProperty('exitcode');
+ $p->setAccessible(true);
+
+ $p->setValue($process, 2);
+ $this->assertEquals('Misuse of shell builtins', $process->getExitCodeText());
+ }
+
+ public function testStartIsNonBlocking()
+ {
+ $process = $this->getProcess(self::$phpBin.' -r "usleep(500000);"');
+ $start = microtime(true);
+ $process->start();
+ $end = microtime(true);
+ $this->assertLessThan(0.4, $end - $start);
+ $process->wait();
+ }
+
+ public function testUpdateStatus()
+ {
+ $process = $this->getProcess(self::$phpBin.' -v');
+ $process->run();
+ $this->assertTrue(strlen($process->getOutput()) > 0);
+ }
+
+ public function testGetExitCodeIsNullOnStart()
+ {
+ $process = $this->getProcess(self::$phpBin.' -r "usleep(200000);"');
+ $this->assertNull($process->getExitCode());
+ $process->start();
+ $this->assertNull($process->getExitCode());
+ $process->wait();
+ $this->assertEquals(0, $process->getExitCode());
+ }
+
+ public function testGetExitCodeIsNullOnWhenStartingAgain()
+ {
+ $process = $this->getProcess(self::$phpBin.' -r "usleep(200000);"');
+ $process->run();
+ $this->assertEquals(0, $process->getExitCode());
+ $process->start();
+ $this->assertNull($process->getExitCode());
+ $process->wait();
+ $this->assertEquals(0, $process->getExitCode());
+ }
+
+ public function testGetExitCode()
+ {
+ $process = $this->getProcess(self::$phpBin.' -v');
+ $process->run();
+ $this->assertSame(0, $process->getExitCode());
+ }
+
+ public function testStatus()
+ {
+ $process = $this->getProcess(self::$phpBin.' -r "usleep(500000);"');
+ $this->assertFalse($process->isRunning());
+ $this->assertFalse($process->isStarted());
+ $this->assertFalse($process->isTerminated());
+ $this->assertSame(Process::STATUS_READY, $process->getStatus());
+ $process->start();
+ $this->assertTrue($process->isRunning());
+ $this->assertTrue($process->isStarted());
+ $this->assertFalse($process->isTerminated());
+ $this->assertSame(Process::STATUS_STARTED, $process->getStatus());
+ $process->wait();
+ $this->assertFalse($process->isRunning());
+ $this->assertTrue($process->isStarted());
+ $this->assertTrue($process->isTerminated());
+ $this->assertSame(Process::STATUS_TERMINATED, $process->getStatus());
+ }
+
+ public function testStop()
+ {
+ $process = $this->getProcess(self::$phpBin.' -r "sleep(4);"');
+ $process->start();
+ $this->assertTrue($process->isRunning());
+ $process->stop();
+ $this->assertFalse($process->isRunning());
+ }
+
+ public function testIsSuccessful()
+ {
+ $process = $this->getProcess(self::$phpBin.' -v');
+ $process->run();
+ $this->assertTrue($process->isSuccessful());
+ }
+
+ public function testIsSuccessfulOnlyAfterTerminated()
+ {
+ $process = $this->getProcess(self::$phpBin.' -r "sleep(1);"');
+ $process->start();
+
+ $this->assertFalse($process->isSuccessful());
+
+ while ($process->isRunning()) {
+ usleep(300000);
+ }
+
+ $this->assertTrue($process->isSuccessful());
+ }
+
+ public function testIsNotSuccessful()
+ {
+ $process = $this->getProcess(self::$phpBin.' -r "usleep(500000);throw new \Exception(\'BOUM\');"');
+ $process->start();
+ $this->assertTrue($process->isRunning());
+ $process->wait();
+ $this->assertFalse($process->isSuccessful());
+ }
+
+ public function testProcessIsNotSignaled()
+ {
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Windows does not support POSIX signals');
+ }
+
+ $process = $this->getProcess(self::$phpBin.' -v');
+ $process->run();
+ $this->assertFalse($process->hasBeenSignaled());
+ }
+
+ public function testProcessWithoutTermSignalIsNotSignaled()
+ {
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Windows does not support POSIX signals');
+ }
+
+ $process = $this->getProcess(self::$phpBin.' -v');
+ $process->run();
+ $this->assertFalse($process->hasBeenSignaled());
+ }
+
+ public function testProcessWithoutTermSignal()
+ {
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Windows does not support POSIX signals');
+ }
+
+ $process = $this->getProcess(self::$phpBin.' -v');
+ $process->run();
+ $this->assertEquals(0, $process->getTermSignal());
+ }
+
+ public function testProcessIsSignaledIfStopped()
+ {
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Windows does not support POSIX signals');
+ }
+
+ $process = $this->getProcess(self::$phpBin.' -r "sleep(4);"');
+ $process->start();
+ $process->stop();
+ $this->assertTrue($process->hasBeenSignaled());
+ }
+
+ public function testProcessWithTermSignal()
+ {
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Windows does not support POSIX signals');
+ }
+
+ // SIGTERM is only defined if pcntl extension is present
+ $termSignal = defined('SIGTERM') ? SIGTERM : 15;
+
+ $process = $this->getProcess(self::$phpBin.' -r "sleep(4);"');
+ $process->start();
+ $process->stop();
+
+ $this->assertEquals($termSignal, $process->getTermSignal());
+ }
+
+ public function testProcessThrowsExceptionWhenExternallySignaled()
+ {
+ if (!function_exists('posix_kill')) {
+ $this->markTestSkipped('Function posix_kill is required.');
+ }
+
+ $termSignal = defined('SIGKILL') ? SIGKILL : 9;
+
+ $process = $this->getProcess('exec '.self::$phpBin.' -r "while (true) {}"');
+ $process->start();
+ posix_kill($process->getPid(), $termSignal);
+
+ $this->setExpectedException('Symfony\Component\Process\Exception\RuntimeException', 'The process has been signaled with signal "9".');
+ $process->wait();
+ }
+
+ public function testRestart()
+ {
+ $process1 = $this->getProcess(self::$phpBin.' -r "echo getmypid();"');
+ $process1->run();
+ $process2 = $process1->restart();
+
+ $process2->wait(); // wait for output
+
+ // Ensure that both processed finished and the output is numeric
+ $this->assertFalse($process1->isRunning());
+ $this->assertFalse($process2->isRunning());
+ $this->assertTrue(is_numeric($process1->getOutput()));
+ $this->assertTrue(is_numeric($process2->getOutput()));
+
+ // Ensure that restart returned a new process by check that the output is different
+ $this->assertNotEquals($process1->getOutput(), $process2->getOutput());
+ }
+
+ public function testRunProcessWithTimeout()
+ {
+ $timeout = 0.5;
+ $process = $this->getProcess(self::$phpBin.' -r "usleep(600000);"');
+ $process->setTimeout($timeout);
+ $start = microtime(true);
+ try {
+ $process->run();
+ $this->fail('A RuntimeException should have been raised');
+ } catch (RuntimeException $e) {
+ }
+ $duration = microtime(true) - $start;
+
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ // Windows is a bit slower as it read file handles, then allow twice the precision
+ $maxDuration = $timeout + 2 * Process::TIMEOUT_PRECISION;
+ } else {
+ $maxDuration = $timeout + Process::TIMEOUT_PRECISION;
+ }
+
+ $this->assertLessThan($maxDuration, $duration);
+ }
+
+ public function testCheckTimeoutOnNonStartedProcess()
+ {
+ $process = $this->getProcess(self::$phpBin.' -r "sleep(3);"');
+ $process->checkTimeout();
+ }
+
+ public function testCheckTimeoutOnTerminatedProcess()
+ {
+ $process = $this->getProcess(self::$phpBin.' -v');
+ $process->run();
+ $process->checkTimeout();
+ }
+
+ public function testCheckTimeoutOnStartedProcess()
+ {
+ $timeout = 0.5;
+ $precision = 100000;
+ $process = $this->getProcess(self::$phpBin.' -r "sleep(3);"');
+ $process->setTimeout($timeout);
+ $start = microtime(true);
+
+ $process->start();
+
+ try {
+ while ($process->isRunning()) {
+ $process->checkTimeout();
+ usleep($precision);
+ }
+ $this->fail('A RuntimeException should have been raised');
+ } catch (RuntimeException $e) {
+ }
+ $duration = microtime(true) - $start;
+
+ $this->assertLessThan($timeout + $precision, $duration);
+ $this->assertFalse($process->isSuccessful());
+ }
+
+ public function testIdleTimeout()
+ {
+ $process = $this->getProcess(self::$phpBin.' -r "sleep(3);"');
+ $process->setTimeout(10);
+ $process->setIdleTimeout(0.5);
+
+ try {
+ $process->run();
+
+ $this->fail('A timeout exception was expected.');
+ } catch (ProcessTimedOutException $ex) {
+ $this->assertTrue($ex->isIdleTimeout());
+ $this->assertFalse($ex->isGeneralTimeout());
+ $this->assertEquals(0.5, $ex->getExceededTimeout());
+ }
+ }
+
+ public function testIdleTimeoutNotExceededWhenOutputIsSent()
+ {
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ $this->markTestIncomplete('This test fails with a timeout on Windows, can someone investigate please?');
+ }
+ $process = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('$n = 30; while ($n--) {echo "foo\n"; usleep(100000); }')));
+ $process->setTimeout(2);
+ $process->setIdleTimeout(1);
+
+ try {
+ $process->run();
+ $this->fail('A timeout exception was expected.');
+ } catch (ProcessTimedOutException $ex) {
+ $this->assertTrue($ex->isGeneralTimeout(), 'A general timeout is expected.');
+ $this->assertFalse($ex->isIdleTimeout(), 'No idle timeout is expected.');
+ $this->assertEquals(2, $ex->getExceededTimeout());
+ }
+ }
+
+ public function testStartAfterATimeout()
+ {
+ $process = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('$n = 1000; while ($n--) {echo \'\'; usleep(1000); }')));
+ $process->setTimeout(0.1);
+
+ try {
+ $process->run();
+ $this->fail('A RuntimeException should have been raised.');
+ } catch (RuntimeException $e) {
+ }
+ $process->start();
+ usleep(1000);
+ $process->stop();
+ }
+
+ public function testGetPid()
+ {
+ $process = $this->getProcess(self::$phpBin.' -r "usleep(500000);"');
+ $process->start();
+ $this->assertGreaterThan(0, $process->getPid());
+ $process->wait();
+ }
+
+ public function testGetPidIsNullBeforeStart()
+ {
+ $process = $this->getProcess(self::$phpBin.' -r "sleep(1);"');
+ $this->assertNull($process->getPid());
+ }
+
+ public function testGetPidIsNullAfterRun()
+ {
+ $process = $this->getProcess(self::$phpBin.' -v');
+ $process->run();
+ $this->assertNull($process->getPid());
+ }
+
+ public function testSignal()
+ {
+ if (!extension_loaded('pcntl')) {
+ $this->markTestSkipped('Extension pcntl is required.');
+ }
+
+ $process = $this->getProcess('exec php -f '.__DIR__.'/SignalListener.php');
+ $process->start();
+ usleep(500000);
+ $process->signal(SIGUSR1);
+
+ while ($process->isRunning() && false === strpos($process->getOutput(), 'Caught SIGUSR1')) {
+ usleep(10000);
+ }
+
+ $this->assertEquals('Caught SIGUSR1', $process->getOutput());
+ }
+
+ public function testExitCodeIsAvailableAfterSignal()
+ {
+ if (!extension_loaded('pcntl')) {
+ $this->markTestSkipped('Extension pcntl is required.');
+ }
+
+ $process = $this->getProcess('sleep 4');
+ $process->start();
+ $process->signal(SIGKILL);
+
+ while ($process->isRunning()) {
+ usleep(10000);
+ }
+
+ $this->assertFalse($process->isRunning());
+ $this->assertTrue($process->hasBeenSignaled());
+ $this->assertFalse($process->isSuccessful());
+ $this->assertEquals(137, $process->getExitCode());
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\LogicException
+ */
+ public function testSignalProcessNotRunning()
+ {
+ if (!extension_loaded('pcntl')) {
+ $this->markTestSkipped('Extension pcntl is required.');
+ }
+
+ $process = $this->getProcess(self::$phpBin.' -v');
+ $process->signal(SIGHUP);
+ }
+
+ /**
+ * @dataProvider provideMethodsThatNeedARunningProcess
+ */
+ public function testMethodsThatNeedARunningProcess($method)
+ {
+ $process = $this->getProcess(self::$phpBin.' -v');
+ $this->setExpectedException('Symfony\Component\Process\Exception\LogicException', sprintf('Process must be started before calling %s.', $method));
+ $process->{$method}();
+ }
+
+ public function provideMethodsThatNeedARunningProcess()
+ {
+ return array(
+ array('getOutput'),
+ array('getIncrementalOutput'),
+ array('getErrorOutput'),
+ array('getIncrementalErrorOutput'),
+ array('wait'),
+ );
+ }
+
+ /**
+ * @dataProvider provideMethodsThatNeedATerminatedProcess
+ */
+ public function testMethodsThatNeedATerminatedProcess($method)
+ {
+ $process = $this->getProcess(self::$phpBin.' -r "sleep(1);"');
+ $process->start();
+ try {
+ $process->{$method}();
+ $process->stop(0);
+ $this->fail('A LogicException must have been thrown');
+ } catch (\Exception $e) {
+ $this->assertInstanceOf('Symfony\Component\Process\Exception\LogicException', $e);
+ $this->assertEquals(sprintf('Process must be terminated before calling %s.', $method), $e->getMessage());
+ }
+ $process->stop(0);
+ }
+
+ public function provideMethodsThatNeedATerminatedProcess()
+ {
+ return array(
+ array('hasBeenSignaled'),
+ array('getTermSignal'),
+ array('hasBeenStopped'),
+ array('getStopSignal'),
+ );
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\RuntimeException
+ */
+ public function testSignalWithWrongIntSignal()
+ {
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('POSIX signals do not work on Windows');
+ }
+
+ $process = $this->getProcess(self::$phpBin.' -r "sleep(3);"');
+ $process->start();
+ $process->signal(-4);
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\RuntimeException
+ */
+ public function testSignalWithWrongNonIntSignal()
+ {
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('POSIX signals do not work on Windows');
+ }
+
+ $process = $this->getProcess(self::$phpBin.' -r "sleep(3);"');
+ $process->start();
+ $process->signal('Céphalopodes');
+ }
+
+ public function testDisableOutputDisablesTheOutput()
+ {
+ $p = $this->getProcess(self::$phpBin.' -r "usleep(500000);"');
+ $this->assertFalse($p->isOutputDisabled());
+ $p->disableOutput();
+ $this->assertTrue($p->isOutputDisabled());
+ $p->enableOutput();
+ $this->assertFalse($p->isOutputDisabled());
+ }
+
+ public function testDisableOutputWhileRunningThrowsException()
+ {
+ $p = $this->getProcess(self::$phpBin.' -r "usleep(500000);"');
+ $p->start();
+ $this->setExpectedException('Symfony\Component\Process\Exception\RuntimeException', 'Disabling output while the process is running is not possible.');
+ $p->disableOutput();
+ }
+
+ public function testEnableOutputWhileRunningThrowsException()
+ {
+ $p = $this->getProcess(self::$phpBin.' -r "usleep(500000);"');
+ $p->disableOutput();
+ $p->start();
+ $this->setExpectedException('Symfony\Component\Process\Exception\RuntimeException', 'Enabling output while the process is running is not possible.');
+ $p->enableOutput();
+ }
+
+ public function testEnableOrDisableOutputAfterRunDoesNotThrowException()
+ {
+ $p = $this->getProcess(self::$phpBin.' -r "usleep(500000);"');
+ $p->disableOutput();
+ $p->start();
+ $p->wait();
+ $p->enableOutput();
+ $p->disableOutput();
+ }
+
+ public function testDisableOutputWhileIdleTimeoutIsSet()
+ {
+ $process = $this->getProcess('sleep 3');
+ $process->setIdleTimeout(1);
+ $this->setExpectedException('Symfony\Component\Process\Exception\LogicException', 'Output can not be disabled while an idle timeout is set.');
+ $process->disableOutput();
+ }
+
+ public function testSetIdleTimeoutWhileOutputIsDisabled()
+ {
+ $process = $this->getProcess('sleep 3');
+ $process->disableOutput();
+ $this->setExpectedException('Symfony\Component\Process\Exception\LogicException', 'Idle timeout can not be set while the output is disabled.');
+ $process->setIdleTimeout(1);
+ }
+
+ public function testSetNullIdleTimeoutWhileOutputIsDisabled()
+ {
+ $process = $this->getProcess('sleep 3');
+ $process->disableOutput();
+ $process->setIdleTimeout(null);
+ }
+
+ /**
+ * @dataProvider provideStartMethods
+ */
+ public function testStartWithACallbackAndDisabledOutput($startMethod, $exception, $exceptionMessage)
+ {
+ $p = $this->getProcess(self::$phpBin.' -r "usleep(500000);"');
+ $p->disableOutput();
+ $this->setExpectedException($exception, $exceptionMessage);
+ $p->{$startMethod}(function () {});
+ }
+
+ public function provideStartMethods()
+ {
+ return array(
+ array('start', 'Symfony\Component\Process\Exception\LogicException', 'Output has been disabled, enable it to allow the use of a callback.'),
+ array('run', 'Symfony\Component\Process\Exception\LogicException', 'Output has been disabled, enable it to allow the use of a callback.'),
+ array('mustRun', 'Symfony\Component\Process\Exception\LogicException', 'Output has been disabled, enable it to allow the use of a callback.'),
+ );
+ }
+
+ /**
+ * @dataProvider provideOutputFetchingMethods
+ */
+ public function testGetOutputWhileDisabled($fetchMethod)
+ {
+ $p = $this->getProcess(self::$phpBin.' -r "usleep(500000);"');
+ $p->disableOutput();
+ $p->start();
+ $this->setExpectedException('Symfony\Component\Process\Exception\LogicException', 'Output has been disabled.');
+ $p->{$fetchMethod}();
+ }
+
+ public function provideOutputFetchingMethods()
+ {
+ return array(
+ array('getOutput'),
+ array('getIncrementalOutput'),
+ array('getErrorOutput'),
+ array('getIncrementalErrorOutput'),
+ );
+ }
+
+ public function responsesCodeProvider()
+ {
+ return array(
+ //expected output / getter / code to execute
+ //array(1,'getExitCode','exit(1);'),
+ //array(true,'isSuccessful','exit();'),
+ array('output', 'getOutput', 'echo \'output\';'),
+ );
+ }
+
+ public function pipesCodeProvider()
+ {
+ $variations = array(
+ 'fwrite(STDOUT, $in = file_get_contents(\'php://stdin\')); fwrite(STDERR, $in);',
+ 'include \''.__DIR__.'/PipeStdinInStdoutStdErrStreamSelect.php\';',
+ );
+
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ // Avoid XL buffers on Windows because of https://bugs.php.net/bug.php?id=65650
+ $sizes = array(1, 2, 4, 8);
+ } else {
+ $sizes = array(1, 16, 64, 1024, 4096);
+ }
+
+ $codes = array();
+ foreach ($sizes as $size) {
+ foreach ($variations as $code) {
+ $codes[] = array($code, $size);
+ }
+ }
+
+ return $codes;
+ }
+
+ /**
+ * provides default method names for simple getter/setter.
+ */
+ public function methodProvider()
+ {
+ $defaults = array(
+ array('CommandLine'),
+ array('Timeout'),
+ array('WorkingDirectory'),
+ array('Env'),
+ array('Stdin'),
+ array('Input'),
+ array('Options'),
+ );
+
+ return $defaults;
+ }
+
+ /**
+ * @param string $commandline
+ * @param null|string $cwd
+ * @param null|array $env
+ * @param null|string $input
+ * @param int $timeout
+ * @param array $options
+ *
+ * @return Process
+ */
+ abstract protected function getProcess($commandline, $cwd = null, array $env = null, $input = null, $timeout = 60, array $options = array());
+}
+
+class Stringifiable
+{
+ public function __toString()
+ {
+ return 'stringifiable';
+ }
+}
+
+class NonStringifiable
+{
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/ExecutableFinderTest.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/ExecutableFinderTest.php
new file mode 100644
index 000000000..812429e88
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/ExecutableFinderTest.php
@@ -0,0 +1,144 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Tests;
+
+use Symfony\Component\Process\ExecutableFinder;
+
+/**
+ * @author Chris Smith
+ */
+class ExecutableFinderTest extends \PHPUnit_Framework_TestCase
+{
+ private $path;
+
+ protected function tearDown()
+ {
+ if ($this->path) {
+ // Restore path if it was changed.
+ putenv('PATH='.$this->path);
+ }
+ }
+
+ private function setPath($path)
+ {
+ $this->path = getenv('PATH');
+ putenv('PATH='.$path);
+ }
+
+ /**
+ * @requires PHP 5.4
+ */
+ public function testFind()
+ {
+ if (ini_get('open_basedir')) {
+ $this->markTestSkipped('Cannot test when open_basedir is set');
+ }
+
+ $this->setPath(dirname(PHP_BINARY));
+
+ $finder = new ExecutableFinder();
+ $result = $finder->find($this->getPhpBinaryName());
+
+ $this->assertSamePath(PHP_BINARY, $result);
+ }
+
+ public function testFindWithDefault()
+ {
+ if (ini_get('open_basedir')) {
+ $this->markTestSkipped('Cannot test when open_basedir is set');
+ }
+
+ $expected = 'defaultValue';
+
+ $this->setPath('');
+
+ $finder = new ExecutableFinder();
+ $result = $finder->find('foo', $expected);
+
+ $this->assertEquals($expected, $result);
+ }
+
+ /**
+ * @requires PHP 5.4
+ */
+ public function testFindWithExtraDirs()
+ {
+ if (ini_get('open_basedir')) {
+ $this->markTestSkipped('Cannot test when open_basedir is set');
+ }
+
+ $this->setPath('');
+
+ $extraDirs = array(dirname(PHP_BINARY));
+
+ $finder = new ExecutableFinder();
+ $result = $finder->find($this->getPhpBinaryName(), null, $extraDirs);
+
+ $this->assertSamePath(PHP_BINARY, $result);
+ }
+
+ /**
+ * @requires PHP 5.4
+ */
+ public function testFindWithOpenBaseDir()
+ {
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Cannot run test on windows');
+ }
+
+ if (ini_get('open_basedir')) {
+ $this->markTestSkipped('Cannot test when open_basedir is set');
+ }
+
+ $this->iniSet('open_basedir', dirname(PHP_BINARY).(!defined('HHVM_VERSION') ? PATH_SEPARATOR.'/' : ''));
+
+ $finder = new ExecutableFinder();
+ $result = $finder->find($this->getPhpBinaryName());
+
+ $this->assertSamePath(PHP_BINARY, $result);
+ }
+
+ /**
+ * @requires PHP 5.4
+ */
+ public function testFindProcessInOpenBasedir()
+ {
+ if (ini_get('open_basedir')) {
+ $this->markTestSkipped('Cannot test when open_basedir is set');
+ }
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Cannot run test on windows');
+ }
+
+ $this->setPath('');
+ $this->iniSet('open_basedir', PHP_BINARY.(!defined('HHVM_VERSION') ? PATH_SEPARATOR.'/' : ''));
+
+ $finder = new ExecutableFinder();
+ $result = $finder->find($this->getPhpBinaryName(), false);
+
+ $this->assertSamePath(PHP_BINARY, $result);
+ }
+
+ private function assertSamePath($expected, $tested)
+ {
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ $this->assertEquals(strtolower($expected), strtolower($tested));
+ } else {
+ $this->assertEquals($expected, $tested);
+ }
+ }
+
+ private function getPhpBinaryName()
+ {
+ return basename(PHP_BINARY, '\\' === DIRECTORY_SEPARATOR ? '.exe' : '');
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/NonStopableProcess.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/NonStopableProcess.php
new file mode 100644
index 000000000..54510c16a
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/NonStopableProcess.php
@@ -0,0 +1,45 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Runs a PHP script that can be stopped only with a SIGKILL (9) signal for 3 seconds.
+ *
+ * @args duration Run this script with a custom duration
+ *
+ * @example `php NonStopableProcess.php 42` will run the script for 42 seconds
+ */
+function handleSignal($signal)
+{
+ switch ($signal) {
+ case SIGTERM:
+ $name = 'SIGTERM';
+ break;
+ case SIGINT:
+ $name = 'SIGINT';
+ break;
+ default:
+ $name = $signal.' (unknown)';
+ break;
+ }
+
+ echo "received signal $name\n";
+}
+
+declare (ticks = 1);
+pcntl_signal(SIGTERM, 'handleSignal');
+pcntl_signal(SIGINT, 'handleSignal');
+
+$duration = isset($argv[1]) ? (int) $argv[1] : 3;
+$start = microtime(true);
+
+while ($duration > (microtime(true) - $start)) {
+ usleep(1000);
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/PhpExecutableFinderTest.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/PhpExecutableFinderTest.php
new file mode 100644
index 000000000..87d0efe9e
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/PhpExecutableFinderTest.php
@@ -0,0 +1,119 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Tests;
+
+use Symfony\Component\Process\PhpExecutableFinder;
+
+/**
+ * @author Robert Schönthal
+ */
+class PhpExecutableFinderTest extends \PHPUnit_Framework_TestCase
+{
+ /**
+ * tests find() with the env var PHP_PATH.
+ */
+ public function testFindWithPhpPath()
+ {
+ if (defined('PHP_BINARY')) {
+ $this->markTestSkipped('The PHP binary is easily available as of PHP 5.4');
+ }
+
+ $f = new PhpExecutableFinder();
+
+ $current = $f->find();
+
+ //not executable PHP_PATH
+ putenv('PHP_PATH=/not/executable/php');
+ $this->assertFalse($f->find(), '::find() returns false for not executable PHP');
+ $this->assertFalse($f->find(false), '::find() returns false for not executable PHP');
+
+ //executable PHP_PATH
+ putenv('PHP_PATH='.$current);
+ $this->assertEquals($f->find(), $current, '::find() returns the executable PHP');
+ $this->assertEquals($f->find(false), $current, '::find() returns the executable PHP');
+ }
+
+ /**
+ * tests find() with the constant PHP_BINARY.
+ *
+ * @requires PHP 5.4
+ */
+ public function testFind()
+ {
+ if (defined('HHVM_VERSION')) {
+ $this->markTestSkipped('Should not be executed in HHVM context.');
+ }
+
+ $f = new PhpExecutableFinder();
+
+ $current = PHP_BINARY;
+ $args = 'phpdbg' === PHP_SAPI ? ' -qrr' : '';
+
+ $this->assertEquals($current.$args, $f->find(), '::find() returns the executable PHP');
+ $this->assertEquals($current, $f->find(false), '::find() returns the executable PHP');
+ }
+
+ /**
+ * tests find() with the env var / constant PHP_BINARY with HHVM.
+ */
+ public function testFindWithHHVM()
+ {
+ if (!defined('HHVM_VERSION')) {
+ $this->markTestSkipped('Should be executed in HHVM context.');
+ }
+
+ $f = new PhpExecutableFinder();
+
+ $current = getenv('PHP_BINARY') ?: PHP_BINARY;
+
+ $this->assertEquals($current.' --php', $f->find(), '::find() returns the executable PHP');
+ $this->assertEquals($current, $f->find(false), '::find() returns the executable PHP');
+ }
+
+ /**
+ * tests find() with the env var PHP_PATH.
+ */
+ public function testFindArguments()
+ {
+ $f = new PhpExecutableFinder();
+
+ if (defined('HHVM_VERSION')) {
+ $this->assertEquals($f->findArguments(), array('--php'), '::findArguments() returns HHVM arguments');
+ } elseif ('phpdbg' === PHP_SAPI) {
+ $this->assertEquals($f->findArguments(), array('-qrr'), '::findArguments() returns phpdbg arguments');
+ } else {
+ $this->assertEquals($f->findArguments(), array(), '::findArguments() returns no arguments');
+ }
+ }
+
+ /**
+ * tests find() with default executable.
+ */
+ public function testFindWithSuffix()
+ {
+ if (defined('PHP_BINARY')) {
+ $this->markTestSkipped('The PHP binary is easily available as of PHP 5.4');
+ }
+
+ putenv('PHP_PATH=');
+ putenv('PHP_PEAR_PHP_BIN=');
+ $f = new PhpExecutableFinder();
+
+ $current = $f->find();
+
+ //TODO maybe php executable is custom or even Windows
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ $this->assertTrue(is_executable($current));
+ $this->assertTrue((bool) preg_match('/'.addslashes(DIRECTORY_SEPARATOR).'php\.(exe|bat|cmd|com)$/i', $current), '::find() returns the executable PHP with suffixes');
+ }
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/PhpProcessTest.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/PhpProcessTest.php
new file mode 100644
index 000000000..2cf79aa1a
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/PhpProcessTest.php
@@ -0,0 +1,53 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Tests;
+
+use Symfony\Component\Process\PhpExecutableFinder;
+use Symfony\Component\Process\PhpProcess;
+
+class PhpProcessTest extends \PHPUnit_Framework_TestCase
+{
+ public function testNonBlockingWorks()
+ {
+ $expected = 'hello world!';
+ $process = new PhpProcess(<<start();
+ $process->wait();
+ $this->assertEquals($expected, $process->getOutput());
+ }
+
+ public function testCommandLine()
+ {
+ if ('phpdbg' === PHP_SAPI) {
+ $this->markTestSkipped('phpdbg SAPI is not supported by this test.');
+ }
+
+ $process = new PhpProcess(<<find();
+
+ $this->assertSame($commandLine, $process->getCommandLine(), '::getCommandLine() returns the command line of PHP before start');
+
+ $process->start();
+ $this->assertSame($commandLine, $process->getCommandLine(), '::getCommandLine() returns the command line of PHP after start');
+
+ $process->wait();
+ $this->assertSame($commandLine, $process->getCommandLine(), '::getCommandLine() returns the command line of PHP after wait');
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/PipeStdinInStdoutStdErrStreamSelect.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/PipeStdinInStdoutStdErrStreamSelect.php
new file mode 100644
index 000000000..bbd7ddfeb
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/PipeStdinInStdoutStdErrStreamSelect.php
@@ -0,0 +1,72 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+define('ERR_SELECT_FAILED', 1);
+define('ERR_TIMEOUT', 2);
+define('ERR_READ_FAILED', 3);
+define('ERR_WRITE_FAILED', 4);
+
+$read = array(STDIN);
+$write = array(STDOUT, STDERR);
+
+stream_set_blocking(STDIN, 0);
+stream_set_blocking(STDOUT, 0);
+stream_set_blocking(STDERR, 0);
+
+$out = $err = '';
+while ($read || $write) {
+ $r = $read;
+ $w = $write;
+ $e = null;
+ $n = stream_select($r, $w, $e, 5);
+
+ if (false === $n) {
+ die(ERR_SELECT_FAILED);
+ } elseif ($n < 1) {
+ die(ERR_TIMEOUT);
+ }
+
+ if (in_array(STDOUT, $w) && strlen($out) > 0) {
+ $written = fwrite(STDOUT, (binary) $out, 32768);
+ if (false === $written) {
+ die(ERR_WRITE_FAILED);
+ }
+ $out = (binary) substr($out, $written);
+ }
+ if (null === $read && '' === $out) {
+ $write = array_diff($write, array(STDOUT));
+ }
+
+ if (in_array(STDERR, $w) && strlen($err) > 0) {
+ $written = fwrite(STDERR, (binary) $err, 32768);
+ if (false === $written) {
+ die(ERR_WRITE_FAILED);
+ }
+ $err = (binary) substr($err, $written);
+ }
+ if (null === $read && '' === $err) {
+ $write = array_diff($write, array(STDERR));
+ }
+
+ if ($r) {
+ $str = fread(STDIN, 32768);
+ if (false !== $str) {
+ $out .= $str;
+ $err .= $str;
+ }
+ if (false === $str || feof(STDIN)) {
+ $read = null;
+ if (!feof(STDIN)) {
+ die(ERR_READ_FAILED);
+ }
+ }
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/ProcessBuilderTest.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/ProcessBuilderTest.php
new file mode 100644
index 000000000..1b5056d1b
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/ProcessBuilderTest.php
@@ -0,0 +1,225 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Tests;
+
+use Symfony\Component\Process\ProcessBuilder;
+
+class ProcessBuilderTest extends \PHPUnit_Framework_TestCase
+{
+ public function testInheritEnvironmentVars()
+ {
+ $_ENV['MY_VAR_1'] = 'foo';
+
+ $proc = ProcessBuilder::create()
+ ->add('foo')
+ ->getProcess();
+
+ unset($_ENV['MY_VAR_1']);
+
+ $env = $proc->getEnv();
+ $this->assertArrayHasKey('MY_VAR_1', $env);
+ $this->assertEquals('foo', $env['MY_VAR_1']);
+ }
+
+ public function testAddEnvironmentVariables()
+ {
+ $pb = new ProcessBuilder();
+ $env = array(
+ 'foo' => 'bar',
+ 'foo2' => 'bar2',
+ );
+ $proc = $pb
+ ->add('command')
+ ->setEnv('foo', 'bar2')
+ ->addEnvironmentVariables($env)
+ ->inheritEnvironmentVariables(false)
+ ->getProcess()
+ ;
+
+ $this->assertSame($env, $proc->getEnv());
+ }
+
+ public function testProcessShouldInheritAndOverrideEnvironmentVars()
+ {
+ $_ENV['MY_VAR_1'] = 'foo';
+
+ $proc = ProcessBuilder::create()
+ ->setEnv('MY_VAR_1', 'bar')
+ ->add('foo')
+ ->getProcess();
+
+ unset($_ENV['MY_VAR_1']);
+
+ $env = $proc->getEnv();
+ $this->assertArrayHasKey('MY_VAR_1', $env);
+ $this->assertEquals('bar', $env['MY_VAR_1']);
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\InvalidArgumentException
+ */
+ public function testNegativeTimeoutFromSetter()
+ {
+ $pb = new ProcessBuilder();
+ $pb->setTimeout(-1);
+ }
+
+ public function testNullTimeout()
+ {
+ $pb = new ProcessBuilder();
+ $pb->setTimeout(10);
+ $pb->setTimeout(null);
+
+ $r = new \ReflectionObject($pb);
+ $p = $r->getProperty('timeout');
+ $p->setAccessible(true);
+
+ $this->assertNull($p->getValue($pb));
+ }
+
+ public function testShouldSetArguments()
+ {
+ $pb = new ProcessBuilder(array('initial'));
+ $pb->setArguments(array('second'));
+
+ $proc = $pb->getProcess();
+
+ $this->assertContains('second', $proc->getCommandLine());
+ }
+
+ public function testPrefixIsPrependedToAllGeneratedProcess()
+ {
+ $pb = new ProcessBuilder();
+ $pb->setPrefix('/usr/bin/php');
+
+ $proc = $pb->setArguments(array('-v'))->getProcess();
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ $this->assertEquals('"/usr/bin/php" "-v"', $proc->getCommandLine());
+ } else {
+ $this->assertEquals("'/usr/bin/php' '-v'", $proc->getCommandLine());
+ }
+
+ $proc = $pb->setArguments(array('-i'))->getProcess();
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ $this->assertEquals('"/usr/bin/php" "-i"', $proc->getCommandLine());
+ } else {
+ $this->assertEquals("'/usr/bin/php' '-i'", $proc->getCommandLine());
+ }
+ }
+
+ public function testArrayPrefixesArePrependedToAllGeneratedProcess()
+ {
+ $pb = new ProcessBuilder();
+ $pb->setPrefix(array('/usr/bin/php', 'composer.phar'));
+
+ $proc = $pb->setArguments(array('-v'))->getProcess();
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ $this->assertEquals('"/usr/bin/php" "composer.phar" "-v"', $proc->getCommandLine());
+ } else {
+ $this->assertEquals("'/usr/bin/php' 'composer.phar' '-v'", $proc->getCommandLine());
+ }
+
+ $proc = $pb->setArguments(array('-i'))->getProcess();
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ $this->assertEquals('"/usr/bin/php" "composer.phar" "-i"', $proc->getCommandLine());
+ } else {
+ $this->assertEquals("'/usr/bin/php' 'composer.phar' '-i'", $proc->getCommandLine());
+ }
+ }
+
+ public function testShouldEscapeArguments()
+ {
+ $pb = new ProcessBuilder(array('%path%', 'foo " bar', '%baz%baz'));
+ $proc = $pb->getProcess();
+
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ $this->assertSame('^%"path"^% "foo \\" bar" "%baz%baz"', $proc->getCommandLine());
+ } else {
+ $this->assertSame("'%path%' 'foo \" bar' '%baz%baz'", $proc->getCommandLine());
+ }
+ }
+
+ public function testShouldEscapeArgumentsAndPrefix()
+ {
+ $pb = new ProcessBuilder(array('arg'));
+ $pb->setPrefix('%prefix%');
+ $proc = $pb->getProcess();
+
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ $this->assertSame('^%"prefix"^% "arg"', $proc->getCommandLine());
+ } else {
+ $this->assertSame("'%prefix%' 'arg'", $proc->getCommandLine());
+ }
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\LogicException
+ */
+ public function testShouldThrowALogicExceptionIfNoPrefixAndNoArgument()
+ {
+ ProcessBuilder::create()->getProcess();
+ }
+
+ public function testShouldNotThrowALogicExceptionIfNoArgument()
+ {
+ $process = ProcessBuilder::create()
+ ->setPrefix('/usr/bin/php')
+ ->getProcess();
+
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ $this->assertEquals('"/usr/bin/php"', $process->getCommandLine());
+ } else {
+ $this->assertEquals("'/usr/bin/php'", $process->getCommandLine());
+ }
+ }
+
+ public function testShouldNotThrowALogicExceptionIfNoPrefix()
+ {
+ $process = ProcessBuilder::create(array('/usr/bin/php'))
+ ->getProcess();
+
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ $this->assertEquals('"/usr/bin/php"', $process->getCommandLine());
+ } else {
+ $this->assertEquals("'/usr/bin/php'", $process->getCommandLine());
+ }
+ }
+
+ public function testShouldReturnProcessWithDisabledOutput()
+ {
+ $process = ProcessBuilder::create(array('/usr/bin/php'))
+ ->disableOutput()
+ ->getProcess();
+
+ $this->assertTrue($process->isOutputDisabled());
+ }
+
+ public function testShouldReturnProcessWithEnabledOutput()
+ {
+ $process = ProcessBuilder::create(array('/usr/bin/php'))
+ ->disableOutput()
+ ->enableOutput()
+ ->getProcess();
+
+ $this->assertFalse($process->isOutputDisabled());
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\InvalidArgumentException
+ * @expectedExceptionMessage Symfony\Component\Process\ProcessBuilder::setInput only accepts strings or stream resources.
+ */
+ public function testInvalidInput()
+ {
+ $builder = ProcessBuilder::create();
+ $builder->setInput(array());
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/ProcessFailedExceptionTest.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/ProcessFailedExceptionTest.php
new file mode 100644
index 000000000..0d763a470
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/ProcessFailedExceptionTest.php
@@ -0,0 +1,146 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Tests;
+
+use Symfony\Component\Process\Exception\ProcessFailedException;
+
+/**
+ * @author Sebastian Marek
+ */
+class ProcessFailedExceptionTest extends \PHPUnit_Framework_TestCase
+{
+ /**
+ * tests ProcessFailedException throws exception if the process was successful.
+ */
+ public function testProcessFailedExceptionThrowsException()
+ {
+ $process = $this->getMock(
+ 'Symfony\Component\Process\Process',
+ array('isSuccessful'),
+ array('php')
+ );
+ $process->expects($this->once())
+ ->method('isSuccessful')
+ ->will($this->returnValue(true));
+
+ $this->setExpectedException(
+ '\InvalidArgumentException',
+ 'Expected a failed process, but the given process was successful.'
+ );
+
+ new ProcessFailedException($process);
+ }
+
+ /**
+ * tests ProcessFailedException uses information from process output
+ * to generate exception message.
+ */
+ public function testProcessFailedExceptionPopulatesInformationFromProcessOutput()
+ {
+ $cmd = 'php';
+ $exitCode = 1;
+ $exitText = 'General error';
+ $output = 'Command output';
+ $errorOutput = 'FATAL: Unexpected error';
+ $workingDirectory = getcwd();
+
+ $process = $this->getMock(
+ 'Symfony\Component\Process\Process',
+ array('isSuccessful', 'getOutput', 'getErrorOutput', 'getExitCode', 'getExitCodeText', 'isOutputDisabled', 'getWorkingDirectory'),
+ array($cmd)
+ );
+ $process->expects($this->once())
+ ->method('isSuccessful')
+ ->will($this->returnValue(false));
+
+ $process->expects($this->once())
+ ->method('getOutput')
+ ->will($this->returnValue($output));
+
+ $process->expects($this->once())
+ ->method('getErrorOutput')
+ ->will($this->returnValue($errorOutput));
+
+ $process->expects($this->once())
+ ->method('getExitCode')
+ ->will($this->returnValue($exitCode));
+
+ $process->expects($this->once())
+ ->method('getExitCodeText')
+ ->will($this->returnValue($exitText));
+
+ $process->expects($this->once())
+ ->method('isOutputDisabled')
+ ->will($this->returnValue(false));
+
+ $process->expects($this->once())
+ ->method('getWorkingDirectory')
+ ->will($this->returnValue($workingDirectory));
+
+ $exception = new ProcessFailedException($process);
+
+ $this->assertEquals(
+ "The command \"$cmd\" failed.\n\nExit Code: $exitCode($exitText)\n\nWorking directory: {$workingDirectory}\n\nOutput:\n================\n{$output}\n\nError Output:\n================\n{$errorOutput}",
+ $exception->getMessage()
+ );
+ }
+
+ /**
+ * Tests that ProcessFailedException does not extract information from
+ * process output if it was previously disabled.
+ */
+ public function testDisabledOutputInFailedExceptionDoesNotPopulateOutput()
+ {
+ $cmd = 'php';
+ $exitCode = 1;
+ $exitText = 'General error';
+ $workingDirectory = getcwd();
+
+ $process = $this->getMock(
+ 'Symfony\Component\Process\Process',
+ array('isSuccessful', 'isOutputDisabled', 'getExitCode', 'getExitCodeText', 'getOutput', 'getErrorOutput', 'getWorkingDirectory'),
+ array($cmd)
+ );
+ $process->expects($this->once())
+ ->method('isSuccessful')
+ ->will($this->returnValue(false));
+
+ $process->expects($this->never())
+ ->method('getOutput');
+
+ $process->expects($this->never())
+ ->method('getErrorOutput');
+
+ $process->expects($this->once())
+ ->method('getExitCode')
+ ->will($this->returnValue($exitCode));
+
+ $process->expects($this->once())
+ ->method('getExitCodeText')
+ ->will($this->returnValue($exitText));
+
+ $process->expects($this->once())
+ ->method('isOutputDisabled')
+ ->will($this->returnValue(true));
+
+ $process->expects($this->once())
+ ->method('getWorkingDirectory')
+ ->will($this->returnValue($workingDirectory));
+
+ $exception = new ProcessFailedException($process);
+
+ $this->assertEquals(
+ "The command \"$cmd\" failed.\n\nExit Code: $exitCode($exitText)\n\nWorking directory: {$workingDirectory}",
+ $exception->getMessage()
+ );
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/ProcessInSigchildEnvironment.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/ProcessInSigchildEnvironment.php
new file mode 100644
index 000000000..3977bcdcf
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/ProcessInSigchildEnvironment.php
@@ -0,0 +1,22 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Tests;
+
+use Symfony\Component\Process\Process;
+
+class ProcessInSigchildEnvironment extends Process
+{
+ protected function isSigchildEnabled()
+ {
+ return true;
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/ProcessUtilsTest.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/ProcessUtilsTest.php
new file mode 100644
index 000000000..e6564cde5
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/ProcessUtilsTest.php
@@ -0,0 +1,48 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Tests;
+
+use Symfony\Component\Process\ProcessUtils;
+
+class ProcessUtilsTest extends \PHPUnit_Framework_TestCase
+{
+ /**
+ * @dataProvider dataArguments
+ */
+ public function testEscapeArgument($result, $argument)
+ {
+ $this->assertSame($result, ProcessUtils::escapeArgument($argument));
+ }
+
+ public function dataArguments()
+ {
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ return array(
+ array('"\"php\" \"-v\""', '"php" "-v"'),
+ array('"foo bar"', 'foo bar'),
+ array('^%"path"^%', '%path%'),
+ array('"<|>\\" \\"\'f"', '<|>" "\'f'),
+ array('""', ''),
+ array('"with\trailingbs\\\\"', 'with\trailingbs\\'),
+ );
+ }
+
+ return array(
+ array("'\"php\" \"-v\"'", '"php" "-v"'),
+ array("'foo bar'", 'foo bar'),
+ array("'%path%'", '%path%'),
+ array("'<|>\" \"'\\''f'", '<|>" "\'f'),
+ array("''", ''),
+ array("'with\\trailingbs\\'", 'with\trailingbs\\'),
+ );
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/SigchildDisabledProcessTest.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/SigchildDisabledProcessTest.php
new file mode 100644
index 000000000..fdae5ec25
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/SigchildDisabledProcessTest.php
@@ -0,0 +1,263 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Tests;
+
+class SigchildDisabledProcessTest extends AbstractProcessTest
+{
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\RuntimeException
+ * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.
+ */
+ public function testGetExitCode()
+ {
+ parent::testGetExitCode();
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\RuntimeException
+ * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.
+ */
+ public function testGetExitCodeIsNullOnStart()
+ {
+ parent::testGetExitCodeIsNullOnStart();
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\RuntimeException
+ * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.
+ */
+ public function testGetExitCodeIsNullOnWhenStartingAgain()
+ {
+ parent::testGetExitCodeIsNullOnWhenStartingAgain();
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\RuntimeException
+ * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.
+ */
+ public function testExitCodeCommandFailed()
+ {
+ parent::testExitCodeCommandFailed();
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\RuntimeException
+ * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.
+ */
+ public function testMustRun()
+ {
+ parent::testMustRun();
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\RuntimeException
+ * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.
+ */
+ public function testSuccessfulMustRunHasCorrectExitCode()
+ {
+ parent::testSuccessfulMustRunHasCorrectExitCode();
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\RuntimeException
+ */
+ public function testMustRunThrowsException()
+ {
+ parent::testMustRunThrowsException();
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\RuntimeException
+ */
+ public function testProcessIsSignaledIfStopped()
+ {
+ parent::testProcessIsSignaledIfStopped();
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\RuntimeException
+ * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.
+ */
+ public function testProcessWithTermSignal()
+ {
+ parent::testProcessWithTermSignal();
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\RuntimeException
+ * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.
+ */
+ public function testProcessIsNotSignaled()
+ {
+ parent::testProcessIsNotSignaled();
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\RuntimeException
+ * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.
+ */
+ public function testProcessWithoutTermSignal()
+ {
+ parent::testProcessWithoutTermSignal();
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\RuntimeException
+ * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.
+ */
+ public function testCheckTimeoutOnStartedProcess()
+ {
+ parent::testCheckTimeoutOnStartedProcess();
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\RuntimeException
+ * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. The process identifier can not be retrieved.
+ */
+ public function testGetPid()
+ {
+ parent::testGetPid();
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\RuntimeException
+ * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. The process identifier can not be retrieved.
+ */
+ public function testGetPidIsNullBeforeStart()
+ {
+ parent::testGetPidIsNullBeforeStart();
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\RuntimeException
+ * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. The process identifier can not be retrieved.
+ */
+ public function testGetPidIsNullAfterRun()
+ {
+ parent::testGetPidIsNullAfterRun();
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\RuntimeException
+ * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.
+ */
+ public function testExitCodeText()
+ {
+ $process = $this->getProcess('qdfsmfkqsdfmqmsd');
+ $process->run();
+
+ $process->getExitCodeText();
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\RuntimeException
+ * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.
+ */
+ public function testExitCodeTextIsNullWhenExitCodeIsNull()
+ {
+ parent::testExitCodeTextIsNullWhenExitCodeIsNull();
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\RuntimeException
+ * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.
+ */
+ public function testIsSuccessful()
+ {
+ parent::testIsSuccessful();
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\RuntimeException
+ * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.
+ */
+ public function testIsSuccessfulOnlyAfterTerminated()
+ {
+ parent::testIsSuccessfulOnlyAfterTerminated();
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\RuntimeException
+ * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.
+ */
+ public function testIsNotSuccessful()
+ {
+ parent::testIsNotSuccessful();
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\RuntimeException
+ * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.
+ */
+ public function testTTYCommandExitCode()
+ {
+ parent::testTTYCommandExitCode();
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\RuntimeException
+ * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. The process can not be signaled.
+ */
+ public function testSignal()
+ {
+ parent::testSignal();
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\RuntimeException
+ * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.
+ */
+ public function testProcessWithoutTermSignalIsNotSignaled()
+ {
+ parent::testProcessWithoutTermSignalIsNotSignaled();
+ }
+
+ public function testStopWithTimeoutIsActuallyWorking()
+ {
+ $this->markTestSkipped('Stopping with signal is not supported in sigchild environment');
+ }
+
+ public function testProcessThrowsExceptionWhenExternallySignaled()
+ {
+ $this->markTestSkipped('Retrieving Pid is not supported in sigchild environment');
+ }
+
+ public function testExitCodeIsAvailableAfterSignal()
+ {
+ $this->markTestSkipped('Signal is not supported in sigchild environment');
+ }
+
+ public function testRunProcessWithTimeout()
+ {
+ $this->markTestSkipped('Signal (required for timeout) is not supported in sigchild environment');
+ }
+
+ public function provideStartMethods()
+ {
+ return array(
+ array('start', 'Symfony\Component\Process\Exception\LogicException', 'Output has been disabled, enable it to allow the use of a callback.'),
+ array('run', 'Symfony\Component\Process\Exception\LogicException', 'Output has been disabled, enable it to allow the use of a callback.'),
+ array('mustRun', 'Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.'),
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getProcess($commandline, $cwd = null, array $env = null, $input = null, $timeout = 60, array $options = array())
+ {
+ $process = new ProcessInSigchildEnvironment($commandline, $cwd, $env, $input, $timeout, $options);
+ $process->setEnhanceSigchildCompatibility(false);
+
+ return $process;
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/SigchildEnabledProcessTest.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/SigchildEnabledProcessTest.php
new file mode 100644
index 000000000..2668a9b4b
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/SigchildEnabledProcessTest.php
@@ -0,0 +1,148 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Tests;
+
+class SigchildEnabledProcessTest extends AbstractProcessTest
+{
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\RuntimeException
+ * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.
+ */
+ public function testProcessIsSignaledIfStopped()
+ {
+ parent::testProcessIsSignaledIfStopped();
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\RuntimeException
+ * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.
+ */
+ public function testProcessWithTermSignal()
+ {
+ parent::testProcessWithTermSignal();
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\RuntimeException
+ * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.
+ */
+ public function testProcessIsNotSignaled()
+ {
+ parent::testProcessIsNotSignaled();
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\RuntimeException
+ * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.
+ */
+ public function testProcessWithoutTermSignal()
+ {
+ parent::testProcessWithoutTermSignal();
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\RuntimeException
+ * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. The process identifier can not be retrieved.
+ */
+ public function testGetPid()
+ {
+ parent::testGetPid();
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\RuntimeException
+ * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. The process identifier can not be retrieved.
+ */
+ public function testGetPidIsNullBeforeStart()
+ {
+ parent::testGetPidIsNullBeforeStart();
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\RuntimeException
+ * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. The process identifier can not be retrieved.
+ */
+ public function testGetPidIsNullAfterRun()
+ {
+ parent::testGetPidIsNullAfterRun();
+ }
+
+ public function testExitCodeText()
+ {
+ $process = $this->getProcess('qdfsmfkqsdfmqmsd');
+ $process->run();
+
+ $this->assertInternalType('string', $process->getExitCodeText());
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\RuntimeException
+ * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. The process can not be signaled.
+ */
+ public function testSignal()
+ {
+ parent::testSignal();
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\RuntimeException
+ * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.
+ */
+ public function testProcessWithoutTermSignalIsNotSignaled()
+ {
+ parent::testProcessWithoutTermSignalIsNotSignaled();
+ }
+
+ public function testProcessThrowsExceptionWhenExternallySignaled()
+ {
+ $this->markTestSkipped('Retrieving Pid is not supported in sigchild environment');
+ }
+
+ public function testExitCodeIsAvailableAfterSignal()
+ {
+ $this->markTestSkipped('Signal is not supported in sigchild environment');
+ }
+
+ public function testStartAfterATimeout()
+ {
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Restarting a timed-out process on Windows is not supported in sigchild environment');
+ }
+ parent::testStartAfterATimeout();
+ }
+
+ public function testStopWithTimeoutIsActuallyWorking()
+ {
+ $this->markTestSkipped('Stopping with signal is not supported in sigchild environment');
+ }
+
+ public function testRunProcessWithTimeout()
+ {
+ $this->markTestSkipped('Signal (required for timeout) is not supported in sigchild environment');
+ }
+
+ public function testCheckTimeoutOnStartedProcess()
+ {
+ $this->markTestSkipped('Signal (required for timeout) is not supported in sigchild environment');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getProcess($commandline, $cwd = null, array $env = null, $input = null, $timeout = 60, array $options = array())
+ {
+ $process = new ProcessInSigchildEnvironment($commandline, $cwd, $env, $input, $timeout, $options);
+ $process->setEnhanceSigchildCompatibility(true);
+
+ return $process;
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/SignalListener.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/SignalListener.php
new file mode 100644
index 000000000..4206550f5
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/SignalListener.php
@@ -0,0 +1,25 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+// required for signal handling
+declare (ticks = 1);
+
+pcntl_signal(SIGUSR1, function () {echo 'Caught SIGUSR1'; exit;});
+
+$n = 0;
+
+// ticks require activity to work - sleep(4); does not work
+while ($n < 400) {
+ usleep(10000);
+ ++$n;
+}
+
+return;
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/SimpleProcessTest.php b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/SimpleProcessTest.php
new file mode 100644
index 000000000..78f20eb10
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/Tests/SimpleProcessTest.php
@@ -0,0 +1,216 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Tests;
+
+use Symfony\Component\Process\Process;
+
+class SimpleProcessTest extends AbstractProcessTest
+{
+ private $enabledSigchild = false;
+
+ protected function setUp()
+ {
+ ob_start();
+ phpinfo(INFO_GENERAL);
+
+ $this->enabledSigchild = false !== strpos(ob_get_clean(), '--enable-sigchild');
+ }
+
+ public function testGetExitCode()
+ {
+ $this->skipIfPHPSigchild(); // This test use exitcode that is not available in this case
+ parent::testGetExitCode();
+ }
+
+ public function testExitCodeCommandFailed()
+ {
+ $this->skipIfPHPSigchild(); // This test use exitcode that is not available in this case
+ parent::testExitCodeCommandFailed();
+ }
+
+ public function testProcessIsSignaledIfStopped()
+ {
+ $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved');
+ parent::testProcessIsSignaledIfStopped();
+ }
+
+ public function testProcessWithTermSignal()
+ {
+ $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved');
+ parent::testProcessWithTermSignal();
+ }
+
+ public function testProcessIsNotSignaled()
+ {
+ $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved');
+ parent::testProcessIsNotSignaled();
+ }
+
+ public function testProcessWithoutTermSignal()
+ {
+ $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved');
+ parent::testProcessWithoutTermSignal();
+ }
+
+ public function testExitCodeText()
+ {
+ $this->skipIfPHPSigchild(); // This test use exitcode that is not available in this case
+ parent::testExitCodeText();
+ }
+
+ public function testIsSuccessful()
+ {
+ $this->skipIfPHPSigchild(); // This test use PID that is not available in this case
+ parent::testIsSuccessful();
+ }
+
+ public function testIsNotSuccessful()
+ {
+ $this->skipIfPHPSigchild(); // This test use PID that is not available in this case
+ parent::testIsNotSuccessful();
+ }
+
+ public function testGetPid()
+ {
+ $this->skipIfPHPSigchild(); // This test use PID that is not available in this case
+ parent::testGetPid();
+ }
+
+ public function testGetPidIsNullBeforeStart()
+ {
+ $this->skipIfPHPSigchild(); // This test use PID that is not available in this case
+ parent::testGetPidIsNullBeforeStart();
+ }
+
+ public function testGetPidIsNullAfterRun()
+ {
+ $this->skipIfPHPSigchild(); // This test use PID that is not available in this case
+ parent::testGetPidIsNullAfterRun();
+ }
+
+ public function testSignal()
+ {
+ $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. The process can not be signaled.');
+ parent::testSignal();
+ }
+
+ public function testProcessWithoutTermSignalIsNotSignaled()
+ {
+ $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved');
+ parent::testProcessWithoutTermSignalIsNotSignaled();
+ }
+
+ public function testProcessThrowsExceptionWhenExternallySignaled()
+ {
+ $this->skipIfPHPSigchild(); // This test use PID that is not available in this case
+ parent::testProcessThrowsExceptionWhenExternallySignaled();
+ }
+
+ public function testExitCodeIsAvailableAfterSignal()
+ {
+ $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. The process can not be signaled.');
+ parent::testExitCodeIsAvailableAfterSignal();
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\LogicException
+ * @expectedExceptionMessage Can not send signal on a non running process.
+ */
+ public function testSignalProcessNotRunning()
+ {
+ parent::testSignalProcessNotRunning();
+ }
+
+ public function testSignalWithWrongIntSignal()
+ {
+ if ($this->enabledSigchild) {
+ $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. The process can not be signaled.');
+ } else {
+ $this->setExpectedException('Symfony\Component\Process\Exception\RuntimeException', 'Error while sending signal `-4`.');
+ }
+ parent::testSignalWithWrongIntSignal();
+ }
+
+ public function testSignalWithWrongNonIntSignal()
+ {
+ if ($this->enabledSigchild) {
+ $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. The process can not be signaled.');
+ } else {
+ $this->setExpectedException('Symfony\Component\Process\Exception\RuntimeException', 'Error while sending signal `Céphalopodes`.');
+ }
+ parent::testSignalWithWrongNonIntSignal();
+ }
+
+ public function testStopTerminatesProcessCleanly()
+ {
+ $process = $this->getProcess(self::$phpBin.' -r "echo \'foo\'; sleep(1); echo \'bar\';"');
+ $process->run(function () use ($process) {
+ $process->stop();
+ });
+ $this->assertTrue(true, 'A call to stop() is not expected to cause wait() to throw a RuntimeException');
+ }
+
+ public function testKillSignalTerminatesProcessCleanly()
+ {
+ $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. The process can not be signaled.');
+
+ $process = $this->getProcess(self::$phpBin.' -r "echo \'foo\'; sleep(1); echo \'bar\';"');
+ $process->run(function () use ($process) {
+ if ($process->isRunning()) {
+ $process->signal(defined('SIGKILL') ? SIGKILL : 9);
+ }
+ });
+ $this->assertTrue(true, 'A call to signal() is not expected to cause wait() to throw a RuntimeException');
+ }
+
+ public function testTermSignalTerminatesProcessCleanly()
+ {
+ $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. The process can not be signaled.');
+
+ $process = $this->getProcess(self::$phpBin.' -r "echo \'foo\'; sleep(1); echo \'bar\';"');
+ $process->run(function () use ($process) {
+ if ($process->isRunning()) {
+ $process->signal(defined('SIGTERM') ? SIGTERM : 15);
+ }
+ });
+ $this->assertTrue(true, 'A call to signal() is not expected to cause wait() to throw a RuntimeException');
+ }
+
+ public function testStopWithTimeoutIsActuallyWorking()
+ {
+ $this->skipIfPHPSigchild();
+
+ parent::testStopWithTimeoutIsActuallyWorking();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getProcess($commandline, $cwd = null, array $env = null, $input = null, $timeout = 60, array $options = array())
+ {
+ return new Process($commandline, $cwd, $env, $input, $timeout, $options);
+ }
+
+ private function skipIfPHPSigchild()
+ {
+ if ($this->enabledSigchild) {
+ $this->markTestSkipped('Your PHP has been compiled with --enable-sigchild, this test can not be executed');
+ }
+ }
+
+ private function expectExceptionIfPHPSigchild($classname, $message)
+ {
+ if ($this->enabledSigchild) {
+ $this->setExpectedException($classname, $message);
+ }
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/composer.json b/modules/devshop/devshop_projects/drush/vendor/symfony/process/composer.json
new file mode 100644
index 000000000..b3cb5186f
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/composer.json
@@ -0,0 +1,33 @@
+{
+ "name": "symfony/process",
+ "type": "library",
+ "description": "Symfony Process Component",
+ "keywords": [],
+ "homepage": "https://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.9"
+ },
+ "autoload": {
+ "psr-4": { "Symfony\\Component\\Process\\": "" },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "minimum-stability": "dev",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.8-dev"
+ }
+ }
+}
diff --git a/modules/devshop/devshop_projects/drush/vendor/symfony/process/phpunit.xml.dist b/modules/devshop/devshop_projects/drush/vendor/symfony/process/phpunit.xml.dist
new file mode 100644
index 000000000..788500084
--- /dev/null
+++ b/modules/devshop/devshop_projects/drush/vendor/symfony/process/phpunit.xml.dist
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+ ./Tests/
+
+
+
+
+
+ ./
+
+ ./Tests
+ ./vendor
+
+
+
+
diff --git a/modules/devshop/devshop_projects/images/loader.gif b/modules/devshop/devshop_projects/images/loader.gif
new file mode 100644
index 000000000..5b33f7e54
Binary files /dev/null and b/modules/devshop/devshop_projects/images/loader.gif differ
diff --git a/modules/devshop/devshop_projects/images/loading.gif b/modules/devshop/devshop_projects/images/loading.gif
new file mode 100644
index 000000000..bd6dd9741
Binary files /dev/null and b/modules/devshop/devshop_projects/images/loading.gif differ
diff --git a/modules/devshop/devshop_projects/images/spinner-white.gif b/modules/devshop/devshop_projects/images/spinner-white.gif
new file mode 100644
index 000000000..f27d7cd48
Binary files /dev/null and b/modules/devshop/devshop_projects/images/spinner-white.gif differ
diff --git a/modules/devshop/devshop_projects/inc/add-environment-options.tpl.php b/modules/devshop/devshop_projects/inc/add-environment-options.tpl.php
new file mode 100644
index 000000000..e69de29bb
diff --git a/modules/devshop/devshop_projects/inc/admin.inc b/modules/devshop/devshop_projects/inc/admin.inc
new file mode 100644
index 000000000..54f0120e0
--- /dev/null
+++ b/modules/devshop/devshop_projects/inc/admin.inc
@@ -0,0 +1,88 @@
+ 'fieldset',
+ '#title' => t('Paths'),
+ );
+ $form['paths']['devshop_projects_allow_custom_code_path'] = array(
+ '#title' => t('Allow custom code path per project.'),
+ '#description' => t('Allow each project to have a custom "Code Path". If not checked, project paths are set as "/var/aegir/projects/{PROJECT_NAME}.'),
+ '#type' => 'checkbox',
+ '#default_value' => variable_get('devshop_projects_allow_custom_code_path', FALSE),
+ );
+ $form['paths']['devshop_project_base_path'] = array(
+ '#title' => t('Projects Base Path'),
+ '#type' => 'textfield',
+ '#description' => t('The default base path that all projects will be created in. Projects each get their own folder inside this path.'),
+ '#default_value' => variable_get('devshop_project_base_path', '/var/aegir/projects'),
+ );
+ $form['paths']['devshop_project_default_drupal_path'] = array(
+ '#title' => t('Default path to Drupal'),
+ '#type' => 'textfield',
+ '#description' => t("If index.php isn't in the root of the git repo, you can edit the 'Path to Drupal' setting on each project. Set a default 'Path to Drupal' here. (For example, an Acquia hosted repo uses 'docroot'.)"),
+ '#default_value' => variable_get('devshop_project_default_drupal_path', ''),
+ );
+
+
+ $form['urls'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('URLs'),
+ );
+
+ $form['urls']['devshop_projects_allow_custom_base_url'] = array(
+ '#title' => t('Allow custom Environment Domain Name Pattern per project.'),
+ '#description' => t('If enabled each project can set an Environment URL Pattern.'),
+ '#type' => 'checkbox',
+ '#default_value' => variable_get('devshop_projects_allow_custom_base_url', FALSE),
+ );
+ $form['urls']['devshop_project_environment_url_pattern'] = array(
+ '#title' => t('Environment Domain Name Pattern'),
+ '#type' => 'textfield',
+ '#description' => t("Each environment will have a system domain name generated for it based on it's name. Use @project for project name, @hostname for '%host', @environment for the environment's name.", array('%host' => $_SERVER['SERVER_NAME'])),
+ '#default_value' => variable_get('devshop_project_environment_url_pattern', '@project.@environment.@hostname'),
+ '#element_validate' => array(
+ 'devshop_project_settings_validate_environment_url_pattern',
+ ),
+ );
+
+// $form['support'] = array(
+// '#type' => 'fieldset',
+// '#title' => t('DevShop Support'),
+// );
+// $form['support']['devshop_support_widget_enable'] = array(
+// '#title' => t('Show Help Widget'),
+// '#description' => t('Uncheck this box if you want to hide the Help widget that appears at the bottom right of the page.'),
+// '#type' => 'checkbox',
+// '#default_value' => variable_get('devshop_support_widget_enable', TRUE),
+// );
+
+ // Server settings.
+ $form['server'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('DevShop Server'),
+ );
+ $form['server']['devshop_public_key'] = array(
+ '#title' => t('Aegir User Public Key'),
+ '#description' => t('The public key of the aegir user on this server. If you change the SSH keys located at /var/aegir/.ssh/id_rsa.pub, then you should update this variable. This variable is for user reference only. If using GitHub API, then devshop will check to make sure the server has access using this public key.'),
+ '#type' => 'textarea',
+ '#default_value' => variable_get('devshop_public_key', ''),
+ );
+ return system_settings_form($form);
+}
+
+/**
+ * Validate that the default environment domain pattern has @environment and @project.
+ */
+function devshop_project_settings_validate_environment_url_pattern($element, &$form_state, $form) {
+ if (strpos($form_state['values']['devshop_project_environment_url_pattern'], '@environment') === FALSE || strpos($form_state['values']['devshop_project_environment_url_pattern'], '@project') === FALSE) {
+ form_error($element, t('The placeholders @project and @environment must be in the Environment Domain Name Pattern.'));
+ }
+}
\ No newline at end of file
diff --git a/modules/devshop/devshop_projects/inc/create-wizard.inc b/modules/devshop/devshop_projects/inc/create-wizard.inc
new file mode 100644
index 000000000..a4abe2daf
--- /dev/null
+++ b/modules/devshop/devshop_projects/inc/create-wizard.inc
@@ -0,0 +1,2 @@
+ NULL,
+ );
+
+ // Setup project
+ $project = ctools_object_cache_get('project', 'devshop_project');
+
+ // Setup Step.
+ if ($step == NULL) {
+ drupal_goto('projects/add/' . current(array_keys($form_info['order'])));
+ }
+
+ // Create default project object
+ if (empty($project) || !isset($project->git_url)) {
+ // set form to first step -- we have no data
+ $step = current(array_keys($form_info['order']));
+ $project = new stdClass();
+ $project->step = $step;
+ $project->git_url = '';
+ $project->nid = NULL;
+ $project->name = '';
+
+ // These are empty until we have our project name
+ $project->code_path = '';
+ $project->base_url = '';
+
+ // ** set the storage object so its ready for whatever comes next
+ ctools_object_cache_set('project', 'devshop_project', $project);
+ }
+ else {
+ // Load latest project object from database and use that for the ctools cache.
+ $project_node = node_load($project->nid);
+ $project = $project_node->project;
+
+ // If we have a name but no code_path or base URL.
+ if (!empty($project->name) && empty($project->code_path)) {
+ $project->code_path = variable_get('devshop_project_base_path', '/var/aegir/projects') . '/' . $project->name;
+ }
+ if (!empty($project->name) && empty($project->base_url)) {
+ $project->base_url = variable_get('devshop_project_environment_url_pattern', "@project.@environment.@hostname");
+ }
+
+ // Save the current step
+ $project->step = $step;
+ ctools_object_cache_set('project', 'devshop_project', $project);
+ }
+
+ // Check verification status
+ if (!empty($project->nid)) {
+ $tasks = hosting_task_fetch_tasks($project->nid);
+ }
+ if (isset($tasks['verify']['nid'])) {
+ // Get "verify" task status for the project
+ $project->verify_task_status = isset($tasks['verify']['task_status']) ? $tasks['verify']['task_status'] : HOSTING_TASK_ERROR;
+ $project->verify_task_nid = $tasks['verify']['nid'];
+ }
+ else {
+ $project->verify_task_status = NULL;
+ $project->verify_task_nid = NULL;
+ }
+
+ // If project verification failed, we need to ask for a new git url.
+ if ($project->verify_task_status == HOSTING_TASK_ERROR && !empty($project_node->nid)) {
+ $messages = db_query("SELECT * FROM {hosting_task_log} WHERE nid = :nid AND type LIKE :type ORDER BY vid, lid", array(':nid' => $project->verify_task_nid, ':type' => 'devshop%'));
+
+ foreach ($messages as $message) {
+ if ($message->type == 'devshop_info') {
+ $messages_to_user[] = $message->message;
+ }
+ }
+ $project->verify_error = theme('devshop_ascii', array('output' => implode('\n', $messages_to_user)));
+
+
+ // If not on the first step, go to it.
+ if ($step != current(array_keys($form_info['order']))) {
+ drupal_goto('projects/add/' . current(array_keys($form_info['order'])));
+ }
+ }
+ else {
+ $project->verify_error = NULL;
+ }
+
+ // All forms can access $form_state['project'];
+ $form_state['project'] = $project;
+
+ // Saving the last visited step for redirects from node
+ $_SESSION['last_step'] = $step;
+
+ // Generate our ctools form and output
+ $output = ctools_wizard_multistep_form($form_info, $step, $form_state);
+ return $output;
+}
+
+/**
+ * The form_info for the ctools wizard
+ */
+function devshop_projects_create_wizard_info() {
+ return array(
+ 'id' => 'devshop_project_create',
+ 'path' => "projects/add/%step",
+ 'show trail' => TRUE,
+ 'show back' => TRUE,
+ 'show cancel' => TRUE,
+ 'show return' => FALSE,
+ 'next text' => 'Next',
+ 'next callback' => 'devshop_projects_create_wizard_next',
+ 'finish callback' => 'devshop_projects_create_wizard_finish',
+ 'cancel callback' => 'devshop_projects_create_wizard_cancel',
+ 'order' => array(
+ 'git_url' => t('Step 1: Source'),
+ 'settings' => t('Step 2: Settings'),
+ 'environments' => t('Step 3: Environments'),
+ 'sites' => t('Step 4: Install Profile'),
+ ),
+ 'forms' => array(
+ 'git_url' => array(
+ 'form id' => 'devshop_project_create_step_git'
+ ),
+ 'settings' => array(
+ 'form id' => 'devshop_project_create_step_settings'
+ ),
+ 'environments' => array(
+ 'form id' => 'devshop_project_create_step_environments'
+ ),
+ 'sites' => array(
+ 'form id' => 'devshop_project_create_step_sites'
+ ),
+ ),
+ );
+}
+
+/**
+ * WIZARD TOOLS
+ */
+
+
+/**
+ * NEXT callback
+ * Saves anything in $form_state['project'] to ctools cache.
+ *
+ * The form submit callbacks are responsible for putting data into
+ * $form_state['project'].
+ */
+function devshop_projects_create_wizard_next(&$form_state) {
+ $project = &$form_state['project'];
+ $cache = ctools_object_cache_set('project', 'devshop_project', $project);
+}
+
+
+
+
+/**
+ * CANCEL callback
+ * Callback generated when the 'cancel' button is clicked.
+ * Remove the project data cache and send back to projects page.
+ */
+function devshop_projects_create_wizard_cancel(&$form_state) {
+ // Update the cache with changes.
+ $project = &$form_state['project'];
+ ctools_object_cache_clear('project', 'devshop_project');
+
+ // Redirect to projects list
+ $form_state['redirect'] = 'projects';
+
+ // If we have a project node, create a "delete" hosting task
+ if (!empty($project->nid)) {
+ hosting_add_task($project->nid, 'delete');
+ }
+
+ // Message
+ drupal_set_message(t('Project creation cancelled.'));
+
+ // Removing last step session variable.
+ unset($_SESSION['last_step']);
+
+}
+
+/**
+ * FINISH callback
+ * Callback generated when the add page process is finished.
+ */
+function devshop_projects_create_wizard_finish(&$form_state) {
+
+ $project = &$form_state['project'];
+ $project_node = node_load($project->nid);
+
+ // Save the extra options to the project node.
+ $project_node->project->install_profile = $form_state['values']['install_profile'];
+
+ // Create the site nodes, saving to the environment.
+ // @TODO: Can we speed things up here by only running install for the first,
+ // then "Cloning" to create the rest?
+ foreach ($project_node->project->environments as $environment_name => &$environment) {
+ // @TODO: Does this set the http_server as well?? Doesn't look like it.
+ $db_server = $environment->db_server_nid;
+ $site_node = devshop_projects_create_site($project_node->project, node_load($environment->platform), $environment_name, $db_server);
+ $environment->site = $site_node->nid;
+ }
+
+ // Set to not verify and to publish.
+ $project_node->no_verify = TRUE;
+ $project_node->status = 1;
+ node_save($project_node);
+
+ ctools_object_cache_clear('project', 'devshop_project');
+ $form_state['redirect'] = 'node/' . $project_node->nid;
+
+ // Removing last step session variable.
+ unset($_SESSION['last_step']);
+
+ // Friendly message
+ drupal_get_messages('status');
+ drupal_set_message(t('Your project has been created. Your sites are being installed.'));
+}
+
+/**
+ * Returns JSON showing the state of the project
+ */
+function devshop_projects_add_status($type = 'platform') {
+ $return = array();
+
+ // Get Project Cache
+ ctools_include('wizard');
+ ctools_include('object-cache');
+ $project = ctools_object_cache_get('project', 'devshop_project');
+
+
+ $project_node = node_load($project->nid);
+
+ $all_tasks_completed = TRUE;
+ $nids = array();
+
+ // When checking project...
+ if ($type == 'project') {
+ $nids = array($project_node->nid);
+ }
+
+ // When checking platforms...
+ if ($type == 'platform') {
+ foreach ($project_node->project->environments as $name => $environment) {
+ $nids[] = $environment->platform;
+ }
+ }
+
+ // Check verification task for all nids
+ foreach ($nids as $nid) {
+ $platform = node_load($nid);
+ $profiles_shortnames = hosting_get_profiles($platform->nid, 'short_name');
+ if (is_array($profiles_shortnames) && !empty($profiles_shortnames)) {
+ $install_profiles = array_combine($profiles_shortnames, (array) hosting_get_profiles($platform->nid));
+ }
+ else {
+ $install_profiles = array();
+ }
+
+ $status = _hosting_parse_error_code($platform->environment->last_task_node->task_status);
+ if ($status == 'Queued') {
+ $status = '...';
+ }
+ $return['tasks'][$nid] = array(
+ 'status' => $status,
+ 'version' => $platform->release->version,
+ 'profiles' => implode(', ', $install_profiles),
+ );
+ // If task is not completed, mark all tasks not complete.
+ if ($platform->environment->last_task_node->task_status == HOSTING_TASK_SUCCESS || $platform->environment->last_task_node->task_status == HOSTING_TASK_ERROR || $platform->environment->last_task_node->task_status == HOSTING_TASK_WARNING) {
+ continue;
+ }
+ else {
+ $all_tasks_completed = FALSE;
+ }
+
+ }
+ $return['tasks_complete'] = $all_tasks_completed;
+ drupal_json_output($return);
+ exit;
+}
+
+/**
+ * Helper for adding reloading feature to form
+ */
+function devshop_form_reloader(&$form, $type = 'platform') {
+ // Add JS that reloads the page when tasks finish.
+ $form['item'] = array(
+ '#type' => 'item',
+ '#value' => '',
+ '#weight' => 10
+ );
+ $settings['devshopReload'] = array(
+ 'type' => $type,
+ 'delay' => 1000,
+ );
+
+ drupal_add_js($settings, array('type' => 'setting'));
+ drupal_add_js(drupal_get_path('module', 'devshop_projects') . '/inc/reload.js');
+}
diff --git a/modules/devshop/devshop_projects/inc/create/create.js b/modules/devshop/devshop_projects/inc/create/create.js
new file mode 100644
index 000000000..96a0ce5bd
--- /dev/null
+++ b/modules/devshop/devshop_projects/inc/create/create.js
@@ -0,0 +1,50 @@
+/**
+ * Start New Project:
+ *
+ * Step 1: Takes the entered name and creates a base url and code path from it.
+ */
+(function ($) {
+ Drupal.behaviors.createStep1 = {
+ attach: function (context, settings) {
+
+ $( "#edit-title" ).keyup(function(event) {
+
+ // Extract project name and base path and URL
+ var projectName = $(this).val();
+ var base_path = $('#edit-code-path').attr('data-base_path') + '/' + projectName;
+ var base_url = projectName + '.' + $('#edit-base-url').attr('data-base_url');
+
+ $('#edit-code-path').val(base_path);
+ $('#edit-base-url').val(base_url);
+
+ });
+
+ }
+ };
+}(jQuery));
+
+/**
+ * Step 2: Settings
+ */
+(function ($) {
+ Drupal.behaviors.createStep2 = {
+ attach: function (context, settings) {
+
+ // Hide unless
+ $('#edit-project-settings-live-live-domain-www-wrapper').hide();
+ $('#edit-project-settings-live-environment-aliases-wrapper').hide();
+
+ $('#edit-project-settings-live-live-domain').keyup(function(){
+ if ($(this).val()){
+ $('#edit-project-settings-live-live-domain-www-wrapper').show();
+ $('#edit-project-settings-live-environment-aliases-wrapper').show();
+ }
+ else {
+ $('#edit-project-settings-live-live-domain-www-wrapper').hide();
+ $('#edit-project-settings-live-environment-aliases-wrapper').hide();
+ }
+ });
+
+ }
+ };
+}(jQuery));
diff --git a/modules/devshop/devshop_projects/inc/create/step-1.inc b/modules/devshop/devshop_projects/inc/create/step-1.inc
new file mode 100644
index 000000000..2528e49a5
--- /dev/null
+++ b/modules/devshop/devshop_projects/inc/create/step-1.inc
@@ -0,0 +1,242 @@
+verify_error) {
+ $form['note'] = array(
+ '#markup' => t('We were unable to connect to your git repository. Check the messages, edit your settings, and try again.'),
+ '#prefix' => '
',
+ '#suffix' => '
',
+ );
+ $form['error'] = array(
+ '#markup' => $project->verify_error,
+ );
+
+ // Check for "host key"
+ if (strpos($project->verify_error, 'Host key verification failed')) {
+ $form['help'] = array(
+ '#markup' => t('Looks like you need to authorize this host. SSH into the server as aegir user and run the command git ls-remote !repo. Add StrictHostKeyChecking no to your ~/.ssh/config file to avoid this for all domains in the future.', array(
+ '!repo' => $project->git_url,
+ )),
+ '#prefix' => '
' . t('Choose a unique name for your project. Only letters and numbers are allowed.') . '
',
+ '#size' => 40,
+ '#maxlength' => 255,
+ );
+ }
+ else {
+ $form['title'] = array(
+ '#type' => 'value',
+ '#value' => $project->name,
+ );
+ $form['title_display'] = array(
+ '#type' => 'item',
+ '#title' => t('Project Code Name'),
+ '#markup' => $project->name,
+ );
+ }
+
+ $username = variable_get('aegir_user', 'aegir');
+
+ $tips[] = t('Use the "ssh" url whenever possible.');
+ $tips[] = t('Code will be cloned to the server using the aegir user. Check the "Repository Access" for SSH information.');
+ $tips[] = t('An entire Drupal codebase must be present in the repository, but does not have to be in the root. See Path to Drupal setting on the next page.');
+ $tips[] = t('If a composer.json file is found in the root of the project, composer install will be run automatically.');
+ $tips = theme('item_list', array('items' => $tips));
+
+ $form['git_url'] = array(
+ '#type' => 'textfield',
+ '#required' => 1,
+ '#title' => t('Git URL'),
+ '#suffix' => '
' . t('Enter the Git URL for your project') . '
' . $tips,
+ '#default_value' => $project->git_url,
+ '#maxlength' => 1024,
+ );
+//
+// // Project code path.
+// $form['code_path'] = array(
+// '#type' => variable_get('devshop_projects_allow_custom_code_path', FALSE) ? 'textfield' : 'value',
+// '#title' => t('Code path'),
+// '#description' => t('The absolute path on the filesystem that will be used to create all platforms within this project. There must not be a file or directory at this path.'),
+// '#required' => variable_get('devshop_projects_allow_custom_code_path', FALSE),
+// '#size' => 40,
+// '#default_value' => $project->code_path,
+// '#maxlength' => 255,
+// '#attributes' => array(
+// 'data-base_path' => variable_get('devshop_project_base_path', '/var/aegir/projects'),
+// ),
+// );
+//
+// // Project base url
+// $form['base_url'] = array(
+// '#type' => variable_get('devshop_projects_allow_custom_base_url', FALSE) ? 'textfield' : 'value',
+// '#title' => t('Base URL'),
+// '#description' => t('All sites will be under a subdomain of this domain.'),
+// '#required' => variable_get('devshop_projects_allow_custom_base_url', FALSE),
+// '#size' => 40,
+// '#default_value' => $project->base_url,
+// '#maxlength' => 255,
+// '#attributes' => array(
+// 'data-base_url' => devshop_projects_url($project->name, ''),
+// ),
+// );
+
+ // Display helpful tips for connecting.
+ $pubkey = variable_get('devshop_public_key', '');
+
+ // If we don't yet have the server's public key saved as a variable...
+ if (empty($pubkey)) {
+ $output = t("This DevShop doesn't yet know your server's public SSH key. To import it, run the following command on your server as aegir user:");
+ $command = 'drush @hostmaster vset devshop_public_key "$(cat ~/.ssh/id_rsa.pub)" --yes';
+ $output .= "";
+ }
+ else {
+ // @TODO: Make this Translatable
+ $output = <<If you haven't granted this server access to your Git repository, you should do so now using it's public SSH key.
+
+HTML;
+ }
+
+ // Add info about connecting to Repo
+ $form['connect'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Repository Access'),
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ );
+ $form['connect']['public_key'] = array(
+ '#markup' => $output,
+ );
+ return $form;
+}
+
+/**
+ * STEP 1: Validate
+ */
+function devshop_project_create_step_git_validate(&$form, &$form_state) {
+ $project = &$form_state['project'];
+
+ if (empty($project->nid)) {
+
+ // PROJECT NAME
+ // No spaces or special characters allowed.
+ $project_name = strtolower(trim($form_state['values']['title'])); // domain names are case-insensitive
+
+ // Set the value to our trimmed and lowercased proejct name.
+ form_set_value($form['title'], $project_name, $form_state);
+
+ // Check for bad characters.
+ if (!ctype_alnum($project_name)) {
+ form_set_error('title', t("You have not specified a valid project codename. Only numbers and letters are allowed."));
+ }
+
+ // Check for duplicate project name here.
+ // hosting_context_load() only works if a node exists with that ID.
+ if (db_query("SELECT nid FROM {hosting_context} WHERE name = :name", array(':name' => 'project_' . $project_name))->fetch()) {
+ form_set_error('title', t('The name @project is in use. Please try again.', array(
+ '@project' => $project_name,
+ )));
+ }
+ }
+
+//
+// // CODE PATH & BASE URL
+// // Code path and base url must be unique
+// if (db_query('SELECT n.nid FROM {hosting_devshop_project} d LEFT JOIN {node} n ON d.nid = n.nid WHERE status = :status AND code_path = :code_path;', array(':status' => 1, ':code_path' => $form_state['values']['code_path']))->fetchField()) {
+// form_set_error('code_path', t('Another project already has this code path. Code path must be unique.'));
+// }
+// if (db_query('SELECT n.nid FROM {hosting_devshop_project} d LEFT JOIN {node} n ON d.nid = n.nid WHERE status = :status AND base_url = :base_url;', array(':status' => 1, ':base_url' => $form_state['values']['base_url']))->fetchField()) {
+// form_set_error('base_url', t('Another project already has this base url. Base URL must be unique.'));
+// }
+//
+// // If custom code paths are not allowed, set the value here using the project name.
+// if (!variable_get('devshop_projects_allow_custom_code_path', FALSE)) {
+// form_set_value($form['code_path'], variable_get('devshop_project_base_path', '/var/aegir/projects') . '/' . $project_name, $form_state);
+// }
+//
+// // If custom base URLs are not allowed, set the value here using the project name.
+// if (!variable_get('devshop_projects_allow_custom_base_url', FALSE)) {
+// form_set_value($form['base_url'], devshop_projects_url($project_name), $form_state);
+// }
+}
+
+/**
+ * STEP 1: Submit
+ */
+function devshop_project_create_step_git_submit(&$from, &$form_state) {
+ global $user;
+
+ $project = &$form_state['project'];
+
+ // If the project already exists, this means the git url has changed...
+ if ($project->nid) {
+ // Change the git url and save the node. Verification will run again.
+ $node = node_load($project->nid);
+ $node->project->git_url = $form_state['values']['git_url'];
+ $node->project->code_path = $form_state['values']['code_path'];
+ $node->project->base_url = $form_state['values']['base_url'];
+ node_save($node);
+ }
+ // Create new project node
+ elseif (empty($project->nid)) {
+ // Create the project node now. We will update it with the chosen path.
+ // This is so we can check for branches and save the hosting_context as soon
+ // as possible.
+ $node = new stdClass;
+ $node->title = $form_state['values']['title'];
+
+ $node->type = 'project';
+ $node->status = 0;
+ $node->uid = $user->uid;
+ $node->name = $user->name;
+
+ // Create project object
+ $project->name = $form_state['values']['title'];
+ $project->git_url = $form_state['values']['git_url'];
+ $project->code_path = $form_state['values']['code_path'];
+ $project->base_url = $form_state['values']['base_url'];
+
+ // @TODO: We will clone the code for step 2 and look for drupal.
+ $project->drupal_path = variable_get('devshop_projects_default_drupal_path', '');
+
+ // Attach to node
+ $node->project = $project;
+
+ // Save project node, triggering verification.
+ if ($node = node_submit($node)) {
+ node_save($node);
+ }
+
+ // Save NID to ctools object cache.
+ if ($node->nid) {
+ $project->nid = $node->nid;
+ }
+ }
+
+ // Remove default "task" messages.
+ drupal_get_messages();
+}
diff --git a/modules/devshop/devshop_projects/inc/create/step-2.inc b/modules/devshop/devshop_projects/inc/create/step-2.inc
new file mode 100644
index 000000000..c28271f3e
--- /dev/null
+++ b/modules/devshop/devshop_projects/inc/create/step-2.inc
@@ -0,0 +1,64 @@
+nid);
+
+ $node_type = 'project';
+ $form_id = $node_type . '_node_form';
+ $form_state['build_info']['args'] = array($node);
+ $form_state['build_info']['files'][] = array('inc', 'node', 'node.pages');
+ module_load_include('inc', 'node', 'node.pages');
+ $project_form = drupal_retrieve_form($form_id, $form_state);
+ drupal_prepare_form($form_id, $project_form, $form_state);
+ // Remove node buttons
+ $actions = $project_form['actions'];
+ unset($project_form['actions']);
+
+ // Merge project node form into ctools wizard form.
+ $form = array_merge($project_form, $form);
+
+ // Make sure the project isn't re-verified on this page.
+ $form['no_verify'] = array(
+ '#type' => 'value',
+ '#value' => TRUE,
+ );
+
+ // Add additional additional submit and validate handlers for the node form.
+ $form['buttons']['next']['#submit'] = $actions['submit']['#submit'];
+ $form['buttons']['next']['#submit'][] = 'ctools_wizard_submit';
+
+ drupal_add_js(drupal_get_path('module', 'devshop_projects') . '/inc/create/create.js');
+
+ // Remove "live environment" selector.
+ $form['project']['codebase']['live_environment']['#type'] = 'value';
+ $form['project']['codebase']['live_environment']['#suffix'] = '';
+
+ return $form;
+}
+
+/**
+ * STEP 2: Validate
+ */
+function devshop_project_create_step_settings_validate(&$from, &$form_state) {
+ // Nothing is needed here. We've replaced our validator with node_form_validate,
+ // which passes through to devshop_projects_validate()
+}
+
+/**
+ * STEP 2: Submit
+ */
+function devshop_project_create_step_settings_submit(&$from, &$form_state) {
+ // Nothing is needed here. We've replaced our validator with node_form_validate,
+
+}
diff --git a/modules/devshop/devshop_projects/inc/create/step-3.inc b/modules/devshop/devshop_projects/inc/create/step-3.inc
new file mode 100644
index 000000000..4309c40ec
--- /dev/null
+++ b/modules/devshop/devshop_projects/inc/create/step-3.inc
@@ -0,0 +1,402 @@
+verify_task_status == HOSTING_TASK_QUEUED || $project->verify_task_status == HOSTING_TASK_PROCESSING) {
+ $note = '
' . t('Please wait while we connect to your repository and determine any branches.') . '
',
+ );
+
+ // If there was a problem with loading branches or tags, hide the form and show a link to re-verify.
+ if (empty($project->settings->git['branches']) && empty($project->settings->git['tags'])) {
+ $form['error'] = array(
+ '#markup' => t('Unable to read branches or tags. Please !verify.', array(
+ '!verify' => l(t('Re-verify the project'), "hosting_confirm/{$project->nid}/project_verify", array(
+ 'query' => array('token' => drupal_get_token($user->uid)),
+ 'attributes' => array(
+ 'class' => array('btn btn-success'),
+ ),
+ ))
+ )),
+ '#prefix' => '
',
+ );
+ $form['#no_finish'] = TRUE;
+ }
+
+ // If ready...
+ else {
+
+ // If no common profiles found, just set to standard.
+ if (count($available_profiles) == 0) {
+ $available_profiles['standard'] = 'No default profile.';
+ $default_profile = 'standard';
+ }
+
+ $project->no_finish = FALSE;
+
+ // Install Profile
+ // Sensible default?
+ // Lets go with standard for now... we can update later.
+ if (isset($available_profiles['standard'])) {
+ $default_profile = 'standard';
+ }
+ // If 'drupal' profile exists, it is likely drupal6!
+ elseif (isset($available_profiles['drupal'])) {
+ $default_profile = 'drupal';
+ }
+
+ $form['install_profile'] = array(
+ '#type' => 'radios',
+ '#options' => $available_profiles,
+ '#title' => t('Project Install Profile'),
+ '#required' => 1,
+ '#description' => t('All sites in your project must use the same installation profile, and it must exist in all branches. Choose the installation profile for this project.'),
+ '#default_value' => $default_profile,
+ );
+ }
+
+ $form['#suffix'] = $modals;
+ return $form;
+}
+
+/**
+ * STEP 4: Validate
+ */
+function devshop_project_create_step_sites_validate(&$from, &$form_state) {
+ if ($form_state['triggering_element']['#value'] == t('Retry')) {
+ foreach ($form_state['project']->environments as $name => $environment) {
+ $task = current($environment->tasks['verify']);
+ if (isset($task->nid)) {
+ hosting_task_retry($task->nid);
+ }
+ }
+ }
+ elseif (empty($form_state['values']['install_profile'])) {
+ form_set_error('install_profile', t('You must choose an install profile. Please wait for all environments to verify.'));
+ }
+}
diff --git a/modules/devshop/devshop_projects/inc/drush.inc b/modules/devshop/devshop_projects/inc/drush.inc
new file mode 100644
index 000000000..f807a7c90
--- /dev/null
+++ b/modules/devshop/devshop_projects/inc/drush.inc
@@ -0,0 +1,45 @@
+environments as $name => $environment) {
+
+ $remote_user = 'aegir';
+ $remote_host = $environment->remote_host;
+ $root = $environment->root;
+ $uri = $environment->uri;
+ $file_path = "sites/{$environment->uri}/files";
+
+ $output .= << '$root',
+ 'uri' => '$uri',
+ 'remote-user' => '$remote_user',
+ 'remote-host' => '$remote_host',
+ 'path-aliases' => array(
+ '%files' => '$file_path',
+ ),
+);
+
+PHP;
+ }
+
+ return $output;
+}
+
+
+/**
+ * Downloads the drush aliases for this site.
+ * @param $project
+ */
+function devshop_project_drush_aliases_page($node) {
+ $project = $node->project;
+ $filename = $project->name . '.aliases.drushrc.php';
+ header("Content-Disposition: attachment; filename='$filename'");
+ print devshop_project_aliases($project);
+}
diff --git a/modules/devshop/devshop_projects/inc/forms.inc b/modules/devshop/devshop_projects/inc/forms.inc
new file mode 100644
index 000000000..4eb612d72
--- /dev/null
+++ b/modules/devshop/devshop_projects/inc/forms.inc
@@ -0,0 +1,1699 @@
+project;
+
+ // Save last project data
+ $form['old'] = array(
+ '#value' => $node,
+ '#type' => 'value',
+ );
+
+ // Project Settings
+ // Every value under $form['project'] gets serialized and saved into a project's "data" column.
+ $form['project'] = array(
+ '#tree' => TRUE,
+ );
+
+ // Hidden fields that can't change.
+ $form['title'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Project Code Name'),
+ '#required' => TRUE,
+ '#description' => t('Choose a unique name for your project.'),
+ '#size' => 40,
+ '#default_value' => $node->title,
+ '#maxlength' => 255,
+ );
+ $form['project']['git_url'] = array(
+ '#type' => 'value',
+ '#value' => $project->git_url,
+ );
+ $form['project']['codebase'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('General Settings'),
+ '#group' => 'project_settings',
+ '#tree' => FALSE,
+ '#weight' => -11,
+ );
+ $form['project']['codebase']['code_path'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Base path'),
+ '#description' => t('All environments in this project will be installed inside this path. Changing this will only affect new environments. Existing environments will not change.'),
+ '#required' => TRUE,
+ '#size' => 40,
+ '#default_value' => $project->code_path,
+ '#maxlength' => 255,
+ '#weight' => 2,
+ '#parents' => array(
+ 'project',
+ 'code_path',
+ ),
+ );
+
+ $items = [];
+ $items[] = t('Enter the relative path to the exposed document root within your repository. Leave blank if index.php is in the root. Common paths are docroot or web.');
+ $items[] = t('If you are using the drupal-composer project for Drupal 8, the default document root is "web".');
+
+ $form['project']['codebase']['drupal_path'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Path to Drupal'),
+ '#description' => theme('item_list', ['items' => $items]),
+ '#size' => 40,
+ '#default_value' => $project->drupal_path,
+ '#maxlength' => 255,
+ '#weight' => 3,
+ '#parents' => array(
+ 'project',
+ 'drupal_path',
+ ),
+ );
+// This was accidentally merged from the makefiles branch.
+// $form['project']['codebase']['makefile_path'] = array(
+// '#type' => 'textfield',
+// '#title' => t('Path to Makefile'),
+// '#description' => t('Enter the relative path to a makefile inside your repository. Leave blank if a full Drupal codebasee is in the repository.'),
+// '#size' => 40,
+// '#default_value' => $project->settings->makefile_path,
+// '#maxlength' => 255,
+// '#weight' => 4,
+// '#parents' => array(
+// 'project',
+// 'settings',
+// 'makefile_path',
+// ),
+// );
+ $form['project']['codebase']['base_url'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Environment Domain Name Pattern'),
+ '#description' => t("Each environment will have a system domain name generated for it based on it's name. Use @project for project name, @hostname for '%host', @environment for the environment's name.", array('%host' => $_SERVER['SERVER_NAME'])) . '' . t('Changing this will only affect new environments.'). '',
+ '#required' => TRUE,
+ '#size' => 40,
+ '#default_value' => $project->base_url,
+ '#maxlength' => 255,
+ '#weight' => 4,
+ '#access' => variable_get('devshop_projects_allow_custom_base_url', FALSE),
+ '#parents' => array(
+ 'project',
+ 'base_url',
+ ),
+ );
+
+ // Don't allow editing
+ if ($node->nid) {
+
+ // Title
+ $form['title']['#value'] = $form['title']['#default_value'];
+ $form['title']['#type'] = 'value';
+
+ }
+
+ // Prevent editing code path unless allowed.
+ if (!variable_get('devshop_projects_allow_custom_code_path', FALSE)) {
+ $form['project']['codebase']['code_path']['#type'] = 'value';
+ $form['project']['codebase']['code_path']['#value'] = $form['project']['codebase']['code_path']['#default_value'];
+ }
+
+ // Project Settings
+ $form['project']['settings'] = array(
+ '#weight' => -10,
+ );
+
+ // Project Settings Vertical Tabs
+ $form['project_settings'] = array(
+ '#type' => 'vertical_tabs',
+ '#weight' => -11,
+ );
+
+ // Save git branches and tags
+ $form['project']['settings']['git']['branches'] = array(
+ '#type' => 'value',
+ '#value' => isset($project->settings->git['branches']) ? $project->settings->git['branches'] : NULL,
+ );
+ $form['project']['settings']['git']['tags'] = array(
+ '#type' => 'value',
+ '#value' => isset($project->settings->git['tags']) ? $project->settings->git['tags'] : NULL,
+ );
+
+ // Live Environment settings.
+ $form['project']['settings']['live'] = array(
+ '#type' => 'fieldset',
+ '#collapsible' => TRUE,
+ '#collapsed' => arg(1) != $project->nid,
+ '#title' => t('Domain Name Settings'),
+ '#group' => 'project_settings',
+ );
+
+ // Live Environment
+ $environments = array_keys($project->environments);
+ if (empty($environments)) {
+ $environments_options = array();
+ }
+ else {
+ $environments_options = array_combine($environments, $environments);
+ $environments_options[''] = t('None');
+ }
+
+ $form['project']['codebase']['live_environment'] = array(
+ '#type' => 'select',
+ '#title' => t('Primary Environment'),
+ '#suffix' => '
' . t('Select the primary environment for this project, typically the live environment. This environment will be marked with a icon. This is the environment that will be cloned when pull requests are created.') . '
',
+ '#options' => $environments_options,
+ '#default_value' => isset($project->settings->live) ? $project->settings->live['live_environment'] : '',
+ '#parents' => array(
+ 'project',
+ 'settings',
+ 'live',
+ 'live_environment',
+ ),
+ );
+
+ // Live Domain
+ $form['project']['settings']['live']['live_domain'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Live Domain'),
+ '#description' => t('The live domain for this project. Used only for links and when creating subdomain aliases for other environments. You must still add the Domain to your live environment manually.'),
+ '#size' => 40,
+ '#default_value' => isset($project->settings->live) ? $node->project->settings->live['live_domain'] : '',
+ );
+
+ // Use aliases
+ $form['project']['settings']['live']['environment_aliases'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('For new environments, create subdomains under Live Domain.'),
+ '#description' => t('When new environments are created, automatically add a domain name such as http://ENVIRONMENT.LIVEDOMAIN.com. Does not affect existing environments. Does not remove domains when disabled.'),
+ '#default_value' => isset($project->settings->live) ? $project->settings->live['environment_aliases'] : FALSE,
+ );
+
+ // Pull Code Method
+ $form['project']['settings']['deploy'] = array(
+ '#type' => 'fieldset',
+ '#collapsible' => TRUE,
+ '#collapsed' => arg(1) != $project->nid,
+ '#title' => t('Deployment Automation'),
+ '#description' => t('Configure how code is delivered to the servers. Post Deploy hooks are configured per environment.'),
+ '#weight' => -9,
+ '#group' => 'project_settings',
+ );
+
+ $form['project']['settings']['deploy']['method'] = array(
+ '#title' => 'Deploy Code Method',
+ '#type' => 'radios',
+ '#description' => t('Choose the method used to deploy code to the server.'),
+ '#default_value' => isset($project->settings->deploy['method']) ? $project->settings->deploy['method'] : 'webhook',
+ );
+
+ // Commit Webhook
+ $form['project']['settings']['deploy']['method']['#options']['webhook'] = t('Immediate Deployment');
+ $form['project']['settings']['deploy']['method']['#options']['webhook'] .= '
' . t('Recommended. Deploy code as it is delivered to your repository.') . ' ' . t('Requires setting up a webhook with your git repository host.') . '' . '
' . t('Deploy code to servers manually via devshop or drush.');
+
+ $form['project']['settings']['deploy']['method']['#options']['manual'] .= ' ' . t('Not recommended. All environments must be manually updated.') . '' . '
';
+
+ // Add link to hosting queues admin if the user can access them.
+ if (!$queues['deploy']['enabled'] && user_access('administer hosting queues')) {
+ $form['project']['settings']['deploy']['queue_admin'] = array(
+ '#value' => t('The !link is disabled. Enable it to allow projects to pull code in the queue.', array(
+ '!link' => l(t('Pull Queue'), 'admin/hosting/queues'),
+ )),
+ '#prefix' => '
',
+ '#suffix' => '
',
+ );
+ }
+
+ // Load deploy hooks form element.
+ $form['project']['settings']['deploy']['default_hooks'] = devshop_environment_deploy_hooks_form($project);
+
+ // Deploy hooks configuration
+ $form['project']['settings']['deploy']['default_hooks']['label'] = array(
+ '#value' => t('Default Deploy Hooks:'),
+ '#prefix' => '',
+ '#weight' => -100,
+ );
+ $form['project']['settings']['deploy']['default_hooks']['description'] = array(
+ '#value' => t('New environments will run these actions when new code or data is deployed.'),
+ '#prefix' => '
',
+ '#suffix' => '
',
+ '#weight' => -100,
+ );
+
+ $form['project']['settings']['deploy']['default_hooks']['allow_environment_deploy_config'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Allow environment-specific deploy hook configuration.'),
+ '#default_value' => isset($node->project->settings->deploy) ? $node->project->settings->deploy['allow_environment_deploy_config'] : FALSE,
+ '#description' => t('Each environment can be configured to have different deploy hooks. Be sure to check your environments settings if you enable this.' ),
+ '#parents' => array(
+ 'project',
+ 'settings',
+ 'deploy',
+ 'allow_environment_deploy_config',
+ ),
+ );
+ $form['project']['settings']['deploy']['default_hooks']['allow_environment_deploy_hooks_override'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Allow users to override hooks when deploying manually.'),
+ '#default_value' => isset($node->project->settings->deploy) ? $node->project->settings->deploy['allow_environment_deploy_hooks_override'] : FALSE,
+ '#description' => t('Check this box to allow users to override the hooks that are run on manual deployments. If left unchecked, all environments will run the deploy hooks configured above on every deployment.' ),
+ '#parents' => array(
+ 'project',
+ 'settings',
+ 'deploy',
+ 'allow_environment_deploy_hooks_override',
+ ),
+ );
+
+ // @TODO: is there a better way to save certain values? We lose data without these.
+ $form['project']['settings']['deploy']['last_webhook'] = array(
+ '#type' => 'value',
+ '#value' => isset($node->project->settings->deploy) ? $node->project->settings->deploy['last_webhook'] : NULL,
+ );
+ $form['project']['settings']['deploy']['last_webhook_status'] = array(
+ '#type' => 'value',
+ '#value' => isset($node->project->settings->deploy) ? $node->project->settings->deploy['last_webhook_status'] : NULL,
+ );
+ $form['project']['settings']['deploy']['last_webhook_ip'] = array(
+ '#type' => 'value',
+ '#value' => isset($node->project->settings->deploy) ? $node->project->settings->deploy['last_webhook_ip'] : NULL,
+ );
+
+ //All settings git pull in project page
+ $form['project']['settings']['default_environment'] = array(
+ '#type' => 'fieldset',
+ '#collapsible' => TRUE,
+ '#collapsed' => arg(1) != $project->nid,
+ '#title' => t('Default Environment Settings'),
+ '#description' => t('These settings will be used when creating new environments.'),
+ '#group' => 'project_settings',
+ );
+
+ // Install Profile
+ $available_profiles = array();
+ foreach ($project->environments as $name => $environment) {
+ // Passing null to hosting_get_profiles returns all profiles.
+ if (is_null($environment->platform)) {
+ continue;
+ }
+ $profiles_shortnames = hosting_get_profiles($environment->platform, 'short_name');
+ if (is_array($profiles_shortnames) && !empty($profiles_shortnames)) {
+ $profiles[$name] = array_combine($profiles_shortnames, (array)hosting_get_profiles($environment->platform));
+ } else {
+ $profiles[$name] = array();
+ }
+ if (empty($available_profiles)) {
+ $available_profiles = $profiles[$name];
+ } else {
+ $available_profiles = array_intersect_key($available_profiles, $profiles[$name]);
+ }
+ }
+
+ $form['project']['settings']['default_environment']['install_profile'] = array(
+ '#type' => 'radios',
+ '#options' => $available_profiles,
+ '#title' => t('Default Install Profile'),
+ '#required' => 1,
+ '#description' => t('New environments will be created using this install profile. Existing environments will not be affected.'),
+ '#default_value' => $node->project->install_profile,
+ '#access' => count($available_profiles),
+ );
+
+ // HTTP Server select.
+ $http_servers = hosting_get_servers('http');
+ if (count($http_servers)) {
+ $form['project']['settings']['default_environment']['web_server'] = array(
+ '#title' => t('Web server'),
+ '#type' => 'select',
+ '#options' => $http_servers,
+ '#default_value' => isset($project->settings->default_environment) ? $project->settings->default_environment['web_server'] : current($http_servers),
+ );
+ }
+
+ // DB Server select.
+ $db_servers = hosting_get_servers('db');
+ if (count($db_servers)) {
+ $form['project']['settings']['default_environment']['db_server'] = array(
+ '#title' => t('Database server'),
+ '#type' => 'select',
+ '#options' => $db_servers,
+ '#default_value' => isset($project->settings->default_environment) ? $project->settings->default_environment['db_server'] : current($db_servers),
+ );
+ }
+
+ // Solr Server Select
+ $solr_servers = hosting_get_servers('solr');
+ if (count($solr_servers)) {
+ $form['project']['settings']['default_environment']['solr_server'] = array(
+ '#title' => t('Solr server'),
+ '#type' => 'select',
+ '#options' => $solr_servers,
+ '#default_value' => $project->settings->default_environment['solr_server'],
+ );
+ }
+
+ // Force the servers for this project's environments
+ $form['project']['settings']['default_environment']['force_default_servers'] = array(
+ '#title' => t('Force new environments to use these servers.'),
+ '#description' => t('When new environments are created, they will use the servers selected above.'),
+ '#type' => 'checkbox',
+ '#default_value' => $project->settings->default_environment['force_default_servers'],
+ );
+ return $form;
+}
+
+/**
+ * Implementation of hook_validate().
+ *
+ * This function is no longer used since we have a ctools wizard for
+ * project creation.
+ */
+function devshop_projects_validate($node, &$form, &$form_state) {
+
+ // Code path and base url must be unique
+ if (db_query('SELECT n.nid FROM {hosting_devshop_project} d LEFT JOIN {node} n ON d.nid = n.nid WHERE status = :status AND code_path = :code_path AND n.nid != :nid;', array(':status' => 1, ':code_path' => $form_state['values']['project']['code_path'], ':nid' => $form_state['values']['nid']))->fetchField()) {
+ form_set_error('code_path', t('The code path %path is in use by another project. Please enter a different path.', array(
+ '%path' => $form_state['values']['project']['code_path'],
+ )));
+ }
+ // If custom domain pattern is allowed, AND domain pattern does not have "@project" in it, AND domain pattern is not the default AND it is found on another project, throw an error.
+ if (variable_get('devshop_projects_allow_custom_base_url', FALSE) && strpos($form_state['values']['project']['base_url'], '@project') === FALSE && $form_state['values']['project']['base_url'] != variable_get('devshop_project_environment_url_pattern', '@project.@environment.@hostname') && db_query('SELECT n.nid FROM {hosting_devshop_project} d LEFT JOIN {node} n ON d.nid = n.nid WHERE status = :status AND base_url = :base_url AND n.nid != :nid;;', array(':status' => 1, ':base_url' => $form_state['values']['project']['base_url'], ':nid' => $form_state['values']['nid']))->fetchField()) {
+ form_set_error('base_url', t('The Environment Domain Pattern %url is in use by another project. Please enter a different Environment Domain Pattern.', array(
+ '%url' => $form_state['values']['project']['base_url'],
+ )));
+ }
+
+ // "Environment Domain Name Pattern" (Base URL)
+ // Ensure @environment is included.
+ if (strpos($form_state['values']['project']['base_url'], '@environment') === FALSE ) {
+ form_set_error('project][base_url', t('Environment Domain Name Pattern must include @environment placeholder.'));
+ }
+}
+
+/**
+ * Implements hook_form_alter().
+ */
+function devshop_projects_form_alter(&$form, &$form_state, $form_id) {
+
+ // Removing unneccesary fieldgroups
+ if ($form_id == 'project_node_form') {
+ global $user;
+
+ $project = $form['#node'];
+ unset($form['menu']);
+ unset($form['revision_information']);
+ unset($form['author']);
+ unset($form['options']);
+ unset($form['actions']['delete']);
+ unset($form['actions']['preview']);
+ if (isset($form['retry']['#value'])) {
+ $form['actions']['submit']['#value'] = t('Save and Retry');
+ }
+
+ //Add button for delete project
+ $form['actions']['delete'] = array(
+ '#markup' => l(' ' . t('Delete Project'), 'hosting_confirm/' . $project->nid . '/project_delete', array(
+ 'query' => array('token' => drupal_get_token($user->uid)),
+ 'attributes' => array(
+ 'class' => array('btn btn-danger pull-right'),
+ ),
+ 'html' => TRUE,
+ )),
+ '#weight' => 10,
+ '#access' => drupal_valid_path('hosting_confirm/' . $project->nid . '/project_delete'),
+ );
+ }
+
+ // Create Project Wizard
+ if (
+ $form_id == 'devshop_project_create_step_git' ||
+ $form_id == 'devshop_project_create_step_sites' ||
+ $form_id == 'devshop_project_create_step_settings' ||
+ $form_id == 'devshop_project_create_step_environments'
+ ){
+ if (isset($form['#no_finish'])) {
+ unset($form['buttons']['return']);
+ }
+ if (isset($form['#no_next'])) {
+ unset($form['buttons']['next']);
+ }
+
+ if (isset($form['buttons']['next']['#value'])) {
+ $form['buttons']['next']['#value'] .= ' ';
+ }
+
+ if (isset($form['buttons']['return']['#value'])) {
+ $form['buttons']['return']['#value'] = ' ' . t('Create Project & Environments');
+ }
+
+ $form['buttons']['next']['#attributes'] =
+ $form['buttons']['return']['#attributes'] = array(
+ 'class' => array(
+ 'btn btn-success',
+ ),
+ );
+ $form['buttons']['previous']['#attributes'] = array(
+ 'class' => array(
+ 'btn btn-default',
+ ),
+ );
+ $form['buttons']['cancel']['#attributes'] = array(
+ 'class' => array(
+ 'btn btn-link',
+ ),
+ );
+
+ }
+
+ // Hosting Task Forms
+ if ($form_id == 'hosting_task_confirm_form') {
+ switch ($form['task']['#value']) {
+
+ // Migrate Form: used for changing database server.
+ case 'migrate':
+
+ // To change the database server, we use the migrate form.
+ if ($_GET['deploy'] == 'stack') {
+ drupal_set_title(t('Change Database Server'));
+ $site_node = node_load($form['nid']['#value']);
+ $environment = $site_node->environment;
+ $form['help']['#weight'] = 100;
+ $form['help']['#value'] = t("Are you sure you want to change this environment's database server?");
+
+ // Hide "URI" field unless "rename" GET parameter is added.
+ if (!isset($_GET['rename'])) {
+ $form['parameters']['new_uri']['#type'] = 'value';
+ $form['parameters']['new_uri']['#value'] = $form['parameters']['new_uri']['#default_value'];
+ }
+
+ // Display something helpful
+ $form['old'] = array(
+ '#type' => 'item',
+ '#title' => t('Current Database Server'),
+ '#value' => l($environment->servers['db']['name'], 'hosting/c/server_' . $environment->servers['db']['nid']),
+ '#weight' => '-1',
+ );
+ }
+ if (isset($_GET['rename'])) {
+ drupal_set_title(t('Change Domain Name for environment @env in project @proj?', array(
+ '@env' => $environment->name,
+ '@proj' => $environment->project_name,
+ )));
+ $form['help']['#value'] = t("Are you sure you want to change this environment's domain name?");
+ $form['submit']['#value'] = t('Change Domain');
+ }
+ if (isset($_GET['rename']) || $_GET['deploy'] == 'stack') {
+ foreach ($form['parameters'] as $key => $element) {
+ if (is_int($key)) {
+ // Don't unset the target platform. Just hide it.
+ if (isset($element['target_platform']['#default_value'])) {
+ $form['parameters'][$key]['target_platform']['#type'] = 'value';
+ $form['parameters'][$key]['target_platform']['#value'] = $form['parameters'][$key]['target_platform']['#default_value'];
+ }
+ else {
+ unset($form['parameters'][$key]);
+ }
+ }
+ }
+ }
+ break;
+
+ // Deploy task form
+ case 'devshop-deploy':
+
+ // Alter title of deploy task.
+ $node = node_load($form['nid']['#value']);
+ drupal_set_title(t('Deploy code to Environment "@env"', array('@env' => $node->environment->name)), PASS_THROUGH);
+ $form['actions']['cancel']['#value'] = l(t('Cancel'), "node/{$node->project->nid}");
+ break;
+
+ // Sync task form
+ case 'sync':
+ if (isset($_GET['source'])) {
+ $node = node_load($form['nid']['#value']);
+
+ if ($_GET['source'] == 'other') {
+ $source = '';
+ }
+ else {
+ $source = node_load($_GET['source']);
+ if (isset($source->nid) && $source->type != 'site') {
+ break;
+ }
+ }
+
+ drupal_set_title(t('Sync Data to Environment "@env"', array('@env' => $node->environment->name)), PASS_THROUGH);
+ $environment = $node->environment;
+
+ // If source is empty, ask for an alias.
+ if (empty($source)) {
+ $form['parameters']['source'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Drush Alias for Source'),
+ '#description' => t('Enter a full drush alias to use as the source. Make sure to include the "@" character at the beginning.'),
+ );
+ $form['parameters']['destination'] = array(
+ '#type' => 'value',
+ '#value' => $node->environment->system_alias,
+ );
+ }
+ // If a source is specified, add help and set it as a value.
+ else {
+
+ $source_environment = $source->environment;
+
+ $form['parameters']['source'] = array(
+ '#type' => 'value',
+ '#value' => $source->environment->system_alias,
+ );
+ $form['parameters']['destination'] = array(
+ '#type' => 'value',
+ '#value' => $node->environment->system_alias,
+ );
+ }
+
+ // Don't copy modules and themes.
+ unset($form['parameters']['modules']);
+ unset($form['parameters']['themes']);
+ unset($form['parameters']['libraries']);
+
+ // Better output
+ $form['parameters']['database']['#prefix'] = '';
+ $form['parameters']['registry-rebuild']['#prefix'] = '';
+ $form['parameters']['backup']['#weight'] = -11;
+
+ $form['actions']['submit']['#value'] = '' . t('Sync Data');
+ $form['actions']['submit']['#attributes'] = array(
+ 'class' => array(
+ 'btn-success'
+ ),
+ );
+ $form['actions']['cancel']['#value'] = l(t('Cancel'), "node/{$node->project->nid}");
+
+ $form['warning']['#markup'] = '
' . t('Clicking "Sync Data" will DESTROY the database for the environment !link.', array('!link' => l($environment->url, $environment->url, array('attributes' => array('target' => '_blank'))))) . '
';
+
+
+ $destroyed = t('This environment will be destroyed.');
+ $source = t('This environment will be copied.');
+
+ $html = <<
+
'
+ );
+
+ // Get a list of environments.
+ foreach ($node->project->environments as $environment) {
+ $items[] = l($environment->uri, 'http://' . $environment->uri);
+
+ }
+ $environments = theme('item_list', array('items' => $items));
+
+ // Display a scary warning message.
+ $form['help']['#markup'] = '
' . t('Are you sure you wish to destroy the project !project and all of these environments?', array(
+ '!project' => l($node->project->name, "node/{$node->nid}"),
+ )) . $environments . '
',
+ '#weight' => -1000,
+ );
+
+ // Add our own validator and submit handlers.
+ $form['actions']['submit']['#value'] = t('Create New Environment');
+// array_unshift($form['actions']['submit']['#submit'], 'devshop_projects_create_environment_form_submit');
+
+ // Generate field prefix and suffix using domain name pattern.
+ if (variable_get('devshop_projects_allow_custom_base_url')) {
+ $pattern = $project->base_url;
+ }
+ else {
+ $pattern = variable_get('devshop_project_environment_url_pattern', '@project.@environment.@hostname');
+ }
+
+ $labels = explode('@environment', strtr($pattern, array(
+ '@project' => $project_node->title,
+ '@hostname' => $_SERVER['SERVER_NAME'],
+ )));
+
+ // Hide the "Domain Name" field, as this will be generated
+ $form['title']['#access'] = false;
+ $form['title']['#required'] = false;
+
+ $form['environment_name'] = array(
+ '#title' => t('Environment Name'),
+ '#type' => 'textfield',
+ '#description' => t('Enter a system name for your environment. For consistency, you might want to match the branch name.'),
+ '#required' => TRUE,
+ '#field_prefix' => '
http://' . $labels[0] . '
',
+ '#field_suffix' => '
' . $labels[1] .'
+
',
+ '#size' => 10,
+ '#maxlength' => 64,
+ '#weight' => -101,
+ '#attributes' => array(
+ 'data-placement' => 'bottom',
+ ),
+ '#element_validate' => array(
+ 'devshop_projects_create_environment_form_validate_name'
+ ),
+ '#wrapper_attributes' => array(
+ 'class' => array('col-sm-8 col-md-8'),
+ ),
+ );
+
+ $branch_options = devshop_projects_git_ref_options($project);
+ $form['git_ref'] = array(
+ '#title' => t('Branch or Tag'),
+ '#type' => 'select',
+ '#description' => t('The git reference to checkout for this environment. You can change this later using the "Deploy Code" button.'),
+ '#options' => $branch_options,
+ '#required' => TRUE,
+ '#weight' => -100,
+ '#wrapper_attributes' => array(
+ 'class' => array('col-sm-4 col-md-4'),
+ ),
+ );
+
+ $form['install_method'] = array(
+ '#type' => 'fieldset',
+ '#weight' => -99,
+ '#tree' => TRUE,
+ '#attributes' => array(
+ 'class' => array('clearfix'),
+ ),
+ );
+ $form['install_method']['method'] = array(
+ '#type' => 'radios',
+ '#required' => TRUE,
+ '#title' => t('Install Method'),
+ '#process' => array('devshop_environment_method_process'),
+ '#weight' => -99,
+ '#options' => array(
+ 'profile' => t('Drupal Profile'),
+ 'manual' => t('Empty Database'),
+ 'clone' => t('Clone Environment'),
+ 'import' => t('Import Database'),
+ ),
+ );
+
+ $form['install_method']['none'] = array(
+ '#type' => 'container',
+ '#weight' => -98,
+ '#states' => array(
+ 'visible' => array(
+ ':input[name="install_method[method]"]' => array('value' => 'manual'),
+ ),
+ ),
+ 'note' => array(
+ '#markup' => t('An empty database will be created. You can install Drupal manually by visiting install.php, by using Drush, or you can manually import a database.'),
+ ),
+ );
+
+ $form['install_method']['profile'] = array(
+ '#type' => 'radios',
+ '#title' => t('Drupal Install Profile'),
+ '#description' => t('These install profiles were found in other environments in this project. If you choose an install profile that does not exist in the codebase, the "Standard" profile will be used.'),
+ '#description_display' => 'before',
+ '#weight' => -98,
+ '#required' => TRUE,
+ '#options' => devshop_environment_add_form_profile_options($project),
+ '#states' => array(
+ 'visible' => array(
+ ':input[name="install_method[method]"]' => array('value' => 'profile'),
+ ),
+ ),
+ );
+
+ // Set default profile.
+ if (count($form['install_method']['profile']['#options']) == 1 || empty($project->install_profile)) {
+ $form['install_method']['profile']['#default_value'] = '_other';
+ }
+ else {
+ $form['install_method']['profile']['#default_value'] = $project->install_profile;
+ }
+
+ $form['install_method']['profile_other'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Other Install Profile'),
+ '#title_display' => 'invisible',
+ '#description' => t('Enter the exact name the install profile to use. It must already exist in your git branch or tag.'),
+ '#element_validate' => array(
+ 'devshop_projects_create_environment_form_validate_profile'
+ ),
+ '#attributes' => array(
+ 'placeholder' => t('Profile Name'),
+ ),
+ '#weight' => -97,
+ '#states' => array(
+ 'visible' => array(
+ ':input[name="install_method[method]"]' => array('value' => 'profile'),
+ ':input[name="install_method[profile]"]' => array('value' => '_other'),
+ ),
+ ),
+ );
+
+ // Generate environments options.
+ foreach ($project->environments as $e => $environment) {
+ $link = l(t('View') . ' ', $environment->url, array(
+ 'html' => TRUE,
+ 'attributes' => array(
+ 'target' => '_blank',
+ 'class' => array('btn-text'),
+ ),
+ )) . '';
+ $git_ref = $environment->git_ref;
+ $icon = $environment->git_ref_type == 'tag'? 'tag': 'code-fork';
+ $environment_options[$environment->system_alias] = "{$environment->name} $git_ref $link";
+ }
+ $environment_options['_other'] = t('Other Drush Alias');
+
+ if (count($environment_options)) {
+ $form['install_method']['clone_source'] = array(
+ '#type' => 'radios',
+ '#title' => t('Environment to clone'),
+ '#weight' => -98,
+ '#required' => TRUE,
+ '#options' => $environment_options,
+ '#default_value' => key($environment_options),
+ '#states' => array(
+ 'visible' => array(
+ ':input[name="install_method[method]"]' => array('value' => 'clone'),
+ ),
+ ),
+ );
+ }
+
+ // Detect clone link. Set defaults.
+ if (arg(4) == 'clone') {
+ $environment_to_clone = arg(5);
+ $form['install_method']['method']['#default_value'] = 'clone';
+ $form['install_method']['clone_source']['#default_value'] = $project->environments[$environment_to_clone]->system_alias;
+ $form['git_ref']['#default_value'] = $project->environments[$environment_to_clone]->git_ref;
+ }
+
+ // Detect fork link.
+ // @TODO: Get "forking" back in place for the next beta.
+// if (arg(4) == 'fork') {
+// $environment_to_clone = arg(5);
+// $form['install_method']['method']['#default_value'] = 'clone';
+// $form['install_method']['clone_source']['#default_value'] = $project->environments[$environment_to_clone]->system_alias;
+// $form['git_ref']['#default_value'] = $project->environments[$environment_to_clone]->git_ref;
+// $form['git_ref']['#title'] = t('Base Branch or Tag');
+// $form['git_ref']['#description'] = t('The git reference you want to create a new branch from.');
+//
+// $form['git_ref_new'] = array(
+// '#title' => t('New branch name'),
+// '#description' => t('New branch name'),
+// );
+// }
+
+
+ $form['install_method']['clone_source_drush'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Drush Alias'),
+ '#description' => t('Enter a Drush alias you would like to Sync your database and files from. It must already exist on the server.'),
+ '#element_validate' => array(
+ 'devshop_projects_create_environment_form_validate_clone_source'
+ ),
+ '#weight' => -98,
+ '#attributes' => array(
+ 'placeholder' => t('@drush.alias'),
+ ),
+ '#states' => array(
+ 'visible' => array(
+ ':input[name="install_method[clone_source]"]' => array('value' => '_other'),
+ ':input[name="install_method[method]"]' => array('value' => 'clone'),
+ ),
+ ),
+ );
+
+ $form['install_method']['import'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Path to SQL'),
+ '#description' =>
+ t('Enter either a remote MySQL address (such as mysql://username:password@host/database), or an absolute path to an SQL dump (such as /var/aegir/site-backup.sql).') .
+ '' . t('This string is stored in plain text. Use with caution.') . ''
+ ,
+ '#weight' => -98,
+ '#states' => array(
+ 'visible' => array(
+ ':input[name="install_method[method]"]' => array('value' => 'import'),
+ ),
+ ),
+ '#element_validate' => array(
+ 'devshop_projects_create_environment_form_validate_import'
+ ),
+ );
+
+ $form['server_stack'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Server Stack'),
+ '#group' => 'environment_settings',
+ );
+
+ $form['server_stack']['db_server'] = $form['db_server'];
+ $form['server_stack']['db_server']['#group'] = 'environment_settings';
+ $form['server_stack']['db_server']['#tree'] = FALSE;
+ unset($form['db_server']);
+
+ $web_servers = hosting_get_servers('http');
+ $form['server_stack']['web_server'] = array(
+ '#type' => 'radios',
+ '#title' => t('Web server'),
+ '#description' => t('The web server the site will be hosted on.'),
+ '#options' => $web_servers,
+ '#required' => TRUE,
+ '#tree' => FALSE,
+ '#default_value' => $project->settings->default_environment['web_server'],
+ );
+
+ // Force the new environment to use the "default" servers.
+ if ($project->settings->default_environment['force_default_servers']) {
+ $form['server_stack']['#description'] = t('All environments in this project must use this server stack.');
+
+ $web_server_nid = $project->settings->default_environment['web_server'];
+ $form['server_stack']['web_server']['#type'] = 'value';
+ $form['server_stack']['web_server']['#value'] = $web_server_nid;
+ $form['server_stack']['web_server_label'] = array(
+ '#type' => 'item',
+ '#title' => t('Web server'),
+ '#markup' => l($web_servers[$web_server_nid], "node/$web_server_nid", array('attributes' => array('target' => '_blank'))),
+ );
+
+ $db_servers = hosting_get_servers('db');
+ $db_server_nid = $project->settings->default_environment['db_server'];
+ $form['server_stack']['db_server']['#type'] = 'value';
+ $form['server_stack']['db_server']['#value'] = $db_server_nid;
+ $form['server_stack']['db_server_label'] = array(
+ '#type' => 'item',
+ '#title' => t('Database server'),
+ '#markup' => l($db_servers[$db_server_nid], "node/$db_server_nid", array('attributes' => array('target' => '_blank'))),
+ );
+ }
+
+ $form['project_nid'] = array(
+ '#value' => $project->nid,
+ '#type' => 'value',
+ );
+
+ $form['platform_node'] = array(
+ '#type' => 'value',
+ '#value' => NULL,
+ );
+
+
+ // Remove platform and profile options.
+ // @TODO: Patch Aegir so we don't have to load all of these platform options.
+ $form['platform']['#access'] = FALSE;
+ $form['profile']['#access'] = FALSE;
+ $form['git']['#access'] = FALSE;
+}
+
+/**
+ * Validator for the domain name field: Sets domain according to pattern.
+ * @param $element
+ * @param $form_state
+ * @param $form
+ */
+function devshop_projects_create_environment_form_validate_name($element, &$form_state, $form) {
+
+ if (!empty($element['#value'])) {
+ $project_node = node_load($form_state['values']['project_nid']);
+ $project = $project_node->project;
+
+ // Check existence of the environment name
+ if (!empty($project->environments[$element['#value']])) {
+ form_set_error('environment_name', t('There is already an environment named %name in this project. Please choose a different name.', array('%name' => $form_state['values']['environment_name'])));
+ }
+
+ // Check for illegal chars
+ if (!preg_match('!^[a-z0-9_]+$!', $element['#value'])) {
+ form_set_error('environment_name', t('The environment name must contain only lowercase letters, numbers, and underscores.'));
+ }
+
+ // Generate field prefix and suffix using domain name pattern.
+ form_set_value($form['title'], devshop_environment_url($project, $element['#value']), $form_state);
+
+ // Attach a new platform node.
+ $platform_node = devshop_prepare_platform_node($project, $element['#value'], $form_state['values']['git_ref'], $form_state['values']['web_server']);
+
+ // Set value in form_state.
+ form_set_value($form['platform_node'], $platform_node, $form_state);
+ form_set_value($form['profile'], 0, $form_state);
+ form_set_value($form['platform'], 0, $form_state);
+
+ }
+}
+
+/**
+ * Helper to prepare the platform object to be attached to the site node.
+ *
+ * Example:
+ *
+ * $platform = devshop_prepare_platform_node($project, $environment_name, $git_ref, $web_server, $prepare_node);
+ * if ($platform = node_submit($platform)) {
+ * node_save($platform);
+ * }
+ *
+ * @param $project
+ * A fully loaded project object.
+ *
+ * @param $environment_name
+ * The name of the environment.
+ *
+ * @param $git_ref
+ * The desired git reference for the environment.
+ *
+ * @param $web_server
+ * The NID of a web server to use for this platform.
+ *
+ * @return stdClass
+ * A populated object ready for node_submit and node_save.
+ */
+function devshop_prepare_platform_node($project, $environment_name, $git_ref, $web_server) {
+ $platform_node = new stdClass();
+ $platform_node->type = 'platform';
+ $platform_node->title = $project->name . '_' . $environment_name;
+
+ // Specify GIT Url for hosting_git.module
+ $platform_node->git['repo_url'] = $project->git_url;
+ $platform_node->git['git_ref'] = $git_ref;
+
+ // Enable git pull queue for this platform if chosen.
+ if ($project->settings->deploy['method'] == 'queue') {
+ $platform_node->git['pull_method'] = HOSTING_GIT_PULL_QUEUE;
+ }
+ // For devshop "webhook" and "manual" methods, disable git pull for now.
+ // @TODO: Remove devshop "code deploy" task in exchange for hosting_git_pull module.
+ else {
+ $platform_node->git['pull_method'] = HOSTING_GIT_PULL_DISABLED;
+ }
+
+
+ // Determine which web server to use.
+ // If no web server was set and project has a default, make sure we set it.
+ if (empty($web_server) && !empty($project->settings->default_environment['web_server'])) {
+ $platform_node->web_server = $project->settings->default_environment['web_server'];
+ }
+ else {
+ $platform_node->web_server = $web_server;
+ }
+
+ $platform_node->git['repo_path'] = $project->code_path . '/' . $environment_name;
+
+ // Append drupal_path to repo_path if there is one. If not, repo_path is publish_path.
+ if ($project->drupal_path) {
+ $platform_node->publish_path = $platform_node->git['repo_path'] . '/' . $project->drupal_path;
+ $platform_node->git['repo_docroot'] = $project->drupal_path;
+ }
+ else {
+ $platform_node->publish_path = $platform_node->git['repo_path'];
+ }
+ return $platform_node;
+}
+
+/**
+ * Validator for the "other profile" field.
+ *
+ * @param $element
+ * @param $form_state
+ * @param $form
+ */
+function devshop_projects_create_environment_form_validate_profile($element, &$form_state, $form) {
+ if ($form_state['values']['install_method']['method'] == 'profile' && $form_state['values']['install_method']['profile'] =='_other' && empty($element['#value'])) {
+ form_set_error('install_method][profile_other', t('You must enter a profile name.'));
+ }
+}
+
+/**
+ * Validator for the "clone_source" field.
+ *
+ * @param $element
+ * @param $form_state
+ * @param $form
+ */
+function devshop_projects_create_environment_form_validate_clone_source($element, &$form_state, $form) {
+ if ($form_state['values']['install_method']['method'] == 'clone' && $form_state['values']['install_method']['clone_source'] =='_other' && empty($element['#value'])) {
+ form_set_error('install_method][clone_source_drush', t('You must enter a drush alias.'));
+ }
+}
+
+/**
+ * Validator for the "Path to SQL" field.
+ *
+ * @param $element
+ * @param $form_state
+ * @param $form
+ */
+function devshop_projects_create_environment_form_validate_import($element, &$form_state, $form) {
+
+ $url_components = parse_url($element['#value']);
+
+ if ($form_state['values']['install_method']['method'] == 'import' && empty($element['#value'])) {
+ form_set_error('install_method][import', t('If using the "Import Database" Install Method, you must enter a MySQL connection URL or absolute path to an SQL file.'));
+ }
+ elseif ($url_components['scheme'] == 'mysql') {
+ $database = array(
+ 'database' => ltrim($url_components['path'], '/'),
+ 'username' => $url_components['user'],
+ 'password' => $url_components['pass'],
+ 'host' => $url_components['host'],
+ 'driver' => 'mysql', // replace with your database driver
+ );
+ Database::addConnectionInfo('devshop_remote_db', 'default', $database);
+
+ try {
+ db_set_active('devshop_remote_db');
+ $tables = db_query('SHOW TABLES');
+ drupal_set_message(t('Database connection successful.'));
+ }
+ catch (\PDOException $e) {
+ form_set_error('install_method][import', t('Unable to connect to the database: %e', array('%e' => $e->getMessage())));
+ }
+ finally {
+ db_set_active();
+ }
+ }
+}
+
+/**
+ * Validator for site_node_form for node creation, when creating an environment
+ * in a project.
+ */
+function devshop_projects_create_environment_form_submit($form, &$form_state) {
+
+ // @TODO: Clean up install_method values.
+
+}
+
+/**
+ * Form element processor for Install Method radio buttons.
+ * @param $element
+ * @return array
+ */
+function devshop_environment_method_process($element) {
+ $element = form_process_radios($element);
+
+ foreach (element_children($element) as $i) {
+ $element[$i]['#label_attributes']['class'] = array(
+ 'btn btn-link'
+ );
+ $element[$i]['#wrapper_attributes']['class'] = array(
+ 'install-method-wrapper'
+ );
+ }
+
+ return $element;
+}
+
+/**
+ * Return all available install profiles found in all platforms for this project.
+ */
+function devshop_environment_add_form_profile_options($project) {
+
+
+ foreach ($project->environments as $e => $environment) {
+
+ $packages = hosting_package_instances_load(array(
+ 'package_type' => 'profile',
+ 'rid' => $environment->platform,
+ ));
+
+ foreach ($packages as $instance) {
+ $options[$instance->short_name] = $instance->title;
+ $options[$instance->short_name] .= ' ' . $instance->description . '';
+ }
+ }
+ $options['_other'] = t('Other') . ' ' . t('Choose another profile.') . '';
+ return $options;
+}
+
+/**
+ * Helper to output the forms selection for Deploy Hooks.
+ *
+ * Used in the Projects settings form, environment settings form, deploy task form, and sync form.
+ *
+ * @param $project
+ * @param null $environment
+ * @return array
+ */
+function devshop_environment_deploy_hooks_form($project, $environment = NULL, $task_type = NULL) {
+
+ // If environment not specified, just grab one.
+ if (!$environment) {
+ $environment = current($project->environments);
+ $is_environment_form = FALSE;
+ }
+ else {
+ $is_environment_form = TRUE;
+ }
+ $return = array(
+ '#type' => 'fieldset',
+ '#group' => 'project_settings',
+ '#title' => t('Deployment Hooks'),
+ '#description' => t('Deployment hooks are run whenever your codebase changes. It is recommended to always enable database updates and cache clearing.'),
+ '#weight' => -10,
+ );
+
+ $return['#project'] = $project;
+ $return['#environment'] = $environment;
+
+ // If we are on the project creation wizard, set some sane defaults
+ if (current_path() == 'projects/add/settings') {
+ $environment_update = FALSE;
+ $environment_cache = FALSE;
+ $environment_revert = FALSE;
+ $environment_dothooks = FALSE;
+ $environment_acquia_hooks = FALSE;
+ $environment_composer_install = FALSE;
+ $project_update = TRUE;
+ $project_cache = TRUE;
+ $project_revert = FALSE;
+ $project_dothooks = FALSE;
+ $project_acquia_hooks = FALSE;
+ $project_composer_install = FALSE;
+ }
+ else {
+ $environment_update = isset($environment->settings->deploy) ? $environment->settings->deploy['update'] : FALSE;
+ $environment_cache = isset($environment->settings->deploy) ? $environment->settings->deploy['cache'] : FALSE;
+ $environment_revert = isset($environment->settings->deploy) ? $environment->settings->deploy['revert'] : FALSE;
+ $environment_dothooks = isset($environment->settings->deploy) ? $environment->settings->deploy['dothooks'] : FALSE;
+ $environment_acquia_hooks = isset($environment->settings->deploy) ? $environment->settings->deploy['acquia_hooks'] : FALSE;
+ $environment_composer_install = isset($environment->settings->deploy) ? $environment->settings->deploy['composer'] : FALSE;
+ $project_update = isset($project->settings->deploy) ? $project->settings->deploy['default_hooks']['update'] : FALSE;
+ $project_cache = isset($project->settings->deploy) ? $project->settings->deploy['default_hooks']['cache'] : FALSE;
+ $project_revert = isset($project->settings->deploy) ? $project->settings->deploy['default_hooks']['revert'] : FALSE;
+ $project_dothooks = isset($project->settings->deploy) ? $project->settings->deploy['default_hooks']['dothooks'] : FALSE;
+ $project_acquia_hooks = isset($project->settings->deploy) ? $project->settings->deploy['default_hooks']['acquia_hooks'] : FALSE;
+ $project_composer_install = isset($project->settings->deploy) ? $project->settings->deploy['default_hooks']['composer'] : FALSE;
+ }
+
+ $return['update'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Run database updates.'),
+ '#default_value' => empty($environment->settings->deploy) ? $project_update : $environment_update,
+ );
+ $return['cache'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Clear all caches.'),
+ '#default_value' => empty($environment->settings->deploy)? $project_cache : $environment_cache,
+ );
+ $return['revert'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Revert all features.'),
+ '#description' => t('If features is enabled, revert all of them.'),
+ '#default_value' => empty($environment->settings->deploy)? $project_revert : $environment_revert,
+ );
+ // Look for .hooks or .hooks.yml
+ if (!$is_environment_form || isset($project->settings->deploy, $project->settings->deploy['default_hooks']['dothooks'])) {
+ $return['dothooks'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Run deploy commands in the .hooks file.'),
+ '#default_value' => empty($environment->settings->deploy) ? $project_dothooks : $environment_dothooks,
+ '#description' => t('You can add your desired deploy hooks to a file in the root folder of your project. This is recommended as it gives your developers control over what happens when their code is deployed. See the !dothooks for more information. If you use a .hooks file you probably want to uncheck the deploy hooks here.', array(
+ '!dothooks' => l(t('.hooks documentation'), 'admin/help/devshop_dothooks'),
+ )),
+ );
+ }
+
+ // Look for acquia cloud hooks.
+ if (module_exists('devshop_acquia') && (!$is_environment_form || isset($project->settings->deploy, $project->settings->deploy['default_hooks']['acquia_hooks']))) {
+ $return['acquia_hooks'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Run Acquia Cloud Hooks'),
+ '#description' => '
' . t('Acquia Cloud Hooks were detected in your project. Check this box to run all acquia cloud hooks.') . '
' . t('Available cloud hooks are: "post-code-update" when deploying code via git, "post-code-deploy" when deploying code manually, "post-db-copy" when running a sync task with a database, and "post-files-copy" when running a sync task with files.') . '
',
+ '#default_value' => empty($environment->settings->deploy) ? $project_acquia_hooks : $environment_acquia_hooks,
+ );
+ }
+
+ // Allow other modules to alter the deploy hooks form elements.
+ // @TODO: Create a new hook: "hook_devshop_deploy_hooks()"
+ drupal_alter('devshop_deploy_hooks_form_elements', $return, $is_environment_form);
+
+ // Disable deploy hooks on environment form, deploy task form ,and sync task form.
+ if (
+ // Environment form
+ ($is_environment_form && !$project->settings->deploy['allow_environment_deploy_config']) ||
+ ($task_type == 'devshop-deploy' && !$project->settings->deploy['allow_environment_deploy_hooks_override'])
+ ) {
+
+ foreach (element_children($return) as $i) {
+ $return[$i]['#type'] = 'value';
+ $return[$i]['#value'] = $project->settings->deploy['default_hooks'][$i];
+
+ $return["{$i}_display"] = array(
+ '#type' => 'markup',
+ '#markup' => t($return[$i]['#title']),
+ '#prefix' => '