@@ -109,7 +109,11 @@ class BackendController extends ControllerBase
             $action,
             base_path().'/modules'
         )) {
-            return $controllerObj->run($action, $controllerParams);
+            return [
+                'controller' => $controllerObj,
+                'action' => $action,
+                'params' => $controllerParams
+            ];
         }

         /*

<?php namespace Backend\Classes;

use Str;
use App;
use File;
use View;
use Config;
use Response;
use Illuminate\Routing\Controller as ControllerBase;
use October\Rain\Router\Helper as RouterHelper;
use Closure;
use System\Classes\PluginManager;

/**
 * This is the master controller for all back-end pages.
 * All requests that are prefixed with the backend URI pattern are sent here,
 * then the next URI segments are analysed and the request is routed to the
 * relevant back-end controller.
 *
 * For example, a request with the URL `/backend/acme/blog/posts` will look
 * for the `Posts` controller inside the `Acme.Blog` plugin.
 *
 * @see Backend\Classes\Controller Base class for back-end controllers
 * @package october\backend
 * @author Alexey Bobkov, Samuel Georges
 */
class BackendController extends ControllerBase
{
    use \October\Rain\Extension\ExtendableTrait;

    /**
     * @var array Behaviors implemented by this controller.
     */
    public $implement;

    /**
     * @var string Allows early access to page action.
     */
    public static $action;

    /**
     * @var array Allows early access to page parameters.
     */
    public static $params;

    /**
     * Instantiate a new BackendController instance.
     */
    public function __construct()
    {
        $this->extendableConstruct();
    }

    /**
     * Extend this object properties upon construction.
     */
    public static function extend(Closure $callback)
    {
        self::extendableExtendCallback($callback);
    }

    /**
     * Pass unhandled URLs to the CMS Controller, if it exists
     *
     * @param string $url
     * @return Response
     */
    protected function passToCmsController($url)
    {
        if (class_exists('\Cms\Classes\Controller')) {
            return App::make('Cms\Classes\Controller')->run($url);
        } else {
            return Response::make(View::make('backend::404'), 404);
        }
    }

    /**
     * Finds and serves the requested backend controller.
     * If the controller cannot be found, returns the Cms page with the URL /404.
     * If the /404 page doesn't exist, returns the system 404 page.
     * @param string $url Specifies the requested page URL.
     * If the parameter is omitted, the current URL used.
     * @return string Returns the processed page content.
     */
    public function run($url = null)
    {
        $params = RouterHelper::segmentizeUrl($url);

        /*
         * Database check
         */
        if (!App::hasDatabase()) {
            return Config::get('app.debug', false)
                ? Response::make(View::make('backend::no_database'), 200)
                : $this->passToCmsController($url);
        }

        /*
         * Look for a Module controller
         */
        $module = $params[0] ?? 'backend';
        $controller = $params[1] ?? 'index';
        self::$action = $action = isset($params[2]) ? $this->parseAction($params[2]) : 'index';
        self::$params = $controllerParams = array_slice($params, 3);
        $controllerClass = '\\'.$module.'\Controllers\\'.$controller;
        if ($controllerObj = $this->findController(
            $controllerClass,
            $action,
            base_path().'/modules'
        )) {
            return $controllerObj->run($action, $controllerParams);
        }

        /*
         * Look for a Plugin controller
         */
        if (count($params) >= 2) {
            list($author, $plugin) = $params;

            $pluginCode = ucfirst($author) . '.' . ucfirst($plugin);
            if (PluginManager::instance()->isDisabled($pluginCode)) {
                return Response::make(View::make('backend::404'), 404);
            }

            $controller = $params[2] ?? 'index';
            self::$action = $action = isset($params[3]) ? $this->parseAction($params[3]) : 'index';
            self::$params = $controllerParams = array_slice($params, 4);
            $controllerClass = '\\'.$author.'\\'.$plugin.'\Controllers\\'.$controller;
            if ($controllerObj = $this->findController(
                $controllerClass,
                $action,
                plugins_path()
            )) {
                return $controllerObj->run($action, $controllerParams);
            }
        }

        /*
         * Fall back on Cms controller
         */
        return $this->passToCmsController($url);
    }

    /**
     * This method is used internally.
     * Finds a backend controller with a callable action method.
     * @param string $controller Specifies a method name to execute.
     * @param string $action Specifies a method name to execute.
     * @param string $inPath Base path for class file location.
     * @return ControllerBase Returns the backend controller object
     */
    protected function findController($controller, $action, $inPath)
    {
        /*
         * Workaround: Composer does not support case insensitivity.
         */
        if (!class_exists($controller)) {
            $controller = Str::normalizeClassName($controller);
            $controllerFile = $inPath.strtolower(str_replace('\\', '/', $controller)) . '.php';
            if ($controllerFile = File::existsInsensitive($controllerFile)) {
                include_once $controllerFile;
            }
        }

        if (!class_exists($controller)) {
            return false;
        }

        $controllerObj = App::make($controller);

        if ($controllerObj->actionExists($action)) {
            return $controllerObj;
        }

        return false;
    }

    /**
     * Process the action name, since dashes are not supported in PHP methods.
     * @param  string $actionName
     * @return string
     */
    protected function parseAction($actionName)
    {
        if (strpos($actionName, '-') !== false) {
            return camel_case($actionName);
        }

        return $actionName;
    }
}


@@ -15,12 +15,21 @@ import (
 	"github.com/stretchr/testify/assert"
 )

+var appTypeSettingsLocations = map[string][]string{
+	"drupal6":  {"sites/default/settings.php", "sites/default/settings.ddev.php"},
+	"drupal7":  {"sites/default/settings.php", "sites/default/settings.ddev.php"},
+	"drupal8":  {"sites/default/settings.php", "sites/default/settings.ddev.php"},
+	"backdrop": {"settings.php", "settings.ddev.php"},
+}
+
 // TestWriteSettings tests writing app settings (like Drupal
 // settings.php/settings.local.php
 func TestWriteSettings(t *testing.T) {
 	expectations := map[string]string{
-		"drupal7":   "sites/default/settings.php",
-		"drupal8":   "sites/default/settings.php",
+		"backdrop":  "settings.ddev.php",
+		"drupal6":   "sites/default/settings.ddev.php",
+		"drupal7":   "sites/default/settings.ddev.php",
+		"drupal8":   "sites/default/settings.ddev.php",
 		"wordpress": "wp-config.php",
 		"typo3":     "typo3conf/AdditionalConfiguration.php",
 	}

package ddevapp_test

import (
	"path/filepath"
	"testing"

	"os"

	"io/ioutil"

	. "github.com/drud/ddev/pkg/ddevapp"
	"github.com/drud/ddev/pkg/fileutil"
	"github.com/drud/ddev/pkg/testcommon"
	"github.com/drud/ddev/pkg/util"
	"github.com/stretchr/testify/assert"
)

// TestWriteSettings tests writing app settings (like Drupal
// settings.php/settings.local.php
func TestWriteSettings(t *testing.T) {
	expectations := map[string]string{
		"drupal7":   "sites/default/settings.php",
		"drupal8":   "sites/default/settings.php",
		"wordpress": "wp-config.php",
		"typo3":     "typo3conf/AdditionalConfiguration.php",
	}
	dir := testcommon.CreateTmpDir("example")
	err := os.MkdirAll(filepath.Join(dir, "sites/default"), 0777)
	assert.NoError(t, err)
	err = os.MkdirAll(filepath.Join(dir, "typo3conf"), 0777)
	assert.NoError(t, err)

	// TYPO3 wants LocalConfiguration.php to exist in the repo ahead of time.
	err = ioutil.WriteFile(filepath.Join(dir, "typo3conf", "LocalConfiguration.php"), []byte("<?php\n"), 0644)
	assert.NoError(t, err)

	app, err := NewApp(dir, DefaultProviderName)
	assert.NoError(t, err)

	for apptype, settingsRelativePath := range expectations {
		app.Type = apptype

		expectedSettingsFile := filepath.Join(dir, settingsRelativePath)
		_, err = os.Stat(expectedSettingsFile)
		assert.True(t, os.IsNotExist(err))
		// nolint: vetshadow
		createdFile, err := app.CreateSettingsFile()
		assert.NoError(t, err)
		assert.EqualValues(t, expectedSettingsFile, createdFile)
		_, err = os.Stat(expectedSettingsFile)
		assert.NoError(t, err)
		// nolint: vetshadow
		signatureFound, err := fileutil.FgrepStringInFile(expectedSettingsFile, DdevFileSignature)
		assert.NoError(t, err)
		assert.True(t, signatureFound)
		err = os.Remove(expectedSettingsFile)
		assert.NoError(t, err)
	}

	err = os.RemoveAll(dir)
	assert.NoError(t, err)
}

// @todo: Take a look at drush config in general to make sure its config
// is noted properly. Do we need it? Are we using it?
func TestWriteDrushConfig(t *testing.T) {

	dir := testcommon.CreateTmpDir("example")

	file, err := ioutil.TempFile(dir, "file")
	assert.NoError(t, err)

	drushConfig := NewDrushConfig()
	err = WriteDrushConfig(drushConfig, file.Name())
	assert.NoError(t, err)

	util.CheckClose(file)

	err = os.Chmod(dir, 0755)
	assert.NoError(t, err)
	err = os.Chmod(file.Name(), 0666)
	assert.NoError(t, err)

	err = os.RemoveAll(dir)
	assert.NoError(t, err)
}


@@ -26,8 +26,6 @@ var StartCmd = &cobra.Command{
 	Short:   "Start the local development environment for a site.",
 	Long:    `Start initializes and configures the web server and database containers to provide a working environment for development.`,
 	PreRun: func(cmd *cobra.Command, args []string) {
-		fmt.Printf("Starting environment for %s...\n", activeApp)
-
 		client, err := platform.GetDockerClient()
 		if err != nil {
 			log.Fatal(err)

package cmd

import (
	"fmt"
	"log"
	"strings"

	"github.com/drud/ddev/pkg/plugins/platform"
	"github.com/spf13/cobra"
)

const netName = "ddev_default"

var (
	serviceType string
	webImage    string
	dbImage     string
	webImageTag string
	dbImageTag  string
)

// StartCmd represents the add command
var StartCmd = &cobra.Command{
	Use:     "start",
	Aliases: []string{"add"},
	Short:   "Start the local development environment for a site.",
	Long:    `Start initializes and configures the web server and database containers to provide a working environment for development.`,
	PreRun: func(cmd *cobra.Command, args []string) {
		fmt.Printf("Starting environment for %s...\n", activeApp)

		client, err := platform.GetDockerClient()
		if err != nil {
			log.Fatal(err)
		}

		err = EnsureNetwork(client, netName)
		if err != nil {
			log.Fatal(err)
		}

	},
	Run: func(cmd *cobra.Command, args []string) {
		app := platform.PluginMap[strings.ToLower(plugin)]
		err := app.Init()
		if err != nil {
			log.Fatalf("Could not initialize application: %v", err)
		}

		err = app.Start()
		if err != nil {
			Failed("Failed to start %s: %s", app.GetName(), err)
		}

		fmt.Println("Waiting for the environment to become ready. This may take a couple of minutes...")
		siteURL, err := app.Wait()
		if err != nil {
			Failed("The environment for %s never became ready: %s", activeApp, err)
		}

		Success("Successfully started %s", activeApp)
		Success("Your application can be reached at: %s", siteURL)

	},
}

func init() {
	StartCmd.Flags().StringVarP(&webImage, "web-image", "", "", "Change the image used for the app's web server")
	StartCmd.Flags().StringVarP(&dbImage, "db-image", "", "", "Change the image used for the app's database server")
	StartCmd.Flags().StringVarP(&webImageTag, "web-image-tag", "", "", "Override the default web image tag")
	StartCmd.Flags().StringVarP(&dbImageTag, "db-image-tag", "", "", "Override the default web image tag")

	RootCmd.AddCommand(StartCmd)
}


@@ -24,16 +24,19 @@ class ArrayFilterType extends FilterType
      */
     public function buildForm(FormBuilderInterface $builder, array $options)
     {
-        $builder->add('value', $options['value_type'], $options['value_type_options'] + [
-            'label' => false,
-            'choice_loader' => new DynamicChoiceLoader(),
-        ]);
+        $defaultOptions = ['label' => false];
+        if (!isset($options['value_type_options']['choices']) || [] === $options['value_type_options']['choices']) {
+            $defaultOptions += ['choice_loader' => new DynamicChoiceLoader()];
+        }
+        $builder->add('value', $options['value_type'], $options['value_type_options'] + $defaultOptions);

         $builder->addModelTransformer(new CallbackTransformer(
             static function ($data) { return $data; },
             static function ($data) {
-                if ([] === $data['value']) {
+                if (null === $data['value'] || [] === $data['value']) {
                     $data['comparison'] = ComparisonType::CONTAINS === $data['comparison'] ? 'IS NULL' : 'IS NOT NULL';
+                } else {
+                    $data['value'] = (array) $data['value'];
                 }

                 return $data;

<?php

namespace EasyCorp\Bundle\EasyAdminBundle\Form\Filter\Type;

use Doctrine\ORM\Query\Expr;
use Doctrine\ORM\QueryBuilder;
use EasyCorp\Bundle\EasyAdminBundle\Form\ChoiceList\Loader\DynamicChoiceLoader;
use EasyCorp\Bundle\EasyAdminBundle\Form\Type\ComparisonType;
use Symfony\Component\Form\CallbackTransformer;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

/**
 * @author Yonel Ceruto <yonelceruto@gmail.com>
 */
class ArrayFilterType extends FilterType
{
    use FilterTypeTrait;

    /**
     * {@inheritdoc}
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('value', $options['value_type'], $options['value_type_options'] + [
            'label' => false,
            'choice_loader' => new DynamicChoiceLoader(),
        ]);

        $builder->addModelTransformer(new CallbackTransformer(
            static function ($data) { return $data; },
            static function ($data) {
                if ([] === $data['value']) {
                    $data['comparison'] = ComparisonType::CONTAINS === $data['comparison'] ? 'IS NULL' : 'IS NOT NULL';
                }

                return $data;
            }
        ));
    }

    /**
     * {@inheritdoc}
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'comparison_type_options' => ['type' => 'array'],
            'value_type' => ChoiceType::class,
            'value_type_options' => [
                'multiple' => true,
                'attr' => [
                    'data-widget' => 'select2',
                    'data-select2-tags' => 'true',
                ],
            ],
        ]);
    }

    /**
     * {@inheritdoc}
     */
    public function getParent()
    {
        return ComparisonFilterType::class;
    }

    /**
     * {@inheritdoc}
     */
    public function filter(QueryBuilder $queryBuilder, FormInterface $form, array $metadata)
    {
        $alias = \current($queryBuilder->getRootAliases());
        $property = $metadata['property'];
        $paramName = static::createAlias($property);
        $useQuotes = 'simple_array' !== $metadata['dataType'];
        $data = $form->getData();

        if ([] === $data['value']) {
            $queryBuilder->andWhere(\sprintf('%s.%s %s', $alias, $property, $data['comparison']));
        } else {
            $orX = new Expr\Orx();
            foreach ($data['value'] as $value) {
                $orX->add(\sprintf('%s.%s %s :%s', $alias, $property, $data['comparison'], $paramName));
                $queryBuilder->setParameter($paramName, $useQuotes ? '%"'.$value.'"%' : '%'.$value.'%');
            }
            if (ComparisonType::NOT_CONTAINS === $data['comparison']) {
                $orX->add(\sprintf('%s.%s IS NULL', $alias, $property));
            }
            $queryBuilder->andWhere($orX);
        }
    }
}


@@ -18,6 +18,7 @@ use Symfony\Component\Form\FormEvent;
 use Symfony\Component\Form\FormEvents;
 use Symfony\Component\Form\FormInterface;
 use Symfony\Component\Form\FormView;
+use Symfony\Component\HttpKernel\Kernel;
 use Symfony\Component\OptionsResolver\OptionsResolverInterface;

 use Sonata\MediaBundle\Provider\Pool;

<?php

/*
 * This file is part of the Sonata project.
 *
 * (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Sonata\MediaBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;

use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

use Sonata\MediaBundle\Provider\Pool;
use Sonata\MediaBundle\Form\DataTransformer\ProviderDataTransformer;

class MediaType extends AbstractType
{
    protected $pool;

    protected $class;

    /**
     * @param Pool   $pool
     * @param string $class
     */
    public function __construct(Pool $pool, $class)
    {
        $this->pool  = $pool;
        $this->class = $class;
    }

    /**
     * {@inheritdoc}
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->addModelTransformer(new ProviderDataTransformer($this->pool, $this->class, array(
            'provider'      => $options['provider'],
            'context'       => $options['context'],
            'empty_on_new'  => $options['empty_on_new'],
            'new_on_update' => $options['new_on_update'],
        )));

        $builder->addEventListener(FormEvents::BIND, function(FormEvent $event) {
            if ($event->getForm()->get('unlink')->getData()) {
                $event->setData(null);
            }
        });

        $this->pool->getProvider($options['provider'])->buildMediaType($builder);

        $builder->add('unlink', 'checkbox', array(
            'mapped'   => false,
            'data'     => false,
            'required' => false
        ));
    }

    /**
     * {@inheritdoc}
     */
    public function buildView(FormView $view, FormInterface $form, array $options)
    {
        $view->vars['provider'] = $options['provider'];
        $view->vars['context'] = $options['context'];
    }

    /**
     * {@inheritdoc}
     */
    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'data_class'    => $this->class,
            'provider'      => null,
            'context'       => null,
            'empty_on_new'  => true,
            'new_on_update' => true,
        ));
    }

    /**
     * {@inheritdoc}
     */
    public function getParent()
    {
        return 'form';
    }

    /**
     * {@inheritdoc}
     */
    public function getName()
    {
        return 'sonata_media_type';
    }
}


@@ -11,13 +11,13 @@ namespace Ergonode\Attribute\Infrastructure\Provider\Strategy;

 use Ergonode\Attribute\Domain\Entity\AbstractAttribute;
 use Ergonode\Attribute\Domain\Entity\Attribute\GalleryAttribute;
-use Ergonode\Attribute\Infrastructure\Provider\AttributeValueConstraintStrategyInterface;
+use Ergonode\Attribute\Infrastructure\Provider\ContextAwareAttributeValueConstraintStrategyInterface;
 use Ergonode\Multimedia\Domain\Query\MultimediaQueryInterface;
 use Symfony\Component\Validator\Constraint;
 use Symfony\Component\Validator\Constraints\Choice;
 use Symfony\Component\Validator\Constraints\Collection;

-class GalleryAttributeValueConstraintStrategy implements AttributeValueConstraintStrategyInterface
+class GalleryAttributeValueConstraintStrategy implements ContextAwareAttributeValueConstraintStrategyInterface
 {
     private MultimediaQueryInterface $query;

<?php

/**
 * Copyright © Ergonode Sp. z o.o. All rights reserved.
 * See LICENSE.txt for license details.
 */

declare(strict_types=1);

namespace Ergonode\Attribute\Infrastructure\Provider\Strategy;

use Ergonode\Attribute\Domain\Entity\AbstractAttribute;
use Ergonode\Attribute\Domain\Entity\Attribute\GalleryAttribute;
use Ergonode\Attribute\Infrastructure\Provider\AttributeValueConstraintStrategyInterface;
use Ergonode\Multimedia\Domain\Query\MultimediaQueryInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Constraints\Choice;
use Symfony\Component\Validator\Constraints\Collection;

class GalleryAttributeValueConstraintStrategy implements AttributeValueConstraintStrategyInterface
{
    private MultimediaQueryInterface $query;

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

    /**
     * {@inheritDoc}
     */
    public function supports(AbstractAttribute $attribute): bool
    {
        return $attribute instanceof GalleryAttribute;
    }

    /**
     * {@inheritDoc}
     */
    public function get(AbstractAttribute $attribute): Constraint
    {
        $multimedia = $this->query->getAll();

        return new Collection([
            'value' => [
                new Choice(['choices' => $multimedia, 'multiple' => true]),
            ],
        ]);
    }
}


@@ -6,7 +6,15 @@ namespace Bolt\Controller;

 use Bolt\Configuration\Config;
 use Bolt\Entity\Field\TemplateselectField;
+use Bolt\Snippet\RequestZone;
+use Bolt\Snippet\Target;
 use Bolt\Version;
+use Bolt\Widget\BoltHeaderWidget;
+use Bolt\Widget\CanonicalLinkWidget;
+use Bolt\Widget\NewsWidget;
+use Bolt\Widget\SnippetWidget;
+use Bolt\Widget\WeatherWidget;
+use Bolt\Widgets;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 use Symfony\Component\HttpFoundation\Response;
 use Tightenco\Collect\Support\Collection;

<?php

declare(strict_types=1);

namespace Bolt\Controller;

use Bolt\Configuration\Config;
use Bolt\Entity\Field\TemplateselectField;
use Bolt\Version;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Tightenco\Collect\Support\Collection;
use Twig\Environment;

class TwigAwareController extends AbstractController
{
    /**
     * @var Config
     */
    protected $config;

    /**
     * @var Environment
     */
    protected $twig;

    /**
     * @required
     */
    public function setAutowire(Config $config, Environment $twig): void
    {
        $this->config = $config;
        $this->twig = $twig;
    }

    /**
     * Renders a view.
     *
     * @final
     *
     * @param string|array $template
     *
     * @throws \Twig_Error_Loader  When none of the templates can be found
     * @throws \Twig_Error_Syntax  When an error occurred during compilation
     * @throws \Twig_Error_Runtime When an error occurred during rendering
     */
    protected function renderTemplate($template, array $parameters = [], ?Response $response = null): Response
    {
        // Set config and version.
        $parameters['config'] = $parameters['config'] ?? $this->config;
        $parameters['version'] = $parameters['version'] ?? Version::VERSION;
        $parameters['user'] = $parameters['user'] ?? $this->getUser();

        // Resolve string|array of templates into the first one that is found.
        if (is_array($template)) {
            $templates = (new Collection($template))
                ->map(function ($element): ?string {
                    if ($element instanceof TemplateselectField) {
                        return $element->__toString();
                    }
                    return $element;
                })
                ->filter()
                ->toArray();
            $template = $this->twig->resolveTemplate($templates);
        }

        $content = $this->twig->render($template, $parameters);

        if ($response === null) {
            $response = new Response();
        }

        $response->setContent($content);

        return $response;
    }
}


@@ -27,21 +27,18 @@ class ProductTemplateRelationshipStrategy implements RelationshipStrategyInterfa
         $this->query = $query;
     }

-    /**
-     * {@inheritDoc}
-     */
     public function supports(AggregateId $id): bool
     {
         return $id instanceof TemplateId;
     }

-    /**
-     * {@inheritDoc}
-     */
     public function getRelationshipGroup(AggregateId $id): RelationshipGroup
     {
         Assert::isInstanceOf($id, TemplateId::class);

-        return new RelationshipGroup(self::MESSAGE, $this->query->findProductIdByTemplateId($id));
+        $relations = $this->query->findProductIdByTemplateId($id);
+        $message = count($relations) === 1 ? self::ONE_MESSAGE : self::MULTIPLE_MESSAGE;
+
+        return new RelationshipGroup($message, $relations);
     }
 }

<?php

/**
 * Copyright © Ergonode Sp. z o.o. All rights reserved.
 * See LICENSE.txt for license details.
 */

declare(strict_types=1);

namespace Ergonode\Designer\Infrastructure\Strategy\Relationship;

use Ergonode\Core\Infrastructure\Strategy\RelationshipStrategyInterface;
use Ergonode\Designer\Domain\Query\TemplateQueryInterface;
use Ergonode\SharedKernel\Domain\Aggregate\TemplateId;
use Ergonode\SharedKernel\Domain\AggregateId;
use Webmozart\Assert\Assert;
use Ergonode\Core\Infrastructure\Model\RelationshipGroup;

class ProductTemplateRelationshipStrategy implements RelationshipStrategyInterface
{
    private const MESSAGE = 'Object has active relationships with product %relations%';

    private TemplateQueryInterface $query;

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

    /**
     * {@inheritDoc}
     */
    public function supports(AggregateId $id): bool
    {
        return $id instanceof TemplateId;
    }

    /**
     * {@inheritDoc}
     */
    public function getRelationshipGroup(AggregateId $id): RelationshipGroup
    {
        Assert::isInstanceOf($id, TemplateId::class);

        return new RelationshipGroup(self::MESSAGE, $this->query->findProductIdByTemplateId($id));
    }
}


@@ -169,10 +169,22 @@ class Settings extends Controller
     }

     /**
-     * Locates a setting item for a module or plugin
+     * Locates a setting item for a module or plugin.
+     *
+     * If none of the parameters are provided, they will be auto-guessed from the URL.
+     *
+     * @param string|null $author
+     * @param string|null $plugin
+     * @param string|null $code
+     *
+     * @return array
      */
-    protected function findSettingItem($author, $plugin, $code)
+    protected function findSettingItem($author = null, $plugin = null, $code = null)
     {
+        if (is_null($author) || is_null($plugin)) {
+            [$author, $plugin, $code] = $this->guessSettingItem();
+        }
+
         $manager = SettingsManager::instance();

         $moduleOwner = $author;

<?php namespace System\Controllers;

use Lang;
use Flash;
use Backend;
use BackendMenu;
use System\Classes\SettingsManager;
use Backend\Classes\Controller;
use ApplicationException;
use Exception;

/**
 * Settings controller
 *
 * @package october\system
 * @author Alexey Bobkov, Samuel Georges
 *
 */
class Settings extends Controller
{
    /**
     * @var WidgetBase Reference to the widget object.
     */
    protected $formWidget;

    /**
     * @var array Permissions required to view this page.
     */
    public $requiredPermissions = [];

    /**
     * Constructor.
     */
    public function __construct()
    {
        parent::__construct();

        if ($this->action == 'backend_preferences') {
            $this->requiredPermissions = ['backend.manage_preferences'];
        }

        $this->addCss('/modules/system/assets/css/settings/settings.css', 'core');

        BackendMenu::setContext('October.System', 'system', 'settings');
    }

    public function index()
    {
        $this->pageTitle = 'system::lang.settings.menu_label';
        $this->vars['items'] = SettingsManager::instance()->listItems('system');
        $this->bodyClass = 'compact-container sidenav-tree-root';
    }

    public function mysettings()
    {
        BackendMenu::setContextSideMenu('mysettings');
        $this->pageTitle = 'backend::lang.mysettings.menu_label';
        $this->vars['items'] = SettingsManager::instance()->listItems('mysettings');
        $this->bodyClass = 'compact-container';
    }

    //
    // Generated Form
    //

    public function update($author, $plugin, $code = null)
    {
        SettingsManager::setContext($author.'.'.$plugin, $code);

        $this->vars['parentLink'] = Backend::url('system/settings');
        $this->vars['parentLabel'] = Lang::get('system::lang.settings.menu_label');

        try {
            if (!$item = $this->findSettingItem($author, $plugin, $code)) {
                throw new ApplicationException(Lang::get('system::lang.settings.not_found'));
            }

            $this->pageTitle = $item->label;

            if ($item->context == 'mysettings') {
                $this->vars['parentLink'] = Backend::url('system/settings/mysettings');
                $this->vars['parentLabel'] = Lang::get('backend::lang.mysettings.menu_label');
            }

            $model = $this->createModel($item);
            $this->initWidgets($model);
        }
        catch (Exception $ex) {
            $this->handleError($ex);
        }
    }

    public function update_onSave($author, $plugin, $code = null)
    {
        $item = $this->findSettingItem($author, $plugin, $code);
        $model = $this->createModel($item);
        $this->initWidgets($model);

        $saveData = $this->formWidget->getSaveData();
        foreach ($saveData as $attribute => $value) {
            $model->{$attribute} = $value;
        }
        $model->save(null, $this->formWidget->getSessionKey());

        Flash::success(Lang::get('system::lang.settings.update_success', ['name' => Lang::get($item->label)]));

        /*
         * Handle redirect
         */
        if ($redirectUrl = post('redirect', true)) {
            $redirectUrl = ($item->context == 'mysettings')
                ? 'system/settings/mysettings'
                : 'system/settings';

            return Backend::redirect($redirectUrl);
        }
    }

    public function update_onResetDefault($author, $plugin, $code = null)
    {
        $item = $this->findSettingItem($author, $plugin, $code);
        $model = $this->createModel($item);
        $model->resetDefault();

        Flash::success(Lang::get('backend::lang.form.reset_success'));

        return Backend::redirect('system/settings/update/'.$author.'/'.$plugin.'/'.$code);
    }

    /**
     * Render the form.
     */
    public function formRender($options = [])
    {
        if (!$this->formWidget) {
            throw new ApplicationException(Lang::get('backend::lang.form.behavior_not_ready'));
        }

        return $this->formWidget->render($options);
    }

    /**
     * Prepare the widgets used by this action
     * Model $model
     */
    protected function initWidgets($model)
    {
        $config = $model->getFieldConfig();
        $config->model = $model;
        $config->arrayName = class_basename($model);
        $config->context = 'update';

        $widget = $this->makeWidget('Backend\Widgets\Form', $config);
        $widget->bindToController();
        $this->formWidget = $widget;
    }

    /**
     * Internal method, prepare the list model object
     */
    protected function createModel($item)
    {
        if (!isset($item->class) || !strlen($item->class)) {
            throw new ApplicationException(Lang::get('system::lang.settings.missing_model'));
        }

        $class = $item->class;
        return $class::instance();
    }

    /**
     * Locates a setting item for a module or plugin
     */
    protected function findSettingItem($author, $plugin, $code)
    {
        $manager = SettingsManager::instance();

        $moduleOwner = $author;
        $moduleCode = $plugin;
        $item = $manager->findSettingItem($moduleOwner, $moduleCode);

        if (!$item) {
            $pluginOwner = $author . '.' . $plugin;
            $pluginCode = $code;
            $item = $manager->findSettingItem($pluginOwner, $pluginCode);
        }

        return $item;
    }
}


@@ -87,6 +87,7 @@ class MailLayout extends Model
     /**
      * Loops over each mail layout and ensures the system has a layout,
      * if the layout does not exist, it will create one.
+     *
      * @return void
      */
     public static function createLayouts()

<?php namespace System\Models;

use View;
use Model;
use System\Classes\MailManager;
use October\Rain\Mail\MailParser;
use ApplicationException;
use File as FileHelper;

/**
 * Mail layout
 *
 * @package october\system
 * @author Alexey Bobkov, Samuel Georges
 */
class MailLayout extends Model
{
    use \October\Rain\Database\Traits\Validation;

    /**
     * @var string The database table used by the model.
     */
    protected $table = 'system_mail_layouts';

    /**
     * @var array Guarded fields
     */
    protected $guarded = [];

    /**
     * @var array Fillable fields
     */
    protected $fillable = [];

    /**
     * @var array Validation rules
     */
    public $rules = [
        'code'                  => 'required|unique:system_mail_layouts',
        'name'                  => 'required',
        'content_html'          => 'required',
    ];

    /**
     * @var array Options array
     */
    protected $jsonable = [
        'options'
    ];

    public static $codeCache;

    public function beforeDelete()
    {
        if ($this->is_locked) {
            throw new ApplicationException('Cannot delete this template because it is locked');
        }
    }

    public static function listCodes()
    {
        if (self::$codeCache !== null) {
            return self::$codeCache;
        }

        return self::$codeCache = self::lists('id', 'code');
    }

    public static function getIdFromCode($code)
    {
        return array_get(self::listCodes(), $code);
    }

    public static function findOrMakeLayout($code)
    {
        $layout = self::whereCode($code)->first();

        if (!$layout && View::exists($code)) {
            $layout = new self;
            $layout->code = $code;
            $layout->fillFromView($code);
        }

        return $layout;
    }

    /**
     * Loops over each mail layout and ensures the system has a layout,
     * if the layout does not exist, it will create one.
     * @return void
     */
    public static function createLayouts()
    {
        $dbLayouts = self::lists('code', 'code');

        $definitions = MailManager::instance()->listRegisteredLayouts();
        foreach ($definitions as $code => $path) {
            if (array_key_exists($code, $dbLayouts)) {
                continue;
            }

            $layout = new static;
            $layout->code = $code;
            $layout->is_locked = true;
            $layout->fillFromView($path);
            $layout->save();
        }
    }

    public function fillFromCode($code = null)
    {
        $definitions = MailManager::instance()->listRegisteredLayouts();

        if ($code === null) {
            $code = $this->code;
        }

        if (!$definition = array_get($definitions, $code)) {
            throw new ApplicationException('Unable to find a registered layout with code: '.$code);
        }

        $this->fillFromView($definition);
    }

    public function fillFromView($path)
    {
        $sections = self::getTemplateSections($path);

        $css = '
@media only screen and (max-width: 600px) {
    .inner-body {
        width: 100% !important;
    }

    .footer {
        width: 100% !important;
    }
}

@media only screen and (max-width: 500px) {
    .button {
        width: 100% !important;
    }
}
        ';

        $this->name = array_get($sections, 'settings.name', '???');
        $this->content_css = $css;
        $this->content_html =  array_get($sections, 'html');
        $this->content_text = array_get($sections, 'text');
    }

    protected static function getTemplateSections($code)
    {
        return MailParser::parse(FileHelper::get(View::make($code)->getPath()));
    }
}


@@ -3,6 +3,7 @@
 namespace Shopsys\FrameworkBundle\DependencyInjection\Compiler;

 use Doctrine\Common\Cache\RedisCache;
+use Shopsys\FrameworkBundle\Component\Environment\EnvironmentType;
 use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
 use Symfony\Component\DependencyInjection\ContainerBuilder;

<?php

namespace Shopsys\FrameworkBundle\DependencyInjection\Compiler;

use Doctrine\Common\Cache\RedisCache;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;

/**
 * Set Redis-related services as lazy because it might not even be connected (e.g during Docker image build)
 */
class LazyRedisCompilerPass implements CompilerPassInterface
{
    /**
     * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
     */
    public function process(ContainerBuilder $container): void
    {
        $container->getDefinition('session')
            ->setLazy(true);

        foreach ($container->getDefinitions() as $definition) {
            if ($definition->getClass() === RedisCache::class) {
                $definition->setLazy(true);
            }
        }
    }
}


@@ -2,8 +2,8 @@

 namespace Shopsys\FrameworkBundle\Form\Admin\Heureka;

+use Shopsys\FrameworkBundle\Form\GroupType;
 use Symfony\Component\Form\AbstractType;
-use Symfony\Component\Form\Extension\Core\Type\FormType;
 use Symfony\Component\Form\Extension\Core\Type\SubmitType;
 use Symfony\Component\Form\Extension\Core\Type\TextareaType;
 use Symfony\Component\Form\Extension\Core\Type\TextType;

<?php

namespace Shopsys\FrameworkBundle\Form\Admin\Heureka;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints;

class HeurekaShopCertificationFormType extends AbstractType
{
    /**
     * @param \Symfony\Component\Form\FormBuilderInterface $builder
     * @param array $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builderSettingsGroup = $builder->create('settings', FormType::class, [
            'inherit_data' => true,
            'label' => t('Settings'),
            'is_group_container' => true,
            'is_group_container_to_render_as_the_last_one' => true,
        ]);

        $builderSettingsGroup
            ->add('heurekaApiKey', TextType::class, [
                'required' => false,
                'constraints' => [
                    new Constraints\Length([
                        'min' => 32,
                        'max' => 32,
                        'exactMessage' => 'Heureka API must be {{ limit }} characters',
                    ]),
                ],
                'label' => t('Code of service Heureka - Verified by Customer'),
                'icon_title' => t('Enter 32-digit code which will be sent to server') . ' ' . $options['server_name'],
            ])
            ->add('heurekaWidgetCode', TextareaType::class, [
                'required' => false,
                'label' => t('Heureka Widget code'),
            ]);

        $builder
            ->add($builderSettingsGroup)
            ->add('save', SubmitType::class);
    }

    /**
     * @param \Symfony\Component\OptionsResolver\OptionsResolver $resolver
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver
            ->setRequired('server_name')
            ->setAllowedTypes('server_name', ['string', 'null'])
            ->setDefaults([
            'attr' => ['novalidate' => 'novalidate'],
            ]);
    }
}


@@ -13,9 +13,11 @@ declare(strict_types=1);

 namespace Sonata\MediaBundle\Listener\ODM;

-use Doctrine\Common\EventArgs;
+use Doctrine\ODM\MongoDB\Event\LifecycleEventArgs as ODMLifecycleEventArgs;
 use Doctrine\ODM\MongoDB\Events;
+use Doctrine\Persistence\Event\LifecycleEventArgs;
 use Sonata\MediaBundle\Listener\BaseMediaEventSubscriber;
+use Sonata\MediaBundle\Model\MediaInterface;

 final class MediaEventSubscriber extends BaseMediaEventSubscriber
 {

<?php

declare(strict_types=1);

/*
 * This file is part of the Sonata Project package.
 *
 * (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Sonata\MediaBundle\Listener\ODM;

use Doctrine\Common\EventArgs;
use Doctrine\ODM\MongoDB\Events;
use Sonata\MediaBundle\Listener\BaseMediaEventSubscriber;

final class MediaEventSubscriber extends BaseMediaEventSubscriber
{
    public function getSubscribedEvents()
    {
        return [
            Events::prePersist,
            Events::preUpdate,
            Events::preRemove,
            Events::postUpdate,
            Events::postRemove,
            Events::postPersist,
        ];
    }

    protected function recomputeSingleEntityChangeSet(EventArgs $args): void
    {
        $em = $args->getDocumentManager();

        $em->getUnitOfWork()->recomputeSingleDocumentChangeSet(
            $em->getClassMetadata(\get_class($args->getDocument())),
            $args->getDocument()
        );
    }

    protected function getMedia(EventArgs $args)
    {
        return $args->getDocument();
    }
}


@@ -17,8 +17,6 @@ use Ergonode\Multimedia\Application\Model\MultimediaUploadModel;
 use Ergonode\Multimedia\Application\Form\MultimediaUploadForm;
 use Ergonode\Multimedia\Domain\Command\AddMultimediaCommand;
 use Ergonode\SharedKernel\Domain\Aggregate\MultimediaId;
-use Ergonode\Multimedia\Domain\Query\MultimediaQueryInterface;
-use Ergonode\Multimedia\Infrastructure\Service\HashCalculationServiceInterface;
 use Ergonode\SharedKernel\Domain\Bus\CommandBusInterface;
 use Symfony\Component\Form\FormFactoryInterface;
 use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;

<?php

/**
 * Copyright © Ergonode Sp. z o.o. All rights reserved.
 * See LICENSE.txt for license details.
 */

declare(strict_types=1);

namespace Ergonode\Multimedia\Application\Controller\Api\Multimedia;

use Ergonode\Api\Application\Exception\FormValidationHttpException;
use Swagger\Annotations as SWG;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Ergonode\Multimedia\Application\Model\MultimediaUploadModel;
use Ergonode\Multimedia\Application\Form\MultimediaUploadForm;
use Ergonode\Multimedia\Domain\Command\AddMultimediaCommand;
use Ergonode\SharedKernel\Domain\Aggregate\MultimediaId;
use Ergonode\Multimedia\Domain\Query\MultimediaQueryInterface;
use Ergonode\Multimedia\Infrastructure\Service\HashCalculationServiceInterface;
use Ergonode\SharedKernel\Domain\Bus\CommandBusInterface;
use Symfony\Component\Form\FormFactoryInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;

/**
 * @Route(
 *     name="ergonode_multimedia_upload",
 *     path="/multimedia/upload",
 *     methods={"POST"},
 * )
 */
class UploadMultimediaAction
{
    private MultimediaQueryInterface $query;

    private HashCalculationServiceInterface $hashService;

    private FormFactoryInterface $formFactory;

    private CommandBusInterface $commandBus;

    public function __construct(
        MultimediaQueryInterface $query,
        HashCalculationServiceInterface $hashService,
        FormFactoryInterface $formFactory,
        CommandBusInterface $commandBus
    ) {
        $this->query = $query;
        $this->hashService = $hashService;
        $this->formFactory = $formFactory;
        $this->commandBus = $commandBus;
    }

    /**
     * @IsGranted("ERGONODE_ROLE_MULTIMEDIA_POST")
     *
     * @SWG\Tag(name="Multimedia")
     * @SWG\Parameter(
     *     name="upload",
     *     in="formData",
     *     type="file",
     *     description="The field used to upload multimedia",
     * )
     * @SWG\Response(
     *     response=201,
     *     description="Returns multimedia ID",
     * )
     * @SWG\Response(
     *     response=400,
     *     description="Validation error",
     *     @SWG\Schema(ref="#/definitions/validation_error_response")
     * )
     *
     * @throws \Exception
     */
    public function __invoke(Request $request): MultimediaId
    {
        $uploadModel = new MultimediaUploadModel();

        $form = $this->formFactory->create(MultimediaUploadForm::class, $uploadModel);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $hash = $this->hashService->calculateHash($uploadModel->upload);
            if ($this->query->fileExists($hash)) {
                $id = $this->query->findIdByHash($hash);
            } else {
                $command = new AddMultimediaCommand(MultimediaId::generate(), $uploadModel->upload);
                $this->commandBus->dispatch($command);
                $id = $command->getId();
            }
        } else {
            throw new FormValidationHttpException($form);
        }

        return $id;
    }
}


@@ -5,6 +5,7 @@ namespace Shopsys\FrameworkBundle\Model\Product\Search\Export;
 use Doctrine\ORM\EntityManagerInterface;
 use Doctrine\ORM\Query\Expr\Join;
 use Doctrine\ORM\QueryBuilder;
+use Shopsys\FrameworkBundle\Component\Paginator\QueryPaginator;
 use Shopsys\FrameworkBundle\Model\Product\Product;
 use Shopsys\FrameworkBundle\Model\Product\ProductVisibility;

<?php

namespace Shopsys\FrameworkBundle\Model\Product\Search\Export;

use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\QueryBuilder;
use Shopsys\FrameworkBundle\Model\Product\Product;
use Shopsys\FrameworkBundle\Model\Product\ProductVisibility;

/**
 * @deprecated Use ProductSearchExportWithFilterRepository instead
 */
class ProductSearchExportRepository
{
    /**
     * @var \Doctrine\ORM\EntityManagerInterface
     */
    protected $em;

    /**
     * @param \Doctrine\ORM\EntityManagerInterface $em
     */
    public function __construct(EntityManagerInterface $em)
    {
        $this->em = $em;
    }

    /**
     * @param int $domainId
     * @param string $locale
     * @param int $startFrom
     * @param int $batchSize
     * @return array
     */
    public function getProductsData(int $domainId, string $locale, int $startFrom, int $batchSize): array
    {
        $queryBuilder = $this->createQueryBuilder($domainId, $locale)
            ->setFirstResult($startFrom)
            ->setMaxResults($batchSize);

        $query = $queryBuilder->getQuery();
        return $query->getArrayResult();
    }

    /**
     * @param int $domainId
     * @param string $locale
     * @return \Doctrine\ORM\QueryBuilder
     */
    protected function createQueryBuilder(int $domainId, string $locale): QueryBuilder
    {
        $fields = 'p.id, p.catnum, p.partno, p.ean, t.name, d.description, d.shortDescription';
        $queryBuilder = $this->em->createQueryBuilder()
            ->select($fields)
            ->from(Product::class, 'p')
                ->where('p.variantType != :variantTypeVariant')
            ->join(ProductVisibility::class, 'prv', Join::WITH, 'prv.product = p.id')
                ->andWhere('prv.domainId = :domainId')
                ->andWhere('prv.visible = TRUE')
            ->join('p.translations', 't')
                ->andWhere('t.locale = :locale')
            ->join('p.domains', 'd')
                ->andWhere('d.domainId = :domainId')
            ->groupBy($fields)
            ->orderBy('p.id');

        $queryBuilder->setParameter('domainId', $domainId)
            ->setParameter('locale', $locale)
            ->setParameter('variantTypeVariant', Product::VARIANT_TYPE_VARIANT);

        return $queryBuilder;
    }
}


@@ -124,3 +124,45 @@ func GetDBAImage() string {
 func GetBgsyncImage() string {
 	return fmt.Sprintf("%s:%s", BgsyncImg, BgsyncTag)
 }
+
+// GetDockerComposeVersion runs docker-compose -v to get the current version
+func GetDockerComposeVersion() (string, error) {
+	if DockerComposeVersion != "" {
+		return DockerComposeVersion, nil
+	}
+
+	executableName := "docker-compose"
+
+	path, err := exec.LookPath(executableName)
+	if err != nil {
+		return "", fmt.Errorf("no docker-compose")
+	}
+
+	out, err := exec.Command(path, "version", "--short").Output()
+	if err != nil {
+		return "", err
+	}
+
+	v := string(out)
+	DockerComposeVersion = strings.TrimSpace(v)
+	return DockerComposeVersion, nil
+}
+
+// GetDockerVersion gets the cached or api-sourced version of docker engine
+func GetDockerVersion() (string, error) {
+	if DockerVersion != "" {
+		return DockerVersion, nil
+	}
+	var client *docker.Client
+	var err error
+	if client, err = docker.NewClientFromEnv(); err != nil {
+		return "", err
+	}
+
+	v, err := client.Version()
+	if err != nil {
+		return "", err
+	}
+	DockerVersion = v.Get("Version")
+	return DockerVersion, nil
+}

package version

import (
	"fmt"
)

// MariaDBDefaultVersion is the default version we use in the db container
const MariaDBDefaultVersion = "10.2"

// VERSION is supplied with the git committish this is built from
var VERSION = ""

// IMPORTANT: These versions are overridden by version ldflags specifications VERSION_VARIABLES in the Makefile

// DdevVersion is the current version of ddev, by default the git committish (should be current git tag)
var DdevVersion = "v0.0.0-overridden-by-make" // Note that this is overridden by make

// SentryDSN is the ddev-specific key for the Sentry service.
// It is compiled in using link-time variables
var SentryDSN = ""

// DockerVersionConstraint is the current minimum version of docker required for ddev.
// See https://godoc.org/github.com/Masterminds/semver#hdr-Checking_Version_Constraints
// for examples defining version constraints.
// REMEMBER TO CHANGE docs/index.md if you touch this!
var DockerVersionConstraint = ">= 18.06.0-ce"

// DockerComposeVersionConstraint is the current minimum version of docker-compose required for ddev.
// REMEMBER TO CHANGE docs/index.md if you touch this!
var DockerComposeVersionConstraint = ">= 1.21.0"

// DockerComposeFileFormatVersion is the compose version to be used
var DockerComposeFileFormatVersion = "3.6"

// WebImg defines the default web image used for applications.
var WebImg = "drud/ddev-webserver"

// WebTag defines the default web image tag for drud dev
var WebTag = "20181124_pecl_upload_progress" // Note that this can be overridden by make

// DBImg defines the default db image used for applications.
var DBImg = "drud/ddev-dbserver"

// BaseDBTag is the main tag, DBTag is constructed from it
var BaseDBTag = "20181203_mariadb_2_versions"

// DBAImg defines the default phpmyadmin image tag used for applications.
var DBAImg = "drud/phpmyadmin"

// DBATag defines the default phpmyadmin image tag used for applications.
var DBATag = "v1.4.0" // Note that this can be overridden by make

// BgsyncImg defines the default bgsync image tag used for applications.
var BgsyncImg = "drud/ddev-bgsync"

// BgsyncTag defines the default phpmyadmin image tag used for applications.
var BgsyncTag = "20181117_bgsync" // Note that this can be overridden by make

// RouterImage defines the image used for the router.
var RouterImage = "drud/ddev-router"

// RouterTag defines the tag used for the router.
var RouterTag = "v1.4.0" // Note that this can be overridden by make

var SSHAuthImage = "drud/ddev-ssh-agent"

var SSHAuthTag = "20181122_load_all_keys"

// COMMIT is the actual committish, supplied by make
var COMMIT = "COMMIT should be overridden"

// BUILDINFO is information with date and context, supplied by make
var BUILDINFO = "BUILDINFO should have new info"

// DockerVersion is cached version of docker
var DockerVersion = ""

// DockerComposeVersion is filled with the version we find for docker-compose
var DockerComposeVersion = ""

// DDevTLD defines the tld to use for DDev site URLs.
const DDevTLD = "ddev.local"

// GetVersionInfo returns a map containing the version info defined above.
func GetVersionInfo() map[string]string {
	versionInfo := make(map[string]string)

	versionInfo["cli"] = DdevVersion
	versionInfo["web"] = GetWebImage()
	versionInfo["db"] = GetDBImage()
	versionInfo["dba"] = GetDBAImage()
	versionInfo["bgsync"] = BgsyncImg + ":" + BgsyncTag
	versionInfo["router"] = RouterImage + ":" + RouterTag
	versionInfo["ddev-ssh-agent"] = SSHAuthImage + ":" + SSHAuthTag
	versionInfo["commit"] = COMMIT
	versionInfo["domain"] = DDevTLD
	versionInfo["build info"] = BUILDINFO
	versionInfo["docker"] = DockerVersion
	versionInfo["docker-compose"] = DockerComposeVersion

	return versionInfo
}

// GetWebImage returns the correctly formatted web image:tag reference
func GetWebImage() string {
	return fmt.Sprintf("%s:%s", WebImg, WebTag)
}

// GetDBImage returns the correctly formatted db image:tag reference
func GetDBImage(mariaDBVersion ...string) string {
	version := MariaDBDefaultVersion
	if len(mariaDBVersion) > 0 {
		version = mariaDBVersion[0]
	}
	return fmt.Sprintf("%s:%s", DBImg, BaseDBTag+"-"+version)
}

// GetDBAImage returns the correctly formatted dba image:tag reference
func GetDBAImage() string {
	return fmt.Sprintf("%s:%s", DBAImg, DBATag)
}

// GetDBAImage returns the correctly formatted dba image:tag reference
func GetBgsyncImage() string {
	return fmt.Sprintf("%s:%s", BgsyncImg, BgsyncTag)
}


@@ -2,10 +2,12 @@

 namespace EasyCorp\Bundle\EasyAdminBundle\DependencyInjection;

+use EasyCorp\Bundle\EasyAdminBundle\Cache\CacheWarmer;
 use EasyCorp\Bundle\EasyAdminBundle\Registry\CrudControllerRegistry;
 use EasyCorp\Bundle\EasyAdminBundle\Registry\DashboardControllerRegistry;
 use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
 use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Reference;

 /**
  * Creates the services of the Dashboard and CRUD controller registries. They can't

<?php

namespace EasyCorp\Bundle\EasyAdminBundle\DependencyInjection;

use EasyCorp\Bundle\EasyAdminBundle\Registry\CrudControllerRegistry;
use EasyCorp\Bundle\EasyAdminBundle\Registry\DashboardControllerRegistry;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;

/**
 * Creates the services of the Dashboard and CRUD controller registries. They can't
 * be defined as normal services because they cause circular dependencies.
 * See https://github.com/EasyCorp/EasyAdminBundle/issues/3541
 *
 * @author Javier Eguiluz <javier.eguiluz@gmail.com>
 */
class CreateControllerRegistriesPass implements CompilerPassInterface
{
    public function process(ContainerBuilder $container)
    {
        $this->createDashboardControllerRegistryService($container);
        $this->createCrudControllerRegistryService($container);
    }

    private function createDashboardControllerRegistryService(ContainerBuilder $container): void
    {
        $dashboardControllersFqcn = array_keys($container->findTaggedServiceIds(EasyAdminExtension::TAG_DASHBOARD_CONTROLLER, true));

        $container
            ->register(DashboardControllerRegistry::class, DashboardControllerRegistry::class)
            ->setPublic(false)
            ->setArguments([
                $container->getParameter('kernel.secret'),
                $container->getParameter('kernel.cache_dir'),
                $dashboardControllersFqcn,
            ]);
    }

    private function createCrudControllerRegistryService(ContainerBuilder $container): void
    {
        $crudControllersFqcn = array_keys($container->findTaggedServiceIds(EasyAdminExtension::TAG_CRUD_CONTROLLER, true));

        $container
            ->register(CrudControllerRegistry::class, CrudControllerRegistry::class)
            ->setPublic(false)
            ->setArguments([
                $container->getParameter('kernel.secret'),
                $crudControllersFqcn,
            ]);
    }
}


@@ -90,46 +90,6 @@ class ProductsResolver implements ResolverInterface, AliasedInterface
         return $paginator->auto($argument, count($products));
     }

-    /**
-     * @param \Shopsys\FrameworkBundle\Model\Category\Category $category
-     * @param int $offset
-     * @param int $limit
-     * @return \Shopsys\FrameworkBundle\Model\Product\Product[]
-     */
-    protected function getProductsByCategory(Category $category, int $offset, int $limit): array
-    {
-        $queryBuilder = $this->productOnCurrentDomainFacade->getAllListableTranslatedAndOrderedQueryBuilderByCategory(
-            ProductListOrderingConfig::ORDER_BY_PRIORITY,
-            $category
-        );
-
-        $queryBuilder->setFirstResult($offset)
-            ->setMaxResults($limit + 2);
-        $query = $queryBuilder->getQuery();
-        $query->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, SortableNullsWalker::class);
-
-        return $query->execute();
-    }
-
-    /**
-     * @param int $offset
-     * @param int $limit
-     * @return \Shopsys\FrameworkBundle\Model\Product\Product[]
-     */
-    protected function getProductsForAll(int $offset, int $limit): array
-    {
-        $queryBuilder = $this->productOnCurrentDomainFacade->getAllListableTranslatedAndOrderedQueryBuilder(
-            ProductListOrderingConfig::ORDER_BY_PRIORITY
-        );
-
-        $queryBuilder->setFirstResult($offset)
-            ->setMaxResults($limit + 2);
-        $query = $queryBuilder->getQuery();
-        $query->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, SortableNullsWalker::class);
-
-        return $query->execute();
-    }
-
     /**
      * @param \Overblog\GraphQLBundle\Definition\Argument $argument
      */

<?php

declare(strict_types=1);

namespace Shopsys\FrontendApiBundle\Model\Resolver\Products;

use Doctrine\ORM\Query;
use Overblog\GraphQLBundle\Definition\Argument;
use Overblog\GraphQLBundle\Definition\Resolver\AliasedInterface;
use Overblog\GraphQLBundle\Definition\Resolver\ResolverInterface;
use Overblog\GraphQLBundle\Relay\Connection\ConnectionBuilder;
use Overblog\GraphQLBundle\Relay\Connection\Paginator;
use Shopsys\FrameworkBundle\Component\Doctrine\SortableNullsWalker;
use Shopsys\FrameworkBundle\Model\Category\Category;
use Shopsys\FrameworkBundle\Model\Product\Listing\ProductListOrderingConfig;
use Shopsys\FrameworkBundle\Model\Product\ProductOnCurrentDomainFacade;

class ProductsResolver implements ResolverInterface, AliasedInterface
{
    protected const DEFAULT_FIRST_LIMIT = 10;

    /**
     * @var \Shopsys\FrameworkBundle\Model\Product\ProductOnCurrentDomainFacade
     */
    protected $productOnCurrentDomainFacade;

    /**
     * @var \Overblog\GraphQLBundle\Relay\Connection\ConnectionBuilder
     */
    protected $connectionBuilder;

    /**
     * @param \Shopsys\FrameworkBundle\Model\Product\ProductOnCurrentDomainFacade $productOnCurrentDomainFacade
     */
    public function __construct(
        ProductOnCurrentDomainFacade $productOnCurrentDomainFacade
    ) {
        $this->productOnCurrentDomainFacade = $productOnCurrentDomainFacade;
        $this->connectionBuilder = new ConnectionBuilder();
    }

    /**
     * @param \Overblog\GraphQLBundle\Definition\Argument $argument
     * @return \Overblog\GraphQLBundle\Relay\Connection\ConnectionInterface|object
     */
    public function resolve(Argument $argument)
    {
        if ($argument->offsetExists('last')) {
            $limit = (int)$argument->offsetGet('last');
            $cursor = $argument->offsetGet('before');
            $offset = max((int)$this->connectionBuilder->cursorToOffset($cursor) - $limit, 0);
        } else {
            $this->setDefaultFirstOffsetIfNecessary($argument);
            $limit = (int)$argument->offsetGet('first');
            $cursor = $argument->offsetGet('after');
            $offset = (int)$this->connectionBuilder->cursorToOffset($cursor);
        }

        $products = $this->getProductsForAll($offset, $limit);
        $paginator = new Paginator(function () use ($products) {
            return $products;
        });

        return $paginator->auto($argument, count($products));
    }

    /**
     * @param \Overblog\GraphQLBundle\Definition\Argument $argument
     * @param \Shopsys\FrameworkBundle\Model\Category\Category $category
     * @return \Overblog\GraphQLBundle\Relay\Connection\ConnectionInterface|object
     */
    public function resolveByCategory(Argument $argument, Category $category)
    {
        if ($argument->offsetExists('last')) {
            $limit = (int)$argument->offsetGet('last');
            $cursor = $argument->offsetGet('before');
            $offset = max((int)$this->connectionBuilder->cursorToOffset($cursor) - $limit, 0);
        } else {
            $this->setDefaultFirstOffsetIfNecessary($argument);
            $limit = (int)$argument->offsetGet('first');
            $cursor = $argument->offsetGet('after');
            $offset = (int)$this->connectionBuilder->cursorToOffset($cursor);
        }

        $products = $this->getProductsByCategory($category, $offset, $limit);
        $paginator = new Paginator(function () use ($products) {
            return $products;
        });

        return $paginator->auto($argument, count($products));
    }

    /**
     * @param \Shopsys\FrameworkBundle\Model\Category\Category $category
     * @param int $offset
     * @param int $limit
     * @return \Shopsys\FrameworkBundle\Model\Product\Product[]
     */
    protected function getProductsByCategory(Category $category, int $offset, int $limit): array
    {
        $queryBuilder = $this->productOnCurrentDomainFacade->getAllListableTranslatedAndOrderedQueryBuilderByCategory(
            ProductListOrderingConfig::ORDER_BY_PRIORITY,
            $category
        );

        $queryBuilder->setFirstResult($offset)
            ->setMaxResults($limit + 2);
        $query = $queryBuilder->getQuery();
        $query->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, SortableNullsWalker::class);

        return $query->execute();
    }

    /**
     * @param int $offset
     * @param int $limit
     * @return \Shopsys\FrameworkBundle\Model\Product\Product[]
     */
    protected function getProductsForAll(int $offset, int $limit): array
    {
        $queryBuilder = $this->productOnCurrentDomainFacade->getAllListableTranslatedAndOrderedQueryBuilder(
            ProductListOrderingConfig::ORDER_BY_PRIORITY
        );

        $queryBuilder->setFirstResult($offset)
            ->setMaxResults($limit + 2);
        $query = $queryBuilder->getQuery();
        $query->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, SortableNullsWalker::class);

        return $query->execute();
    }

    /**
     * @param \Overblog\GraphQLBundle\Definition\Argument $argument
     */
    protected function setDefaultFirstOffsetIfNecessary(Argument $argument): void
    {
        if ($argument->offsetExists('first') === false) {
            $argument->offsetSet('first', static::DEFAULT_FIRST_LIMIT);
        }
    }

    /**
     * @return string[]
     */
    public static function getAliases(): array
    {
        return [
            'resolve' => 'products',
        ];
    }
}


@@ -24,8 +24,9 @@ class FPN(nn.Module):
             build the feature pyramid. Default: -1, which means the last level.
         add_extra_convs (bool): Whether to add conv layers on top of the
             original feature maps. Default: False.
-        extra_convs_on_inputs (bool): Whether to apply extra conv on
-            the original feature from the backbone. Default: False.
+        extra_convs_on_inputs (bool): Whether to apply extra convs
+            on the original feature from the backbone. Default: True. If True,
+            it is equivalent to `extra_convs_source='inputs'`.
         relu_before_extra_convs (bool): Whether to apply relu before the extra
             conv. Default: False.
         no_norm_on_lateral (bool): Whether to apply norm on lateral.

import torch.nn as nn
import torch.nn.functional as F
from mmcv.cnn import ConvModule, xavier_init

from mmdet.core import auto_fp16
from ..builder import NECKS

@NECKS.register_module()
class FPN(nn.Module):
    """
    Feature Pyramid Network.

    This is an implementation of - Feature Pyramid Networks for Object
    Detection (https://arxiv.org/abs/1612.03144)

    Args:
        in_channels (List[int]): Number of input channels per scale.
        out_channels (int): Number of output channels (used at each scale)
        num_outs (int): Number of output scales.
        start_level (int): Index of the start input backbone level used to
            build the feature pyramid. Default: 0.
        end_level (int): Index of the end input backbone level (exclusive) to
            build the feature pyramid. Default: -1, which means the last level.
        add_extra_convs (bool): Whether to add conv layers on top of the
            original feature maps. Default: False.
        extra_convs_on_inputs (bool): Whether to apply extra conv on
            the original feature from the backbone. Default: False.
        relu_before_extra_convs (bool): Whether to apply relu before the extra
            conv. Default: False.
        no_norm_on_lateral (bool): Whether to apply norm on lateral.
            Default: False.
        conv_cfg (dict): Config dict for convolution layer. Default: None.
        norm_cfg (dict): Config dict for normalization layer. Default: None.
        act_cfg (str): Config dict for activation layer in ConvModule.
            Default: None.

    Example:
        >>> import torch
        >>> in_channels = [2, 3, 5, 7]
        >>> scales = [340, 170, 84, 43]
        >>> inputs = [torch.rand(1, c, s, s)
        ...           for c, s in zip(in_channels, scales)]
        >>> self = FPN(in_channels, 11, len(in_channels)).eval()
        >>> outputs = self.forward(inputs)
        >>> for i in range(len(outputs)):
        ...     print(f'outputs[{i}].shape = {outputs[i].shape}')
        outputs[0].shape = torch.Size([1, 11, 340, 340])
        outputs[1].shape = torch.Size([1, 11, 170, 170])
        outputs[2].shape = torch.Size([1, 11, 84, 84])
        outputs[3].shape = torch.Size([1, 11, 43, 43])
    """

    def __init__(self,
                 in_channels,
                 out_channels,
                 num_outs,
                 start_level=0,
                 end_level=-1,
                 add_extra_convs=False,
                 extra_convs_on_inputs=True,
                 relu_before_extra_convs=False,
                 no_norm_on_lateral=False,
                 conv_cfg=None,
                 norm_cfg=None,
                 act_cfg=None):
        super(FPN, self).__init__()
        assert isinstance(in_channels, list)
        self.in_channels = in_channels
        self.out_channels = out_channels
        self.num_ins = len(in_channels)
        self.num_outs = num_outs
        self.relu_before_extra_convs = relu_before_extra_convs
        self.no_norm_on_lateral = no_norm_on_lateral
        self.fp16_enabled = False

        if end_level == -1:
            self.backbone_end_level = self.num_ins
            assert num_outs >= self.num_ins - start_level
        else:
            # if end_level < inputs, no extra level is allowed
            self.backbone_end_level = end_level
            assert end_level <= len(in_channels)
            assert num_outs == end_level - start_level
        self.start_level = start_level
        self.end_level = end_level
        self.add_extra_convs = add_extra_convs
        self.extra_convs_on_inputs = extra_convs_on_inputs

        self.lateral_convs = nn.ModuleList()
        self.fpn_convs = nn.ModuleList()

        for i in range(self.start_level, self.backbone_end_level):
            l_conv = ConvModule(
                in_channels[i],
                out_channels,
                1,
                conv_cfg=conv_cfg,
                norm_cfg=norm_cfg if not self.no_norm_on_lateral else None,
                act_cfg=act_cfg,
                inplace=False)
            fpn_conv = ConvModule(
                out_channels,
                out_channels,
                3,
                padding=1,
                conv_cfg=conv_cfg,
                norm_cfg=norm_cfg,
                act_cfg=act_cfg,
                inplace=False)

            self.lateral_convs.append(l_conv)
            self.fpn_convs.append(fpn_conv)

        # add extra conv layers (e.g., RetinaNet)
        extra_levels = num_outs - self.backbone_end_level + self.start_level
        if add_extra_convs and extra_levels >= 1:
            for i in range(extra_levels):
                if i == 0 and self.extra_convs_on_inputs:
                    in_channels = self.in_channels[self.backbone_end_level - 1]
                else:
                    in_channels = out_channels
                extra_fpn_conv = ConvModule(
                    in_channels,
                    out_channels,
                    3,
                    stride=2,
                    padding=1,
                    conv_cfg=conv_cfg,
                    norm_cfg=norm_cfg,
                    act_cfg=act_cfg,
                    inplace=False)
                self.fpn_convs.append(extra_fpn_conv)

    # default init_weights for conv(msra) and norm in ConvModule
    def init_weights(self):
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                xavier_init(m, distribution='uniform')

    @auto_fp16()
    def forward(self, inputs):
        assert len(inputs) == len(self.in_channels)

        # build laterals
        laterals = [
            lateral_conv(inputs[i + self.start_level])
            for i, lateral_conv in enumerate(self.lateral_convs)
        ]

        # build top-down path
        used_backbone_levels = len(laterals)
        for i in range(used_backbone_levels - 1, 0, -1):
            prev_shape = laterals[i - 1].shape[2:]
            laterals[i - 1] += F.interpolate(
                laterals[i], size=prev_shape, mode='nearest')

        # build outputs
        # part 1: from original levels
        outs = [
            self.fpn_convs[i](laterals[i]) for i in range(used_backbone_levels)
        ]
        # part 2: add extra levels
        if self.num_outs > len(outs):
            # use max pool to get more levels on top of outputs
            # (e.g., Faster R-CNN, Mask R-CNN)
            if not self.add_extra_convs:
                for i in range(self.num_outs - used_backbone_levels):
                    outs.append(F.max_pool2d(outs[-1], 1, stride=2))
            # add conv layers on top of original feature maps (RetinaNet)
            else:
                if self.extra_convs_on_inputs:
                    orig = inputs[self.backbone_end_level - 1]
                    outs.append(self.fpn_convs[used_backbone_levels](orig))
                else:
                    outs.append(self.fpn_convs[used_backbone_levels](outs[-1]))
                for i in range(used_backbone_levels + 1, self.num_outs):
                    if self.relu_before_extra_convs:
                        outs.append(self.fpn_convs[i](F.relu(outs[-1])))
                    else:
                        outs.append(self.fpn_convs[i](outs[-1]))
        return tuple(outs)


@@ -1,4 +1,8 @@
+import base64
 import pytz
+import requests
+import six
+import time
 from flask import current_app
 import spotipy.oauth2

import pytz
from flask import current_app
import spotipy.oauth2

from listenbrainz.db import spotify as db_spotify
import datetime

SPOTIFY_API_RETRIES = 5

SPOTIFY_PERMISSIONS_SCOPE = 'user-read-currently-playing streaming user-read-birthdate user-read-email user-read-private user-read-recently-played'

class Spotify:
    def __init__(self, user_id, musicbrainz_id, musicbrainz_row_id, user_token, token_expires,
                 refresh_token, last_updated, active, error_message, latest_listened_at):
        self.user_id = user_id
        self.user_token = user_token
        self.token_expires = token_expires
        self.refresh_token = refresh_token
        self.last_updated = last_updated
        self.active = active
        self.error_message = error_message
        self.musicbrainz_id = musicbrainz_id
        self.latest_listened_at = latest_listened_at
        self.musicbrainz_row_id = musicbrainz_row_id

    def get_spotipy_client(self):
        return spotipy.Spotify(auth=self.user_token)

    @property
    def last_updated_iso(self):
        if self.last_updated is None:
            return None
        return self.last_updated.isoformat() + "Z"

    @property
    def latest_listened_at_iso(self):
        if self.latest_listened_at is None:
            return None
        return self.latest_listened_at.isoformat() + "Z"

    @property
    def token_expired(self):
        now = datetime.datetime.utcnow()
        now = now.replace(tzinfo=pytz.UTC)
        return now >= self.token_expires

    @staticmethod
    def from_dbrow(row):
        return Spotify(
           user_id=row['user_id'],
           user_token=row['user_token'],
           token_expires=row['token_expires'],
           refresh_token=row['refresh_token'],
           last_updated=row['last_updated'],
           active=row['active'],
           error_message=row['error_message'],
           musicbrainz_id=row['musicbrainz_id'],
           musicbrainz_row_id=row['musicbrainz_row_id'],
           latest_listened_at=row['latest_listened_at'],
        )

    def __str__(self):
        return "<Spotify(user:%s): %s>" % (self.user_id, self.musicbrainz_id)

def refresh_user_token(spotify_user):
    """ Refreshes the user token for the given spotify user.

    Args:
        spotify_user (domain.spotify.Spotify): the user whose token is to be refreshed

    Returns:
        user (domain.spotify.Spotify): the same user with updated tokens
    """
    auth = get_spotify_oauth()

    retries = SPOTIFY_API_RETRIES
    new_token = None
    while retries > 0:
        new_token = auth.refresh_access_token(spotify_user.refresh_token)
        if new_token:
            break
        retries -= 1
    if new_token is None:
        raise SpotifyAPIError('Could not refresh API Token for Spotify user')

    access_token = new_token['access_token']
    refresh_token = new_token['refresh_token']
    expires_at = new_token['expires_at']
    db_spotify.update_token(spotify_user.user_id, access_token, refresh_token, expires_at)
    return get_user(spotify_user.user_id)

def get_spotify_oauth():
    """ Returns a spotipy OAuth instance that can be used to authenticate with spotify.
    """
    client_id = current_app.config['SPOTIFY_CLIENT_ID']
    client_secret = current_app.config['SPOTIFY_CLIENT_SECRET']
    scope = SPOTIFY_PERMISSIONS_SCOPE
    redirect_url = current_app.config['SPOTIFY_CALLBACK_URL']
    return spotipy.oauth2.SpotifyOAuth(client_id, client_secret, redirect_uri=redirect_url, scope=scope)

def get_user(user_id):
    """ Returns a Spotify instance corresponding to the specified LB row ID.
    If the user_id is not present in the spotify table, returns None

    Args:
        user_id (int): the ListenBrainz row ID of the user
    """
    row = db_spotify.get_user(user_id)
    if row:
        return Spotify.from_dbrow(row)
    return None

def remove_user(user_id):
    """ Delete user entry for user with specified ListenBrainz user ID.

    Args:
        user_id (int): the ListenBrainz row ID of the user
    """
    db_spotify.delete_spotify(user_id)

def add_new_user(user_id, spot_access_token):
    """Create a spotify row for a user based on OAuth access tokens

    Args:
        user_id: A flask auth `current_user.id`
        spot_access_token: A spotipy access token from SpotifyOAuth.get_access_token
    """

    access_token = spot_access_token['access_token']
    refresh_token = spot_access_token['refresh_token']
    expires_at = spot_access_token['expires_at']

    db_spotify.create_spotify(user_id, access_token, refresh_token, expires_at)

def get_active_users_to_process():
    """ Returns a list of Spotify user instances that need their Spotify listens imported.
    """
    return [Spotify.from_dbrow(row) for row in db_spotify.get_active_users_to_process()]

def update_last_updated(user_id, success=True, error_message=None):
    """ Update the last_update field for user with specified user ID.
    Also, set the user as active or inactive depending on whether their listens
    were imported without error.

    If there was an error, add the error to the db.

    Args:
        user_id (int): the ListenBrainz row ID of the user
        success (bool): flag representing whether the last import was successful or not.
        error_message (str): the user-friendly error message to be displayed.
    """
    if error_message:
        db_spotify.add_update_error(user_id, error_message)
    else:
        db_spotify.update_last_updated(user_id, success)

def update_latest_listened_at(user_id, timestamp):
    """ Update the latest_listened_at field for user with specified ListenBrainz user ID.

    Args:
        user_id (int): the ListenBrainz row ID of the user
        timestamp (int): the unix timestamp of the latest listen imported for the user
    """
    db_spotify.update_latest_listened_at(user_id, timestamp)

class SpotifyImporterException(Exception):
    pass

class SpotifyListenBrainzError(Exception):
    pass

class SpotifyAPIError(Exception):
    pass


@@ -16,11 +16,18 @@ async def test_commands_exist():
     await m.load_flow(tflow())

     for binding in km.bindings:
-        cmd, *args = command.lexer(binding.command)
+        results = command_manager.parse_partial(binding.command)
+
+        cmd = results[0][0].value
+        args = [a.value for a in results[0][1:]]
+
         assert cmd in m.commands.commands

         cmd_obj = m.commands.commands[cmd]
         try:
             cmd_obj.prepare_args(args)
         except Exception as e:
+
+            import pdb
+            pdb.set_trace()
             raise ValueError("Invalid command: {}".format(binding.command)) from e

from mitmproxy.test.tflow import tflow
from mitmproxy.tools.console import defaultkeys
from mitmproxy.tools.console import keymap
from mitmproxy.tools.console import master
from mitmproxy import command

import pytest

@pytest.mark.asyncio
async def test_commands_exist():
    km = keymap.Keymap(None)
    defaultkeys.map(km)
    assert km.bindings
    m = master.ConsoleMaster(None)
    await m.load_flow(tflow())

    for binding in km.bindings:
        cmd, *args = command.lexer(binding.command)
        assert cmd in m.commands.commands

        cmd_obj = m.commands.commands[cmd]
        try:
            cmd_obj.prepare_args(args)
        except Exception as e:
            raise ValueError("Invalid command: {}".format(binding.command)) from e


@@ -7,6 +7,7 @@ from mmdet.core.bbox import distance2bbox
 from mmdet.core.mask.structures import BitmapMasks, PolygonMasks
 from mmdet.core.utils import (center_of_mass, filter_scores_and_topk,
                               flip_tensor, mask2ndarray, select_single_mlvl)
+from mmdet.utils import find_latest_checkpoint

 def dummy_raw_polygon_masks(size):

# Copyright (c) OpenMMLab. All rights reserved.
import numpy as np
import pytest
import torch

from mmdet.core.bbox import distance2bbox
from mmdet.core.mask.structures import BitmapMasks, PolygonMasks
from mmdet.core.utils import (center_of_mass, filter_scores_and_topk,
                              flip_tensor, mask2ndarray, select_single_mlvl)

def dummy_raw_polygon_masks(size):
    """
    Args:
        size (tuple): expected shape of dummy masks, (N, H, W)

    Return:
        list[list[ndarray]]: dummy mask
    """
    num_obj, height, width = size
    polygons = []
    for _ in range(num_obj):
        num_points = np.random.randint(5) * 2 + 6
        polygons.append([np.random.uniform(0, min(height, width), num_points)])
    return polygons

def test_mask2ndarray():
    raw_masks = np.ones((3, 28, 28))
    bitmap_mask = BitmapMasks(raw_masks, 28, 28)
    output_mask = mask2ndarray(bitmap_mask)
    assert np.allclose(raw_masks, output_mask)

    raw_masks = dummy_raw_polygon_masks((3, 28, 28))
    polygon_masks = PolygonMasks(raw_masks, 28, 28)
    output_mask = mask2ndarray(polygon_masks)
    assert output_mask.shape == (3, 28, 28)

    raw_masks = np.ones((3, 28, 28))
    output_mask = mask2ndarray(raw_masks)
    assert np.allclose(raw_masks, output_mask)

    raw_masks = torch.ones((3, 28, 28))
    output_mask = mask2ndarray(raw_masks)
    assert np.allclose(raw_masks, output_mask)

    # test unsupported type
    raw_masks = []
    with pytest.raises(TypeError):
        output_mask = mask2ndarray(raw_masks)

def test_distance2bbox():
    point = torch.Tensor([[74., 61.], [-29., 106.], [138., 61.], [29., 170.]])

    distance = torch.Tensor([[0., 0, 1., 1.], [1., 2., 10., 6.],
                             [22., -29., 138., 61.], [54., -29., 170., 61.]])
    expected_decode_bboxes = torch.Tensor([[74., 61., 75., 62.],
                                           [0., 104., 0., 112.],
                                           [100., 90., 100., 120.],
                                           [0., 120., 100., 120.]])
    out_bbox = distance2bbox(point, distance, max_shape=(120, 100))
    assert expected_decode_bboxes.allclose(out_bbox)
    out = distance2bbox(point, distance, max_shape=torch.Tensor((120, 100)))
    assert expected_decode_bboxes.allclose(out)

    batch_point = point.unsqueeze(0).repeat(2, 1, 1)
    batch_distance = distance.unsqueeze(0).repeat(2, 1, 1)
    batch_out = distance2bbox(
        batch_point, batch_distance, max_shape=(120, 100))[0]
    assert out.allclose(batch_out)
    batch_out = distance2bbox(
        batch_point, batch_distance, max_shape=[(120, 100), (120, 100)])[0]
    assert out.allclose(batch_out)

    batch_out = distance2bbox(point, batch_distance, max_shape=(120, 100))[0]
    assert out.allclose(batch_out)

    # test max_shape is not equal to batch
    with pytest.raises(AssertionError):
        distance2bbox(
            batch_point,
            batch_distance,
            max_shape=[(120, 100), (120, 100), (32, 32)])

    rois = torch.zeros((0, 4))
    deltas = torch.zeros((0, 4))
    out = distance2bbox(rois, deltas, max_shape=(120, 100))
    assert rois.shape == out.shape

    rois = torch.zeros((2, 0, 4))
    deltas = torch.zeros((2, 0, 4))
    out = distance2bbox(rois, deltas, max_shape=(120, 100))
    assert rois.shape == out.shape

@pytest.mark.parametrize('mask', [
    torch.ones((28, 28)),
    torch.zeros((28, 28)),
    torch.rand(28, 28) > 0.5,
    torch.tensor([[0, 0, 0, 0], [0, 1, 1, 0], [0, 1, 1, 0], [0, 0, 0, 0]])
])
def test_center_of_mass(mask):
    center_h, center_w = center_of_mass(mask)
    if mask.shape[0] == 4:
        assert center_h == 1.5
        assert center_w == 1.5
    assert isinstance(center_h, torch.Tensor) \
           and isinstance(center_w, torch.Tensor)
    assert 0 <= center_h <= 28 \
           and 0 <= center_w <= 28

def test_flip_tensor():
    img = np.random.random((1, 3, 10, 10))
    src_tensor = torch.from_numpy(img)

    # test flip_direction parameter error
    with pytest.raises(AssertionError):
        flip_tensor(src_tensor, 'flip')

    # test tensor dimension
    with pytest.raises(AssertionError):
        flip_tensor(src_tensor[0], 'vertical')

    hfilp_tensor = flip_tensor(src_tensor, 'horizontal')
    expected_hflip_tensor = torch.from_numpy(img[..., ::-1, :].copy())
    expected_hflip_tensor.allclose(hfilp_tensor)

    vfilp_tensor = flip_tensor(src_tensor, 'vertical')
    expected_vflip_tensor = torch.from_numpy(img[..., ::-1].copy())
    expected_vflip_tensor.allclose(vfilp_tensor)

    diag_filp_tensor = flip_tensor(src_tensor, 'diagonal')
    expected_diag_filp_tensor = torch.from_numpy(img[..., ::-1, ::-1].copy())
    expected_diag_filp_tensor.allclose(diag_filp_tensor)

def test_select_single_mlvl():
    mlvl_tensors = [torch.rand(2, 1, 10, 10)] * 5
    mlvl_tensor_list = select_single_mlvl(mlvl_tensors, 1)
    assert len(mlvl_tensor_list) == 5 and mlvl_tensor_list[0].ndim == 3

def test_filter_scores_and_topk():
    score = torch.tensor([[0.1, 0.3, 0.2], [0.12, 0.7, 0.9], [0.02, 0.8, 0.08],
                          [0.4, 0.1, 0.08]])
    bbox_pred = torch.tensor([[0.2, 0.3], [0.4, 0.7], [0.1, 0.1], [0.5, 0.1]])
    score_thr = 0.15
    nms_pre = 4
    # test results type error
    with pytest.raises(NotImplementedError):
        filter_scores_and_topk(score, score_thr, nms_pre, (score, ))

    filtered_results = filter_scores_and_topk(
        score, score_thr, nms_pre, results=dict(bbox_pred=bbox_pred))
    filtered_score, labels, keep_idxs, results = filtered_results
    assert filtered_score.allclose(torch.tensor([0.9, 0.8, 0.7, 0.4]))
    assert labels.allclose(torch.tensor([2, 1, 1, 0]))
    assert keep_idxs.allclose(torch.tensor([1, 2, 1, 3]))
    assert results['bbox_pred'].allclose(
        torch.tensor([[0.4, 0.7], [0.1, 0.1], [0.4, 0.7], [0.5, 0.1]]))


@@ -69,14 +69,13 @@ class UtilsTestCase(SparkTestCase):
                         'release_mbid': '1',
                         'listen_count': 1
                     }]
-                     }
-
-        self.assertDictEqual(user_utils.get_recordings('table'), dictionary)
-        self.assertEqual(user_utils.get_recordings('table')['user1'][0]['listen_count'], 2)
-
+                      }
+
+        self.assertDictEqual(user.utils.get_recordings(df), dictionary)
+        self.assertEqual(user.utils.get_recordings(df)['user1'][0]['listen_count'], 2)
+
     def test_get_releases(self):
         df = self.create_df()
-        df.createOrReplaceTempView('table')
         dictionary =  {
                     'user1' : [{
                         'artist_name': 'artist1',

import listenbrainz_spark.stats.user.utils as user_utils
from listenbrainz_spark import utils
from listenbrainz_spark.tests import SparkTestCase

from pyspark.sql import Row

class UtilsTestCase(SparkTestCase):

    def create_df(self):
        df = utils.create_dataframe(Row(user_name='user2', artist_name='artist1', artist_msid='1',artist_mbids='1',
            track_name='test', recording_msid='1', recording_mbid='1', release_name='test',release_msid='1',
            release_mbid='1'), schema=None)
        df1 = utils.create_dataframe(Row(user_name='user1',artist_name='artist1', artist_msid='1',artist_mbids='1',
            track_name='test', recording_msid='1', recording_mbid='1', release_name='test',release_msid='1',
             release_mbid='1'), schema=None)
        df = df.union(df1)
        df2 = utils.create_dataframe(Row(user_name='user1',artist_name='artist1', artist_msid='1',artist_mbids='1',
            track_name='test', recording_msid='1', recording_mbid='1', release_name='test',release_msid='1',
            release_mbid='1'), schema=None)
        df = df.union(df2)
        return df

    def test_get_artists(self):
        df = self.create_df()
        df.createOrReplaceTempView('table')
        dictionary = {
                    'user1': [{
                        'artist_name': 'artist1',
                        'artist_msid': '1',
                        'artist_mbids': '1',
                        'listen_count': 2
                    }],
                        'user2': [{
                        'artist_name': 'artist1',
                        'artist_msid': '1',
                        'artist_mbids': '1',
                        'listen_count': 1
                    }]
                     }

        self.assertDictEqual(user_utils.get_artists('table'), dictionary)
        self.assertEqual(user_utils.get_artists('table')['user1'][0]['listen_count'], 2)

    def test_get_recordings(self):
        df = self.create_df()
        df.createOrReplaceTempView('table')
        dictionary =  {
                    'user1' : [{
                        'track_name': 'test',
                        'recording_msid': '1',
                        'recording_mbid': '1',
                        'artist_name': 'artist1',
                        'artist_msid': '1',
                        'artist_mbids': '1',
                        'release_name': 'test',
                        'release_msid': '1',
                        'release_mbid': '1',
                        'listen_count': 2
                    }],
                    'user2' : [{
                        'track_name': 'test',
                        'recording_msid': '1',
                        'recording_mbid': '1',
                        'artist_name': 'artist1',
                        'artist_msid': '1',
                        'artist_mbids': '1',
                        'release_name': 'test',
                        'release_msid': '1',
                        'release_mbid': '1',
                        'listen_count': 1
                    }]
                     }

        self.assertDictEqual(user_utils.get_recordings('table'), dictionary)
        self.assertEqual(user_utils.get_recordings('table')['user1'][0]['listen_count'], 2)

    def test_get_releases(self):
        df = self.create_df()
        df.createOrReplaceTempView('table')
        dictionary =  {
                    'user1' : [{
                        'artist_name': 'artist1',
                        'artist_msid': '1',
                        'artist_mbids': '1',
                        'release_name': 'test',
                        'release_msid': '1',
                        'release_mbid': '1',
                        'listen_count': 2
                    }],
                    'user2' : [{
                        'artist_name': 'artist1',
                        'artist_msid': '1',
                        'artist_mbids': '1',
                        'release_name': 'test',
                        'release_msid': '1',
                        'release_mbid': '1',
                        'listen_count': 1
                    }]
                     }

        self.assertDictEqual(user_utils.get_releases('table'), dictionary)
        self.assertEqual(user_utils.get_releases('table')['user1'][0]['listen_count'], 2)


@@ -1,11 +1,37 @@
 import glob
-from mitmproxy import utils, script
+import json
+import os
+from contextlib import contextmanager
+
+from mitmproxy import utils, script, contentviews
 from mitmproxy.proxy import config
-from . import tservers
+from netlib import tutils as netutils
+from netlib.http import Headers
+from . import tservers, tutils
+
+example_dir = utils.Data(__name__).path("../../examples")
+
+
+class DummyContext(object):
+    """Emulate script.ScriptContext() functionality."""
+
+    def log(self, *args, **kwargs):
+        pass
+
+    def add_contentview(self, view_obj):
+        pass
+
+
+@contextmanager
+def example(command):
+    command = os.path.join(example_dir, command)
+    ctx = DummyContext()
+    s = script.Script(command, ctx)
+    yield s
+    s.unload()

 def test_load_scripts():
-    example_dir = utils.Data(__name__).path("../../examples")
     scripts = glob.glob("%s/*.py" % example_dir)

     tmaster = tservers.TestMaster(config.ProxyConfig())

import glob
from mitmproxy import utils, script
from mitmproxy.proxy import config
from . import tservers

def test_load_scripts():
    example_dir = utils.Data(__name__).path("../../examples")
    scripts = glob.glob("%s/*.py" % example_dir)

    tmaster = tservers.TestMaster(config.ProxyConfig())

    for f in scripts:
        if "har_extractor" in f:
            continue
        if "flowwriter" in f:
            f += " -"
        if "iframe_injector" in f:
            f += " foo"  # one argument required
        if "filt" in f:
            f += " ~a"
        if "modify_response_body" in f:
            f += " foo bar"  # two arguments required
        try:
            s = script.Script(f, script.ScriptContext(tmaster))  # Loads the script file.
        except Exception as v:
            if "ImportError" not in str(v):
                raise
        else:
            s.unload()


@@ -8,6 +8,7 @@ from bzt.modules.gatling import DataLogReader as GatlingLogReader
 from bzt.modules.grinder import DataLogReader as GrinderLogReader
 from bzt.modules.jmeter import JTLReader, XMLJTLReader
 from bzt.modules.pbench import PBenchKPIReader
+from bzt.utils import dehumanize_time

 class ExternalResultsLoader(ScenarioExecutor, AggregatorListener):

import re

from bzt import TaurusInternalException, TaurusConfigError
from bzt.engine import ScenarioExecutor
from bzt.modules.ab import TSVDataReader
from bzt.modules.aggregator import AggregatorListener, ConsolidatingAggregator, DataPoint
from bzt.modules.gatling import DataLogReader as GatlingLogReader
from bzt.modules.grinder import DataLogReader as GrinderLogReader
from bzt.modules.jmeter import JTLReader, XMLJTLReader
from bzt.modules.pbench import PBenchKPIReader

class ExternalResultsLoader(ScenarioExecutor, AggregatorListener):
    AB_HEADER = "starttime\tseconds\tctime\tdtime\tttime\twait"
    PBENCH_FORMAT = re.compile("^[0-9]+\.[0-9]{3}\t[^\t]*\t([0-9]+\t){9}[0-9]+$")

    def __init__(self):
        # TODO: document this executor
        super(ExternalResultsLoader, self).__init__()
        self.data_file = None
        self.errors_file = None
        self.reader = None
        self._last_ts = -1
        self._prev_ts = -1

    def prepare(self):
        exc = TaurusConfigError("Option is required for executor: data-file")
        self.data_file = self.execution.get("data-file", exc)
        self.data_file = self.engine.find_file(self.data_file)
        self.label = self.data_file
        self.errors_file = self.execution.get("errors-jtl", None)
        if self.errors_file:
            self.errors_file = self.engine.find_file(self.errors_file)

        self.reader = self._get_reader()
        if isinstance(self.engine.aggregator, ConsolidatingAggregator):
            self.engine.aggregator.add_underling(self.reader)

        if isinstance(self.engine.aggregator, ConsolidatingAggregator):
            self.engine.aggregator.add_listener(self)

    def _get_reader(self):
        with open(self.data_file) as fhd:
            header = fhd.readline(2048).strip()  # just header chunk of file

        # TODO: detect CSV dialect for JTLs

        if header.startswith(self.AB_HEADER):
            return TSVDataReader(self.data_file, self.log)
        elif header.startswith("<?xml"):
            return XMLJTLReader(self.data_file, self.log)
        elif self.PBENCH_FORMAT.match(header):
            return PBenchKPIReader(self.data_file, self.log, self.errors_file)
        elif header.startswith("RUN\t") or "\tRUN\t" in header:
            return GatlingLogReader(self.data_file, self.log, None)
        elif "timestamp" in header.lower() and "elapsed" in header.lower():
            return JTLReader(self.data_file, self.log, self.errors_file)
        elif "worker process" in header.lower() and header.startswith("worker."):
            return GrinderLogReader(self.data_file, self.log)
        else:
            self.log.info("Header line was: %s", header)
            raise TaurusInternalException("Unable to detect results format for: %s" % self.data_file)

    def aggregated_second(self, data):
        self._last_ts = data[DataPoint.TIMESTAMP]

    def check(self):
        if self._last_ts > 0 and self._last_ts == self._prev_ts:
            return True
        else:
            self._prev_ts = self._last_ts