partkeepr

fork of partkeepr
git clone https://git.e1e0.net/partkeepr.git
Log | Files | Refs | Submodules | README | LICENSE

commit 7f858dcf3beff02ecd8e1491412492052fd6ca58
parent 161a3f411d65cb229288dc0d139313ebe7fa50b4
Author: Felicitus <felicitus@felicitus.de>
Date:   Wed, 15 Sep 2010 06:03:11 +0200

Added doctrine2-nestedset support
Diffstat:
A3rdparty/doctrine2-nestedset/INSTALL.markdown | 14++++++++++++++
A3rdparty/doctrine2-nestedset/LICENSE | 504+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A3rdparty/doctrine2-nestedset/README.markdown | 326+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A3rdparty/doctrine2-nestedset/lib/DoctrineExtensions/NestedSet/Config.php | 336+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A3rdparty/doctrine2-nestedset/lib/DoctrineExtensions/NestedSet/Manager.php | 580+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A3rdparty/doctrine2-nestedset/lib/DoctrineExtensions/NestedSet/MultipleRootNode.php | 47+++++++++++++++++++++++++++++++++++++++++++++++
A3rdparty/doctrine2-nestedset/lib/DoctrineExtensions/NestedSet/Node.php | 76++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A3rdparty/doctrine2-nestedset/lib/DoctrineExtensions/NestedSet/NodeWrapper.php | 1651+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A3rdparty/doctrine2-nestedset/tests/.gitignore | 1+
A3rdparty/doctrine2-nestedset/tests/DoctrineExtensions/NestedSet/Tests/ConfigTest.php | 238+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A3rdparty/doctrine2-nestedset/tests/DoctrineExtensions/NestedSet/Tests/DatabaseTest.php | 77+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A3rdparty/doctrine2-nestedset/tests/DoctrineExtensions/NestedSet/Tests/ManagerTest.php | 475+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A3rdparty/doctrine2-nestedset/tests/DoctrineExtensions/NestedSet/Tests/Mocks/ManagerMock.php | 36++++++++++++++++++++++++++++++++++++
A3rdparty/doctrine2-nestedset/tests/DoctrineExtensions/NestedSet/Tests/Mocks/NodeMock.php | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A3rdparty/doctrine2-nestedset/tests/DoctrineExtensions/NestedSet/Tests/Mocks/NonNodeMock.php | 31+++++++++++++++++++++++++++++++
A3rdparty/doctrine2-nestedset/tests/DoctrineExtensions/NestedSet/Tests/Mocks/SingleRootNodeMock.php | 72++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A3rdparty/doctrine2-nestedset/tests/DoctrineExtensions/NestedSet/Tests/NodeWrapperTest.php | 1022+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A3rdparty/doctrine2-nestedset/tests/DoctrineExtensions/NestedSet/Tests/SingleRootNodeWrapperTest.php | 221+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A3rdparty/doctrine2-nestedset/tests/README.markdown | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A3rdparty/doctrine2-nestedset/tests/autoload.php | 19+++++++++++++++++++
A3rdparty/doctrine2-nestedset/tests/install_vendors.sh | 16++++++++++++++++
A3rdparty/doctrine2-nestedset/tests/phpunit.xml.dist | 22++++++++++++++++++++++
A3rdparty/doctrine2-nestedset/tests/update_vendors.sh | 8++++++++
23 files changed, 5907 insertions(+), 0 deletions(-)

diff --git a/3rdparty/doctrine2-nestedset/INSTALL.markdown b/3rdparty/doctrine2-nestedset/INSTALL.markdown @@ -0,0 +1,14 @@ +Installing NestedSet for Doctrine2 +================================== + +In the vendor directory of your project, use git to clone the NestedSet +extension: + + $ cd vendor + $ git clone git://github.com/blt04/doctrine2-nestedset.git + +Now add the `DoctrineExtensions\NestedSet` namespace to your class autoloader. +For example, if using Doctrine's autoloader: + + $loader = new Doctrine\Common\ClassLoader("DoctrineExtensions\\NestedSet", "vendor/doctrine2-nestedset"); + $loader->register(); diff --git a/3rdparty/doctrine2-nestedset/LICENSE b/3rdparty/doctrine2-nestedset/LICENSE @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + <one line to give the library's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + <signature of Ty Coon>, 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/3rdparty/doctrine2-nestedset/README.markdown b/3rdparty/doctrine2-nestedset/README.markdown @@ -0,0 +1,326 @@ +Doctrine2 NestedSet +=================== + +This Doctrine2 extension implements the nested set model (modified pre-order +tree traversal algorithm) for Doctrine2. This allows storing hierarchical +data, a collection of data where each item has a single parent and zero or +more children, in the flat tables of a relational database. For more +information on the nested set model, see: + +[http://dev.mysql.com/tech-resources/articles/hierarchical-data.html](http://dev.mysql.com/tech-resources/articles/hierarchical-data.html) + + +## Introduction + +Nested Set is a solution for storing hierarchical data that provides very fast +read access. However, updating nested set trees is more costly. Therefore this +solution is best suited for hierarchies that are much more frequently read +than written to. And because of the nature of the web, this is the case for +most web applications. + + +## Setting Up + +To set up your model as a Nested Set, your entity classes must implement the +`DoctrineExtensions\NestedSet\Node` interface. Each entity class must contain +mapped fields for holding the Nested Set left and right values. + +Here's an example using annotation mapping: + + namespace Entity; + + use DoctrineExtensions\NestedSet\Node; + + class Category implements Node + { + /** + * @Id @Column(type="integer") + */ + private $id; + + /** + * @Column(type="integer") + */ + private $lft; + + /** + * @Column(type="integer") + */ + private $rgt; + + /** + * @Column(type="string", length="16") + */ + private $name; + + + public function getId() { return $this->id; } + + public function getLeftValue() { return $this->lft; } + public function setLeftValue($lft) { $this->lft = $lft; } + + public function getRightValue() { return $this->rgt; } + public function setRightValue($rgt) { $this->rgt = $rgt; } + + public function __toString() { return $this->name; } + } + +Generally you do not need to, and should not, interact with the left and right +fields. These are used internally to manage the tree structure. + + +## Multiple Trees + +The nested set implementation can be configured to allow your table to have +multiple root nodes, and therefore multiple trees within the same table. This +is done by implementing the `DoctrineExtensions\NestedSet\MultipleRootNode` +interface (instead of `DoctrineExtensions\NestedSet\Node`) and mapping a root +field. + +Extending our annotation example: + + /** + * @Column(type="integer") + */ + private $root; + + public function getRootValue() { return $this->root; } + public function setRootValue($root) { $this->root = $root; } + +Like the left and right fields, you generally do not need to interact with the +root value. + + +## Working with Trees + +After you successfully set up your model as a nested set you can start working +with it. Working with Doctrine2's nested set implementation is all about two +classes: Manager and NodeWrapper. NodeWrapper wraps your entity classes +giving you access to the underlying tree structure. Manager provides methods +for creating new trees and fetching existing trees. + +To fetch an entire tree from the database: + + $config = new Config($em, 'Entity\Category'); + $nsm = new Manager($config); + $rootNode = $nsm->fetchTree(1); + +In this example, `$rootNode` is an instance of `NodeWrapper` wrapping your +model's root node. To get access to your model object: + + $modelObject = $rootNode->getNode(); + + +### Creating a Root Node + + $config = new Config($em, 'Entity\Category'); + $nsm = new Manager($config); + + $category = new Category(); + $category->name = 'Root Category 1'; + + $rootNode = $nsm->createRoot($category); + + +### Inserting a Node + + $child1 = new Category(); + $child1->name = 'Child Category 1'; + + $child2 = new Category(); + $child2->name = 'Child Category 2'; + + $rootNode->addChild($child1); + $rootNode->addChild($child2); + + +### Deleting a Node + +You must always delete a node using the `NodeWrapper::delete()` method instead +of EntityManager's delete method. `NodeWrapper::delete()` takes care of +updating the tree when deleting nodes: + + $category = $em->getRepository('Entity\Category')->findOneByName('Child Category 1'); + $node = $nsm->wrapNode($category); + $node->delete(); + +Deleting a node will also delete all descendants of that node. So make sure +you move them elsewhere before you delete the node if you don't want to delete +them. + + +### Moving a Node + +Moving a node is simple. NodeWrapper offers several methods for moving nodes +around between trees: + +* moveAsLastChildOf($other) +* moveAsFirstChildOf($other) +* moveAsPrevSiblingOf($other) +* moveAsNextSiblingOf($other) + + +### Examining a Node + +You can examine the nodes and what type of node they are by using some of the +following functions: + + $isLeaf = $node->isLeaf(); + $isRoot = $node->isRoot(); + + +### Examining and Retrieving Siblings + +You can easily check if a node has any next or previous siblings by using the +following methods: + + $hasNextSib = $node->hasNextSibling(); + $hasPrevSib = $node->hasPrevSibling(); + +You can also retrieve the next or previous siblings if they exist with the +following methods: + + $nextSib = $node->getNextSibling(); + $prevSib = $node->getPrevSibling(); + +If you want to retrieve an array of all the siblings you can simply use the +`getSiblings()` method: + + $siblings = $node->getSiblings(); + + +### Examining and Retrieving Descendants + +You can check if a node has a parent or children by using the following +methods: + + $hasChildren = $node->hasChildren(); + $hasParent = $node->hasParent(); + +You can retrieve a nodes first and last child by using the following methods: + + $firstChild = $node->getFirstChild(); + $lastChild = $node->getLastChild(); + +Or if you want to retrieve the parent of a node: + + $parent = $node->getParent(); + +You can get the children of a node by using the following method: + + $children = $node->getChildren(); + +> The `getChildren()` method returns only the direct descendants. If you want +> all descendants, use the `getDescendants()` method. + +You can get the descendants or ancestors of a node by using the following +methods: + + $descendants = $node->getDescendants(); + $ancestors = $node->getAncestors(); + +Sometimes you may just want to get the number of children or descendants. You +can use the following methods to accomplish this: + + $numChildren = $node->getNumberChildren(); + $numDescendants = $node->getNumberDescendants(); + +The `getDescendants()` method accepts a parameter that you can use to specify +the depth of the resulting branch. For example `getDescendants(1)` retrieves +only the direct descendants (the descendants that are 1 level below, that's +the same as `getChildren()`). + + +### Rendering a Simple Tree + + $tree = $nsm->fetchTreeAsArray(1); + + foreach ($tree as $node) { + echo str_repeat('&nbsp;&nbsp;', $node->getLevel()) . $node->getName() . "\n"; + } + + +## Advanced Usage + +The previous sections have explained the basic usage of Doctrine's nested set +implementation. This section will go one step further. + + +### Fetching a Tree with Relations + +If you're a demanding software developer this question may already have come +into your mind: "How do I fetch a tree/branch with related data?". Simple +example: You want to display a tree of categories, but you also want to +display some related data of each category, let's say some details of the +hottest product in that category. Fetching the tree as seen in the previous +sections and simply accessing the relations while iterating over the tree is +possible but produces a lot of unnecessary database queries. Luckily, +Manager and some flexibility in the nested set implementation have come +to your rescue. The nested set implementation uses `QueryBuilder` objects for +all it's database work. By giving you access to the base query builder of the +nested set implementation you can unleash the full power of `QueryBuilder` +while using your nested set. + + $qb = $em->createQueryBuilder(); + $qb->select('c.name, p.name, m.name') + ->from('Category c') + ->leftJoin('c.HottestProduct p') + ->leftJoin('p.Manufacturer m'); + +Now we need to set the above query as the base query for the tree: + + $nsm->getConfiguration()->setBaseQueryBuilder($qb); + $tree = $nsm->fetchTree(1); + +There it is, the tree with all the related data you need, all in one query. + +> If you don't set your own base query then one will be automatically created +> for you internally. + +When you are done it is a good idea to reset the base query back to normal: + + $nsm->getConfiguration()->resetBaseQueryBuilder(); + + +### Transactions + +When modifying a tree using methods from `NodeWrapper`, each method is +executed immediately. This differs from working with normal Doctrine2 +entities where changes are queued via the EntityManager and not executed until +`flush` is called. + +If you are making multiple changes, it is recommended to wrap these changes in +a transaction: + + $em->getConnection()->beginTransaction(); + try { + + $root = $nsm->createRoot(new Category('Root')); + $root->addChild(new Category('Child 1')); + $root->addChild(new Category('Child 2')); + + $em->getConnection()->commit(); + } catch (Exception $e) { + $em->close(); + $em->getConnection()->rollback(); + throw $e; + } + + +### Customizing left, right and root fields + +NestedSet requires you include left, right and root fields in your entity +class. By default, NestedSet expects these fields to be named lft, rgt and +root respectively. You can customize the names of these fields using via the +manager configuration: + + $config = new Config($em, 'Entity\Category'); + $config->setLeftFieldName('nsLeft'); + $config->setRightFieldName('nsRight'); + $config->setRootFieldName('nsRoot'); + $nsm = new Manager($config); + + +## Conclusion + +NestedSet makes managing hierarchical data in Doctrine2 quick and easy. diff --git a/3rdparty/doctrine2-nestedset/lib/DoctrineExtensions/NestedSet/Config.php b/3rdparty/doctrine2-nestedset/lib/DoctrineExtensions/NestedSet/Config.php @@ -0,0 +1,336 @@ +<?php +/* + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * This software consists of voluntary contributions made by many individuals + * and is licensed under the LGPL. + */ + +namespace DoctrineExtensions\NestedSet; + +use Doctrine\ORM\EntityManager; +use Doctrine\ORM\QueryBuilder; +use Doctrine\ORM\Mapping\ClassMetadata; + +/** + * The Config class holds configuration for each NestedSet Manager instance. + * + * @author Brandon Turner <bturner@bltweb.net> + */ +class Config +{ + private + $em, + $classname, + $classMetadata, + $leftFieldName, + $rightFieldName, + $rootFieldName, + $baseQueryBuilder, + $hydrateLevel, + $hydrateOutlineNumber, + $hasManyRoots; + + /** + * Constructor. + * + * @param EntityManager $em + * @param mixed $clazz a class name or ClassMetadata object representing + * the entity class associated with this configuration + */ + public function __construct(EntityManager $em, $clazz=null) + { + $this->em = $em; + + // Set defaults + $this->hasManyRoots = false; + $this->setLeftFieldName('lft'); + $this->setRightFieldName('rgt'); + $this->setRootFieldName('root'); + $this->setHydrateLevel(true); + $this->setHydrateOutlineNumber(true); + + if($clazz) + { + $this->setClass($clazz); + } + } + + + /** + * Sets the class associated with this configuration + * + * @param mixed $clazz a class name or ClassMetadata object representing + * the entity class associated with this configuration + * + * @return Config $this for fluent API + */ + public function setClass($clazz) + { + if($clazz instanceof ClassMetadata) + { + $classMetadata = $clazz; + $classname = $clazz->getReflectionClass()->getName(); + } + else + { + if(!class_exists($clazz)) + { + throw new \InvalidArgumentException("Can't find class: $clazz"); + } + + $classname = $clazz; + $classMetadata = $this->getEntityManager()->getClassMetadata($clazz); + } + + $reflectionClass = $classMetadata->getReflectionClass(); + if(!$reflectionClass->implementsInterface('DoctrineExtensions\NestedSet\Node')) + { + throw new \InvalidArgumentException('Class must implement Node interface: ' . $classname); + } + + $this->hasManyRoots = $reflectionClass->implementsInterface('DoctrineExtensions\NestedSet\MultipleRootNode'); + $this->classMetadata = $classMetadata; + $this->classname = $classname; + + return $this; + } + + + /** + * gets the entity class name associated with this configuration + * + * @return string + */ + public function getClassname() + { + return $this->classname; + } + + + /** + * gets the class metadata associated with this configuration + * + * @return ClassMetadata + */ + public function getClassMetadata() + { + return $this->classMetadata; + } + + + /** + * Returns the Doctrine entity manager associated with this Manager + * + * @return Doctrine\ORM\EntityManager + */ + public function getEntityManager() + { + return $this->em; + } + + + /** + * gets the left field name + * + * @return string + */ + public function getLeftFieldName() + { + return $this->leftFieldName; + } + + /** + * sets the left field name + * + * @param string $fieldName + * + * @return Config $this for fluent API + */ + public function setLeftFieldName($fieldName) + { + $this->leftFieldName = $fieldName; + return $this; + } + + + /** + * gets the right field name + * + * @return string + */ + public function getRightFieldName() + { + return $this->rightFieldName; + } + + /** + * sets the right field name + * + * @param string $fieldName + * + * @return Config $this for fluent API + */ + public function setRightFieldName($fieldName) + { + $this->rightFieldName = $fieldName; + return $this; + } + + + /** + * gets the root field name + * + * @return string + */ + public function getRootFieldName() + { + return $this->rootFieldName; + } + + /** + * sets the root field name + * + * @param string $fieldName + * + * @return Config $this for fluent API + */ + public function setRootFieldName($fieldName) + { + $this->rootFieldName = $fieldName; + return $this; + } + + + /** + * returns true if the root field is supported + * + * @return bool + */ + public function hasManyRoots() + { + return $this->hasManyRoots; + } + + /** + * gets the base query builder + * + * @return QueryBuilder + */ + public function getBaseQueryBuilder() + { + if(!$this->baseQueryBuilder) + { + $this->baseQueryBuilder = $this->getDefaultQueryBuilder(); + } + + return clone $this->baseQueryBuilder; + } + + + /** + * sets the base query builder + * + * @param Query $baseQueryBuilder or null to reset the base query builder + */ + public function setBaseQueryBuilder(QueryBuilder $baseQueryBuilder=null) + { + if($baseQueryBuilder === null) + { + $this->baseQueryBuilder = $this->getDefaultQueryBuilder(); + } + else + { + $this->baseQueryBuilder = $baseQueryBuilder; + } + } + + + /** + * rests the base query builder back to the default + */ + public function resetBaseQueryBuilder() + { + $this->setBaseQueryBuilder(null); + } + + + /** + * gets the default query builder + * + * @return QueryBuilder + */ + public function getDefaultQueryBuilder() + { + $em = $this->getEntityManager(); + return $em->createQueryBuilder() + ->select('n') + ->from($this->getClassname(), 'n'); + } + + + public function getQueryBuilderAlias() + { + return $this->getBaseQueryBuilder()->getRootAlias(); + } + + + /** + * Returns true if the level should be hydrated when fetching trees + * + * @return bool + */ + public function getHydrateLevel() + { + return $this->hydrateLevel; + } + + /** + * Sets whether or not to hydrate the level field when fetching trees + * + * @param bool $b + * + * @return Config $this for fluent API + */ + public function setHydrateLevel($b) + { + $this->hydrateLevel = (bool)$b; + return $this; + } + + + /* + * Returns true if the outline number should be hydrated when fetching + * trees + * + * @return bool + */ + public function getHydrateOutlineNumber() + { + return $this->hydrateOutlineNumber; + } + + + /** + * Sets whether or not to hydrate the outline number when fetching trees + * + * @param bool $b + * + * @return Config $this for fluent API + */ + public function setHydrateOutlineNumber($b) + { + $this->hydrateOutlineNumber = (bool)$b; + return $this; + } +} diff --git a/3rdparty/doctrine2-nestedset/lib/DoctrineExtensions/NestedSet/Manager.php b/3rdparty/doctrine2-nestedset/lib/DoctrineExtensions/NestedSet/Manager.php @@ -0,0 +1,580 @@ +<?php +/* + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * This software consists of voluntary contributions made by many individuals + * and is licensed under the LGPL. + */ + +namespace DoctrineExtensions\NestedSet; + +/** + * The Manager provides functions for creating and fetching a NestedSet tree. + * + * @author Brandon Turner <bturner@bltweb.net> + */ +class Manager +{ + /** @var Config */ + protected $config; + + /** @var array */ + protected $wrappers; + + + /** + * Initializes a new NestedSet Manager. + * + * @param string|Doctrine\ORM\Mapping\ClassMetadata $clazz the fully qualified entity class name + * or a ClassMetadata object representing the class of nodes to be managed + * by this manager + * @param Doctrine\ORM\EntityManager $em The EntityManager to use. + */ + public function __construct(Config $config) + { + $this->config = $config; + $this->wrappers = array(); + } + + + /** + * Fetches the complete tree, returning the root node of the tree + * + * @param mixed $rootId the root id of the tree (or null if model doesn't + * support multiple trees + * @param int $depth the depth to retrieve or null for unlimited + * + * @return NodeWrapper $root + */ + public function fetchTree($rootId=null, $depth=null) + { + $wrappers = $this->fetchTreeAsArray($rootId, $depth); + + return (!is_array($wrappers) || empty($wrappers)) ? null : $wrappers[0]; + } + + + /** + * Fetches the complete tree, returning a flat array of node wrappers with + * parent, children, ancestors and descendants pre-populated. + * + * @param mixed $rootId the root id of the tree (or null if model doesn't + * support multiple trees + * @param int $depth the depth to retrieve or null for unlimited + * + * @return array + */ + public function fetchTreeAsArray($rootId=null, $depth=null) + { + $config = $this->getConfiguration(); + $lftField = $config->getLeftFieldName(); + $rgtField = $config->getRightFieldName(); + $rootField = $config->getRootFieldName(); + $hasManyRoots = $config->hasManyRoots(); + + if($rootId === null && $rootField !== null) + { + throw new \InvalidArgumentException('Must provide root id'); + } + + if($depth === 0) + { + return array(); + } + + $qb = $config->getBaseQueryBuilder(); + $alias = $config->getQueryBuilderAlias(); + + $qb->andWhere("$alias.$lftField >= :lowerbound") + ->setParameter('lowerbound', 1) + ->orderBy("$alias.$lftField", "ASC"); + + if($hasManyRoots) + { + $qb->andWhere("$alias.$rootField = :rootid") + ->setParameter('rootid', $rootId); + } + + $nodes = $qb->getQuery()->execute(); + if(empty($nodes)) + { + return array(); + } + + // TODO: Filter depth using a cross join instead of this + if($depth !== null) + { + $nodes = $this->filterNodeDepth($nodes, $depth); + } + + $wrappers = array(); + foreach($nodes as $node) + { + $wrappers[] = $this->wrapNode($node); + } + + $this->buildTree($wrappers); + + return $wrappers; + } + + + /** + * Fetches a branch of a tree, returning the starting node of the branch. + * All children and descendants are pre-populated. + * + * @param mixed $pk the primary key used to locate the node to traverse + * the tree from + * @param int $depth the depth to retrieve or null for unlimited + * + * @return NodeWrapper $branch + */ + public function fetchBranch($pk, $depth=null) + { + $wrappers = $this->fetchBranchAsArray($pk, $depth); + + return (!is_array($wrappers) || empty($wrappers)) ? null : $wrappers[0]; + } + + + /** + * Fetches a branch of a tree, returning a flat array of node wrappers with + * parent, children, ancestors and descendants pre-populated. + * + * @param mixed $pk the primary key used to locate the node to traverse + * the tree from + * @param int $depth the depth to retrieve or null for unlimited + * + * @return array + */ + public function fetchBranchAsArray($pk, $depth=null) + { + $config = $this->getConfiguration(); + $lftField = $config->getLeftFieldName(); + $rgtField = $config->getRightFieldName(); + $rootField = $config->getRootFieldName(); + $hasManyRoots = $config->hasManyRoots(); + + if($depth === 0) + { + return array(); + } + + $node = $this->getEntityManager()->find($this->getConfiguration()->getClassname(), $pk); + + if(!$node) + { + return array(); + } + + // Detach the fetched node as Doctrine uses it instead of the node + // fetched as part of the tree. The node fetched as part of the tree + // may have additional joined classes via base query builder. + $this->getEntityManager()->detach($node); + + + $qb = $config->getBaseQueryBuilder(); + $alias = $config->getQueryBuilderAlias(); + + $qb->andWhere("$alias.$lftField >= :lowerbound") + ->setParameter('lowerbound', $node->getLeftValue()) + ->andWhere("$alias.$rgtField <= :upperbound") + ->setParameter('upperbound', $node->getRightValue()) + ->orderBy("$alias.$lftField", "ASC"); + + // TODO: Add support for depth via a cross join + + if($hasManyRoots) + { + $qb->andWhere("$alias.$rootField = :rootid") + ->setParameter('rootid', $node->getRootValue()); + } + + $nodes = $qb->getQuery()->execute(); + // @codeCoverageIgnoreStart + if(empty($nodes)) + { + return null; + } + // @codeCoverageIgnoreEnd + + // TODO: Filter depth using a cross join instead of this + if($depth !== null) + { + $nodes = $this->filterNodeDepth($nodes, $depth); + } + + $wrappers = array(); + foreach($nodes as $node) + { + $wrappers[] = $this->wrapNode($node); + } + + $this->buildTree($wrappers); + + return $wrappers; + } + + + /** + * Creates a new root node + * + * @param Node + * + * @return NodeWrapper + */ + public function createRoot(Node $node) + { + if($node instanceof NodeWrapper) + { + throw new \InvalidArgumentException('Can\'t create a root node from a NodeWrapper node'); + } + + $node->setLeftValue(1); + $node->setRightValue(2); + + if($this->getConfiguration()->hasManyRoots()) + { + $rootValue = $node->getId(); + if($rootValue === null) + { + // Set a temporary value in case wrapped node requires root value to be set + $node->setRootValue(0); + $this->getEntityManager()->persist($node); + $this->getEntityManager()->flush(); + $rootValue = $node->getId(); + } + + if($rootValue === null) + { + // @codeCoverageIgnoreStart + throw new \RuntimeException('Node must have an identifier available via getId()'); + // @codeCoverageIgnoreEnd + } + + $node->setRootValue($rootValue); + } + + + $this->getEntityManager()->persist($node); + $this->getEntityManager()->flush(); + + return $this->wrapNode($node); + } + + + /** + * wraps the node using the NodeWrapper class + * + * @param Node $node + * + * @return NodeWrapper + */ + public function wrapNode(Node $node) + { + if($node instanceof NodeWrapper) + { + throw new \InvalidArgumentException('Can\'t wrap a NodeWrapper node'); + } + + $oid = spl_object_hash($node); + if(!isset($this->wrappers[$oid])) + { + $this->wrappers[$oid] = new NodeWrapper($node, $this); + } + + return $this->wrappers[$oid]; + } + + + + + /** + * Returns the Doctrine entity manager associated with this Manager + * + * @return Doctrine\ORM\EntityManager + */ + public function getEntityManager() + { + return $this->getConfiguration()->getEntityManager(); + } + + + /** + * gets configuration + * + * @return Config + */ + public function getConfiguration() + { + return $this->config; + } + + + + // + // Methods marked internal should not be used outside of the + // NestedSet namespace + // + + + /** + * Internal + * Updates the left values of managed nodes + * + * @param int $first first left value to shift + * @param int $last last left value to shift, or 0 + * @param int $delta offset to shift by + * @param mixed $rootVal the root value of entities to act upon + * + */ + public function updateLeftValues($first, $last, $delta, $rootVal=null) + { + $hasManyRoots = $this->getConfiguration()->hasManyRoots(); + + foreach($this->wrappers as $wrapper) + { + if(!$hasManyRoots || ($wrapper->getRootValue() == $rootVal)) + { + if($wrapper->getLeftValue() >= $first && ($last === 0 || $wrapper->getLeftValue() <= $last)) + { + $wrapper->setLeftValue($wrapper->getLeftValue() + $delta); + $wrapper->invalidate(); + } + } + } + } + + + /** + * Internal + * Updates the right values of managed nodes + * + * @param int $first first right value to shift + * @param int $last last right value to shift, or 0 + * @param int $delta offset to shift by + * @param mixed $rootVal the root value of entities to act upon + * + */ + public function updateRightValues($first, $last, $delta, $rootVal=null) + { + $hasManyRoots = $this->getConfiguration()->hasManyRoots(); + + foreach($this->wrappers as $wrapper) + { + if(!$hasManyRoots || ($wrapper->getRootValue() == $rootVal)) + { + if($wrapper->getRightValue() >= $first && ($last === 0 || $wrapper->getRightValue() <= $last)) + { + $wrapper->setRightValue($wrapper->getRightValue() + $delta); + $wrapper->invalidate(); + } + } + } + } + + + /** + * Internal + * Updates the left, right and root values of managed nodes + * + * @param int $first lowerbound (lft/rgt) of nodes to update + * @param int $last upperbound (lft/rgt) of nodes to update, or 0 + * @param int $delta delta to add to lft/rgt values (can be negative) + * @param mixed $oldRoot the old root value of entities to act upon + * @param mixed $newRoot the new root value to set (or null to not change root) + */ + public function updateValues($first, $last, $delta, $oldRoot=null, $newRoot=null) + { + if(!$this->wrappers) + { + return; + } + + $hasManyRoots = $this->getConfiguration()->hasManyRoots(); + + foreach($this->wrappers as $wrapper) + { + if(!$hasManyRoots || ($wrapper->getRootValue() == $oldRoot)) + { + if($wrapper->getLeftValue() >= $first && ($last === 0 || $wrapper->getRightValue() <= $last)) + { + if($delta !== 0) + { + $wrapper->setLeftValue($wrapper->getLeftValue() + $delta); + $wrapper->setRightValue($wrapper->getRightValue() + $delta); + } + if($hasManyRoots && $newRoot !== null) + { + $wrapper->setRootValue($newRoot); + } + } + } + } + } + + + /** + * Internal + * Removes managed nodes + * + * @param int $left + * @param int $right + * @param mixed $root + */ + public function removeNodes($left, $right, $root=null) + { + $hasManyRoots = $this->getConfiguration()->hasManyRoots(); + + $removed = array(); + foreach($this->wrappers as $oid => $wrapper) + { + if(!$hasManyRoots || ($wrapper->getRootValue() == $root)) + { + if($wrapper->getLeftValue() >= $left && $wrapper->getRightValue() <= $right) + { + $removed[$oid] = $wrapper; + } + } + } + + foreach($removed as $key => $wrapper) + { + unset($this->wrappers[$key]); + $wrapper->setLeftValue(0); + $wrapper->setRightValue(0); + if($hasManyRoots) + { + $wrapper->setRootValue(0); + } + $this->getEntityManager()->detach($wrapper->getNode()); + } + } + + + /** + * Internal + * Filters an array of nodes by depth + * + * @param array array of Node instances + * @param int $depth the depth to filter to + * + * @return array array of Node instances + */ + public function filterNodeDepth($nodes, $depth) + { + if(empty($nodes) || $depth === 0) + { + return array(); + } + + $newNodes = array(); + $stack = array(); + $level = 0; + + foreach($nodes as $node) + { + $parent = end($stack); + while($parent && $node->getLeftValue() > $parent->getRightValue()) + { + array_pop($stack); + $parent = end($stack); + $level--; + } + + if($level < $depth) + { + $newNodes[] = $node; + } + + if(($node->getRightValue() - $node->getLeftValue()) > 1) + { + array_push($stack, $node); + $level++; + } + } + + return $newNodes; + } + + + + + protected function buildTree($wrappers) + { + // @codeCoverageIgnoreStart + if(empty($wrappers)) + { + return; + } + // @codeCoverageIgnoreEnd + + $config = $this->getConfiguration(); + + $rootNode = $wrappers[0]; + $stack = array(); + + $level = 0; + if($config->getHydrateLevel()) + { + $level = $wrappers[0]->getLevel(); + } + + $outlineNumbers = array(0); + if($config->getHydrateOutlineNumber()) + { + $outlineNumbers = explode('.', $wrappers[0]->getOutlineNumber('.', true)); + $outlineNumbers[count($outlineNumbers)-1]--; + } + + foreach($wrappers as $wrapper) + { + $parent = end($stack); + while($parent && $wrapper->getLeftValue() > $parent->getRightValue()) + { + array_pop($stack); + $parent = end($stack); + $level--; + array_pop($outlineNumbers); + } + + $outlineNumbers[count($outlineNumbers)-1]++; + + if($parent && $wrapper !== $rootNode) + { + $wrapper->internalSetParent($parent); + $parent->internalAddChild($wrapper); + $wrapper->internalSetAncestors($stack); + if($config->getHydrateLevel()) + { + $wrapper->internalSetLevel($level); + } + if($config->getHydrateOutlineNumber()) + { + $wrapper->internalSetOutlineNumbers($outlineNumbers); + } + foreach($stack as $anc) + { + $anc->internalAddDescendant($wrapper); + } + } + + if($wrapper->hasChildren()) + { + array_push($stack, $wrapper); + $level++; + array_push($outlineNumbers, 0); + } + } + } +} diff --git a/3rdparty/doctrine2-nestedset/lib/DoctrineExtensions/NestedSet/MultipleRootNode.php b/3rdparty/doctrine2-nestedset/lib/DoctrineExtensions/NestedSet/MultipleRootNode.php @@ -0,0 +1,47 @@ +<?php +/* + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * This software consists of voluntary contributions made by many individuals + * and is licensed under the LGPL. + */ + +namespace DoctrineExtensions\NestedSet; + + +/** + * Indicates a class supports hierarchical relations where each element has + * exactly 0 or 1 parent, multiple children and multiple root nodes. The + * position within this hierarchy is calculated using a NestedSet algorithm. + * + * For more information on NestedSets: + * http://dev.mysql.com/tech-resources/articles/hierarchical-data.html + * + * @author Brandon Turner <bturner@bltweb.net> + */ +interface MultipleRootNode extends Node +{ + /** + * gets Node's root value + * + * @return mixed + */ + public function getRootValue(); + + /** + * sets Node's root value + * + * @param mixed $root + */ + public function setRootValue($root); +} diff --git a/3rdparty/doctrine2-nestedset/lib/DoctrineExtensions/NestedSet/Node.php b/3rdparty/doctrine2-nestedset/lib/DoctrineExtensions/NestedSet/Node.php @@ -0,0 +1,76 @@ +<?php +/* + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * This software consists of voluntary contributions made by many individuals + * and is licensed under the LGPL. + */ + +namespace DoctrineExtensions\NestedSet; + + +/** + * Indicates a class supports hierarchical relations where each element has + * exactly 0 or 1 parent and multiple children. The position within this + * hierarchy is calculated using a NestedSet algorithm. + * + * For more information on NestedSets: + * http://dev.mysql.com/tech-resources/articles/hierarchical-data.html + * + * @author Brandon Turner <bturner@bltweb.net> + */ +interface Node +{ + + /** + * gets a unique identifier for this node + * + * @return mixed + */ + public function getId(); + + /** + * gets a string representation of the Node + * + * @return string + */ + public function __toString(); + + /** + * gets Node's left value + * + * @return int + */ + public function getLeftValue(); + + /** + * sets Node's left value + * + * @param int $lft + */ + public function setLeftValue($lft); + + /** + * gets Node's right value + * + * @return int + */ + public function getRightValue(); + + /** + * sets Node's right value + * + * @param int $rgt + */ + public function setRightValue($rgt); +} diff --git a/3rdparty/doctrine2-nestedset/lib/DoctrineExtensions/NestedSet/NodeWrapper.php b/3rdparty/doctrine2-nestedset/lib/DoctrineExtensions/NestedSet/NodeWrapper.php @@ -0,0 +1,1651 @@ +<?php +/* + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * This software consists of voluntary contributions made by many individuals + * and is licensed under the LGPL. + */ + +namespace DoctrineExtensions\NestedSet; + +/** + * Wraps a Doctrine entity providing methods for interacting with the tree. + * + * @author Brandon Turner <bturner@bltweb.net> + */ +class NodeWrapper implements Node +{ + + + /** + * @var Node The wrapped Node + **/ + private $node; + + + /** @var Manager */ + private $manager; + + + /** @var NodeWrapper */ + private $parent = null; + + /** @var array of NodeWrappers */ + private $ancestors = null; + + /** @var array of NodeWrappers */ + private $descendants = null; + + /** @var array of NodeWrappers */ + private $children = null; + + private $level = null; + + private $outlineNumbers = null; + + + + + public function __construct(Node $node, Manager $manager) + { + if($node instanceof NodeWrapper) + { + throw new \InvalidArgumentException('node must not be a NodeWrapper'); + } + + $this->node = $node; + $this->manager = $manager; + } + + + + + + + // + // Tree Query Methods + // + + + /** + * gets first child or null + * + * @return NodeWrapper + */ + public function getFirstChild() + { + if($this->isLeaf()) + { + return null; + } + + if($this->children !== null) + { + $ary = array_slice($this->children, 0, 1); + return $ary[0]; + } + + $qb = $this->getManager()->getConfiguration()->getBaseQueryBuilder(); + $alias = $this->getManager()->getConfiguration()->getQueryBuilderAlias(); + + $qb->andWhere("$alias.".$this->getLeftFieldName()." = :lft1") + ->setParameter('lft1', $this->getLeftValue() + 1); + if($this->hasManyRoots()) + { + $qb->andWhere("$alias.".$this->getRootFieldName()." = :root") + ->setParameter('root', $this->getRootValue()); + } + + $results = $qb->getQuery()->getResult(); + + return $this->getManager()->wrapNode($results[0]); + } + + + /** + * gets last child or null + * + * @return NodeWrapper + */ + public function getLastChild() + { + if($this->isLeaf()) + { + return null; + } + + if($this->children !== null) + { + $ary = array_slice($this->children, -1, 1); + return $ary[0]; + } + + $qb = $this->getManager()->getConfiguration()->getBaseQueryBuilder(); + $alias = $this->getManager()->getConfiguration()->getQueryBuilderAlias(); + + $qb->andWhere("$alias.".$this->getRightFieldName()." = :rgt1") + ->setParameter('rgt1', $this->getRightValue() - 1); + if($this->hasManyRoots()) + { + $qb->andWhere("$alias.".$this->getRootFieldName()." = :root") + ->setParameter('root', $this->getRootValue()); + } + + $results = $qb->getQuery()->getResult(); + + return $this->getManager()->wrapNode($results[0]); + } + + + /** + * gets children of this node (direct descendants only) + * + * @return array array of NodeWrapper objects + */ + public function getChildren() + { + if($this->children === null) + { + $this->children = $this->getDescendants(1); + } + + return $this->children; + } + + + /** + * gets descendants for this node + * + * @param int $depth or null for unlimited depth + * + * @return array array of NodeWrapper objects + */ + public function getDescendants($depth = null) + { + if($this->descendants === null) + { + $qb = $this->getManager()->getConfiguration()->getBaseQueryBuilder(); + $alias = $this->getManager()->getConfiguration()->getQueryBuilderAlias(); + + $qb->andWhere("$alias.".$this->getLeftFieldName()." > :lft1") + ->setParameter('lft1', $this->getLeftValue()) + ->andWhere("$alias.".$this->getRightFieldName()." < :rgt1") + ->setParameter('rgt1', $this->getRightValue()) + ->orderBy("$alias.".$this->getLeftFieldName(), "ASC"); + if($this->hasManyRoots()) + { + $qb->andWhere("$alias.".$this->getRootFieldName()." = :root") + ->setParameter('root', $this->getRootValue()); + } + + // TODO: Add depth support or self join support? + + $results = $qb->getQuery()->getResult(); + + $this->descendants = array(); + foreach($results as $result) + { + $this->descendants[] = $this->getManager()->wrapNode($result); + } + } + + if($depth !== null) + { + // TODO: Don't rebuild filtered array everytime? + return $this->getManager()->filterNodeDepth($this->descendants, $depth); + } + + return $this->descendants; + } + + + /** + * gets parent Node or null + * + * @return NodeWrapper + */ + public function getParent() + { + if($this->isRoot()) + { + return null; + } + + if($this->parent == null && $this->ancestors) + { + // Shortcut if we already loaded the ancestors + $this->parent = $this->ancestors[count($this->ancestors)-1]; + } + + if($this->parent == null) + { + $qb = $this->getManager()->getConfiguration()->getBaseQueryBuilder(); + $alias = $this->getManager()->getConfiguration()->getQueryBuilderAlias(); + + $qb->andWhere("$alias.".$this->getLeftFieldName()." < :lft1") + ->setParameter('lft1', $this->getLeftValue()) + ->andWhere("$alias.".$this->getRightFieldName()." > :rgt1") + ->setParameter('rgt1', $this->getRightValue()) + ->orderBy("$alias.".$this->getRightFieldName(), "ASC") + ->setMaxResults(1); + if($this->hasManyRoots()) + { + $qb->andWhere("$alias.".$this->getRootFieldName()." = :root") + ->setParameter('root', $this->getRootValue()); + } + + $results = $qb->getQuery()->getResult(); + $this->parent = $this->getManager()->wrapNode($results[0]); + } + + return $this->parent; + } + + + /** + * gets ancestors for node + * + * @return array array of NodeWrapper objects + */ + public function getAncestors() + { + if($this->isRoot()) + { + return array(); + } + + if($this->ancestors === null) + { + $qb = $this->getManager()->getConfiguration()->getBaseQueryBuilder(); + $alias = $this->getManager()->getConfiguration()->getQueryBuilderAlias(); + + $qb->andWhere("$alias.".$this->getLeftFieldName()." < :lft1") + ->setParameter('lft1', $this->getLeftValue()) + ->andWhere("$alias.".$this->getRightFieldName()." > :rgt1") + ->setParameter('rgt1', $this->getRightValue()) + ->orderBy("$alias.".$this->getLeftFieldName(), "ASC"); + if($this->hasManyRoots()) + { + $qb->andWhere("$alias.".$this->getRootFieldName()." = :root") + ->setParameter('root', $this->getRootValue()); + } + + $results = $qb->getQuery()->getResult(); + + $this->ancestors = array(); + foreach($results as $result) + { + $this->ancestors[] = $this->getManager()->wrapNode($result); + } + } + + return $this->ancestors; + } + + + /** + * gets the level of this node + * + * @return int + */ + public function getLevel() + { + if($this->level === null) + { + $this->level = count($this->getAncestors()); + } + + return $this->level; + } + + + /** + * gets path to node from root, uses Node::toString() method to get node + * names + * + * @param string $separator path separator + * @param bool $includeNode whether or not to include node at end of path + * + * @return string string representation of path + */ + public function getPath($separator=' > ', $includeNode=false) + { + $path = array(); + + $ancestors = $this->getAncestors(); + if($ancestors) + { + foreach($ancestors as $ancestor) + { + $path[] = $ancestor->getNode()->__toString(); + } + } + + if($includeNode) + { + $path[] = $this->getNode()->__toString(); + } + + return implode($separator, $path); + } + + + /** + * gets a number representing this node, e.g. 1.2.6 + * + * @param string $separator string to separate the numbers from each + * level (default '.') + * @param bool $includeRoot include the root node when numbering + * (default: true) + * + * @return string + */ + public function getOutlineNumber($separator='.', $includeRoot=true) + { + if($this->outlineNumbers === null) + { + $numbers = array(1); + + if(!$this->isRoot()) + { + $ancestors = $this->getAncestors(); + $root = $ancestors[0]; + + $rootDescendants = $root->getDescendants(count($ancestors)); + + $siblingNumber = 1; + $level = 1; + $pathLevel = 1; + $stack = array(); + foreach($rootDescendants as $wrapper) + { + $parent = end($stack); + while($parent && $wrapper->getLeftValue() > $parent->getRightValue()) + { + array_pop($stack); + $parent = end($stack); + $level--; + } + + if($wrapper->getLeftValue() <= $this->getLeftValue() && $wrapper->getRightValue() >= $this->getRightValue()) + { + // On path + + if($wrapper->isEqualTo($this)) + { + $numbers[] = $siblingNumber; + break; + } + else if($wrapper->isEqualTo($ancestors[$pathLevel])) + { + $numbers[] = $siblingNumber; + $siblingNumber = 1; + $pathLevel++; + } + } + else if($pathLevel == $level) + { + $siblingNumber++; + } + + if($wrapper->hasChildren()) + { + array_push($stack, $wrapper); + $level++; + } + } + } + + $this->outlineNumbers = $numbers; + } + + if($includeRoot === false) + { + return implode(array_slice($this->outlineNumbers, 1), $separator); + } + + return implode($this->outlineNumbers, $separator); + } + + + /** + * gets number of children (direct descendants) + * + * @return int + */ + public function getNumberChildren() + { + $children = $this->getChildren(); + return $children ? count($children) : 0; + } + + + /** + * gets number of descendants (children and their children ...) + * + * @return int + */ + public function getNumberDescendants() + { + return ($this->getRightValue() - $this->getLeftValue() - 1) / 2; + } + + + + + /** + * gets siblings for node + * + * @param bool $includeNode whether to include this node in the list of + * sibling nodes (default: false) + * + * @return array array of NodeWrapper objects + */ + public function getSiblings($includeNode=false) + { + $parent = $this->getParent(); + $siblings = array(); + if($parent && $parent->isValidNode()) + { + $children = $parent->getChildren(); + foreach($children as $child) + { + if(!$includeNode && $this->isEqualTo($child)) + { + continue; + } + $siblings[] = $child; + } + } + + return $siblings; + } + + + /** + * gets prev sibling or null + * + * @return Node + */ + public function getPrevSibling() + { + // If parent and children are already loaded, avoid database query + if($this->parent !== null && ($children = $this->parent->internalGetChildren()) !== null) + { + for($i=0; $i<count($children); $i++) + { + if($children[$i] == $this) + { + return ($i-1 < 0) ? null : $children[$i-1]; + } + } + // @codeCoverageIgnoreStart + } + // @codeCoverageIgnoreEnd + + + $qb = $this->getManager()->getConfiguration()->getBaseQueryBuilder(); + $alias = $this->getManager()->getConfiguration()->getQueryBuilderAlias(); + + $qb->andWhere("$alias.".$this->getRightFieldName()." = :rgt1") + ->setParameter('rgt1', $this->getLeftValue() - 1); + if($this->hasManyRoots()) + { + $qb->andWhere("$alias.".$this->getRootFieldName()." = :root") + ->setParameter('root', $this->getRootValue()); + } + + $results = $qb->getQuery()->getResult(); + + if(!$results) + { + return null; + } + + return $this->getManager()->wrapNode($results[0]); + } + + + /** + * gets next sibling or null + * + * @return Node + */ + public function getNextSibling() + { + // If parent and children are already loaded, avoid database query + if($this->parent !== null && ($children = $this->parent->internalGetChildren()) !== null) + { + for($i=0; $i<count($children); $i++) + { + if($children[$i] == $this) + { + return ($i+1 >= count($children)) ? null : $children[$i+1]; + } + } + // @codeCoverageIgnoreStart + } + // @codeCoverageIgnoreEnd + + + $qb = $this->getManager()->getConfiguration()->getBaseQueryBuilder(); + $alias = $this->getManager()->getConfiguration()->getQueryBuilderAlias(); + + $qb->andWhere("$alias.".$this->getLeftFieldName()." = :lft1") + ->setParameter('lft1', $this->getRightValue() + 1); + if($this->hasManyRoots()) + { + $qb->andWhere("$alias.".$this->getRootFieldName()." = :root") + ->setParameter('root', $this->getRootValue()); + } + + $results = $qb->getQuery()->getResult(); + + if(!$results) + { + return null; + } + + return $this->getManager()->wrapNode($results[0]); + } + + + /** + * test if node has previous sibling + * + * @return bool + */ + public function hasPrevSibling() + { + $n = $this->getPrevSibling(); + return $n && $n->isValidNode(); + } + + + /** + * test if node has next sibling + * + * @return bool + */ + public function hasNextSibling() + { + $n = $this->getNextSibling(); + return $n && $n->isValidNode(); + } + + + + + + + // + // Tree Modification Methods + // + + + /** + * inserts node as parent of given node + * + * @param NodeWrapper $node + */ + public function insertAsParentOf(NodeWrapper $node) + { + if($node == $this) + { + throw new \InvalidArgumentException('Cannot insert node as a parent of itself'); + } + + if($this->isValidNode()) + { + throw new \InvalidArgumentException('Cannot insert a node that already has a place within the tree'); + } + + if($node->isRoot()) + { + throw new \InvalidArgumentException('Cannot insert as parent of root'); + } + + $em = $this->getManager()->getEntityManager(); + $lftField = $this->getLeftFieldName(); + $rgtField = $this->getRightFieldName(); + + $newLft = $node->getLeftValue(); + $newRgt = $node->getRightValue() + 2; + $newRoot = $this->hasManyRoots() ? $node->getRootValue() : null; + + // beginTransaction + $em->getConnection()->beginTransaction(); + try + { + // Make space for new node + $this->shiftRLRange($newRgt - 1, 0, 2, $newRoot); + + // Slide child nodes over one and down one to allow new parent to wrap them + $qb = $em->createQueryBuilder() + ->update(get_class($this->getNode()), 'n') + ->set("n.$lftField", "n.$lftField + 1") + ->set("n.$rgtField", "n.$rgtField + 1") + ->where("n.$lftField >= ?1") + ->setParameter(1, $newLft) + ->andWhere("n.$rgtField <= ?2") + ->setParameter(2, $newRgt); + if($this->hasManyRoots()) + { + $qb->andWhere("n.".$this->getRootFieldName()." = ?3") + ->setParameter(3, $newRoot); + } + $qb->getQuery()->execute(); + $this->getManager()->updateValues($newLft, $newRgt, 1, $newRoot); + + $this->insertNode($newLft, $newRgt, $newRoot); + + $em->flush(); + $em->getConnection()->commit(); + } + catch (\Exception $e) + { + // @codeCoverageIgnoreStart + $em->close(); + $em->getConnection()->rollback(); + throw $e; + // @codeCoverageIgnoreEnd + } + // endTransaction + } + + + /** + * inserts node as previous sibling of given node + * + * @param NodeWrapper $node + */ + public function insertAsPrevSiblingOf(NodeWrapper $node) + { + if($node == $this) + { + throw new \InvalidArgumentException('Cannot insert node as a sibling of itself'); + } + + $em = $this->getManager()->getEntityManager(); + $newLeft = $node->getLeftValue(); + $newRight = $node->getLeftValue() + 1; + $newRoot = $this->hasManyRoots() ? $node->getRootValue() : null; + + // beginTransaction + $em->getConnection()->beginTransaction(); + try + { + $this->shiftRLRange($newLeft, 0, 2, $newRoot); + $this->insertNode($newLeft, $newRight, $newRoot); + + $em->flush(); + $em->getConnection()->commit(); + } + catch (\Exception $e) + { + // @codeCoverageIgnoreStart + $em->close(); + $em->getConnection()->rollback(); + throw $e; + // @codeCoverageIgnoreEnd + } + // endTransaction + } + + + /** + * inserts node as next sibling of given node + * + * @param NodeWrapper $node + */ + public function insertAsNextSiblingOf(NodeWrapper $node) + { + if($node == $this) + { + throw new \InvalidArgumentException('Cannot insert node as a sibling of itself'); + } + + $em = $this->getManager()->getEntityManager(); + $newLeft = $node->getRightValue() + 1; + $newRight = $node->getRightValue() + 2; + $newRoot = $this->hasManyRoots() ? $node->getRootValue() : null; + + // beginTransaction + $em->getConnection()->beginTransaction(); + try + { + $this->shiftRLRange($newLeft, 0, 2, $newRoot); + $this->insertNode($newLeft, $newRight, $newRoot); + + $em->flush(); + $em->getConnection()->commit(); + } + catch (\Exception $e) + { + // @codeCoverageIgnoreStart + $em->close(); + $em->getConnection()->rollback(); + throw $e; + // @codeCoverageIgnoreEnd + } + // endTransaction + } + + + /** + * inserts node as first child of given node + * + * @param NodeWrapper $node + */ + public function insertAsFirstChildOf(NodeWrapper $node) + { + if($node == $this) + { + throw new \InvalidArgumentException('Cannot insert node as a child of itself'); + } + + $em = $this->getManager()->getEntityManager(); + $newLeft = $node->getLeftValue() + 1; + $newRight = $node->getLeftValue() + 2; + $newRoot = $this->hasManyRoots() ? $node->getRootValue() : null; + + // beginTransaction + $em->getConnection()->beginTransaction(); + try + { + $this->shiftRLRange($newLeft, 0, 2, $newRoot); + $this->insertNode($newLeft, $newRight, $newRoot); + + $em->flush(); + $em->getConnection()->commit(); + } + catch (\Exception $e) + { + // @codeCoverageIgnoreStart + $em->close(); + $em->getConnection()->rollback(); + throw $e; + // @codeCoverageIgnoreEnd + } + // endTransaction + } + + + /** + * inserts node as last child of given node + * + * @param NodeWrapper $node + */ + public function insertAsLastChildOf(NodeWrapper $node) + { + if($node == $this) + { + throw new \InvalidArgumentException('Cannot insert node as a child of itself'); + } + + $em = $this->getManager()->getEntityManager(); + $newLeft = $node->getRightValue(); + $newRight = $node->getRightValue() + 1; + $newRoot = $this->hasManyRoots() ? $node->getRootValue() : null; + + // beginTransaction + $em->getConnection()->beginTransaction(); + try + { + $this->shiftRLRange($newLeft, 0, 2, $newRoot); + $this->insertNode($newLeft, $newRight, $newRoot); + + $em->flush(); + $em->getConnection()->commit(); + } + catch (\Exception $e) + { + // @codeCoverageIgnoreStart + $em->close(); + $em->getConnection()->rollback(); + throw $e; + // @codeCoverageIgnoreEnd + } + // endTransaction + } + + + /** + * moves node as previous sibling of the given node + * + * @param NodeWrapper $node + */ + public function moveAsPrevSiblingOf(NodeWrapper $node) + { + if($node == $this) + { + throw new \InvalidArgumentException('Cannot move node as a sibling of itself'); + } + + $em = $this->getManager()->getEntityManager(); + + // beginTransaction + $em->getConnection()->beginTransaction(); + try + { + if($this->hasManyRoots() && $this->getRootValue() !== $node->getRootValue()) + { + $this->moveBetweenTrees($node, $node->getLeftValue(), __FUNCTION__); + } + else + { + $this->updateNode($node->getLeftValue()); + } + + $em->flush(); + $em->getConnection()->commit(); + } + catch (\Exception $e) + { + // @codeCoverageIgnoreStart + $em->close(); + $em->getConnection()->rollback(); + throw $e; + // @codeCoverageIgnoreEnd + } + // endTransaction + } + + + /** + * moves node as next sibling of the given node + * + * @param NodeWrapper $node + */ + public function moveAsNextSiblingOf(NodeWrapper $node) + { + if($node == $this) + { + throw new \InvalidArgumentException('Cannot move node as a sibling of itself'); + } + + $em = $this->getManager()->getEntityManager(); + + // beginTransaction + $em->getConnection()->beginTransaction(); + try + { + if($this->hasManyRoots() && $this->getRootValue() !== $node->getRootValue()) + { + $this->moveBetweenTrees($node, $node->getRightValue() + 1, __FUNCTION__); + } + else + { + $this->updateNode($node->getRightValue() + 1); + } + + $em->flush(); + $em->getConnection()->commit(); + } + catch (\Exception $e) + { + // @codeCoverageIgnoreStart + $em->close(); + $em->getConnection()->rollback(); + throw $e; + // @codeCoverageIgnoreEnd + } + // endTransaction + } + + + /** + * moves node as first child of the given node + * + * @param NodeWrapper $node + */ + public function moveAsFirstChildOf(NodeWrapper $node) + { + if($node == $this) + { + throw new \InvalidArgumentException('Cannot move node as a child of itself'); + } + + $em = $this->getManager()->getEntityManager(); + + // beginTransaction + $em->getConnection()->beginTransaction(); + try + { + if($this->hasManyRoots() && $this->getRootValue() !== $node->getRootValue()) + { + $this->moveBetweenTrees($node, $node->getLeftValue() + 1, __FUNCTION__); + } + else + { + $this->updateNode($node->getLeftValue() + 1); + } + + $em->flush(); + $em->getConnection()->commit(); + } + catch (\Exception $e) + { + // @codeCoverageIgnoreStart + $em->close(); + $em->getConnection()->rollback(); + throw $e; + // @codeCoverageIgnoreEnd + } + // endTransaction + } + + + /** + * moves node as last child of the given node + * + * @param NodeWrapper $node + */ + public function moveAsLastChildOf(NodeWrapper $node) + { + if($node == $this) + { + throw new \InvalidArgumentException('Cannot move node as a child of itself'); + } + + $em = $this->getManager()->getEntityManager(); + + // beginTransaction + $em->getConnection()->beginTransaction(); + try + { + if($this->hasManyRoots() && $this->getRootValue() !== $node->getRootValue()) + { + $this->moveBetweenTrees($node, $node->getRightValue(), __FUNCTION__); + } + else + { + $this->updateNode($node->getRightValue()); + } + + $em->flush(); + $em->getConnection()->commit(); + } + catch (\Exception $e) + { + // @codeCoverageIgnoreStart + $em->close(); + $em->getConnection()->rollback(); + throw $e; + // @codeCoverageIgnoreEnd + } + // endTransaction + } + + + /** + * Makes this node a root node. + * + * @param mixed $newRoot + */ + public function makeRoot($newRoot) + { + if($this->isRoot()) + { + return; + } + + if(!$this->hasManyRoots()) + { + throw new \BadMethodCallException(sprintf('%s::%s is only supported on multiple root trees', __CLASS__, __METHOD__)); + } + + $em = $this->getManager()->getEntityManager(); + $lftField = $this->getLeftFieldName(); + $rgtField = $this->getRightFieldName(); + $rootField = $this->getRootFieldName(); + + $oldRgt = $this->getRightValue(); + $oldLft = $this->getLeftValue(); + $oldRoot = $this->getRootValue(); + + // beginTransaction + $em->getConnection()->beginTransaction(); + try + { + // Update descendants lft/rgt/root values + $diff = 1 - $oldLft; + $qb = $em->createQueryBuilder() + ->update(get_class($this->getNode()), 'n') + ->set("n.$lftField", "n.$lftField + ?1") + ->setParameter(1, $diff) + ->set("n.$rgtField", "n.$rgtField + ?2") + ->setParameter(2, $diff) + ->set("n.$rootField", "?3") + ->setParameter(3, $newRoot) + ->where("n.$lftField > ?4") + ->setParameter(4, $oldLft) + ->andWhere("n.$rgtField < ?5") + ->setParameter(5, $oldRgt) + ->andWhere("n.$rootField = ?6") + ->setParameter(6, $oldRoot); + $qb->getQuery()->execute(); + $this->getManager()->updateValues($oldLft, $oldRgt, $diff, $oldRoot, $newRoot); + + // Close gap in old tree + $first = $oldRgt + 1; + $delta = $oldLft - $oldRgt - 1; + $this->shiftRLRange($first, 0, $delta, $oldRoot); + + // Set new lft/rgt/root values for root node + $this->setLeftValue(1); + $this->setRightValue($oldRgt - $oldLft + 1); + $this->setRootValue($newRoot); + $em->persist($this->getNode()); + + $em->flush(); + $em->getConnection()->commit(); + } + catch (\Exception $e) + { + // @codeCoverageIgnoreStart + $em->close(); + $em->getConnection()->rollback(); + throw $e; + // @codeCoverageIgnoreEnd + } + // endTransaction + } + + + /** + * adds given node as the last child of this entity + * + * @param Node $node + * + * @return NodeWrapper $wrapper + */ + public function addChild(Node $node) + { + if($node instanceof NodeWrapper) + { + if($node == $this) + { + throw new \InvalidArgumentException('Cannot insert node as a child of itself'); + } + + $node->insertAsLastChildOf($this); + return $node; + } + + $node->setLeftValue($this->getRightValue()); + $node->setRightValue($this->getRightValue() + 1); + if($this->hasManyRoots()) + { + $node->setRootValue($this->getRootValue()); + } + + $em = $this->getManager()->getEntityManager(); + + // beginTransaction + $em->getConnection()->beginTransaction(); + try + { + $this->shiftRLRange($this->getRightValue(), 0, 2, ($this->hasManyRoots() ? $this->getRootValue() : null)); + + $em->persist($node); + + $em->flush(); + $em->getConnection()->commit(); + } + catch (\Exception $e) + { + // @codeCoverageIgnoreStart + $em->close(); + $em->getConnection()->rollback(); + throw $e; + // @codeCoverageIgnoreEnd + } + // endTransaction + + return $this->getManager()->wrapNode($node); + } + + + /** + * deletes this node and it's decendants + * + */ + public function delete() + { + $em = $this->getManager()->getEntityManager(); + $lftField = $this->getLeftFieldName(); + $rgtField = $this->getRightFieldName(); + + $oldLft = $this->getLeftValue(); + $oldRgt = $this->getRightValue(); + $oldRoot = $this->hasManyRoots() ? $this->getRootValue() : null; + + // beginTransaction + $em->getConnection()->beginTransaction(); + try + { + $qb = $em->createQueryBuilder() + ->delete(get_class($this->getNode()), 'n') + ->where("n.$lftField >= ?1") + ->setParameter(1, $oldLft) + ->andWhere("n.$rgtField <= ?2") + ->setParameter(2, $oldRgt); + if($this->hasManyRoots()) + { + $qb->andWhere("n.".$this->getRootFieldName()." = ?3") + ->setParameter(3, $oldRoot); + } + $qb->getQuery()->execute(); + $this->getManager()->removeNodes($oldLft, $oldRgt, $oldRoot); + + // Close gap in tree + $first = $oldRgt + 1; + $delta = $oldLft - $oldRgt - 1; + $this->shiftRLRange($first, 0, $delta, $oldRoot); + + $em->flush(); + $em->getConnection()->commit(); + } + catch (\Exception $e) + { + // @codeCoverageIgnoreStart + $em->close(); + $em->getConnection()->rollback(); + throw $e; + // @codeCoverageIgnoreEnd + } + // endTransaction + } + + + /** + * sets node's left and right values and persist's it + * + * NOTE: This method does not wrap its database queries in a transaction. + * This should be done before invoking this code. + * + * @param int $destLeft + * @param int $destRight + * @param mixed $destRoot + * + */ + protected function insertNode($destLeft, $destRight, $destRoot=null) + { + $this->setLeftValue($destLeft); + $this->setRightValue($destRight); + if($this->hasManyRoots()) + { + $this->setRootValue($destRoot); + } + $this->getManager()->getEntityManager()->persist($this->getNode()); + } + + + /** + * moves this node and its children to location $destLeft and updates the + * rest of the tree + * + * NOTE: This method does not wrap its database queries in a transaction. + * This should be done before invoking this code. + * + * @param int $destLeft + */ + protected function updateNode($destLeft) + { + $left = $this->getLeftValue(); + $right = $this->getRightValue(); + $root = $this->hasManyRoots() ? $this->getRootValue() : null; + + $treeSize = $right - $left + 1; + + // Make room in the new branch + $this->shiftRLRange($destLeft, 0, $treeSize, $root); + + if($left >= $destLeft) + { + // src was shifted too + $left += $treeSize; + $right += $treeSize; + } + + // Now there's enough room next to target to move the subtree + $this->shiftRLRange($left, $right, $destLeft - $left, $root); + + // Correct values after source (close gap in old tree) + $this->shiftRLRange($right + 1, 0, -$treeSize, $root); + } + + + /** + * adds '$delta' to all Left and Right values that are >= '$first' and + * <= '$last'. If $last is 0, it is ignored and no upper bound exists. + * '$delta' can also be negative. + * + * NOTE: This method does not wrap its database queries in a transaction. + * This should be done before invoking this code. + * + * @param int $first first left/right value to shift + * @param int $last last left/right value to shift, or 0 + * @param int $delta offset to shift by + * @param mixed $rootVal the root value of entities to act upon + */ + protected function shiftRLRange($first, $last, $delta, $rootVal) + { + $em = $this->getManager()->getEntityManager(); + + foreach(array($this->getLeftFieldName(), $this->getRightFieldName()) as $field) + { + // Prepare left query + $q = $em->createQueryBuilder() + ->update(get_class($this->getNode()), 'n') + ->set("n.$field", "n.$field + :delta") + ->setParameter('delta', $delta) + ->where("n.$field >= :lowerbound") + ->setParameter('lowerbound', $first); + + if($last > 0) + { + $q->andWhere("n.$field <= :upperbound") + ->setParameter('upperbound', $last); + } + + if($this->hasManyRoots()) + { + $q->andWhere("n.".$this->getRootFieldName()." = :root") + ->setParameter('root', $rootVal); + } + + $q->getQuery()->execute(); + } + + $this->getManager()->updateLeftValues($first, $last, $delta, $rootVal); + $this->getManager()->updateRightValues($first, $last, $delta, $rootVal); + } + + + /** + * Accomplishes moving of nodes between different trees. + * Used by the move* methods if the root values of the two nodes are + * different. + * + * NOTE: This method does not wrap its database queries in a transaction. + * This should be done before invoking this code. + * + * @param NodeWrapper $node + * @param int $newLeftValue + * @param string $moveType + */ + protected function moveBetweenTrees(NodeWrapper $node, $newLeftValue, $moveType) + { + if(!$this->hasManyRoots()) + { + // @codeCoverageIgnoreStart + throw new \BadMethodCallException(sprintf('%s::%s is only supported on multiple root trees', __CLASS__, __METHOD__)); + // @codeCoverageIgnoreEnd + } + + $em = $this->getManager()->getEntityManager(); + $lftField = $this->getLeftFieldName(); + $rgtField = $this->getRightFieldName(); + $rootField = $this->getRootFieldName(); + + $newRoot = $node->getRootValue(); + $oldRoot = $this->getRootValue(); + $oldLft = $this->getLeftValue(); + $oldRgt = $this->getRightValue(); + + // Prepare target tree for insertion, make room + $this->shiftRLRange($newLeftValue, 0, $oldRgt - $oldLft + 1, $newRoot); + + // Set new position and root of this node + $this->setLeftValue($newLeftValue); + $this->setRightValue($newLeftValue + ($oldRgt - $oldLft)); + $this->setRootValue($newRoot); + $em->persist($this->getNode()); + $em->flush(); + + // Relocate descendants of the node + $diff = $this->getLeftValue() - $oldLft; + //echo "\nRelocating: oldLft=$oldLft, oldRgt=$oldRgt, diff=$diff, oldRoot=$oldRoot, newRoot=$newRoot\n"; + $qb = $em->createQueryBuilder() + ->update(get_class($this->getNode()), 'n') + ->set("n.$lftField", "n.$lftField + ?1") + ->setParameter(1, $diff) + ->set("n.$rgtField", "n.$rgtField + ?2") + ->setParameter(2, $diff) + ->set("n.$rootField", "?3") + ->setParameter(3, $newRoot) + ->where("n.$lftField > ?4") + ->setParameter(4, $oldLft) + ->andWhere("n.$rgtField < ?5") + ->setParameter(5, $oldRgt) + ->andWhere("n.$rootField = ?6") + ->setParameter(6, $oldRoot); + $qb->getQuery()->execute(); + $this->getManager()->updateValues($oldLft, $oldRgt, $diff, $oldRoot, $newRoot); + + // Close gap in old tree + $first = $oldRgt + 1; + $delta = $oldLft - $oldRgt - 1; + $this->shiftRLRange($first, 0, $delta, $oldRoot); + } + + + + + + + + // + // State Methods + // These methods require no datbase calls + // + + + /** + * Returns the wrapped node + * + * @return Node + */ + public function getNode() + { + return $this->node; + } + + + /** + * test if node has children + * + * @return bool + */ + public function hasChildren() + { + return (($this->getRightValue() - $this->getLeftValue()) > 1); + } + + + /** + * test if node has parent + * + * @return bool + */ + public function hasParent() + { + return $this->isValidNode() && !$this->isRoot(); + } + + + /** + * determines if node is root + * + * @return bool + */ + public function isRoot() + { + return ($this->getLeftValue() == 1); + } + + + /** + * determines if node is leaf + * + * @return bool + */ + public function isLeaf() + { + return (($this->getRightValue() - $this->getLeftValue()) == 1); + } + + + /** + * determines if node is valid + * + * @return bool + */ + public function isValidNode() + { + return ($this->getNode() && ($this->getRightValue() > $this->getLeftValue())); + } + + + /** + * Removes all cached ancestor/descendant entites + * + */ + public function invalidate() + { + $this->parent = null; + $this->ancestors = null; + $this->descendants = null; + $this->children = null; + $this->level = null; + $this->outlineNumbers = null; + } + + + /** + * determines if this node is a child of the given node + * + * @param Node + * + * @return bool + */ + public function isDescendantOf(Node $node) + { + return (($this->getLeftValue() > $node->getLeftValue()) && + ($this->getRightValue() < $node->getRightValue()) && + (!$this->hasManyRoots() || ($this->getRootValue() == $node->getRootValue()))); + } + + + /** + * determines if this node is an ancestor of the given node + * + * @param Node + * + * @return bool + */ + public function isAncestorOf(Node $node) + { + return (($this->getLeftValue() < $node->getLeftValue()) && + ($this->getRightValue() > $node->getRightValue()) && + (!$this->hasManyRoots() || ($this->getRootValue() == $node->getRootValue()))); + } + + + /** + * determines if this node is equal to the given node + * + * @param Node + * + * @return bool + */ + public function isEqualTo(Node $node) + { + return (($this->getLeftValue() == $node->getLeftValue()) && + ($this->getRightValue() == $node->getRightValue()) && + (!$this->hasManyRoots() || ($this->getRootValue() == $node->getRootValue()))); + } + + + + + + + + + /** + * Returns the NestedSet Manager + * + * @return Manager + */ + public function getManager() + { + return $this->manager; + } + + + protected function getLeftFieldName() + { + return $this->getManager()->getConfiguration()->getLeftFieldName(); + } + + + protected function getRightFieldName() + { + return $this->getManager()->getConfiguration()->getRightFieldName(); + } + + + protected function getRootFieldName() + { + return $this->getManager()->getConfiguration()->getRootFieldName(); + } + + + protected function hasManyRoots() + { + return $this->getManager()->getConfiguration()->hasManyRoots(); + } + + + + + + + + + // + // Internal methods + // Use of these methods outside the NestedSet package is not recommended + // + + /** + * INTERNAL + */ + public function internalSetParent(NodeWrapper $parent) + { + $this->parent = $parent; + } + + + /** + * INTERNAL + */ + public function internalAddChild(NodeWrapper $child) + { + if($this->children === null) + { + $this->children = array(); + } + + $this->children[] = $child; + } + + + /** + * INTERNAL + */ + public function internalAddDescendant(NodeWrapper $descendant) + { + if($this->descendants === null) + { + $this->descendants = array(); + } + + $this->descendants[] = $descendant; + } + + + /** + * INTERNAL + */ + public function internalSetAncestors(array $ancestors) + { + $this->ancestors = $ancestors; + } + + + /** + * INTERNAL + */ + public function internalGetChildren() + { + return $this->children; + } + + + /** + * INTERNAL + */ + public function internalSetLevel($level) + { + $this->level = $level; + } + + + /** + * INTERNAL + */ + public function internalSetOutlineNumbers($numbers) + { + $this->outlineNumbers = $numbers; + } + + + + // + // Node Interface Methods + // + + public function getId() + { + return $this->getNode()->getId(); + } + + public function __toString() + { + return $this->getNode()->__toString(); + } + + public function getLeftValue() + { + return $this->getNode()->getLeftValue(); + } + + public function setLeftValue($lft) + { + return $this->getNode()->setLeftValue($lft); + } + + public function getRightValue() + { + return $this->getNode()->getRightValue(); + } + + public function setRightValue($rgt) + { + return $this->getNode()->setRightValue($rgt); + } + + public function getRootValue() + { + return $this->getNode()->getRootValue(); + } + + public function setRootValue($root) + { + return $this->getNode()->setRootValue($root); + } +} diff --git a/3rdparty/doctrine2-nestedset/tests/.gitignore b/3rdparty/doctrine2-nestedset/tests/.gitignore @@ -0,0 +1 @@ +cov/ diff --git a/3rdparty/doctrine2-nestedset/tests/DoctrineExtensions/NestedSet/Tests/ConfigTest.php b/3rdparty/doctrine2-nestedset/tests/DoctrineExtensions/NestedSet/Tests/ConfigTest.php @@ -0,0 +1,238 @@ +<?php +/* + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * This software consists of voluntary contributions made by many individuals + * and is licensed under the LGPL. + */ + +namespace DoctrineExtensions\NestedSet\Tests; + +use DoctrineExtensions\NestedSet\Config; + + +class ConfigTest extends DatabaseTest +{ + + protected + $config; + + public function setUp() + { + $this->config = new Config($this->getEntityManager()); + } + + /** + * @covers DoctrineExtensions\NestedSet\Config::__construct + */ + public function testConstructor() + { + $this->assertInstanceOf('DoctrineExtensions\NestedSet\Config', $this->config, '->__construct() works with default parameters'); + + $clazz = 'DoctrineExtensions\NestedSet\Tests\Mocks\NodeMock'; + $this->assertInstanceOf('DoctrineExtensions\NestedSet\Config', new Config($this->getEntityManager(), $clazz), '->construct() works with a classname'); + + $metadata = $this->getEntityManager()->getClassMetadata($clazz); + $this->assertInstanceOf('DoctrineExtensions\NestedSet\Config', new Config($this->getEntityManager(), $metadata), '->construct() works with metadata'); + + $this->assertEquals('lft', $this->config->getLeftFieldName(), '->__construct() sets default left field name'); + $this->assertEquals('rgt', $this->config->getRightFieldName(), '->__construct() sets default right field name'); + $this->assertEquals('root', $this->config->getRootFieldName(), '->__construct() sets default root field name'); + $this->assertFalse($this->config->hasManyRoots(), '->__construct sets default hasManyRoots'); + $this->assertTrue($this->config->getHydrateLevel(), '->__construct() sets hydrate level to true'); + $this->assertTrue($this->config->getHydrateOutlineNumber(), '->__construct() sets default hydrate outline number'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\Config::setClass + * @expectedException InvalidArgumentException + */ + public function testSetUnknownClass() + { + $this->config->setClass('BogusClass'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\Config::setClass + * @expectedException Doctrine\ORM\Mapping\MappingException + */ + public function testSetNonEntityClass() + { + $this->config->setClass('DoctrineExtensions\NestedSet\Tests\ConfigTest'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\Config::setClass + * @expectedException InvalidArgumentException + */ + public function testSetNonNodeClass() + { + $this->config->setClass('DoctrineExtensions\NestedSet\Tests\Mocks\NonNodeMock'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\Config::setClass + * @covers DoctrineExtensions\NestedSet\Config::getClassname + * @covers DoctrineExtensions\NestedSet\Config::getClassMetadata + */ + public function testSetClass() + { + $clazz = 'DoctrineExtensions\NestedSet\Tests\Mocks\NodeMock'; + $metadata = $this->getEntityManager()->getClassMetadata($clazz); + $this->config->setClass($metadata); + $this->assertEquals($clazz, $this->config->getClassname(), '->setClass() accepts a metadata object'); + $this->assertSame($metadata, $this->config->getClassMetadata(), '->getClassMetadata() works'); + + $this->assertSame($this->config, $this->config->setClass($clazz), '->setClass() returns $this for fluent API'); + $this->assertEquals($clazz, $this->config->getClassname(), '->getClassname() works'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\Config::getEntityManager + */ + public function testGetEntityManager() + { + $this->assertInstanceOf('Doctrine\ORM\EntityManager', $this->config->getEntityManager(), '->getEntityManager() returns EntityManager'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\Config::getLeftFieldName + * @covers DoctrineExtensions\NestedSet\Config::setLeftFieldName + */ + public function testSetLeftFieldName() + { + $this->assertSame($this->config, $this->config->setLeftFieldName('foo'), '->setLeftFieldName() returns $this for fluent API'); + $this->assertEquals('foo', $this->config->getLeftFieldName(), '->getLeftFieldName() works'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\Config::getRightFieldName + * @covers DoctrineExtensions\NestedSet\Config::setRightFieldName + */ + public function testSetRightFieldName() + { + $this->assertSame($this->config, $this->config->setRightFieldName('foo'), '->setRightFieldName() returns $this for fluent API'); + $this->assertEquals('foo', $this->config->getRightFieldName(), '->getRightFieldName() works'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\Config::getRootFieldName + * @covers DoctrineExtensions\NestedSet\Config::setRootFieldName + */ + public function testSetRootFieldName() + { + $this->assertSame($this->config, $this->config->setRootFieldName('foo'), '->setRootFieldName() returns $this for fluent API'); + $this->assertEquals('foo', $this->config->getRootFieldName(), '->getRootFieldName() works'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\Config::hasManyRoots + */ + public function testIsSingleRoot() + { + $this->config->setClass('DoctrineExtensions\NestedSet\Tests\Mocks\NodeMock'); + $this->assertTrue($this->config->hasManyRoots(), '->hasManyRoots() returns true for MutlipleRootNode node'); + + $this->config->setClass('DoctrineExtensions\NestedSet\Tests\Mocks\SingleRootNodeMock'); + $this->assertFalse($this->config->hasManyRoots(), '->hasManyRoots() returns false for Node node'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\Config::getBaseQueryBuilder + * @covers DoctrineExtensions\NestedSet\Config::setBaseQueryBuilder + */ + public function testSetBaseQueryBuilder() + { + $defaultQb = $this->config->getDefaultQueryBuilder(); + $this->assertEquals($defaultQb, $this->config->getBaseQueryBuilder(), '->getBaseQueryBuilder() returns default QueryBuilder if none is set'); + + $qb = $this->getEntityManager()->createQueryBuilder() + ->select('y') + ->from('DoctrineExtensions\NestedSet\Tests\Mocks\NodeMock', 'y'); + $this->config->setBaseQueryBuilder($qb); + $this->assertEquals($qb, $this->config->getBaseQueryBuilder(), '->setBaseQueryBuilder() sets a QueryBuilder object'); + $this->assertNotSame($qb, $this->config->getBaseQueryBuilder(), '->getBaseQueryBuilder() returns a clone of the base query builder object'); + + $this->config->setBaseQueryBuilder(); + $this->assertEquals($defaultQb, $this->config->getBaseQueryBuilder(), '->setBaseQueryBuilder() reverts to default QueryBuilder when nothing is set'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\Config::resetBaseQueryBuilder + * @covers DoctrineExtensions\NestedSet\Config::setBaseQueryBuilder + */ + public function testResetBaseQueryBuilder() + { + $defaultQb = $this->config->getDefaultQueryBuilder(); + + $qb = $this->getEntityManager()->createQueryBuilder() + ->select('y') + ->from('DoctrineExtensions\NestedSet\Tests\Mocks\NodeMock', 'y'); + $this->config->setBaseQueryBuilder($qb); + + $this->config->resetBaseQueryBuilder(); + $this->assertEquals($defaultQb, $this->config->getBaseQueryBuilder(), '->resetBaseQueryBuilder() reverts to default QueryBuilder'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\Config::getDefaultQueryBuilder + */ + public function testGetDefaultQueryBuilder() + { + $qb = $this->config->getDefaultQueryBuilder(); + $this->assertInstanceOf('Doctrine\ORM\QueryBuilder', $qb, '->getDefaultQueryBuilder() returns QueryBuilder object'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\Config::getQueryBuilderAlias + */ + public function testGetQueryBuilderAlias() + { + $this->assertEquals('n', $this->config->getQueryBuilderAlias()); + } + + + /** + * @covers DoctrineExtensions\NestedSet\Config::getHydrateLevel + * @covers DoctrineExtensions\NestedSet\Config::setHydrateLevel + */ + public function testSetHydrateLevel() + { + $this->assertSame($this->config, $this->config->setHydrateLevel(false), '->setHydrateLevel() returns $this for fluent API'); + $this->assertFalse($this->config->getHydrateLevel(), '->getHydrateLevel() works'); + } + + + /* + * @covers DoctrineExtensions\NestedSet\Config::getHydrateOutlineNumber + * @covers DoctrineExtensions\NestedSet\Config::setHydrateOutlineNumber + */ + public function testSetHydrateOutlineNumber() + { + $this->assertSame($this->config, $this->config->setHydrateOutlineNumber(false), '->setHydrateOutlineNumber() returns $this for fluent API'); + $this->assertFalse($this->config->getHydrateOutlineNumber(), '->getHydrateOutlineNumber() works'); + } +} diff --git a/3rdparty/doctrine2-nestedset/tests/DoctrineExtensions/NestedSet/Tests/DatabaseTest.php b/3rdparty/doctrine2-nestedset/tests/DoctrineExtensions/NestedSet/Tests/DatabaseTest.php @@ -0,0 +1,77 @@ +<?php +/* + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * This software consists of voluntary contributions made by many individuals + * and is licensed under the LGPL. + */ + +namespace DoctrineExtensions\NestedSet\Tests; + +abstract class DatabaseTest extends \PHPUnit_Framework_TestCase +{ + + private + $em; + + /** + * gets the entity manager to use for all tests + * + * @return Doctrine\ORM\EntityManager + */ + protected function getEntityManager() + { + if(!$this->em) + { + $this->em = $this->_createEntityManager(); + } + + return $this->em; + } + + /** + * Creates an entity manager to use for all tests + * + * @return Doctrine\ORM\EntityManager + */ + protected function _createEntityManager() + { + $conn = \Doctrine\DBAL\DriverManager::getConnection(array( + 'driver' => 'pdo_sqlite', + 'memory' => true + )); + + $config = new \Doctrine\ORM\Configuration(); + $config->setProxyDir(__DIR__ . '/../Proxies'); + $config->setProxyNamespace('DoctrineExtensions\NestedSet\Tests\Proxies'); + $config->setMetadataDriverImpl(\Doctrine\ORM\Mapping\Driver\AnnotationDriver::create()); + + return \Doctrine\ORM\EntityManager::create($conn, $config); + } + + + /** + * Loads the schema for the given classes + * + * @param array $classes + * + */ + protected function loadSchema($classes) + { + $schemaTool = new \Doctrine\ORM\Tools\SchemaTool($this->getEntityManager()); + $schemaTool->createSchema($classes); + } + + + +} diff --git a/3rdparty/doctrine2-nestedset/tests/DoctrineExtensions/NestedSet/Tests/ManagerTest.php b/3rdparty/doctrine2-nestedset/tests/DoctrineExtensions/NestedSet/Tests/ManagerTest.php @@ -0,0 +1,475 @@ +<?php +/* + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * This software consists of voluntary contributions made by many individuals + * and is licensed under the LGPL. + */ + +namespace DoctrineExtensions\NestedSet\Tests; + +use DoctrineExtensions\NestedSet\Tests\Mocks\NodeMock; +use DoctrineExtensions\NestedSet\Tests\Mocks\ManagerMock; +use DoctrineExtensions\NestedSet\NodeWrapper; + +class ManagerTest extends DatabaseTest +{ + + protected + $nsm, + $nodes, + $nodes2; + + + public function setUp() + { + $this->nsm = new ManagerMock($this->getEntityManager(), 'DoctrineExtensions\NestedSet\Tests\Mocks\NodeMock'); + } + + public function setUpDb($em=null) + { + if($em === null) + { + $em = $this->getEntityManager(); + } + + $this->loadSchema(array($em->getClassMetadata('DoctrineExtensions\NestedSet\Tests\Mocks\NodeMock'))); + } + + protected function loadData() + { + $this->setUpDb(); + $em = $this->getEntityManager(); + + $this->nodes = array( + new NodeMock(1, '1', 1, 10), # 0 + new NodeMock(2, '1.1', 2, 7), # 1 + new NodeMock(3, '1.1.1', 3, 4), # 2 + new NodeMock(4, '1.1.2', 5, 6), # 3 + new NodeMock(5, '1.2', 8, 9), # 4 + ); + + $this->nodes2 = array( + new NodeMock(11, '1', 1, 12, 2), # 0 + new NodeMock(12, '1.1', 2, 7, 2), # 1 + new NodeMock(13, '1.1.1', 3, 4, 2), # 2 + new NodeMock(14, '1.1.2', 5, 6, 2), # 3 + new NodeMock(15, '1.2', 8, 9, 2), # 4 + new NodeMock(16, '1.3', 10, 11, 2), # 5 + ); + + + foreach($this->nodes as $node) + { + $em->persist($node); + } + + $this->wrappers2 = array(); + foreach($this->nodes2 as $node) + { + $em->persist($node); + } + + $em->flush(); + } + + + /** + * @covers DoctrineExtensions\NestedSet\Manager::__construct + */ + public function testConstructor() + { + $this->assertInstanceOf('DoctrineExtensions\NestedSet\Manager', $this->nsm); + } + + + /** + * @covers DoctrineExtensions\NestedSet\Manager::fetchTree + * @covers DoctrineExtensions\NestedSet\Manager::fetchTreeAsArray + * @expectedException InvalidArgumentException + */ + public function testFetchTreeNoRootId() + { + $this->nsm->fetchTree(); + } + + /** + * @covers DoctrineExtensions\NestedSet\Manager::fetchTree + * @covers DoctrineExtensions\NestedSet\Manager::fetchTreeAsArray + * @covers DoctrineExtensions\NestedSet\Manager::buildTree + * @covers DoctrineExtensions\NestedSet\NodeWrapper::internalSetParent + * @covers DoctrineExtensions\NestedSet\NodeWrapper::internalSetAncestors + * @covers DoctrineExtensions\NestedSet\NodeWrapper::internalAddDescendant + * @covers DoctrineExtensions\NestedSet\NodeWrapper::internalAddChild + * @covers DoctrineExtensions\NestedSet\NodeWrapper::internalSetLevel + * @covers DoctrineExtensions\NestedSet\NodeWrapper::internalSetOutlineNumbers + */ + public function testFetchTree() + { + $this->loadData(); + $nodes = $this->nodes; + + $this->assertNull($this->nsm->fetchTree(10), '->fetchTree() returns null when no nodes exist'); + + $root = $this->nsm->fetchTree(1); + $this->assertInstanceOf('DoctrineExtensions\NestedSet\NodeWrapper', $root, '->fetchTree() returns a NodeWrapper object'); + + // + // NOTE: Testing private variables + // + + $root_parent = $this->readAttribute($root, 'parent'); + $root_children = $this->readAttribute($root, 'children'); + $root_ancestors = $this->readAttribute($root, 'ancestors'); + $root_descendants = $this->readAttribute($root, 'descendants'); + + $this->assertEquals($nodes[0]->getId(), $root->getId(), '->fetchTree() root id is correct'); + $this->assertAttributeEquals(0, 'level', $root, '->fetchTree() root level is correct'); + $this->assertAttributeEquals(array(1), 'outlineNumbers', $root, '->fetchTree() root outlineNumbers is correct'); + $this->assertEmpty($root_ancestors, '->fetchTree() root ancestors is empty'); + $this->assertNull($root_parent, '->fetchTree() root parent is null'); + $this->assertEquals( + array($nodes[1]->getId(), $nodes[4]->getId()), + array_map(function($e) {return $e->getNode()->getId();}, $root_children), + '->fetchTree() root children populated' + ); + $this->assertEquals( + array($nodes[1]->getId(), $nodes[2]->getId(), $nodes[3]->getId(), $nodes[4]->getId()), + array_map(function($e) {return $e->getNode()->getId();}, $root_descendants), + '->fetchTree() root descendants populated' + ); + $this->assertAttributeEquals(1, 'level', $root_children[0], '->fetchTree() root children level is correct'); + $this->assertAttributeEquals(array(1,1), 'outlineNumbers', $root_children[0], '->fetchTree() root children outlineNumbers is correct'); + + + $node1_parent = $this->readAttribute($root_children[0], 'parent'); + $node1_children = $this->readAttribute($root_children[0], 'children'); + $node1_ancestors = $this->readAttribute($root_children[0], 'ancestors'); + $node1_descendants = $this->readAttribute($root_children[0], 'descendants'); + + $this->assertEquals($nodes[0]->getId(), $node1_parent->getNode()->getId(), '->fetchTree() first child parent is correct'); + $this->assertEquals( + array($nodes[0]->getId()), + array_map(function($e) {return $e->getNode()->getId();}, $node1_ancestors), + '->fetchTree() first child ancestors is correct' + ); + $this->assertEquals( + array($nodes[2]->getId(), $nodes[3]->getId()), + array_map(function($e) {return $e->getNode()->getId();}, $node1_children), + '->fetchTree() first child children populated' + ); + $this->assertEquals( + array($nodes[2]->getId(), $nodes[3]->getId()), + array_map(function($e) {return $e->getNode()->getId();}, $node1_descendants), + '->fetchTree() first child descendants populated' + ); + $this->assertAttributeEquals(2, 'level', $node1_children[0], '->fetchTree() node 1 children level is correct'); + $this->assertAttributeEquals(array(1,1,2), 'outlineNumbers', $node1_children[1], '->fetchTree() node 1 children outlineNumbers is correct'); + + + $node3_parent = $this->readAttribute($root_descendants[2], 'parent'); + $node3_children = $this->readAttribute($root_descendants[2], 'children'); + $node3_ancestors = $this->readAttribute($root_descendants[2], 'ancestors'); + $node3_descendants = $this->readAttribute($root_descendants[2], 'descendants'); + $this->assertEquals($nodes[1]->getId(), $node3_parent->getNode()->getId(), '->fetchTree() leaf parent is correct'); + $this->assertEquals( + array($nodes[0]->getId(), $nodes[1]->getId()), + array_map(function($e) {return $e->getNode()->getId();}, $node3_ancestors), + '->fetchTree() leaf ancestors is correct' + ); + $this->assertEmpty($node3_children, '->fetchTree() leaf children is empty'); + $this->assertEmpty($node3_descendants, '->fetchTree() leaf descendants is empty'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\Manager::fetchTree + * @covers DoctrineExtensions\NestedSet\Manager::fetchTreeAsArray + * @covers DoctrineExtensions\NestedSet\Manager::buildTree + * @covers DoctrineExtensions\NestedSet\Manager::filterNodeDepth + */ + public function testFetchTreeDepth() + { + $this->loadData(); + $nodes = $this->nodes; + + $this->assertNull($this->nsm->fetchTree(1, 0)); + + $root = $this->nsm->fetchTree(1, 2); + $this->assertInstanceOf('DoctrineExtensions\NestedSet\NodeWrapper', $root, '->fetchTree() returns a NodeWrapper object'); + + // + // NOTE: Testing private variables + // + $node1_children = $this->readAttribute($root->getFirstChild(), 'children'); + $node1_descendants = $this->readAttribute($root->getFirstChild(), 'descendants'); + $this->assertEmpty($node1_children, '->fetchTree() empty children with depth filtered'); + $this->assertEmpty($node1_descendants, '->fetchTree() empty descendants with depth filtered'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\Manager::fetchBranch + * @covers DoctrineExtensions\NestedSet\Manager::fetchBranchAsArray + * @covers DoctrineExtensions\NestedSet\Manager::buildTree + * @covers DoctrineExtensions\NestedSet\NodeWrapper::internalSetParent + * @covers DoctrineExtensions\NestedSet\NodeWrapper::internalSetAncestors + * @covers DoctrineExtensions\NestedSet\NodeWrapper::internalAddDescendant + * @covers DoctrineExtensions\NestedSet\NodeWrapper::internalAddChild + */ + public function testFetchBranch() + { + $this->loadData(); + $nodes = $this->nodes; + + $this->assertNull($this->nsm->fetchBranch(-10), '->fetchBranch() returns null when branch node doesn\'t exist'); + + $root = $this->nsm->fetchBranch(2); + $this->assertInstanceOf('DoctrineExtensions\NestedSet\NodeWrapper', $root, '->fetchBranch() returns a NodeWrapper object'); + + // + // NOTE: Testing private variables + // + + $root_parent = $this->readAttribute($root, 'parent'); + $root_children = $this->readAttribute($root, 'children'); + $root_ancestors = $this->readAttribute($root, 'ancestors'); + $root_descendants = $this->readAttribute($root, 'descendants'); + + $this->assertEquals($nodes[1]->getId(), $root->getId(), '->fetchBranch() start id is correct'); + $this->assertAttributeEquals(1, 'level', $root, '->fetchBranch() start level is correct'); + $this->assertAttributeEquals(array(1,1), 'outlineNumbers', $root, '->fetchBranch() start outlineNumbers is correct'); + $this->assertNull($root_parent, '->fetchBranch() start parent is null'); + $this->assertEquals( + array($nodes[2]->getId(), $nodes[3]->getId()), + array_map(function($e) {return $e->getNode()->getId();}, $root_children), + '->fetchBranch() start children populated' + ); + $this->assertEquals( + array($nodes[2]->getId(), $nodes[3]->getId()), + array_map(function($e) {return $e->getNode()->getId();}, $root_descendants), + '->fetchBranch() start descendants populated' + ); + $this->assertAttributeEquals(2, 'level', $root_children[0], '->fetchBranch() child level is correct'); + $this->assertAttributeEquals(array(1,1,2), 'outlineNumbers', $root_children[1], '->fetchBranch() child outlineNumbers is correct'); + + + $node2_parent = $this->readAttribute($root_children[0], 'parent'); + $node2_children = $this->readAttribute($root_children[0], 'children'); + $node2_ancestors = $this->readAttribute($root_children[0], 'ancestors'); + $node2_descendants = $this->readAttribute($root_children[0], 'descendants'); + + $this->assertEquals($nodes[1]->getId(), $node2_parent->getNode()->getId(), '->fetchBranch() first child parent is correct'); + $this->assertEquals( + array($nodes[1]->getId()), + array_map(function($e) {return $e->getNode()->getId();}, $node2_ancestors), + '->fetchBranch() first child ancestors is correct' + ); + $this->assertEmpty($node2_children, '->fetchBranch() first child children populated'); + $this->assertEmpty($node2_descendants, '->fetchBranch() first child descendants populated'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\Manager::fetchBranch + * @covers DoctrineExtensions\NestedSet\Manager::fetchBranchAsArray + * @covers DoctrineExtensions\NestedSet\Manager::buildTree + * @covers DoctrineExtensions\NestedSet\Manager::filterNodeDepth + */ + public function testFetchBranchDepth() + { + $this->loadData(); + $nodes = $this->nodes; + + $this->assertNull($this->nsm->fetchBranch(2, 0)); + + $root = $this->nsm->fetchBranch(2, 1); + $this->assertInstanceOf('DoctrineExtensions\NestedSet\NodeWrapper', $root, '->fetchTree() returns a NodeWrapper object'); + + // + // NOTE: Testing private variables + // + $node1_children = $this->readAttribute($root, 'children'); + $node1_descendants = $this->readAttribute($root, 'descendants'); + $this->assertEmpty($node1_children, '->fetchTree() empty children with depth filtered'); + $this->assertEmpty($node1_descendants, '->fetchTree() empty descendants with depth filtered'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\Manager::createRoot + */ + public function testCreateRoot() + { + $this->setUpDb(); + + $node = new NodeMock(21, '1'); + $wrapper = $this->nsm->createRoot($node); + $this->assertInstanceOf('DoctrineExtensions\NestedSet\NodeWrapper', $wrapper, '->createRoot() returns a NodeWrapper()'); + $this->assertEquals(1, $wrapper->getLeftValue(), '->createRoot() sets left value'); + $this->assertEquals(2, $wrapper->getRightValue(), '->createRoot() sets right value'); + $this->assertEquals(21, $wrapper->getRootValue(), '->createRoot() sets root value'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\Manager::createRoot + * @expectedException InvalidArgumentException + */ + public function testCreateRoot_CantPassNodeWrapper() + { + $node = new NodeMock(21, '1'); + $wrapper = $this->nsm->wrapNode($node); + $this->nsm->createRoot($wrapper); + } + + + /** + * @covers DoctrineExtensions\NestedSet\Manager::createRoot + */ + public function testCreateRoot_NoId() + { + $this->setUpDb(); + + $node = new NodeMock(null, '1'); + $wrapper = $this->nsm->createRoot($node); + $this->assertInstanceOf('DoctrineExtensions\NestedSet\NodeWrapper', $wrapper, '->createRoot() returns a NodeWrapper()'); + $this->assertEquals($wrapper->getId(), $wrapper->getRootValue(), '->createRoot() sets root value'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\Manager::wrapNode + */ + public function testWrapNode() + { + $node = new NodeMock(1, '1'); + $wrapper = $this->nsm->wrapNode($node); + $this->assertInstanceOf('DoctrineExtensions\NestedSet\NodeWrapper', $wrapper, '->wrapNode returns NodeWrapper object'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\Manager::wrapNode + * @expectedException InvalidArgumentException + */ + public function testWrapNode_CantWrapNodeWrapper() + { + $node = new NodeMock(1, '1'); + $wrapper = new NodeWrapper($node, $this->nsm); + $this->nsm->wrapNode($wrapper); + } + + + /** + * @covers DoctrineExtensions\NestedSet\Manager::getEntityManager + */ + public function testGetEntityManager() + { + $this->assertInstanceOf('Doctrine\ORM\EntityManager', $this->nsm->getEntityManager(), '->getEntityManager() returns instance of EntityManager'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\Manager::getConfiguration + */ + public function testGetConfiguration() + { + $this->assertInstanceOf('DoctrineExtensions\NestedSet\Config', $this->nsm->getConfiguration(), '->getConfiguration() works'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\Manager::updateLeftValues + */ + public function testUpdateLeftValues() + { + $wrappers = array( + $this->nsm->wrapNode(new NodeMock(1, '1', 1, 6)), + $this->nsm->wrapNode(new NodeMock(2, '1.1', 2, 3)), + $this->nsm->wrapNode(new NodeMock(3, '1.2', 4, 5)), + ); + + $this->nsm->updateLeftValues(2, 0, 2, 1); + + $this->assertEquals(4, $wrappers[1]->getLeftValue(), '->updateLeftValues() updates left value'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\Manager::updateRightValues + */ + public function testUpdateRightValues() + { + $wrappers = array( + $this->nsm->wrapNode(new NodeMock(1, '1', 1, 6)), + $this->nsm->wrapNode(new NodeMock(2, '1.1', 2, 3)), + $this->nsm->wrapNode(new NodeMock(3, '1.2', 4, 5)), + ); + + $this->nsm->updateRightValues(2, 0, 2, 1); + + $this->assertEquals(5, $wrappers[1]->getRightValue(), '->updateRightValues() updates right value'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\Manager::updateValues + */ + public function testUpdateValues() + { + // Make sure updateValues can be called with no registered wrappers + $this->nsm->updateValues(1, 0, 2, 1, 15); + + $wrappers = array( + $this->nsm->wrapNode(new NodeMock(1, '1', 1, 6)), + $this->nsm->wrapNode(new NodeMock(2, '1.1', 2, 3)), + $this->nsm->wrapNode(new NodeMock(3, '1.2', 4, 5)), + ); + + $this->nsm->updateValues(1, 0, 2, 1, 15); + + $this->assertEquals(4, $wrappers[1]->getLeftValue(), '->updateValues() updates left value'); + $this->assertEquals(5, $wrappers[1]->getRightValue(), '->updateValues() updates right value'); + $this->assertEquals(15, $wrappers[1]->getRootValue(), '->updateValues() updates root value'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\Manager::removeNodes + */ + public function testRemoveNodes() + { + $wrappers = array( + $this->nsm->wrapNode(new NodeMock(1, '1', 1, 6)), + $this->nsm->wrapNode(new NodeMock(2, '1.1', 2, 3)), + $this->nsm->wrapNode(new NodeMock(3, '1.2', 4, 5)), + ); + + $this->nsm->removeNodes(2,3,1); + $this->assertFalse($this->nsm->wrapperExists($wrappers[1]->getId()), '->removeNodes() removes node from manager'); + $this->assertFalse($this->nsm->getEntityManager()->contains($wrappers[2]->getNode()), '->removeNodes() removes node from entity manager'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\Manager::filterNodeDepth + */ + public function testFilterNodeDepth_Empty() + { + $this->assertEmpty($this->nsm->filterNodeDepth(array(), 1), '->filterNodeDepth() returns an empty array when given an empty array'); + $this->assertEmpty($this->nsm->filterNodeDepth($this->nodes, 0), '->filterNodeDepth() returns an empty array for depth=0'); + } + + +} diff --git a/3rdparty/doctrine2-nestedset/tests/DoctrineExtensions/NestedSet/Tests/Mocks/ManagerMock.php b/3rdparty/doctrine2-nestedset/tests/DoctrineExtensions/NestedSet/Tests/Mocks/ManagerMock.php @@ -0,0 +1,36 @@ +<?php +/* + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * This software consists of voluntary contributions made by many individuals + * and is licensed under the LGPL. + */ + +namespace DoctrineExtensions\NestedSet\Tests\Mocks; + +use DoctrineExtensions\NestedSet\Manager; +use DoctrineExtensions\NestedSet\Config; +use Doctrine\ORM\EntityManager; + +class ManagerMock extends Manager +{ + public function __construct(EntityManager $em, $clazz=null) + { + parent::__construct(new Config($em, $clazz)); + } + + public function wrapperExists($key) + { + return array_key_exists($key, $this->wrappers); + } +} diff --git a/3rdparty/doctrine2-nestedset/tests/DoctrineExtensions/NestedSet/Tests/Mocks/NodeMock.php b/3rdparty/doctrine2-nestedset/tests/DoctrineExtensions/NestedSet/Tests/Mocks/NodeMock.php @@ -0,0 +1,79 @@ +<?php +/* + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * This software consists of voluntary contributions made by many individuals + * and is licensed under the LGPL. + */ + +namespace DoctrineExtensions\NestedSet\Tests\Mocks; + +use DoctrineExtensions\NestedSet\MultipleRootNode; + + +/** + * @Entity + */ +class NodeMock implements MultipleRootNode +{ + /** + * @Id @Column(type="integer") + * @GeneratedValue + */ + private $id; + + /** + * @Column(type="integer") + */ + private $lft; + + /** + * @Column(type="integer") + */ + private $rgt; + + /** + * @Column(type="string", length="16") + */ + private $name; + + /** + * @Column(type="integer") + */ + private $root; + + public function __construct($id, $name=null, $lft=null, $rgt=null, $root=1) + { + $this->id = $id; + $this->lft = $lft; + $this->rgt = $rgt; + $this->root = $root; + $this->name = $name; + } + + public function getId() { return $this->id; } + + public function getLeftValue() { return $this->lft; } + public function setLeftValue($lft) { $this->lft = $lft; } + + public function getRightValue() { return $this->rgt; } + public function setRightValue($rgt) { $this->rgt = $rgt; } + + public function getRootValue() { return $this->root; } + public function setRootValue($root) { $this->root = $root; } + + public function getName() { return $this->name; } + public function setName($name) { $this->name = $name; } + + public function __toString() { return $this->name; } +} diff --git a/3rdparty/doctrine2-nestedset/tests/DoctrineExtensions/NestedSet/Tests/Mocks/NonNodeMock.php b/3rdparty/doctrine2-nestedset/tests/DoctrineExtensions/NestedSet/Tests/Mocks/NonNodeMock.php @@ -0,0 +1,31 @@ +<?php +/* + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * This software consists of voluntary contributions made by many individuals + * and is licensed under the LGPL. + */ + +namespace DoctrineExtensions\NestedSet\Tests\Mocks; + + +/** + * @Entity + */ +class NonNodeMock +{ + /** + * @Id @Column(type="integer") + */ + private $id; +} diff --git a/3rdparty/doctrine2-nestedset/tests/DoctrineExtensions/NestedSet/Tests/Mocks/SingleRootNodeMock.php b/3rdparty/doctrine2-nestedset/tests/DoctrineExtensions/NestedSet/Tests/Mocks/SingleRootNodeMock.php @@ -0,0 +1,72 @@ +<?php +/* + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * This software consists of voluntary contributions made by many individuals + * and is licensed under the LGPL. + */ + +namespace DoctrineExtensions\NestedSet\Tests\Mocks; + +use DoctrineExtensions\NestedSet\Node; + + +/** + * @Entity + */ +class SingleRootNodeMock implements Node +{ + /** + * @Id @Column(type="integer") + */ + private $id; + + /** + * @Column(type="integer") + */ + private $lft; + + /** + * @Column(type="integer") + */ + private $rgt; + + /** + * @Column(type="string", length="16") + */ + private $name; + + public function __construct($id, $name=null, $lft=null, $rgt=null) + { + $this->id = $id; + $this->lft = $lft; + $this->rgt = $rgt; + $this->name = $name; + } + + public function getId() { return $this->id; } + + public function getLeftValue() { return $this->lft; } + public function setLeftValue($lft) { $this->lft = $lft; } + + public function getRightValue() { return $this->rgt; } + public function setRightValue($rgt) { $this->rgt = $rgt; } + + public function getRootValue() { return null; } + public function setRootValue($root) { } + + public function getName() { return $this->name; } + public function setName($name) { $this->name = $name; } + + public function __toString() { return $this->name; } +} diff --git a/3rdparty/doctrine2-nestedset/tests/DoctrineExtensions/NestedSet/Tests/NodeWrapperTest.php b/3rdparty/doctrine2-nestedset/tests/DoctrineExtensions/NestedSet/Tests/NodeWrapperTest.php @@ -0,0 +1,1022 @@ +<?php +/* + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * This software consists of voluntary contributions made by many individuals + * and is licensed under the LGPL. + */ + +namespace DoctrineExtensions\NestedSet\Tests; + +use DoctrineExtensions\NestedSet\Tests\Mocks\NodeMock; +use DoctrineExtensions\NestedSet\Tests\Mocks\ManagerMock; +use DoctrineExtensions\NestedSet\NodeWrapper; + + + +class NodeWrapperTest extends DatabaseTest +{ + protected + $nsm, + $nodes, + $nodes2, + $wrappers, + $wrappers2; + + protected function setUp() + { + $em = $this->getEntityManager(); + $this->loadSchema(array($em->getClassMetadata('DoctrineExtensions\NestedSet\Tests\Mocks\NodeMock'))); + + $this->nsm = new ManagerMock($em, 'DoctrineExtensions\NestedSet\Tests\Mocks\NodeMock'); + + $this->nodes = array( + new NodeMock(1, '1', 1, 10), # 0 + new NodeMock(2, '1.1', 2, 7), # 1 + new NodeMock(3, '1.1.1', 3, 4), # 2 + new NodeMock(4, '1.1.2', 5, 6), # 3 + new NodeMock(5, '1.2', 8, 9), # 4 + ); + + $this->nodes2 = array( + new NodeMock(11, '1', 1, 12, 2), # 0 + new NodeMock(12, '1.1', 2, 7, 2), # 1 + new NodeMock(13, '1.1.1', 3, 4, 2), # 2 + new NodeMock(14, '1.1.2', 5, 6, 2), # 3 + new NodeMock(15, '1.2', 8, 9, 2), # 4 + new NodeMock(16, '1.3', 10, 11, 2), # 5 + ); + + + $this->wrappers = array(); + foreach($this->nodes as $node) + { + $em->persist($node); + $this->wrappers[] = $this->nsm->wrapNode($node); + } + + $this->wrappers2 = array(); + foreach($this->nodes2 as $node) + { + $em->persist($node); + $this->wrappers2[] = $this->nsm->wrapNode($node); + } + + $em->flush(); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::__construct + */ + public function testConstructor() + { + $this->assertType('DoctrineExtensions\NestedSet\NodeWrapper', new NodeWrapper($this->nodes[2], $this->nsm), '->__construct() works'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::__construct + * @expectedException InvalidArgumentException + */ + public function testConstructorWithNodeWrapper() + { + new NodeWrapper($this->wrappers[0], $this->nsm); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::getNode + */ + public function testGetNode() + { + $this->assertSame($this->nodes[0], $this->wrappers[0]->getNode(), '->getNode() returns wrapped node'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::hasPrevSibling + */ + public function testHasPrevSibling() + { + $this->assertTrue($this->wrappers[3]->hasPrevSibling(), '->hasPrevSibling() returns true when previous sibling exists'); + $this->assertFalse($this->wrappers[2]->hasPrevSibling(), '->hasPrevSibling() returns false when previous sibling doesn\'t exist'); + $this->assertFalse($this->wrappers[0]->hasPrevSibling(), '->hasPrevSibling() returns false for root node'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::hasNextSibling + */ + public function testHasNextSibling() + { + $this->assertTrue($this->wrappers[2]->hasNextSibling(), '->hasNextSibling() returns true when next sibling exists'); + $this->assertFalse($this->wrappers[3]->hasNextSibling(), '->hasNextSibling() returns false when next sibling doesn\'t exist'); + $this->assertFalse($this->wrappers[0]->hasNextSibling(), '->hasNextSibling() returns false for root node'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::hasChildren + */ + public function testHasChildren() + { + $this->assertTrue($this->wrappers[0]->hasChildren()); + $this->assertFalse($this->wrappers[4]->hasChildren()); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::hasParent + */ + public function testHasParent() + { + $this->assertFalse($this->wrappers[0]->hasParent()); + $this->assertTrue($this->wrappers[4]->hasParent()); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::isRoot + */ + public function testIsRoot() + { + $this->assertTrue($this->wrappers[0]->isRoot()); + $this->assertFalse($this->wrappers[1]->isRoot()); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::isLeaf + */ + public function testIsLeaf() + { + $this->assertFalse($this->wrappers[0]->isLeaf()); + $this->assertTrue($this->wrappers[2]->isLeaf()); + $this->assertTrue($this->wrappers[4]->isLeaf()); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::isValidNode + */ + public function testIsValidNode() + { + $this->assertTrue($this->wrappers[0]->isValidNode(), '->isValidNode() returns true for valid nodes'); + + $this->wrappers[0]->setLeftValue(12); + $this->assertFalse($this->wrappers[0]->isValidNode(), '->isValidNode() returns false for invalid nodes'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::invalidate + */ + public function testInvalidate() + { + $this->wrappers[0]->invalidate(); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::isDescendantOf + */ + public function testIsDescendantOf() + { + $this->assertTrue($this->wrappers[3]->isDescendantOf($this->wrappers[0])); + $this->assertTrue($this->wrappers[2]->isDescendantOf($this->nodes[1])); + $this->assertFalse($this->wrappers[1]->isDescendantOf($this->wrappers[4])); + $this->assertFalse($this->wrappers[1]->isDescendantOf($this->nodes[2])); + } + + + /* + public function testIsSiblingOf() + { + $this->assertTrue($this->wrappers[3]->isSiblingOf($this->wrappers[2])); + $this->assertTrue($this->wrappers[4]->isSiblingOf($this->nodes[1])); + $this->assertFalse($this->wrappers[1]->isSiblingOf($this->wrappers[0])); + $this->assertFalse($this->wrappers[1]->isSiblingOf($this->nodes[2])); + } + */ + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::isAncestorOf + */ + public function testIsAncestorOf() + { + $this->assertTrue($this->wrappers[0]->isAncestorOf($this->wrappers[1])); + $this->assertTrue($this->wrappers[1]->isAncestorOf($this->nodes[2])); + $this->assertFalse($this->wrappers[4]->isAncestorOf($this->wrappers[1])); + $this->assertFalse($this->wrappers[3]->isAncestorOf($this->wrappers[0])); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::isEqualTo + */ + public function testIsEqualTo() + { + $this->assertTrue($this->wrappers[0]->isEqualTo($this->nodes[0]), '->isEqualTo() returns true for equal nodes'); + $this->assertFalse($this->wrappers[2]->isEqualTo($this->wrappers[3]), '->isEqualTo() returns false for non-equal nodes'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::getParent + */ + public function testGetParent() + { + $this->assertEquals($this->nodes[0]->getId(), $this->wrappers[1]->getParent()->getNode()->getId()); + $this->assertNull($this->wrappers[0]->getParent()); + + $this->wrappers[3]->getAncestors(); + $this->assertEquals($this->nodes[1]->getId(), $this->wrappers[3]->getParent()->getNode()->getId()); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::getAncestors + */ + public function testGetAncestors() + { + $a = $this->wrappers[3]->getAncestors(); + $this->assertEquals( + array($this->nodes[0]->getId(), $this->nodes[1]->getId()), + array_map(function($node) {return $node->getNode()->getId();}, $a) + ); + + $this->assertEmpty($this->wrappers[0]->getAncestors()); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::getPath + */ + public function testGetPath() + { + $this->assertEquals('1 > 1.1', $this->wrappers[3]->getPath(), '->getPath() works with default parameters'); + + $this->assertEquals('1 | 1.1', $this->wrappers[3]->getPath(' | '), '->getPath() supports custom separator'); + + $this->assertEquals('1 > 1.1 > 1.1.2', $this->wrappers[3]->getPath(' > ', true), '->getPath() can include self'); + + $this->assertEquals('', $this->wrappers[0]->getPath(), '->getPath() returns empty string for root node'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::getOutlineNumber + */ + public function testGetOutlineNumber() + { + $this->assertEquals('1.1.1', $this->wrappers[2]->getOutlineNumber(), '->getOutlineNumber() works for 1.1.1'); + $this->assertEquals('1.1.2', $this->wrappers[3]->getOutlineNumber(), '->getOutlineNumber() works for 1.1.2'); + $this->assertEquals('', $this->wrappers[0]->getOutlineNumber('.',false), '->getOutlineNumber() works for root'); + $this->assertEquals('2', $this->wrappers[4]->getOutlineNumber('.',false), '->getOutlineNumber() works with includeNode=false'); + $this->assertEquals('1-1', $this->wrappers[1]->getOutlineNumber('-'), '->getOutlineNumber() supports separator'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::getDescendants + * @covers DoctrineExtensions\NestedSet\Manager::filterNodeDepth + * @covers DoctrineExtensions\NestedSet\NodeWrapper::getLeftFieldName + * @covers DoctrineExtensions\NestedSet\NodeWrapper::getRightFieldName + * @covers DoctrineExtensions\NestedSet\NodeWrapper::getRootFieldName + * @covers DoctrineExtensions\NestedSet\NodeWrapper::hasManyRoots + */ + public function testGetDescendants() + { + $d = $this->wrappers[1]->getDescendants(); + $this->assertEquals( + array($this->nodes[2]->getId(), $this->nodes[3]->getId()), + array_map(function($node) {return $node->getNode()->getId();}, $d), + '->getDescendants() depth=unlimited' + ); + + $this->assertEmpty($this->wrappers[0]->getDescendants(0), '->getDescendants() depth=0'); + + $d = $this->wrappers[0]->getDescendants(1); + $this->assertEquals( + array($this->nodes[1]->getId(), $this->nodes[4]->getId()), + array_map(function($node) {return $node->getNode()->getId();}, $d), + '->getDescendants() depth=1' + ); + + $d = $this->wrappers[0]->getDescendants(2); + $this->assertEquals( + array($this->nodes[1]->getId(), $this->nodes[2]->getId(), $this->nodes[3]->getId(), $this->nodes[4]->getId()), + array_map(function($node) {return $node->getNode()->getId();}, $d), + '->getDescendants() depth=2' + ); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::getPrevSibling + */ + public function testGetPrevSibling_UnloadedTree() + { + $this->assertEquals($this->nodes[2]->getId(), $this->wrappers[3]->getPrevSibling()->getNode()->getId(), '->getPrevSibling() returns previous sibling'); + + $this->assertNull($this->wrappers[2]->getPrevSibling(), '->getPrevSibling() returns null for first sibling'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::getPrevSibling + * @covers DoctrineExtensions\NestedSet\NodeWrapper::internalGetChildren + */ + public function testGetPrevSibling_LoadedTree() + { + $this->wrappers[3]->getParent(); + $this->assertEquals($this->nodes[2]->getId(), $this->wrappers[3]->getPrevSibling()->getNode()->getId(), '->getPrevSibling() returns previous sibling with parent loaded'); + + $this->wrappers[2]->getParent(); + $this->wrappers[1]->getChildren(); + + $this->assertEquals($this->nodes[2]->getId(), $this->wrappers[3]->getPrevSibling()->getNode()->getId(), '->getPrevSibling() returns previous sibling'); + + $this->assertNull($this->wrappers[2]->getPrevSibling(), '->getPrevSibling() returns null for first sibling'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::getNextSibling + */ + public function testGetNextSibling() + { + $this->assertEquals($this->nodes[3]->getId(), $this->wrappers[2]->getNextSibling()->getNode()->getId(), '->getNextSibling() returns next sibling'); + + $this->assertNull($this->wrappers[3]->getNextSibling(), '->getNextSibling() returns null for last sibling'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::getNextSibling + * @covers DoctrineExtensions\NestedSet\NodeWrapper::internalGetChildren + */ + public function testGetNextSibling_LoadedTree() + { + $this->wrappers[2]->getParent(); + $this->assertEquals($this->nodes[3]->getId(), $this->wrappers[2]->getNextSibling()->getNode()->getId(), '->getNextSibling() returns previous sibling with parent loaded'); + + $this->wrappers[3]->getParent(); + $this->wrappers[1]->getChildren(); + + $this->assertEquals($this->nodes[3]->getId(), $this->wrappers[2]->getNextSibling()->getNode()->getId(), '->getNextSibling() returns next sibling'); + + $this->assertNull($this->wrappers[3]->getNextSibling(), '->getNextSibling() returns null for last sibling'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::getSiblings + */ + public function testGetSiblings() + { + $siblings = $this->wrappers[2]->getSiblings(); + $this->assertEquals( + array($this->nodes[3]->getId()), + array_map(function($node) {return $node->getNode()->getId();}, $siblings), + '->getSiblings() excludes current node by default' + ); + + $siblings = $this->wrappers[2]->getSiblings(true); + $this->assertEquals( + array($this->nodes[2]->getId(), $this->nodes[3]->getId()), + array_map(function($node) {return $node->getNode()->getId();}, $siblings), + '->getSiblings() includes current node' + ); + } + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::getFirstChild + */ + public function testGetFirstChild() + { + $this->assertNull($this->wrappers[2]->getFirstChild(), '->getFirstChild() returns null for leaf node'); + + $this->assertEquals($this->nodes[2]->getId(), $this->wrappers[1]->getFirstChild()->getNode()->getId(), '->getFirstChild() queries for child'); + + $this->wrappers[0]->getChildren(); + $this->assertEquals($this->nodes[1]->getId(), $this->wrappers[0]->getFirstChild()->getNode()->getId(), '->getFirstChild() works when children exist'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::getLastChild + */ + public function testGetLastChild() + { + $this->assertNull($this->wrappers[2]->getLastChild(), '->getLastChild() returns null for leaf node'); + + $this->assertEquals($this->nodes[3]->getId(), $this->wrappers[1]->getLastChild()->getNode()->getId(), '->getLastChild() queries for child'); + + $this->wrappers[0]->getChildren(); + $this->assertEquals($this->nodes[4]->getId(), $this->wrappers[0]->getLastChild()->getNode()->getId(), '->getLastChild() works when children exist'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::getChildren + */ + public function testGetChildren() + { + $c = $this->wrappers[1]->getChildren(); + $this->assertEquals( + array($this->nodes[2]->getId(), $this->nodes[3]->getId()), + array_map(function($node) {return $node->getNode()->getId();}, $c) + ); + + $c = $this->wrappers[0]->getChildren(); + $this->assertEquals( + array($this->nodes[1]->getId(), $this->nodes[4]->getId()), + array_map(function($node) {return $node->getNode()->getId();}, $c) + ); + + $this->assertEmpty($this->wrappers[2]->getChildren(), 'no children'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::getNumberChildren + */ + public function testGetNumberChildren() + { + $this->assertEquals(2, $this->wrappers[1]->getNumberChildren()); + $this->assertEquals(0, $this->wrappers[3]->getNumberChildren()); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::getNumberDescendants + */ + public function testGetNumberDescendants() + { + $this->assertEquals(4, $this->wrappers[0]->getNumberDescendants()); + $this->assertEquals(0, $this->wrappers[2]->getNumberDescendants()); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::getLevel + */ + public function testGetLevel() + { + $this->assertEquals(0, $this->wrappers[0]->getLevel(), '->getLevel() works for root node'); + $this->assertEquals(2, $this->wrappers[3]->getLevel(), '->getLevel() works for leaf node'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::insertAsParentOf + */ + public function testInsertAsParentOf() + { + $newWrapper = $this->nsm->wrapNode(new NodeMock(6, '1.1', 0, 0, 0)); + + $newWrapper->insertAsParentOf($this->wrappers[4]); + $this->assertEquals(8, $newWrapper->getLeftValue(), '->insertAsParentOf() updates new node\'s left value'); + $this->assertEquals(11, $newWrapper->getRightValue(), '->insertAsParentOf() updates new node\'s right value'); + $this->assertEquals(1, $newWrapper->getRootValue(), '->insertAsParentOf() updates new node\'s root value'); + $this->assertEquals(9, $this->wrappers[4]->getLeftValue(), '->insertAsParentOf() updates next node\'s left value'); + $this->assertEquals(10, $this->wrappers[4]->getRightValue(), '->insertAsParentOf() updates next node\'s right value'); + $this->assertEquals(12, $this->wrappers[0]->getRightValue(), '->insertAsParentOf() updates parent node\'s right value'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::insertAsParentOf + * @expectedException InvalidArgumentException + */ + public function testInsertAsParentOf_CantInsertSelf() + { + $this->wrappers[1]->insertAsParentOf($this->wrappers[1]); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::insertAsParentOf + * @expectedException InvalidArgumentException + */ + public function testInsertAsParentOf_CantInsertValidNode() + { + $newWrapper = $this->nsm->wrapNode(new NodeMock(6, '1.1', 0, 0, 0)); + $this->wrappers[1]->insertAsParentOf($newWrapper); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::insertAsParentOf + * @expectedException InvalidArgumentException + */ + public function testInsertAsParentOf_CantInsertRoot() + { + $newWrapper = $this->nsm->wrapNode(new NodeMock(6, '1.1', 0, 0, 0)); + $newWrapper->insertAsParentOf($this->wrappers[0]); + } + + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::insertAsPrevSiblingOf + * @covers DoctrineExtensions\NestedSet\NodeWrapper::shiftRLRange + * @covers DoctrineExtensions\NestedSet\NodeWrapper::insertNode + */ + public function testInsertAsPrevSiblingOf() + { + $newNode = new NodeWrapper(new NodeMock(21, '1.1.1(.5)'), $this->nsm); + + $newNode->insertAsPrevSiblingOf($this->wrappers[3]); + $this->assertEquals(5, $newNode->getLeftValue(), '->insertAsPrevSiblingOf() updates new node\'s left value'); + $this->assertEquals(6, $newNode->getRightValue(), '->insertAsPrevSiblingOf() updates new node\'s right value'); + $this->assertEquals(3, $this->wrappers[2]->getLeftValue(), '->insertAsPrevSiblingOf updates prev node\'s left value'); + $this->assertEquals(4, $this->wrappers[2]->getRightValue(), '->insertAsPrevSiblingOf updates prev node\'s right value'); + $this->assertEquals(7, $this->wrappers[3]->getLeftValue(), '->insertAsPrevSiblingOf updates next node\'s left value'); + $this->assertEquals(8, $this->wrappers[3]->getRightValue(), '->insertAsPrevSiblingOf updates next node\'s right value'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::insertAsPrevSiblingOf + * @expectedException InvalidArgumentException + */ + public function testInsertAsPrevSiblingOf_CantInsertSelf() + { + $newNode = new NodeWrapper(new NodeMock(21, '1.1.1(.5)'), $this->nsm); + $newNode->insertAsPrevSiblingOf($newNode); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::insertAsNextSiblingOf + * @covers DoctrineExtensions\NestedSet\NodeWrapper::shiftRLRange + * @covers DoctrineExtensions\NestedSet\NodeWrapper::insertNode + */ + public function testInsertAsNextSiblingOf() + { + $newNode = new NodeWrapper(new NodeMock(21, '1.1.1(.5)'), $this->nsm); + + $newNode->insertAsNextSiblingOf($this->wrappers[2]); + $this->assertEquals(5, $newNode->getLeftValue(), '->insertAsNextSiblingOf() updates new node\'s left value'); + $this->assertEquals(6, $newNode->getRightValue(), '->insertAsNextSiblingOf() updates new node\'s right value'); + $this->assertEquals(3, $this->wrappers[2]->getLeftValue(), '->insertAsNextSiblingOf updates prev node\'s left value'); + $this->assertEquals(4, $this->wrappers[2]->getRightValue(), '->insertAsNextSiblingOf updates prev node\'s right value'); + $this->assertEquals(7, $this->wrappers[3]->getLeftValue(), '->insertAsNextSiblingOf updates next node\'s left value'); + $this->assertEquals(8, $this->wrappers[3]->getRightValue(), '->insertAsNextSiblingOf updates next node\'s right value'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::insertAsNextSiblingOf + * @expectedException InvalidArgumentException + */ + public function testInsertAsNextSiblingOf_CantInsertSelf() + { + $newNode = new NodeWrapper(new NodeMock(21, '1.1.1(.5)'), $this->nsm); + $newNode->insertAsNextSiblingOf($newNode); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::insertAsFirstChildOf + * @covers DoctrineExtensions\NestedSet\NodeWrapper::shiftRLRange + * @covers DoctrineExtensions\NestedSet\NodeWrapper::insertNode + */ + public function testInsertAsFirstChildOf() + { + $newNode = new NodeWrapper(new NodeMock(21, '1.1.0'), $this->nsm); + + $newNode->insertAsFirstChildOf($this->wrappers[1]); + $this->assertEquals(3, $newNode->getLeftValue(), '->insertAsFirstChildOf() updates new node\'s left value'); + $this->assertEquals(4, $newNode->getRightValue(), '->insertAsFirstChildOf() updates new node\'s right value'); + $this->assertEquals(2, $this->wrappers[1]->getLeftValue(), '->insertAsFirstChildOf updates parent node\'s left value'); + $this->assertEquals(9, $this->wrappers[1]->getRightValue(), '->insertAsFirstChildOf updates parent node\'s right value'); + $this->assertEquals(5, $this->wrappers[2]->getLeftValue(), '->insertAsFirstChildOf updates first child node\'s left value'); + $this->assertEquals(6, $this->wrappers[2]->getRightValue(), '->insertAsFirstChildOf updates first child node\'s right value'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::insertAsFirstChildOf + * @expectedException InvalidArgumentException + */ + public function testInsertAsFirstChildOf_CantInsertSelf() + { + $newNode = new NodeWrapper(new NodeMock(21, '1.1.0'), $this->nsm); + $newNode->insertAsFirstChildOf($newNode); + } + + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::insertAsLastChildOf + * @covers DoctrineExtensions\NestedSet\NodeWrapper::shiftRLRange + * @covers DoctrineExtensions\NestedSet\NodeWrapper::insertNode + */ + public function testInsertAsLastChildOf() + { + $newNode = new NodeWrapper(new NodeMock(21, '1.1.3'), $this->nsm); + + $newNode->insertAsLastChildOf($this->wrappers[1]); + $this->assertEquals(7, $newNode->getLeftValue(), '->insertAsLastChildOf() updates new node\'s left value'); + $this->assertEquals(8, $newNode->getRightValue(), '->insertAsLastChildOf() updates new node\'s right value'); + $this->assertEquals(2, $this->wrappers[1]->getLeftValue(), '->insertAsLastChildOf updates parent node\'s left value'); + $this->assertEquals(9, $this->wrappers[1]->getRightValue(), '->insertAsLastChildOf updates parent node\'s right value'); + $this->assertEquals(5, $this->wrappers[3]->getLeftValue(), '->insertAsLastChildOf doesn\'t update last child node\'s left value'); + $this->assertEquals(6, $this->wrappers[3]->getRightValue(), '->insertAsLastChildOf doesn\'t update last child node\'s right value'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::insertAsLastChildOf + * @expectedException InvalidArgumentException + */ + public function testInsertAsLastChildOf_CantInsertSelf() + { + $newNode = new NodeWrapper(new NodeMock(21, '1.1.3'), $this->nsm); + $newNode->insertAsLastChildOf($newNode); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::moveAsPrevSiblingOf + * @covers DoctrineExtensions\NestedSet\NodeWrapper::updateNode + * @covers DoctrineExtensions\NestedSet\NodeWrapper::shiftRLRange + */ + public function testMoveAsPrevSiblingOf() + { + $this->wrappers[4]->moveAsPrevSiblingOf($this->wrappers[1]); + $this->assertEquals(2, $this->wrappers[4]->getLeftValue(), '->moveAsPrevSiblingOf() updates moved node\'s left value'); + $this->assertEquals(3, $this->wrappers[4]->getRightValue(), '->moveAsPrevSiblingOf() updates moved node\'s right value'); + $this->assertEquals(4, $this->wrappers[1]->getLeftValue(), '->moveAsPrevSiblingOf() updates next node\'s left value'); + $this->assertEquals(9, $this->wrappers[1]->getRightValue(), '->moveAsPrevSiblingOf() updates next node\'s right value'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::moveAsPrevSiblingOf + * @expectedException InvalidArgumentException + */ + public function testMoveAsPrevSiblingOf_CantMoveSelf() + { + $this->wrappers[1]->moveAsPrevSiblingOf($this->wrappers[1]); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::moveAsPrevSiblingOf + * @covers DoctrineExtensions\NestedSet\NodeWrapper::moveBetweenTrees + * @covers DoctrineExtensions\NestedSet\NodeWrapper::shiftRLRange + */ + public function testMoveAsPrevSiblingBetweenTrees() + { + $this->wrappers2[1]->moveAsPrevSiblingOf($this->wrappers[2]); + $this->assertEquals(3, $this->wrappers2[1]->getLeftValue(), '->moveAsPrevSiblingOf() updates moved node\'s left value'); + $this->assertEquals(8, $this->wrappers2[1]->getRightValue(), '->moveAsPrevSiblingOf() updates moved node\'s right value'); + $this->assertEquals(1, $this->wrappers2[1]->getRootValue(), '->moveAsPrevSiblingOf() updates moved node\'s root value'); + $this->assertEquals(9, $this->wrappers[2]->getLeftValue(), '->moveAsPrevSiblingOf() updates next node\'s left value'); + $this->assertEquals(10, $this->wrappers[2]->getRightValue(), '->moveAsPrevSiblingOf() updates next node\'s right value'); + $this->assertEquals(2, $this->wrappers2[4]->getLeftValue(), '->moveAsPrevSiblingOf() updates old tree next node\'s left value'); + $this->assertEquals(3, $this->wrappers2[4]->getRightValue(), '->moveAsPrevSiblingOf() updates old tree next node\'s right value'); + $this->assertEquals(4, $this->wrappers2[2]->getLeftValue(), '->moveAsPrevSiblingOf() updates descendant node\'s left value'); + $this->assertEquals(5, $this->wrappers2[2]->getRightValue(), '->moveAsPrevSiblingOf() updates descendant node\'s right value'); + $this->assertEquals(1, $this->wrappers2[2]->getRootValue(), '->moveAsPrevSiblingOf() updates descendant node\'s root value'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::moveAsNextSiblingOf + * @covers DoctrineExtensions\NestedSet\NodeWrapper::updateNode + * @covers DoctrineExtensions\NestedSet\NodeWrapper::shiftRLRange + */ + public function testMoveAsNextSiblingOf() + { + $this->wrappers[1]->moveAsNextSiblingOf($this->wrappers[4]); + $this->assertEquals(4, $this->wrappers[1]->getLeftValue(), '->moveAsNextSiblingOf() updates moved node\'s left value'); + $this->assertEquals(9, $this->wrappers[1]->getRightValue(), '->moveAsNextSiblingOf() updates moved node\'s right value'); + $this->assertEquals(2, $this->wrappers[4]->getLeftValue(), '->moveAsNextSiblingOf() updates previous node\'s left value'); + $this->assertEquals(3, $this->wrappers[4]->getRightValue(), '->moveAsNextSiblingOf() updates previous node\'s right value'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::moveAsNextSiblingOf + * @expectedException InvalidArgumentException + */ + public function testMoveAsNextSiblingOf_CantMoveSelf() + { + $this->wrappers[1]->moveAsNextSiblingOf($this->wrappers[1]); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::moveAsNextSiblingOf + * @covers DoctrineExtensions\NestedSet\NodeWrapper::moveBetweenTrees + * @covers DoctrineExtensions\NestedSet\NodeWrapper::shiftRLRange + */ + public function testMoveAsNextSiblingBetweenTrees() + { + $this->wrappers2[1]->moveAsNextSiblingOf($this->wrappers[2]); + $this->assertEquals(5, $this->wrappers2[1]->getLeftValue(), '->moveAsNextSiblingOf() updates moved node\'s left value'); + $this->assertEquals(10, $this->wrappers2[1]->getRightValue(), '->moveAsNextSiblingOf() updates moved node\'s right value'); + $this->assertEquals(1, $this->wrappers2[1]->getRootValue(), '->moveAsNextSiblingOf() updates moved node\'s root value'); + $this->assertEquals(11, $this->wrappers[3]->getLeftValue(), '->moveAsNextSiblingOf() updates next node\'s left value'); + $this->assertEquals(12, $this->wrappers[3]->getRightValue(), '->moveAsNextSiblingOf() updates next node\'s right value'); + $this->assertEquals(2, $this->wrappers2[4]->getLeftValue(), '->moveAsNextSiblingOf() updates old tree next node\'s left value'); + $this->assertEquals(3, $this->wrappers2[4]->getRightValue(), '->moveAsNextSiblingOf() updates old tree next node\'s right value'); + $this->assertEquals(6, $this->wrappers2[2]->getLeftValue(), '->moveAsNextSiblingOf() updates descendant node\'s left value'); + $this->assertEquals(7, $this->wrappers2[2]->getRightValue(), '->moveAsNextSiblingOf() updates descendant node\'s right value'); + $this->assertEquals(1, $this->wrappers2[2]->getRootValue(), '->moveAsNextSiblingOf() updates descendant node\'s root value'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::moveAsFirstChildOf + * @covers DoctrineExtensions\NestedSet\NodeWrapper::updateNode + * @covers DoctrineExtensions\NestedSet\NodeWrapper::shiftRLRange + */ + public function testMoveAsFirstChildOf() + { + $this->wrappers[4]->moveAsFirstChildOf($this->wrappers[2]); + $this->assertEquals(4, $this->wrappers[4]->getLeftValue(), '->moveAsFirstChildOf() updates moved node\'s left value'); + $this->assertEquals(5, $this->wrappers[4]->getRightValue(), '->moveAsFirstChildOf() updates moved node\'s right value'); + $this->assertEquals(3, $this->wrappers[2]->getLeftValue(), '->moveAsFirstChildOf() updates parent node\'s left value'); + $this->assertEquals(6, $this->wrappers[2]->getRightValue(), '->moveAsFirstChildOf() updates parent node\'s right value'); + $this->assertEquals(7, $this->wrappers[3]->getLeftValue(), '->moveAsFirstChildOf() updates next node\'s left value'); + $this->assertEquals(8, $this->wrappers[3]->getRightValue(), '->moveAsFirstChildOf() updates next node\'s right value'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::moveAsFirstChildOf + * @expectedException InvalidArgumentException + */ + public function testMoveAsFirstChildOf_CantMoveSelf() + { + $this->wrappers[1]->moveAsFirstChildOf($this->wrappers[1]); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::moveAsFirstChildOf + * @covers DoctrineExtensions\NestedSet\NodeWrapper::moveBetweenTrees + * @covers DoctrineExtensions\NestedSet\NodeWrapper::shiftRLRange + */ + public function testMoveAsFirstChildBetweenTrees() + { + $this->wrappers2[1]->moveAsFirstChildOf($this->wrappers[1]); + $this->assertEquals(3, $this->wrappers2[1]->getLeftValue(), '->moveAsFirstChildOf() updates moved node\'s left value'); + $this->assertEquals(8, $this->wrappers2[1]->getRightValue(), '->moveAsFirstChildOf() updates moved node\'s right value'); + $this->assertEquals(1, $this->wrappers2[1]->getRootValue(), '->moveAsFirstChildOf() updates moved node\'s root value'); + $this->assertEquals(9, $this->wrappers[2]->getLeftValue(), '->moveAsFirstChildOf() updates next node\'s left value'); + $this->assertEquals(10, $this->wrappers[2]->getRightValue(), '->moveAsFirstChildOf() updates next node\'s right value'); + $this->assertEquals(2, $this->wrappers2[4]->getLeftValue(), '->moveAsFirstChildOf() updates old tree next node\'s left value'); + $this->assertEquals(3, $this->wrappers2[4]->getRightValue(), '->moveAsFirstChildOf() updates old tree next node\'s right value'); + $this->assertEquals(4, $this->wrappers2[2]->getLeftValue(), '->moveAsFirstChildOf() updates descendant node\'s left value'); + $this->assertEquals(5, $this->wrappers2[2]->getRightValue(), '->moveAsFirstChildOf() updates descendant node\'s right value'); + $this->assertEquals(1, $this->wrappers2[2]->getRootValue(), '->moveAsFirstChildOf() updates descendant node\'s root value'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::moveAsLastChildOf + * @covers DoctrineExtensions\NestedSet\NodeWrapper::updateNode + * @covers DoctrineExtensions\NestedSet\NodeWrapper::shiftRLRange + */ + public function testMoveAsLastChildOf() + { + $this->wrappers[4]->moveAsLastChildOf($this->wrappers[1]); + $this->assertEquals(7, $this->wrappers[4]->getLeftValue(), '->moveAsLastChildOf() updates moved node\'s left value'); + $this->assertEquals(8, $this->wrappers[4]->getRightValue(), '->moveAsLastChildOf() updates moved node\'s right value'); + $this->assertEquals(2, $this->wrappers[1]->getLeftValue(), '->moveAsLastChildOf() updates parent node\'s left value'); + $this->assertEquals(9, $this->wrappers[1]->getRightValue(), '->moveAsLastChildOf() updates parent node\'s right value'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::moveAsLastChildOf + * @expectedException InvalidArgumentException + */ + public function testMoveAsLastChildOf_CantMoveSelf() + { + $this->wrappers[1]->moveAsLastChildOf($this->wrappers[1]); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::moveAsLastChildOf + * @covers DoctrineExtensions\NestedSet\NodeWrapper::moveBetweenTrees + * @covers DoctrineExtensions\NestedSet\NodeWrapper::shiftRLRange + */ + public function testMoveAsLastChildBetweenTrees() + { + $this->wrappers2[1]->moveAsLastChildOf($this->wrappers[1]); + $this->assertEquals(7, $this->wrappers2[1]->getLeftValue(), '->moveAsLastChildOf() updates moved node\'s left value'); + $this->assertEquals(12, $this->wrappers2[1]->getRightValue(), '->moveAsLastChildOf() updates moved node\'s right value'); + $this->assertEquals(1, $this->wrappers2[1]->getRootValue(), '->moveAsLastChildOf() updates moved node\'s root value'); + $this->assertEquals(2, $this->wrappers[1]->getLeftValue(), '->moveAsLastChildOf() updates parent node\'s left value'); + $this->assertEquals(13, $this->wrappers[1]->getRightValue(), '->moveAsLastChildOf() updates parent node\'s right value'); + $this->assertEquals(2, $this->wrappers2[4]->getLeftValue(), '->moveAsLastChildOf() updates old tree next node\'s left value'); + $this->assertEquals(3, $this->wrappers2[4]->getRightValue(), '->moveAsLastChildOf() updates old tree next node\'s right value'); + $this->assertEquals(8, $this->wrappers2[2]->getLeftValue(), '->moveAsLastChildOf() updates descendant node\'s left value'); + $this->assertEquals(9, $this->wrappers2[2]->getRightValue(), '->moveAsLastChildOf() updates descendant node\'s right value'); + $this->assertEquals(1, $this->wrappers2[2]->getRootValue(), '->moveAsLastChildOf() updates descendant node\'s root value'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::makeRoot + * @covers DoctrineExtensions\NestedSet\NodeWrapper::shiftRLRange + */ + public function testMakeRoot() + { + $this->wrappers[1]->makeRoot(3); + $this->assertEquals(1, $this->wrappers[1]->getLeftValue(), '->makeRoot() updates new root node\'s left value'); + $this->assertEquals(6, $this->wrappers[1]->getRightValue(), '->makeRoot() updates new root node\'s right value'); + $this->assertEquals(3, $this->wrappers[1]->getRootValue(), '->makeRoot() updates new root node\'s root value'); + $this->assertEquals(2, $this->wrappers[2]->getLeftValue(), '->makeRoot() updates child node\'s left value'); + $this->assertEquals(3, $this->wrappers[2]->getRightValue(), '->makeRoot() updates child node\'s right value'); + $this->assertEquals(3, $this->wrappers[3]->getRootValue(), '->makeRoot() updates child node\'s root value'); + $this->assertEquals(2, $this->wrappers[4]->getLeftValue(), '->makeRoot() updates old tree next node\'s left value'); + $this->assertEquals(3, $this->wrappers[4]->getRightValue(), '->makeRoot() updates old tree next node\'s right value'); + $this->assertEquals(4, $this->wrappers[0]->getRightValue(), '->makeRoot() updates old tree parent node\'s right value'); + + $this->wrappers[1]->makeRoot(4); + $this->assertEquals(3, $this->wrappers[1]->getRootValue(), '->makeRoot() doesn\'t change existing roots'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::addChild + * @covers DoctrineExtensions\NestedSet\NodeWrapper::insertAsLastChildOf + * @covers DoctrineExtensions\NestedSet\NodeWrapper::shiftRLRange + * @covers DoctrineExtensions\NestedSet\NodeWrapper::insertNode + */ + public function testAddChild_Node() + { + $newWrapper = $this->wrappers[1]->addChild(new NodeMock(6, '1.1.3', 0, 0, 0)); + $this->assertInstanceOf('DoctrineExtensions\NestedSet\NodeWrapper', $newWrapper, '->addChild() returns a NodeWrapper'); + $this->assertEquals(7, $newWrapper->getLeftValue(), '->addChild() updates new node\'s left value'); + $this->assertEquals(8, $newWrapper->getRightValue(), '->addChild() updates new node\'s right value'); + $this->assertEquals(1, $newWrapper->getRootValue(), '->addChild() updates new node\'s root value'); + $this->assertEquals(9, $this->wrappers[1]->getRightValue(), '->addChild() updates parent\'s right value'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::addChild + * @covers DoctrineExtensions\NestedSet\NodeWrapper::insertAsLastChildOf + * @covers DoctrineExtensions\NestedSet\NodeWrapper::shiftRLRange + * @covers DoctrineExtensions\NestedSet\NodeWrapper::insertNode + */ + public function testAddChild_NodeWrapper() + { + $wrapper = $this->nsm->wrapNode(new NodeMock(6, '1.1.3', 0, 0, 0)); + $newWrapper = $this->wrappers[1]->addChild($wrapper); + $this->assertSame($wrapper, $newWrapper, '->addChild() returns original wrapper when passing a NodeWrapper'); + $this->assertEquals(7, $newWrapper->getLeftValue(), '->addChild() updates new node\'s left value'); + $this->assertEquals(8, $newWrapper->getRightValue(), '->addChild() updates new node\'s right value'); + $this->assertEquals(1, $newWrapper->getRootValue(), '->addChild() updates new node\'s root value'); + $this->assertEquals(9, $this->wrappers[1]->getRightValue(), '->addChild() updates parent\'s right value'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::addChild + * @expectedException InvalidArgumentException + */ + public function testAddChild_CantMoveSelf() + { + $this->wrappers[1]->addChild($this->wrappers[1]); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::delete + * @covers DoctrineExtensions\NestedSet\NodeWrapper::shiftRLRange + */ + public function testDelete() + { + $this->wrappers[1]->delete(); + $this->assertEquals(2, $this->wrappers[4]->getLeftValue(), '->delete() updates next node\'s left value'); + $this->assertEquals(3, $this->wrappers[4]->getRightValue(), '->delete() updates next node\'s right value'); + $this->assertEquals(4, $this->wrappers[0]->getRightValue(), '->delete() updates parent node\'s right value'); + $this->assertFalse($this->wrappers[1]->isValidNode(), '->delete() sets deleted node to invalid'); + $this->assertFalse($this->wrappers[2]->isValidNode(), '->delete() sets descendant node\'s to invalid'); + } + + + + + + + + + + + + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::getManager + */ + public function testGetManager() + { + $this->assertSame($this->nsm, $this->wrappers[0]->getManager()); + } + + + + // + // Node Interface Methods + // + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::getLeftValue + */ + public function testGetLeftValue() + { + $this->assertEquals(2, $this->wrappers[1]->getLeftValue()); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::setLeftValue + */ + public function testSetLeftValue() + { + $this->wrappers[1]->setLeftValue(1); + $this->assertEquals(1, $this->wrappers[1]->getLeftValue()); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::getRightValue + */ + public function testGetRightValue() + { + $this->assertEquals(6, $this->wrappers[3]->getRightValue()); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::setRightValue + */ + public function testSetRightValue() + { + $this->wrappers[0]->setRightValue(2); + $this->assertEquals(2, $this->wrappers[0]->getRightValue()); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::getRootValue + */ + public function testGetRootValue() + { + $this->assertEquals(1, $this->wrappers[0]->getRootValue()); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::setRootValue + */ + public function testSetRootValue() + { + $this->wrappers[0]->setRootValue(4); + $this->assertEquals(4, $this->wrappers[0]->getRootValue()); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::__toString + */ + public function testToString() + { + $this->assertType('string', $this->wrappers[0]->__toString(), '->__toString() returns a string'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::getId + */ + public function testGetId() + { + $id = $this->wrappers[0]->getId(); + $this->assertNotEmpty($id); + } +} diff --git a/3rdparty/doctrine2-nestedset/tests/DoctrineExtensions/NestedSet/Tests/SingleRootNodeWrapperTest.php b/3rdparty/doctrine2-nestedset/tests/DoctrineExtensions/NestedSet/Tests/SingleRootNodeWrapperTest.php @@ -0,0 +1,221 @@ +<?php +/* + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * This software consists of voluntary contributions made by many individuals + * and is licensed under the LGPL. + */ + +namespace DoctrineExtensions\NestedSet\Tests; + +use DoctrineExtensions\NestedSet\Tests\Mocks\SingleRootNodeMock; +use DoctrineExtensions\NestedSet\Tests\Mocks\ManagerMock; +use DoctrineExtensions\NestedSet\NodeWrapper; + + + +class SingleRootNodeWrapperTest extends DatabaseTest +{ + protected + $nsm, + $nodes, + $wrappers; + + protected function setUp() + { + $em = $this->getEntityManager(); + $this->loadSchema(array($em->getClassMetadata('DoctrineExtensions\NestedSet\Tests\Mocks\SingleRootNodeMock'))); + + $this->nsm = new ManagerMock($em, 'DoctrineExtensions\NestedSet\Tests\Mocks\SingleRootNodeMock'); + $this->nsm->getConfiguration()->setRootFieldName(null); + + $this->nodes = array( + new SingleRootNodeMock(1, '1', 1, 10), # 0 + new SingleRootNodeMock(2, '1.1', 2, 7), # 1 + new SingleRootNodeMock(3, '1.1.1', 3, 4), # 2 + new SingleRootNodeMock(4, '1.1.2', 5, 6), # 3 + new SingleRootNodeMock(5, '1.2', 8, 9), # 4 + ); + + $this->wrappers = array(); + foreach($this->nodes as $node) + { + $em->persist($node); + $this->wrappers[] = $this->nsm->wrapNode($node); + } + + $em->flush(); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::makeRoot + * @expectedException BadMethodCallException + */ + public function testMakeRoot() + { + $this->wrappers[1]->makeRoot(3); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::getChildren + */ + public function testGetChildren() + { + $c = $this->wrappers[1]->getChildren(); + $this->assertEquals( + array($this->nodes[2]->getId(), $this->nodes[3]->getId()), + array_map(function($node) {return $node->getNode()->getId();}, $c) + ); + + $c = $this->wrappers[0]->getChildren(); + $this->assertEquals( + array($this->nodes[1]->getId(), $this->nodes[4]->getId()), + array_map(function($node) {return $node->getNode()->getId();}, $c) + ); + + $this->assertEmpty($this->wrappers[2]->getChildren(), 'no children'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::getDescendants + * @covers DoctrineExtensions\NestedSet\Manager::filterNodeDepth + */ + public function testGetDescendants() + { + $d = $this->wrappers[1]->getDescendants(); + $this->assertEquals( + array($this->nodes[2]->getId(), $this->nodes[3]->getId()), + array_map(function($node) {return $node->getNode()->getId();}, $d), + '->getDescendants() depth=unlimited' + ); + + $this->assertEmpty($this->wrappers[0]->getDescendants(0), '->getDescendants() depth=0'); + + $d = $this->wrappers[0]->getDescendants(1); + $this->assertEquals( + array($this->nodes[1]->getId(), $this->nodes[4]->getId()), + array_map(function($node) {return $node->getNode()->getId();}, $d), + '->getDescendants() depth=1' + ); + + $d = $this->wrappers[0]->getDescendants(2); + $this->assertEquals( + array($this->nodes[1]->getId(), $this->nodes[2]->getId(), $this->nodes[3]->getId(), $this->nodes[4]->getId()), + array_map(function($node) {return $node->getNode()->getId();}, $d), + '->getDescendants() depth=2' + ); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::getSiblings + */ + public function testGetSiblings() + { + $siblings = $this->wrappers[2]->getSiblings(); + $this->assertEquals( + array($this->nodes[3]->getId()), + array_map(function($node) {return $node->getNode()->getId();}, $siblings), + '->getSiblings() excludes current node by default' + ); + + $siblings = $this->wrappers[2]->getSiblings(true); + $this->assertEquals( + array($this->nodes[2]->getId(), $this->nodes[3]->getId()), + array_map(function($node) {return $node->getNode()->getId();}, $siblings), + '->getSiblings() includes current node' + ); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::getParent + */ + public function testGetParent() + { + $this->assertEquals($this->nodes[0]->getId(), $this->wrappers[1]->getParent()->getNode()->getId()); + $this->assertNull($this->wrappers[0]->getParent()); + + $this->wrappers[3]->getAncestors(); + $this->assertEquals($this->nodes[1]->getId(), $this->wrappers[3]->getParent()->getNode()->getId()); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::getAncestors + */ + public function testGetAncestors() + { + $a = $this->wrappers[3]->getAncestors(); + $this->assertEquals( + array($this->nodes[0]->getId(), $this->nodes[1]->getId()), + array_map(function($node) {return $node->getNode()->getId();}, $a) + ); + + $this->assertEmpty($this->wrappers[0]->getAncestors()); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::insertAsParentOf + */ + public function testInsertAsParentOf() + { + $newWrapper = $this->nsm->wrapNode(new SingleRootNodeMock(6, '1.1', 0, 0, 0)); + + $newWrapper->insertAsParentOf($this->wrappers[4]); + $this->assertEquals(8, $newWrapper->getLeftValue(), '->insertAsParentOf() updates new node\'s left value'); + $this->assertEquals(11, $newWrapper->getRightValue(), '->insertAsParentOf() updates new node\'s right value'); + $this->assertEquals(null, $newWrapper->getRootValue(), '->insertAsParentOf() updates new node\'s root value'); + $this->assertEquals(9, $this->wrappers[4]->getLeftValue(), '->insertAsParentOf() updates next node\'s left value'); + $this->assertEquals(10, $this->wrappers[4]->getRightValue(), '->insertAsParentOf() updates next node\'s right value'); + $this->assertEquals(12, $this->wrappers[0]->getRightValue(), '->insertAsParentOf() updates parent node\'s right value'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::insertAsPrevSiblingOf + * @covers DoctrineExtensions\NestedSet\NodeWrapper::shiftRLRange + * @covers DoctrineExtensions\NestedSet\NodeWrapper::insertNode + */ + public function testInsertAsPrevSiblingOf() + { + $newNode = new NodeWrapper(new SingleRootNodeMock(21, '1.1.1(.5)'), $this->nsm); + + $newNode->insertAsPrevSiblingOf($this->wrappers[3]); + $this->assertEquals(5, $newNode->getLeftValue(), '->insertAsPrevSiblingOf() updates new node\'s left value'); + $this->assertEquals(6, $newNode->getRightValue(), '->insertAsPrevSiblingOf() updates new node\'s right value'); + $this->assertEquals(3, $this->wrappers[2]->getLeftValue(), '->insertAsPrevSiblingOf updates prev node\'s left value'); + $this->assertEquals(4, $this->wrappers[2]->getRightValue(), '->insertAsPrevSiblingOf updates prev node\'s right value'); + $this->assertEquals(7, $this->wrappers[3]->getLeftValue(), '->insertAsPrevSiblingOf updates next node\'s left value'); + $this->assertEquals(8, $this->wrappers[3]->getRightValue(), '->insertAsPrevSiblingOf updates next node\'s right value'); + } + + + /** + * @covers DoctrineExtensions\NestedSet\NodeWrapper::moveAsPrevSiblingOf + * @covers DoctrineExtensions\NestedSet\NodeWrapper::updateNode + * @covers DoctrineExtensions\NestedSet\NodeWrapper::shiftRLRange + */ + public function testMoveAsPrevSiblingOf() + { + $this->wrappers[4]->moveAsPrevSiblingOf($this->wrappers[1]); + $this->assertEquals(2, $this->wrappers[4]->getLeftValue(), '->moveAsPrevSiblingOf() updates moved node\'s left value'); + $this->assertEquals(3, $this->wrappers[4]->getRightValue(), '->moveAsPrevSiblingOf() updates moved node\'s right value'); + $this->assertEquals(4, $this->wrappers[1]->getLeftValue(), '->moveAsPrevSiblingOf() updates next node\'s left value'); + $this->assertEquals(9, $this->wrappers[1]->getRightValue(), '->moveAsPrevSiblingOf() updates next node\'s right value'); + } +} diff --git a/3rdparty/doctrine2-nestedset/tests/README.markdown b/3rdparty/doctrine2-nestedset/tests/README.markdown @@ -0,0 +1,56 @@ +Running the NestedSet Test Suite +================================ + + +## Install Test suite Dependencies + +### PHPUnit + +PHPUnit 3.5.0 or later is required. As of writing, PHPUnit 3.5 is not stable. +The easiest way to install it is via Git: + + $ git clone git://github.com/sebastianbergmann/phpunit.git + $ cd phpunit + $ pear package + $ pear install PHPUnit-3.5.XXX.tgz + + +### Doctrine2 + +The ORM, DBAL, and Common Doctrine2 components are required. The default +location is + +> doctrine2-nestedset/vendor + +Use the `install_vendors.sh` script to install the necessary dependencies: + + $ cd doctrine2-nestedset/tests + $ ./install_vendors.sh + +Alternatively, you can modify the `autoload.php` file for your Doctrine2 +installation directories. + + +### Sqlite + +Sqlite and the PHP bindings for sqlite are required. Consult your +distribution's documentation for installation instructions. For example, on +Ubuntu: + + $ sudo apitutude install sqlite3 php5-sqlite + + +## Run Tests + +To run all tests: + + $ cd doctrine2-nestedset/tests/ + $ phpunit + + +To generate code coverage (requires XDebug): + + $ cd doctrine2-nestedset/tests/ + $ phpunit --coverage-html=cov/ + +Check the code coverage by opening `cov/index.html` page in a browser. diff --git a/3rdparty/doctrine2-nestedset/tests/autoload.php b/3rdparty/doctrine2-nestedset/tests/autoload.php @@ -0,0 +1,19 @@ +<?php + +require_once(__DIR__.'/../vendor/doctrine/lib/vendor/doctrine-common/lib/Doctrine/Common/ClassLoader.php'); + +$loader = new Doctrine\Common\ClassLoader("Doctrine\\Common", __DIR__.'/../vendor/doctrine/lib/vendor/doctrine-common/lib'); +$loader->register(); + +$loader = new Doctrine\Common\ClassLoader("Doctrine\\DBAL", __DIR__.'/../vendor/doctrine/lib/vendor/doctrine-dbal/lib'); +$loader->register(); + +$loader = new Doctrine\Common\ClassLoader("Doctrine\\ORM", __DIR__.'/../vendor/doctrine/lib'); +$loader->register(); + +$loader = new Doctrine\Common\ClassLoader("DoctrineExtensions\\NestedSet\\Tests", __DIR__); +$loader->register(); + +$loader = new Doctrine\Common\ClassLoader("DoctrineExtensions\\NestedSet", __DIR__."/../lib"); +$loader->register(); + diff --git a/3rdparty/doctrine2-nestedset/tests/install_vendors.sh b/3rdparty/doctrine2-nestedset/tests/install_vendors.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +BASEDIR=`dirname $0`/.. + +# initialization +mkdir -p $BASEDIR/vendor +rm -rf $BASEDIR/vendor/* +cd $BASEDIR/vendor + +# Doctrine +git clone git://github.com/doctrine/doctrine2.git doctrine +cd doctrine +git submodule init +git submodule update +cd .. + diff --git a/3rdparty/doctrine2-nestedset/tests/phpunit.xml.dist b/3rdparty/doctrine2-nestedset/tests/phpunit.xml.dist @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<phpunit + convertErrorsToExceptions="true" + convertWarningsToExceptions="true" + convertNoticesToExceptions="true" + processIsolation="false" + stopOnFailure="false" + syntaxCheck="false" + bootstrap="autoload.php" +> + <testsuites> + <testsuite name="NestedSet"> + <directory>DoctrineExtensions/NestedSet</directory> + </testsuite> + </testsuites> + + <filter> + <whitelist> + <directory>../lib/DoctrineExtensions/NestedSet</directory> + </whitelist> + </filter> +</phpunit> diff --git a/3rdparty/doctrine2-nestedset/tests/update_vendors.sh b/3rdparty/doctrine2-nestedset/tests/update_vendors.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +BASEDIR="`dirname $0`/.." + +# Doctrine +cd $BASEDIR/vendor/doctrine +git pull +git submodule update