1

I have made a Craft 3 plugin that is manually used in the Craft Admin CP area; click a button and we sync products in Craft with a Shopify API. Now I am trying to automate the process.

Craft 3 Admin CP Plugin Section

I would like to schedule a task that runs the controller that is executed when an admin user manually clicks the Manual Sync button, shown above. Clicking the button runs the actionIndex() method of my plugin's controller, which extends the Craft 3 web controller:

<?php

namespace vendor\shopify\controllers;

use \vendor\shopify\Shopify;
use craft\elements\Entry;
use craft\elements\MatrixBlock;
use DateTime;

class SyncController extends \craft\web\Controller
{
    public function actionIndex()
    {
        $apiKey = \vendor\shopify\Shopify::getInstance()->getSettings()->apiKey;
        $password = \vendor\shopify\Shopify::getInstance()->getSettings()->password;
        $secret = \vendor\shopify\Shopify::getInstance()->getSettings()->secret;
        $hostname = \vendor\shopify\Shopify::getInstance()->getSettings()->hostname;

        $curl = curl_init();
        curl_setopt_array($curl, array(
            CURLOPT_URL => "https://$apiKey:$password@$hostname/admin/products.json",
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_ENCODING => "",
            CURLOPT_MAXREDIRS => 10,
            CURLOPT_TIMEOUT => 30,
            CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
            CURLOPT_CUSTOMREQUEST => "GET",
            CURLOPT_POSTFIELDS => "",
            CURLOPT_HTTPHEADER => array(
                "cache-control: no-cache"
            ),
        ));
        $response = curl_exec($curl);
        $err = curl_error($curl);
        curl_close($curl);

        if ($err) {
            \Craft::info("(Vendor Shopify plugin) cURL Error #:" . $err, __METHOD__);
        }
        else {
            $section = \Craft::$app->sections->getSectionByHandle('products');
            $entryTypes = $section->getEntryTypes();

            foreach (json_decode($response)->products as $product) {
                $query = Entry::find();
                $query->section('products');
                $query->shopifyProduct($product->id);
                $currentEntries = $query->exists();

                if ($currentEntries) {
                    $existingEntry = $query->all();
                    $existingEntry = $existingEntry[0];
                    $blockIds = [];

                    $field = \Craft::$app->getFields()->getFieldByHandle('productVariants');
                    $existingMatrixQuery = $existingEntry->getFieldValue('productVariants');

                    foreach ($existingMatrixQuery->all() as $block) {
                        $blockIds[] = $block->id;
                        \Craft::info("(Vendor Shopify plugin) Block Id??: " . json_encode($block->id), __METHOD__);
                    }

                    $serializedMatrix = $field->serializeValue($existingMatrixQuery, $existingEntry);

                    if (count($product->variants) === count($blockIds)) {
                        foreach ($product->variants as $key => $variant) {
                            $serializedMatrix[$blockIds[$key]] = [
                                'type' => 'productVariant',
                                'enabled' => true,
                                'fields' => [
                                    'sku' => $variant->sku,
                                    'variantSize' => $variant->title,
                                    'variantPrice' => number_format((float)$variant->price, 2, '.', ''),
                                    'variantCompareAtPrice' => number_format((float)$variant->compare_at_price, 2, '.', ''),
                                    'variantQuantity' => $variant->inventory_quantity,
                                ]
                            ];
                        }
                    }
                    else {
                        $serializedMatrix = [];
                        foreach ($product->variants as $key => $variant) {
                            $serializedMatrix['new' . $key] = [
                                'type' => 'productVariant',
                                'enabled' => true,
                                'fields' => [
                                    'sku' => $variant->sku,
                                    'variantSize' => $variant->title,
                                    'variantPrice' => number_format((float)$variant->price, 2, '.', ''),
                                    'variantCompareAtPrice' => number_format((float)$variant->compare_at_price, 2, '.', ''),
                                    'variantQuantity' => $variant->inventory_quantity,
                                ]
                            ];
                        }
                    };

                    $existingEntry->setFieldValues([
                        'productDescription' => $product->body_html,
                        'productVariants' => $serializedMatrix,
                    ]);

                    $success = \Craft::$app->elements->saveElement($existingEntry);

                    if (!$success) {
                        \Craft::info("(Vendor Shopify plugin) Error Saving Product: " . $product->title . " " . $err, __METHOD__);
                    }
                }
            }
        }
        \Craft::$app->getSession()->setNotice('Manual Sync complete.');
        return $this->renderTemplate('shopify/done');
    }
}

So far, I have explored options discussed in these two StackExchange questions that are somewhat related to my issue (Call Craft controller with cron job and Craft TaskService Overview/Cron Job). Unfortunately my labors haven't been fruitful! I am open to any and all suggestions in regards to how I might accomplish the automation of calling this plugin class's actionIndex method. Thank you, I sincerely appreciate any feedback!

3

You could add console command in your plugin then use third party cron job package to run the console command, your console command will be responsible for calling index function. i had similar kind of requirement earlier, i accomplished this using third party cron job package which was dependency for my plugin to work.

There is new craft 3 plugin call scheduler https://github.com/supercool/scheduler.

I am not 100% sure that this idea will work on your side but just a heads up for you to get started.

| improve this answer | |
  • 1
    Thank you for the suggestion about console commands - this is what I ended up doing today and it worked perfectly. I will post another answer about this to follow up. I appreciate you taking the time to steer me in the right direction! – joshykautz Feb 12 '19 at 18:35
1

I took the route of adding a controller with methods that could be triggered as craft console commands, and then simply ran these commands via cron tasks.

In the init() public method of the main plugin class, I added the console commands controller:

public function init()
{
   // ...

   // Add in our console commands
   if (Craft::$app instanceof ConsoleApplication) {
      $this->controllerNamespace = 'vendor\shopify\console\controllers';
   }

   // ...
}

Inside this namespace of console controllers above, I have a single controller named CronCroller, which extends yii\console\Controller:

<?php

namespace vendor\shopify\console\controllers;

use vendor\shopify\Shopify;

use Craft;
use yii\console\Controller;
use craft\elements\Entry;

/**
 * Shopify plugin commands for cron automation
 *
 * The first line of this class docblock is displayed as the description
 * of the Console Command in ./craft help
 *
 * Craft can be invoked via commandline console by using the `./craft` command
 * from the project root.
 *
 * Console Commands are just controllers that are invoked to handle console
 * actions. The segment routing is plugin-name/controller-name/action-name
 *
 * The actionIndex() method is what is executed if no sub-commands are supplied.
 *
 * Actions must be in 'kebab-case' so actionDoSomething() maps to 'do-something'
 *
 */
class CronController extends Controller
{
    // Public Methods
    // =========================================================================

    /**
     * Run Shopify product Sync
     *
     * The first line of this method docblock is displayed as the description
     * of the Console Command in ./craft help
     *
     * @return mixed
     */
    public function actionSync()
    {
        echo "\nSyncing Craft products with Shopify.\n";

        // [Automated Task Processes]

        echo "\nSuccess.\n";
        return;
    }
}

Since the name of the controller is CronController, I can run the code in the actionSync() public method through the terminal with a command like:

php craft shopify/cron/sync;

PluginFactory.io is a terrific resource for exploring and learning more, which is how I set up my plugin's console commands.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.