1<?php
2
3/**
4 * This file is part of the FreeDSx LDAP package.
5 *
6 * (c) Chad Sikorra <Chad.Sikorra@gmail.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace FreeDSx\Ldap\Control\Sorting;
13
14use FreeDSx\Asn1\Asn1;
15use FreeDSx\Asn1\Exception\EncoderException;
16use FreeDSx\Asn1\Exception\PartialPduException;
17use FreeDSx\Asn1\Type\AbstractType;
18use FreeDSx\Asn1\Type\IncompleteType;
19use FreeDSx\Asn1\Type\OctetStringType;
20use FreeDSx\Asn1\Type\SequenceType;
21use FreeDSx\Ldap\Control\Control;
22use FreeDSx\Ldap\Exception\ProtocolException;
23use FreeDSx\Ldap\Protocol\LdapEncoder;
24
25/**
26 * A Server Side Sorting request control value. RFC 2891.
27 *
28 * SortKeyList ::= SEQUENCE OF SEQUENCE {
29 *     attributeType   AttributeDescription,
30 *     orderingRule    [0] MatchingRuleId OPTIONAL,
31 *     reverseOrder    [1] BOOLEAN DEFAULT FALSE }
32 *
33 * @author Chad Sikorra <Chad.Sikorra@gmail.com>
34 */
35class SortingControl extends Control
36{
37    /**
38     * @var SortKey[]
39     */
40    protected $sortKeys = [];
41
42    /**
43     * @param SortKey ...$sortKeys
44     */
45    public function __construct(SortKey ...$sortKeys)
46    {
47        $this->sortKeys = $sortKeys;
48        parent::__construct(self::OID_SORTING);
49    }
50
51    /**
52     * @param SortKey ...$sortKeys
53     * @return $this
54     */
55    public function addSortKeys(SortKey ...$sortKeys)
56    {
57        foreach ($sortKeys as $sortKey) {
58            $this->sortKeys[] = $sortKey;
59        }
60
61        return $this;
62    }
63
64    /**
65     * @param SortKey ...$sortKeys
66     * @return $this
67     */
68    public function setSortKeys(SortKey ...$sortKeys)
69    {
70        $this->sortKeys = $sortKeys;
71
72        return $this;
73    }
74
75    /**
76     * @return SortKey[]
77     */
78    public function getSortKeys(): array
79    {
80        return $this->sortKeys;
81    }
82
83    /**
84     * {@inheritDoc}
85     * @return Control
86     * @throws EncoderException
87     * @throws PartialPduException
88     */
89    public static function fromAsn1(AbstractType $type)
90    {
91        $response = self::decodeEncodedValue($type);
92        if (!$response instanceof SequenceType) {
93            throw new ProtocolException('The sorting control is malformed.');
94        }
95
96        $sortKeys = [];
97        foreach ($response->getChildren() as $sequence) {
98            if (!$sequence instanceof SequenceType) {
99                throw new ProtocolException('The sort key is malformed.');
100            }
101
102            $attrName = '';
103            $matchRule = null;
104            $useReverseOrder = false;
105
106            $encoder = new LdapEncoder();
107            /** @var AbstractType $keyItem */
108            foreach ($sequence->getChildren() as $keyItem) {
109                if ($keyItem instanceof OctetStringType && $keyItem->getTagClass() === AbstractType::TAG_CLASS_UNIVERSAL) {
110                    $attrName = $keyItem->getValue();
111                } elseif ($keyItem->getTagClass() === AbstractType::TAG_CLASS_CONTEXT_SPECIFIC && $keyItem->getTagNumber() === 0) {
112                    $matchRule = $keyItem->getValue();
113                } elseif ($keyItem->getTagClass() === AbstractType::TAG_CLASS_CONTEXT_SPECIFIC && $keyItem->getTagNumber() === 1) {
114                    if (!$keyItem instanceof IncompleteType) {
115                        throw new ProtocolException('The sorting control is malformed.');
116                    }
117                    /** @var IncompleteType $keyItem */
118                    $useReverseOrder = $encoder->complete($keyItem, AbstractType::TAG_TYPE_BOOLEAN)->getValue();
119                } else {
120                    throw new ProtocolException('The sorting control contains unexpected data.');
121                }
122            }
123            if ($attrName === '') {
124                throw new ProtocolException('The sort key is missing an attribute description.');
125            }
126
127            $sortKeys[] = new SortKey($attrName, $useReverseOrder, $matchRule);
128        }
129        $control = new self(...$sortKeys);
130
131        return self::mergeControlData($control, $type);
132    }
133
134    /**
135     * {@inheritdoc}
136     */
137    public function toAsn1(): AbstractType
138    {
139        $this->controlValue = Asn1::sequenceOf();
140
141        foreach ($this->sortKeys as $sortKey) {
142            $child = Asn1::sequence(Asn1::octetString($sortKey->getAttribute()));
143            if ($sortKey->getOrderingRule() !== null) {
144                $child->addChild(Asn1::context(0, Asn1::octetString($sortKey->getOrderingRule())));
145            }
146            if ($sortKey->getUseReverseOrder()) {
147                $child->addChild(Asn1::context(1, Asn1::boolean(true)));
148            }
149            $this->controlValue->addChild($child);
150        }
151
152        return parent::toAsn1();
153    }
154}
155