Completed
Push — master ( a06f3d...7dd891 )
by Joe
06:11
created

Plugin::EnsureDirExists()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 9
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
cc 4
eloc 7
c 0
b 0
f 0
nc 4
nop 2
dl 0
loc 9
ccs 0
cts 9
cp 0
crap 20
rs 9.2
1
<?php
2
/**
3
 * Plugins Management
4
 * @author Joe Huss <[email protected]>
5
 * @copyright 2017
6
 * @package MyAdmin
7
 * @category Plugins
8
 */
9
10
/**
11
 * MyAdmin Installer Plugin
12
 * @link https://github.com/composer/composer/blob/master/src/Composer/Plugin/PluginInterface.php
13
 */
14
15
namespace MyAdmin\Plugins;
16
17
use Composer\Composer;
18
use Composer\EventDispatcher\EventSubscriberInterface;
19
use Composer\IO\IOInterface;
20
use Composer\Plugin\PluginInterface;
21
use Composer\Plugin\Capable;
22
use Composer\Plugin\PluginEvents;
23
use Composer\Plugin\PreFileDownloadEvent;
24
use Composer\Script\Event;
25
26
/**
27
 * MyAdmin Installer Plugin
28
 */
29
class Plugin implements PluginInterface, EventSubscriberInterface, Capable {
30
	protected $composer;
31
	protected $io;
32
33
	/**
34
	 * Apply plugin modifications to Composer
35
	 *
36
	 * @param Composer	$composer
37
	 * @param IOInterface $io
38
	 */
39
	public function activate(Composer $composer, IOInterface $io) {
40
		$this->composer = $composer;
41
		$this->io = $io;
42
		print 'Hello peoples...';
43
		$installer = new Installer($this->io, $this->composer);
44
		$this->composer->getInstallationManager()->addInstaller($installer);
45
	}
46
47
	/**
48
	 * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string,string>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
49
	 */
50
	public function getCapabilities() {
51
		return [
52
			'Composer\Plugin\Capability\CommandProvider' => 'MyAdmin\Plugins\CommandProvider'
53
		];
54
	}
55
56
	/**
57
	 *
58
	 *	Events
59
	 *
60
	 *		Command Events					Composer\Script\Event
61
	 *
62
	 * 			pre-install-cmd				occurs before the install command is executed with a lock file present.
63
	 * 			post-install-cmd			occurs after the install command has been executed with a lock file present.
64
	 * 			pre-update-cmd				occurs before the update command is executed, or before the install command is executed without a lock file present.
65
	 * 			post-update-cmd				occurs after the update command has been executed, or after the install command has been executed without a lock file present.
66
	 * 			post-status-cmd				occurs after the status command has been executed.
67
	 * 			pre-archive-cmd				occurs before the archive command is executed.
68
	 * 			post-archive-cmd			occurs after the archive command has been executed.
69
	 * 			pre-autoload-dump			occurs before the autoloader is dumped, either during install/update, or via the dump-autoload command.
70
	 * 			post-autoload-dump			occurs after the autoloader has been dumped, either during install/update, or via the dump-autoload command.
71
	 * 			post-root-package-install	occurs after the root package has been installed, during the create-project command.
72
	 *			post-create-project-cmd		occurs after the create-project command has been executed.
73
	 *
74
	 *		Installer Events				Composer\Installer\InstallerEvent
75
	 *
76
	 * 			pre-dependencies-solving	occurs before the dependencies are resolved.
77
	 * 			post-dependencies-solving	occurs after the dependencies have been resolved.
78
	 *
79
	 *		Package Events					Composer\Installer\PackageEvent
80
	 *
81
	 * 			pre-package-install			occurs before a package is installed.
82
	 * 			post-package-install		occurs after a package has been installed.
83
	 * 			pre-package-update			occurs before a package is updated.
84
	 * 			post-package-update			occurs after a package has been updated.
85
	 * 			pre-package-uninstall		occurs before a package is uninstalled.
86
	 * 			post-package-uninstall		occurs after a package has been uninstalled.
87
	 *
88
	 * 		Plugin Events					Composer\Plugin\PluginEvents
89
	 *
90
	 * 			init						occurs after a Composer instance is done being initialized.
91
	 * 			command						occurs before any Composer Command is executed on the CLI. It provides you with access to the input and output objects of the program.
92
	 * 			pre-file-download			occurs before files are downloaded and allows you to manipulate the RemoteFilesystem object prior to downloading files based on the URL to be downloaded.
93
	 */
94
	public static function getSubscribedEvents() {
95
		return [
96
			PluginEvents::PRE_FILE_DOWNLOAD => [
97
				['onPreFileDownload', 0]
98
			]
99
		];
100
	}
101
102
	/**
103
	* @param PreFileDownloadEvent $event
104
	*/
105
	public function onPreFileDownload(PreFileDownloadEvent $event) {
106
		/*$protocol = parse_url($event->getProcessedUrl(), PHP_URL_SCHEME);
0 ignored issues
show
Unused Code Comprehensibility introduced by
59% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
107
		if ($protocol === 's3') {
108
			$awsClient = new AwsClient($this->io, $this->composer->getConfig());
109
			$s3RemoteFilesystem = new S3RemoteFilesystem($this->io, $event->getRemoteFilesystem()->getOptions(), $awsClient);
110
			$event->setRemoteFilesystem($s3RemoteFilesystem);
111
		}*/
112
	}
113
114
	/**
115
	 * An event that triggers setting writable permissions on any directories specified in the writable-dirs composer extra options
116
	 *
117
	 * @param Event $event
118
	 * @return void
119
	 */
120
	public static function setPermissions(Event $event) {
121
		if ('WIN' === strtoupper(substr(PHP_OS, 0, 3))) {
122
			$event->getIO()->write('<info>No permissions setup is required on Windows.</info>');
123
			return;
124
		}
125
		$event->getIO()->write('Setting up permissions.');
126
		try {
127
			self::setPermissionsSetfacl($event);
128
			return;
129
		} catch (\Exception $setfaclException) {
130
			$event->getIO()->write(sprintf('<error>%s</error>', $setfaclException->getMessage()));
131
			$event->getIO()->write('<info>Trying chmod...</info>');
132
		}
133
		try {
134
			self::setPermissionsChmod($event);
135
			return;
136
		} catch (\Exception $chmodException) {
137
			$event->getIO()->write(sprintf('<error>%s</error>', $chmodException->getMessage()));
138
		}
139
	}
140
141
	/**
142
	 * returns a list of writeable directories specified in the writeable-dirs composer extra options
143
	 *
144
	 * @param Event $event
145
	 * @return array an array of directory paths
146
	 */
147
	public static function getWritableDirs(Event $event) {
148
		$configuration = $event->getComposer()->getPackage()->getExtra();
149
		if (!isset($configuration['writable-dirs']))
150
			throw new \Exception('The writable-dirs must be specified in composer arbitrary extra data.');
151
		if (!is_array($configuration['writable-dirs']))
152
			throw new \Exception('The writable-dirs must be an array.');
153
		return $configuration['writable-dirs'];
154
	}
155
156
	/**
157
	 * Sets Writrable Directory permissions for any directories listed in the writeable-dirs option using setfacl
158
	 *
159
	 * @param Event $event
160
	 */
161
	public static function setPermissionsSetfacl(Event $event) {
162
		$http_user = self::getHttpdUser($event);
163
		foreach (self::getWritableDirs($event) as $path)
164
			self::SetfaclPermissionsSetter($event, $http_user, $path);
165
	}
166
167
	/**
168
	 * Sets Writrable Directory permissions for any directories listed in the writeable-dirs option using chmod
169
	 *
170
	 * @param Event $event
171
	 */
172
	public static function setPermissionsChmod(Event $event) {
173
		$http_user = self::getHttpdUser($event);
174
		foreach (self::getWritableDirs($event) as $path)
175
			self::ChmodPermissionsSetter($event, $http_user, $path);
176
	}
177
178
	/**
179
	 * returns the user the webserver is running as
180
	 *
181
	 * @param Event $event
182
	 * @return string the webserver username
0 ignored issues
show
Documentation introduced by
Should the return type not be string|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
183
	 */
184
	public static function getHttpdUser(Event $event) {
185
		$ps = self::runProcess($event, 'ps aux');
186
		preg_match_all('/^.*([a]pache|[h]ttpd|[_]www|[w]ww-data|[n]ginx)$/m', $ps, $matches);
187
		foreach ($matches[0] as $match) {
188
			$user = substr($match, 0, strpos($match, ' '));
189
			if ($user != 'root')
190
				return $user;
191
		}
192
	}
193
194
	/**
195
	 * sets the needed permissions for the $http_user and the running user on $path using setfacl
196
	 *
197
	 * @param Event $event
198
	 * @param string $http_user the webserver username
199
	 * @param string $path the directory to set permissions on
200
	 */
201
	public static function SetfaclPermissionsSetter(Event $event, $http_user, $path) {
0 ignored issues
show
Coding Style introduced by
SetfaclPermissionsSetter uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style Naming introduced by
The parameter $http_user is not named in camelCase.

This check marks parameter names that have not been written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection string becomes databaseConnectionString.

Loading history...
202
		self::EnsureDirExists($event, $path);
203
		self::runProcess($event, 'setfacl -m u:"'.$http_user.'":rwX -m u:'.$_SERVER['USER'].':rwX '.$path);
204
		self::runProcess($event, 'setfacl -d -m u:"'.$http_user.'":rwX -m u:'.$_SERVER['USER'].':rwX '.$path);
205
	}
206
207
	/**
208
	 * sets the needed permissions for the $http_user and the running user on $path using chmod
209
	 *
210
	 * @param Event $event
211
	 * @param string $http_user the webserver username
212
	 * @param string $path the directory to set permissions on
213
	 */
214
	public static function ChmodPermissionsSetter(Event $event, $http_user, $path) {
0 ignored issues
show
Coding Style introduced by
ChmodPermissionsSetter uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style Naming introduced by
The parameter $http_user is not named in camelCase.

This check marks parameter names that have not been written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection string becomes databaseConnectionString.

Loading history...
215
		self::EnsureDirExists($event, $path);
216
		self::runProcess($event, 'chmod +a "'.$http_user.' allow delete,write,append,file_inherit,directory_inherit" '.$path);
217
		self::runProcess($event, 'chmod +a "'.$_SERVER['USER'].' allow delete,write,append,file_inherit,directory_inherit" '.$path);
218
	}
219
220
	/**
221
	 * checks if the given directory exists and if not tries to create it.
222
	 *
223
	 * @param Event $event
224
	 * @param string $path the directory
225
	 * @throws \Exception
226
	 */
227
	public static function EnsureDirExists(Event $event, $path) {
228
		if (!is_dir($path)) {
229
			mkdir($path, 0777, true);
230
			if (!is_dir($path))
231
				throw new \Exception('Path Not Found: '.$path);
232
			if ($event->getIO()->isVerbose() === TRUE)
233
				$event->getIO()->write(sprintf('Created Directory <info>%s</info>', $path));
234
		}
235
	}
236
237
	/**
238
	 * runs a command process returning the output and checking return code
239
	 *
240
	 * @param Event $event
241
	 * @param string $commandline the command line to run
242
	 * @return string the output
243
	 * @throws \Exception
244
	 */
245
	public static function runProcess(Event $event, $commandline) {
246
		if ($event->getIO()->isVerbose() === TRUE)
247
			$event->getIO()->write(sprintf('Running <info>%s</info>', $commandline));
248
		exec($commandline, $output, $return);
249
		if ($return != 0)
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $return of type integer|null to 0; this is ambiguous as not only 0 == 0 is true, but null == 0 is true, too. Consider using a strict comparison ===.
Loading history...
250
			throw new \Exception('Returned Error Code '.$return);
251
		return implode(PHP_EOL, $output);
252
	}
253
}
254