vendor/sulu/sulu/src/Sulu/Component/Webspace/Loader/XmlFileLoader10.php line 125

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of Sulu.
  4.  *
  5.  * (c) Sulu GmbH
  6.  *
  7.  * This source file is subject to the MIT license that is bundled
  8.  * with this source code in the file LICENSE.
  9.  */
  10. namespace Sulu\Component\Webspace\Loader;
  11. use Sulu\Component\Localization\Localization;
  12. use Sulu\Component\Webspace\CustomUrl;
  13. use Sulu\Component\Webspace\Environment;
  14. use Sulu\Component\Webspace\Exception\InvalidWebspaceException;
  15. use Sulu\Component\Webspace\Loader\Exception\ExpectedDefaultTemplatesNotFound;
  16. use Sulu\Component\Webspace\Loader\Exception\InvalidAmountOfDefaultErrorTemplateException;
  17. use Sulu\Component\Webspace\Loader\Exception\InvalidCustomUrlException;
  18. use Sulu\Component\Webspace\Loader\Exception\InvalidDefaultErrorTemplateException;
  19. use Sulu\Component\Webspace\Loader\Exception\InvalidDefaultLocalizationException;
  20. use Sulu\Component\Webspace\Loader\Exception\InvalidErrorTemplateException;
  21. use Sulu\Component\Webspace\Loader\Exception\InvalidPortalDefaultLocalizationException;
  22. use Sulu\Component\Webspace\Loader\Exception\InvalidUrlDefinitionException;
  23. use Sulu\Component\Webspace\Loader\Exception\InvalidWebspaceDefaultLocalizationException;
  24. use Sulu\Component\Webspace\Loader\Exception\InvalidWebspaceDefaultSegmentException;
  25. use Sulu\Component\Webspace\Loader\Exception\PortalDefaultLocalizationNotFoundException;
  26. use Sulu\Component\Webspace\Loader\Exception\WebspaceDefaultSegmentNotFoundException;
  27. use Sulu\Component\Webspace\Navigation;
  28. use Sulu\Component\Webspace\NavigationContext;
  29. use Sulu\Component\Webspace\Portal;
  30. use Sulu\Component\Webspace\Security;
  31. use Sulu\Component\Webspace\Segment;
  32. use Sulu\Component\Webspace\Url;
  33. use Sulu\Component\Webspace\Webspace;
  34. use Symfony\Component\Config\Util\XmlUtils;
  35. /**
  36.  * This file loader is responsible for webspace configuration files in the xml format using the 1.0 version of the
  37.  * webspace schema definition.
  38.  *
  39.  * @deprecated
  40.  */
  41. class XmlFileLoader10 extends BaseXmlFileLoader
  42. {
  43.     public const SCHEMA_LOCATION '/schema/webspace/webspace-1.0.xsd';
  44.     public const SCHEMA_URI 'http://schemas.sulu.io/webspace/webspace-1.0.xsd';
  45.     /**
  46.      * @var \DOMXPath
  47.      */
  48.     protected $xpath;
  49.     /**
  50.      * The webspace which is created by this file loader.
  51.      *
  52.      * @var Webspace
  53.      */
  54.     protected $webspace;
  55.     /**
  56.      * Loads a webspace from a xml file.
  57.      *
  58.      * @param mixed $resource The resource
  59.      * @param string $type The resource type
  60.      *
  61.      * @return Webspace The webspace object for the given resource
  62.      */
  63.     public function load($resource$type null)
  64.     {
  65.         $path $this->getLocator()->locate($resource);
  66.         // load data in path
  67.         return $this->parseXml($path);
  68.     }
  69.     /**
  70.      * Returns true if this class supports the given resource.
  71.      *
  72.      * @param mixed $resource A resource
  73.      * @param string $type The resource type
  74.      *
  75.      * @return bool true if this class supports the given resource, false otherwise
  76.      */
  77.     public function supports($resource$type null)
  78.     {
  79.         return parent::supports($resource$type);
  80.     }
  81.     /**
  82.      * Parses the entire file and returns a webspace object.
  83.      *
  84.      * @param string $file
  85.      *
  86.      * @return Webspace
  87.      */
  88.     protected function parseXml($file)
  89.     {
  90.         $this->xpath = new \DOMXPath($this->tryLoad($file));
  91.         $this->xpath->registerNamespace('x''http://schemas.sulu.io/webspace/webspace');
  92.         // set simple webspace properties
  93.         $this->webspace = new Webspace();
  94.         $this->webspace->setName($this->xpath->query('/x:webspace/x:name')->item(0)->nodeValue);
  95.         $this->webspace->setKey($this->xpath->query('/x:webspace/x:key')->item(0)->nodeValue);
  96.         $this->webspace->setTheme($this->generateTheme());
  97.         $this->webspace->setNavigation($this->generateNavigation());
  98.         $this->webspace->setResourceLocatorStrategy('tree_leaf_edit');
  99.         $this->generateTemplates($this->webspace);
  100.         $this->generateDefaultTemplates($this->webspace);
  101.         // set security
  102.         $this->generateSecurity();
  103.         // set localizations on webspaces
  104.         $this->generateWebspaceLocalizations();
  105.         // set segments on webspaces
  106.         $this->generateSegments();
  107.         // set portals on webspaces
  108.         $this->generatePortals();
  109.         // validate the webspace, and throw exceptions if not valid
  110.         $this->validate();
  111.         return $this->webspace;
  112.     }
  113.     /**
  114.      * Returns xml-doc when one scheme matches.
  115.      *
  116.      * @param string $file
  117.      *
  118.      * @return \DOMDocument
  119.      *
  120.      * @throws InvalidWebspaceException
  121.      */
  122.     protected function tryLoad($file)
  123.     {
  124.         try {
  125.             return XmlUtils::loadFile($file__DIR__ . static::SCHEMA_LOCATION);
  126.         } catch (\InvalidArgumentException $e) {
  127.             throw new InvalidWebspaceException(
  128.                 \sprintf(
  129.                     'Could not parse webspace XML file "%s"',
  130.                     $file
  131.                 ),
  132.                 null,
  133.                 $e
  134.             );
  135.         }
  136.     }
  137.     /**
  138.      * Validate result.
  139.      */
  140.     protected function validate()
  141.     {
  142.         $this->validateWebspaceDefaultLocalization();
  143.         $this->validateDefaultPortalLocalization();
  144.         $this->validateWebspaceDefaultSegment();
  145.     }
  146.     /**
  147.      * Sets the default localization for the given portal.
  148.      *
  149.      * @param Portal $portal
  150.      *
  151.      * @return bool True when successful, otherwise false
  152.      */
  153.     protected function loadPortalLocalizationDefaultFromWebspace($portal)
  154.     {
  155.         $webspaceDefaultLocalization $this->webspace->getDefaultLocalization();
  156.         foreach ($portal->getLocalizations() as $localization) {
  157.             if ($webspaceDefaultLocalization
  158.                 && $webspaceDefaultLocalization->getLocale() == $localization->getLocale()
  159.             ) {
  160.                 $localization->setDefault(true);
  161.                 $portal->setDefaultLocalization($localization);
  162.                 return true;
  163.             }
  164.         }
  165.         return false;
  166.     }
  167.     /**
  168.      * Generates all localizations for the given portal.
  169.      */
  170.     protected function generatePortalLocalizations(\DOMNode $portalNodePortal $portal)
  171.     {
  172.         if ($this->xpath->query('x:localizations'$portalNode)->length 0) {
  173.             // set localizations from portal, if they are set
  174.             $localizationNodes $this->xpath->query('x:localizations/x:localization'$portalNode);
  175.             $this->generateLocalizationsFromNodeList($localizationNodes$portal);
  176.         } else {
  177.             // if the portal has no localizations fallback to the localizations from the webspace
  178.             $localizationNodes $this->xpath->query('/x:webspace/x:localizations//x:localization');
  179.             $this->generateLocalizationsFromNodeList($localizationNodes$portaltrue);
  180.         }
  181.     }
  182.     /**
  183.      * Generates the localizations for the given portal from the given DOMNodeList.
  184.      *
  185.      * @param bool $flat
  186.      */
  187.     protected function generateLocalizationsFromNodeList(\DOMNodeList $localizationNodesPortal $portal$flat false)
  188.     {
  189.         foreach ($localizationNodes as $localizationNode) {
  190.             $localization $this->generateLocalizationFromNode($localizationNode$flat);
  191.             $portal->addLocalization($localization);
  192.         }
  193.     }
  194.     /**
  195.      * Generates a localization from the given node.
  196.      *
  197.      * @param bool $flat
  198.      * @param ?\DOMElement $parent
  199.      *
  200.      * @return Localization
  201.      */
  202.     protected function generateLocalizationFromNode(\DOMElement $localizationNode$flat false$parent null)
  203.     {
  204.         $localization = new Localization($localizationNode->attributes->getNamedItem('language')->nodeValue);
  205.         // set parent if given
  206.         if ($parent) {
  207.             $localization->setParent($parent);
  208.         }
  209.         // set optional nodes
  210.         $countryNode $localizationNode->attributes->getNamedItem('country');
  211.         if ($countryNode) {
  212.             $localization->setCountry($countryNode->nodeValue);
  213.         }
  214.         $shadowNode $localizationNode->attributes->getNamedItem('shadow');
  215.         if ($shadowNode) {
  216.             $localization->setShadow($shadowNode->nodeValue);
  217.         }
  218.         $defaultNode $localizationNode->attributes->getNamedItem('default');
  219.         if ($defaultNode) {
  220.             $localization->setDefault('true' == $defaultNode->nodeValue);
  221.         } else {
  222.             $localization->setDefault(false);
  223.         }
  224.         $xDefaultNode $localizationNode->attributes->getNamedItem('x-default');
  225.         if ($xDefaultNode) {
  226.             // @deprecated
  227.             @trigger_deprecation('sulu/sulu''2.3''Set x-default="true" attribute on the `<localization>` tag in webspace is deprecated use default="true" instead.');
  228.             $localization->setXDefault('true' == $xDefaultNode->nodeValue);
  229.         } else {
  230.             $localization->setXDefault(false);
  231.         }
  232.         // set child nodes
  233.         if (!$flat) {
  234.             foreach ($this->xpath->query('x:localization'$localizationNode) as $childNode) {
  235.                 $localization->addChild($this->generateLocalizationFromNode($childNode$flat$localization));
  236.             }
  237.         }
  238.         return $localization;
  239.     }
  240.     /**
  241.      * Generates and sets the security object from the XML document.
  242.      */
  243.     protected function generateSecurity()
  244.     {
  245.         $securityNodeList $this->xpath->query('/x:webspace/x:security');
  246.         if ($securityNodeList->length 0) {
  247.             $securityNode $securityNodeList->item(0);
  248.             $securitySystemNode $this->xpath->query('x:system'$securityNode);
  249.             $permissionCheckAttribute $securityNode->attributes->getNamedItem('permission-check');
  250.             $security = new Security();
  251.             $security->setSystem($securitySystemNode->item(0)->nodeValue);
  252.             $security->setPermissionCheck($permissionCheckAttribute 'true' === $permissionCheckAttribute->nodeValue false);
  253.             $this->webspace->setSecurity($security);
  254.         }
  255.     }
  256.     /**
  257.      * Generates the localization for the webspace from the XML document.
  258.      */
  259.     protected function generateWebspaceLocalizations()
  260.     {
  261.         foreach ($this->xpath->query('/x:webspace/x:localizations/x:localization') as $localizationNode) {
  262.             $localization $this->generateLocalizationFromNode($localizationNode);
  263.             $this->webspace->addLocalization($localization);
  264.         }
  265.     }
  266.     /**
  267.      * Generates the available segments for the webspace from the XML document.
  268.      */
  269.     protected function generateSegments()
  270.     {
  271.         foreach ($this->xpath->query('/x:webspace/x:segments/x:segment') as $segmentNode) {
  272.             /** @var \DOMNode $segmentNode */
  273.             $segment = new Segment();
  274.             $segment->setKey($segmentNode->attributes->getNamedItem('key')->nodeValue);
  275.             $segment->setMetadata($this->loadMeta('x:meta/x:*'$segmentNode));
  276.             $defaultNode $segmentNode->attributes->getNamedItem('default');
  277.             if ($defaultNode) {
  278.                 $segment->setDefault('true' == $defaultNode->nodeValue);
  279.             } else {
  280.                 $segment->setDefault(false);
  281.             }
  282.             $this->webspace->addSegment($segment);
  283.         }
  284.     }
  285.     /**
  286.      * Generate all the portals for the webspace.
  287.      */
  288.     protected function generatePortals()
  289.     {
  290.         foreach ($this->xpath->query('/x:webspace/x:portals/x:portal') as $portalNode) {
  291.             /** @var \DOMNode $portalNode */
  292.             $portal = new Portal();
  293.             $portal->setName($this->xpath->query('x:name'$portalNode)->item(0)->nodeValue);
  294.             $portal->setKey($this->xpath->query('x:key'$portalNode)->item(0)->nodeValue);
  295.             // set localization on portal
  296.             $this->generatePortalLocalizations($portalNode$portal);
  297.             $this->webspace->addPortal($portal);
  298.             $portal->setWebspace($this->webspace);
  299.             // set environments
  300.             $this->generateEnvironments($portalNode$portal);
  301.         }
  302.     }
  303.     /**
  304.      * Generates the theme for the webspace.
  305.      *
  306.      * @return string
  307.      */
  308.     protected function generateTheme()
  309.     {
  310.         $nodes $this->xpath->query('/x:webspace/x:theme/x:key');
  311.         if ($nodes->length 0) {
  312.             return $nodes->item(0)->nodeValue;
  313.         }
  314.         $nodes $this->xpath->query('/x:webspace/x:theme');
  315.         if (=== $nodes->length) {
  316.             return;
  317.         }
  318.         return $nodes->item(0)->nodeValue;
  319.     }
  320.     /**
  321.      * Generates the available template types for the given webspace.
  322.      *
  323.      * @return Webspace
  324.      *
  325.      * @throws InvalidAmountOfDefaultErrorTemplateException
  326.      * @throws InvalidDefaultErrorTemplateException
  327.      * @throws InvalidErrorTemplateException
  328.      */
  329.     protected function generateTemplates(Webspace $webspace)
  330.     {
  331.         $defaultErrorTemplates 0;
  332.         foreach ($this->xpath->query('/x:webspace/x:theme/x:error-templates/x:error-template') as $errorTemplateNode) {
  333.             /* @var \DOMNode $errorTemplateNode */
  334.             $template $errorTemplateNode->nodeValue;
  335.             if (null !== ($codeNode $errorTemplateNode->attributes->getNamedItem('code'))) {
  336.                 $webspace->addTemplate('error-' $codeNode->nodeValue$template);
  337.             } elseif (null !== ($defaultNode $errorTemplateNode->attributes->getNamedItem('default'))) {
  338.                 $default 'true' === $defaultNode->nodeValue;
  339.                 if (!$default) {
  340.                     throw new InvalidDefaultErrorTemplateException($template$this->webspace->getKey());
  341.                 }
  342.                 ++$defaultErrorTemplates;
  343.                 $webspace->addTemplate('error'$template);
  344.             } else {
  345.                 throw new InvalidErrorTemplateException($template$this->webspace->getKey());
  346.             }
  347.         }
  348.         // only one or none default error-template is legal
  349.         if ($defaultErrorTemplates 1) {
  350.             throw new InvalidAmountOfDefaultErrorTemplateException($this->webspace->getKey());
  351.         }
  352.         return $webspace;
  353.     }
  354.     /**
  355.      * Generates the default templates for the webspace.
  356.      *
  357.      * @return Webspace
  358.      *
  359.      * @throws ExpectedDefaultTemplatesNotFound
  360.      */
  361.     protected function generateDefaultTemplates(Webspace $webspace)
  362.     {
  363.         $expected = ['page''home'];
  364.         foreach ($this->xpath->query('/x:webspace/x:theme/x:default-templates/x:default-template') as $node) {
  365.             /* @var \DOMNode $node */
  366.             $template $node->nodeValue;
  367.             $type $node->attributes->getNamedItem('type')->nodeValue;
  368.             $webspace->addDefaultTemplate($type$template);
  369.             if ('homepage' === $type) {
  370.                 $webspace->addDefaultTemplate('home'$template);
  371.             }
  372.         }
  373.         $found \array_keys($webspace->getDefaultTemplates());
  374.         foreach ($expected as $item) {
  375.             if (!\in_array($item$found)) {
  376.                 throw new ExpectedDefaultTemplatesNotFound($this->webspace->getKey(), $expected$found);
  377.             }
  378.         }
  379.         return $webspace;
  380.     }
  381.     /**
  382.      * Generates the availabel navigation contexts for the webspace.
  383.      *
  384.      * @return Navigation
  385.      */
  386.     protected function generateNavigation()
  387.     {
  388.         $contexts = [];
  389.         foreach ($this->xpath->query('/x:webspace/x:navigation/x:contexts/x:context') as $contextNode) {
  390.             /* @var \DOMNode $contextNode */
  391.             $contexts[] = new NavigationContext(
  392.                 $contextNode->attributes->getNamedItem('key')->nodeValue,
  393.                 $this->loadMeta('x:meta/x:*'$contextNode)
  394.             );
  395.         }
  396.         return new Navigation($contexts);
  397.     }
  398.     /**
  399.      * Loads the meta information like a translatable title from the webspace.
  400.      *
  401.      * @param string $path
  402.      *
  403.      * @return mixed[]
  404.      */
  405.     protected function loadMeta($path, ?\DOMNode $context null)
  406.     {
  407.         $result = [];
  408.         /** @var \DOMElement $node */
  409.         foreach ($this->xpath->query($path$context) as $node) {
  410.             $attribute $node->tagName;
  411.             $lang $this->xpath->query('@lang'$node)->item(0)->nodeValue;
  412.             if (!isset($result[$node->tagName])) {
  413.                 $result[$attribute] = [];
  414.             }
  415.             $result[$attribute][$lang] = $node->textContent;
  416.         }
  417.         return $result;
  418.     }
  419.     /**
  420.      * Generates the definitions for the available environments for this webspace.
  421.      */
  422.     protected function generateEnvironments(\DOMNode $portalNodePortal $portal)
  423.     {
  424.         foreach ($this->xpath->query('x:environments/x:environment'$portalNode) as $environmentNode) {
  425.             /** @var \DOMNode $environmentNode */
  426.             $environment = new Environment();
  427.             $environment->setType($environmentNode->attributes->getNamedItem('type')->nodeValue);
  428.             $this->generateUrls($environmentNode$environment);
  429.             $this->generateCustomUrls($environmentNode$environment);
  430.             $portal->addEnvironment($environment);
  431.         }
  432.     }
  433.     /**
  434.      * Generates the URLs for the given environment.
  435.      *
  436.      * @throws InvalidUrlDefinitionException
  437.      */
  438.     protected function generateUrls(\DOMNode $environmentNodeEnvironment $environment)
  439.     {
  440.         foreach ($this->xpath->query('x:urls/x:url'$environmentNode) as $urlNode) {
  441.             // check if the url is valid, and throw an exception otherwise
  442.             if (!$this->checkUrlNode($urlNode)) {
  443.                 throw new InvalidUrlDefinitionException($this->webspace$urlNode->nodeValue);
  444.             }
  445.             /** @var \DOMNode $urlNode */
  446.             $url = new Url();
  447.             $url->setUrl(\rtrim($urlNode->nodeValue'/'));
  448.             // set optional nodes
  449.             $url->setLanguage($this->getOptionalNodeAttribute($urlNode'language'));
  450.             $url->setCountry($this->getOptionalNodeAttribute($urlNode'country'));
  451.             $url->setSegment($this->getOptionalNodeAttribute($urlNode'segment'));
  452.             $url->setRedirect($this->getOptionalNodeAttribute($urlNode'redirect'));
  453.             $url->setMain($this->getOptionalNodeAttribute($urlNode'main'false));
  454.             $environment->addUrl($url);
  455.         }
  456.     }
  457.     /**
  458.      * Generates the custom URLs from the XML document.
  459.      *
  460.      * A custom URL must contain at lease one *, which will be used as a placeholder.
  461.      *
  462.      * @throws InvalidCustomUrlException
  463.      */
  464.     protected function generateCustomUrls(\DOMNode $environmentNodeEnvironment $environment)
  465.     {
  466.         foreach ($this->xpath->query('x:custom-urls/x:custom-url'$environmentNode) as $urlNode) {
  467.             /** @var \DOMNode $urlNode */
  468.             $url = new CustomUrl();
  469.             $url->setUrl(\rtrim($urlNode->nodeValue'/'));
  470.             if (false === \strpos($url->getUrl(), '*')) {
  471.                 throw new InvalidCustomUrlException($this->webspace$url->getUrl());
  472.             }
  473.             $environment->addCustomUrl($url);
  474.         }
  475.     }
  476.     /**
  477.      * Returns an optional value from the given node. The default value will be used if the node does not exist.
  478.      *
  479.      * @param string $name
  480.      */
  481.     protected function getOptionalNodeAttribute(\DOMNode $node$name$default null)
  482.     {
  483.         $attribute $node->attributes->getNamedItem($name);
  484.         if ($attribute) {
  485.             return $attribute->nodeValue;
  486.         }
  487.         return $default;
  488.     }
  489.     /**
  490.      * Checks if the urlNode is valid for this webspace.
  491.      *
  492.      * @return bool
  493.      */
  494.     protected function checkUrlNode(\DOMNode $urlNode)
  495.     {
  496.         $hasLocalization = (null != $urlNode->attributes->getNamedItem('localization'))
  497.             || (false !== \strpos($urlNode->nodeValue'{localization}'));
  498.         $hasLanguage = (null != $urlNode->attributes->getNamedItem('language'))
  499.             || (false !== \strpos($urlNode->nodeValue'{language}'))
  500.             || $hasLocalization;
  501.         $hasRedirect = (null != $urlNode->attributes->getNamedItem('redirect'));
  502.         return $hasLanguage || $hasRedirect;
  503.     }
  504.     /**
  505.      * Validate default webspace localization.
  506.      *
  507.      * @throws InvalidWebspaceDefaultLocalizationException
  508.      */
  509.     protected function validateWebspaceDefaultLocalization()
  510.     {
  511.         try {
  512.             $this->validateDefaultLocalization($this->webspace->getLocalizations());
  513.         } catch (InvalidDefaultLocalizationException $ex) {
  514.             throw new InvalidWebspaceDefaultLocalizationException($this->webspace$ex);
  515.         }
  516.     }
  517.     /**
  518.      * Validate portal localization.
  519.      *
  520.      * @throws PortalDefaultLocalizationNotFoundException
  521.      * @throws InvalidPortalDefaultLocalizationException
  522.      */
  523.     protected function validateDefaultPortalLocalization()
  524.     {
  525.         // check all portal localizations
  526.         foreach ($this->webspace->getPortals() as $portal) {
  527.             try {
  528.                 if (!$this->validateDefaultLocalization($portal->getLocalizations())) {
  529.                     // try to load the webspace localizations before throwing an exception
  530.                     if (!$this->loadPortalLocalizationDefaultFromWebspace($portal)) {
  531.                         throw new PortalDefaultLocalizationNotFoundException($this->webspace$portal);
  532.                     }
  533.                 }
  534.             } catch (InvalidDefaultLocalizationException $ex) {
  535.                 throw new InvalidPortalDefaultLocalizationException($this->webspace$portal$ex);
  536.             }
  537.         }
  538.     }
  539.     /**
  540.      * Validate webspace default segment.
  541.      *
  542.      * @throws WebspaceDefaultSegmentNotFoundException
  543.      * @throws InvalidWebspaceDefaultSegmentException
  544.      */
  545.     protected function validateWebspaceDefaultSegment()
  546.     {
  547.         // check if there are duplicate defaults in the webspaces segments
  548.         $segments $this->webspace->getSegments();
  549.         if ($segments) {
  550.             $webspaceDefaultSegmentFound false;
  551.             foreach ($segments as $webspaceSegment) {
  552.                 if ($webspaceSegment->isDefault()) {
  553.                     // throw an exception, if a new default segment is found, although there already is one
  554.                     if ($webspaceDefaultSegmentFound) {
  555.                         throw new InvalidWebspaceDefaultSegmentException($this->webspace);
  556.                     }
  557.                     $webspaceDefaultSegmentFound true;
  558.                 }
  559.             }
  560.             if (!$webspaceDefaultSegmentFound) {
  561.                 throw new WebspaceDefaultSegmentNotFoundException($this->webspace);
  562.             }
  563.         }
  564.     }
  565.     /**
  566.      * Returns true if there is one default localization.
  567.      *
  568.      * @param Localization[] $localizations
  569.      *
  570.      * @return bool
  571.      *
  572.      * @throws InvalidDefaultLocalizationException
  573.      */
  574.     protected function validateDefaultLocalization($localizations)
  575.     {
  576.         $result false;
  577.         foreach ($localizations as $localization) {
  578.             if ($localization->isDefault()) {
  579.                 if ($result) {
  580.                     throw new InvalidDefaultLocalizationException();
  581.                 }
  582.                 $result true;
  583.             }
  584.         }
  585.         return $result;
  586.     }
  587. }