diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..ce33665 Binary files /dev/null and b/.DS_Store differ diff --git a/bin/shopmonkey b/bin/shopmonkey index 4d0e929..60f263d 100755 --- a/bin/shopmonkey +++ b/bin/shopmonkey @@ -13,6 +13,7 @@ use Shopmonkeynl\ShopmonkeyCli\Commands\Watch; use Shopmonkeynl\ShopmonkeyCli\Commands\Auth; use Shopmonkeynl\ShopmonkeyCli\Commands\Init; + use Shopmonkeynl\ShopmonkeyCli\Commands\Theme; use Symfony\Component\Console\Application; $application = new Application(); @@ -21,4 +22,5 @@ $application->add(new Watch()); $application->add(new Auth()); $application->add(new Init()); + $application->add(new Theme()); $application->run(); \ No newline at end of file diff --git a/composer.json b/composer.json index 005c77c..d531a74 100644 --- a/composer.json +++ b/composer.json @@ -17,9 +17,16 @@ "require": { "php": "^8.0", "symfony/console": "^5.0|^6.0", - "nunomaduro/termwind": "^1.15" + "nunomaduro/termwind": "^1.15", + "spekulatius/phpscraper": "^2.0", + "symfony/browser-kit": "^6.3", + "symfony/css-selector": "^6.3", + "symfony/panther": "^2.1", + "dbrekelmans/bdi": "^1.1" }, "bin": [ "bin/shopmonkey" - ] + ], + "require-dev": { + } } diff --git a/src/Commands/Init.php b/src/Commands/Init.php index 03e6158..0525dfa 100644 --- a/src/Commands/Init.php +++ b/src/Commands/Init.php @@ -58,6 +58,8 @@ private function installWatcherFiles() $newGulpFile = getcwd() . '/gulpfile.js'; $oldGulpConfig = $oldDir . 'gulp.config.js'; $newGulpConfig = getcwd() . '/gulp.config.js'; + $oldGeckoDriver = __DIR__ .'/../src/drivers/geckodriver'; + $newGeckoDriver = getcwd() . '/geckodriver'; $this->installNodeDependencies(); $this->createFilesAndFolders(); @@ -69,6 +71,7 @@ private function installWatcherFiles() copy($oldWatcher, $newWatcher); copy($oldGulpFile, $newGulpFile); copy($oldGulpConfig, $newGulpConfig); + copy($oldGeckoDriver, $newGeckoDriver); } private function addRecommendedExtensions() diff --git a/src/Commands/Theme.php b/src/Commands/Theme.php new file mode 100644 index 0000000..8b62a79 --- /dev/null +++ b/src/Commands/Theme.php @@ -0,0 +1,37 @@ +file = getcwd() . '/config.json'; } - public function get($input, $output) { + public function get($input, $output) + { $file = $this->file; if (file_exists($file)) { - $settings = file_get_contents($file); - $settings = json_decode($settings,true); + $settings = file_get_contents($file); + $settings = json_decode($settings, true); return $settings; } else { return $this->create($input, $output); } - } - public function update($input, $output) { + public function update($input, $output) + { $io = new InputOutput($input, $output); $file = $this->file; - $current_settings = file_get_contents($file); - $current_settings = json_decode($current_settings,true); + $current_settings = file_get_contents($file); + $current_settings = json_decode($current_settings, true); $csrf = $io->question('Enter current CSRF token'); $backend_session_id = $io->question('Enter current backend session ID'); @@ -44,60 +51,59 @@ public function update($input, $output) { file_put_contents($file, json_encode($new_settings, JSON_PRETTY_PRINT)); return $new_settings; - } - public function create($input, $output) { + public function create($input, $output) + { $io = new InputOutput($input, $output); $file = $this->file; $shop_url = $io->question('Enter shop URL'); - $theme_id = $io->question('Enter theme ID'); - $csrf = $io->question('Enter current CSRF token'); - $backend_session_id = $io->question('Enter current backend session ID'); $parsed_url = parse_url($shop_url); $base_url = $parsed_url['scheme'] . '://' . $parsed_url['host'] . '/'; - $settings = [ - 'theme_id' => $theme_id, - 'shop_url' => $base_url, - 'csrf' => $csrf, - 'backend_session_id' => $backend_session_id - ]; + $settings = $this->logIn($io, $base_url); file_put_contents($file, json_encode($settings, JSON_PRETTY_PRINT)); return $settings; - } - public function authenticate($input, $output) { - + public function authenticate($input, $output) + { + if (!$this->isPackageInstalled('dbrekelmans/bdi')) { + echo 'package not installed'; + exec('composer require --dev dbrekelmans/bdi && vendor/bin/bdi detect drivers'); + } + $settings = $this->get($input, $output); $io = new InputOutput($input, $output); $curl = curl_init(); - curl_setopt_array($curl, array( - CURLOPT_URL => $settings['shop_url'].'admin/themes/'.$settings['theme_id'].'/templates.json', - CURLOPT_RETURNTRANSFER => true, - CURLOPT_ENCODING => '', - CURLOPT_MAXREDIRS => 10, - CURLOPT_TIMEOUT => 0, - CURLOPT_FOLLOWLOCATION => true, - CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, - CURLOPT_CUSTOMREQUEST => 'GET', - CURLOPT_HTTPHEADER => array( - 'Accept: application/json, text/plain, */*', - 'Content-Type: application/json;charset=UTF-8', - 'Sec-Fetch-Dest: empty', - 'Sec-Fetch-Mode: cors', - 'Sec-Fetch-Site: same-origin', - 'x-csrf-token: '.$settings['csrf'], - 'Cookie: shared_session_id='.$settings['backend_session_id'].'; backend_session_id='.$settings['backend_session_id'].'; request_method=GET' - ), - )); + curl_setopt_array( + $curl, + array( + CURLOPT_URL => $settings['shop_url'] . 'admin/themes/' . $settings['theme_id'] . '/templates.json', + CURLOPT_RETURNTRANSFER => true, + CURLOPT_ENCODING => '', + CURLOPT_MAXREDIRS => 10, + CURLOPT_TIMEOUT => 0, + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, + CURLOPT_CUSTOMREQUEST => 'GET', + CURLOPT_HTTPHEADER => array( + 'Accept: application/json, text/plain, */*', + 'Content-Type: application/json;charset=UTF-8', + 'Sec-Fetch-Dest: empty', + 'Sec-Fetch-Mode: cors', + 'Sec-Fetch-Site: same-origin', + 'x-csrf-token: ' . $settings['csrf'], + 'Cookie: shared_session_id=' . $settings['backend_session_id'] . '; backend_session_id=' . $settings['backend_session_id'] . '; request_method=GET' + ), + ) + ); $response = curl_exec($curl); @@ -109,18 +115,108 @@ public function authenticate($input, $output) { } else { $response = json_decode($response, true); if (isset($response['error'])) { - + $io->info("Please log in below"); // $io->info(json_encode($settings, JSON_PRETTY_PRINT)); $settings = $this->create($input, $output); $this->authenticate($input, $output); - } else { - $io->right("Authentication successful for '". $settings['shop_url'] ."'."); + $io->right("Authentication successful for '" . $settings['shop_url'] . "'."); return true; } } + } + + private function logIn(InputOutput $io, string $base_url, bool $require_new_password = false) + { + $login_url = $base_url . 'admin/auth/login'; + $io->info('Logging in on: ' . $login_url); + + $client = Client::createChromeClient(); + $client->request('GET', $login_url); + $crawler = $client->waitFor('#form_auth_login'); + $form = $crawler->filter('#form_auth_login')->form(); + + $file = $this->file; + if (file_exists($file)) { + $currentSettings = file_get_contents($file); + $currentSettings = json_decode($currentSettings, true); + } + if (isset($currentSettings) && $currentSettings['user']) { + $user = $currentSettings['user']; + } else { + $user = $io->ask('Log in email address'); + } + + exec('security find-generic-password -a "' . $user . '" -s "shopmonkeycli" -w', $output, $returnCode); + if ($returnCode === 0 && !$require_new_password) { + $password = trim($output[0]); + } else { + $password = $io->askHidden('password'); + passthru('security add-generic-password -a "' . $user . '" -s "shopmonkeycli" -w "' . $password . '" -U'); + } + + $form->setValues([ + 'login[email]' => $user, + 'login[password]' => $password + ]); + + $client->submit($form); + $client->waitFor('.Sidebar'); + + // $io->info($client->getCurrentURL() === $login_url); + if ($client->getCurrentURL() === $login_url) { + $io->error('Could not log in'); + $client->quit(); + $this->logIn($io, $base_url, true); + return false; + } + + $crawler = $client->waitFor('[name="csrf-token"]'); + $csrf = $crawler->filter('[name="csrf-token"]')->first()->attr('content'); + $io->success('CSRF Token: ' . $csrf); + + $themes_url = $base_url . 'admin/themes.json'; + $client->request('GET', $themes_url); + $crawler = $client->waitFor('pre'); + $text = $crawler->filter('pre')->getText(); + $themes_json = json_decode($text); + $theme_id = $themes_json->themes[0]->id; + + $io->success('Theme ID: ' . $theme_id); + + $cookieJar = $client->getCookieJar(); + $cookie = $cookieJar->get('backend_session_id'); + $backend_session_id = $cookie->getValue(); + $io->success('Backend session ID: ' . $backend_session_id); + + return [ + 'theme_id' => $theme_id, + 'csrf' => $csrf, + 'backend_session_id' => $backend_session_id, + 'shop_url' => $base_url, + 'user' => $user + ]; } -} \ No newline at end of file + /** + * Checks if a composer package installed + * + * @param string $packageName - The name of the package eg. 'vendor/package' + * + * @return bool + */ + public function isPackageInstalled($packageName): bool + { + // Run the `composer show` command to get a list of installed packages as an array + exec('composer show -N', $output, $exitCode); + + if ($exitCode === 0) { + // Return the opposite because if it is in array it is installed. + return !in_array($packageName, $output); + } + + return false; + } +}