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\Operation\Request;
12
13use FreeDSx\Asn1\Asn1;
14use FreeDSx\Asn1\Type\AbstractType;
15use FreeDSx\Asn1\Type\BooleanType;
16use FreeDSx\Asn1\Type\OctetStringType;
17use FreeDSx\Asn1\Type\SequenceType;
18use FreeDSx\Ldap\Entry\Dn;
19use FreeDSx\Ldap\Entry\Rdn;
20use FreeDSx\Ldap\Exception\ProtocolException;
21
22/**
23 * A Modify DN Request. RFC 4511, 4.9
24 *
25 * ModifyDNRequest ::= [APPLICATION 12] SEQUENCE {
26 *     entry           LDAPDN,
27 *     newrdn          RelativeLDAPDN,
28 *     deleteoldrdn    BOOLEAN,
29 *     newSuperior     [0] LDAPDN OPTIONAL }
30 *
31 * @author Chad Sikorra <Chad.Sikorra@gmail.com>
32 */
33class ModifyDnRequest implements RequestInterface, DnRequestInterface
34{
35    protected const APP_TAG = 12;
36
37    /**
38     * @var Dn
39     */
40    protected $dn;
41
42    /**
43     * @var Rdn
44     */
45    protected $newRdn;
46
47    /**
48     * @var bool
49     */
50    protected $deleteOldRdn;
51
52    /**
53     * @var null|Dn
54     */
55    protected $newParentDn;
56
57    /**
58     * @param string|Dn $dn
59     * @param string|Rdn $newRdn
60     * @param bool $deleteOldRdn
61     * @param null|string|Dn $newParentDn
62     */
63    public function __construct($dn, $newRdn, bool $deleteOldRdn, $newParentDn = null)
64    {
65        $this->setDn($dn);
66        $this->setNewRdn($newRdn);
67        $this->setNewParentDn($newParentDn);
68        $this->deleteOldRdn = $deleteOldRdn;
69    }
70
71    /**
72     * @return Dn
73     */
74    public function getDn(): Dn
75    {
76        return $this->dn;
77    }
78
79    /**
80     * @param string|Dn $dn
81     * @return $this
82     */
83    public function setDn($dn)
84    {
85        $this->dn = $dn instanceof Dn ? $dn : new Dn($dn);
86
87        return $this;
88    }
89
90    /**
91     * @return Rdn
92     */
93    public function getNewRdn(): Rdn
94    {
95        return $this->newRdn;
96    }
97
98    /**
99     * @param string|Rdn $newRdn
100     * @return $this
101     */
102    public function setNewRdn($newRdn)
103    {
104        $this->newRdn = $newRdn instanceof Rdn ? $newRdn : Rdn::create($newRdn);
105
106        return $this;
107    }
108
109    /**
110     * @return bool
111     */
112    public function getDeleteOldRdn(): bool
113    {
114        return $this->deleteOldRdn;
115    }
116
117    /**
118     * @param bool $deleteOldRdn
119     * @return $this
120     */
121    public function setDeleteOldRdn(bool $deleteOldRdn)
122    {
123        $this->deleteOldRdn = $deleteOldRdn;
124
125        return $this;
126    }
127
128    /**
129     * @return null|Dn
130     */
131    public function getNewParentDn(): ?Dn
132    {
133        return $this->newParentDn;
134    }
135
136    /**
137     * @param null|string|Dn $newParentDn
138     * @return $this
139     */
140    public function setNewParentDn($newParentDn)
141    {
142        if ($newParentDn !== null) {
143            $newParentDn = $newParentDn instanceof Dn ? $newParentDn : new Dn($newParentDn);
144        }
145        $this->newParentDn = $newParentDn;
146
147        return $this;
148    }
149
150    /**
151     * {@inheritdoc}
152     */
153    public static function fromAsn1(AbstractType $type)
154    {
155        if (!($type instanceof SequenceType && \count($type) >= 3 && \count($type) <= 4)) {
156            throw new ProtocolException('The modify dn request is malformed');
157        }
158        $entry = $type->getChild(0);
159        $newRdn = $type->getChild(1);
160        $deleteOldRdn = $type->getChild(2);
161        $newSuperior = $type->getChild(3);
162
163        if (!($entry instanceof OctetStringType && $newRdn instanceof OctetStringType && $deleteOldRdn instanceof BooleanType)) {
164            throw new ProtocolException('The modify dn request is malformed');
165        }
166        if ($newSuperior !== null && !($newSuperior->getTagClass() === AbstractType::TAG_CLASS_CONTEXT_SPECIFIC && $newSuperior->getTagNumber() === 0)) {
167            throw new ProtocolException('The modify dn request is malformed');
168        }
169        if ($newSuperior !== null && !$newSuperior instanceof OctetStringType) {
170            throw new ProtocolException('The modify dn request is malformed');
171        }
172        $newSuperior = ($newSuperior !== null) ? $newSuperior->getValue() : null;
173
174        return new self($entry->getValue(), $newRdn->getValue(), $deleteOldRdn->getValue(), $newSuperior);
175    }
176
177    /**
178     * {@inheritdoc}
179     */
180    public function toAsn1(): AbstractType
181    {
182        /** @var \FreeDSx\Asn1\Type\SequenceType $asn1 */
183        $asn1 = Asn1::application(self::APP_TAG, Asn1::sequence(
184            Asn1::octetString($this->dn->toString()),
185            // @todo Make a RDN type. Future validation purposes?
186            Asn1::octetString($this->newRdn->toString()),
187            Asn1::boolean($this->deleteOldRdn)
188        ));
189        if ($this->newParentDn !== null) {
190            $asn1->addChild(Asn1::context(0, Asn1::octetString($this->newParentDn->toString())));
191        }
192
193        return $asn1;
194    }
195}
196