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 |