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: 
<?php
namespace Omeka\Api\Adapter;

use Omeka\Api\Exception\NotFoundException;
use Omeka\Api\Request;
use Omeka\Entity\EntityInterface;
use Omeka\Entity\SiteBlockAttachment;
use Omeka\Entity\SitePage;
use Omeka\Entity\SitePageBlock;
use Omeka\Stdlib\ErrorStore;
use Omeka\Stdlib\Message;

class SitePageAdapter extends AbstractEntityAdapter
{
    use SiteSlugTrait;

    /**
     * {@inheritDoc}
     */
    protected $sortFields = [
        'id' => 'id',
        'created' => 'created',
        'modified' => 'modified',
    ];

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

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

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

    /**
     * {@inheritDoc}
     */
    public function hydrate(Request $request, EntityInterface $entity,
        ErrorStore $errorStore
    ) {
        $title = null;
        $data = $request->getContent();
        $blockData = $request->getValue('o:block', []);

        if (Request::CREATE === $request->getOperation() && isset($data['o:site']['o:id'])) {
            $site = $this->getAdapter('sites')->findEntity($data['o:site']['o:id']);
            $this->authorize($site, 'add-page');
            $entity->setSite($site);

            if (!$blockData) {
                // Add the pageTitle block to all new pages.
                $blockData = [[
                    'o:layout' => 'pageTitle',
                    'o:data' => [],
                ]];
            }
        }

        if ($this->shouldHydrate($request, 'o:title')) {
            $title = trim($request->getValue('o:title', ''));
            $entity->setTitle($title);
        }

        if ($this->shouldHydrate($request, 'o:slug')) {
            $slug = trim($request->getValue('o:slug', ''));
            if ($slug === ''
                && $request->getOperation() === Request::CREATE
                && is_string($title) && $title !== ''
                && isset($site)
            ) {
                $slug = $this->getAutomaticSlug($title, $site);
            }
            $entity->setSlug($slug);
        }

        $appendBlocks = $request->getOperation() === Request::UPDATE && $request->getOption('isPartial', false);
        $this->hydrateBlocks($blockData, $entity, $errorStore, $appendBlocks);
        $this->updateTimestamps($request, $entity);
    }

    /**
     * {@inheritDoc}
     */
    public function validateEntity(EntityInterface $entity, ErrorStore $errorStore)
    {
        if (!$entity->getSite()) {
            $errorStore->addError('o:site', 'A page must belong to a site.'); // @translate
        }

        $title = $entity->getTitle();
        if (!is_string($title) || $title === '') {
            $errorStore->addError('o:title', 'A page 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
        }
        $site = $entity->getSite();
        if ($site && $site->getId() && !$this->isUnique($entity, [
                'slug' => $slug,
                'site' => $entity->getSite(),
        ])) {
            $errorStore->addError('o:slug', new Message(
                'The slug "%s" is already taken.', // @translate
                $slug
            ));
        }
    }

    /**
     * Hydrate block data for the page.
     *
     * @param array $blockData
     * @param SitePage $page
     * @param bool $append
     */
    private function hydrateBlocks(array $blockData, SitePage $page, ErrorStore $errorStore,
        $append = false)
    {
        $blocks = $page->getBlocks();
        $existingBlocks = $blocks->toArray();
        $newBlocks = [];
        $position = 1;
        $fallbackBlock = [
            'o:layout' => null,
            'o:data' => [],
        ];

        foreach ($blockData as $inputBlock) {
            if (!is_array($inputBlock)) {
                continue;
            }

            $inputBlock = array_merge($fallbackBlock, $inputBlock);

            $block = current($existingBlocks);
            if ($block === false || $append) {
                $block = new SitePageBlock;
                $block->setPage($page);
                $newBlocks[] = $block;
            } else {
                // Null out values as we re-use them
                $existingBlocks[key($existingBlocks)] = null;
                next($existingBlocks);
            }

            if (!is_string($inputBlock['o:layout']) || $inputBlock['o:layout'] === '') {
                $errorStore->addError('o:block', 'All blocks must have a layout.'); // @translate
                return;
            }
            if (!is_array($inputBlock['o:data'])) {
                $errorStore->addError('o:block', 'Block data must not be a scalar value.'); // @translate
                return;
            }

            $block->setLayout($inputBlock['o:layout']);
            $block->setData($inputBlock['o:data']);

            // (Re-)order blocks by their order in the input
            $block->setPosition($position++);

            $attachmentData = isset($inputBlock['o:attachment'])
                ? $inputBlock['o:attachment'] : [];

            // Hydrate attachments, and abort block hydration if there's an error
            if (!$this->hydrateAttachments($attachmentData, $block, $errorStore)) {
                return;
            }

            $handler = $this->getServiceLocator()
                ->get('Omeka\BlockLayoutManager')
                ->get($inputBlock['o:layout'])
                ->onHydrate($block, $errorStore);
        }

        // Remove any blocks that weren't reused
        if (!$append) {
            foreach ($existingBlocks as $key => $existingBlock) {
                if ($existingBlock !== null) {
                    $blocks->remove($key);
                }
            }
        }

        // Add any new blocks that had to be created
        foreach ($newBlocks as $newBlock) {
            $blocks->add($newBlock);
        }
    }

    /**
     * Hydrate attachment data for a block
     *
     * @param array $attachmentData
     * @param SitePageBlock $block
     * @param ErrorStore $errorStore
     * @return bool true on success, false on error
     */
    private function hydrateAttachments(array $attachmentData, SitePageBlock $block,
        ErrorStore $errorStore)
    {
        $itemAdapter = $this->getAdapter('items');
        $attachments = $block->getAttachments();
        $existingAttachments = $attachments->toArray();
        $newAttachments = [];
        $position = 1;

        foreach ($attachmentData as $inputAttachment) {
            if (!is_array($inputAttachment)) {
                continue;
            }

            $attachment = current($existingAttachments);
            if ($attachment === false) {
                $attachment = new SiteBlockAttachment;
                $attachment->setBlock($block);
                $newAttachments[] = $attachment;
            } else {
                // Null out values as we re-use them
                $existingAttachments[key($existingAttachments)] = null;
                next($existingAttachments);
            }

            try {
                $item = $itemAdapter->findEntity($inputAttachment['o:item']['o:id']);
            } catch (NotFoundException $e) {
                $item = null;
            }

            if ($item && isset($inputAttachment['o:media']['o:id'])) {
                $itemMedia = $item->getMedia();
                $media = $itemMedia->get($inputAttachment['o:media']['o:id']);
            } else {
                $media = null;
            }

            $caption = isset($inputAttachment['o:caption']) ? $inputAttachment['o:caption'] : '';
            $purifier = $this->getServiceLocator()->get('Omeka\HtmlPurifier');
            $caption = $purifier->purify($caption);

            $attachment->setItem($item);
            $attachment->setMedia($media);
            $attachment->setCaption($caption);
            $attachment->setPosition($position++);
        }

        // Remove any blocks that weren't reused
        foreach ($existingAttachments as $key => $existingAttachment) {
            if ($existingAttachment !== null) {
                $attachments->remove($key);
            }
        }

        // Add any new blocks that had to be created
        foreach ($newAttachments as $newAttachment) {
            $attachments->add($newAttachment);
        }

        return true;
    }
}