1:   2:   3:   4:   5:   6:   7:   8:   9:  10:  11:  12:  13:  14:  15:  16:  17:  18:  19:  20:  21:  22:  23:  24:  25:  26:  27:  28:  29:  30:  31:  32:  33:  34:  35:  36:  37:  38:  39:  40:  41:  42:  43:  44:  45:  46:  47:  48:  49:  50:  51:  52:  53:  54:  55:  56:  57:  58:  59:  60:  61:  62:  63:  64:  65:  66:  67:  68:  69:  70:  71:  72:  73:  74:  75:  76:  77:  78:  79:  80:  81:  82:  83:  84:  85:  86:  87:  88:  89:  90:  91:  92:  93:  94:  95:  96:  97:  98:  99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216: 217: 218: 219: 220: 221: 222: 223: 224: 225: 226: 227: 228: 229: 230: 231: 232: 233: 234: 235: 236: 237: 238: 239: 240: 241: 242: 243: 244: 245: 246: 247: 248: 249: 250: 251: 252: 253: 254: 255: 256: 257: 258: 259: 260: 261: 262: 263: 264: 265: 266: 267: 268: 269: 270: 271: 272: 273: 274: 275: 276: 277: 278: 279: 280: 281: 282: 283: 284: 285: 286: 287: 288: 289: 290: 291: 292: 293: 294: 295: 296: 297: 298: 299: 300: 301: 302: 303: 304: 305: 306: 307: 308: 309: 310: 311: 312: 313: 314: 315: 316: 317: 318: 319: 320: 321: 322: 323: 324: 325: 326: 327: 328: 329: 330: 331: 332: 333: 334: 335: 336: 
<?php
namespace Omeka\Api\Adapter;

use Doctrine\Common\Collections\Criteria;
use Doctrine\ORM\QueryBuilder;
use Omeka\Api\Request;
use Omeka\Entity\EntityInterface;
use Omeka\Entity\SitePermission;
use Omeka\Entity\SiteItemSet;
use Omeka\Stdlib\ErrorStore;
use Omeka\Stdlib\Message;

class SiteAdapter extends AbstractEntityAdapter
{
    use SiteSlugTrait;

    /**
     * {@inheritDoc}
     */
    public function getResourceName()
    {
        return 'sites';
    }

    /**
     * {@inheritDoc}
     */
    public function getRepresentationClass()
    {
        return 'Omeka\Api\Representation\SiteRepresentation';
    }

    /**
     * {@inheritDoc}
     */
    public function getEntityClass()
    {
        return 'Omeka\Entity\Site';
    }

    /**
     * {@inheritDoc}
     */
    public function hydrate(Request $request, EntityInterface $entity,
        ErrorStore $errorStore
    ) {
        $this->hydrateOwner($request, $entity);
        $title = null;

        if (Request::CREATE === $request->getOperation()) {
            // Automatically add the site owner as a site administrator.
            $user = $this->getServiceLocator()->get('Omeka\AuthenticationService')->getIdentity();
            if ($user) {
                $sitePermission = new SitePermission;
                $sitePermission->setSite($entity);
                $sitePermission->setUser($user);
                $sitePermission->setRole('admin');
                $entity->getSitePermissions()->add($sitePermission);
            }
        }
        if ($this->shouldHydrate($request, 'o:theme')) {
            $entity->setTheme($request->getValue('o:theme'));
        }
        if ($this->shouldHydrate($request, 'o:title')) {
            $title = trim($request->getValue('o:title', ''));
            $entity->setTitle($title);
        }
        if ($this->shouldHydrate($request, 'o:slug')) {
            $default = null;
            $slug = trim($request->getValue('o:slug', ''));
            if ($slug === ''
                && $request->getOperation() === Request::CREATE
                && is_string($title)
                && $title !== ''
            ) {
                $slug = $this->getAutomaticSlug($title);
            }
            $entity->setSlug($slug);
        }
        if ($this->shouldHydrate($request, 'o:navigation')) {
            $default = [];
            if ($request->getOperation() === Request::CREATE) {
                $default = $this->getDefaultNavigation();
            }
            $entity->setNavigation($request->getValue('o:navigation', $default));
        }
        if ($this->shouldHydrate($request, 'o:item_pool')) {
            $entity->setItemPool($request->getValue('o:item_pool', []));
        }
        if ($this->shouldHydrate($request, 'o:is_public')) {
            $entity->setIsPublic($request->getValue('o:is_public', true));
        }

        if ($this->shouldHydrate($request, 'o:page')) {
            $pagesData = $request->getValue('o:page', []);
            $adapter = $this->getAdapter('site_pages');
            $retainPages = [];
            foreach ($pagesData as $pageData) {
                if (isset($pageData['o:id'])) {
                    $page = $adapter->findEntity($pageData['o:id']);
                    $retainPages[] = $page;
                }
            }

            $pages = $entity->getPages();
            // Remove pages not included in request.
            foreach ($pages as $page) {
                if (!in_array($page, $retainPages, true)) {
                    $pages->removeElement($page);
                }
            }

            if ($request->getOperation() === Request::CREATE) {
                $class = $adapter->getEntityClass();
                $page = new $class;
                $page->setSite($entity);
                $translator = $this->getServiceLocator()->get('MvcTranslator');
                $subErrorStore = new ErrorStore;
                $subrequest = new Request(Request::CREATE, 'site_pages');
                $subrequest->setContent(
                        [
                            'o:title' => $translator->translate('Welcome'),
                            'o:slug' => 'welcome',
                            'o:block' => [
                                [
                                    'o:layout' => 'html',
                                    'o:data' => ['html' => $translator->translate('Welcome to your new site. This is an example page.')],
                                ],
                            ],
                        ]
                    );
                try {
                    $adapter->hydrateEntity($subrequest, $page, $subErrorStore);
                } catch (Exception\ValidationException $e) {
                    $errorStore->mergeErrors($e->getErrorStore(), 'o:page');
                }
                $pages->add($page);
            }
        }

        $sitePermissionsData = $request->getValue('o:site_permission');
        if ($this->shouldHydrate($request, 'o:site_permission')
            && is_array($sitePermissionsData)
        ) {
            $userAdapter = $this->getAdapter('users');
            $sitePermissions = $entity->getSitePermissions();
            $sitePermissionsToRetain = [];

            foreach ($sitePermissionsData as $sitePermissionData) {
                if (!isset($sitePermissionData['o:user']['o:id'])) {
                    continue;
                }
                if (!isset($sitePermissionData['o:role'])) {
                    continue;
                }

                $user = $userAdapter->findEntity($sitePermissionData['o:user']['o:id']);
                $criteria = Criteria::create()
                    ->where(Criteria::expr()->eq('user', $user));
                $sitePermission = $sitePermissions->matching($criteria)->first();

                if (!$sitePermission) {
                    $sitePermission = new SitePermission;
                    $sitePermission->setSite($entity);
                    $sitePermission->setUser($user);
                    $entity->getSitePermissions()->add($sitePermission);
                }

                $sitePermission->setRole($sitePermissionData['o:role']);
                $sitePermissionsToRetain[] = $sitePermission;
            }
            foreach ($sitePermissions as $sitePermissionId => $sitePermission) {
                if (!in_array($sitePermission, $sitePermissionsToRetain)) {
                    $sitePermissions->remove($sitePermissionId);
                }
            }
        }

        if ($this->shouldHydrate($request, 'o:site_item_set')) {
            $itemSetsData = $request->getValue('o:site_item_set', []);
            $siteItemSets = $entity->getSiteItemSets();
            $itemSetsAdapter = $this->getAdapter('item_sets');
            $siteItemSetsToRetain = [];

            $position = 1;
            foreach ($itemSetsData as $itemSetData) {
                if (!isset($itemSetData['o:item_set']['o:id'])) {
                    continue;
                }
                $itemSet = $itemSetsAdapter->findEntity($itemSetData['o:item_set']['o:id']);
                $criteria = Criteria::create()->where(Criteria::expr()->eq('itemSet', $itemSet));
                $siteItemSet = $siteItemSets->matching($criteria)->first();
                if (!$siteItemSet) {
                    $siteItemSet = new SiteItemSet;
                    $siteItemSet->setSite($entity);
                    $siteItemSet->setItemSet($itemSet);
                    $siteItemSets->add($siteItemSet);
                }
                $siteItemSet->setPosition($position++);
                $siteItemSetsToRetain[] = $siteItemSet;
            }
            foreach ($siteItemSets as $siteItemSet) {
                if (!in_array($siteItemSet, $siteItemSetsToRetain)) {
                    $siteItemSets->removeElement($siteItemSet);
                }
            }
        }

        $this->updateTimestamps($request, $entity);
    }

    /**
     * {@inheritDoc}
     */
    public function validateEntity(EntityInterface $entity, ErrorStore $errorStore)
    {
        $title = $entity->getTitle();
        if (!is_string($title) || $title === '') {
            $errorStore->addError('o:title', 'A site must have a title.'); // @translate
        }
        $slug = $entity->getSlug();
        if (!is_string($slug) || $slug === '') {
            $errorStore->addError('o:slug', 'The slug cannot be empty.'); // @translate
        }
        if (preg_match('/[^a-zA-Z0-9_-]/u', $slug)) {
            $errorStore->addError('o:slug', 'A slug can only contain letters, numbers, underscores, and hyphens.'); // @translate
        }
        if (!$this->isUnique($entity, ['slug' => $slug])) {
            $errorStore->addError('o:slug', new Message(
                'The slug "%s" is already taken.', // @translate
                $slug
            ));
        }

        if (false == $entity->getTheme()) {
            $errorStore->addError('o:theme', 'A site must have a theme.'); // @translate
        }

        $this->validateNavigation($entity, $errorStore);
        if (!is_array($entity->getItemPool())) {
            $errorStore->addError('o:item_pool', 'A site must have item pool data.'); // @translate
        }
    }

    public function buildQuery(QueryBuilder $qb, array $query)
    {
        if (isset($query['owner_id'])) {
            $userAlias = $this->createAlias();
            $qb->innerJoin(
                'Omeka\Entity\Site.owner',
                $userAlias
            );
            $qb->andWhere($qb->expr()->eq(
                "$userAlias.id",
                $this->createNamedParameter($qb, $query['owner_id']))
            );
        }
    }

    /**
     * Validate navigation.
     *
     * Prevent corrupt navigation data by validating prior to saving.
     *
     * @param EntityInterface $entity
     * @param ErrorStore $errorStore
     */
    protected function validateNavigation(EntityInterface $entity,
        ErrorStore $errorStore
    ) {
        $navigation = $entity->getNavigation();

        if (!is_array($navigation)) {
            $errorStore->addError('o:navigation', 'Invalid navigation: navigation must be an array'); // @translate
            return;
        }

        $pagesInNavigation = [];
        $manager = $this->getServiceLocator()->get('Omeka\Site\NavigationLinkManager');
        $validateLinks = function ($linksIn) use (&$validateLinks, $manager, $errorStore, $pagesInNavigation) {
            foreach ($linksIn as $key => $data) {
                if (!isset($data['type'])) {
                    $errorStore->addError('o:navigation', 'Invalid navigation: link missing type'); // @translate
                    return;
                }
                if (!isset($data['data'])) {
                    $errorStore->addError('o:navigation', 'Invalid navigation: link missing data'); // @translate
                    return;
                }
                if (!$manager->get($data['type'])->isValid($data['data'], $errorStore)) {
                    $errorStore->addError('o:navigation', 'Invalid navigation: invalid link data'); // @translate
                    return;
                }
                if ('page' === $data['type']) {
                    if (in_array($data['data']['id'], $pagesInNavigation)) {
                        $errorStore->addError('o:navigation', 'Invalid navigation: page links must be unique'); // @translate
                        return;
                    }
                    $pagesInNavigation[] = $data['data']['id'];
                }
                if (isset($data['links'])) {
                    if (!is_array($data['links'])) {
                        $errorStore->addError('o:navigation', 'Invalid navigation: links must be an array'); // @translate
                        return;
                    }
                    $validateLinks($data['links']);
                }
            }
        };
        $validateLinks($navigation);
    }

    /**
     * Get the default nav array for new sites with no specified
     * navigation.
     *
     * The default is to just include a link to the browse page.
     *
     * @return array
     */
    protected function getDefaultNavigation()
    {
        $translator = $this->getServiceLocator()->get('MvcTranslator');
        return [
            [
                'type' => 'browse',
                'data' => [
                    'label' => $translator->translate('Browse'),
                    'query' => '',
                ],
                'links' => [],
            ],
        ];
    }
}