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