Minor code style fixes
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / system / devtools / pip / DevtoolsPip.class.php
1 <?php
2 namespace wcf\system\devtools\pip;
3 use wcf\data\devtools\project\DevtoolsProject;
4 use wcf\data\package\installation\plugin\PackageInstallationPlugin;
5 use wcf\data\DatabaseObjectDecorator;
6 use wcf\system\application\ApplicationHandler;
7 use wcf\system\WCF;
8 use wcf\util\FileUtil;
9 use wcf\util\JSON;
10
11 /**
12 * Wrapper class for package installation plugins for use with the sync feature.
13 *
14 * @author Alexander Ebert
15 * @copyright 2001-2017 WoltLab GmbH
16 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
17 * @package WoltLabSuite\Core\System\Devtools\Pip
18 * @since 3.1
19 *
20 * @method PackageInstallationPlugin getDecoratedObject()
21 * @mixin PackageInstallationPlugin
22 */
23 class DevtoolsPip extends DatabaseObjectDecorator {
24 /**
25 * @inheritDoc
26 */
27 protected static $baseClass = PackageInstallationPlugin::class;
28
29 /**
30 * Returns true if the PIP class can be found.
31 *
32 * @return boolean
33 */
34 public function classExists() {
35 return class_exists($this->getDecoratedObject()->className);
36 }
37
38 /**
39 * Returns true if the PIP is expected to be idempotent.
40 *
41 * @return boolean
42 */
43 public function isIdempotent() {
44 return is_subclass_of($this->getDecoratedObject()->className, IIdempotentPackageInstallationPlugin::class);
45 }
46
47 /**
48 * Returns the default filename of this PIP.
49 *
50 * @return string
51 */
52 public function getDefaultFilename() {
53 return call_user_func([$this->getDecoratedObject()->className, 'getDefaultFilename']);
54 }
55
56 public function getEffectiveDefaultFilename() {
57 return './' . preg_replace('~\.tar$~', '/', $this->getDefaultFilename());
58 }
59
60 /**
61 * Returns true if the PIP exists, has a default filename and is idempotent.
62 *
63 * @return boolean
64 */
65 public function isSupported() {
66 return $this->classExists() && $this->getDefaultFilename() && $this->isIdempotent();
67 }
68
69 public function getSyncDependencies($toJson = true) {
70 $dependencies = call_user_func([$this->getDecoratedObject()->className, 'getSyncDependencies']);
71
72 return ($toJson) ? JSON::encode($dependencies) : $dependencies;
73 }
74
75 /**
76 * Returns the first validation error.
77 *
78 * @return string
79 */
80 public function getFirstError() {
81 if (!$this->classExists()) {
82 return WCF::getLanguage()->getDynamicVariable('wcf.acp.devtools.pip.error.className', ['className' => $this->getDecoratedObject()->className]);
83 }
84 else if (!$this->isIdempotent()) {
85 return WCF::getLanguage()->get('wcf.acp.devtools.pip.error.notIdempotent');
86 }
87 else if (!$this->getDefaultFilename()) {
88 return WCF::getLanguage()->get('wcf.acp.devtools.pip.error.defaultFilename');
89 }
90
91 throw new \LogicException("Please call `isSupported()` to check for potential errors.");
92 }
93
94 /**
95 * Returns the list of valid targets for this pip.
96 *
97 * @param DevtoolsProject $project
98 * @return string[]
99 */
100 public function getTargets(DevtoolsProject $project) {
101 if (!$this->isSupported()) {
102 return [];
103 }
104
105 $path = $project->path;
106 $defaultFilename = $this->getDefaultFilename();
107 $targets = [];
108
109 // the core uses a significantly different file layout
110 if ($project->isCore()) {
111 switch ($this->getDecoratedObject()->pluginName) {
112 case 'acpTemplate':
113 case 'file':
114 case 'template':
115 // these pips are satisfied by definition
116 return [$defaultFilename];
117
118 case 'language':
119 foreach (glob($path . 'wcfsetup/install/lang/*.xml') as $file) {
120 $targets[] = basename($file);
121 }
122
123 // `glob()` returns files in an arbitrary order
124 sort($targets, SORT_NATURAL);
125
126 return $targets;
127 }
128
129 if (strpos($defaultFilename, '*') !== false) {
130 foreach (glob($path . 'com.woltlab.wcf/' . $defaultFilename) as $file) {
131 $targets[] = basename($file);
132 }
133
134 // `glob()` returns files in an arbitrary order
135 sort($targets, SORT_NATURAL);
136 }
137 else {
138 if (file_exists($path . 'com.woltlab.wcf/' . $defaultFilename)) {
139 $targets[] = $defaultFilename;
140 }
141 }
142 }
143 else {
144 if (preg_match('~^(?<filename>.*)\.tar$~', $defaultFilename, $match)) {
145 if (is_dir($path . $match['filename'])) {
146 $targets[] = $defaultFilename;
147 }
148
149 // check for application-specific pips too
150 foreach (ApplicationHandler::getInstance()->getAbbreviations() as $abbreviation) {
151 if (is_dir($path . $match['filename'] . '_' . $abbreviation)) {
152 $targets[] = $match['filename'] . "_{$abbreviation}.tar";
153 }
154 }
155 }
156 else {
157 if (strpos($defaultFilename, '*') !== false) {
158 foreach (glob($path . $defaultFilename) as $file) {
159 $targets[] = basename($file);
160 }
161
162 // `glob()` returns files in an arbitrary order
163 sort($targets, SORT_NATURAL);
164 }
165 else {
166 if (file_exists($path . $defaultFilename)) {
167 $targets[] = $defaultFilename;
168 }
169 }
170 }
171 }
172
173 return $targets;
174 }
175
176 /**
177 * Computes and prepares the instruction value for the provided target file.
178 *
179 * @param DevtoolsProject $project
180 * @param string $target
181 * @return string
182 */
183 public function getInstructionValue(DevtoolsProject $project, $target) {
184 $defaultFilename = $this->getDefaultFilename();
185 $pluginName = $this->getDecoratedObject()->pluginName;
186 $tar = $project->getPackageArchive()->getTar();
187 $tar->reset();
188
189 if ($project->isCore()) {
190 switch ($pluginName) {
191 case 'acpTemplate':
192 case 'file':
193 case 'template':
194 if ($pluginName === 'acpTemplate' || $pluginName === 'template') {
195 $path = ($pluginName === 'acpTemplate') ? 'wcfsetup/install/files/acp/templates/' : 'com.woltlab.wcf/templates/';
196 foreach (glob($project->path . $path . '*.tpl') as $template) {
197 $tar->registerFile(basename($template), FileUtil::unifyDirSeparator($template));
198 }
199 }
200 else {
201 $path = 'wcfsetup/install/files/';
202
203 $directory = new \RecursiveDirectoryIterator($project->path . $path);
204 $filter = new \RecursiveCallbackFilterIterator($directory, function ($current) {
205 /** @var \SplFileInfo $current */
206 $filename = $current->getFilename();
207 if ($filename[0] === '.') {
208 // ignore dot files and files/directories starting with a dot
209 return false;
210 }
211 else if ($filename === 'templates') {
212 // ignores both `templates` and `acp/templates`
213 return false;
214 }
215
216 return true;
217 });
218
219 $iterator = new \RecursiveIteratorIterator($filter, \RecursiveIteratorIterator::SELF_FIRST);
220 foreach ($iterator as $value => $item) {
221 /** @var \SplFileInfo $item */
222 $itemPath = $item->getRealPath();
223 if (is_dir($itemPath)) continue;
224
225 $tar->registerFile(
226 FileUtil::getRelativePath($project->path . $path, $item->getPath()) . $item->getFilename(),
227 $itemPath
228 );
229 }
230 }
231
232 return $defaultFilename;
233
234 case 'language':
235 $filename = "wcfsetup/install/lang/{$target}";
236 $tar->registerFile($filename, $project->path . $filename);
237
238 return $filename;
239
240 default:
241 $filename = "com.woltlab.wcf/{$target}";
242 $tar->registerFile($filename, $project->path . $filename);
243
244 return $filename;
245 }
246 }
247 else {
248 switch ($pluginName) {
249 case 'acpTemplate':
250 case 'file':
251 case 'template':
252 if ($pluginName === 'acpTemplate' || $pluginName === 'template') {
253 $path = ($pluginName === 'acpTemplate') ? 'acptemplates/' : 'templates/';
254 foreach (glob($project->path . $path . '*.tpl') as $template) {
255 $tar->registerFile(basename($template), FileUtil::unifyDirSeparator($template));
256 }
257 }
258 else {
259 $path = 'files/';
260
261 $directory = new \RecursiveDirectoryIterator($project->path . $path);
262 $filter = new \RecursiveCallbackFilterIterator($directory, function ($current) {
263 /** @var \SplFileInfo $current */
264 $filename = $current->getFilename();
265 if ($filename[0] === '.') {
266 // ignore dot files and files/directories starting with a dot
267 return false;
268 }
269
270 return true;
271 });
272
273 $iterator = new \RecursiveIteratorIterator($filter, \RecursiveIteratorIterator::SELF_FIRST);
274 foreach ($iterator as $value => $item) {
275 /** @var \SplFileInfo $item */
276 $itemPath = $item->getRealPath();
277 if (is_dir($itemPath)) continue;
278
279 $tar->registerFile(
280 FileUtil::getRelativePath($project->path . $path, $item->getPath()) . $item->getFilename(),
281 $itemPath
282 );
283 }
284 }
285
286 return $defaultFilename;
287
288 default:
289 if (strpos($defaultFilename, '*') !== false) {
290 $filename = str_replace('*', $target, $defaultFilename);
291 $tar->registerFile($filename, $project->path . $filename);
292 }
293 else {
294 $filename = "com.woltlab.wcf/{$target}";
295 $tar->registerFile($filename, $project->path . $filename);
296 }
297
298 return $filename;
299 }
300 }
301 }
302 }