INFRA-132 - CRM/Case - Misc
[civicrm-core.git] / CRM / Extension / Downloader.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
39de6fd5 4 | CiviCRM version 4.6 |
6a488035 5 +--------------------------------------------------------------------+
06b69b18 6 | Copyright CiviCRM LLC (c) 2004-2014 |
6a488035
TO
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
13 | |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
18 | |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
26*/
27
28/**
29 * This class handles downloads of remotely-provided extensions
30 *
31 * @package CRM
06b69b18 32 * @copyright CiviCRM LLC (c) 2004-2014
6a488035
TO
33 * $Id$
34 *
35 */
36class CRM_Extension_Downloader {
37 /**
38 * @var CRM_Extension_Container_Basic the place where downloaded extensions are ultimately stored
39 */
40 public $container;
41
42 /**
43 * @var string local path to a temporary data directory
44 */
45 public $tmpDir;
46
47 /**
fd31fa4c 48 * @param CRM_Extension_Manager $manager
f41911fd
TO
49 * @param string $containerDir
50 * The place to store downloaded & extracted extensions.
6a488035
TO
51 * @param string $tmpDir
52 */
53 public function __construct(CRM_Extension_Manager $manager, $containerDir, $tmpDir) {
54 $this->manager = $manager;
55 $this->containerDir = $containerDir;
56 $this->tmpDir = $tmpDir;
57 }
58
59 /**
60 * Determine whether downloading is supported
61 *
62 * @return array list of error messages; empty if OK
63 */
64 public function checkRequirements() {
65 $errors = array();
66
67 if (!$this->containerDir || !is_dir($this->containerDir) || !is_writeable($this->containerDir)) {
68 $civicrmDestination = urlencode(CRM_Utils_System::url('civicrm/admin/extensions', 'reset=1'));
69 $url = CRM_Utils_System::url('civicrm/admin/setting/path', "reset=1&civicrmDestination=${civicrmDestination}");
70 $errors[] = array(
71 'title' => ts('Directory Unwritable'),
72 //'message' => ts('Your extensions directory: %1 is not web server writable. Please go to the <a href="%2">path setting page</a> and correct it.<br/>',
73 'message' => ts("Your extensions directory is not set or is not writable. Click <a href='%1'>here</a> to set the extensions directory.",
74 array(
75 //1 => $this->containerDir,
76 1 => $url,
77 )
78 )
79 );
80 }
81
82 if (!class_exists('ZipArchive')) {
83 $errors[] = array(
84 'title' => ts('ZIP Support Required'),
85 'message' => ts('You will not be able to install extensions at this time because your installation of PHP does not support ZIP archives. Please ask your system administrator to install the standard PHP-ZIP extension.'),
86 );
87 }
88
eb066397
TO
89 if (empty($errors) && ! CRM_Utils_HttpClient::singleton()->isRedirectSupported()) {
90 CRM_Core_Session::setStatus(ts('WARNING: The downloader may be unable to download files which require HTTP redirection. This may be a configuration issue with PHP\'s open_basedir or safe_mode.'));
91 CRM_Core_Error::debug_log_message('WARNING: The downloader may be unable to download files which require HTTP redirection. This may be a configuration issue with PHP\'s open_basedir or safe_mode.');
92 }
93
6a488035
TO
94 return $errors;
95 }
96
97 /**
98 * Install or upgrade an extension from a remote URL
99 *
f41911fd
TO
100 * @param string $key
101 * The name of the extension being installed.
102 * @param string $downloadUrl
103 * URL of a .zip file.
6a488035
TO
104 * @return bool TRUE for success
105 * @throws CRM_Extension_Exception
106 */
107 public function download($key, $downloadUrl) {
108 $filename = $this->tmpDir . DIRECTORY_SEPARATOR . $key . '.zip';
109 $destDir = $this->containerDir . DIRECTORY_SEPARATOR . $key;
110
111 if (!$downloadUrl) {
112 CRM_Core_Error::fatal('Cannot install this extension - downloadUrl is not set!');
113 }
114
115 if (! $this->fetch($downloadUrl, $filename)) {
116 return FALSE;
117 }
118
119 $extractedZipPath = $this->extractFiles($key, $filename);
120 if (! $extractedZipPath) {
121 return FALSE;
122 }
123
124 if (! $this->validateFiles($key, $extractedZipPath)) {
125 return FALSE;
126 }
127
128 $this->manager->replace($extractedZipPath);
129
130 return TRUE;
131 }
132
133 /**
134 * Download the remote zipfile.
135 *
f41911fd
TO
136 * @param string $remoteFile
137 * URL of a .zip file.
138 * @param string $localFile
139 * Path at which to store the .zip file.
6a488035
TO
140 * @return boolean Whether the download was successful.
141 */
142 public function fetch($remoteFile, $localFile) {
3b6f287b 143 $result = CRM_Utils_HttpClient::singleton()->fetch($remoteFile, $localFile);
6a488035
TO
144 switch ($result) {
145 case CRM_Utils_HttpClient::STATUS_OK:
146 return TRUE;
b3a4b879 147
6a488035
TO
148 default:
149 return FALSE;
150 }
151 }
152
153 /**
154 * Extract an extension from a zip file
155 *
f41911fd
TO
156 * @param string $key
157 * The name of the extension being installed; this usually matches the basedir in the .zip.
158 * @param string $zipFile
159 * The local path to a .zip file.
6a488035
TO
160 * @return string|FALSE zip file path
161 */
162 public function extractFiles($key, $zipFile) {
163 $config = CRM_Core_Config::singleton();
164
165 $zip = new ZipArchive();
166 $res = $zip->open($zipFile);
167 if ($res === TRUE) {
168 $zipSubDir = CRM_Utils_Zip::guessBasedir($zip, $key);
169 if ($zipSubDir === FALSE) {
170 CRM_Core_Session::setStatus(ts('Unable to extract the extension: bad directory structure'), '', 'error');
171 return FALSE;
172 }
173 $extractedZipPath = $this->tmpDir . DIRECTORY_SEPARATOR . $zipSubDir;
174 if (is_dir($extractedZipPath)) {
175 if (!CRM_Utils_File::cleanDir($extractedZipPath, TRUE, FALSE)) {
176 CRM_Core_Session::setStatus(ts('Unable to extract the extension: %1 cannot be cleared', array(1 => $extractedZipPath)), ts('Installation Error'), 'error');
177 return FALSE;
178 }
179 }
180 if (!$zip->extractTo($this->tmpDir)) {
181 CRM_Core_Session::setStatus(ts('Unable to extract the extension to %1.', array(1 => $this->tmpDir)), ts('Installation Error'), 'error');
182 return FALSE;
183 }
184 $zip->close();
185 }
186 else {
187 CRM_Core_Session::setStatus(ts('Unable to extract the extension.'), '', 'error');
188 return FALSE;
189 }
190
191 return $extractedZipPath;
192 }
193
194 /**
195 * Validate that $extractedZipPath contains valid for extension $key
196 *
da6b46f4
EM
197 * @param $key
198 * @param $extractedZipPath
199 *
6a488035
TO
200 * @return bool
201 */
00be9182 202 public function validateFiles($key, $extractedZipPath) {
6a488035
TO
203 $filename = $extractedZipPath . DIRECTORY_SEPARATOR . CRM_Extension_Info::FILENAME;
204 if (!is_readable($filename)) {
205 CRM_Core_Session::setStatus(ts('Failed reading data from %1 during installation', array(1 => $filename)), ts('Installation Error'), 'error');
206 return FALSE;
207 }
208
209 try {
210 $newInfo = CRM_Extension_Info::loadFromFile($filename);
211 } catch (Exception $e) {
212 CRM_Core_Session::setStatus(ts('Failed reading data from %1 during installation', array(1 => $filename)), ts('Installation Error'), 'error');
213 return FALSE;
214 }
215
216 if ($newInfo->key != $key) {
217 CRM_Core_Error::fatal('Cannot install - there are differences between extdir XML file and archive XML file!');
218 }
219
220 return TRUE;
221 }
222
223}