1: <?php
2: declare(strict_types=1);
3: /**
4: * +------------------------------------------------------------+
5: * | apnscp |
6: * +------------------------------------------------------------+
7: * | Copyright (c) Apis Networks |
8: * +------------------------------------------------------------+
9: * | Licensed under Artistic License 2.0 |
10: * +------------------------------------------------------------+
11: * | Author: Matt Saladna (msaladna@apisnetworks.com) |
12: * +------------------------------------------------------------+
13: */
14:
15: /**
16: * Magento management
17: *
18: * An interface to wp-cli
19: *
20: * @package core
21: */
22: class Magento_Module extends \Module\Support\Webapps\Magento
23: {
24: const APP_NAME = 'Magento';
25: // primary domain document root
26: const MAGENTO_CLI = '/usr/share/pear/n98-magerun.phar';
27: const MAGENTO2_CLI = '/usr/share/pear/n98-magerun2.phar';
28: // latest release
29: const MAGENTO_CLI_URL = 'https://files.magerun.net/n98-magerun.phar';
30: const MAGENTO2_CLI_URL = 'https://files.magerun.net/n98-magerun2.phar';
31:
32: const VERSION_CHECK_URL = 'http://mirror.apisnetworks.com/magento';
33: const ADMIN_URL = '/admin';
34:
35: const DEFAULT_VERSION_LOCK = 'major';
36:
37: protected $aclList = array(
38: 'min' => array(
39: 'media',
40: 'var',
41: 'downloader',
42: 'generated',
43: 'pub/static'
44: ),
45: 'max' => array(
46: 'media/downloadable',
47: 'downloader',
48: 'var/cache',
49: 'var/session',
50: 'var/page_cache',
51: 'var/log',
52: 'generated/code',
53: 'generated/metadata',
54: 'pub/static'
55: )
56: );
57:
58: /**
59: * void __construct(void)
60: *
61: * @ignore
62: */
63: public function __construct()
64: {
65:
66: parent::__construct();
67: }
68:
69: /**
70: * Install Magento into a pre-existing location
71: *
72: * @param string $hostname domain or subdomain to install Magento
73: * @param string $path optional path under hostname
74: * @param array $opts additional install options
75: * @return bool
76: */
77: public function install(string $hostname, string $path = '', array $opts = array()): bool
78: {
79: if (!$this->mysql_enabled()) {
80: return error('%(what)s must be enabled to install %(app)s',
81: ['what' => 'MySQL', 'app' => static::APP_NAME]);
82: }
83: $docroot = $this->getAppRoot($hostname, $path);
84: if (!$docroot) {
85: return error('failed to install Magento');
86: }
87:
88: if (!$this->parseInstallOptions($opts, $hostname, $path)) {
89: return false;
90: }
91:
92: if (!is_debug() && !$this->ssl_permitted()) {
93: return error('Account requires SSL enabled on account to manage a Magento store');
94: }
95: if (empty($opts['ssl'])) {
96: return error('Magento requires SSL to operate');
97: }
98:
99: $version = $opts['version'];
100: $args = array();
101: // cap to 1.x branch
102: $vercap = !empty($args['version']) && $args['version'][0] === 2 && $this->mysql_version() < 50600;
103: if (!$vercap && !\Opcenter\Php::extensionEnabled('intl')) {
104: warn('intl extension missing - Magento capped to 1.x branch');
105: $vercap = true;
106: }
107: if (version_compare(Opcenter\Php::version(), '7.1', '<')) {
108: warn('Magento 2.x requires PHP 7.1+. Capping to 1.x');
109: $version = $this->getLatestVersion('1');
110: } else if (null !== $version) {
111: if (!$this->_versionValid($version)) {
112: return error("unknown Magento version `%s'", $version);
113: }
114: if ($vercap) {
115: warn('capping magento version to 1.x');
116: $version = $this->getLatestVersion('1');
117: }
118: } else {
119: $version = $this->getLatestVersion($vercap ? '1' : null);
120: }
121: $args['version'] = $version;
122:
123: if (version_compare($args['version'], '2', '>=')) {
124: if (version_compare($args['version'], '2.2', '>=') && version_compare(Opcenter\Php::version(), '7.1',
125: '<')) {
126: return error('Magento 2.2+ required for PHP 7.1');
127: }
128: $args['vername'] = 'magento-ce-' . $args['version'];
129: } else {
130: // 1.x
131: $args['vername'] = 'apnscp-mirror-' . $args['version'];
132: }
133: if (!$this->get_key() && version_compare($args['version'], '2.0', '>=')) {
134: return error('Magento Connect key must be setup first, do so via Account > Settings');
135: }
136: $fqdn = $this->web_normalize_hostname($hostname);
137: $args['baseurl'] = 'http://' . $fqdn;
138: if ($path) {
139: $args['baseurl'] .= '/' . $path;
140: }
141:
142: $db = \Module\Support\Webapps\DatabaseGenerator::mysql($this->getAuthContext(), $hostname);
143: if (!$db->create()) {
144: return false;
145: }
146:
147: $args['dbuser'] = $db->username;
148: $args['dbpass'] = $db->password;
149: $args['dbname'] = $db->database;
150: $args['dbhost'] = $db->hostname;
151: $args['docroot'] = $docroot;
152: $magerunver = 1;
153: if (version_compare($args['version'], '2.0', '>=')) {
154: $magerunver = 2;
155: }
156:
157: // copy custom config to user
158: $magerunconf = $this->_copyMagerunConfig($magerunver);
159:
160: $cmd = 'install -n --installationFolder=%(docroot)s --dbHost=%(dbhost)s --baseUrl=%(baseurl)s ' .
161: '--dbUser=%(dbuser)s --dbPass=%(dbpass)s --installSampleData=no --dbName=%(dbname)s --magentoVersionByName=' .
162: '%(vername)s';
163: $ret = $this->_exec($docroot, $cmd, $args, $magerunver);
164: if ($magerunconf && $this->file_exists($magerunconf)) {
165: $this->file_delete($magerunconf);
166: }
167: if (!$ret['success']) {
168: info('removing temporary files');
169: $this->file_delete($docroot, true);
170: $db->rollback();
171: if (false !== strpos($ret['stderr'], 'to locate Magento version')) {
172: return error("failed to install magento: unknown Magento version `%s'",
173: $args['version']
174: );
175: }
176:
177: return error('failed to install magento: %s', coalesce($ret['stderr'], $ret['stdout']));
178: }
179: /** post install fixup */
180: // we install with some stupid defaults, change them
181: $ret = $this->_exec($docroot, 'admin:user:delete -f admin');
182: if (!$ret['success']) {
183: warn('failed to delete placeholder admin');
184: }
185: $this->_exec($docroot, 'cache:enable');
186:
187: $autogenpw = false;
188: if (!isset($opts['password'])) {
189: $autogenpw = true;
190: $opts['password'] = \Opcenter\Auth\Password::generate(10);
191: info("autogenerated password `%s'", $opts['password']);
192: }
193:
194: info("setting admin user to `%s'", $opts['username']);
195:
196: $args = array(
197: 'email' => $opts['email'],
198: 'user' => $opts['user'],
199: 'password' => $opts['password']
200: );
201: if ($magerunver === 1) {
202: $this->_exec($docroot, 'admin:user:create %(user)s %(email)s %(password)s Store Admin', $args);
203: } else {
204: $this->_exec($docroot, 'admin:user:create --admin-user %(user)s --admin-email %(email)s ' .
205: '--admin-password %(password)s --admin-firstname Store --admin-lastname Admin', $args);
206: }
207: if (!$ret['success']) {
208: info('removing temporary files');
209: $this->file_delete($docroot, true);
210: $db->rollback();
211:
212: return error('failed to create admin user: %s', $ret['stdout']);
213: }
214:
215: // by default, let's only open up ACLs to the bare minimum
216:
217: $this->file_touch($docroot . '/.htaccess');
218: parent::fixRewriteBase($docroot, $path);
219: parent::fixSymlinkTraversal($docroot);
220: if ($magerunver === 2) {
221: parent::fixRewriteBase($docroot . '/pub', rtrim($path, '/') . '/');
222: parent::fixRewriteBase($docroot . '/pub/static', rtrim($path, '/') . '/static');
223: }
224:
225: // simple confirmation
226: $version = $this->get_version($hostname, $path);
227: $params = array(
228: 'version' => $version,
229: 'hostname' => $hostname,
230: 'path' => $path,
231: 'autoupdate' => (bool)$opts['autoupdate'],
232: );
233: $this->map('add', $docroot, $params);
234: $this->fortify($hostname, $path, 'max');
235: if ($this->guessMajor($docroot) === 1) {
236: $this->file_chmod($docroot . '/app/etc/local.xml', 644);
237: $this->_fixModelPHP7($docroot);
238: }
239: $this->_fixConnectConfig($docroot);
240: $url = 'https://' . $fqdn . '/' . $path . '/';
241: $args = array(
242: 'path' => 'web/secure/base_url',
243: 'url' => rtrim($url, '/') . '/'
244: );
245: $this->_exec($docroot, 'config:set %(path)s %(url)s', $args);
246: if ($magerunver === 2) {
247: $this->_exec($docroot, 'config:set web/secure/use_in_frontend 1');
248: }
249: $this->_exec($docroot, 'cache:flush');
250:
251: /**
252: * @todo relink domain/subdomain to pub/
253: * setup cron
254: * ssl
255: */
256: if ($magerunver === 1) {
257: $this->_fixModelPHP7($docroot);
258: }
259:
260: if (array_get($opts, 'notify', true)) {
261: $msg = 'Hello!' . "\r\n" .
262: 'This is a confirmation that Magento has been installed under ' . $docroot .
263: '. You may access Magento via ' . $url . '. Access the administrative ' .
264: 'panel at ' . rtrim($url, '/') . self::ADMIN_URL . ' using the following details:' . "\r\n\r\n" .
265: 'Username: ' . $opts['user'] . "\r\n" .
266: ($autogenpw ? 'Password: ' . $opts['password'] . "\r\n" : '');
267: $msg .= "\r\nWhen installing plugins or themes, you will need to use your " .
268: 'control panel password!';
269: $hdrs = 'From: ' . Crm_Module::FROM_NAME . ' <' .
270: Crm_Module::FROM_ADDRESS . ">\r\nReply-To: " . Crm_Module::REPLY_ADDRESS;
271: Mail::send($opts['email'], 'Magento Installed', $msg, $hdrs);
272: }
273:
274: return info('%(app)s installed - confirmation email with login info sent to %(email)s',
275: ['app' => static::APP_NAME, 'email' => $opts['email']]);
276: }
277:
278: protected function getAppRoot(string $hostname, string $path = ''): ?string
279: {
280: return parent::getAppRoot($hostname, $path);
281: }
282:
283: /**
284: * Requested version is known by magerun
285: *
286: * @param string $version
287: * @return bool
288: */
289: private function _versionValid($version): bool
290: {
291: $versions = $this->_getVersions();
292:
293: return in_array($version, $versions);
294: }
295:
296: /**
297: * Get all current major versions
298: *
299: * @return array
300: */
301: protected function _getVersions(): array
302: {
303: $key = 'magento.versions';
304: $cache = Cache_Super_Global::spawn();
305: // @TODO
306:
307: if (false !== ($vers = $cache->get($key))) {
308: return $vers;
309: }
310:
311: $req = file_get_contents(self::VERSION_CHECK_URL . '?all');
312: if (!$req) {
313: return array();
314: }
315: $req = json_decode($req);
316: $cache->set($key, $req, 43200);
317:
318: return $req;
319: }
320:
321: /**
322: * Get Magento key for use with Magento Connect
323: *
324: * @return mixed
325: */
326: public function get_key()
327: {
328: $file = $this->_keyAuthFile();
329: if (!$this->file_exists($file)) {
330: return null;
331: }
332: $contents = json_decode($this->file_get_file_contents($file), true);
333: if (!isset($contents['http-basic']['repo.magento.com'])) {
334: return null;
335: }
336: $tmp = $contents['http-basic']['repo.magento.com'];
337:
338: return array($tmp['username'], $tmp['password']);
339: }
340:
341: private function _keyAuthFile()
342: {
343: $home = $this->user_get_home();
344:
345: return $home . '/.composer/auth.json';
346: }
347:
348: private function _copyMagerunConfig($version = 1)
349: {
350: $f = resource_path('storehouse/magento/magento' . $version . '.yaml');
351: if (!file_exists($f)) {
352: return error('failed to locate magento download YAML');
353: }
354: $filename = '.n98-magerun' . ($version > 1 ? $version : '') . '.yaml';
355: copy($f, $this->domain_fs_path() . '/tmp/' . $filename);
356: $dest = $this->user_get_home() . '/' . $filename;
357: $this->file_copy('/tmp/' . $filename, $dest);
358: unlink($this->domain_fs_path() . '/tmp/' . $filename);
359:
360: return $dest;
361: }
362:
363: private function _exec(string $path = null, $cmd, array $args = array(), $ver = null)
364: {
365:
366: if ($path && null === $ver) {
367: $ver = $this->guessMajor($path);
368: }
369:
370: $magerun = $ver === 1 ? self::MAGENTO_CLI : self::MAGENTO2_CLI;
371: // client may override tz, propagate to bin
372: $tz = date_default_timezone_get();
373: $cli = 'php -d pdo_mysql.default_socket=' . escapeshellarg(ini_get('mysqli.default_socket')) .
374: ' -d date.timezone=' . $tz . ' -d memory_limit=192m ' . $magerun . '';
375:
376: if (!is_array($args)) {
377: $args = func_get_args();
378: array_shift($args);
379: }
380: $user = $this->username;
381: if ($path) {
382: $cli = 'cd %(path)s && ' . $cli;
383: $args['path'] = $path;
384: $stat = $this->file_stat($path);
385: $user = !empty($stat['owner']) && $stat['uid'] >= \a23r::get_class_from_module('user')::MIN_UID ?
386: $stat['owner'] : $this->username;
387: }
388: $cmd = $cli . ' ' . $cmd;
389: $ret = $this->pman_run($cmd, $args, null, ['user' => $user]);
390: if (!$ret['success'] && $ret['stderr']) {
391: $ret['stderr'] = $ret['stdout'];
392: }
393:
394: return $ret;
395: }
396:
397: /**
398: * Get installed version
399: *
400: * @param string $hostname
401: * @param string $path
402: * @return null|string version number
403: */
404: public function get_version(string $hostname, string $path = ''): ?string
405: {
406: if (!$this->valid($hostname, $path)) {
407: return null;
408: }
409:
410: $approot = $this->getAppRoot($hostname, $path);
411:
412: $ret = $this->_exec($approot, 'sys:info --format=json', []);
413:
414: if (!$ret['success']) {
415: return null;
416: }
417: $info = json_decode($ret['stdout'], true);
418: foreach ($info as $el) {
419: if (strtolower($el['name']) === 'version') {
420: return $el['value'];
421: }
422: }
423:
424: return null;
425:
426: }
427:
428: /**
429: * Location is a valid WP install
430: *
431: * @param string $hostname or $docroot
432: * @param string $path
433: * @return bool
434: */
435: public function valid(string $hostname, string $path = ''): bool
436: {
437: if ($hostname[0] == '/') {
438: $docroot = $hostname;
439: } else {
440: $docroot = $this->getAppRoot($hostname, $path);
441: if (!$docroot) {
442: return false;
443: }
444: }
445:
446: return $this->guessMajor($docroot) !== null;
447: }
448:
449: private function _fixModelPHP7($docroot)
450: {
451: // PHP7 fix
452: // @see https://www.atwix.com/magento/magento-and-php-7/
453: if (version_compare(platform_version(), '6.5', '<')) {
454: return true;
455: }
456: $f = $docroot . '/app/code/core/Mage/Core/Model/Layout.php';
457: $contents = $this->file_get_file_contents($f);
458: $old = '$out .= $this->getBlock($callback[0])->$callback[1]();';
459: $new = '$out .= $this->getBlock($callback[0])->{$callback[1]}();';
460: $replacement = str_replace($old, $new, $contents);
461:
462: return $this->file_put_file_contents($f, $replacement);
463: }
464:
465: private function _fixConnectConfig($docroot)
466: {
467: $file = $docroot . '/downloader/connect.cfg';
468: $preamble = '::ConnectConfig::v::1.0::';
469: if ($this->file_exists($file)) {
470: $raw = $this->file_get_file_contents($file);
471: if (!preg_match('/^((?:::[[[:alnum:].]*]*)+?)([sibNaO]:.*)$/mi', $raw, $preamble)) {
472: return error('cannot set Magento Connect FTP login information, ' .
473: 'config is malformed: %s', $raw);
474: }
475: $contents = \Util_PHP::unserialize($preamble[2]);
476: $preamble = $preamble[1];
477: } else {
478: $contents = array();
479: }
480: $contents['remote_config'] = 'ftp://' . $this->username . '@' . $this->domain . ':debug@localhost';
481: $contents['downloader_path'] = $this->domain_fs_path() . $docroot . '/downloader';
482: $contents['magento_root'] = $this->domain_fs_path() . $docroot;
483: $newdata = $preamble . serialize($contents);
484:
485: return $this->file_put_file_contents($file, $newdata);
486: }
487:
488: /**
489: * Install and activate plugin
490: *
491: * @param string $hostname domain or subdomain of wp install
492: * @param string $path optional path component of wp install
493: * @param string $plugin plugin name
494: * @param string $version optional plugin version
495: * @return bool
496: */
497: public function install_plugin(
498: string $hostname,
499: string $path,
500: string $plugin,
501: string $version = 'stable'
502: ): bool {
503: $docroot = $this->getAppRoot($hostname, $path);
504: if (!$docroot) {
505: return error('invalid WP location');
506: }
507:
508: return info('not implemented');
509: }
510:
511: /**
512: * Uninstall a plugin
513: *
514: * @param string $hostname
515: * @param string $path
516: * @param string $plugin plugin name
517: * @param bool|string $force delete even if plugin activated
518: * @return bool
519: */
520: public function uninstall_plugin(string $hostname, string $path, string $plugin, bool $force = false): bool
521: {
522: $docroot = $this->getAppRoot($hostname, $path);
523: if (!$docroot) {
524: return error('invalid Magento location');
525: }
526:
527: return info('not implemented');
528: }
529:
530: /**
531: * Recovery mode to disable all plugins
532: *
533: * @param string $hostname subdomain or domain of WP
534: * @param string $path optional path
535: * @return bool
536: */
537: public function disable_all_plugins(string $hostname, string $path = ''): bool
538: {
539: $docroot = $this->getAppRoot($hostname, $path);
540: if (!$docroot) {
541: return error('failed to determine path');
542: }
543:
544: return info('not implemented');
545: }
546:
547: /**
548: * Uninstall WP from a location
549: *
550: * @param $hostname
551: * @param string $path
552: * @param string $delete "all", "db", or "files"
553: * @return bool
554: */
555: public function uninstall(string $hostname, string $path = '', string $delete = 'all'): bool
556: {
557: /**
558: * @todo delete app whose docroot is one level below app root
559: */
560: return parent::uninstall($hostname, $path, $delete);
561: }
562:
563: /**
564: * Get database configuration for a blog
565: *
566: * @param string $hostname domain or subdomain of wp blog
567: * @param string $path optional path
568: * @return array|bool
569: */
570: public function db_config(string $hostname, string $path = '')
571: {
572: $docroot = $this->getAppRoot($hostname, $path);
573: if (!$docroot) {
574: return error('failed to determine Magento');
575: }
576: if ($this->guessMajor($docroot) === 1) {
577: $file = $this->domain_fs_path() . $docroot . '/app/etc/local.xml';
578: $code = simplexml_load_string(file_get_contents($file, false), 'SimpleXMLElement', LIBXML_NOCDATA);
579: $conn = $code->xpath('//connection');
580: if (!$conn) {
581: return error("failed to obtain Magento configuration for `%s'", $docroot);
582: }
583: $conn = array_pop($conn);
584:
585: return array(
586: 'user' => (string)$conn->username,
587: 'host' => (string)$conn->host,
588: 'db' => (string)$conn->dbname,
589: 'password' => (string)$conn->password,
590: 'prefix' => (string)$conn->prefix
591: );
592: }
593: $file = \dirname($docroot) . '/app/etc/env.php';
594: $fstfile = $this->domain_fs_path() . $file;
595:
596: if (!file_exists($fstfile)) {
597: return error("failed to obtain Magento configuration for `%s'", $docroot);
598: }
599:
600: $ret = $this->pman_run('cd %s && php -r %s', [
601: \dirname($docroot),
602: 'echo json_encode(include "' . $file . '", true);'
603: ], [], ['user' => $this->getDocrootUser($docroot)]);
604:
605: if (!$ret['success']) {
606: warn("Failed to parse `%s'", $file);
607: return [];
608: }
609:
610: $conn = json_decode($ret['stdout'], true);
611: return [
612: 'user' => array_get($conn, 'connection.default.username'),
613: 'host' => array_get($conn, 'connection.default.host'),
614: 'db' => array_get($conn, 'connection.default.dbname'),
615: 'password' => array_get($conn, 'connection.default.password'),
616: 'prefix' => array_get($conn, 'table_prefix'),
617: ];
618: }
619:
620: /**
621: * Check if version is latest or get latest version
622: *
623: * @param string|null $version
624: * @param string|null $branchcomp
625: * @return int|string
626: */
627: public function is_current(string $version = null, string $branchcomp = null)
628: {
629: return parent::is_current($version, $branchcomp);
630: }
631:
632: /**
633: * Change Magento admin credentials
634: *
635: * $fields is a hash whose indices match password
636: *
637: * @param string $hostname
638: * @param string $path
639: * @param array $fields
640: * @return bool
641: */
642: public function change_admin(string $hostname, string $path, array $fields): bool
643: {
644: $approot = $this->getAppRoot($hostname, $path);
645: if (!$approot) {
646: return warn('failed to change administrator information');
647: }
648: $admin = $this->get_admin($hostname, $path);
649:
650: if (!$admin) {
651: return error('cannot determine admin of Magento install');
652: }
653: $args = array(
654: 'username' => $admin
655: );
656: if (isset($fields['password'])) {
657: $args['password'] = $fields['password'];
658: $ret = $this->_exec($approot, 'admin:user:change-password %(username)s %(password)s', $args);
659: if (!$ret['success']) {
660: return error("failed to change admin password, `%s'", $ret['stderr']);
661: }
662: } else {
663: return warn('no other fields besides password implemented');
664: }
665:
666: return true;
667:
668: }
669:
670: /**
671: * Get the primary admin for a Magento instance
672: *
673: * @param string $hostname
674: * @param string|null $path
675: * @return null|string admin or false on failure
676: */
677: public function get_admin(string $hostname, string $path = ''): ?string
678: {
679: $approot = $this->getAppRoot($hostname, $path);
680: $ret = $this->_exec($approot, 'admin:user:list --format=json');
681: if (!$ret['success']) {
682: warn('failed to enumerate administrative users');
683:
684: return null;
685: }
686: $users = json_decode($ret['stdout'], true);
687: if (!$users) {
688: error('no administrative users found');
689:
690: return null;
691: }
692: foreach ($users as $user) {
693: if ($user['status'] === 'active') {
694: break;
695: }
696: }
697:
698: return $user['username'];
699: }
700:
701: /**
702: * @inheritDoc
703: */
704: public function has_fortification(string $hostname, string $path = '', string $mode = null): bool
705: {
706: return parent::has_fortification($hostname, $path, $mode);
707: }
708:
709: /**
710: * @inheritDoc
711: */
712: public function fortification_modes(string $hostname, string $path = ''): array
713: {
714: return parent::fortification_modes($hostname, $path);
715: }
716:
717: /**
718: * @param string $hostname
719: * @param string $path
720: * @param string $mode
721: * @param null $args
722: * @return bool
723: */
724: public function fortify(string $hostname, string $path = '', string $mode = 'max', $args = []): bool
725: {
726: return parent::fortify($hostname, $path, $mode);
727: }
728:
729: /**
730: * Relax permissions to allow write-access
731: *
732: * @param string $hostname
733: * @param string $path
734: * @return bool
735: */
736: public function unfortify(string $hostname, string $path = ''): bool
737: {
738: return parent::unfortify($hostname, $path);
739: }
740:
741: /**
742: * Update core, plugins, and themes atomically
743: *
744: * @param string $hostname subdomain or domain
745: * @param string $path optional path under hostname
746: * @param string $version
747: * @return bool
748: */
749: public function update_all(string $hostname, string $path = '', string $version = null): bool
750: {
751: $ret = ($this->update($hostname, $path, $version) && $this->update_plugins($hostname, $path) &&
752: $this->update_themes($hostname, $path)) || error('failed to update all components');
753:
754: parent::setInfo($this->getDocumentRoot($hostname, $path), [
755: 'version' => $this->get_version($hostname, $path),
756: 'failed' => !$ret
757: ]);
758:
759: return $ret;
760: }
761:
762: /**
763: * Update Magento to latest version
764: *
765: * @param string $hostname domain or subdomain under which WP is installed
766: * @param string $path optional subdirectory
767: * @param string $version
768: * @return bool
769: */
770: public function update(string $hostname, string $path = '', string $version = null): bool
771: {
772: $docroot = $this->getAppRoot($hostname, $path);
773: if (!$docroot) {
774: return error('update failed');
775: }
776:
777: return error('Magento updates not supported yet');
778: }
779:
780: /**
781: * Update Magento plugins
782: *
783: * @param string $hostname domain or subdomain
784: * @param string $path optional path within host
785: * @param array $plugins
786: * @return bool
787: */
788: public function update_plugins(string $hostname, string $path = '', array $plugins = array()): bool
789: {
790: $docroot = $this->getAppRoot($hostname, $path);
791: if (!$docroot) {
792: return error('update failed');
793: }
794:
795: return info('to-do');
796: }
797:
798: /**
799: * Update Magento themes
800: *
801: * @param string $hostname subdomain or domain
802: * @param string $path optional path under hostname
803: * @param array $themes
804: * @return bool
805: */
806: public function update_themes(string $hostname, string $path = '', array $themes = array()): bool
807: {
808: $docroot = $this->getAppRoot($hostname, $path);
809: if (!$docroot) {
810: return error('update failed');
811: }
812:
813: return info('not implemented');
814: }
815:
816: public function delete_key()
817: {
818: $file = $this->_keyAuthFile();
819: if (!$this->file_exists($file)) {
820: return error("failed to get Magento key file `%s'", $file);
821: }
822: $contents = json_decode($this->file_get_file_contents($file), true);
823: if (!isset($contents['http-basic'])) {
824: return true;
825: }
826: unset($contents['http-basic']['repo.magento.com']);
827:
828: return $this->file_put_file_contents($file, json_encode($contents));
829: }
830:
831: public function set_key($publickey, $privatekey)
832: {
833: if (!ctype_alnum($publickey) || !ctype_alnum($privatekey)) {
834: return error('invalid public and/or private magento key');
835: }
836: $file = $this->_keyAuthFile();
837: $contents = array();
838: if ($this->file_exists($file)) {
839: $contents = $this->file_get_file_contents($file);
840: $contents = json_decode($contents, true);
841: } else if (!$this->file_exists(dirname($file))) {
842: $this->file_create_directory(dirname($file), 0755, true);
843: }
844: if (!isset($contents['http-basic'])) {
845: $contents['http-basic'] = array();
846: }
847: $contents['http-basic']['repo.magento.com'] = array(
848: 'username' => $publickey,
849: 'password' => $privatekey
850: );
851:
852: return $this->file_put_file_contents($file, json_encode($contents), true);
853: }
854:
855: /**
856: * Install wp-cli if necessary
857: *
858: * @return bool
859: * @throws \Exception
860: */
861: public function _housekeeping(): bool
862: {
863: $clis = array(
864: self::MAGENTO_CLI => self::MAGENTO_CLI_URL,
865: self::MAGENTO2_CLI => self::MAGENTO2_CLI_URL
866: );
867: foreach ($clis as $cli => $url) {
868: if (!file_exists($cli)) {
869: $res = Util_HTTP::download($url, $cli);
870: if (!$res) {
871: return error('failed to install magento cli');
872: }
873: info('downloaded %s', basename($cli));
874: }
875:
876: $local = $this->service_template_path('siteinfo') . '/' . $cli;
877: if (!file_exists($local)) {
878: return copy($cli, $local);
879: }
880: }
881:
882: return true;
883: }
884:
885: /**
886: * Get plugin status
887: *
888: * @param string $hostname domain or subdomain
889: * @param null|string $path optional path under hostname
890: * @param null|string $plugin specific plugin to query
891: * @return array|bool
892: */
893: public function plugin_status(string $hostname, string $path = '', string $plugin = null)
894: {
895: return error('not implemented yet');
896: }
897:
898: /**
899: * Get all available versions
900: *
901: * @return array
902: */
903: public function get_versions(): array
904: {
905: return $this->_getVersions();
906: }
907:
908: public function next_version(string $version, string $maximalbranch = '99999999.99999999.99999999'): ?string
909: {
910: return parent::next_version($version, $maximalbranch);
911: }
912:
913: public function theme_status(string $hostname, string $path = '', string $theme = null)
914: {
915: return parent::theme_status($hostname, $path, $theme); // TODO: Change the autogenerated stub
916: }
917:
918: public function install_theme(string $hostname, string $path, string $theme, string $version = null): bool
919: {
920: return parent::install_theme($hostname, $path, $theme, $version);
921: }
922:
923: /**
924: * @inheritDoc
925: */
926: public function reconfigure(string $hostname, string $path, $param, $value = null): bool
927: {
928: return parent::reconfigure($hostname, $path, $param, $value); // TODO: Change the autogenerated stub
929: }
930:
931: /**
932: * @inheritDoc
933: */
934: public function reconfigurables(string $hostname, string $path = ''): array
935: {
936: return parent::reconfigurables($hostname, $path);
937: }
938: }
939: