Adding indexmenu version 2024-01-05 (ed06f21).
Signed-off-by: Daniel Baumann <daniel@debian.org>
11
plugins/55/indexmenu/.github/workflows/dokuwiki.yml
vendored
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
name: DokuWiki Default Tasks
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
schedule:
|
||||||
|
- cron: '14 17 21 * *'
|
||||||
|
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
all:
|
||||||
|
uses: dokuwiki/github-action/.github/workflows/all.yml@main
|
340
plugins/55/indexmenu/COPYING
Normal file
|
@ -0,0 +1,340 @@
|
||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 2, June 1991
|
||||||
|
|
||||||
|
Copyright (C) 1989, 1991 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.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The licenses for most software are designed to take away your
|
||||||
|
freedom to share and change it. By contrast, the GNU General Public
|
||||||
|
License is intended to guarantee your freedom to share and change free
|
||||||
|
software--to make sure the software is free for all its users. This
|
||||||
|
General Public License applies to most of the Free Software
|
||||||
|
Foundation's software and to any other program whose authors commit to
|
||||||
|
using it. (Some other Free Software Foundation software is covered by
|
||||||
|
the GNU Library General Public License instead.) You can apply it to
|
||||||
|
your programs, too.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, 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 or use pieces of it
|
||||||
|
in new free programs; and that you know you can do these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to make restrictions that forbid
|
||||||
|
anyone to deny you these rights or to ask you to surrender the rights.
|
||||||
|
These restrictions translate to certain responsibilities for you if you
|
||||||
|
distribute copies of the software, or if you modify it.
|
||||||
|
|
||||||
|
For example, if you distribute copies of such a program, whether
|
||||||
|
gratis or for a fee, you must give the recipients all the rights that
|
||||||
|
you have. You must make sure that they, too, receive or can get the
|
||||||
|
source code. And you must show them these terms so they know their
|
||||||
|
rights.
|
||||||
|
|
||||||
|
We protect your rights with two steps: (1) copyright the software, and
|
||||||
|
(2) offer you this license which gives you legal permission to copy,
|
||||||
|
distribute and/or modify the software.
|
||||||
|
|
||||||
|
Also, for each author's protection and ours, we want to make certain
|
||||||
|
that everyone understands that there is no warranty for this free
|
||||||
|
software. If the software is modified by someone else and passed on, we
|
||||||
|
want its recipients to know that what they have is not the original, so
|
||||||
|
that any problems introduced by others will not reflect on the original
|
||||||
|
authors' reputations.
|
||||||
|
|
||||||
|
Finally, any free program is threatened constantly by software
|
||||||
|
patents. We wish to avoid the danger that redistributors of a free
|
||||||
|
program will individually obtain patent licenses, in effect making the
|
||||||
|
program proprietary. To prevent this, we have made it clear that any
|
||||||
|
patent must be licensed for everyone's free use or not licensed at all.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||||
|
|
||||||
|
0. This License applies to any program or other work which contains
|
||||||
|
a notice placed by the copyright holder saying it may be distributed
|
||||||
|
under the terms of this General Public License. The "Program", below,
|
||||||
|
refers to any such program or work, and a "work based on the Program"
|
||||||
|
means either the Program or any derivative work under copyright law:
|
||||||
|
that is to say, a work containing the Program or a portion of it,
|
||||||
|
either verbatim or with modifications and/or translated into another
|
||||||
|
language. (Hereinafter, translation is included without limitation in
|
||||||
|
the term "modification".) Each licensee is addressed as "you".
|
||||||
|
|
||||||
|
Activities other than copying, distribution and modification are not
|
||||||
|
covered by this License; they are outside its scope. The act of
|
||||||
|
running the Program is not restricted, and the output from the Program
|
||||||
|
is covered only if its contents constitute a work based on the
|
||||||
|
Program (independent of having been made by running the Program).
|
||||||
|
Whether that is true depends on what the Program does.
|
||||||
|
|
||||||
|
1. You may copy and distribute verbatim copies of the Program's
|
||||||
|
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 give any other recipients of the Program a copy of this License
|
||||||
|
along with the Program.
|
||||||
|
|
||||||
|
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 Program or any portion
|
||||||
|
of it, thus forming a work based on the Program, 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) You must cause the modified files to carry prominent notices
|
||||||
|
stating that you changed the files and the date of any change.
|
||||||
|
|
||||||
|
b) You must cause any work that you distribute or publish, that in
|
||||||
|
whole or in part contains or is derived from the Program or any
|
||||||
|
part thereof, to be licensed as a whole at no charge to all third
|
||||||
|
parties under the terms of this License.
|
||||||
|
|
||||||
|
c) If the modified program normally reads commands interactively
|
||||||
|
when run, you must cause it, when started running for such
|
||||||
|
interactive use in the most ordinary way, to print or display an
|
||||||
|
announcement including an appropriate copyright notice and a
|
||||||
|
notice that there is no warranty (or else, saying that you provide
|
||||||
|
a warranty) and that users may redistribute the program under
|
||||||
|
these conditions, and telling the user how to view a copy of this
|
||||||
|
License. (Exception: if the Program itself is interactive but
|
||||||
|
does not normally print such an announcement, your work based on
|
||||||
|
the Program is not required to print an announcement.)
|
||||||
|
|
||||||
|
These requirements apply to the modified work as a whole. If
|
||||||
|
identifiable sections of that work are not derived from the Program,
|
||||||
|
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 Program, 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 Program.
|
||||||
|
|
||||||
|
In addition, mere aggregation of another work not based on the Program
|
||||||
|
with the Program (or with a work based on the Program) on a volume of
|
||||||
|
a storage or distribution medium does not bring the other work under
|
||||||
|
the scope of this License.
|
||||||
|
|
||||||
|
3. You may copy and distribute the Program (or a work based on it,
|
||||||
|
under Section 2) in object code or executable form under the terms of
|
||||||
|
Sections 1 and 2 above provided that you also do one of the following:
|
||||||
|
|
||||||
|
a) 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; or,
|
||||||
|
|
||||||
|
b) Accompany it with a written offer, valid for at least three
|
||||||
|
years, to give any third party, for a charge no more than your
|
||||||
|
cost of physically performing source distribution, a complete
|
||||||
|
machine-readable copy of the corresponding source code, to be
|
||||||
|
distributed under the terms of Sections 1 and 2 above on a medium
|
||||||
|
customarily used for software interchange; or,
|
||||||
|
|
||||||
|
c) Accompany it with the information you received as to the offer
|
||||||
|
to distribute corresponding source code. (This alternative is
|
||||||
|
allowed only for noncommercial distribution and only if you
|
||||||
|
received the program in object code or executable form with such
|
||||||
|
an offer, in accord with Subsection b above.)
|
||||||
|
|
||||||
|
The source code for a work means the preferred form of the work for
|
||||||
|
making modifications to it. For an executable work, 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 executable. However, as a
|
||||||
|
special exception, the source code 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.
|
||||||
|
|
||||||
|
If distribution of executable or 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 counts as
|
||||||
|
distribution of the source code, even though third parties are not
|
||||||
|
compelled to copy the source along with the object code.
|
||||||
|
|
||||||
|
4. You may not copy, modify, sublicense, or distribute the Program
|
||||||
|
except as expressly provided under this License. Any attempt
|
||||||
|
otherwise to copy, modify, sublicense or distribute the Program 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.
|
||||||
|
|
||||||
|
5. 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 Program or its derivative works. These actions are
|
||||||
|
prohibited by law if you do not accept this License. Therefore, by
|
||||||
|
modifying or distributing the Program (or any work based on the
|
||||||
|
Program), you indicate your acceptance of this License to do so, and
|
||||||
|
all its terms and conditions for copying, distributing or modifying
|
||||||
|
the Program or works based on it.
|
||||||
|
|
||||||
|
6. Each time you redistribute the Program (or any work based on the
|
||||||
|
Program), the recipient automatically receives a license from the
|
||||||
|
original licensor to copy, distribute or modify the Program 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 to
|
||||||
|
this License.
|
||||||
|
|
||||||
|
7. 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 Program at all. For example, if a patent
|
||||||
|
license would not permit royalty-free redistribution of the Program 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 Program.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
8. If the distribution and/or use of the Program is restricted in
|
||||||
|
certain countries either by patents or by copyrighted interfaces, the
|
||||||
|
original copyright holder who places the Program 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.
|
||||||
|
|
||||||
|
9. The Free Software Foundation may publish revised and/or new versions
|
||||||
|
of the 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 Program
|
||||||
|
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 Program does not specify a version number of
|
||||||
|
this License, you may choose any version ever published by the Free Software
|
||||||
|
Foundation.
|
||||||
|
|
||||||
|
10. If you wish to incorporate parts of the Program into other free
|
||||||
|
programs whose distribution conditions are different, 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
|
||||||
|
|
||||||
|
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||||
|
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||||
|
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||||
|
PROVIDE THE PROGRAM "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 PROGRAM IS WITH YOU. SHOULD THE
|
||||||
|
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||||
|
REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
12. 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 PROGRAM 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 PROGRAM (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 PROGRAM TO OPERATE WITH ANY OTHER
|
||||||
|
PROGRAMS), 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 Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to the public, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. 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 program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program 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 General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program; 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.
|
||||||
|
|
||||||
|
If the program is interactive, make it output a short notice like this
|
||||||
|
when it starts in an interactive mode:
|
||||||
|
|
||||||
|
Gnomovision version 69, Copyright (C) year name of author
|
||||||
|
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
|
This is free software, and you are welcome to redistribute it
|
||||||
|
under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
|
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||||
|
parts of the General Public License. Of course, the commands you use may
|
||||||
|
be called something other than `show w' and `show c'; they could even be
|
||||||
|
mouse-clicks or menu items--whatever suits your program.
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or your
|
||||||
|
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||||
|
necessary. Here is a sample; alter the names:
|
||||||
|
|
||||||
|
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||||
|
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||||
|
|
||||||
|
<signature of Ty Coon>, 1 April 1989
|
||||||
|
Ty Coon, President of Vice
|
||||||
|
|
||||||
|
This General Public License does not permit incorporating your program into
|
||||||
|
proprietary programs. If your program is a subroutine library, you may
|
||||||
|
consider it more useful to permit linking proprietary applications with the
|
||||||
|
library. If this is what you want to do, use the GNU Library General
|
||||||
|
Public License instead of this License.
|
64
plugins/55/indexmenu/CREDITS
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
Thanks to:
|
||||||
|
|
||||||
|
Geir Landro:
|
||||||
|
Dtree Javascript code.
|
||||||
|
|
||||||
|
Roland Hellebart:
|
||||||
|
The Dtree idea.
|
||||||
|
|
||||||
|
Chris Beetle:
|
||||||
|
The root namespace index.
|
||||||
|
|
||||||
|
Gleb:
|
||||||
|
The nons e headpage option suggestion.
|
||||||
|
|
||||||
|
Malyfred:
|
||||||
|
Resolved incorrect namespaces levels bug.
|
||||||
|
|
||||||
|
Raymond Elferink:
|
||||||
|
Resolved incorrect ACLs bug.
|
||||||
|
|
||||||
|
Ilya Lebedev:
|
||||||
|
Skip index option.
|
||||||
|
|
||||||
|
Franck Baron:
|
||||||
|
Js id option.
|
||||||
|
|
||||||
|
Jon B:
|
||||||
|
Skip file option.
|
||||||
|
|
||||||
|
Neosky:
|
||||||
|
Javascript toolbar bug.
|
||||||
|
|
||||||
|
Paul Grove:
|
||||||
|
Css dynamic properties and suggestion of js theme with differents image formats
|
||||||
|
|
||||||
|
Anja Vag:
|
||||||
|
Great help in testing and finding bugs.
|
||||||
|
|
||||||
|
Blaz:
|
||||||
|
Current page highliting suggestion.
|
||||||
|
|
||||||
|
Adrien CLERC:
|
||||||
|
Start page bug.
|
||||||
|
|
||||||
|
Ryan Jake and Fullindex plugin:
|
||||||
|
Sort by metada suggestion.
|
||||||
|
|
||||||
|
Herman Huitema:
|
||||||
|
Context menu search function and great help in testing patches.
|
||||||
|
|
||||||
|
Thomas Binder:
|
||||||
|
Fixed a bug with msort/nsort that did not manage empty arrays.
|
||||||
|
|
||||||
|
Fabian Pfannes:
|
||||||
|
German language
|
||||||
|
|
||||||
|
Urban:
|
||||||
|
Context menu patch and other suggestions
|
||||||
|
|
||||||
|
Gerrit Uitslag (Klap-in):
|
||||||
|
Rewrite of indexmenu to add new dokuwiki compatibility
|
||||||
|
Added a new toolbar wizard
|
||||||
|
add ''hsort'' for sorting [[config:startpage]] pages to top of listing
|
||||||
|
many others improvements
|
7
plugins/55/indexmenu/README
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
====== Indexmenu Plugin for DokuWiki ======
|
||||||
|
|
||||||
|
All documentation for the Indexmenu Plugin is available online at:
|
||||||
|
|
||||||
|
* https://dokuwiki.org/plugin:indexmenu
|
||||||
|
|
||||||
|
(c) 2006 - 2012 by Samuele Tognini <samuele@samuele.netsons.org>
|
858
plugins/55/indexmenu/Search.php
Normal file
|
@ -0,0 +1,858 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace dokuwiki\plugin\indexmenu;
|
||||||
|
|
||||||
|
use dokuwiki\Utf8\Sort;
|
||||||
|
|
||||||
|
class Search
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var bool|string sort by t=title, d=date of creation, 0 if not set i.e. default page sort (old dTree..)
|
||||||
|
*/
|
||||||
|
private $sort;
|
||||||
|
/**
|
||||||
|
* @var string 'indexmenu_n' or other key from the metadata structure
|
||||||
|
*/
|
||||||
|
private $msort;
|
||||||
|
/**
|
||||||
|
* @var bool Reverse the sorting of pages, combined with $nsort also the namespaces
|
||||||
|
*/
|
||||||
|
private $rsort;
|
||||||
|
/**
|
||||||
|
* @var bool also sorts the namespaces
|
||||||
|
*/
|
||||||
|
private $nsort;
|
||||||
|
/**
|
||||||
|
* @var bool Sort the headpages as defined by global config setting startpage to the top
|
||||||
|
*/
|
||||||
|
private $hsort;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search constructor.
|
||||||
|
*
|
||||||
|
* @param array $sort
|
||||||
|
* $sort['sort']
|
||||||
|
* $sort['msort']
|
||||||
|
* $sort['rsort']
|
||||||
|
* $sort['nsort']
|
||||||
|
* $sort['hsort'];
|
||||||
|
*/
|
||||||
|
public function __construct($sort)
|
||||||
|
{
|
||||||
|
$this->sort = $sort['sort'];
|
||||||
|
$this->msort = $sort['msort'];
|
||||||
|
$this->rsort = $sort['rsort'];
|
||||||
|
$this->nsort = $sort['nsort'];
|
||||||
|
$this->hsort = $sort['hsort'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the data array for fancytree from search results
|
||||||
|
*
|
||||||
|
* @param array $data results from search
|
||||||
|
* @param bool $isInit true if first level of nodes from tree, false if next levels
|
||||||
|
* @param bool $currentPage current wikipage id
|
||||||
|
* @param bool $isNopg if nopg is set
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function buildFancytreeData($data, $isInit, $currentPage, $isNopg)
|
||||||
|
{
|
||||||
|
if (empty($data)) return [];
|
||||||
|
|
||||||
|
$children = [];
|
||||||
|
$opts = [
|
||||||
|
'currentPage' => $currentPage,
|
||||||
|
'isParentLazy' => false,
|
||||||
|
'nopg' => $isNopg
|
||||||
|
];
|
||||||
|
$hasActiveNode = false;
|
||||||
|
$this->makeNodes($data, -1, 0, $children, $hasActiveNode, $opts);
|
||||||
|
|
||||||
|
if ($isInit) {
|
||||||
|
$nodes['children'] = $children;
|
||||||
|
return $nodes;
|
||||||
|
} else {
|
||||||
|
return $children;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collects the children at the same level since last parsed item
|
||||||
|
*
|
||||||
|
* @param array $data results from search
|
||||||
|
* @param int $indexLatestParsedItem
|
||||||
|
* @param int $previousLevel level of parent
|
||||||
|
* @param array $nodes by reference, here the child nodes are stored
|
||||||
|
* @param bool $hasActiveNode active node must be unique, needs tracking
|
||||||
|
* @param array $opts <ul>
|
||||||
|
* <li>$opts['currentPage'] string id of main article</li>
|
||||||
|
* <li>$opts['isParentLazy'] bool Used for recognizing the extra level below lazy nodes</li>
|
||||||
|
* <li>$opts['nopg'] bool needed for currentpage handling</li>
|
||||||
|
* </ul>
|
||||||
|
* @return int latest parsed item from data array
|
||||||
|
*/
|
||||||
|
private function makeNodes(&$data, $indexLatestParsedItem, $previousLevel, &$nodes, &$hasActiveNode, $opts)
|
||||||
|
{
|
||||||
|
$i = 0;
|
||||||
|
$counter = 0;
|
||||||
|
foreach ($data as $i => $item) {
|
||||||
|
//skip parsed items
|
||||||
|
if ($i <= $indexLatestParsedItem) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($item['level'] < $previousLevel || $counter === 0 && $item['level'] == $previousLevel) {
|
||||||
|
return $i - 1;
|
||||||
|
}
|
||||||
|
$node = [
|
||||||
|
'title' => $item['title'],
|
||||||
|
'key' => $item['id'] . ($item['type'] === 'f' ? '' : ':'), //ensure ns is unique
|
||||||
|
'hns' => $item['hns'] //false if not available
|
||||||
|
];
|
||||||
|
|
||||||
|
// f=file, d=directory, l=directory which is lazy loaded later
|
||||||
|
if ($item['type'] == 'f') {
|
||||||
|
// let php create url (considering rewriting etc)
|
||||||
|
$node['url'] = wl($item['id']);
|
||||||
|
|
||||||
|
//set current page to active
|
||||||
|
if ($opts['currentPage'] == $item['id']) {
|
||||||
|
if (!$hasActiveNode) {
|
||||||
|
$node['active'] = true;
|
||||||
|
$hasActiveNode = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// type: d/l
|
||||||
|
$node['folder'] = true;
|
||||||
|
// let php create url (considering rewriting etc)
|
||||||
|
$node['url'] = $item['hns'] === false ? false : wl($item['hns']);
|
||||||
|
if (!$item['hnsExists']) {
|
||||||
|
//change link color
|
||||||
|
$node['hnsNotExisting'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($item['open'] === true) {
|
||||||
|
$node['expanded'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$node['children'] = [];
|
||||||
|
$indexLatestParsedItem = $this->makeNodes(
|
||||||
|
$data,
|
||||||
|
$i,
|
||||||
|
$item['level'],
|
||||||
|
$node['children'],
|
||||||
|
$hasActiveNode,
|
||||||
|
[
|
||||||
|
'currentPage' => $opts['currentPage'],
|
||||||
|
'isParentLazy' => $item['type'] === 'l',
|
||||||
|
'nopg' => $opts['nopg']
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
// a lazy node, but because we have sometime no pages or nodes (due e.g. acl/hidden/nopg), it could be
|
||||||
|
// empty. Therefore we did extra work by walking a level deeper and check here whether it has children
|
||||||
|
if ($item['type'] === 'l') {
|
||||||
|
if (empty($node['children'])) {
|
||||||
|
//an empty lazy node, is not marked lazy
|
||||||
|
if ($opts['isParentLazy']) {
|
||||||
|
//a lazy node with a lazy parent has no children loaded, so stays always empty
|
||||||
|
//(these nodes are not really used, but only counted)
|
||||||
|
$node['lazy'] = true;
|
||||||
|
unset($node['children']);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//has children, so mark lazy
|
||||||
|
$node['lazy'] = true;
|
||||||
|
unset($node['children']); //do not keep, because these nodes do not know yet their child folders
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//might be duplicated if hide_headpage is disabled, or with nopg and a :same: headpage
|
||||||
|
//mark active after processing children, such that deepest level is activated
|
||||||
|
if (
|
||||||
|
$item['hns'] === $opts['currentPage']
|
||||||
|
|| $opts['nopg'] && getNS($opts['currentPage']) === $item['id']
|
||||||
|
) {
|
||||||
|
//with hide_headpage enabled, the parent node must be actived
|
||||||
|
//special: nopg has no pages, therefore, mark its parent node active
|
||||||
|
if (!$hasActiveNode) {
|
||||||
|
$node['active'] = true;
|
||||||
|
$hasActiveNode = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($item['type'] === 'f' || !empty($node['children']) || isset($node['lazy']) || $item['hns'] !== false) {
|
||||||
|
// add only files, non-empty folders, lazy-loaded or folder with only a headpage
|
||||||
|
$nodes[] = $node;
|
||||||
|
}
|
||||||
|
|
||||||
|
$previousLevel = $item['level'];
|
||||||
|
$counter++;
|
||||||
|
}
|
||||||
|
return $i;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search pages/folders depending on the given options $opts
|
||||||
|
*
|
||||||
|
* @param string $ns
|
||||||
|
* @param array $opts<ul>
|
||||||
|
* <li>$opts['skipns'] string regexp matching namespaceids to skip (ignored)</li>
|
||||||
|
* <li>$opts['skipfile'] string regexp matching pageids to skip (ignored)</li>
|
||||||
|
* <li>$opts['skipnscombined'] array regexp matching namespaceids to skip</li>
|
||||||
|
* <li>$opts['skipfilecombined'] array regexp matching pageids to skip</li>
|
||||||
|
* <li>$opts['headpage'] string headpages options or pageids</li>
|
||||||
|
* <li>$opts['level'] int desired depth of main namespace, -1 = all levels</li>
|
||||||
|
* <li>$opts['subnss'] array with entries: array(namespaceid,level) specifying namespaces with their own
|
||||||
|
* number of opened levels</li>
|
||||||
|
* <li>$opts['nons'] bool exclude namespace nodes</li>
|
||||||
|
* <li>$opts['max'] int If initially closed, the node at max level will retrieve all its child nodes
|
||||||
|
* through the AJAX mechanism</li>
|
||||||
|
* <li>$opts['nopg'] bool exclude page nodes</li>
|
||||||
|
* <li>$opts['hide_headpage'] int don't hide (0) or hide (1)</li>
|
||||||
|
* <li>$opts['js'] bool use js-render (only used for old 'searchIndexmenuItems')</li>
|
||||||
|
* </ul>
|
||||||
|
* @return array The results of the search
|
||||||
|
*/
|
||||||
|
public function search($ns, $opts): array
|
||||||
|
{
|
||||||
|
global $conf;
|
||||||
|
|
||||||
|
if (!empty($opts['tempNew'])) {
|
||||||
|
//a specific callback for Fancytree
|
||||||
|
$callback = [$this, 'searchIndexmenuItemsNew'];
|
||||||
|
} else {
|
||||||
|
$callback = [$this, 'searchIndexmenuItems'];
|
||||||
|
}
|
||||||
|
$dataDir = $conf['datadir'];
|
||||||
|
$data = [];
|
||||||
|
$fsDir = "/" . utf8_encodeFN(str_replace(':', '/', $ns));
|
||||||
|
if ($this->sort || $this->msort || $this->rsort || $this->hsort) {
|
||||||
|
$this->customSearch($data, $dataDir, $callback, $opts, $fsDir);
|
||||||
|
} else {
|
||||||
|
search($data, $dataDir, $callback, $opts, $fsDir);
|
||||||
|
}
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback that adds an item of namespace/page to the browsable index, if it fits in the specified options
|
||||||
|
*
|
||||||
|
* @param array $data Already collected nodes
|
||||||
|
* @param string $base Where to start the search, usually this is $conf['datadir']
|
||||||
|
* @param string $file Current file or directory relative to $base
|
||||||
|
* @param string $type Type either 'd' for directory or 'f' for file
|
||||||
|
* @param int $lvl Current recursion depth
|
||||||
|
* @param array $opts Option array as given to search():<ul>
|
||||||
|
* <li>$opts['skipns'] string regexp matching namespaceids to skip (ignored),</li>
|
||||||
|
* <li>$opts['skipfile'] string regexp matching pageids to skip (ignored),</li>
|
||||||
|
* <li>$opts['skipnscombined'] array regexp matching namespaceids to skip,</li>
|
||||||
|
* <li>$opts['skipfilecombined'] array regexp matching pageids to skip,</li>
|
||||||
|
* <li>$opts['headpage'] string headpages options or pageids,</li>
|
||||||
|
* <li>$opts['level'] int desired depth of main namespace, -1 = all levels,</li>
|
||||||
|
* <li>$opts['subnss'] array with entries: array(namespaceid,level) specifying namespaces with their own number
|
||||||
|
* of opened levels,</li>
|
||||||
|
* <li>$opts['nons'] bool Exclude namespace nodes,</li>
|
||||||
|
* <li>$opts['max'] int If initially closed, the node at max level will retrieve all its child nodes through
|
||||||
|
* the AJAX mechanism,</li>
|
||||||
|
* <li>$opts['nopg'] bool Exclude page nodes,</li>
|
||||||
|
* <li>$opts['hide_headpage'] int don't hide (0) or hide (1),</li>
|
||||||
|
* <li>$opts['js'] bool use js-render</li>
|
||||||
|
* </ul>
|
||||||
|
* @return bool if this directory should be traversed (true) or not (false)
|
||||||
|
*
|
||||||
|
* @author Andreas Gohr <andi@splitbrain.org>
|
||||||
|
* modified by Samuele Tognini <samuele@samuele.netsons.org>
|
||||||
|
*/
|
||||||
|
public function searchIndexmenuItems(&$data, $base, $file, $type, $lvl, $opts)
|
||||||
|
{
|
||||||
|
global $conf;
|
||||||
|
|
||||||
|
$hns = false;
|
||||||
|
$isOpen = false;
|
||||||
|
$title = null;
|
||||||
|
$skipns = $opts['skipnscombined'];
|
||||||
|
$skipfile = $opts['skipfilecombined'];
|
||||||
|
$headpage = $opts['headpage'];
|
||||||
|
$id = pathID($file);
|
||||||
|
|
||||||
|
if ($type == 'd') {
|
||||||
|
// Skip folders in plugin conf
|
||||||
|
foreach ($skipns as $skipn) {
|
||||||
|
if (!empty($skipn) && preg_match($skipn, $id)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//check ACL (for sneaky_index namespaces too).
|
||||||
|
if ($conf['sneaky_index'] && auth_quickaclcheck($id . ':') < AUTH_READ) return false;
|
||||||
|
|
||||||
|
//Open requested level
|
||||||
|
if ($opts['level'] > $lvl || $opts['level'] == -1) {
|
||||||
|
$isOpen = true;
|
||||||
|
}
|
||||||
|
//Search optional subnamespaces with
|
||||||
|
if (!empty($opts['subnss'])) {
|
||||||
|
$subnss = $opts['subnss'];
|
||||||
|
$counter = count($subnss);
|
||||||
|
for ($a = 0; $a < $counter; $a++) {
|
||||||
|
if (preg_match("/^" . $id . "($|:.+)/i", $subnss[$a][0], $match)) {
|
||||||
|
//It contains a subnamespace
|
||||||
|
$isOpen = true;
|
||||||
|
} elseif (preg_match("/^" . $subnss[$a][0] . "(:.*)/i", $id, $match)) {
|
||||||
|
//It's inside a subnamespace, check level
|
||||||
|
// -1 is open all, otherwise count number of levels in the remainer of the pageid
|
||||||
|
// (match[0] is always prefixed with :)
|
||||||
|
if ($subnss[$a][1] == -1 || substr_count($match[1], ":") < $subnss[$a][1]) {
|
||||||
|
$isOpen = true;
|
||||||
|
} else {
|
||||||
|
$isOpen = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//decide if it should be traversed
|
||||||
|
if ($opts['nons']) {
|
||||||
|
return $isOpen; // in nons, level is only way to show/hide nodes (in nons nodes are not expandable)
|
||||||
|
} elseif ($opts['max'] > 0 && !$isOpen && $lvl >= $opts['max']) {
|
||||||
|
//Stop recursive searching
|
||||||
|
$shouldBeTraversed = false;
|
||||||
|
//change type
|
||||||
|
$type = "l";
|
||||||
|
} elseif ($opts['js']) {
|
||||||
|
$shouldBeTraversed = true; //TODO if js tree, then traverse deeper???
|
||||||
|
} else {
|
||||||
|
$shouldBeTraversed = $isOpen;
|
||||||
|
}
|
||||||
|
//Set title and headpage
|
||||||
|
$title = static::getNamespaceTitle($id, $headpage, $hns);
|
||||||
|
// when excluding page nodes: guess a headpage based on the headpage setting
|
||||||
|
if ($opts['nopg'] && $hns === false) {
|
||||||
|
$hns = $this->guessHeadpage($headpage, $id);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//Nopg. Dont show pages
|
||||||
|
if ($opts['nopg']) return false;
|
||||||
|
|
||||||
|
$shouldBeTraversed = true;
|
||||||
|
//Nons.Set all pages at first level
|
||||||
|
if ($opts['nons']) {
|
||||||
|
$lvl = 1;
|
||||||
|
}
|
||||||
|
//don't add
|
||||||
|
if (substr($file, -4) != '.txt') return false;
|
||||||
|
//check hiddens and acl
|
||||||
|
if (isHiddenPage($id) || auth_quickaclcheck($id) < AUTH_READ) return false;
|
||||||
|
//Skip files in plugin conf
|
||||||
|
foreach ($skipfile as $skipf) {
|
||||||
|
if (!empty($skipf) && preg_match($skipf, $id)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Skip headpages to hide (nons has no namespace nodes, therefore, no duplicated links to headpage)
|
||||||
|
if (!$opts['nons'] && !empty($headpage) && $opts['hide_headpage']) {
|
||||||
|
//start page is in root
|
||||||
|
if ($id == $conf['start']) return false;
|
||||||
|
|
||||||
|
$ahp = explode(",", $headpage);
|
||||||
|
foreach ($ahp as $hp) {
|
||||||
|
switch ($hp) {
|
||||||
|
case ":inside:":
|
||||||
|
if (noNS($id) == noNS(getNS($id))) return false;
|
||||||
|
break;
|
||||||
|
case ":same:":
|
||||||
|
if (@is_dir(dirname(wikiFN($id)) . "/" . utf8_encodeFN(noNS($id)))) return false;
|
||||||
|
break;
|
||||||
|
//it' s an inside start
|
||||||
|
case ":start:":
|
||||||
|
if (noNS($id) == $conf['start']) return false;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (noNS($id) == cleanID($hp)) return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Set title
|
||||||
|
if ($conf['useheading'] == 1 || $conf['useheading'] === 'navigation') {
|
||||||
|
$title = p_get_first_heading($id, false);
|
||||||
|
}
|
||||||
|
if (is_null($title)) {
|
||||||
|
$title = noNS($id);
|
||||||
|
}
|
||||||
|
$title = hsc($title);
|
||||||
|
}
|
||||||
|
|
||||||
|
$item = [
|
||||||
|
'id' => $id,
|
||||||
|
'type' => $type,
|
||||||
|
'level' => $lvl,
|
||||||
|
'open' => $isOpen,
|
||||||
|
'title' => $title,
|
||||||
|
'hns' => $hns,
|
||||||
|
'file' => $file,
|
||||||
|
'shouldBeTraversed' => $shouldBeTraversed
|
||||||
|
];
|
||||||
|
$item['sort'] = $this->getSortValue($item);
|
||||||
|
$data[] = $item;
|
||||||
|
|
||||||
|
return $shouldBeTraversed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback that adds an item of namespace/page to the browsable index, if it fits in the specified options
|
||||||
|
*
|
||||||
|
* TODO Version as used for Fancytree js tree
|
||||||
|
*
|
||||||
|
* @param array $data indexed array of collected nodes, each item has:<ul>
|
||||||
|
* <li>$item['id'] string namespace or page id</li>
|
||||||
|
* <li>$item['type'] string f/d/l</li>
|
||||||
|
* <li>$item['level'] string current recursion depth (start count at 1)</li>
|
||||||
|
* <li>$item['open'] bool if a node is open</li>
|
||||||
|
* <li>$item['title'] string </li>
|
||||||
|
* <li>$item['hns'] string|false page id or false</li>
|
||||||
|
* <li>$item['hnsExists'] bool only false if hns is guessed(not-existing) for nopg</li>
|
||||||
|
* <li>$item['file'] string path to file or directory</li>
|
||||||
|
* <li>$item['shouldBeTraversed'] bool directory should be searched</li>
|
||||||
|
* <li>$item['sort'] mixed sort value</li>
|
||||||
|
* </ul>
|
||||||
|
* @param string $base Where to start the search, usually this is $conf['datadir']
|
||||||
|
* @param string $file Current file or directory relative to $base
|
||||||
|
* @param string $type Type either 'd' for directory or 'f' for file
|
||||||
|
* @param int $lvl Current recursion depth
|
||||||
|
* @param array $opts Option array as given to search()<ul>
|
||||||
|
* <li>$opts['skipns'] string regexp matching namespaceids to skip (ignored)</li>
|
||||||
|
* <li>$opts['skipfile'] string regexp matching pageids to skip (ignored)</li>
|
||||||
|
* <li>$opts['skipnscombined'] array regexp matching namespaceids to skip</li>
|
||||||
|
* <li>$opts['skipfilecombined'] array regexp matching pageids to skip</li>
|
||||||
|
* <li>$opts['headpage'] string headpages options or pageids</li>
|
||||||
|
* <li>$opts['level'] int desired depth of main namespace, -1 = all levels</li>
|
||||||
|
* <li>$opts['subnss'] array with entries: array(namespaceid,level) specifying namespaces with their
|
||||||
|
* own level</li>
|
||||||
|
* <li>$opts['nons'] bool exclude namespace nodes</li>
|
||||||
|
* <li>$opts['max'] int If initially closed, the node at max level will retrieve all its child nodes
|
||||||
|
* through the AJAX mechanism</li>
|
||||||
|
* <li>$opts['nopg'] bool exclude page nodes</li>
|
||||||
|
* <li>$opts['hide_headpage'] int don't hide (0) or hide (1)</li>
|
||||||
|
* </ul>
|
||||||
|
* @return bool if this directory should be traversed (true) or not (false)
|
||||||
|
*
|
||||||
|
* @author Andreas Gohr <andi@splitbrain.org>
|
||||||
|
* modified by Samuele Tognini <samuele@samuele.netsons.org>
|
||||||
|
*/
|
||||||
|
public function searchIndexmenuItemsNew(&$data, $base, $file, $type, $lvl, $opts)
|
||||||
|
{
|
||||||
|
global $conf;
|
||||||
|
|
||||||
|
$hns = false;
|
||||||
|
$isOpen = false;
|
||||||
|
$title = null;
|
||||||
|
$skipns = $opts['skipnscombined'];
|
||||||
|
$skipfile = $opts['skipfilecombined'];
|
||||||
|
$headpage = $opts['headpage'];
|
||||||
|
$hnsExists = true; //nopg guesses pages
|
||||||
|
$id = pathID($file);
|
||||||
|
|
||||||
|
if ($type == 'd') {
|
||||||
|
// Skip folders in plugin conf
|
||||||
|
foreach ($skipns as $skipn) {
|
||||||
|
if (!empty($skipn) && preg_match($skipn, $id)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//check ACL (for sneaky_index namespaces too).
|
||||||
|
if ($conf['sneaky_index'] && auth_quickaclcheck($id . ':') < AUTH_READ) return false;
|
||||||
|
|
||||||
|
//Open requested level
|
||||||
|
if ($opts['level'] > $lvl || $opts['level'] == -1) {
|
||||||
|
$isOpen = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Search optional subnamespaces with
|
||||||
|
$isFolderAdjacentToSubNss = false;
|
||||||
|
if (!empty($opts['subnss'])) {
|
||||||
|
$subnss = $opts['subnss'];
|
||||||
|
$counter = count($subnss);
|
||||||
|
|
||||||
|
for ($a = 0; $a < $counter; $a++) {
|
||||||
|
if (preg_match("/^" . $id . "($|:.+)/i", $subnss[$a][0], $match)) {
|
||||||
|
//this folder contains a subnamespace
|
||||||
|
$isOpen = true;
|
||||||
|
} elseif (preg_match("/^" . $subnss[$a][0] . "(:.*)/i", $id, $match)) {
|
||||||
|
//this folder is inside a subnamespace, check level
|
||||||
|
if ($subnss[$a][1] == -1 || substr_count($match[1], ":") < $subnss[$a][1]) {
|
||||||
|
$isOpen = true;
|
||||||
|
} else {
|
||||||
|
$isOpen = false;
|
||||||
|
}
|
||||||
|
} elseif (
|
||||||
|
preg_match(
|
||||||
|
"/^" . (($ns = getNS($id)) === false ? '' : $ns) . "($|:.+)/i",
|
||||||
|
$subnss[$a][0],
|
||||||
|
$match
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
// parent folder contains a subnamespace, if level deeper it does not match anymore
|
||||||
|
// that is handled with normal >max handling
|
||||||
|
$isOpen = false;
|
||||||
|
if ($opts['max'] > 0) {
|
||||||
|
$isFolderAdjacentToSubNss = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//decide if it should be traversed
|
||||||
|
if ($opts['nons']) {
|
||||||
|
return $isOpen; // in nons, level is only way to show/hide nodes (in nons nodes are not expandable)
|
||||||
|
} elseif ($opts['max'] > 0 && !$isOpen) { // note: for Fancytree >=1 is used
|
||||||
|
// limited levels per request, node is closed
|
||||||
|
if ($lvl == $opts['max'] || $isFolderAdjacentToSubNss) {
|
||||||
|
// change type, more nodes should be loaded by ajax, but for nopg we need extra level to determine
|
||||||
|
// if folder is empty
|
||||||
|
// and folders adjacent to subns must be traversed as well
|
||||||
|
$type = "l";
|
||||||
|
$shouldBeTraversed = true;
|
||||||
|
} elseif ($lvl > $opts['max']) { // deeper lvls only used temporary for checking existance children
|
||||||
|
//change type, more nodes should be loaded by ajax
|
||||||
|
$type = "l"; // use lazy loading
|
||||||
|
$shouldBeTraversed = false;
|
||||||
|
} else {
|
||||||
|
//node is closed, but still more levels requested with max
|
||||||
|
$shouldBeTraversed = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$shouldBeTraversed = $isOpen;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Set title and headpage
|
||||||
|
$title = static::getNamespaceTitle($id, $headpage, $hns);
|
||||||
|
|
||||||
|
// when excluding page nodes: guess a headpage based on the headpage setting
|
||||||
|
if ($opts['nopg'] && $hns === false) {
|
||||||
|
$hns = $this->guessHeadpage($headpage, $id);
|
||||||
|
$hnsExists = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//Nopg.Dont show pages
|
||||||
|
if ($opts['nopg']) return false;
|
||||||
|
|
||||||
|
$shouldBeTraversed = true;
|
||||||
|
//Nons.Set all pages at first level
|
||||||
|
if ($opts['nons']) {
|
||||||
|
$lvl = 1;
|
||||||
|
}
|
||||||
|
//don't add
|
||||||
|
if (substr($file, -4) != '.txt') return false;
|
||||||
|
//check hiddens and acl
|
||||||
|
if (isHiddenPage($id) || auth_quickaclcheck($id) < AUTH_READ) return false;
|
||||||
|
//Skip files in plugin conf
|
||||||
|
foreach ($skipfile as $skipf) {
|
||||||
|
if (!empty($skipf) && preg_match($skipf, $id)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Skip headpages to hide
|
||||||
|
if (!$opts['nons'] && !empty($headpage) && $opts['hide_headpage']) {
|
||||||
|
//start page is in root
|
||||||
|
if ($id == $conf['start']) return false;
|
||||||
|
|
||||||
|
$hpOptions = explode(",", $headpage);
|
||||||
|
foreach ($hpOptions as $hp) {
|
||||||
|
switch ($hp) {
|
||||||
|
case ":inside:":
|
||||||
|
if (noNS($id) == noNS(getNS($id))) return false;
|
||||||
|
break;
|
||||||
|
case ":same:":
|
||||||
|
if (@is_dir(dirname(wikiFN($id)) . "/" . utf8_encodeFN(noNS($id)))) return false;
|
||||||
|
break;
|
||||||
|
//it' s an inside start
|
||||||
|
case ":start:":
|
||||||
|
if (noNS($id) == $conf['start']) return false;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (noNS($id) == cleanID($hp)) return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Set title
|
||||||
|
if ($conf['useheading'] == 1 || $conf['useheading'] === 'navigation') {
|
||||||
|
$title = p_get_first_heading($id, false);
|
||||||
|
}
|
||||||
|
if (is_null($title)) {
|
||||||
|
$title = noNS($id);
|
||||||
|
}
|
||||||
|
$title = hsc($title);
|
||||||
|
}
|
||||||
|
|
||||||
|
$item = [
|
||||||
|
'id' => $id,
|
||||||
|
'type' => $type,
|
||||||
|
'level' => $lvl,
|
||||||
|
'open' => $isOpen,
|
||||||
|
'title' => $title,
|
||||||
|
'hns' => $hns,
|
||||||
|
'hnsExists' => $hnsExists,
|
||||||
|
'file' => $file,
|
||||||
|
'shouldBeTraversed' => $shouldBeTraversed
|
||||||
|
];
|
||||||
|
$item['sort'] = $this->getSortValue($item);
|
||||||
|
$data[] = $item;
|
||||||
|
|
||||||
|
return $shouldBeTraversed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* callback that recurse directory
|
||||||
|
*
|
||||||
|
* This function recurses into a given base directory
|
||||||
|
* and calls the supplied function for each file and directory
|
||||||
|
*
|
||||||
|
* Similar to search() of inc/search.php, but has extended sorting options
|
||||||
|
*
|
||||||
|
* @param array $data The results of the search are stored here
|
||||||
|
* @param string $base Where to start the search
|
||||||
|
* @param callback $func Callback (function name or array with object,method)
|
||||||
|
* @param array $opts List of indexmenu options
|
||||||
|
* @param string $dir Current directory beyond $base
|
||||||
|
* @param int $lvl Recursion Level
|
||||||
|
*
|
||||||
|
* @author Andreas Gohr <andi@splitbrain.org>
|
||||||
|
* @author modified by Samuele Tognini <samuele@samuele.netsons.org>
|
||||||
|
*/
|
||||||
|
public function customSearch(&$data, $base, $func, $opts, $dir = '', $lvl = 1)
|
||||||
|
{
|
||||||
|
$dirs = [];
|
||||||
|
$files = [];
|
||||||
|
$files_tmp = [];
|
||||||
|
$dirs_tmp = [];
|
||||||
|
$count = count($data);
|
||||||
|
|
||||||
|
//read in directories and files
|
||||||
|
$dh = @opendir($base . '/' . $dir);
|
||||||
|
if (!$dh) return;
|
||||||
|
while (($file = readdir($dh)) !== false) {
|
||||||
|
//skip hidden files and upper dirs
|
||||||
|
if (preg_match('/^[._]/', $file)) continue;
|
||||||
|
if (is_dir($base . '/' . $dir . '/' . $file)) {
|
||||||
|
$dirs[] = $dir . '/' . $file;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$files[] = $dir . '/' . $file;
|
||||||
|
}
|
||||||
|
closedir($dh);
|
||||||
|
|
||||||
|
//Collect and sort files
|
||||||
|
foreach ($files as $file) {
|
||||||
|
call_user_func_array($func, [&$files_tmp, $base, $file, 'f', $lvl, $opts]);
|
||||||
|
}
|
||||||
|
usort($files_tmp, [$this, "compareNodes"]);
|
||||||
|
|
||||||
|
//Collect and sort dirs
|
||||||
|
if ($this->nsort) {
|
||||||
|
//collect the wanted directories in dirs_tmp
|
||||||
|
foreach ($dirs as $dir) {
|
||||||
|
call_user_func_array($func, [&$dirs_tmp, $base, $dir, 'd', $lvl, $opts]);
|
||||||
|
}
|
||||||
|
//combine directories and pages and sort together
|
||||||
|
$dirsAndFiles = array_merge($dirs_tmp, $files_tmp);
|
||||||
|
usort($dirsAndFiles, [$this, "compareNodes"]);
|
||||||
|
|
||||||
|
//add and search each directory
|
||||||
|
foreach ($dirsAndFiles as $dirOrFile) {
|
||||||
|
$data[] = $dirOrFile;
|
||||||
|
if ($dirOrFile['type'] != 'f' && $dirOrFile['shouldBeTraversed']) {
|
||||||
|
$this->customSearch($data, $base, $func, $opts, $dirOrFile['file'], $lvl + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//sort by directory name
|
||||||
|
Sort::sort($dirs);
|
||||||
|
//collect directories
|
||||||
|
foreach ($dirs as $dir) {
|
||||||
|
if (call_user_func_array($func, [&$data, $base, $dir, 'd', $lvl, $opts])) {
|
||||||
|
$this->customSearch($data, $base, $func, $opts, $dir, $lvl + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//count added items
|
||||||
|
$added = count($data) - $count;
|
||||||
|
|
||||||
|
if ($added === 0 && $files_tmp === []) {
|
||||||
|
//remove empty directory again, only if it has not a headpage associated
|
||||||
|
$lastItem = end($data);
|
||||||
|
if (!$lastItem['hns']) {
|
||||||
|
array_pop($data);
|
||||||
|
}
|
||||||
|
} elseif (!$this->nsort) {
|
||||||
|
//add files to index
|
||||||
|
$data = array_merge($data, $files_tmp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get namespace title, checking for headpages
|
||||||
|
*
|
||||||
|
* @param string $ns namespace
|
||||||
|
* @param string $headpage comma-separated headpages options and headpages
|
||||||
|
* @param string|false $hns reference pageid of headpage, false when not existing
|
||||||
|
* @return string when headpage & heading on: title of headpage, otherwise: namespace name
|
||||||
|
*
|
||||||
|
* @author Samuele Tognini <samuele@samuele.netsons.org>
|
||||||
|
*/
|
||||||
|
public static function getNamespaceTitle($ns, $headpage, &$hns)
|
||||||
|
{
|
||||||
|
global $conf;
|
||||||
|
$hns = false;
|
||||||
|
$title = noNS($ns);
|
||||||
|
if (empty($headpage)) {
|
||||||
|
return $title;
|
||||||
|
}
|
||||||
|
$hpOptions = explode(",", $headpage);
|
||||||
|
foreach ($hpOptions as $hp) {
|
||||||
|
switch ($hp) {
|
||||||
|
case ":inside:":
|
||||||
|
$page = $ns . ":" . noNS($ns);
|
||||||
|
break;
|
||||||
|
case ":same:":
|
||||||
|
$page = $ns;
|
||||||
|
break;
|
||||||
|
//it's an inside start
|
||||||
|
case ":start:":
|
||||||
|
$page = ltrim($ns . ":" . $conf['start'], ":");
|
||||||
|
break;
|
||||||
|
//inside pages
|
||||||
|
default:
|
||||||
|
if (!blank($hp)) { //empty setting results in empty string here
|
||||||
|
$page = $ns . ":" . $hp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//check headpage
|
||||||
|
if (@file_exists(wikiFN($page)) && auth_quickaclcheck($page) >= AUTH_READ) {
|
||||||
|
if ($conf['useheading'] == 1 || $conf['useheading'] === 'navigation') {
|
||||||
|
$title_tmp = p_get_first_heading($page, false);
|
||||||
|
if (!is_null($title_tmp)) {
|
||||||
|
$title = $title_tmp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$title = hsc($title);
|
||||||
|
$hns = $page;
|
||||||
|
//headpage found, exit for
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $title;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* callback that sorts nodes
|
||||||
|
*
|
||||||
|
* @param array $a first node as array with 'sort' entry
|
||||||
|
* @param array $b second node as array with 'sort' entry
|
||||||
|
* @return int if less than zero 1st node is less than 2nd, otherwise equal respectively larger
|
||||||
|
*/
|
||||||
|
private function compareNodes($a, $b)
|
||||||
|
{
|
||||||
|
if ($this->rsort) {
|
||||||
|
return Sort::strcmp($b['sort'], $a['sort']);
|
||||||
|
} else {
|
||||||
|
return Sort::strcmp($a['sort'], $b['sort']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add sort information to item.
|
||||||
|
*
|
||||||
|
* @param array $item
|
||||||
|
* @return bool|int|mixed|string
|
||||||
|
*
|
||||||
|
* @author Samuele Tognini <samuele@samuele.netsons.org>
|
||||||
|
*/
|
||||||
|
private function getSortValue($item)
|
||||||
|
{
|
||||||
|
global $conf;
|
||||||
|
|
||||||
|
$sort = false;
|
||||||
|
$page = false;
|
||||||
|
if ($item['type'] == 'd' || $item['type'] == 'l') {
|
||||||
|
//Fake order info when nsort is not requested
|
||||||
|
if ($this->nsort) {
|
||||||
|
$page = $item['hns'];
|
||||||
|
} else {
|
||||||
|
$sort = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($item['type'] == 'f') {
|
||||||
|
$page = $item['id'];
|
||||||
|
}
|
||||||
|
if ($page) {
|
||||||
|
if ($this->hsort && noNS($item['id']) == $conf['start']) {
|
||||||
|
$sort = 1;
|
||||||
|
}
|
||||||
|
if ($this->msort) {
|
||||||
|
$sort = p_get_metadata($page, $this->msort);
|
||||||
|
}
|
||||||
|
if (!$sort && $this->sort) {
|
||||||
|
switch ($this->sort) {
|
||||||
|
case 't':
|
||||||
|
$sort = $item['title'];
|
||||||
|
break;
|
||||||
|
case 'd':
|
||||||
|
$sort = @filectime(wikiFN($page));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($sort === false) {
|
||||||
|
$sort = noNS($item['id']);
|
||||||
|
}
|
||||||
|
return $sort;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Guess based on first option of the headpage config setting (default :start: if enabled) the headpage of the node
|
||||||
|
*
|
||||||
|
* @param string $headpage config setting
|
||||||
|
* @param string $ns namespace
|
||||||
|
* @return string guessed headpage
|
||||||
|
*/
|
||||||
|
private function guessHeadpage(string $headpage, string $ns): string
|
||||||
|
{
|
||||||
|
global $conf;
|
||||||
|
$hns = false;
|
||||||
|
|
||||||
|
$hpOptions = explode(",", $headpage);
|
||||||
|
foreach ($hpOptions as $hp) {
|
||||||
|
switch ($hp) {
|
||||||
|
case ":inside:":
|
||||||
|
$hns = $ns . ":" . noNS($ns);
|
||||||
|
break 2;
|
||||||
|
case ":same:":
|
||||||
|
$hns = $ns;
|
||||||
|
break 2;
|
||||||
|
//it's an inside start
|
||||||
|
case ":start:":
|
||||||
|
$hns = ltrim($ns . ":" . $conf['start'], ":");
|
||||||
|
break 2;
|
||||||
|
//inside pages
|
||||||
|
default:
|
||||||
|
if (!blank($hp)) {
|
||||||
|
$hns = $ns . ":" . $hp;
|
||||||
|
break 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($hns === false) {
|
||||||
|
//fallback to start if headpage setting was empty
|
||||||
|
$hns = ltrim($ns . ":" . $conf['start'], ":");
|
||||||
|
}
|
||||||
|
return $hns;
|
||||||
|
}
|
||||||
|
}
|
35
plugins/55/indexmenu/_test/ActionTest.php
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test namespace includes
|
||||||
|
*
|
||||||
|
* @group plugin_indexmenu
|
||||||
|
* @group plugins
|
||||||
|
*/
|
||||||
|
class ActionTest extends DokuWikiTest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Setup - enable and load the include plugin and create the test pages
|
||||||
|
*/
|
||||||
|
public function setUp(): void
|
||||||
|
{
|
||||||
|
$this->pluginsEnabled[] = 'indexmenu';
|
||||||
|
parent::setUp(); // this enables the include plugin
|
||||||
|
// $this->helper = plugin_load('helper', 'include');
|
||||||
|
|
||||||
|
// global $conf;
|
||||||
|
// $conf['hidepages'] = 'inclhidden:hidden';
|
||||||
|
|
||||||
|
// for testing hidden pages
|
||||||
|
saveWikiText('ns2:bpage', "======H1======\nText", 'Sort different naturally/title/page');
|
||||||
|
saveWikiText('ns2:apage', "======H3======\nText", 'Sort different naturally/title/page');
|
||||||
|
saveWikiText('ns2:cpage', "======H2======\nText", 'Sort different naturally/title/page');
|
||||||
|
|
||||||
|
// pages on different levels
|
||||||
|
saveWikiText('ns1:ns1:apage', 'Page on level 1', 'Created page on level 1');
|
||||||
|
saveWikiText('ns1:lvl2:lvl3:lvl4:apage', 'Page on level 4', 'Created page on level 4');
|
||||||
|
saveWikiText('ns1:ns2:apage', 'Page on level 2', 'Created page on level 2');
|
||||||
|
saveWikiText('ns1:ns0:bpage', 'Page on level 2', 'Created page on level 2');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
345
plugins/55/indexmenu/_test/AjaxRequestsTest.php
Normal file
|
@ -0,0 +1,345 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test sorting
|
||||||
|
*
|
||||||
|
* Principle copied from _test/tests/lib/exe/ajax_requests.test.php
|
||||||
|
*
|
||||||
|
* @group ajax
|
||||||
|
* @group plugin_indexmenu
|
||||||
|
* @group plugins
|
||||||
|
*/
|
||||||
|
class AjaxRequestsTest extends DokuWikiTest
|
||||||
|
{
|
||||||
|
public function setUp(): void
|
||||||
|
{
|
||||||
|
$this->pluginsEnabled[] = 'indexmenu';
|
||||||
|
parent::setUp(); // this enables the indexmenu plugin
|
||||||
|
|
||||||
|
//needed for 'tsort' to use First headings, sets title during search, otherwise as fallback page name used.
|
||||||
|
global $conf;
|
||||||
|
$conf['useheading'] = 'navigation';
|
||||||
|
|
||||||
|
|
||||||
|
// for testing sorting pages
|
||||||
|
saveWikiText('ns2:cpage', "======Bb======\nText", 'Sort different page/title/creation date');
|
||||||
|
sleep(1); // ensure different timestamps for 'dsort'
|
||||||
|
saveWikiText('ns2:bpage', "======Aa======\nText", 'Sort different page/title/creation date');
|
||||||
|
sleep(1);
|
||||||
|
saveWikiText('ns2:apage', "======Cc======\nText", 'Sort different page/title/creation date');
|
||||||
|
|
||||||
|
//ensures title is added to metadata of page
|
||||||
|
idx_addPage('ns2:cpage');
|
||||||
|
idx_addPage('ns2:bpage');
|
||||||
|
idx_addPage('ns2:apage');
|
||||||
|
|
||||||
|
// pages on different levels
|
||||||
|
saveWikiText('ns1:ns2:apage', "======Bb======\nPage on level 2", 'Created page on level 2');
|
||||||
|
saveWikiText('ns1:ns1:apage', "======Ee======\nPage on level 2", 'Created page on level 2');
|
||||||
|
saveWikiText('ns1:ns1:lvl3:lvl4:apage', "======Cc======\nPage on levl 4", 'Page on level 4');
|
||||||
|
saveWikiText('ns1:ns1:start', "======Aa======\nPage on level 2", 'Startpage on level 2');
|
||||||
|
saveWikiText('ns1:ns0:bpage', "======Aa2======\nPage on level 2", 'Created page on level 2');
|
||||||
|
saveWikiText('ns1:apage', "======Dd======\nPage on level 1", 'Created page on level 1');
|
||||||
|
|
||||||
|
//ensures title is added to metadata
|
||||||
|
idx_addPage('ns1:ns1:apage');
|
||||||
|
idx_addPage('ns1:ns1:lvl3:lvl4:apage');
|
||||||
|
idx_addPage('ns1:ns1:start');
|
||||||
|
idx_addPage('ns1:ns2:apage');
|
||||||
|
idx_addPage('ns1:ns0:bpage');
|
||||||
|
idx_addPage('ns1:apage');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DataProvider for the builtin Ajax calls
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function indexmenuCalls()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
// Call, POST parameters, result function
|
||||||
|
[
|
||||||
|
'indexmenu',
|
||||||
|
AjaxRequestsTest::prepareParams(['level' => 1]),
|
||||||
|
'expectedResultWiki'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'indexmenu',
|
||||||
|
AjaxRequestsTest::prepareParams(['ns' => 'ns2', 'level' => 1]),
|
||||||
|
'expectedResultNs2PageSort'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'indexmenu',
|
||||||
|
AjaxRequestsTest::prepareParams(['ns' => 'ns2', 'level' => 1, 'sort' => 't']),
|
||||||
|
'expectedResultNs2TitleSort'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'indexmenu',
|
||||||
|
AjaxRequestsTest::prepareParams(['ns' => 'ns2', 'level' => 1, 'sort' => 'd']),
|
||||||
|
'expectedResultNs2CreationDateSort'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'indexmenu',
|
||||||
|
AjaxRequestsTest::prepareParams(['ns' => 'ns1', 'level' => 1, 'sort' => 't']),
|
||||||
|
'expectedResultNs1TitleSort'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'indexmenu',
|
||||||
|
AjaxRequestsTest::prepareParams(['ns' => 'ns1', 'level' => 1, 'sort' => 't', 'nsort' => 1]),
|
||||||
|
'expectedResultNs1TitleSortNamespaceSort'
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider indexmenuCalls
|
||||||
|
*
|
||||||
|
* @param string $call
|
||||||
|
* @param array $post
|
||||||
|
* @param $expectedResult
|
||||||
|
*/
|
||||||
|
public function testBasicSorting($call, $post, $expectedResult)
|
||||||
|
{
|
||||||
|
$request = new TestRequest();
|
||||||
|
$response = $request->post(['call' => $call] + $post, '/lib/exe/ajax.php');
|
||||||
|
// $this->assertNotEquals("AJAX call '$call' unknown!\n", $response->getContent());
|
||||||
|
|
||||||
|
//var_export(json_decode($response->getContent()), true); // print as PHP array
|
||||||
|
|
||||||
|
$actualArray = json_decode($response->getContent(), true);
|
||||||
|
unset($actualArray['debug']);
|
||||||
|
unset($actualArray['sort']);
|
||||||
|
unset($actualArray['opts']);
|
||||||
|
|
||||||
|
$this->assertEquals($this->$expectedResult(), $actualArray);
|
||||||
|
|
||||||
|
// $regexp: null, or regexp pattern to match
|
||||||
|
// example: '/^<div class="odd type_d/'
|
||||||
|
// if (!empty($regexp)) {
|
||||||
|
// $this->assertRegExp($regexp, $response->getContent());
|
||||||
|
// }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_params()
|
||||||
|
{
|
||||||
|
// print_r(AjaxRequestsTest::prepareParams(['level' => 2]));
|
||||||
|
|
||||||
|
$this->assertTrue(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function prepareParams($params = [])
|
||||||
|
{
|
||||||
|
$defaults = [
|
||||||
|
'ns' => 'wiki',
|
||||||
|
'req' => 'fancytree',
|
||||||
|
'level' => 1,
|
||||||
|
'nons' => 0,
|
||||||
|
'nopg' => 0,
|
||||||
|
'max' => 0,
|
||||||
|
'skipns' => ['/^board:(first|second|third|fourth|fifth)$/'],
|
||||||
|
'skipfile' => ['/(:start$)/'],
|
||||||
|
'sort' => 0,
|
||||||
|
'msort' => 0,
|
||||||
|
'rsort' => 0,
|
||||||
|
'nsort' => 0,
|
||||||
|
'hsort' => 0,
|
||||||
|
'init' => 1
|
||||||
|
];
|
||||||
|
$return = [];
|
||||||
|
foreach ($defaults as $key => $default) {
|
||||||
|
$return[$key] = $params[$key] ?? $default;
|
||||||
|
}
|
||||||
|
return $return;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function expectedResultWiki()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'children' => [
|
||||||
|
0 => [
|
||||||
|
'title' => 'dokuwiki',
|
||||||
|
'key' => 'wiki:dokuwiki',
|
||||||
|
'hns' => false,
|
||||||
|
'url' => '/./doku.php?id=wiki:dokuwiki'
|
||||||
|
],
|
||||||
|
1 => [
|
||||||
|
'title' => 'syntax',
|
||||||
|
'key' => 'wiki:syntax',
|
||||||
|
'hns' => false,
|
||||||
|
'url' => '/./doku.php?id=wiki:syntax'
|
||||||
|
]
|
||||||
|
]];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function expectedResultNs1()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'children' => [
|
||||||
|
0 => [
|
||||||
|
'title' => 'dokuwiki',
|
||||||
|
'key' => 'wiki:dokuwiki',
|
||||||
|
'hns' => false,
|
||||||
|
'url' => '/./doku.php?id=wiki:dokuwiki'
|
||||||
|
],
|
||||||
|
1 => [
|
||||||
|
'title' => 'syntax',
|
||||||
|
'key' => 'wiki:syntax',
|
||||||
|
'hns' => false,
|
||||||
|
'url' => '/./doku.php?id=wiki:syntax'
|
||||||
|
]
|
||||||
|
]];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function expectedResultNs2PageSort()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'children' => [
|
||||||
|
0 => [
|
||||||
|
'title' => 'Cc',
|
||||||
|
'key' => 'ns2:apage',
|
||||||
|
'hns' => false,
|
||||||
|
'url' => '/./doku.php?id=ns2:apage'
|
||||||
|
],
|
||||||
|
1 => [
|
||||||
|
'title' => 'Aa',
|
||||||
|
'key' => 'ns2:bpage',
|
||||||
|
'hns' => false,
|
||||||
|
'url' => '/./doku.php?id=ns2:bpage'
|
||||||
|
],
|
||||||
|
2 => [
|
||||||
|
'title' => 'Bb',
|
||||||
|
'key' => 'ns2:cpage',
|
||||||
|
'hns' => false,
|
||||||
|
'url' => '/./doku.php?id=ns2:cpage'
|
||||||
|
]
|
||||||
|
]];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function expectedResultNs2TitleSort()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'children' => [
|
||||||
|
0 => [
|
||||||
|
'title' => 'Aa',
|
||||||
|
'key' => 'ns2:bpage',
|
||||||
|
'hns' => false,
|
||||||
|
'url' => '/./doku.php?id=ns2:bpage'
|
||||||
|
],
|
||||||
|
1 => [
|
||||||
|
'title' => 'Bb',
|
||||||
|
'key' => 'ns2:cpage',
|
||||||
|
'hns' => false,
|
||||||
|
'url' => '/./doku.php?id=ns2:cpage'
|
||||||
|
],
|
||||||
|
2 => [
|
||||||
|
'title' => 'Cc',
|
||||||
|
'key' => 'ns2:apage',
|
||||||
|
'hns' => false,
|
||||||
|
'url' => '/./doku.php?id=ns2:apage'
|
||||||
|
]
|
||||||
|
]];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function expectedResultNs2CreationDateSort()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'children' => [
|
||||||
|
0 => [
|
||||||
|
'title' => 'Bb',
|
||||||
|
'key' => 'ns2:cpage',
|
||||||
|
'hns' => false,
|
||||||
|
'url' => '/./doku.php?id=ns2:cpage'
|
||||||
|
],
|
||||||
|
1 => [
|
||||||
|
'title' => 'Aa',
|
||||||
|
'key' => 'ns2:bpage',
|
||||||
|
'hns' => false,
|
||||||
|
'url' => '/./doku.php?id=ns2:bpage'
|
||||||
|
],
|
||||||
|
2 => [
|
||||||
|
'title' => 'Cc',
|
||||||
|
'key' => 'ns2:apage',
|
||||||
|
'hns' => false,
|
||||||
|
'url' => '/./doku.php?id=ns2:apage'
|
||||||
|
]
|
||||||
|
]];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function expectedResultNs1TitleSort()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'children' => [
|
||||||
|
0 => [
|
||||||
|
'title' => 'ns0',
|
||||||
|
'key' => 'ns1:ns0:',
|
||||||
|
'hns' => false,
|
||||||
|
'folder' => true,
|
||||||
|
'lazy' => true,
|
||||||
|
'url' => false
|
||||||
|
],
|
||||||
|
1 => [
|
||||||
|
'title' => 'Aa',
|
||||||
|
'key' => 'ns1:ns1:',
|
||||||
|
'hns' => 'ns1:ns1:start',
|
||||||
|
'folder' => true,
|
||||||
|
'lazy' => true,
|
||||||
|
'url' => '/./doku.php?id=ns1:ns1:start'
|
||||||
|
],
|
||||||
|
2 => [
|
||||||
|
'title' => 'ns2',
|
||||||
|
'key' => 'ns1:ns2:',
|
||||||
|
'hns' => false,
|
||||||
|
'folder' => true,
|
||||||
|
'lazy' => true,
|
||||||
|
'url' => false
|
||||||
|
],
|
||||||
|
3 => [
|
||||||
|
'title' => 'Dd',
|
||||||
|
'key' => 'ns1:apage',
|
||||||
|
'hns' => false,
|
||||||
|
'url' => '/./doku.php?id=ns1:apage'
|
||||||
|
]
|
||||||
|
]];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function expectedResultNs1TitleSortNamespaceSort()
|
||||||
|
{
|
||||||
|
// 'nsort' let the sort explicitly use the namespace name as sort key.
|
||||||
|
// 'nsort' + 'tsort' works only for nsort if head pages are used.
|
||||||
|
return [
|
||||||
|
'children' => [
|
||||||
|
0 => [
|
||||||
|
'title' => 'Aa',
|
||||||
|
'key' => 'ns1:ns1:',
|
||||||
|
'hns' => 'ns1:ns1:start',
|
||||||
|
'folder' => true,
|
||||||
|
'lazy' => true,
|
||||||
|
'url' => '/./doku.php?id=ns1:ns1:start'
|
||||||
|
],
|
||||||
|
1 => [
|
||||||
|
'title' => 'Dd',
|
||||||
|
'key' => 'ns1:apage',
|
||||||
|
'hns' => false,
|
||||||
|
'url' => '/./doku.php?id=ns1:apage'
|
||||||
|
],
|
||||||
|
2 => [
|
||||||
|
'title' => 'ns0',
|
||||||
|
'key' => 'ns1:ns0:',
|
||||||
|
'hns' => false,
|
||||||
|
'folder' => true,
|
||||||
|
'lazy' => true,
|
||||||
|
'url' => false
|
||||||
|
],
|
||||||
|
3 => [
|
||||||
|
'title' => 'ns2',
|
||||||
|
'key' => 'ns1:ns2:',
|
||||||
|
'hns' => false,
|
||||||
|
'folder' => true,
|
||||||
|
'lazy' => true,
|
||||||
|
'url' => false
|
||||||
|
]
|
||||||
|
]];
|
||||||
|
}
|
||||||
|
}
|
86
plugins/55/indexmenu/_test/GeneralTest.php
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace dokuwiki\plugin\indexmenu\test;
|
||||||
|
|
||||||
|
use DokuWikiTest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* General tests for the indexmenu plugin
|
||||||
|
*
|
||||||
|
* @group plugin_indexmenu
|
||||||
|
* @group plugins
|
||||||
|
*/
|
||||||
|
class GeneralTest extends DokuWikiTest
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple test to make sure the plugin.info.txt is in correct format
|
||||||
|
*/
|
||||||
|
public function testPluginInfo(): void
|
||||||
|
{
|
||||||
|
$file = __DIR__ . '/../plugin.info.txt';
|
||||||
|
$this->assertFileExists($file);
|
||||||
|
|
||||||
|
$info = confToHash($file);
|
||||||
|
|
||||||
|
$this->assertArrayHasKey('base', $info);
|
||||||
|
$this->assertArrayHasKey('author', $info);
|
||||||
|
$this->assertArrayHasKey('email', $info);
|
||||||
|
$this->assertArrayHasKey('date', $info);
|
||||||
|
$this->assertArrayHasKey('name', $info);
|
||||||
|
$this->assertArrayHasKey('desc', $info);
|
||||||
|
$this->assertArrayHasKey('url', $info);
|
||||||
|
|
||||||
|
$this->assertEquals('indexmenu', $info['base']);
|
||||||
|
$this->assertRegExp('/^https?:\/\//', $info['url']);
|
||||||
|
$this->assertTrue(mail_isvalid($info['email']));
|
||||||
|
$this->assertRegExp('/^\d\d\d\d-\d\d-\d\d$/', $info['date']);
|
||||||
|
$this->assertTrue(false !== strtotime($info['date']));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test to ensure that every conf['...'] entry in conf/default.php has a corresponding meta['...'] entry in
|
||||||
|
* conf/metadata.php.
|
||||||
|
*/
|
||||||
|
public function testPluginConf(): void
|
||||||
|
{
|
||||||
|
$conf_file = __DIR__ . '/../conf/default.php';
|
||||||
|
$meta_file = __DIR__ . '/../conf/metadata.php';
|
||||||
|
|
||||||
|
if (!file_exists($conf_file) && !file_exists($meta_file)) {
|
||||||
|
self::markTestSkipped('No config files exist -> skipping test');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file_exists($conf_file)) {
|
||||||
|
include($conf_file);
|
||||||
|
}
|
||||||
|
if (file_exists($meta_file)) {
|
||||||
|
include($meta_file);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
gettype($conf),
|
||||||
|
gettype($meta),
|
||||||
|
'Both ' . DOKU_PLUGIN . 'indexmenu/conf/default.php and ' . DOKU_PLUGIN . 'indexmenu/conf/metadata.php have to exist and contain the same keys.'
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($conf !== null && $meta !== null) {
|
||||||
|
foreach ($conf as $key => $value) {
|
||||||
|
$this->assertArrayHasKey(
|
||||||
|
$key,
|
||||||
|
$meta,
|
||||||
|
'Key $meta[\'' . $key . '\'] missing in ' . DOKU_PLUGIN . 'indexmenu/conf/metadata.php'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($meta as $key => $value) {
|
||||||
|
$this->assertArrayHasKey(
|
||||||
|
$key,
|
||||||
|
$conf,
|
||||||
|
'Key $conf[\'' . $key . '\'] missing in ' . DOKU_PLUGIN . 'indexmenu/conf/default.php'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
353
plugins/55/indexmenu/_test/IndexmenuSyntaxTest.php
Normal file
|
@ -0,0 +1,353 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use DOMWrap\Document;
|
||||||
|
|
||||||
|
require_once DOKU_INC . 'inc/parser/xhtml.php';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @group plugin_indexmenu
|
||||||
|
*/
|
||||||
|
class IndexmenuSyntaxTest extends DokuWikiTest
|
||||||
|
{
|
||||||
|
|
||||||
|
public function setup(): void
|
||||||
|
{
|
||||||
|
// global $conf;
|
||||||
|
$this->pluginsEnabled[] = 'indexmenu';
|
||||||
|
parent::setup();
|
||||||
|
|
||||||
|
//$conf['plugin']['indexmenu']['headpage'] = '';
|
||||||
|
//$conf['plugin']['indexmenu']['hide_headpage'] = false;
|
||||||
|
|
||||||
|
//saveWikiText('titleonly:sub:test', "====== Title ====== \n content", 'created');
|
||||||
|
//saveWikiText('test', "====== Title ====== \n content", 'created');
|
||||||
|
//idx_addPage('titleonly:sub:test');
|
||||||
|
//idx_addPage('test');
|
||||||
|
}
|
||||||
|
|
||||||
|
// public function __construct() {
|
||||||
|
//// $this->exampleIndex = "{{indexmenu>:}}";
|
||||||
|
//
|
||||||
|
// parent::__construct();
|
||||||
|
// }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create from list of values the output array of handle()
|
||||||
|
*
|
||||||
|
* @param array $values
|
||||||
|
* @return array aligned similar to output of handle()
|
||||||
|
*/
|
||||||
|
private function createData($values)
|
||||||
|
{
|
||||||
|
|
||||||
|
[
|
||||||
|
$ns, $theme, $identifier, $nocookie, $navbar, $noscroll, $maxjs, $notoc, $jsajax, $context, $nomenu,
|
||||||
|
$sort, $msort, $rsort, $nsort, $level, $nons, $nopg, $subnss, $max, $maxAjax, $js, $skipns, $skipfile,
|
||||||
|
$skipnscombined, $skipfilecombined, $hsort, $headpage, $hide_headpage, $jsVersion
|
||||||
|
] = $values;
|
||||||
|
|
||||||
|
return [
|
||||||
|
$ns,
|
||||||
|
[
|
||||||
|
'theme' => $theme,
|
||||||
|
'identifier' => $identifier,
|
||||||
|
'nocookie' => $nocookie,
|
||||||
|
'navbar' => $navbar,
|
||||||
|
'noscroll' => $noscroll,
|
||||||
|
'maxJs' => $maxjs,
|
||||||
|
'notoc' => $notoc,
|
||||||
|
'jsAjax' => $jsajax,
|
||||||
|
'context' => $context,
|
||||||
|
'nomenu' => $nomenu,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'sort' => $sort,
|
||||||
|
'msort' => $msort,
|
||||||
|
'rsort' => $rsort,
|
||||||
|
'nsort' => $nsort,
|
||||||
|
'hsort' => $hsort,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'level' => $level,
|
||||||
|
'nons' => $nons,
|
||||||
|
'nopg' => $nopg,
|
||||||
|
'subnss' => $subnss,
|
||||||
|
'max' => $max,
|
||||||
|
'js' => $js,
|
||||||
|
'skipns' => $skipns,
|
||||||
|
'skipfile' => $skipfile,
|
||||||
|
'skipnscombined' => $skipnscombined,
|
||||||
|
'skipfilecombined' => $skipfilecombined,
|
||||||
|
'headpage' => $headpage,
|
||||||
|
'hide_headpage' => $hide_headpage,
|
||||||
|
'maxajax' => $maxAjax,
|
||||||
|
'navbar' => $navbar,
|
||||||
|
'theme' => $theme
|
||||||
|
],
|
||||||
|
$jsVersion
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data provider
|
||||||
|
*
|
||||||
|
* @return array[]
|
||||||
|
*/
|
||||||
|
public static function someSyntaxes()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
//root ns (empty is not recognized..)
|
||||||
|
// [syntax, data]
|
||||||
|
[
|
||||||
|
"{{indexmenu>:}}",
|
||||||
|
[
|
||||||
|
'', 'default', 'random', false, false, false, 1, false, '', false, false,
|
||||||
|
0, false, false, false, -1, false, false, [], 0, 1, false, '', '', [''], [''], false,
|
||||||
|
":start:,:same:,:inside:", 1, 1
|
||||||
|
]
|
||||||
|
],
|
||||||
|
//root ns, #levels=1, js renderer
|
||||||
|
[
|
||||||
|
"{{indexmenu>#1|js}}",
|
||||||
|
[
|
||||||
|
'', 'default', 'random', false, false, false, 1, false, '', false, false,
|
||||||
|
0, false, false, false, 1, false, false, [], 0, 1, true, '', '', [''], [''], false,
|
||||||
|
":start:,:same:,:inside:", 1, 1
|
||||||
|
]
|
||||||
|
],
|
||||||
|
//root ns, #levels=2, all not js specific options (nocookie is from context)
|
||||||
|
[
|
||||||
|
"{{indexmenu>#2 test#6|navbar context tsort dsort msort hsort rsort nsort nons nopg}}",
|
||||||
|
[
|
||||||
|
'', 'default', 'random', true, true, false, 1, false, '&sort=t&msort=indexmenu_n&rsort=1&nsort=1&hsort=1&nopg=1', true, false,
|
||||||
|
't', 'indexmenu_n', true, true, 2, true, true, [['test', 6]], 0, 1, false, '', '', [''], [''], true,
|
||||||
|
":start:,:same:,:inside:", 1, 1
|
||||||
|
]
|
||||||
|
],
|
||||||
|
//root ns, #levels=2, js renderer, all not js specific options
|
||||||
|
[
|
||||||
|
"{{indexmenu>#2 test#6|navbar js#bj_ubuntu.png context tsort dsort msort hsort rsort nsort nons nopg}}",
|
||||||
|
[
|
||||||
|
'', 'bj_ubuntu.png', 'random', true, true, false, 1, false, '&sort=t&msort=indexmenu_n&rsort=1&nsort=1&hsort=1&nopg=1', true, false,
|
||||||
|
't', 'indexmenu_n', true, true, 2, true, true, [['test', 6]], 0, 1, true, '', '', [''], [''], true,
|
||||||
|
":start:,:same:,:inside:", 1, 1
|
||||||
|
]
|
||||||
|
],
|
||||||
|
//root ns, #levels=1, all options
|
||||||
|
[
|
||||||
|
"{{indexmenu>#1|navbar context nocookie noscroll notoc nomenu dsort msort#date:modified hsort rsort nsort nons nopg max#2#4 maxjs#3 id#54321}}",
|
||||||
|
[
|
||||||
|
'', 'default', 'random', true, true, true, 1, true, '&sort=d&msort=date modified&rsort=1&nsort=1&hsort=1&nopg=1', true, true,
|
||||||
|
'd', 'date modified', true, true, 1, true, true, [], 0, 1, false, '', '', [''], [''], true,
|
||||||
|
":start:,:same:,:inside:", 1, 1
|
||||||
|
]
|
||||||
|
],
|
||||||
|
//root ns, #levels=1, js renderer, all options
|
||||||
|
[
|
||||||
|
"{{indexmenu>#1|js#bj_ubuntu.png navbar context nocookie noscroll notoc nomenu dsort msort#date:modified hsort rsort nsort nons nopg max#2#4 maxjs#3 id#54321}}",
|
||||||
|
[
|
||||||
|
'', 'bj_ubuntu.png', 54321, true, true, true, 3, true, '&sort=d&msort=date modified&rsort=1&nsort=1&hsort=1&nopg=1&max=4', true, true,
|
||||||
|
'd', 'date modified', true, true, 1, true, true, [], 2, 4, true, '', '', [''], [''], true,
|
||||||
|
":start:,:same:,:inside:", 1, 1
|
||||||
|
]
|
||||||
|
],
|
||||||
|
//root ns, #levels=1, skipfile and ns
|
||||||
|
|
||||||
|
[
|
||||||
|
"{{indexmenu>#1 test|skipfile+/(^myusers:spaces$|privatens:userss)/ skipns=/(^myusers:spaces$|privatens:users)/ id#ns}}",
|
||||||
|
[
|
||||||
|
'', 'default', 'random', false, false, false, 1, false, '&skipns=%3D/%28%5Emyusers%3Aspaces%24%7Cprivatens%3Ausers%29/&skipfile=%2B/%28%5Emyusers%3Aspaces%24%7Cprivatens%3Auserss%29/', false, false,
|
||||||
|
0, false, false, false, 1, false, false, [['test', -1]], 0, 1, false, '=/(^myusers:spaces$|privatens:users)/',
|
||||||
|
'+/(^myusers:spaces$|privatens:userss)/', ['/(^myusers:spaces$|privatens:users)/'], ['', '/(^myusers:spaces$|privatens:userss)/'], false,
|
||||||
|
":start:,:same:,:inside:", 1, 1
|
||||||
|
]
|
||||||
|
],
|
||||||
|
//root ns, #levels=1, js renderer, skipfile and ns
|
||||||
|
[
|
||||||
|
"{{indexmenu>#1 test|js skipfile=/(^myusers:spaces$|privatens:userss)/ skipns+/(^myusers:spaces$|privatens:userssss)/ id#ns}}",
|
||||||
|
[
|
||||||
|
'', 'default', 0, false, false, false, 1, false, '&skipns=%2B/%28%5Emyusers%3Aspaces%24%7Cprivatens%3Auserssss%29/&skipfile=%3D/%28%5Emyusers%3Aspaces%24%7Cprivatens%3Auserss%29/', false, false,
|
||||||
|
0, false, false, false, 1, false, false, [['test', -1]], 0, 1, true, '+/(^myusers:spaces$|privatens:userssss)/',
|
||||||
|
'=/(^myusers:spaces$|privatens:userss)/', ['', '/(^myusers:spaces$|privatens:userssss)/'], ['/(^myusers:spaces$|privatens:userss)/'], false,
|
||||||
|
":start:,:same:,:inside:", 1, 1
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the syntax to options
|
||||||
|
* expect: different combinations with or without js option, covers recognizing all syntax options
|
||||||
|
*
|
||||||
|
* @dataProvider someSyntaxes
|
||||||
|
*/
|
||||||
|
public function testHandle($syntax, $changedData)
|
||||||
|
{
|
||||||
|
$plugin = new syntax_plugin_indexmenu_indexmenu();
|
||||||
|
|
||||||
|
$null = new Doku_Handler();
|
||||||
|
$result = $plugin->handle($syntax, 0, 40, $null);
|
||||||
|
|
||||||
|
//copy unique generated number, which is about 23 characters
|
||||||
|
$len_id = strlen($result[1]['identifier']);
|
||||||
|
if (!is_numeric($changedData[2]) && ($len_id > 18 && $len_id <= 23)) {
|
||||||
|
$changedData[2] = $result[1]['identifier'];
|
||||||
|
}
|
||||||
|
$data = $this->createData($changedData);
|
||||||
|
|
||||||
|
$this->assertEquals($data, $result, 'Data array corrupted');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data provider
|
||||||
|
*
|
||||||
|
* @return array[]
|
||||||
|
*/
|
||||||
|
public static function differentNSs()
|
||||||
|
{
|
||||||
|
$pageInRoot = 'page';
|
||||||
|
$pageInLvl1 = 'ns:page';
|
||||||
|
$pageInLvl2 = 'ns1:ns2:page';
|
||||||
|
return [
|
||||||
|
//indexmenu on page at root level
|
||||||
|
['{{indexmenu>|}}', '', [], $pageInRoot],
|
||||||
|
['{{indexmenu>#1}}', '', [], $pageInRoot],
|
||||||
|
['{{indexmenu>:}}', '', [], $pageInRoot],
|
||||||
|
['{{indexmenu>.}}', '', [], $pageInRoot],
|
||||||
|
['{{indexmenu>.:}}', '', [], $pageInRoot],
|
||||||
|
['{{indexmenu>..}}', '', [], $pageInRoot],
|
||||||
|
['{{indexmenu>..:}}', '', [], $pageInRoot],
|
||||||
|
['{{indexmenu>myns}}', 'myns', [], $pageInRoot],
|
||||||
|
['{{indexmenu>:myns}}', 'myns', [], $pageInRoot],
|
||||||
|
['{{indexmenu>.myns}}', 'myns', [], $pageInRoot],
|
||||||
|
['{{indexmenu>.:myns}}', 'myns', [], $pageInRoot],
|
||||||
|
['{{indexmenu>..myns}}', 'myns', [], $pageInRoot],
|
||||||
|
['{{indexmenu>..:myns}}', 'myns', [], $pageInRoot],
|
||||||
|
|
||||||
|
//indexmenu on page in a namespace
|
||||||
|
['{{indexmenu>|}}', '', [], $pageInLvl1],
|
||||||
|
['{{indexmenu>#1}}', '', [], $pageInLvl1],
|
||||||
|
['{{indexmenu>:}}', '', [], $pageInLvl1],
|
||||||
|
['{{indexmenu>.}}', 'ns', [], $pageInLvl1],
|
||||||
|
['{{indexmenu>.:}}', 'ns', [], $pageInLvl1],
|
||||||
|
['{{indexmenu>..}}', '', [], $pageInLvl1],
|
||||||
|
['{{indexmenu>..:}}', '', [], $pageInLvl1],
|
||||||
|
['{{indexmenu>myns}}', 'myns', [], $pageInLvl1], //was ns:myns
|
||||||
|
['{{indexmenu>:myns}}', 'myns', [], $pageInLvl1],
|
||||||
|
['{{indexmenu>.myns}}', 'ns:myns', [], $pageInLvl1],
|
||||||
|
['{{indexmenu>.:myns}}', 'ns:myns', [], $pageInLvl1],
|
||||||
|
['{{indexmenu>..myns}}', 'myns', [], $pageInLvl1],
|
||||||
|
['{{indexmenu>..:myns}}', 'myns', [], $pageInLvl1],
|
||||||
|
['{{indexmenu>myns:myns}}', 'myns:myns', [], $pageInLvl2],
|
||||||
|
|
||||||
|
//indexmenu on page in a namespace
|
||||||
|
['{{indexmenu>|}}', '', [], $pageInLvl2],
|
||||||
|
['{{indexmenu>#1}}', '', [], $pageInLvl2],
|
||||||
|
['{{indexmenu>:}}', '', [], $pageInLvl2],
|
||||||
|
['{{indexmenu>.}}', 'ns1:ns2', [], $pageInLvl2],
|
||||||
|
['{{indexmenu>.:}}', 'ns1:ns2', [], $pageInLvl2],
|
||||||
|
['{{indexmenu>..}}', '', [], $pageInLvl2], //strange indexmenu specific exception! TODO remove?
|
||||||
|
['{{indexmenu>..:}}', 'ns1', [], $pageInLvl2],
|
||||||
|
['{{indexmenu>myns}}', 'myns', [], $pageInLvl2], //was ns1:ns2:myns
|
||||||
|
['{{indexmenu>:myns}}', 'myns', [], $pageInLvl2],
|
||||||
|
['{{indexmenu>.myns}}', 'ns1:ns2:myns', [], $pageInLvl2],
|
||||||
|
['{{indexmenu>.:myns}}', 'ns1:ns2:myns', [], $pageInLvl2],
|
||||||
|
['{{indexmenu>..myns}}', 'ns1:myns', [], $pageInLvl2],
|
||||||
|
['{{indexmenu>..:myns}}', 'ns1:myns', [], $pageInLvl2],
|
||||||
|
['{{indexmenu>myns:myns}}', 'myns:myns', [], $pageInLvl2],
|
||||||
|
|
||||||
|
['{{indexmenu>..:..:myns}}', 'ns1:myns', [], 'ns1:ns2:ns3:page'],
|
||||||
|
['{{indexmenu>0}}', '0', [], 'ns1:page'], //was ns1:0
|
||||||
|
|
||||||
|
//indexmenu on page at root level and subns
|
||||||
|
['{{indexmenu> #1|}}', '', [], $pageInLvl2], //no subns, spaces before are removed
|
||||||
|
['{{indexmenu>#1 #1}}', '', [['', 1]], $pageInLvl2],
|
||||||
|
['{{indexmenu>: :}}', '', [['', -1]], $pageInLvl2],
|
||||||
|
['{{indexmenu>. .}}', 'ns1:ns2', [['ns1:ns2', -1]], $pageInLvl2],
|
||||||
|
['{{indexmenu>.: .:}}', 'ns1:ns2', [['ns1:ns2', -1]], $pageInLvl2],
|
||||||
|
['{{indexmenu>.. ..}}', '', [['', -1]], $pageInLvl2],
|
||||||
|
['{{indexmenu>..: ..:}}', 'ns1', [['ns1', -1]], $pageInLvl2],
|
||||||
|
['{{indexmenu>myns myns}}', 'myns', [['myns', -1]], $pageInLvl2], //was ns1:ns2:myns
|
||||||
|
['{{indexmenu>:myns :myns}}', 'myns', [['myns', -1]], $pageInLvl2],
|
||||||
|
['{{indexmenu>.myns .myns}}', 'ns1:ns2:myns', [['ns1:ns2:myns', -1]], $pageInLvl2],
|
||||||
|
['{{indexmenu>.:myns .:myns}}', 'ns1:ns2:myns', [['ns1:ns2:myns', -1]], $pageInLvl2],
|
||||||
|
['{{indexmenu>..myns ..myns}}', 'ns1:myns', [['ns1:myns', -1]], $pageInLvl2],
|
||||||
|
['{{indexmenu>..:myns ..myns}}', 'ns1:myns', [['ns1:myns', -1]], $pageInLvl2],
|
||||||
|
['{{indexmenu>myns:myns myns:myns}}', 'myns:myns', [['myns:myns', -1]], $pageInLvl2],
|
||||||
|
|
||||||
|
//indexmenu on page in a namespace
|
||||||
|
['{{indexmenu>|}}', '', [], $pageInLvl2],
|
||||||
|
['{{indexmenu>#1}}', '', [], $pageInLvl2],
|
||||||
|
['{{indexmenu>:}}', '', [], $pageInLvl2],
|
||||||
|
['{{indexmenu>.}}', 'ns1:ns2', [], $pageInLvl2],
|
||||||
|
['{{indexmenu>.:}}', 'ns1:ns2', [], $pageInLvl2],
|
||||||
|
['{{indexmenu>..}}', '', [], $pageInLvl2], //strange indexmenu specific exception! TODO remove?
|
||||||
|
['{{indexmenu>..:}}', 'ns1', [], $pageInLvl2],
|
||||||
|
['{{indexmenu>myns:}}', 'myns', [], $pageInLvl2], //was ns1:ns2:myns
|
||||||
|
['{{indexmenu>:myns:}}', 'myns', [], $pageInLvl2],
|
||||||
|
['{{indexmenu>.myns:}}', 'ns1:ns2:myns', [], $pageInLvl2],
|
||||||
|
['{{indexmenu>.:myns:}}', 'ns1:ns2:myns', [], $pageInLvl2],
|
||||||
|
['{{indexmenu>..myns:}}', 'ns1:myns', [], $pageInLvl2],
|
||||||
|
['{{indexmenu>..:myns:}}', 'ns1:myns', [], $pageInLvl2],
|
||||||
|
['{{indexmenu>myns:myns:}}', 'myns:myns', [], $pageInLvl2],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the syntax to options
|
||||||
|
* expect: different combinations with or without js option, covers recognizing all syntax options
|
||||||
|
*
|
||||||
|
* @dataProvider differentNSs
|
||||||
|
*/
|
||||||
|
public function testResolving($syntax, $expectedNs, $expectedSubNss, $pageWithIndexmenu)
|
||||||
|
{
|
||||||
|
global $ID;
|
||||||
|
$ID = $pageWithIndexmenu;
|
||||||
|
|
||||||
|
$plugin = new syntax_plugin_indexmenu_indexmenu();
|
||||||
|
|
||||||
|
$null = new Doku_Handler();
|
||||||
|
$result = $plugin->handle($syntax, 0, 40, $null);
|
||||||
|
|
||||||
|
$this->assertEquals($expectedNs, $result[0], 'check resolved ns');
|
||||||
|
$this->assertEquals($expectedSubNss, $result[3]['subnss'], 'check resolved subNSs');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rendering for nonexisting namespace
|
||||||
|
* expect: no paragraph due to no message set
|
||||||
|
* expect: one paragraph, since message set
|
||||||
|
* expect: contains namespace which replaced {{ns}}
|
||||||
|
* expect: message contained rendered italic syntax
|
||||||
|
*/
|
||||||
|
public function testRenderEmptymsg()
|
||||||
|
{
|
||||||
|
global $conf;
|
||||||
|
|
||||||
|
$noexistns = 'nonexisting:namespace';
|
||||||
|
$emptyindexsyntax = "{{indexmenu>$noexistns}}";
|
||||||
|
|
||||||
|
$xhtml = new Doku_Renderer_xhtml();
|
||||||
|
$plugin = new syntax_plugin_indexmenu_indexmenu();
|
||||||
|
|
||||||
|
$null = new Doku_Handler();
|
||||||
|
$result = $plugin->handle($emptyindexsyntax, 0, 10, $null);
|
||||||
|
|
||||||
|
//no empty message
|
||||||
|
$plugin->render('xhtml', $xhtml, $result);
|
||||||
|
|
||||||
|
$doc = (new Document())->html($xhtml->doc);
|
||||||
|
$this->assertEquals(0, $doc->find('p')->count());
|
||||||
|
|
||||||
|
// Fill in empty message
|
||||||
|
$conf['plugin']['indexmenu']['empty_msg'] = 'This namespace is //empty//: {{ns}}';
|
||||||
|
$plugin->render('xhtml', $xhtml, $result);
|
||||||
|
$doc = (new Document())->html($xhtml->doc);
|
||||||
|
|
||||||
|
$this->assertEquals(1, $doc->find('p')->count());
|
||||||
|
// $this->assertEquals(1, $doc->find("p:contains($noexistns)")->count());
|
||||||
|
$this->assertEquals(1, $doc->find("p em")->count());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
559
plugins/55/indexmenu/action.php
Normal file
|
@ -0,0 +1,559 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indexmenu Action Plugin: Indexmenu Component.
|
||||||
|
*
|
||||||
|
* @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
|
||||||
|
* @author Samuele Tognini <samuele@samuele.netsons.org>
|
||||||
|
*/
|
||||||
|
|
||||||
|
use dokuwiki\Extension\ActionPlugin;
|
||||||
|
use dokuwiki\Extension\Event;
|
||||||
|
use dokuwiki\Extension\EventHandler;
|
||||||
|
use dokuwiki\plugin\indexmenu\Search;
|
||||||
|
use dokuwiki\Ui\Index;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class action_plugin_indexmenu
|
||||||
|
*/
|
||||||
|
class action_plugin_indexmenu extends ActionPlugin
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* plugin should use this method to register its handlers with the dokuwiki's event controller
|
||||||
|
*
|
||||||
|
* @param EventHandler $controller DokuWiki's event controller object.
|
||||||
|
*/
|
||||||
|
public function register(EventHandler $controller)
|
||||||
|
{
|
||||||
|
if ($this->getConf('only_admins')) {
|
||||||
|
$controller->register_hook('IO_WIKIPAGE_WRITE', 'BEFORE', $this, 'removeSyntaxIfNotAdmin');
|
||||||
|
}
|
||||||
|
if ($this->getConf('page_index') != '') {
|
||||||
|
$controller->register_hook('TPL_ACT_RENDER', 'BEFORE', $this, 'loadOwnIndexPage');
|
||||||
|
}
|
||||||
|
$controller->register_hook('DOKUWIKI_STARTED', 'AFTER', $this, 'extendJSINFO');
|
||||||
|
$controller->register_hook('PARSER_CACHE_USE', 'BEFORE', $this, 'purgeCache');
|
||||||
|
if ($this->getConf('show_sort')) {
|
||||||
|
$controller->register_hook('TPL_CONTENT_DISPLAY', 'BEFORE', $this, 'showSortNumberAtTopOfPage');
|
||||||
|
}
|
||||||
|
$controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'ajaxCalls');
|
||||||
|
$controller->register_hook('TPL_METAHEADER_OUTPUT', 'BEFORE', $this, 'addStylesForSkins');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if user has permission to insert indexmenu
|
||||||
|
*
|
||||||
|
* @param Event $event
|
||||||
|
*
|
||||||
|
* @author Samuele Tognini <samuele@samuele.netsons.org>
|
||||||
|
*/
|
||||||
|
public function removeSyntaxIfNotAdmin(Event $event)
|
||||||
|
{
|
||||||
|
global $INFO;
|
||||||
|
if (!$INFO['ismanager']) {
|
||||||
|
$event->data[0][1] = preg_replace("/{{indexmenu(|_n)>.+?}}/", "", $event->data[0][1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add additional info to $JSINFO
|
||||||
|
*
|
||||||
|
* @param Event $event
|
||||||
|
*
|
||||||
|
* @author Gerrit Uitslag <klapinklapin@gmail.com>
|
||||||
|
* @author Samuele Tognini <samuele@samuele.netsons.org>
|
||||||
|
*/
|
||||||
|
public function extendJSINFO(Event $event)
|
||||||
|
{
|
||||||
|
global $INFO, $JSINFO;
|
||||||
|
|
||||||
|
$JSINFO['isadmin'] = (int)$INFO['isadmin'];
|
||||||
|
$JSINFO['isauth'] = isset($INFO['userinfo']) ? (int) $INFO['userinfo'] : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check for pages changes and eventually purge cache.
|
||||||
|
*
|
||||||
|
* @param Event $event
|
||||||
|
*
|
||||||
|
* @author Samuele Tognini <samuele@samuele.netsons.org>
|
||||||
|
*/
|
||||||
|
public function purgeCache(Event $event)
|
||||||
|
{
|
||||||
|
global $ID;
|
||||||
|
global $conf;
|
||||||
|
global $INPUT;
|
||||||
|
global $INFO;
|
||||||
|
|
||||||
|
/** @var cache_parser $cache */
|
||||||
|
$cache = &$event->data;
|
||||||
|
|
||||||
|
if (!isset($cache->page)) return;
|
||||||
|
//purge only xhtml cache
|
||||||
|
if ($cache->mode != "xhtml") return;
|
||||||
|
//Check if it is an indexmenu page
|
||||||
|
if (!p_get_metadata($ID, 'indexmenu hasindexmenu')) return;
|
||||||
|
|
||||||
|
$aclcache = $this->getConf('aclcache');
|
||||||
|
if ($conf['useacl']) {
|
||||||
|
$newkey = false;
|
||||||
|
if ($aclcache == 'user') {
|
||||||
|
//Cache per user
|
||||||
|
if ($INPUT->server->str('REMOTE_USER')) {
|
||||||
|
$newkey = $INPUT->server->str('REMOTE_USER');
|
||||||
|
}
|
||||||
|
} elseif ($aclcache == 'groups') {
|
||||||
|
//Cache per groups
|
||||||
|
if (isset($INFO['userinfo']['grps'])) {
|
||||||
|
$newkey = implode('#', $INFO['userinfo']['grps']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($newkey) {
|
||||||
|
$cache->key .= "#" . $newkey;
|
||||||
|
$cache->cache = getCacheName($cache->key, $cache->ext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Check if a page is more recent than purgefile.
|
||||||
|
if (@filemtime($cache->cache) < @filemtime($conf['cachedir'] . '/purgefile')) {
|
||||||
|
$event->preventDefault();
|
||||||
|
$event->stopPropagation();
|
||||||
|
$event->result = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a defined page as index.
|
||||||
|
*
|
||||||
|
* @param Event $event
|
||||||
|
*
|
||||||
|
* @author Samuele Tognini <samuele@samuele.netsons.org>
|
||||||
|
*/
|
||||||
|
public function loadOwnIndexPage(Event $event)
|
||||||
|
{
|
||||||
|
if ('index' != $event->data) return;
|
||||||
|
if (!file_exists(wikiFN($this->getConf('page_index')))) return;
|
||||||
|
|
||||||
|
global $lang;
|
||||||
|
|
||||||
|
echo '<h1><a id="index">' . $lang['btn_index'] . "</a></h1>\n";
|
||||||
|
echo p_wiki_xhtml($this->getConf('page_index'));
|
||||||
|
$event->preventDefault();
|
||||||
|
$event->stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the indexmenu sort number.
|
||||||
|
*
|
||||||
|
* @param Event $event
|
||||||
|
*
|
||||||
|
* @author Samuele Tognini <samuele@samuele.netsons.org>
|
||||||
|
*/
|
||||||
|
public function showSortNumberAtTopOfPage(Event $event)
|
||||||
|
{
|
||||||
|
global $ID, $ACT, $INFO;
|
||||||
|
if ($INFO['isadmin'] && $ACT == 'show') {
|
||||||
|
if ($n = p_get_metadata($ID, 'indexmenu_n')) {
|
||||||
|
echo '<div class="info">';
|
||||||
|
echo $this->getLang('showsort') . $n;
|
||||||
|
echo '</div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles ajax requests for indexmenu
|
||||||
|
*
|
||||||
|
* @param Event $event
|
||||||
|
*/
|
||||||
|
public function ajaxCalls(Event $event)
|
||||||
|
{
|
||||||
|
if ($event->data !== 'indexmenu') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//no other ajax call handlers needed
|
||||||
|
$event->stopPropagation();
|
||||||
|
$event->preventDefault();
|
||||||
|
|
||||||
|
global $INPUT;
|
||||||
|
switch ($INPUT->str('req')) {
|
||||||
|
case 'local':
|
||||||
|
//list themes
|
||||||
|
$this->getlocalThemes();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'toc':
|
||||||
|
//print toc preview
|
||||||
|
if ($INPUT->has('id')) {
|
||||||
|
echo $this->printToc($INPUT->str('id'));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'index':
|
||||||
|
//for dTree
|
||||||
|
//retrieval of data of the extra nodes for the indexmenu (if ajax loading set with max#m(#n)
|
||||||
|
if ($INPUT->has('idx')) {
|
||||||
|
echo $this->printIndex($INPUT->str('idx'));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'fancytree':
|
||||||
|
//data for new index build with Fancytree
|
||||||
|
$this->getDataFancyTree();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles ajax requests for FancyTree
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function getDataFancyTree()
|
||||||
|
{
|
||||||
|
global $INPUT;
|
||||||
|
|
||||||
|
$ns = $INPUT->str('ns', '');
|
||||||
|
$ns = rtrim($ns, ':');
|
||||||
|
//key of directory has extra : on the end
|
||||||
|
$level = -1; //opened levels. -1=all levels open
|
||||||
|
$max = 1; //levels to load by lazyloading. Before the default was 0. CHANGED to 1.
|
||||||
|
$skipFileCombined = [];
|
||||||
|
$skipNsCombined = [];
|
||||||
|
|
||||||
|
if ($INPUT->int('max') > 0) {
|
||||||
|
$max = $INPUT->int('max'); // max#n#m, if init: #n, otherwise #m
|
||||||
|
$level = $max;
|
||||||
|
}
|
||||||
|
if ($INPUT->int('level', -10) >= -1) {
|
||||||
|
$level = $INPUT->int('level');
|
||||||
|
}
|
||||||
|
$isInit = $INPUT->bool('init');
|
||||||
|
|
||||||
|
$currentPage = $INPUT->str('currentpage');
|
||||||
|
if ($isInit) {
|
||||||
|
$subnss = $INPUT->arr('subnss');
|
||||||
|
// if 'navbar' is enabled add current ns to list
|
||||||
|
if ($INPUT->bool('navbar')) {
|
||||||
|
$currentNs = getNS($currentPage);
|
||||||
|
if ($currentNs !== false) {
|
||||||
|
$subnss[] = [$currentNs, 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// alternative, via javascript.. https://wwwendt.de/tech/fancytree/doc/jsdoc/Fancytree.html#loadKeyPath
|
||||||
|
} else {
|
||||||
|
//not set via javascript at the moment.. ajax opens per level, so subnss has no use here
|
||||||
|
$subnss = $INPUT->str('subnss');
|
||||||
|
if ($subnss !== '') {
|
||||||
|
$subnss = [[cleanID($subnss), 1]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$skipf = $INPUT->str('skipfile');
|
||||||
|
$skipFileCombined[] = $this->getConf('skip_file');
|
||||||
|
if (!empty($skipf)) {
|
||||||
|
$index = 0;
|
||||||
|
//prefix is '=' or '+'
|
||||||
|
if ($skipf[0] == '+') {
|
||||||
|
$index = 1;
|
||||||
|
}
|
||||||
|
$skipFileCombined[$index] = substr($skipf, 1);
|
||||||
|
}
|
||||||
|
$skipn = $INPUT->str('skipns');
|
||||||
|
$skipNsCombined[] = $this->getConf('skip_index');
|
||||||
|
if (!empty($skipn)) {
|
||||||
|
$index = 0;
|
||||||
|
//prefix is '=' or '+'
|
||||||
|
if ($skipn[0] == '+') {
|
||||||
|
$index = 1;
|
||||||
|
}
|
||||||
|
$skipNsCombined[$index] = substr($skipn, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$opts = [
|
||||||
|
//only set for init, lazy requests equal to max
|
||||||
|
'level' => $level,
|
||||||
|
//nons only needed for init as it has no nested nodes
|
||||||
|
'nons' => $INPUT->bool('nons'),
|
||||||
|
'nopg' => $INPUT->bool('nopg'),
|
||||||
|
//init with complex array, empty if lazy loading
|
||||||
|
'subnss' => $subnss,
|
||||||
|
'max' => $max,
|
||||||
|
'skipnscombined' => $skipNsCombined,
|
||||||
|
'skipfilecombined' => $skipFileCombined,
|
||||||
|
'headpage' => $this->getConf('headpage'),
|
||||||
|
'hide_headpage' => $this->getConf('hide_headpage'),
|
||||||
|
];
|
||||||
|
|
||||||
|
$sort = [
|
||||||
|
'sort' => $INPUT->str('sort'),
|
||||||
|
'msort' => $INPUT->str('msort'),
|
||||||
|
'rsort' => $INPUT->bool('rsort'),
|
||||||
|
'nsort' => $INPUT->bool('nsort'),
|
||||||
|
'hsort' => $INPUT->bool('hsort')
|
||||||
|
];
|
||||||
|
|
||||||
|
$opts['tempNew'] = true; //TODO temporary for recognizing treenew in the search function
|
||||||
|
|
||||||
|
$search = new Search($sort);
|
||||||
|
$data = $search->search($ns, $opts);
|
||||||
|
$fancytreeData = $search->buildFancytreeData($data, $isInit, $currentPage, $opts['nopg']);
|
||||||
|
|
||||||
|
//add eventually debug info
|
||||||
|
if ($isInit) {
|
||||||
|
//for lazy loading are other items than children not supported.
|
||||||
|
// $fancytreeData['opts'] = $opts;
|
||||||
|
// $fancytreeData['sort'] = $sort;
|
||||||
|
// $fancytreeData['debug'] = $data;
|
||||||
|
} else {
|
||||||
|
//returns only children, therefore, add debug info to first child
|
||||||
|
// $fancytreeData[0]['opts'] = $opts;
|
||||||
|
// $fancytreeData[0]['sort'] = $sort;
|
||||||
|
// $fancytreeData[0]['debug'] = $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
echo json_encode($fancytreeData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print a list of local themes
|
||||||
|
*
|
||||||
|
* @author Samuele Tognini <samuele@samuele.netsons.org>
|
||||||
|
* @author Gerrit Uitslag <klapinklapin@gmail.com>
|
||||||
|
*/
|
||||||
|
private function getlocalThemes()
|
||||||
|
{
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
$themebase = 'lib/plugins/indexmenu/images';
|
||||||
|
|
||||||
|
$handle = @opendir(DOKU_INC . $themebase);
|
||||||
|
$themes = [];
|
||||||
|
while (false !== ($file = readdir($handle))) {
|
||||||
|
if (
|
||||||
|
is_dir(DOKU_INC . $themebase . '/' . $file)
|
||||||
|
&& $file != "."
|
||||||
|
&& $file != ".."
|
||||||
|
&& $file != "repository"
|
||||||
|
&& $file != "tmp"
|
||||||
|
&& $file != ".svn"
|
||||||
|
) {
|
||||||
|
$themes[] = $file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
closedir($handle);
|
||||||
|
sort($themes);
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'themebase' => $themebase,
|
||||||
|
'themes' => $themes
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print a toc preview
|
||||||
|
*
|
||||||
|
* @param string $id
|
||||||
|
* @return string
|
||||||
|
*
|
||||||
|
* @author Samuele Tognini <samuele@samuele.netsons.org>
|
||||||
|
* @author Andreas Gohr <andi@splitbrain.org>
|
||||||
|
*/
|
||||||
|
private function printToc($id)
|
||||||
|
{
|
||||||
|
$id = cleanID($id);
|
||||||
|
if (auth_quickaclcheck($id) < AUTH_READ) return '';
|
||||||
|
|
||||||
|
$meta = p_get_metadata($id);
|
||||||
|
$toc = $meta['description']['tableofcontents'] ?? [];
|
||||||
|
|
||||||
|
if (count($toc) > 1) {
|
||||||
|
//display ToC of two or more headings
|
||||||
|
$out = $this->renderToc($toc);
|
||||||
|
} else {
|
||||||
|
//display page abstract
|
||||||
|
$out = $this->renderAbstract($id, $meta);
|
||||||
|
}
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the TOC rendered to XHTML
|
||||||
|
*
|
||||||
|
* @param $toc
|
||||||
|
* @return string
|
||||||
|
*
|
||||||
|
* @author Andreas Gohr <andi@splitbrain.org>
|
||||||
|
* @author Gerrit Uitslag <klapinklapin@gmail.com>
|
||||||
|
*/
|
||||||
|
private function renderToc($toc)
|
||||||
|
{
|
||||||
|
global $lang;
|
||||||
|
$out = '<div class="tocheader">';
|
||||||
|
$out .= $lang['toc'];
|
||||||
|
$out .= '</div>';
|
||||||
|
$out .= '<div class="indexmenu_toc_inside">';
|
||||||
|
$out .= html_buildlist($toc, 'toc', [$this, 'formatIndexmenuListTocItem'], null, true);
|
||||||
|
$out .= '</div>';
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the page abstract rendered to XHTML
|
||||||
|
*
|
||||||
|
* @param $id
|
||||||
|
* @param array $meta by reference
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function renderAbstract($id, $meta)
|
||||||
|
{
|
||||||
|
$out = '<div class="tocheader">';
|
||||||
|
$out .= '<a href="' . wl($id) . '">';
|
||||||
|
$out .= $meta['title'] ? hsc($meta['title']) : hsc(noNS($id));
|
||||||
|
$out .= '</a>';
|
||||||
|
$out .= '</div>';
|
||||||
|
if ($meta['description']['abstract']) {
|
||||||
|
$out .= '<div class="indexmenu_toc_inside">';
|
||||||
|
$out .= p_render('xhtml', p_get_instructions($meta['description']['abstract']), $info);
|
||||||
|
$out .= '</div></div>';
|
||||||
|
}
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback for html_buildlist
|
||||||
|
*
|
||||||
|
* @param $item
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function formatIndexmenuListTocItem($item)
|
||||||
|
{
|
||||||
|
global $INPUT;
|
||||||
|
|
||||||
|
$id = cleanID($INPUT->str('id'));
|
||||||
|
|
||||||
|
if (isset($item['hid'])) {
|
||||||
|
$link = '#' . $item['hid'];
|
||||||
|
} else {
|
||||||
|
$link = $item['link'];
|
||||||
|
}
|
||||||
|
|
||||||
|
//prefix anchers with page id
|
||||||
|
if ($link[0] == '#') {
|
||||||
|
$link = wl($id, $link, false, '');
|
||||||
|
}
|
||||||
|
return '<a href="' . $link . '">' . hsc($item['title']) . '</a>';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print index nodes
|
||||||
|
*
|
||||||
|
* @param $ns
|
||||||
|
* @return string
|
||||||
|
*
|
||||||
|
* @author Rene Hadler <rene.hadler@iteas.at>
|
||||||
|
* @author Samuele Tognini <samuele@samuele.netsons.org>
|
||||||
|
* @author Andreas Gohr <andi@splitbrain.org>
|
||||||
|
*/
|
||||||
|
private function printIndex($ns)
|
||||||
|
{
|
||||||
|
global $conf, $INPUT;
|
||||||
|
$idxm = new syntax_plugin_indexmenu_indexmenu();
|
||||||
|
$ns = $idxm->parseNs(rawurldecode($ns));
|
||||||
|
$level = -1;
|
||||||
|
$max = 0;
|
||||||
|
$data = [];
|
||||||
|
$skipfilecombined = [];
|
||||||
|
$skipnscombined = [];
|
||||||
|
|
||||||
|
if ($INPUT->int('max') > 0) {
|
||||||
|
$max = $INPUT->int('max');
|
||||||
|
$level = $max;
|
||||||
|
}
|
||||||
|
$nss = $INPUT->str('nss', '', true);
|
||||||
|
$sort['sort'] = $INPUT->str('sort', '', true);
|
||||||
|
$sort['msort'] = $INPUT->str('msort', '', true);
|
||||||
|
$sort['rsort'] = $INPUT->bool('rsort', false, true);
|
||||||
|
$sort['nsort'] = $INPUT->bool('nsort', false, true);
|
||||||
|
$sort['hsort'] = $INPUT->bool('hsort', false, true);
|
||||||
|
$search = new Search($sort);
|
||||||
|
$fsdir = "/" . utf8_encodeFN(str_replace(':', '/', $ns));
|
||||||
|
|
||||||
|
$skipf = utf8_decodeFN($INPUT->str('skipfile'));
|
||||||
|
$skipfilecombined[] = $this->getConf('skip_file');
|
||||||
|
if (!empty($skipf)) {
|
||||||
|
$index = 0;
|
||||||
|
if ($skipf[0] == '+') {
|
||||||
|
$index = 1;
|
||||||
|
}
|
||||||
|
$skipfilecombined[$index] = substr($skipf, 1);
|
||||||
|
}
|
||||||
|
$skipn = utf8_decodeFN($INPUT->str('skipns'));
|
||||||
|
$skipnscombined[] = $this->getConf('skip_index');
|
||||||
|
if (!empty($skipn)) {
|
||||||
|
$index = 0;
|
||||||
|
if ($skipn[0] == '+') {
|
||||||
|
$index = 1;
|
||||||
|
}
|
||||||
|
$skipnscombined[$index] = substr($skipn, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$opts = [
|
||||||
|
'level' => $level,
|
||||||
|
'nons' => $INPUT->bool('nons', false, true),
|
||||||
|
'nss' => [[$nss, 1]],
|
||||||
|
'max' => $max,
|
||||||
|
'js' => false,
|
||||||
|
'nopg' => $INPUT->bool('nopg', false, true),
|
||||||
|
'skipnscombined' => $skipnscombined,
|
||||||
|
'skipfilecombined' => $skipfilecombined,
|
||||||
|
'headpage' => $idxm->getConf('headpage'),
|
||||||
|
'hide_headpage' => $idxm->getConf('hide_headpage')
|
||||||
|
];
|
||||||
|
if ($sort['sort'] || $sort['msort'] || $sort['rsort'] || $sort['hsort']) {
|
||||||
|
$search->customSearch($data, $conf['datadir'], [$search, 'searchIndexmenuItems'], $opts, $fsdir);
|
||||||
|
} else {
|
||||||
|
search($data, $conf['datadir'], [$search, 'searchIndexmenuItems'], $opts, $fsdir);
|
||||||
|
}
|
||||||
|
|
||||||
|
$out = '';
|
||||||
|
if ($INPUT->int('nojs') === 1) {
|
||||||
|
$idx = new Index();
|
||||||
|
$out_tmp = html_buildlist($data, 'idx', [$idxm, 'formatIndexmenuItem'], [$idx, 'tagListItem']);
|
||||||
|
$out .= preg_replace('/<ul class="idx">(.*)<\/ul>/s', "$1", $out_tmp);
|
||||||
|
} else {
|
||||||
|
$nodes = $idxm->builddTreeNodes($data, '', false);
|
||||||
|
$out = "ajxnodes = [";
|
||||||
|
$out .= rtrim($nodes[0], ",");
|
||||||
|
$out .= "];";
|
||||||
|
}
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add Js & Css after template is displayed
|
||||||
|
*
|
||||||
|
* @param Event $event
|
||||||
|
*/
|
||||||
|
public function addStylesForSkins(Event $event)
|
||||||
|
{
|
||||||
|
|
||||||
|
// $event->data["link"][] = [
|
||||||
|
// "type" => "text/css",
|
||||||
|
// "rel" => "stylesheet",
|
||||||
|
// "href" => DOKU_BASE . "lib/plugins/indexmenu/scripts/fancytree/... etc etc"
|
||||||
|
// ];
|
||||||
|
|
||||||
|
// $event->data["link"][] = [
|
||||||
|
// "type" => "text/css",
|
||||||
|
// "rel" => "stylesheet",
|
||||||
|
// "href" => "//fonts.googleapis.com/icon?family=Material+Icons"
|
||||||
|
// ];
|
||||||
|
|
||||||
|
// $event->data["link"][] = [
|
||||||
|
// "type" => "text/css",
|
||||||
|
// "rel" => "stylesheet",
|
||||||
|
// "href" => "//code.getmdl.io/1.3.0/material.indigo-pink.min.css"
|
||||||
|
// ];
|
||||||
|
}
|
||||||
|
}
|
85
plugins/55/indexmenu/ajax.php
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
<?php
|
||||||
|
// phpcs:ignorefile
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AJAX Backend for indexmenu
|
||||||
|
*
|
||||||
|
* @author Samuele Tognini <samuele@samuele.netsons.org>
|
||||||
|
* @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
|
||||||
|
*/
|
||||||
|
|
||||||
|
//fix for Opera XMLHttpRequests
|
||||||
|
if ($_POST === [] && @$HTTP_RAW_POST_DATA) {
|
||||||
|
parse_str($HTTP_RAW_POST_DATA, $_POST);
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once(DOKU_INC . 'inc/init.php');
|
||||||
|
require_once(DOKU_INC . 'inc/auth.php');
|
||||||
|
|
||||||
|
//close session
|
||||||
|
session_write_close();
|
||||||
|
|
||||||
|
$ajax_indexmenu = new ajax_indexmenu_plugin();
|
||||||
|
$ajax_indexmenu->render();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class ajax_indexmenu_plugin
|
||||||
|
* @deprecated 2023-11 not used anymore
|
||||||
|
*/
|
||||||
|
class ajax_indexmenu_plugin
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Output
|
||||||
|
*
|
||||||
|
* @author Samuele Tognini <samuele@samuele.netsons.org>
|
||||||
|
*/
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
$req = $_REQUEST['req'];
|
||||||
|
$succ = false;
|
||||||
|
//send the zip
|
||||||
|
if ($req == 'send' && isset($_REQUEST['t'])) {
|
||||||
|
include(DOKU_PLUGIN . 'indexmenu/inc/repo.class.php');
|
||||||
|
$repo = new repo_indexmenu_plugin();
|
||||||
|
$succ = $repo->sendTheme($_REQUEST['t']);
|
||||||
|
}
|
||||||
|
if ($succ) return;
|
||||||
|
|
||||||
|
header('Content-Type: text/html; charset=utf-8');
|
||||||
|
header('Cache-Control: public, max-age=3600');
|
||||||
|
header('Pragma: public');
|
||||||
|
if ($req === 'local') {
|
||||||
|
//required for admin.php
|
||||||
|
//list themes
|
||||||
|
echo $this->localThemes();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print a list of local themes
|
||||||
|
* TODO: delete this funstion; copy of this function is already in action.php
|
||||||
|
* @author Samuele Tognini <samuele@samuele.netsons.org>
|
||||||
|
*/
|
||||||
|
public function localThemes()
|
||||||
|
{
|
||||||
|
$list = 'indexmenu,' . DOKU_URL . ",lib/plugins/indexmenu/images,";
|
||||||
|
$data = [];
|
||||||
|
$handle = @opendir(DOKU_PLUGIN . "indexmenu/images");
|
||||||
|
while (false !== ($file = readdir($handle))) {
|
||||||
|
if (
|
||||||
|
is_dir(DOKU_PLUGIN . 'indexmenu/images/' . $file)
|
||||||
|
&& $file != "."
|
||||||
|
&& $file != ".."
|
||||||
|
&& $file != "repository"
|
||||||
|
&& $file != "tmp"
|
||||||
|
&& $file != ".svn"
|
||||||
|
) {
|
||||||
|
$data[] = $file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
closedir($handle);
|
||||||
|
sort($data);
|
||||||
|
$list .= implode(",", $data);
|
||||||
|
return $list;
|
||||||
|
}
|
||||||
|
}
|
44
plugins/55/indexmenu/all.less
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
//The data-uri() links in skin-common.less break. Needs to be replaced by url(), DokuWiki can inline if needed
|
||||||
|
|
||||||
|
//moved from skin-common.less to here to prevent wrong prefixing and renamed from spin to spin-fancytree
|
||||||
|
@keyframes spin-fancytree {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(359deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//Mixins
|
||||||
|
// note: import of skin-common.less in the imported file below works only if skin-common.less is copied to EACH skin
|
||||||
|
// folder and referred from its ui.fancytree.less respectively.
|
||||||
|
.importSkin(@skin-foldername) {
|
||||||
|
&.@{skin-foldername} {
|
||||||
|
@import "scripts/fancytree/@{skin-foldername}/ui.fancytree.less";
|
||||||
|
//overwrite default variable: @fancy-image-prefix: "./skin-win8/"; the current less compressor does not update paths
|
||||||
|
//relative to lib/exe/(css.php), workaround DOKU_BASE not available in css
|
||||||
|
@fancy-image-prefix: "../plugins/indexmenu/scripts/fancytree/@{skin-foldername}/";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//wrap everything by plugin class to ensure its dominates default dokuwiki paddings etc.
|
||||||
|
.indexmenu_js2 {
|
||||||
|
//workaround needed for LESS processor of DokuWiki
|
||||||
|
.setBgImageUrl(@url) when not (@fancy-use-sprites) {}
|
||||||
|
.useSprite(@x, @y) when not(@fancy-use-sprites) {}
|
||||||
|
|
||||||
|
.importSkin(skin-awesome);
|
||||||
|
.importSkin(skin-bootstrap);
|
||||||
|
.importSkin(skin-bootstrap-n);
|
||||||
|
.importSkin(skin-lion);
|
||||||
|
.importSkin(skin-material);
|
||||||
|
.importSkin(skin-mdi);
|
||||||
|
.importSkin(skin-vista);
|
||||||
|
.importSkin(skin-win7);
|
||||||
|
.importSkin(skin-win8);
|
||||||
|
.importSkin(skin-xp);
|
||||||
|
.importSkin(skin-typicons);
|
||||||
|
}
|
21
plugins/55/indexmenu/conf/default.php
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default configuration for indexmenu plugin
|
||||||
|
*
|
||||||
|
* @license: GPL 2 (http://www.gnu.org/licenses/gpl.html)
|
||||||
|
* @author: Samuele Tognini <samuele@samuele.netsons.org>
|
||||||
|
*/
|
||||||
|
|
||||||
|
$conf['defaultoptions'] = '';
|
||||||
|
$conf['only_admins'] = 0;
|
||||||
|
$conf['aclcache'] = 'groups';
|
||||||
|
$conf['headpage'] = ':start:,:same:,:inside:';
|
||||||
|
$conf['hide_headpage'] = 1;
|
||||||
|
$conf['page_index'] = '';
|
||||||
|
$conf['empty_msg'] = '';
|
||||||
|
$conf['skip_index'] = '';
|
||||||
|
$conf['skip_file'] = '';
|
||||||
|
$conf['show_sort'] = 1;
|
||||||
|
//$conf['themes_url'] = 'http://samuele.netsons.org/dokuwiki';
|
||||||
|
//$conf['be_repo'] = 0;
|
21
plugins/55/indexmenu/conf/metadata.php
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration-manager metadata for indexmenu plugin
|
||||||
|
*
|
||||||
|
* @license: GPL 2 (http://www.gnu.org/licenses/gpl.html)
|
||||||
|
* @author: Samuele Tognini <samuele@samuele.netsons.org>
|
||||||
|
*/
|
||||||
|
|
||||||
|
$meta['defaultoptions'] = array('string');
|
||||||
|
$meta['only_admins'] = array('onoff','_caution' => 'warning');
|
||||||
|
$meta['aclcache'] = array('multichoice', '_choices' => array('none', 'user', 'groups'));
|
||||||
|
$meta['headpage'] = array('multicheckbox', '_choices' => array(':start:', ':same:', ':inside:'));
|
||||||
|
$meta['hide_headpage'] = array('onoff');
|
||||||
|
$meta['page_index'] = array('string', '_pattern' => '#^[a-z:]*#');
|
||||||
|
$meta['empty_msg'] = array('string');
|
||||||
|
$meta['skip_index'] = array('string', '_pattern' => '/^($|\/.*\/.*$)/');
|
||||||
|
$meta['skip_file'] = array('string', '_pattern' => '/^($|\/.*\/.*$)/');
|
||||||
|
$meta['show_sort'] = array('onoff');
|
||||||
|
//$meta['themes_url'] = array('string','_pattern' => '/^($|http:\/\/\S+$)/i');
|
||||||
|
//$meta['be_repo'] = array('onoff');
|
BIN
plugins/55/indexmenu/images/bj-tango.png/base.png
Normal file
After Width: | Height: | Size: 606 B |
BIN
plugins/55/indexmenu/images/bj-tango.png/empty.png
Normal file
After Width: | Height: | Size: 157 B |
BIN
plugins/55/indexmenu/images/bj-tango.png/folder.png
Normal file
After Width: | Height: | Size: 498 B |
BIN
plugins/55/indexmenu/images/bj-tango.png/folderh.png
Normal file
After Width: | Height: | Size: 528 B |
BIN
plugins/55/indexmenu/images/bj-tango.png/folderhopen.png
Normal file
After Width: | Height: | Size: 537 B |
BIN
plugins/55/indexmenu/images/bj-tango.png/folderopen.png
Normal file
After Width: | Height: | Size: 523 B |
3
plugins/55/indexmenu/images/bj-tango.png/info.txt
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
author=Bernard JOUVE <office [at] hd-recording [dot] at>
|
||||||
|
url=
|
||||||
|
description=
|
BIN
plugins/55/indexmenu/images/bj-tango.png/join.png
Normal file
After Width: | Height: | Size: 131 B |
BIN
plugins/55/indexmenu/images/bj-tango.png/joinbottom.png
Normal file
After Width: | Height: | Size: 132 B |
BIN
plugins/55/indexmenu/images/bj-tango.png/line.png
Normal file
After Width: | Height: | Size: 128 B |
BIN
plugins/55/indexmenu/images/bj-tango.png/minus.png
Normal file
After Width: | Height: | Size: 171 B |
BIN
plugins/55/indexmenu/images/bj-tango.png/minusbottom.png
Normal file
After Width: | Height: | Size: 167 B |
BIN
plugins/55/indexmenu/images/bj-tango.png/nolines_minus.png
Normal file
After Width: | Height: | Size: 144 B |
BIN
plugins/55/indexmenu/images/bj-tango.png/nolines_plus.png
Normal file
After Width: | Height: | Size: 160 B |
BIN
plugins/55/indexmenu/images/bj-tango.png/page.png
Normal file
After Width: | Height: | Size: 333 B |
BIN
plugins/55/indexmenu/images/bj-tango.png/plus.png
Normal file
After Width: | Height: | Size: 183 B |
BIN
plugins/55/indexmenu/images/bj-tango.png/plusbottom.png
Normal file
After Width: | Height: | Size: 183 B |
BIN
plugins/55/indexmenu/images/bj_ubuntu.png/base.png
Normal file
After Width: | Height: | Size: 525 B |
BIN
plugins/55/indexmenu/images/bj_ubuntu.png/empty.png
Normal file
After Width: | Height: | Size: 157 B |
BIN
plugins/55/indexmenu/images/bj_ubuntu.png/folder.png
Normal file
After Width: | Height: | Size: 498 B |
BIN
plugins/55/indexmenu/images/bj_ubuntu.png/folderh.png
Normal file
After Width: | Height: | Size: 537 B |
BIN
plugins/55/indexmenu/images/bj_ubuntu.png/folderhopen.png
Normal file
After Width: | Height: | Size: 523 B |
BIN
plugins/55/indexmenu/images/bj_ubuntu.png/folderopen.png
Normal file
After Width: | Height: | Size: 523 B |
3
plugins/55/indexmenu/images/bj_ubuntu.png/info.txt
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
author=Andreas Neuhold <office [at] hd-recording [dot] at>
|
||||||
|
url=
|
||||||
|
description=
|
BIN
plugins/55/indexmenu/images/bj_ubuntu.png/join.png
Normal file
After Width: | Height: | Size: 131 B |
BIN
plugins/55/indexmenu/images/bj_ubuntu.png/joinbottom.png
Normal file
After Width: | Height: | Size: 132 B |
BIN
plugins/55/indexmenu/images/bj_ubuntu.png/line.png
Normal file
After Width: | Height: | Size: 128 B |
BIN
plugins/55/indexmenu/images/bj_ubuntu.png/minus.png
Normal file
After Width: | Height: | Size: 171 B |
BIN
plugins/55/indexmenu/images/bj_ubuntu.png/minusbottom.png
Normal file
After Width: | Height: | Size: 167 B |
BIN
plugins/55/indexmenu/images/bj_ubuntu.png/nolines_minus.png
Normal file
After Width: | Height: | Size: 144 B |
BIN
plugins/55/indexmenu/images/bj_ubuntu.png/nolines_plus.png
Normal file
After Width: | Height: | Size: 160 B |
BIN
plugins/55/indexmenu/images/bj_ubuntu.png/page.png
Normal file
After Width: | Height: | Size: 333 B |
BIN
plugins/55/indexmenu/images/bj_ubuntu.png/plus.png
Normal file
After Width: | Height: | Size: 183 B |
BIN
plugins/55/indexmenu/images/bj_ubuntu.png/plusbottom.png
Normal file
After Width: | Height: | Size: 183 B |
BIN
plugins/55/indexmenu/images/bw.png/base.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
plugins/55/indexmenu/images/bw.png/empty.png
Normal file
After Width: | Height: | Size: 183 B |
BIN
plugins/55/indexmenu/images/bw.png/folder.png
Normal file
After Width: | Height: | Size: 3 KiB |
BIN
plugins/55/indexmenu/images/bw.png/folderh.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
plugins/55/indexmenu/images/bw.png/folderhopen.png
Normal file
After Width: | Height: | Size: 399 B |
BIN
plugins/55/indexmenu/images/bw.png/folderopen.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
2
plugins/55/indexmenu/images/bw.png/info.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
author=Dric
|
||||||
|
description=Black and White PNG icons.
|
BIN
plugins/55/indexmenu/images/bw.png/join.png
Normal file
After Width: | Height: | Size: 201 B |
BIN
plugins/55/indexmenu/images/bw.png/joinbottom.png
Normal file
After Width: | Height: | Size: 204 B |
BIN
plugins/55/indexmenu/images/bw.png/line.png
Normal file
After Width: | Height: | Size: 198 B |
BIN
plugins/55/indexmenu/images/bw.png/minus.png
Normal file
After Width: | Height: | Size: 234 B |
BIN
plugins/55/indexmenu/images/bw.png/minusbottom.png
Normal file
After Width: | Height: | Size: 235 B |
BIN
plugins/55/indexmenu/images/bw.png/nolines_minus.png
Normal file
After Width: | Height: | Size: 207 B |
BIN
plugins/55/indexmenu/images/bw.png/nolines_plus.png
Normal file
After Width: | Height: | Size: 212 B |
BIN
plugins/55/indexmenu/images/bw.png/page.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
plugins/55/indexmenu/images/bw.png/plus.png
Normal file
After Width: | Height: | Size: 243 B |
BIN
plugins/55/indexmenu/images/bw.png/plusbottom.png
Normal file
After Width: | Height: | Size: 242 B |
BIN
plugins/55/indexmenu/images/close.gif
Normal file
After Width: | Height: | Size: 64 B |
BIN
plugins/55/indexmenu/images/contis_tango.png/base.png
Normal file
After Width: | Height: | Size: 440 B |
BIN
plugins/55/indexmenu/images/contis_tango.png/empty.png
Normal file
After Width: | Height: | Size: 157 B |
BIN
plugins/55/indexmenu/images/contis_tango.png/folder.png
Normal file
After Width: | Height: | Size: 417 B |
BIN
plugins/55/indexmenu/images/contis_tango.png/folderh.png
Normal file
After Width: | Height: | Size: 440 B |
BIN
plugins/55/indexmenu/images/contis_tango.png/folderhopen.png
Normal file
After Width: | Height: | Size: 438 B |
BIN
plugins/55/indexmenu/images/contis_tango.png/folderopen.png
Normal file
After Width: | Height: | Size: 438 B |
3
plugins/55/indexmenu/images/contis_tango.png/info.txt
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
author=Andreas Neuhold <office [at] hd-recording [dot] at>
|
||||||
|
url=
|
||||||
|
description=
|
BIN
plugins/55/indexmenu/images/contis_tango.png/join.png
Normal file
After Width: | Height: | Size: 131 B |
BIN
plugins/55/indexmenu/images/contis_tango.png/joinbottom.png
Normal file
After Width: | Height: | Size: 132 B |
BIN
plugins/55/indexmenu/images/contis_tango.png/line.png
Normal file
After Width: | Height: | Size: 128 B |
BIN
plugins/55/indexmenu/images/contis_tango.png/minus.png
Normal file
After Width: | Height: | Size: 171 B |
BIN
plugins/55/indexmenu/images/contis_tango.png/minusbottom.png
Normal file
After Width: | Height: | Size: 167 B |
BIN
plugins/55/indexmenu/images/contis_tango.png/nolines_minus.png
Normal file
After Width: | Height: | Size: 144 B |
BIN
plugins/55/indexmenu/images/contis_tango.png/nolines_plus.png
Normal file
After Width: | Height: | Size: 160 B |
BIN
plugins/55/indexmenu/images/contis_tango.png/page.png
Normal file
After Width: | Height: | Size: 333 B |
BIN
plugins/55/indexmenu/images/contis_tango.png/plus.png
Normal file
After Width: | Height: | Size: 183 B |
BIN
plugins/55/indexmenu/images/contis_tango.png/plusbottom.png
Normal file
After Width: | Height: | Size: 183 B |
BIN
plugins/55/indexmenu/images/default/base.gif
Normal file
After Width: | Height: | Size: 578 B |
BIN
plugins/55/indexmenu/images/default/empty.gif
Normal file
After Width: | Height: | Size: 62 B |
BIN
plugins/55/indexmenu/images/default/folder.gif
Normal file
After Width: | Height: | Size: 352 B |
BIN
plugins/55/indexmenu/images/default/folderh.gif
Normal file
After Width: | Height: | Size: 572 B |
BIN
plugins/55/indexmenu/images/default/folderhopen.gif
Normal file
After Width: | Height: | Size: 577 B |
BIN
plugins/55/indexmenu/images/default/folderopen.gif
Normal file
After Width: | Height: | Size: 354 B |
2
plugins/55/indexmenu/images/default/info.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
author=Samuele Tognini
|
||||||
|
url=http://samuele.netsons.org/dokuwiki
|
BIN
plugins/55/indexmenu/images/default/join.gif
Normal file
After Width: | Height: | Size: 69 B |
BIN
plugins/55/indexmenu/images/default/joinbottom.gif
Normal file
After Width: | Height: | Size: 66 B |
BIN
plugins/55/indexmenu/images/default/line.gif
Normal file
After Width: | Height: | Size: 66 B |
BIN
plugins/55/indexmenu/images/default/minus.gif
Normal file
After Width: | Height: | Size: 86 B |
BIN
plugins/55/indexmenu/images/default/minusbottom.gif
Normal file
After Width: | Height: | Size: 85 B |
BIN
plugins/55/indexmenu/images/default/nolines_minus.gif
Normal file
After Width: | Height: | Size: 861 B |
BIN
plugins/55/indexmenu/images/default/nolines_plus.gif
Normal file
After Width: | Height: | Size: 870 B |
BIN
plugins/55/indexmenu/images/default/page.gif
Normal file
After Width: | Height: | Size: 565 B |
BIN
plugins/55/indexmenu/images/default/plus.gif
Normal file
After Width: | Height: | Size: 89 B |
BIN
plugins/55/indexmenu/images/default/plusbottom.gif
Normal file
After Width: | Height: | Size: 88 B |