copyright and version fixes
[civicrm-core.git] / CRM / Extension / Downloader.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
06b69b18 4 | CiviCRM version 4.5 |
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 /**
48 * @param string $containerDir the place to store downloaded & extracted extensions
49 * @param string $tmpDir
50 */
51 public function __construct(CRM_Extension_Manager $manager, $containerDir, $tmpDir) {
52 $this->manager = $manager;
53 $this->containerDir = $containerDir;
54 $this->tmpDir = $tmpDir;
55 }
56
57 /**
58 * Determine whether downloading is supported
59 *
60 * @return array list of error messages; empty if OK
61 */
62 public function checkRequirements() {
63 $errors = array();
64
65 if (!$this->containerDir || !is_dir($this->containerDir) || !is_writeable($this->containerDir)) {
66 $civicrmDestination = urlencode(CRM_Utils_System::url('civicrm/admin/extensions', 'reset=1'));
67 $url = CRM_Utils_System::url('civicrm/admin/setting/path', "reset=1&civicrmDestination=${civicrmDestination}");
68 $errors[] = array(
69 'title' => ts('Directory Unwritable'),
70 //'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/>',
71 'message' => ts("Your extensions directory is not set or is not writable. Click <a href='%1'>here</a> to set the extensions directory.",
72 array(
73 //1 => $this->containerDir,
74 1 => $url,
75 )
76 )
77 );
78 }
79
80 if (!class_exists('ZipArchive')) {
81 $errors[] = array(
82 'title' => ts('ZIP Support Required'),
83 '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.'),
84 );
85 }
86
eb066397
TO
87 if (empty($errors) && ! CRM_Utils_HttpClient::singleton()->isRedirectSupported()) {
88 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.'));
89 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.');
90 }
91
6a488035
TO
92 return $errors;
93 }
94
95 /**
96 * Install or upgrade an extension from a remote URL
97 *
98 * @param string $key the name of the extension being installed
99 * @param string $downloadUrl URL of a .zip file
100 * @return bool TRUE for success
101 * @throws CRM_Extension_Exception
102 */
103 public function download($key, $downloadUrl) {
104 $filename = $this->tmpDir . DIRECTORY_SEPARATOR . $key . '.zip';
105 $destDir = $this->containerDir . DIRECTORY_SEPARATOR . $key;
106
107 if (!$downloadUrl) {
108 CRM_Core_Error::fatal('Cannot install this extension - downloadUrl is not set!');
109 }
110
111 if (! $this->fetch($downloadUrl, $filename)) {
112 return FALSE;
113 }
114
115 $extractedZipPath = $this->extractFiles($key, $filename);
116 if (! $extractedZipPath) {
117 return FALSE;
118 }
119
120 if (! $this->validateFiles($key, $extractedZipPath)) {
121 return FALSE;
122 }
123
124 $this->manager->replace($extractedZipPath);
125
126 return TRUE;
127 }
128
129 /**
130 * Download the remote zipfile.
131 *
132 * @param string $remoteFile URL of a .zip file
133 * @param string $localFile path at which to store the .zip file
134 * @return boolean Whether the download was successful.
135 */
136 public function fetch($remoteFile, $localFile) {
3b6f287b 137 $result = CRM_Utils_HttpClient::singleton()->fetch($remoteFile, $localFile);
6a488035
TO
138 switch ($result) {
139 case CRM_Utils_HttpClient::STATUS_OK:
140 return TRUE;
141 default:
142 return FALSE;
143 }
144 }
145
146 /**
147 * Extract an extension from a zip file
148 *
149 * @param string $key the name of the extension being installed; this usually matches the basedir in the .zip
150 * @param string $zipFile the local path to a .zip file
151 * @return string|FALSE zip file path
152 */
153 public function extractFiles($key, $zipFile) {
154 $config = CRM_Core_Config::singleton();
155
156 $zip = new ZipArchive();
157 $res = $zip->open($zipFile);
158 if ($res === TRUE) {
159 $zipSubDir = CRM_Utils_Zip::guessBasedir($zip, $key);
160 if ($zipSubDir === FALSE) {
161 CRM_Core_Session::setStatus(ts('Unable to extract the extension: bad directory structure'), '', 'error');
162 return FALSE;
163 }
164 $extractedZipPath = $this->tmpDir . DIRECTORY_SEPARATOR . $zipSubDir;
165 if (is_dir($extractedZipPath)) {
166 if (!CRM_Utils_File::cleanDir($extractedZipPath, TRUE, FALSE)) {
167 CRM_Core_Session::setStatus(ts('Unable to extract the extension: %1 cannot be cleared', array(1 => $extractedZipPath)), ts('Installation Error'), 'error');
168 return FALSE;
169 }
170 }
171 if (!$zip->extractTo($this->tmpDir)) {
172 CRM_Core_Session::setStatus(ts('Unable to extract the extension to %1.', array(1 => $this->tmpDir)), ts('Installation Error'), 'error');
173 return FALSE;
174 }
175 $zip->close();
176 }
177 else {
178 CRM_Core_Session::setStatus(ts('Unable to extract the extension.'), '', 'error');
179 return FALSE;
180 }
181
182 return $extractedZipPath;
183 }
184
185 /**
186 * Validate that $extractedZipPath contains valid for extension $key
187 *
188 * @return bool
189 */
190 function validateFiles($key, $extractedZipPath) {
191 $filename = $extractedZipPath . DIRECTORY_SEPARATOR . CRM_Extension_Info::FILENAME;
192 if (!is_readable($filename)) {
193 CRM_Core_Session::setStatus(ts('Failed reading data from %1 during installation', array(1 => $filename)), ts('Installation Error'), 'error');
194 return FALSE;
195 }
196
197 try {
198 $newInfo = CRM_Extension_Info::loadFromFile($filename);
199 } catch (Exception $e) {
200 CRM_Core_Session::setStatus(ts('Failed reading data from %1 during installation', array(1 => $filename)), ts('Installation Error'), 'error');
201 return FALSE;
202 }
203
204 if ($newInfo->key != $key) {
205 CRM_Core_Error::fatal('Cannot install - there are differences between extdir XML file and archive XML file!');
206 }
207
208 return TRUE;
209 }
210
211}