initial commit of starter install drupal 7
This commit is contained in:
@@ -0,0 +1,339 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 Lesser 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.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 Lesser General
|
||||
Public License instead of this License.
|
||||
@@ -0,0 +1,84 @@
|
||||
|
||||
/* Stylesheet for Better Autocomplete jQuery plugin by Didrik Nordström */
|
||||
|
||||
/**
|
||||
* Do NOT edit this file! Instead, include another stylesheet with your custom
|
||||
* overrides and extensions. Copy the selectors you need to edit and place them
|
||||
* in your CSS file. And remember to include that file AFTER this file.
|
||||
*/
|
||||
|
||||
/* Reset styles for lists */
|
||||
|
||||
.better-autocomplete,
|
||||
.better-autocomplete > .result {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.better-autocomplete {
|
||||
text-align: left;
|
||||
font-family: 'Lucida Grande', Helvetica, Arial, Verdana, sans-serif;
|
||||
overflow: auto;
|
||||
cursor: default;
|
||||
background: white;
|
||||
border-bottom: 1px solid #bfbfbf;
|
||||
}
|
||||
|
||||
/* Groups */
|
||||
|
||||
.better-autocomplete > .group {
|
||||
position: relative;
|
||||
padding: 3px;
|
||||
text-align: center;
|
||||
background: #e7e7e7;
|
||||
border-color: #bfbfbf;
|
||||
border-width: 1px 1px 0 1px;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.better-autocomplete > .group > h3 {
|
||||
font-size: 11px;
|
||||
color: #555;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Results */
|
||||
|
||||
.better-autocomplete > .result {
|
||||
position: relative;
|
||||
padding: 5px 7px;
|
||||
background: white;
|
||||
border-color: #bfbfbf;
|
||||
border-width: 1px 1px 0 1px;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.better-autocomplete > .result > h4 {
|
||||
font-size: 12px;
|
||||
color: #3c3c3c;
|
||||
padding: 0;
|
||||
margin: 0 0 2px 0;
|
||||
}
|
||||
|
||||
.better-autocomplete > .result > p {
|
||||
font-size: 11px;
|
||||
line-height: 1.3;
|
||||
color: #6f6f6f;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Highlighting */
|
||||
|
||||
.better-autocomplete > .result.highlight {
|
||||
background: #0075ba;
|
||||
border-top-color: #1f4a64;
|
||||
}
|
||||
|
||||
.better-autocomplete > .result.highlight > h4,
|
||||
.better-autocomplete > .result.highlight > p {
|
||||
color: white;
|
||||
}
|
||||
+943
@@ -0,0 +1,943 @@
|
||||
|
||||
/**
|
||||
* @fileOverview Better Autocomplete is a flexible jQuery plugin which offers
|
||||
* rich text autocompletion, both from local and remote sources.
|
||||
*
|
||||
* @author Didrik Nordström, http://betamos.se/
|
||||
*
|
||||
* @version v1.0-dev
|
||||
*
|
||||
* @requires
|
||||
* <ul><li>
|
||||
* jQuery 1.4+
|
||||
* </li><li>
|
||||
* IE7+ or any decent webkit/gecko-based web browser
|
||||
* </li></ul>
|
||||
*
|
||||
* @preserve Better Autocomplete v1.0-dev
|
||||
* https://github.com/betamos/Better-Autocomplete
|
||||
*
|
||||
* Copyright 2011, Didrik Nordström, http://betamos.se/
|
||||
* Dual licensed under the MIT or GPL Version 2 licenses.
|
||||
*
|
||||
* Requires jQuery 1.4+
|
||||
* http://jquery.com/
|
||||
*/
|
||||
|
||||
/**
|
||||
* Create or alter an autocomplete object instance that belongs to
|
||||
* the elements in the selection. Make sure there are only text field elements
|
||||
* in the selection.
|
||||
*
|
||||
* @constructor
|
||||
*
|
||||
* @name jQuery.betterAutocomplete
|
||||
*
|
||||
* @param {String} method
|
||||
* Should be one of the following:
|
||||
* <ul><li>
|
||||
* init: Initiate Better Autocomplete instances on the text input elements
|
||||
* in the current jQuery selection. They are enabled by default. The other
|
||||
* parameters are then required.
|
||||
* </li><li>
|
||||
* enable: In this jQuery selection, reenable the Better Autocomplete
|
||||
* instances.
|
||||
* </li><li>
|
||||
* disable: In this jQuery selection, disable the Better Autocomplete
|
||||
* instances.
|
||||
* </li><li>
|
||||
* destroy: In this jQuery selection, destroy the Better Autocomplete
|
||||
* instances. It will not be possible to reenable them after this.
|
||||
* </li></ul>
|
||||
*
|
||||
* @param {String|Object} [resource]
|
||||
* If String, it will become the path for a remote resource. If not, it will
|
||||
* be treated like a local resource. The path should provide JSON objects
|
||||
* upon HTTP requests.
|
||||
*
|
||||
* @param {Object} [options]
|
||||
* An object with configurable options:
|
||||
* <ul><li>
|
||||
* charLimit: (default=3 for remote or 1 for local resource) The minimum
|
||||
* number of chars to do an AJAX call. A typical use case for this limit is
|
||||
* to reduce server load.
|
||||
* </li><li>
|
||||
* delay: (default=350) The time in ms between last keypress and AJAX call.
|
||||
* Typically used to prevent looking up irrelevant strings while the user
|
||||
* is still typing. Only relevant for remote resources.
|
||||
* </li><li>
|
||||
* caseSensitive: (default=false) If the search should be case sensitive.
|
||||
* If false, query strings will be converted to lowercase.
|
||||
* </li><li>
|
||||
* cacheLimit: (default=256 for remote or 0 for local resource) The maximum
|
||||
* number of result objects to store in the cache. This option reduces
|
||||
* server load if the user deletes characters to check back on previous
|
||||
* results. To disable caching of previous results, set this option to 0.
|
||||
* </li><li>
|
||||
* remoteTimeout: (default=10000) The timeout for remote (AJAX) calls.
|
||||
* </li><li>
|
||||
* crossOrigin: (default=false) Set to true if cross origin requests will
|
||||
* be performed, i.e. that the remote URL has a different domain. This will
|
||||
* force Internet Explorer to use "jsonp" instead of "json" as datatype.
|
||||
* </li><li>
|
||||
* selectKeys: (default=[9, 13]) The key codes for keys which will select
|
||||
* the current highlighted element. The defaults are tab, enter.
|
||||
* </li><li>
|
||||
* autoHighlight: (default=true) Automatically highlight the first result.
|
||||
* </li></ul>
|
||||
*
|
||||
* @param {Object} [callbacks]
|
||||
* An object containing optional callback functions on certain events. See
|
||||
* {@link callbacks} for details. These callbacks should be used when
|
||||
* customization of the default behavior of Better Autocomplete is required.
|
||||
*
|
||||
* @returns {Object}
|
||||
* The jQuery object with the same element selection, for chaining.
|
||||
*/
|
||||
|
||||
(function($) {
|
||||
|
||||
$.fn.betterAutocomplete = function(method) {
|
||||
|
||||
/*
|
||||
* Each method expects the "this" object to be a valid DOM text input node.
|
||||
* The methods "enable", "disable" and "destroy" expects an instance of a
|
||||
* BetterAutocomplete object as their first argument.
|
||||
*/
|
||||
var methods = {
|
||||
init: function(resource, options, callbacks) {
|
||||
var $input = $(this),
|
||||
bac = new BetterAutocomplete($input, resource, options, callbacks);
|
||||
$input.data('better-autocomplete', bac);
|
||||
bac.enable();
|
||||
},
|
||||
enable: function(bac) {
|
||||
bac.enable();
|
||||
},
|
||||
disable: function(bac) {
|
||||
bac.disable();
|
||||
},
|
||||
destroy: function(bac) {
|
||||
bac.destroy();
|
||||
}
|
||||
}, args = Array.prototype.slice.call(arguments, 1);
|
||||
|
||||
// Method calling logic
|
||||
this.each(function() {
|
||||
switch (method) {
|
||||
case 'init':
|
||||
methods[method].apply(this, args);
|
||||
break;
|
||||
case 'enable':
|
||||
case 'disable':
|
||||
case 'destroy':
|
||||
var bac = $(this).data('better-autocomplete');
|
||||
if (bac instanceof BetterAutocomplete) {
|
||||
methods[method].call(this, bac);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
$.error(['Method', method,
|
||||
'does not exist in jQuery.betterAutocomplete.'].join(' '));
|
||||
}
|
||||
});
|
||||
|
||||
// Maintain chainability
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* The BetterAutocomplete constructor function. Returns a BetterAutocomplete
|
||||
* instance object.
|
||||
*
|
||||
* @private @constructor
|
||||
* @name BetterAutocomplete
|
||||
*
|
||||
* @param {Object} $input
|
||||
* A single input element wrapped in jQuery.
|
||||
*/
|
||||
var BetterAutocomplete = function($input, resource, options, callbacks) {
|
||||
|
||||
var lastRenderedQuery = '',
|
||||
cache = {}, // Key-valued caching of search results
|
||||
cacheOrder = [], // Array of query strings, in the order they are added
|
||||
cacheSize = 0, // Keep count of the cache's size
|
||||
timer, // Used for options.delay
|
||||
activeRemoteCalls = [], // A flat array of query strings that are pending
|
||||
disableMouseHighlight = false, // Suppress the autotriggered mouseover event
|
||||
inputEvents = {},
|
||||
isLocal = ($.type(resource) != 'string'),
|
||||
$results = $('<ul />').addClass('better-autocomplete'),
|
||||
hiddenResults = true, // $results are hidden
|
||||
preventBlurTimer = null; // IE bug workaround, see below in code.
|
||||
|
||||
options = $.extend({
|
||||
charLimit: isLocal ? 1 : 3,
|
||||
delay: 350, // milliseconds
|
||||
caseSensitive: false,
|
||||
cacheLimit: isLocal ? 0 : 256, // Number of result objects
|
||||
remoteTimeout: 10000, // milliseconds
|
||||
crossOrigin: false,
|
||||
selectKeys: [9, 13], // [tab, enter]
|
||||
autoHighlight: true // Automatically highlight the topmost result
|
||||
}, options);
|
||||
|
||||
callbacks = $.extend({}, defaultCallbacks, callbacks);
|
||||
|
||||
callbacks.insertSuggestionList($results, $input);
|
||||
|
||||
inputEvents.focus = function() {
|
||||
// If the blur timer is active, a redraw is redundant.
|
||||
preventBlurTimer || redraw(true);
|
||||
};
|
||||
|
||||
inputEvents.blur = function() {
|
||||
// If the blur prevention timer is active, refocus the input, since the
|
||||
// blur event can not be cancelled.
|
||||
if (preventBlurTimer) {
|
||||
$input.focus();
|
||||
}
|
||||
else {
|
||||
// The input has already lost focus, so redraw the suggestion list.
|
||||
redraw();
|
||||
}
|
||||
};
|
||||
|
||||
inputEvents.keydown = function(event) {
|
||||
var index = getHighlightedIndex();
|
||||
// If an arrow key is pressed and a result is highlighted
|
||||
if ($.inArray(event.keyCode, [38, 40]) >= 0 && $results.children().length > 0) {
|
||||
var newIndex,
|
||||
size = $('.result', $results).length;
|
||||
switch (event.keyCode) {
|
||||
case 38: // Up arrow
|
||||
newIndex = Math.max(0, index - 1);
|
||||
break;
|
||||
case 40: // Down arrow
|
||||
newIndex = Math.min(size - 1, index + 1);
|
||||
break;
|
||||
}
|
||||
disableMouseHighlight = true;
|
||||
setHighlighted(newIndex, 'key', true);
|
||||
return false;
|
||||
}
|
||||
// A select key has been pressed
|
||||
else if ($.inArray(event.keyCode, options.selectKeys) >= 0 &&
|
||||
!event.shiftKey && !event.ctrlKey && !event.altKey &&
|
||||
!event.metaKey) {
|
||||
select();
|
||||
return event.keyCode == 9; // Never cancel tab
|
||||
}
|
||||
};
|
||||
|
||||
inputEvents.keyup = inputEvents.click = reprocess;
|
||||
|
||||
$results.delegate('.result', {
|
||||
// When the user hovers a result with the mouse, highlight it.
|
||||
mouseenter: function() {
|
||||
if (disableMouseHighlight) {
|
||||
return;
|
||||
}
|
||||
setHighlighted($('.result', $results).index($(this)), 'mouse');
|
||||
},
|
||||
mousemove: function() {
|
||||
// Enable mouseover again.
|
||||
disableMouseHighlight = false;
|
||||
},
|
||||
mousedown: function() {
|
||||
select();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// Prevent blur when clicking on group titles, scrollbars etc.,
|
||||
// This event is triggered after the others because of bubbling.
|
||||
$results.mousedown(function() {
|
||||
// Bug in IE where clicking on scrollbar would trigger a blur event for the
|
||||
// input field, despite using preventDefault() on the mousedown event.
|
||||
// This workaround locks the blur event on the input for a small time.
|
||||
clearTimeout(preventBlurTimer);
|
||||
preventBlurTimer = setTimeout(function() {
|
||||
preventBlurTimer = null;
|
||||
}, 50);
|
||||
return false;
|
||||
});
|
||||
|
||||
// If auto highlight is off, remove highlighting
|
||||
$results.mouseleave(function() {
|
||||
if (!options.autoHighlight) {
|
||||
setHighlighted(-1);
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
* PUBLIC METHODS
|
||||
*/
|
||||
|
||||
/**
|
||||
* Enable this instance.
|
||||
*/
|
||||
this.enable = function() {
|
||||
// Turn off the browser's autocompletion
|
||||
$input
|
||||
.attr('autocomplete', 'OFF')
|
||||
.attr('aria-autocomplete', 'list');
|
||||
$input.bind(inputEvents);
|
||||
};
|
||||
|
||||
/**
|
||||
* Disable this instance.
|
||||
*/
|
||||
this.disable = function() {
|
||||
$input
|
||||
.removeAttr('autocomplete')
|
||||
.removeAttr('aria-autocomplete');
|
||||
$results.hide();
|
||||
$input.unbind(inputEvents);
|
||||
};
|
||||
|
||||
/**
|
||||
* Disable and remove this instance. This instance should not be reused.
|
||||
*/
|
||||
this.destroy = function() {
|
||||
$results.remove();
|
||||
$input.unbind(inputEvents);
|
||||
$input.removeData('better-autocomplete');
|
||||
};
|
||||
|
||||
/*
|
||||
* PRIVATE METHODS
|
||||
*/
|
||||
|
||||
/**
|
||||
* Add an array of results to the cache. Internal methods always reads from
|
||||
* the cache, so this method must be invoked even when caching is not used,
|
||||
* e.g. when using local results. This method automatically clears as much of
|
||||
* the cache as required to fit within the cache limit.
|
||||
*
|
||||
* @param {String} query
|
||||
* The query to set the results to.
|
||||
*
|
||||
* @param {Array[Object]} results
|
||||
* The array of results for this query.
|
||||
*/
|
||||
var cacheResults = function(query, results) {
|
||||
cacheSize += results.length;
|
||||
// Now reduce size until it fits
|
||||
while (cacheSize > options.cacheLimit && cacheOrder.length) {
|
||||
var key = cacheOrder.shift();
|
||||
cacheSize -= cache[key].length;
|
||||
delete cache[key];
|
||||
}
|
||||
cacheOrder.push(query);
|
||||
cache[query] = results;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set highlight to a specific result item
|
||||
*
|
||||
* @param {Number} index
|
||||
* The result item's index, or negative if highlight should be removed.
|
||||
*
|
||||
* @param {String} [trigger]
|
||||
* What triggered the highlight: "mouse", "key" or "auto". If index is
|
||||
* negative trigger may be omitted.
|
||||
*
|
||||
* @param {Boolean} [autoScroll]
|
||||
* (default=false) If scrolling of the results list should be automated.
|
||||
*/
|
||||
var setHighlighted = function(index, trigger, autoScroll) {
|
||||
//console.log('Index: '+index)
|
||||
var prevIndex = getHighlightedIndex(),
|
||||
$resultList = $('.result', $results);
|
||||
//console.log('prevIndex: '+prevIndex)
|
||||
$resultList.removeClass('highlight');
|
||||
|
||||
if (index < 0) {
|
||||
return
|
||||
}
|
||||
$resultList.eq(index).addClass('highlight')
|
||||
|
||||
if (prevIndex != index) {
|
||||
var result = getResultByIndex(index);
|
||||
callbacks.highlight(result, $input, trigger);
|
||||
}
|
||||
|
||||
// Scrolling
|
||||
var up = index == 0 || index < prevIndex,
|
||||
$scrollTo = $resultList.eq(index);
|
||||
|
||||
if (!autoScroll) {
|
||||
return;
|
||||
}
|
||||
// Scrolling up, then make sure to show the group title
|
||||
if ($scrollTo.prev().is('.group') && up) {
|
||||
$scrollTo = $scrollTo.prev();
|
||||
}
|
||||
// Is $scrollTo partly above the visible region?
|
||||
if ($scrollTo.position().top < 0) {
|
||||
$results.scrollTop($scrollTo.position().top + $results.scrollTop());
|
||||
}
|
||||
// Or is it partly below the visible region?
|
||||
else if (($scrollTo.position().top + $scrollTo.outerHeight()) >
|
||||
$results.height()) {
|
||||
$results.scrollTop($scrollTo.position().top + $results.scrollTop() +
|
||||
$scrollTo.outerHeight() - $results.height());
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the index of the currently highlighted result item
|
||||
*
|
||||
* @returns {Number}
|
||||
* The result's index or -1 if no result is highlighted.
|
||||
*/
|
||||
var getHighlightedIndex = function() {
|
||||
var res = $('.result.highlight', $results)
|
||||
ind= $('.result', $results).index(res);
|
||||
return ind
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the result object with the specific position in the results list
|
||||
*
|
||||
* @param {Number} index
|
||||
* The index of the item in the current result list.
|
||||
*
|
||||
* @returns {Object}
|
||||
* The result object or null if index out of bounds.
|
||||
*/
|
||||
var getResultByIndex = function(index) {
|
||||
var $result = $('.result', $results).eq(index);
|
||||
if (!$result.length) {
|
||||
return; // No selectable element
|
||||
}
|
||||
return $result.data('result');
|
||||
};
|
||||
|
||||
/**
|
||||
* Select the current highlighted element, if any.
|
||||
*/
|
||||
var select = function() {
|
||||
var highlighted = getHighlightedIndex(),
|
||||
result = getResultByIndex(highlighted);
|
||||
callbacks.select(result, $input);
|
||||
// Redraw again, if the callback changed focus or content
|
||||
reprocess();
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch results asynchronously via AJAX.
|
||||
* Errors are ignored.
|
||||
*
|
||||
* @param {String} query
|
||||
* The query string.
|
||||
*/
|
||||
var fetchResults = function(query) {
|
||||
// Synchronously fetch local data
|
||||
if (isLocal) {
|
||||
cacheResults(query, callbacks.queryLocalResults(query, resource,
|
||||
options.caseSensitive));
|
||||
redraw();
|
||||
}
|
||||
// Asynchronously fetch remote data
|
||||
else {
|
||||
activeRemoteCalls.push(query);
|
||||
var url = callbacks.constructURL(resource, query);
|
||||
callbacks.beginFetching($input);
|
||||
callbacks.fetchRemoteData(url, function(data) {
|
||||
var searchResults = callbacks.processRemoteData(data);
|
||||
if (!$.isArray(searchResults)) {
|
||||
searchResults = [];
|
||||
}
|
||||
cacheResults(query, searchResults);
|
||||
// Remove the query from active remote calls, since it's finished
|
||||
activeRemoteCalls = $.grep(activeRemoteCalls, function(value) {
|
||||
return value != query;
|
||||
});
|
||||
if (!activeRemoteCalls.length) {
|
||||
callbacks.finishFetching($input);
|
||||
}
|
||||
redraw();
|
||||
}, options.remoteTimeout, options.crossOrigin);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Reprocess the contents of the input field, fetch data and redraw if
|
||||
* necessary.
|
||||
*
|
||||
* @param {Object} [event]
|
||||
* The event that triggered the reprocessing. Not always present.
|
||||
*/
|
||||
function reprocess(event) {
|
||||
// If this call was triggered by an arrow key, cancel the reprocessing.
|
||||
if ($.type(event) == 'object' && event.type == 'keyup' &&
|
||||
$.inArray(event.keyCode, [38, 40]) >= 0) {
|
||||
return;
|
||||
}
|
||||
var query = callbacks.canonicalQuery($input.val(), options.caseSensitive);
|
||||
clearTimeout(timer);
|
||||
// Indicate that timer is inactive
|
||||
timer = null;
|
||||
redraw();
|
||||
if (query.length >= options.charLimit && !$.isArray(cache[query]) &&
|
||||
$.inArray(query, activeRemoteCalls) == -1) {
|
||||
// Fetching is required
|
||||
$results.empty();
|
||||
if (isLocal) {
|
||||
fetchResults(query);
|
||||
}
|
||||
else {
|
||||
timer = setTimeout(function() {
|
||||
fetchResults(query);
|
||||
timer = null;
|
||||
}, options.delay);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Redraws the autocomplete list based on current query and focus.
|
||||
*
|
||||
* @param {Boolean} [focus]
|
||||
* (default=false) Force to treat the input element like it's focused.
|
||||
*/
|
||||
var redraw = function(focus) {
|
||||
var query = callbacks.canonicalQuery($input.val(), options.caseSensitive);
|
||||
|
||||
// The query does not exist in db
|
||||
if (!$.isArray(cache[query])) {
|
||||
lastRenderedQuery = null;
|
||||
$results.empty();
|
||||
}
|
||||
// The query exists and is not already rendered
|
||||
else if (lastRenderedQuery !== query) {
|
||||
lastRenderedQuery = query;
|
||||
renderResults(cache[query]);
|
||||
if (options.autoHighlight && $('.result', $results).length > 0) {
|
||||
setHighlighted(0, 'auto');
|
||||
}
|
||||
}
|
||||
// Finally show/hide based on focus and emptiness
|
||||
if (($input.is(':focus') || focus) && !$results.is(':empty')) {
|
||||
$results.filter(':hidden').show() // Show if hidden
|
||||
.scrollTop($results.data('scroll-top')); // Reset the lost scrolling
|
||||
if (hiddenResults) {
|
||||
hiddenResults = false;
|
||||
callbacks.afterShow($results);
|
||||
}
|
||||
}
|
||||
else if ($results.is(':visible')) {
|
||||
// Store the scrolling position for later
|
||||
$results.data('scroll-top', $results.scrollTop())
|
||||
.hide(); // Hiding it resets it's scrollTop
|
||||
if (!hiddenResults) {
|
||||
hiddenResults = true;
|
||||
callbacks.afterHide($results);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Regenerate the DOM content within the results list for a given set of
|
||||
* results. Heavy method, use only when necessary.
|
||||
*
|
||||
* @param {Array[Object]} results
|
||||
* An array of result objects to render.
|
||||
*/
|
||||
var renderResults = function(results) {
|
||||
$results.empty();
|
||||
var groups = {}; // Key is the group name, value is the heading element.
|
||||
|
||||
$.each(results, function(index, result) {
|
||||
if ($.type(result) != 'object') {
|
||||
return; // Continue
|
||||
}
|
||||
|
||||
var output = callbacks.themeResult(result);
|
||||
if ($.type(output) != 'string') {
|
||||
return; // Continue
|
||||
}
|
||||
|
||||
// Add the group if it doesn't exist
|
||||
var group = callbacks.getGroup(result);
|
||||
if ($.type(group) == 'string' && !groups[group]) {
|
||||
var $groupHeading = $('<li />').addClass('group')
|
||||
.append($('<h3 />').html(group))
|
||||
.appendTo($results);
|
||||
groups[group] = $groupHeading;
|
||||
}
|
||||
|
||||
var $result = $('<li />').addClass('result')
|
||||
.append(output)
|
||||
.data('result', result) // Store the result object on this DOM element
|
||||
.addClass(result.addClass);
|
||||
|
||||
// First groupless item
|
||||
if ($.type(group) != 'string' &&
|
||||
!$results.children().first().is('.result')) {
|
||||
$results.prepend($result);
|
||||
return; // Continue
|
||||
}
|
||||
var $traverseFrom = ($.type(group) == 'string') ?
|
||||
groups[group] : $results.children().first();
|
||||
var $target = $traverseFrom.nextUntil('.group').last();
|
||||
$result.insertAfter($target.length ? $target : $traverseFrom);
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
/*
|
||||
* CALLBACK METHODS
|
||||
*/
|
||||
|
||||
/**
|
||||
* These callbacks are supposed to be overridden by you when you need
|
||||
* customization of the default behavior. When you are overriding a callback
|
||||
* function, it is a good idea to copy the source code from the default
|
||||
* callback function, as a skeleton.
|
||||
*
|
||||
* @name callbacks
|
||||
* @namespace
|
||||
*/
|
||||
var defaultCallbacks = {
|
||||
/**
|
||||
* @lends callbacks.prototype
|
||||
*/
|
||||
|
||||
/**
|
||||
* Gets fired when the user selects a result by clicking or using the
|
||||
* keyboard to select an element.
|
||||
*
|
||||
* <br /><br /><em>Default behavior: Inserts the result's title into the
|
||||
* input field.</em>
|
||||
*
|
||||
* @param {Object} result
|
||||
* The result object that was selected.
|
||||
*
|
||||
* @param {Object} $input
|
||||
* The input DOM element, wrapped in jQuery.
|
||||
*/
|
||||
select: function(result, $input) {
|
||||
$input.val(result.title);
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets fired when the a result is highlighted. This may happen either
|
||||
* automatically or by user action.
|
||||
*
|
||||
* <br /><br /><em>Default behavior: Does nothing.</em>
|
||||
*
|
||||
* @param {Object} result
|
||||
* The result object that was selected.
|
||||
*
|
||||
* @param {Object} $input
|
||||
* The input DOM element, wrapped in jQuery.
|
||||
*
|
||||
* @param {String} trigger
|
||||
* The event which triggered the highlighting. Must be one of the
|
||||
* following:
|
||||
* <ul><li>
|
||||
* "mouse": A mouseover event triggered the highlighting.
|
||||
* </li><li>
|
||||
* "key": The user pressed an arrow key to navigate amongst the results.
|
||||
* </li><li>
|
||||
* "auto": If options.autoHighlight is set, an automatic highlight of the
|
||||
* first result will occur each time a new result set is rendered.
|
||||
* </li></ul>
|
||||
*/
|
||||
highlight: function(result, $input, trigger) {
|
||||
// Does nothing
|
||||
},
|
||||
|
||||
/**
|
||||
* Given a result object, theme it to HTML.
|
||||
*
|
||||
* <br /><br /><em>Default behavior: Wraps result.title in an h4 tag, and
|
||||
* result.description in a p tag. Note that no sanitization of malicious
|
||||
* scripts is done here. Whatever is within the title/description is just
|
||||
* printed out. May contain HTML.</em>
|
||||
*
|
||||
* @param {Object} result
|
||||
* The result object that should be rendered.
|
||||
*
|
||||
* @returns {String}
|
||||
* HTML output, will be wrapped in a list element.
|
||||
*/
|
||||
themeResult: function(result) {
|
||||
var output = [];
|
||||
if ($.type(result.title) == 'string') {
|
||||
output.push('<h4>', result.title, '</h4>');
|
||||
}
|
||||
if ($.type(result.description) == 'string') {
|
||||
output.push('<p>', result.description, '</p>');
|
||||
}
|
||||
return output.join('');
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieve local results from the local resource by providing a query
|
||||
* string.
|
||||
*
|
||||
* <br /><br /><em>Default behavior: Automatically handles arrays, if the
|
||||
* data inside each element is either a plain string or a result object.
|
||||
* If it is a result object, it will match the query string against the
|
||||
* title and description property. Search is not case sensitive.</em>
|
||||
*
|
||||
* @param {String} query
|
||||
* The query string, unescaped. May contain any UTF-8 character.
|
||||
* If case insensitive, it already is lowercased.
|
||||
*
|
||||
* @param {Object} resource
|
||||
* The resource provided in the {@link jQuery.betterAutocomplete} init
|
||||
* constructor.
|
||||
*
|
||||
* @param {Boolean} caseSensitive
|
||||
* From options.caseSensitive, the searching should be case sensitive.
|
||||
*
|
||||
* @returns {Array[Object]}
|
||||
* A flat array containing pure result objects. May be an empty array.
|
||||
*/
|
||||
queryLocalResults: function(query, resource, caseSensitive) {
|
||||
if (!$.isArray(resource)) {
|
||||
// Per default Better Autocomplete only handles arrays
|
||||
return [];
|
||||
}
|
||||
var results = [];
|
||||
$.each(resource, function(i, value) {
|
||||
switch ($.type(value)) {
|
||||
case 'string': // Flat array of strings
|
||||
if ((caseSensitive ? value : value.toLowerCase())
|
||||
.indexOf(query) >= 0) {
|
||||
// Match found
|
||||
results.push({ title: value });
|
||||
}
|
||||
break;
|
||||
case 'object': // Array of result objects
|
||||
if ($.type(value.title) == 'string' &&
|
||||
(caseSensitive ? value.title : value.title.toLowerCase())
|
||||
.indexOf(query) >= 0) {
|
||||
// Match found in title field
|
||||
results.push(value);
|
||||
}
|
||||
else if ($.type(value.description) == 'string' &&
|
||||
(caseSensitive ? value.description :
|
||||
value.description.toLowerCase()).indexOf(query) >= 0) {
|
||||
// Match found in description field
|
||||
results.push(value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
return results;
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetch remote result data and return it using completeCallback when
|
||||
* fetching is finished. Must be asynchronous in order to not freeze the
|
||||
* Better Autocomplete instance.
|
||||
*
|
||||
* <br /><br /><em>Default behavior: Fetches JSON data from the url, using
|
||||
* the jQuery.ajax() method. Errors are ignored.</em>
|
||||
*
|
||||
* @param {String} url
|
||||
* The URL to fetch data from.
|
||||
*
|
||||
* @param {Function} completeCallback
|
||||
* This function must be called, even if an error occurs. It takes zero
|
||||
* or one parameter: the data that was fetched.
|
||||
*
|
||||
* @param {Number} timeout
|
||||
* The preferred timeout for the request. This callback should respect
|
||||
* the timeout.
|
||||
*
|
||||
* @param {Boolean} crossOrigin
|
||||
* True if a cross origin request should be performed.
|
||||
*/
|
||||
fetchRemoteData: function(url, completeCallback, timeout, crossOrigin) {
|
||||
$.ajax({
|
||||
url: url,
|
||||
dataType: crossOrigin && !$.support.cors ? 'jsonp' : 'json',
|
||||
timeout: timeout,
|
||||
success: function(data, textStatus) {
|
||||
completeCallback(data);
|
||||
},
|
||||
error: function(jqXHR, textStatus, errorThrown) {
|
||||
completeCallback();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Process remote fetched data by extracting an array of result objects
|
||||
* from it. This callback is useful if the fetched data is not the plain
|
||||
* results array, but a more complicated object which does contain results.
|
||||
*
|
||||
* <br /><br /><em>Default behavior: If the data is defined and is an
|
||||
* array, return it. Otherwise return an empty array.</em>
|
||||
*
|
||||
* @param {mixed} data
|
||||
* The raw data recieved from the server. Can be undefined.
|
||||
*
|
||||
* @returns {Array[Object]}
|
||||
* A flat array containing result objects. May be an empty array.
|
||||
*/
|
||||
processRemoteData: function(data) {
|
||||
if ($.isArray(data)) {
|
||||
return data;
|
||||
}
|
||||
else {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* From a given result object, return it's group name (if any). Used for
|
||||
* grouping results together.
|
||||
*
|
||||
* <br /><br /><em>Default behavior: If the result has a "group" property
|
||||
* defined, return it.</em>
|
||||
*
|
||||
* @param {Object} result
|
||||
* The result object.
|
||||
*
|
||||
* @returns {String}
|
||||
* The group name, may contain HTML. If no group, don't return anything.
|
||||
*/
|
||||
getGroup: function(result) {
|
||||
if ($.type(result.group) == 'string') {
|
||||
return result.group;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when remote fetching begins.
|
||||
*
|
||||
* <br /><br /><em>Default behavior: Adds the CSS class "fetching" to the
|
||||
* input field, for styling purposes.</em>
|
||||
*
|
||||
* @param {Object} $input
|
||||
* The input DOM element, wrapped in jQuery.
|
||||
*/
|
||||
beginFetching: function($input) {
|
||||
$input.addClass('fetching');
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when fetching is finished. All active requests must finish before
|
||||
* this function is called.
|
||||
*
|
||||
* <br /><br /><em>Default behavior: Removes the "fetching" class.</em>
|
||||
*
|
||||
* @param {Object} $input
|
||||
* The input DOM element, wrapped in jQuery.
|
||||
*/
|
||||
finishFetching: function($input) {
|
||||
$input.removeClass('fetching');
|
||||
},
|
||||
|
||||
/**
|
||||
* Executed after the suggestion list has been shown.
|
||||
*
|
||||
* @param {Object} $results
|
||||
* The suggestion list UL element, wrapped in jQuery.
|
||||
*
|
||||
* <br /><br /><em>Default behavior: Does nothing.</em>
|
||||
*/
|
||||
afterShow: function($results) {},
|
||||
|
||||
/**
|
||||
* Executed after the suggestion list has been hidden.
|
||||
*
|
||||
* @param {Object} $results
|
||||
* The suggestion list UL element, wrapped in jQuery.
|
||||
*
|
||||
* <br /><br /><em>Default behavior: Does nothing.</em>
|
||||
*/
|
||||
afterHide: function($results) {},
|
||||
|
||||
/**
|
||||
* Construct the remote fetching URL.
|
||||
*
|
||||
* <br /><br /><em>Default behavior: Adds "?q=<query>" or "&q=<query>" to the
|
||||
* path. The query string is URL encoded.</em>
|
||||
*
|
||||
* @param {String} path
|
||||
* The path given in the {@link jQuery.betterAutocomplete} constructor.
|
||||
*
|
||||
* @param {String} query
|
||||
* The raw query string. Remember to URL encode this to prevent illegal
|
||||
* character errors.
|
||||
*
|
||||
* @returns {String}
|
||||
* The URL, ready for fetching.
|
||||
*/
|
||||
constructURL: function(path, query) {
|
||||
return path + (path.indexOf('?') > -1 ? '&' : '?') + 'q=' + encodeURIComponent(query);
|
||||
},
|
||||
|
||||
/**
|
||||
* To ease up on server load, treat similar strings the same.
|
||||
*
|
||||
* <br /><br /><em>Default behavior: Trims the query from leading and
|
||||
* trailing whitespace.</em>
|
||||
*
|
||||
* @param {String} rawQuery
|
||||
* The user's raw input.
|
||||
*
|
||||
* @param {Boolean} caseSensitive
|
||||
* Case sensitive. Will convert to lowercase if false.
|
||||
*
|
||||
* @returns {String}
|
||||
* The canonical query associated with this string.
|
||||
*/
|
||||
canonicalQuery: function(rawQuery, caseSensitive) {
|
||||
var query = $.trim(rawQuery);
|
||||
if (!caseSensitive) {
|
||||
query = query.toLowerCase();
|
||||
}
|
||||
return query;
|
||||
},
|
||||
|
||||
/**
|
||||
* Insert the results list into the DOM and position it properly.
|
||||
*
|
||||
* <br /><br /><em>Default behavior: Inserts suggestion list directly
|
||||
* after the input element and sets an absolute position using
|
||||
* jQuery.position() for determining left/top values. Also adds a nice
|
||||
* looking box-shadow to the list.</em>
|
||||
*
|
||||
* @param {Object} $results
|
||||
* The UL list element to insert, wrapped in jQuery.
|
||||
*
|
||||
* @param {Object} $input
|
||||
* The text input element, wrapped in jQuery.
|
||||
*/
|
||||
insertSuggestionList: function($results, $input) {
|
||||
$results.width($input.outerWidth() - 2) // Subtract border width.
|
||||
.css({
|
||||
position: 'absolute',
|
||||
zIndex: 10,
|
||||
maxHeight: '330px',
|
||||
// Visually indicate that results are in the topmost layer
|
||||
boxShadow: '0 0 15px rgba(0, 0, 0, 0.5)'
|
||||
})
|
||||
.hide()
|
||||
.insertAfter($input);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* jQuery focus selector, required by Better Autocomplete.
|
||||
*
|
||||
* @see http://stackoverflow.com/questions/967096/using-jquery-to-test-if-an-input-has-focus/2684561#2684561
|
||||
*/
|
||||
var filters = $.expr[':'];
|
||||
if (!filters.focus) {
|
||||
filters.focus = function(elem) {
|
||||
return elem === document.activeElement && (elem.type || elem.href);
|
||||
};
|
||||
}
|
||||
|
||||
})(jQuery);
|
||||
@@ -0,0 +1,222 @@
|
||||
/**
|
||||
* @file
|
||||
* Linkit styles.
|
||||
*/
|
||||
|
||||
#linkit-modal {
|
||||
overflow: visible;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
#linkit-modal.progress-disabled {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#linkit-modal .linkit-search-element,
|
||||
#linkit-modal .linkit-path-element {
|
||||
background-image: none;
|
||||
font-size: 1em;
|
||||
height: 2em;
|
||||
padding: 3px 25px 3px 5px;
|
||||
width: 440px;
|
||||
}
|
||||
|
||||
#linkit-modal .linkit-search-element.fetching {
|
||||
background-image: url(../throbber.gif);
|
||||
background-position: 98% -8px;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
#linkit-modal .description,
|
||||
#linkit-modal a.fieldset-title {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
#linkit-modal .form-item input,
|
||||
#linkit-modal .form-item select,
|
||||
#linkit-modal fieldset.form-wrapper {
|
||||
-moz-border-radius: 3px;
|
||||
-webkit-border-radius: 3px;
|
||||
border-radius: 3px;
|
||||
-moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15) inset;
|
||||
-webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15) inset;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15) inset;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.ui-dialog.linkit-wrapper {
|
||||
border: 1px solid #b2b2b2;
|
||||
border-bottom-color: #999;
|
||||
-moz-border-radius: 3px;
|
||||
-webkit-border-radius: 3px;
|
||||
border-radius: 3px;
|
||||
-moz-box-shadow: 0 0 3px rgba(0, 0, 0, .15);
|
||||
-webkit-box-shadow: 0 0 3px rgba(0, 0, 0, .15);
|
||||
box-shadow: 0 0 3px rgba(0, 0, 0, .15);
|
||||
overflow: visible;
|
||||
padding: 0;
|
||||
}
|
||||
.ui-dialog.linkit-wrapper .ui-dialog-titlebar {
|
||||
font-weight: bold;
|
||||
font-size: 13px;
|
||||
color: #474747;
|
||||
text-shadow: 0 1px 0 rgba(255,255,255,.75);
|
||||
border-bottom: 1px solid #999;
|
||||
padding: 6px 10px;
|
||||
-moz-border-radius: 2px 2px 0 0;
|
||||
-webkit-border-radius: 2px 2px 0 0;
|
||||
border-radius: 2px 2px 0 0;
|
||||
-moz-box-shadow: 0 1px 0 #fff inset;
|
||||
-webkit-box-shadow: 0 1px 0 #fff inset;
|
||||
box-shadow: 0 1px 0 #fff inset;
|
||||
background: #cfd1cf;
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#cfd1cf));
|
||||
background-image: -moz-linear-gradient(top, #f5f5f5, #cfd1cf);
|
||||
background-image: -webkit-linear-gradient(top, #f5f5f5, #cfd1cf);
|
||||
background-image: -o-linear-gradient(top, #f5f5f5, #cfd1cf);
|
||||
background-image: -ms-linear-gradient(top, #f5f5f5, #cfd1cf);
|
||||
background-image: linear-gradient(top, #f5f5f5, #cfd1cf);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#fff5f5f5', endColorstr='#ffcfd1cf');
|
||||
}
|
||||
#linkit-modal fieldset.form-wrapper {
|
||||
width: 470px;
|
||||
}
|
||||
|
||||
#linkit-modal .linkit-attributes .form-item {
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#linkit-modal label {
|
||||
color: #666;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
#linkit-modal .linkit-attributes .form-item label {
|
||||
float: left;
|
||||
margin-right: 1em;
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
#linkit-modal .linkit-attributes .form-item input,
|
||||
#linkit-modal .linkit-attributes .form-item select {
|
||||
float: left;
|
||||
padding: 5px;
|
||||
width: 260px;
|
||||
}
|
||||
|
||||
.linkit-wrapper > .ajax-progress {
|
||||
padding:20px;
|
||||
}
|
||||
|
||||
#linkit-profile-changer {
|
||||
-webkit-border-radius: 3px;
|
||||
-moz-border-radius: 3px;
|
||||
border-radius: 3px;
|
||||
border-top: 5px solid #eee;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
z-index: 211000;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#linkit-profile-changer > div.form-item {
|
||||
background-color: #EEE;
|
||||
-webkit-border-radius: 3px;
|
||||
-moz-border-radius: 3px;
|
||||
border-radius: 3px;
|
||||
color: #999;
|
||||
display: none;
|
||||
margin: 0;
|
||||
padding: 10px 20px;
|
||||
z-index: 1;
|
||||
border-bottom: 1px solid #C9CCCF;
|
||||
}
|
||||
|
||||
#linkit-profile-changer-toggler {
|
||||
background-color: #EEE;
|
||||
border: 1px solid #C9CCCF;
|
||||
border-top: none;
|
||||
-moz-border-radius-bottomleft: 3px;
|
||||
-webkit-border-bottom-left-radius: 3px;
|
||||
border-bottom-left-radius: 3px;
|
||||
-webkit-border-bottom-right-radius: 3px;
|
||||
-moz-border-radius-bottomright: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
color: #999;
|
||||
cursor:pointer;
|
||||
float: right;
|
||||
font-size: 10px;
|
||||
font-weight: bold;
|
||||
margin: -1px 30px 0 0;
|
||||
text-align: center;
|
||||
width: 100px;
|
||||
|
||||
}
|
||||
|
||||
#linkit-imce {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.linkit-wrapper .ajax-progress .throbber {
|
||||
background-image: url(../throbber.gif);
|
||||
background-position: 98% -16px;
|
||||
background-repeat: no-repeat;
|
||||
height: 16px;
|
||||
margin: 2px;
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
/* Additional status types */
|
||||
.better-autocomplete {
|
||||
background-color: transparent;
|
||||
border-bottom: none;
|
||||
}
|
||||
.better-autocomplete > .result.status-ok {
|
||||
background: #397928;
|
||||
border-top-color: #6ba15d;
|
||||
}
|
||||
|
||||
.better-autocomplete > .result.status-warning {
|
||||
background: #8a3131;
|
||||
border-top-color: #b77878;
|
||||
}
|
||||
|
||||
.better-autocomplete > .result.status-notice {
|
||||
border-top-width: 0;
|
||||
background: #575757;
|
||||
}
|
||||
|
||||
.better-autocomplete > .result span.status {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.better-autocomplete > .result.unpublished-node {
|
||||
background: #fff4f4;
|
||||
}
|
||||
|
||||
.better-autocomplete > .result.unpublished-node.highlight {
|
||||
background: #0075ba;
|
||||
border-top-color: #1f4a64;
|
||||
}
|
||||
|
||||
.better-autocomplete > .result img {
|
||||
float: left;
|
||||
margin: 0 5px 5px 0;
|
||||
}
|
||||
|
||||
.better-autocomplete > .result > p:after {
|
||||
clear: both;
|
||||
content: ".";
|
||||
display: block;
|
||||
font-size: 0;
|
||||
height: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.better-autocomplete li:last-child {
|
||||
border-bottom-left-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
border-bottom: 1px solid #BFBFBF;
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 539 B |
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* @file
|
||||
* Linkit ckeditor dialog helper.
|
||||
*/
|
||||
(function ($) {
|
||||
|
||||
// Abort if Drupal.linkit is not defined.
|
||||
if (typeof Drupal.linkit === 'undefined') {
|
||||
return ;
|
||||
}
|
||||
|
||||
Drupal.linkit.registerDialogHelper('ckeditor', {
|
||||
init : function() {},
|
||||
|
||||
/**
|
||||
* Prepare the dialog after init.
|
||||
*/
|
||||
afterInit : function () {
|
||||
var editor = Drupal.settings.linkit.currentInstance.editor;
|
||||
var element = CKEDITOR.plugins.link.getSelectedLink( editor );
|
||||
|
||||
// If we have selected a link element, lets populate the fields in the
|
||||
// modal with the values from that link element.
|
||||
if (element) {
|
||||
link = {
|
||||
path: element.data('cke-saved-href') || element.getAttribute('href') || '',
|
||||
attributes: {}
|
||||
},
|
||||
// Get all attributes that have fields in the modal.
|
||||
additionalAttributes = Drupal.linkit.additionalAttributes();
|
||||
|
||||
for (var i = 0; i < additionalAttributes.length; i++) {
|
||||
link.attributes[additionalAttributes[i]] = element.getAttribute(additionalAttributes[i]);
|
||||
}
|
||||
|
||||
// Populate the fields.
|
||||
Drupal.linkit.populateFields(link);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Insert the link into the editor.
|
||||
*
|
||||
* @param {Object} link
|
||||
* The link object.
|
||||
*/
|
||||
insertLink : function(link) {
|
||||
var editor = Drupal.settings.linkit.currentInstance.editor;
|
||||
CKEDITOR.tools.callFunction(editor._.linkitFnNum, link, editor);
|
||||
}
|
||||
});
|
||||
|
||||
})(jQuery);
|
||||
@@ -0,0 +1,188 @@
|
||||
/**
|
||||
* @file
|
||||
* Plugin for inserting links with Linkit.
|
||||
*/
|
||||
|
||||
(function ($) {
|
||||
|
||||
CKEDITOR.plugins.add( 'linkit', {
|
||||
|
||||
requires : [ 'link' ],
|
||||
hidpi: true,
|
||||
icons: 'linkit',
|
||||
|
||||
init: function( editor ) {
|
||||
|
||||
// Get the major CKeditor verison.
|
||||
// We do not care about minor versions.
|
||||
var version = parseInt(CKEDITOR.version);
|
||||
|
||||
// Add Button.
|
||||
editor.ui.addButton( 'linkit', {
|
||||
label: Drupal.t('Link to content'),
|
||||
command: 'linkit'
|
||||
});
|
||||
|
||||
// Ckeditor version lower then 4 needs to have a icon path.
|
||||
if (version < 4) {
|
||||
editor.ui._.items.linkit.icon = this.path + 'icons/linkit.png';
|
||||
editor.ui._.items.linkit.args[0].icon = this.path + 'icons/linkit.png';
|
||||
}
|
||||
|
||||
// Add Command.
|
||||
editor.addCommand( 'linkit', {
|
||||
// FOR ACF in ckeditor 4.1+, allow everything.
|
||||
allowedContent: 'a[*]{*}(*)',
|
||||
exec : function () {
|
||||
if (typeof Drupal.settings.linkit === 'undefined') {
|
||||
alert(Drupal.t('Could not find the Linkit profile.'));
|
||||
return ;
|
||||
}
|
||||
|
||||
// Set the editor object.
|
||||
Drupal.settings.linkit.currentInstance.editor = editor;
|
||||
// Set profile.
|
||||
Drupal.settings.linkit.currentInstance.profile = Drupal.settings.linkit.fields[editor.name].profile;
|
||||
// Set the name of the source field.
|
||||
Drupal.settings.linkit.currentInstance.source = editor.name;
|
||||
// Set the source type.
|
||||
Drupal.settings.linkit.currentInstance.helper = 'ckeditor';
|
||||
|
||||
var selection = editor.getSelection(),
|
||||
element = null;
|
||||
|
||||
// If we have selected a link element, we what to grab its attributes
|
||||
// so we can inserten them into the Linkit form in the dialog.
|
||||
if ((element = CKEDITOR.plugins.link.getSelectedLink(editor)) && element.hasAttribute('href')) {
|
||||
selection.selectElement(element);
|
||||
}
|
||||
else {
|
||||
element = null;
|
||||
}
|
||||
|
||||
// Save the selection.
|
||||
Drupal.settings.linkit.currentInstance.selection = selection;
|
||||
|
||||
// Lock the selecton for IE.
|
||||
if (CKEDITOR.env.ie && typeof selection !== 'undefined') {
|
||||
selection.lock();
|
||||
}
|
||||
|
||||
// Save the selected element.
|
||||
Drupal.settings.linkit.currentInstance.selectedElement = element;
|
||||
|
||||
// Create the modal.
|
||||
Drupal.linkit.createModal();
|
||||
}
|
||||
});
|
||||
|
||||
// If the "menu" plugin is loaded, register the menu items.
|
||||
if (editor.addMenuItems) {
|
||||
// Use the default link menu group weight and subtract one.
|
||||
var defaultMenuGroup = editor._.menuGroups.link;
|
||||
editor.addMenuGroup("Linkit", defaultMenuGroup - 1);
|
||||
|
||||
editor.addMenuItems({
|
||||
linkit: {
|
||||
label: Drupal.t('Link to content'),
|
||||
command: 'linkit',
|
||||
group: 'Linkit',
|
||||
order: 0
|
||||
}
|
||||
});
|
||||
|
||||
// Remove the default link option.
|
||||
editor.removeMenuItem('link');
|
||||
}
|
||||
|
||||
// If the "contextmenu" plugin is loaded, register the listeners.
|
||||
if (editor.contextMenu) {
|
||||
editor.contextMenu.addListener(function(element, selection) {
|
||||
if (!element || element.isReadOnly() || (selection.getSelectedText().length < 1 && !element.is('a'))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {linkit: CKEDITOR.TRISTATE_OFF};
|
||||
});
|
||||
}
|
||||
|
||||
// Add a shortcut. Only CKeditor version 4 has this function.
|
||||
if (version >= 4) {
|
||||
editor.setKeystroke( CKEDITOR.CTRL + 76 /*L*/, 'linkit' );
|
||||
}
|
||||
|
||||
// Add event listener.
|
||||
editor.on( 'doubleclick', function( evt ) {
|
||||
// Delete the default link dialog.
|
||||
delete evt.data.dialog;
|
||||
|
||||
var element = CKEDITOR.plugins.link.getSelectedLink( editor ) || evt.data.element;
|
||||
if ( !element.isReadOnly() ) {
|
||||
if ( element.is( 'a' ) ) {
|
||||
editor.getSelection().selectElement( element );
|
||||
if (version >= 4) {
|
||||
editor.commands.linkit.exec();
|
||||
}
|
||||
else if(version == 3) {
|
||||
editor._.commands.linkit.exec();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Register an extra fucntion, this will be used in the modal.
|
||||
editor._.linkitFnNum = CKEDITOR.tools.addFunction( insertLink, editor );
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Create or update a link element in the editor.
|
||||
*/
|
||||
function insertLink(data, editor) {
|
||||
var selection = editor.getSelection();
|
||||
|
||||
data.path = CKEDITOR.tools.trim(data.path);
|
||||
// Browser need the "href" for copy/paste link to work. (CKEDITOR ISSUE #6641)
|
||||
data.attributes['data-cke-saved-href'] = data.path;
|
||||
|
||||
if (!Drupal.settings.linkit.currentInstance.selectedElement) {
|
||||
// We have not selected any link element so lets create a new one.
|
||||
var range = selection.getRanges(1)[0];
|
||||
if (range.collapsed) {
|
||||
var content = (Drupal.settings.linkit.currentInstance.linkContent) ? Drupal.settings.linkit.currentInstance.linkContent : data.path;
|
||||
var text = new CKEDITOR.dom.text(content , editor.document );
|
||||
range.insertNode(text);
|
||||
range.selectNodeContents(text);
|
||||
}
|
||||
|
||||
// Delete all attributes that are empty.
|
||||
data.attributes.href = data.path;
|
||||
for (name in data.attributes) {
|
||||
data.attributes[name] ? null : delete data.attributes[name];
|
||||
}
|
||||
// Apply style.
|
||||
var style = new CKEDITOR.style({element : 'a', attributes : data.attributes});
|
||||
style.type = CKEDITOR.STYLE_INLINE;
|
||||
style.applyToRange(range);
|
||||
range.select();
|
||||
}
|
||||
else {
|
||||
var element = Drupal.settings.linkit.currentInstance.selectedElement;
|
||||
// We are editing an existing link, so just overwrite the attributes.
|
||||
element.setAttribute('href', data.path);
|
||||
element.setAttribute('data-cke-saved-href', data.path);
|
||||
for (name in data.attributes) {
|
||||
data.attributes[name] ?
|
||||
element.setAttribute(name, data.attributes[name]) :
|
||||
element.removeAttribute(name);
|
||||
}
|
||||
selection.selectElement( element );
|
||||
}
|
||||
|
||||
// Unlock the selection.
|
||||
if (CKEDITOR.env.ie && typeof selection !== 'undefined') {
|
||||
selection.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
})(jQuery);
|
||||
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* @file
|
||||
* Plugin for inserting links with Linkit.
|
||||
*/
|
||||
|
||||
(function ($) {
|
||||
|
||||
tinymce.create('tinymce.plugins.linkit', {
|
||||
init : function(editor, url) {
|
||||
|
||||
// Register commands
|
||||
editor.addCommand('mceLinkit', function() {
|
||||
if (typeof Drupal.settings.linkit === 'undefined') {
|
||||
alert(Drupal.t('Could not find the Linkit profile.'));
|
||||
return ;
|
||||
}
|
||||
|
||||
// Set the editor object.
|
||||
Drupal.settings.linkit.currentInstance.editor = editor;
|
||||
// Set profile.
|
||||
Drupal.settings.linkit.currentInstance.profile = Drupal.settings.linkit.fields[editor.id].profile;
|
||||
|
||||
// Set the name of the source field..
|
||||
Drupal.settings.linkit.currentInstance.source = editor.id;
|
||||
|
||||
// Set the source type.
|
||||
Drupal.settings.linkit.currentInstance.helper = 'tinymce';
|
||||
|
||||
// Stores the current editor selection for later restoration. This can
|
||||
// be useful since some browsers looses it's selection if a control
|
||||
// element is selected/focused inside the dialogs.
|
||||
editor.windowManager.bookmark = editor.selection.getBookmark(1);
|
||||
|
||||
// Create the modal.
|
||||
Drupal.linkit.createModal();
|
||||
});
|
||||
|
||||
// Register buttons
|
||||
editor.addButton('linkit', {
|
||||
title : Drupal.t('Link to content'),
|
||||
cmd : 'mceLinkit',
|
||||
image : url + '/images/linkit.png'
|
||||
});
|
||||
|
||||
// We need the real contextmenu in order to make this work.
|
||||
if (editor && editor.plugins.contextmenu) {
|
||||
// Contextmenu gets called - this is what we do.
|
||||
editor.plugins.contextmenu.onContextMenu.add(function(th, m, e, col) {
|
||||
// Only if selected node is an link do this.
|
||||
if (e.nodeName == 'A' || !col) {
|
||||
// Remove all options from standard contextmenu.
|
||||
m.removeAll();
|
||||
th._menu.add({
|
||||
title : Drupal.t('Link to content'),
|
||||
cmd : 'mceLinkit',
|
||||
icon : 'linkit'
|
||||
});
|
||||
//m.addSeparator();
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
getInfo : function() {
|
||||
return {
|
||||
longname : 'Linkit',
|
||||
author : 'Emil Stjerneman',
|
||||
authorurl : 'http://www.stjerneman.com',
|
||||
infourl : 'http://drupal.org/project/linkit',
|
||||
version : tinymce.majorVersion + "." + tinymce.minorVersion
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// Register plugin
|
||||
tinymce.PluginManager.add('linkit', tinymce.plugins.linkit);
|
||||
|
||||
})(jQuery);
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 608 B |
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* @file
|
||||
* Linkit tinymce dialog helper.
|
||||
*/
|
||||
(function ($) {
|
||||
|
||||
// Abort if Drupal.linkit is not defined.
|
||||
if (typeof Drupal.linkit === 'undefined') {
|
||||
return ;
|
||||
}
|
||||
|
||||
Drupal.linkit.registerDialogHelper('tinymce', {
|
||||
init : function() {},
|
||||
|
||||
/**
|
||||
* Prepare the dialog after init.
|
||||
*/
|
||||
afterInit : function () {
|
||||
var editor = Drupal.settings.linkit.currentInstance.editor;
|
||||
var element, link;
|
||||
|
||||
// Restore the selection if the browser is IE.
|
||||
if (tinymce.isIE) {
|
||||
editor.selection.moveToBookmark(editor.windowManager.bookmark);
|
||||
}
|
||||
|
||||
// If we have selected a link element, lets populate the fields in the
|
||||
// dialog with the values from that link element.
|
||||
if (element = editor.dom.getParent(editor.selection.getNode(), 'A')) {
|
||||
link = {
|
||||
path: editor.dom.getAttrib(element, 'href'),
|
||||
attributes: {}
|
||||
};
|
||||
|
||||
// Get all attributes that have fields in the modal.
|
||||
var additionalAttributes = Drupal.linkit.additionalAttributes();
|
||||
|
||||
// Add attributes to the link object, but only those that are enabled in Linkit.
|
||||
tinymce.each(additionalAttributes, function(attribute) {
|
||||
var value = editor.dom.getAttrib(element, attribute);
|
||||
if (value) {
|
||||
link.attributes[attribute] = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Populate the fields.
|
||||
Drupal.linkit.populateFields(link);
|
||||
},
|
||||
|
||||
/**
|
||||
* Insert the link into the editor.
|
||||
*
|
||||
* @param {Object} link
|
||||
* The link object.
|
||||
*/
|
||||
insertLink : function(data) {
|
||||
var editor = Drupal.settings.linkit.currentInstance.editor,
|
||||
element = editor.dom.getParent(editor.selection.getNode(), 'A');
|
||||
|
||||
// Restore the selection if the browser is IE.
|
||||
if (tinymce.isIE) {
|
||||
editor.selection.moveToBookmark(editor.windowManager.bookmark);
|
||||
}
|
||||
|
||||
// Set undo begin point.
|
||||
editor.execCommand("mceBeginUndoLevel");
|
||||
data.attributes.href = data.path;
|
||||
// No link element selected, create a new anchor element.
|
||||
if (element == null) {
|
||||
// If there is no selection, lets inser a new element.
|
||||
if (editor.selection.isCollapsed()) {
|
||||
var content = (Drupal.settings.linkit.currentInstance.linkContent) ? Drupal.settings.linkit.currentInstance.linkContent : data.path;
|
||||
editor.execCommand('mceInsertContent', false,
|
||||
editor.dom.createHTML('a', data.attributes, content));
|
||||
} else {
|
||||
editor.execCommand("mceInsertLink", false, data.attributes);
|
||||
}
|
||||
}
|
||||
// We are editing an existing link, so just overwrite the attributes.
|
||||
else {
|
||||
editor.dom.setAttribs(element, data.attributes);
|
||||
}
|
||||
|
||||
// Don't move caret if selection was image
|
||||
if(element != null) {
|
||||
if (element.childNodes.length != 1 || element.firstChild.nodeName != 'IMG') {
|
||||
editor.focus();
|
||||
editor.selection.select(element);
|
||||
editor.selection.collapse(0);
|
||||
// Restore the selection if the browser is IE.
|
||||
if (tinymce.isIE) {
|
||||
editor.selection.moveToBookmark(editor.windowManager.bookmark);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Set undo end point.
|
||||
editor.execCommand("mceEndUndoLevel");
|
||||
}
|
||||
});
|
||||
|
||||
})(jQuery);
|
||||
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Linkit Profile class.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Linkit Profile class implementation.
|
||||
*/
|
||||
class LinkitProfile {
|
||||
|
||||
/**
|
||||
* The profile data (settings).
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $data;
|
||||
|
||||
/**
|
||||
* All enabled attributes for this profile.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $enabled_attribute_plugins;
|
||||
|
||||
/**
|
||||
* All enabled search plugins for this profile.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $enabled_search_plugins;
|
||||
|
||||
/**
|
||||
* Set all enabled attribure plugins.
|
||||
*/
|
||||
public function setEnabledAttributePlugins() {
|
||||
foreach ($this->data['attribute_plugins'] as $attribute_name => $attribute) {
|
||||
if ($attribute['enabled']) {
|
||||
// Load the attribute plugin.
|
||||
$attribute_plugin = linkit_attribute_plugin_load($attribute_name);
|
||||
|
||||
// Call the callback to get the FAPI element.
|
||||
if (isset($attribute_plugin['callback']) && function_exists($attribute_plugin['callback'])) {
|
||||
$attribute_html = $attribute_plugin['callback']($attribute_plugin, $attribute);
|
||||
// Add Linkit specific class, this is used by the editor JS scripts.
|
||||
$attribute_html['#attributes']['class'][] = 'linkit-attribute';
|
||||
|
||||
$this->enabled_attribute_plugins[$attribute_name] = $attribute_html;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set all enabled search plugins.
|
||||
*/
|
||||
public function setEnabledsearchPlugins() {
|
||||
// Sort plugins by weight.
|
||||
uasort($this->data['search_plugins'], 'linkit_sort_plugins_by_weight');
|
||||
|
||||
foreach ($this->data['search_plugins'] as $plugin_name => $plugin) {
|
||||
if ($plugin['enabled']) {
|
||||
// Load plugin definition.
|
||||
$plugin_definition = linkit_search_plugin_load($plugin_name);
|
||||
|
||||
// Get a Linkit search plugin object.
|
||||
$search_plugin = LinkitSearchPlugin::factory($plugin_definition, $this);
|
||||
|
||||
// Only register none broken plugins.
|
||||
if ($search_plugin->broken() !== TRUE) {
|
||||
$this->enabled_search_plugins[$plugin_name] = $search_plugin;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an array with all the enabled attribute plugins for this profile.
|
||||
*
|
||||
* @return
|
||||
* An array with all enabled attribute plugins for this profile.
|
||||
*/
|
||||
public function getEnabledAttributePlugins() {
|
||||
if (!isset($this->enabled_attribute_plugins)) {
|
||||
$this->setEnabledAttributePlugins();
|
||||
}
|
||||
return $this->enabled_attribute_plugins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an array with all enabled search plugins for this profile.
|
||||
*
|
||||
* @return
|
||||
* An array with all enabled search plugins for this profile.
|
||||
*/
|
||||
public function getEnabledsearchPlugins() {
|
||||
if (!isset($this->enabled_search_plugins)) {
|
||||
$this->setEnabledsearchPlugins();
|
||||
}
|
||||
return $this->enabled_search_plugins;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Linkit Search plugin interface.
|
||||
*
|
||||
* Provides an interface and classes to implement Linkit search plugins.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Defines a common interface for a Linkit search plugin.
|
||||
*/
|
||||
interface LinkitSearchPluginInterface {
|
||||
|
||||
/**
|
||||
* Search plugin factory method.
|
||||
*
|
||||
* @param $plugin
|
||||
* A search plugin object.
|
||||
*
|
||||
* @param LinkitProfile $profile
|
||||
* (optional) A LinkitProfile object.
|
||||
*
|
||||
* @return
|
||||
* An instance of the search plugin class or an instance of the
|
||||
* LinkitSearchPluginBroken class.
|
||||
*/
|
||||
public static function factory($plugin, LinkitProfile $profile);
|
||||
|
||||
/**
|
||||
* Return a string representing this handler's name in the UI.
|
||||
*/
|
||||
public function ui_title();
|
||||
|
||||
/**
|
||||
* Return a string representing this handler's description in the UI.
|
||||
*/
|
||||
public function ui_description();
|
||||
|
||||
/**
|
||||
* Fetch search results based on the $search_string.
|
||||
*
|
||||
* @param $search_string
|
||||
* A string that contains the text to search for.
|
||||
*
|
||||
* @return
|
||||
* An associative array whose values are an
|
||||
* associative array containing:
|
||||
* - title: A string to use as the search result label.
|
||||
* - description: (optional) A string with additional information about the
|
||||
* result item.
|
||||
* - path: The URL to the item.
|
||||
* - group: (optional) A string with the group name for the result item.
|
||||
* Best practice is to use the plugin name as group name.
|
||||
* - addClass: (optional) A string with classes to add to the result row..
|
||||
*/
|
||||
public function fetchResults($search_string);
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for Linkit search plugins.
|
||||
*/
|
||||
abstract class LinkitSearchPlugin implements LinkitSearchPluginInterface {
|
||||
|
||||
/**
|
||||
* The plugin definition for this instance.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $plugin;
|
||||
|
||||
/**
|
||||
* The profile instance for this instance.
|
||||
*
|
||||
* @var LinkitProfile object
|
||||
*/
|
||||
protected $profile;
|
||||
|
||||
/**
|
||||
* Initialize this search plugin with the search plugin and the profile.
|
||||
*
|
||||
* @param $plugin
|
||||
* A search plugin object.
|
||||
*
|
||||
* @param LinkitProfile $profile
|
||||
* A LinkitProfile object.
|
||||
*/
|
||||
public function __construct($plugin, LinkitProfile $profile) {
|
||||
$this->plugin = $plugin;
|
||||
$this->profile = $profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements LinkitSearchPluginInterface::factory().
|
||||
*/
|
||||
public static function factory($plugin, LinkitProfile $profile) {
|
||||
ctools_include('plugins');
|
||||
|
||||
// Make sure that the handler class exists and that it has this class as one
|
||||
// of its parents.
|
||||
if (class_exists($plugin['handler']['class']) && is_subclass_of($plugin['handler']['class'], __CLASS__)) {
|
||||
return new $plugin['handler']['class']($plugin, $profile);
|
||||
}
|
||||
else {
|
||||
// The plugin handler class is defined but it cannot be found, so lets
|
||||
// instantiate the LinkitSearchPluginBroken instead.
|
||||
return new LinkitSearchPluginBroken($plugin, $profile);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements LinkitSearchPluginInterface::ui_title().
|
||||
*/
|
||||
public function ui_title() {
|
||||
if (!isset($this->plugin['ui_title'])) {
|
||||
return check_plain($this->plugin['module'] . ':' . $this->plugin['name']);
|
||||
}
|
||||
return check_plain($this->plugin['ui_title']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements LinkitSearchPluginInterface::ui_description().
|
||||
*/
|
||||
public function ui_description() {
|
||||
if (isset($this->plugin['ui_description'])) {
|
||||
return check_plain($this->plugin['ui_description']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a settings form for this handler.
|
||||
* Uses the standard Drupal FAPI.
|
||||
*
|
||||
* @return
|
||||
* An array containing any custom form elements to be displayed in the
|
||||
* profile editing form
|
||||
*/
|
||||
public function buildSettingsForm() {}
|
||||
|
||||
/**
|
||||
* Determine if the handler is considered 'broken', meaning it's a
|
||||
* a placeholder used when a handler can't be found.
|
||||
*/
|
||||
public function broken() { }
|
||||
}
|
||||
|
||||
/**
|
||||
* A special handler to take the place of missing or broken Linkit search
|
||||
* plugin handlers.
|
||||
*/
|
||||
class LinkitSearchPluginBroken extends LinkitSearchPlugin {
|
||||
|
||||
/**
|
||||
* Overrides LinkitSearchPlugin::ui_title().
|
||||
*/
|
||||
public function ui_title() { return t('Broken/missing handler'); }
|
||||
|
||||
/**
|
||||
* Overrides LinkitSearchPlugin::ui_description().
|
||||
*/
|
||||
public function ui_description() {}
|
||||
|
||||
/**
|
||||
* Implements LinkitSearchPluginInterface::fetchResults().
|
||||
*/
|
||||
public function fetchResults($search_string) {}
|
||||
|
||||
/**
|
||||
* Overrides LinkitSearchPlugin::broken().
|
||||
*/
|
||||
public function broken() { return TRUE; }
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Linkit theme functions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Theme callback for the search/attribute plugins in linkit_profiles_form.
|
||||
*/
|
||||
function theme_linkit_plugin_form_table($variables) {
|
||||
$form = $variables['form'];
|
||||
$has_description = FALSE;
|
||||
$rows = array();
|
||||
|
||||
// Iterate over each element.
|
||||
foreach (element_children($form) as $id) {
|
||||
$form[$id]['weight']['#attributes']['class'] = array('weight');
|
||||
$fields = array(
|
||||
drupal_render($form[$id]['name']),
|
||||
drupal_render($form[$id]['weight']),
|
||||
drupal_render($form[$id]['enabled']),
|
||||
);
|
||||
|
||||
if (isset($form[$id]['description'])) {
|
||||
$has_description = TRUE;
|
||||
$fields[] = drupal_render($form[$id]['description']);
|
||||
}
|
||||
|
||||
$rows[$id]['data'] = $fields;
|
||||
$rows[$id]['class'] = array('draggable');
|
||||
}
|
||||
|
||||
drupal_add_tabledrag('linkit-search-plugins', 'order', 'sibling', 'weight');
|
||||
|
||||
$header = array(
|
||||
t('Name'),
|
||||
t('Weight'),
|
||||
t('Enabled'),
|
||||
);
|
||||
|
||||
if ($has_description) {
|
||||
$header[] = t('Description');
|
||||
}
|
||||
|
||||
return theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('id' => 'linkit-search-plugins'), 'sticky' => FALSE));
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
/**
|
||||
* @file
|
||||
* Linkit dashboard functions
|
||||
*/
|
||||
|
||||
(function ($) {
|
||||
|
||||
Drupal.behaviors.linkitDashboard = {
|
||||
attach: function (context, settings) {
|
||||
// Bind the insert link button.
|
||||
$('.linkit-insert', context).once('linkit-insert', function() {
|
||||
$('.linkit-insert', context).click(function() {
|
||||
// Call the insertLink() function.
|
||||
Drupal.linkit.getDialogHelper(Drupal.settings.linkit.currentInstance.helper).insertLink(Drupal.linkit.getLink());
|
||||
|
||||
// Close the dialog.
|
||||
Drupal.linkit.modalClose();
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
||||
// Bind the close link.
|
||||
$('#linkit-cancel', context).once('linkit-cancel', function() {
|
||||
$('#linkit-cancel', context).bind('click', Drupal.linkit.modalClose);
|
||||
});
|
||||
|
||||
// Run the validation if the path field is populated directly.
|
||||
$('#edit-linkit-path', context).bind('keyup paste input propertychange', function(){
|
||||
Drupal.linkit.requiredFieldsValidation();
|
||||
});
|
||||
|
||||
$(".ui-dialog-titlebar").show();
|
||||
|
||||
// Run required field validation.
|
||||
Drupal.linkit.requiredFieldsValidation();
|
||||
|
||||
if (!Drupal.settings.linkit.currentInstance.suppressProfileChanger) {
|
||||
// Make the profile changer
|
||||
Drupal.linkit.profileChanger(context);
|
||||
}
|
||||
if (Drupal.settings.linkit.IMCEurl && !$('#linkit-imce', context).length) {
|
||||
var $imceButton = $('<input />')
|
||||
.attr({type: 'button', id: 'linkit-imce', name: 'linkit-imce'})
|
||||
.addClass('form-submit')
|
||||
.val(Drupal.t('Open file browser'))
|
||||
.insertAfter($('.form-item-linkit-search', context))
|
||||
.click(function(e) {
|
||||
e.preventDefault();
|
||||
Drupal.linkit.openFileBrowser();
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check for mandatory fields in the form and disable for submissions
|
||||
* if any of the fields are empty.
|
||||
*/
|
||||
Drupal.linkit.requiredFieldsValidation = function() {
|
||||
var allowed = true;
|
||||
$('#linkit-modal .required').each(function() {
|
||||
if (!$(this).val()) {
|
||||
allowed = false;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
if (allowed) {
|
||||
$('#linkit-modal .linkit-insert')
|
||||
.removeAttr('disabled')
|
||||
.removeClass('form-button-disabled');
|
||||
}
|
||||
else {
|
||||
$('#linkit-modal .linkit-insert')
|
||||
.attr('disabled', 'disabled')
|
||||
.addClass('form-button-disabled');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Open the IMCE file browser
|
||||
*/
|
||||
Drupal.linkit.openFileBrowser = function () {
|
||||
window.open(decodeURIComponent(Drupal.settings.linkit.IMCEurl), '', 'width=760,height=560,resizable=1');
|
||||
};
|
||||
|
||||
/**
|
||||
* When a file is inserted through IMCE, this function is called.
|
||||
* See IMCE api for details.
|
||||
*
|
||||
* @param file
|
||||
* The file object that was selected inside IMCE
|
||||
* @param win
|
||||
* The IMCE window object
|
||||
*/
|
||||
Drupal.linkit.IMCECallback = function(file, win) {
|
||||
Drupal.linkit.populateFields({
|
||||
path: win.imce.decode(Drupal.settings.basePath +
|
||||
Drupal.settings.linkit.publicFilesDirectory + '/' + file.relpath)
|
||||
});
|
||||
win.close();
|
||||
};
|
||||
|
||||
/**
|
||||
* Populate fields on the dashboard.
|
||||
*
|
||||
* @param link
|
||||
* An object with the following properties (all are optional):
|
||||
* - path: The anchor's href.
|
||||
* - attributes: An object with additional attributes for the anchor element.
|
||||
*/
|
||||
Drupal.linkit.populateFields = function(link) {
|
||||
link = link || {};
|
||||
link.attributes = link.attributes || {};
|
||||
|
||||
$('#linkit-modal .linkit-path-element').val(link.path);
|
||||
|
||||
$.each(link.attributes, function(name, value) {
|
||||
$('#linkit-modal .linkit-attributes .linkit-attribute-' + name).val(value);
|
||||
});
|
||||
|
||||
// Run required field validation.
|
||||
Drupal.linkit.requiredFieldsValidation();
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve a link object by extracting values from the form.
|
||||
*
|
||||
* @return
|
||||
* The link object.
|
||||
*/
|
||||
Drupal.linkit.getLink = function() {
|
||||
var link = {
|
||||
path: $('#linkit-modal .linkit-path-element ').val(),
|
||||
attributes: {}
|
||||
};
|
||||
$.each(Drupal.linkit.additionalAttributes(), function(f, name) {
|
||||
link.attributes[name] = $('#linkit-modal .linkit-attributes .linkit-attribute-' + name).val();
|
||||
});
|
||||
return link;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve a list of the currently available additional attributes in the
|
||||
* dashboard. The attribute "href" is excluded.
|
||||
*
|
||||
* @return
|
||||
* An array with the names of the attributes.
|
||||
*/
|
||||
Drupal.linkit.additionalAttributes = function() {
|
||||
var attributes = [];
|
||||
$('#linkit-modal .linkit-attributes .linkit-attribute').each(function() {
|
||||
// Remove the 'linkit_' prefix.
|
||||
attributes.push($(this).attr('name').substr(7));
|
||||
});
|
||||
return attributes;
|
||||
};
|
||||
|
||||
Drupal.linkit.profileChanger = function(context) {
|
||||
$('#linkit-profile-changer > div.form-item', context).once('linkit-change-profile', function() {
|
||||
var target = $(this);
|
||||
var toggler = $('<div id="linkit-profile-changer-toggler"></div>')
|
||||
.html(Drupal.t('Change profile'))
|
||||
.click(function() {
|
||||
target.slideToggle();
|
||||
});
|
||||
$(this).after(toggler);
|
||||
});
|
||||
|
||||
$('#linkit-profile-changer .form-radio', context).each(function() {
|
||||
var id = $(this).attr('id');
|
||||
var profile = $(this).val();
|
||||
if (typeof Drupal.ajax[id] != 'undefined') {
|
||||
// @TODO: Jquery 1.5 accept success setting to be an array of functions.
|
||||
// But we have to wait for jquery to get updated in Drupal core.
|
||||
// In the meantime we have to override it.
|
||||
Drupal.ajax[id].options.success = function (response, status) {
|
||||
if (typeof response == 'string') {
|
||||
response = $.parseJSON(response);
|
||||
}
|
||||
|
||||
// Update the autocomplete url.
|
||||
Drupal.settings.linkit.currentInstance.autocompletePathParsed = Drupal.settings.linkit.autocompletePath.replace('___profile___', profile);
|
||||
|
||||
// Call the ajax success method.
|
||||
Drupal.ajax[id].success(response, status);
|
||||
$('#linkit-profile-changer > div.form-item').slideToggle();
|
||||
};
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Drupal.behaviors.linkitSearch = {
|
||||
attach: function(context, settings) {
|
||||
|
||||
$('.linkit-search-element').once('linkit-search', function() {
|
||||
// Create a synonym for this to reduce code confusion.
|
||||
var searchElement = $('.linkit-search-element');
|
||||
var callbacks = {
|
||||
constructURL: function(path, search) {
|
||||
return path + encodeURIComponent(search);
|
||||
},
|
||||
|
||||
insertSuggestionList: function($results, $input) {
|
||||
var top = $input.position().top + $input.outerHeight() - 5;
|
||||
|
||||
$results.width($input.outerWidth()).css({
|
||||
position: 'absolute',
|
||||
// High value because of other overlays like
|
||||
// wysiwyg fullscreen (TinyMCE) mode.
|
||||
zIndex: 211000,
|
||||
maxHeight: $(window).height() - (top + 20)
|
||||
})
|
||||
.hide()
|
||||
.insertAfter($input);
|
||||
},
|
||||
|
||||
select: function(result) {
|
||||
if (typeof result == 'undefined') {
|
||||
return false;
|
||||
}
|
||||
// Only change the link text if it is empty.
|
||||
if (typeof result.disabled != 'undefined' && result.disabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Drupal.linkit.populateFields({
|
||||
path: result.path
|
||||
});
|
||||
|
||||
// Store the result title (Used when no selection is made by the user).
|
||||
Drupal.settings.linkit.currentInstance.linkContent = result.title;
|
||||
|
||||
$('.linkit-path-element', context).focus();
|
||||
}
|
||||
}
|
||||
|
||||
searchElement.betterAutocomplete('init', Drupal.settings.linkit.currentInstance.autocompletePathParsed, Drupal.settings.linkit.currentInstance.autocomplete, callbacks);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
})(jQuery);
|
||||
@@ -0,0 +1,135 @@
|
||||
/**
|
||||
* @file
|
||||
* Linkit field ui functions
|
||||
*/
|
||||
|
||||
(function ($) {
|
||||
|
||||
Drupal.behaviors.linkit_field = {
|
||||
attach : function(context, settings) {
|
||||
// If there is no fields, just stop here.
|
||||
|
||||
if (settings.linkit == undefined || settings.linkit.fields == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$.each(settings.linkit.fields, function(field_name, field) {
|
||||
$('#' + field_name, context).once('linkit_field', function() {
|
||||
$('.linkit-field-' + field_name).click(function() {
|
||||
// Set profile.
|
||||
Drupal.settings.linkit.currentInstance.profile = Drupal.settings.linkit.fields[field_name].profile;
|
||||
|
||||
// Set the name of the source field..
|
||||
Drupal.settings.linkit.currentInstance.source = field_name;
|
||||
|
||||
// Set the source type.
|
||||
Drupal.settings.linkit.currentInstance.helper = 'field';
|
||||
|
||||
// Only care about selection if the element is a textarea.
|
||||
if ($('textarea#' + field_name).length) {
|
||||
var selection = Drupal.linkit.getDialogHelper('field').getSelection($('#' + field_name).get(0));
|
||||
// Save the selection.
|
||||
Drupal.settings.linkit.currentInstance.selection = selection;
|
||||
}
|
||||
|
||||
// Suppress profile changer.
|
||||
Drupal.settings.linkit.currentInstance.suppressProfileChanger = true;
|
||||
|
||||
// Create the modal.
|
||||
Drupal.linkit.createModal();
|
||||
|
||||
return false;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Linkit field dialog helper.
|
||||
*/
|
||||
Drupal.linkit.registerDialogHelper('field', {
|
||||
init : function() {},
|
||||
afterInit : function () {},
|
||||
|
||||
/**
|
||||
* Insert the link into the field.
|
||||
*
|
||||
* @param {Object} link
|
||||
* The link object.
|
||||
*/
|
||||
insertLink : function(data) {
|
||||
var source = $('#' + Drupal.settings.linkit.currentInstance.source),
|
||||
field_settings = Drupal.settings.linkit.fields[Drupal.settings.linkit.currentInstance.source],
|
||||
|
||||
// Call the insert plugin.
|
||||
link = Drupal.linkit.getInsertPlugin(field_settings.insert_plugin).insert(data, field_settings);
|
||||
|
||||
if (typeof Drupal.settings.linkit.currentInstance.selection != 'undefined') {
|
||||
// Replace the selection and insert the link there.
|
||||
this.replaceSelection(source.get(0), Drupal.settings.linkit.currentInstance.selection, link);
|
||||
}
|
||||
else {
|
||||
// Replace the field value.
|
||||
this.replaceFieldValue(source.get(0), link);
|
||||
}
|
||||
|
||||
// Link field can have a title field. If they have, we populate the title
|
||||
// field with the search result title if any.
|
||||
if (typeof field_settings.title_field != 'undefined' && typeof Drupal.settings.linkit.currentInstance.linkContent != 'undefined') {
|
||||
this.replaceFieldValue($('#' + field_settings.title_field).get(0), Drupal.settings.linkit.currentInstance.linkContent);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get field selection.
|
||||
*/
|
||||
getSelection : function(e) {
|
||||
// Mozilla and DOM 3.0.
|
||||
if ('selectionStart' in e) {
|
||||
var l = e.selectionEnd - e.selectionStart;
|
||||
return { start: e.selectionStart, end: e.selectionEnd, length: l, text: e.value.substr(e.selectionStart, l) };
|
||||
}
|
||||
// IE.
|
||||
else if(document.selection) {
|
||||
e.focus();
|
||||
var r = document.selection.createRange(),
|
||||
tr = e.createTextRange(),
|
||||
tr2 = tr.duplicate();
|
||||
tr2.moveToBookmark(r.getBookmark());
|
||||
tr.setEndPoint('EndToStart',tr2);
|
||||
|
||||
if (r == null || tr == null) {
|
||||
return { start: e.value.length, end: e.value.length, length: 0, text: '' };
|
||||
}
|
||||
|
||||
// For some reason IE doesn't always count the \n and \r in the length.
|
||||
var text_part = r.text.replace(/[\r\n]/g,'.'),
|
||||
text_whole = e.value.replace(/[\r\n]/g,'.'),
|
||||
the_start = text_whole.indexOf(text_part, tr.text.length);
|
||||
return { start: the_start, end: the_start + text_part.length, length: text_part.length, text: r.text };
|
||||
}
|
||||
// Browser not supported.
|
||||
else {
|
||||
return { start: e.value.length, end: e.value.length, length: 0, text: '' };
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Replace the field selection.
|
||||
*/
|
||||
replaceSelection : function (e, selection, text) {
|
||||
var start_pos = selection.start;
|
||||
var end_pos = start_pos + text.length;
|
||||
e.value = e.value.substr(0, start_pos) + text + e.value.substr(selection.end, e.value.length);
|
||||
},
|
||||
|
||||
/**
|
||||
* Replace the field value.
|
||||
*/
|
||||
replaceFieldValue : function (e, text) {
|
||||
e.value = text;
|
||||
}
|
||||
});
|
||||
|
||||
})(jQuery);
|
||||
@@ -0,0 +1,162 @@
|
||||
/**
|
||||
* @file
|
||||
* Linkit dialog functions.
|
||||
*/
|
||||
|
||||
(function ($) {
|
||||
|
||||
// Create the Linkit namespaces.
|
||||
Drupal.linkit = Drupal.linkit || {};
|
||||
Drupal.linkit.currentInstance = Drupal.linkit.currentInstance || {};
|
||||
Drupal.linkit.dialogHelper = Drupal.linkit.dialogHelper || {};
|
||||
Drupal.linkit.insertPlugins = Drupal.linkit.insertPlugins || {};
|
||||
|
||||
/**
|
||||
* Create the modal dialog.
|
||||
*/
|
||||
Drupal.linkit.createModal = function() {
|
||||
// Create the modal dialog element.
|
||||
Drupal.linkit.createModalElement()
|
||||
// Create jQuery UI Dialog.
|
||||
.dialog(Drupal.linkit.modalOptions())
|
||||
// Remove the title bar from the modal.
|
||||
.siblings(".ui-dialog-titlebar").hide();
|
||||
|
||||
// Make the modal seem "fixed".
|
||||
$(window).bind("scroll resize", function() {
|
||||
$('#linkit-modal').dialog('option', 'position', ['center', 50]);
|
||||
});
|
||||
|
||||
// Get modal content.
|
||||
Drupal.linkit.getDashboard();
|
||||
};
|
||||
|
||||
/**
|
||||
* Create and append the modal element.
|
||||
*/
|
||||
Drupal.linkit.createModalElement = function() {
|
||||
// Create a new div and give it an ID of linkit-modal.
|
||||
// This is the dashboard container.
|
||||
var linkitModal = $('<div id="linkit-modal"></div>');
|
||||
|
||||
// Create a modal div in the <body>.
|
||||
$('body').append(linkitModal);
|
||||
|
||||
return linkitModal;
|
||||
};
|
||||
|
||||
/**
|
||||
* Default jQuery dialog options used when creating the Linkit modal.
|
||||
*/
|
||||
Drupal.linkit.modalOptions = function() {
|
||||
return {
|
||||
dialogClass: 'linkit-wrapper',
|
||||
modal: true,
|
||||
draggable: false,
|
||||
resizable: false,
|
||||
width: 520,
|
||||
position: ['center', 50],
|
||||
minHeight: 0,
|
||||
zIndex: 210000,
|
||||
close: Drupal.linkit.modalClose,
|
||||
open: function (event, ui) {
|
||||
// Change the overlay style.
|
||||
$('.ui-widget-overlay').css({
|
||||
opacity: 0.5,
|
||||
filter: 'Alpha(Opacity=50)',
|
||||
backgroundColor: '#FFFFFF'
|
||||
});
|
||||
},
|
||||
title: 'Linkit'
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Close the Linkit modal.
|
||||
*/
|
||||
Drupal.linkit.modalClose = function (e) {
|
||||
$('#linkit-modal').dialog('destroy').remove();
|
||||
// Make sure the current intstance settings are removed when the modal is
|
||||
// closed.
|
||||
Drupal.settings.linkit.currentInstance = {};
|
||||
|
||||
// The event object does not have a preventDefault member in
|
||||
// Internet Explorer prior to version 9.
|
||||
if (e && e.preventDefault) {
|
||||
e.preventDefault();
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
Drupal.linkit.getDashboard = function () {
|
||||
// Create the AJAX object.
|
||||
var ajax_settings = {};
|
||||
ajax_settings.event = 'LinkitDashboard';
|
||||
ajax_settings.url = Drupal.settings.linkit.dashboardPath + Drupal.settings.linkit.currentInstance.profile;
|
||||
ajax_settings.progress = {
|
||||
type: 'throbber',
|
||||
message : Drupal.t('Loading Linkit dashboard...')
|
||||
};
|
||||
|
||||
Drupal.ajax['linkit-modal'] = new Drupal.ajax('linkit-modal', $('#linkit-modal')[0], ajax_settings);
|
||||
|
||||
// @TODO: Jquery 1.5 accept success setting to be an array of functions.
|
||||
// But we have to wait for jquery to get updated in Drupal core.
|
||||
// In the meantime we have to override it.
|
||||
Drupal.ajax['linkit-modal'].options.success = function (response, status) {
|
||||
if (typeof response == 'string') {
|
||||
response = $.parseJSON(response);
|
||||
}
|
||||
|
||||
// Call the ajax success method.
|
||||
Drupal.ajax['linkit-modal'].success(response, status);
|
||||
// Run the afterInit function.
|
||||
var helper = Drupal.settings.linkit.currentInstance.helper;
|
||||
Drupal.linkit.getDialogHelper(helper).afterInit();
|
||||
|
||||
// Set focus in the search field.
|
||||
$('#linkit-modal .linkit-search-element').focus();
|
||||
};
|
||||
|
||||
// Update the autocomplete url.
|
||||
Drupal.settings.linkit.currentInstance.autocompletePathParsed =
|
||||
Drupal.settings.linkit.autocompletePath.replace('___profile___', Drupal.settings.linkit.currentInstance.profile);
|
||||
|
||||
// Trigger the ajax event.
|
||||
$('#linkit-modal').trigger('LinkitDashboard');
|
||||
};
|
||||
|
||||
/**
|
||||
* Register new dialog helper.
|
||||
*/
|
||||
Drupal.linkit.registerDialogHelper = function(name, helper) {
|
||||
Drupal.linkit.dialogHelper[name] = helper;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a dialog helper.
|
||||
*/
|
||||
Drupal.linkit.getDialogHelper = function(name) {
|
||||
return Drupal.linkit.dialogHelper[name];
|
||||
};
|
||||
|
||||
/**
|
||||
* Register new insert plugins.
|
||||
*/
|
||||
Drupal.linkit.registerInsertPlugin = function(name, plugin) {
|
||||
Drupal.linkit.insertPlugins[name] = plugin;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get an insert plugin.
|
||||
*/
|
||||
Drupal.linkit.getInsertPlugin = function(name) {
|
||||
return Drupal.linkit.insertPlugins[name];
|
||||
};
|
||||
|
||||
})(jQuery);
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Implements hook_linkit_plugin_entities_alter().
|
||||
*
|
||||
* The default behavior for entities is that they will use the default entity
|
||||
* search plugin class, which will provide them with the basic methods they
|
||||
* need.
|
||||
*
|
||||
* Tho there can be some search plugins that will extend those basic method with
|
||||
* more advanced once, therefore the handlers for those plugins will have to be
|
||||
* changed.
|
||||
*
|
||||
* Make sure that your classes is included in the regisrty.
|
||||
* The easiest way to do this is by define them as
|
||||
*
|
||||
* <code> files[] = plugins/linkit_search/my_custom_plugin.class.php </code>
|
||||
*
|
||||
* @param $plugins
|
||||
* An array of all search plugins processed within Linkit entity plugin.
|
||||
*/
|
||||
function hook_linkit_search_plugin_entities_alter(&$plugins) {
|
||||
$path = drupal_get_path('module', 'mymodule') . '/plugins/linkit_search';
|
||||
if (isset($plugins['my_custom_plugin'])) {
|
||||
$handler = array(
|
||||
'class' => 'MyCustomPlugin',
|
||||
'file' => 'my_custom_plugin.class.php',
|
||||
'path' => $path,
|
||||
);
|
||||
$plugins['my_custom_plugin']['handler'] = $handler;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Implementation for Fields and Linkit.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_linkit_allowed_field_types().
|
||||
*/
|
||||
function linkit_linkit_allowed_field_types() {
|
||||
$allowed_field_types = array(
|
||||
'text',
|
||||
'text_long',
|
||||
'text_with_summary',
|
||||
'link_field',
|
||||
);
|
||||
return $allowed_field_types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all allowed filed types.
|
||||
* @return
|
||||
* An array of allowed filed types.
|
||||
*/
|
||||
function linkit_get_allowed_field_types() {
|
||||
// Get allowed field and widget types.
|
||||
$allowed_field_types = module_invoke_all('linkit_allowed_field_types');
|
||||
drupal_alter('linkit_allowed_field_types', $allowed_field_types);
|
||||
return $allowed_field_types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_linkit_allowed_elements().
|
||||
*/
|
||||
function linkit_linkit_allowed_field_elements() {
|
||||
$allowed_elements = array(
|
||||
'textfield',
|
||||
'textarea',
|
||||
'textarea_with_summary',
|
||||
'link_field',
|
||||
);
|
||||
return $allowed_elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all allowed filed elements.
|
||||
* @return
|
||||
* An array of allowed filed elements.
|
||||
*/
|
||||
function linkit_get_allowed_field_elements() {
|
||||
// Get allowed field and widget types.
|
||||
$allowed_field_elements = module_invoke_all('linkit_allowed_field_elements');
|
||||
drupal_alter('linkit_allowed_field_elements', $allowed_field_elements);
|
||||
return $allowed_field_elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_linkit_allowed_field_widget_types().
|
||||
*/
|
||||
function linkit_linkit_allowed_field_widget_types() {
|
||||
$allowed_field_widget_types = array(
|
||||
'text_textfield',
|
||||
'text_textarea',
|
||||
'text_textarea_with_summary',
|
||||
'link_field',
|
||||
);
|
||||
return $allowed_field_widget_types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all allowed filed widget types.
|
||||
* @return
|
||||
* An array of allowed filed widget types.
|
||||
*/
|
||||
function linkit_get_allowed_field_widget_types() {
|
||||
// Get allowed field and widget types.
|
||||
$allowed_field_widget_types = module_invoke_all('linkit_allowed_field_widget_types');
|
||||
drupal_alter('linkit_allowed_field_widget_types', $allowed_field_widget_types);
|
||||
return $allowed_field_widget_types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_form_FIELD_UI_FIELD_EDIT_FORM_alter().
|
||||
*/
|
||||
function linkit_form_field_ui_field_edit_form_alter(&$form, &$form_state) {
|
||||
$instance = $form['#instance'];
|
||||
|
||||
if (isset($form['locked']) && $form['locked']) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get allowed field types.
|
||||
$allowed_field_types = linkit_get_allowed_field_types();
|
||||
// Get allowed field widget types.
|
||||
$allowed_field_widget_types = linkit_get_allowed_field_widget_types();
|
||||
|
||||
$allowed_field = in_array($form['#field']['type'], $allowed_field_types);
|
||||
$allowed_widget = in_array($form['instance']['widget']['type']['#value'], $allowed_field_widget_types);
|
||||
|
||||
// Add the linkit settings to the field instance form if it's valid.
|
||||
if ($allowed_field && $allowed_widget) {
|
||||
|
||||
// Fieldset for Linkit settings on this field instance.
|
||||
$form['instance']['settings']['linkit'] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => t('Linkit field settings'),
|
||||
'#collapsible' => FALSE,
|
||||
'#collapsed' => FALSE,
|
||||
);
|
||||
|
||||
// Enable Linkit on this field instance.
|
||||
$form['instance']['settings']['linkit']['enable'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Enable Linkit for this field.'),
|
||||
'#default_value' => isset($instance['settings']['linkit']['enable'])
|
||||
? $instance['settings']['linkit']['enable'] : 0,
|
||||
'#description' => t('Do not use this for CKeditor and TinyMCE fields. You will have to configure this on the wysiwyg/ckeditor profile.'),
|
||||
);
|
||||
|
||||
$profiles = linkit_profile_field_load_all();
|
||||
$options = array();
|
||||
foreach ($profiles as $profile) {
|
||||
$options[$profile->name] = $profile->admin_title;
|
||||
}
|
||||
|
||||
// Sort the options.
|
||||
natsort($options);
|
||||
|
||||
$form['instance']['settings']['linkit']['profile'] = array(
|
||||
'#type' => 'select',
|
||||
'#title' => t('Profile'),
|
||||
'#options' => $options,
|
||||
'#empty_option' => t('- Select a profile -'),
|
||||
'#default_value' => isset($instance['settings']['linkit']['profile'])
|
||||
? $instance['settings']['linkit']['profile'] : '',
|
||||
'#states' => array(
|
||||
'invisible' => array(
|
||||
'input[name="instance[settings][linkit][enable]"]' => array('checked' => FALSE),
|
||||
),
|
||||
'required' => array(
|
||||
'input[name="instance[settings][linkit][enable]"]' => array('checked' => TRUE),
|
||||
),
|
||||
),
|
||||
'#element_validate' => array('linkit_field_profile_validate'),
|
||||
);
|
||||
|
||||
// Enable Linkit on this field instance.
|
||||
$form['instance']['settings']['linkit']['button_text'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Button text that activates linkit modal.'),
|
||||
'#default_value' => isset($instance['settings']['linkit']['button_text'])
|
||||
? $instance['settings']['linkit']['button_text'] : t('Search'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validation callback; Only validate the profile field if linkit is enabled on
|
||||
* the instance.
|
||||
*
|
||||
* @see linkit_form_field_ui_field_edit_form_alter()
|
||||
*/
|
||||
function linkit_field_profile_validate($element, &$form_state, $form) {
|
||||
if (isset($form_state['values']['instance']['settings']['linkit']['enable'])
|
||||
&& $form_state['values']['instance']['settings']['linkit']['enable']) {
|
||||
if (empty($element['#value'])) {
|
||||
form_error($element, t('You must select an profile.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process callback.
|
||||
*/
|
||||
function linkit_process_field_element($element, &$form_state, &$complete_form) {
|
||||
// Only proceed if the field is attached to an entity.
|
||||
if (!isset($element['#entity_type'])) {
|
||||
return $element;
|
||||
}
|
||||
|
||||
$field = field_info_field($element['#field_name']);
|
||||
$instance = field_info_instance($element['#entity_type'], $element['#field_name'], $element['#bundle']);
|
||||
|
||||
if (isset($instance['settings']['linkit']['enable']) && $instance['settings']['linkit']['enable']) {
|
||||
|
||||
// Load the profile.
|
||||
$profile = linkit_profile_load($instance['settings']['linkit']['profile']);
|
||||
if (!$profile) {
|
||||
return $element;
|
||||
}
|
||||
// Load the insert plugin for the profile.
|
||||
$insert_plugin = linkit_insert_plugin_load($profile->data['insert_plugin']['plugin']);
|
||||
if ($insert_plugin === NULL) {
|
||||
return $element;
|
||||
}
|
||||
|
||||
// Set the field ID.
|
||||
$field_id = $element['#id'];
|
||||
|
||||
// Special treatment for link fields.
|
||||
if ($element['#type'] == 'link_field') {
|
||||
$field_id = $element['#id'] . '-url';
|
||||
}
|
||||
|
||||
$field_js = array(
|
||||
'data' => array(
|
||||
'linkit' => array(
|
||||
'fields' => array(
|
||||
$field_id => array(
|
||||
'profile' => $instance['settings']['linkit']['profile'],
|
||||
'insert_plugin' => $profile->data['insert_plugin']['plugin'],
|
||||
'url_method' => $profile->data['insert_plugin']['url_method'],
|
||||
// @TODO: Add autocomplete settings.
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
'type' => 'setting',
|
||||
);
|
||||
|
||||
|
||||
// Link fields can have a title field.
|
||||
if ($element['#type'] == 'link_field') {
|
||||
if (isset($instance['settings']['title']) && in_array($instance['settings']['title'], array('optional', 'required'))) {
|
||||
$field_js['data']['linkit']['fields'][$field_id]['title_field'] = $element['#id'] . '-title';
|
||||
}
|
||||
}
|
||||
|
||||
// Attach js files and settings Linkit needs.
|
||||
$element['#attached']['library'][] = array('linkit', 'base');
|
||||
$element['#attached']['library'][] = array('linkit', 'field');
|
||||
$element['#attached']['js'][] = $insert_plugin['javascript'];
|
||||
$element['#attached']['js'][] = $field_js;
|
||||
|
||||
$button_text = !empty($instance['settings']['linkit']['button_text']) ? $instance['settings']['linkit']['button_text'] : t('Search');
|
||||
// Add Linkit dialog button to the element suffix.
|
||||
$element['#field_suffix'] = '<a class="button linkit-field-button linkit-field-' . $field_id . '" href="#">' . $button_text . '</a>';
|
||||
}
|
||||
|
||||
return $element;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
name = Linkit
|
||||
description = Provides an easy interface for inserting links.
|
||||
core = 7.x
|
||||
configure = admin/config/content/linkit
|
||||
dependencies[] = ctools
|
||||
dependencies[] = entity
|
||||
|
||||
; Our classes, interfaces and abstract implementations.
|
||||
files[] = includes/profile.class.php
|
||||
files[] = includes/search_plugin.class.php
|
||||
files[] = plugins/linkit_search/entity.class.php
|
||||
files[] = plugins/linkit_search/file.class.php
|
||||
files[] = plugins/linkit_search/node.class.php
|
||||
files[] = plugins/linkit_search/taxonomy_term.class.php
|
||||
files[] = plugins/linkit_search/user.class.php
|
||||
|
||||
; Test files
|
||||
files[] = test/linkit.test
|
||||
files[] = test/linkit_profile.test
|
||||
files[] = test/linkit_search_plugin.test
|
||||
files[] = test/linkit_search_plugin_node.test
|
||||
files[] = test/linkit_search_plugin_user.test
|
||||
files[] = test/linkit_search_plugin_taxonomy_term.test
|
||||
files[] = test/linkit_field.test
|
||||
|
||||
; Information added by Drupal.org packaging script on 2015-01-06
|
||||
version = "7.x-3.3"
|
||||
core = "7.x"
|
||||
project = "linkit"
|
||||
datestamp = "1420580858"
|
||||
|
||||
@@ -0,0 +1,223 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Install, update and uninstall functions for the Linkit module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_schema().
|
||||
*/
|
||||
function linkit_schema() {
|
||||
$schema = array();
|
||||
$schema['linkit_profiles'] = array(
|
||||
'description' => 'Base table holding Linkit profiles.',
|
||||
'export' => array(
|
||||
'key' => 'name',
|
||||
'key name' => 'Name',
|
||||
'primary key' => 'pid',
|
||||
'object' => 'LinkitProfile',
|
||||
'identifier' => 'linkit_profile',
|
||||
'status' => 'linkit_profiles_status',
|
||||
'load callback' => 'linkit_profile_load',
|
||||
'load all callback' => 'linkit_profile_load_all',
|
||||
'bulk export' => TRUE,
|
||||
'api' => array(
|
||||
'owner' => 'linkit',
|
||||
'api' => 'linkit_profiles',
|
||||
'minimum_version' => 1,
|
||||
'current_version' => 1,
|
||||
),
|
||||
),
|
||||
'fields' => array(
|
||||
'pid' => array(
|
||||
'type' => 'serial',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
'no export' => TRUE,
|
||||
'description' => 'Serial id for this profile.',
|
||||
),
|
||||
'name' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 128,
|
||||
'not null' => TRUE,
|
||||
'description' => 'Machine-readable name for this profile.',
|
||||
),
|
||||
'admin_title' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 128,
|
||||
'not null' => TRUE,
|
||||
'description' => 'Administrative title for this profile.',
|
||||
),
|
||||
'admin_description' => array(
|
||||
'type' => 'text',
|
||||
'not null' => TRUE,
|
||||
'size' => 'medium',
|
||||
'description' => 'Administrative description for this profile.',
|
||||
),
|
||||
'profile_type' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 128,
|
||||
'not null' => TRUE,
|
||||
'description' => 'The profile type.',
|
||||
),
|
||||
'data' => array(
|
||||
'type' => 'blob',
|
||||
'size' => 'big',
|
||||
'not null' => TRUE,
|
||||
'serialize' => TRUE,
|
||||
'description' => 'Serialized data containing the profile settings.',
|
||||
),
|
||||
),
|
||||
'primary key' => array('pid'),
|
||||
'unique keys' => array(
|
||||
'name' => array('name'),
|
||||
),
|
||||
'indexes' => array(
|
||||
'pid' => array('pid'),
|
||||
'profile_type' => array('profile_type'),
|
||||
),
|
||||
);
|
||||
return $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate settings from v2 to v3 if needed.
|
||||
*/
|
||||
function linkit_update_7300() {
|
||||
if (!db_field_exists('linkit_profiles', 'role_rids')) {
|
||||
// Already 3.x, no need for migration from 2.x.
|
||||
return;
|
||||
}
|
||||
|
||||
// Get old profiles.
|
||||
$old_profiles = db_query("SELECT * FROM {linkit_profiles} ORDER BY weight DESC");
|
||||
//Drop redundant fields
|
||||
db_drop_field('linkit_profiles', 'role_rids');
|
||||
db_drop_field('linkit_profiles', 'weight');
|
||||
db_add_field('linkit_profiles', 'profile_type',
|
||||
array(
|
||||
'type' => 'varchar',
|
||||
'length' => 128,
|
||||
'not null' => TRUE,
|
||||
'description' => 'The profile type.',
|
||||
'default' => ''
|
||||
)
|
||||
);
|
||||
db_add_field('linkit_profiles', 'admin_description',
|
||||
array(
|
||||
'type' => 'text',
|
||||
'size' => 'medium',
|
||||
'description' => 'Administrative description for this profile.',
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
foreach ($old_profiles as $profile) {
|
||||
$data = unserialize($profile->data);
|
||||
|
||||
// Rename the plugins
|
||||
$data['search_plugins'] = $data['plugins'];
|
||||
unset($data['plugins']);
|
||||
$data['attribute_plugins'] = $data['attributes'];
|
||||
unset($data['plugins']);
|
||||
$data['attribute_plugins']['target'] = array(
|
||||
'enabled' => 0,
|
||||
'weight' => -10,
|
||||
);
|
||||
|
||||
// Add new plugins
|
||||
$data['insert_plugin'] = array(
|
||||
'plugin' => 'raw_url',
|
||||
'url_method' => 1,
|
||||
);
|
||||
|
||||
// Remove reverse_menu_trail
|
||||
foreach ($data as $key => $item) {
|
||||
if(strstr($key, 'entity:')) {
|
||||
unset($data[$key]['reverse_menu_trail']);
|
||||
}
|
||||
}
|
||||
|
||||
$profile->data = serialize($data);
|
||||
|
||||
// All old profiles are migrated as field profiles
|
||||
// Do the update.
|
||||
db_update('linkit_profiles')
|
||||
->fields(array(
|
||||
'data' => $profile->data,
|
||||
'profile_type' => "2"
|
||||
))
|
||||
->condition('pid', $profile->pid)
|
||||
->execute();
|
||||
|
||||
// Store the weightest profile
|
||||
$weightest_profile = clone $profile;
|
||||
|
||||
// Insert an editor profile for every field profile
|
||||
// Copy the prfoile for latter usage
|
||||
$profile_editor = clone $profile;
|
||||
$profile_editor->pid = NULL;
|
||||
$data = unserialize($profile_editor->data);
|
||||
$data['text_formats'] = array(
|
||||
'full_html' => 'full_html',
|
||||
'filtered_html' => 0,
|
||||
'plain_text' => 0
|
||||
);
|
||||
$profile_editor->data = serialize($data);
|
||||
$profile_editor->name = $profile_editor->name . '_editor';
|
||||
$profile_editor->admin_title = $profile_editor->admin_title . ' [editor]';
|
||||
$profile_editor->profile_type = 1;
|
||||
$profile_editor->admin_description = '';
|
||||
unset($profile_editor->role_rids, $profile_editor->weight);
|
||||
|
||||
db_insert('linkit_profiles')
|
||||
->fields((array)$profile_editor)
|
||||
->execute();
|
||||
|
||||
}
|
||||
|
||||
// Update the field instances with the weightest profile
|
||||
$instances_info = field_info_instances();
|
||||
foreach ($instances_info as $entity_type_name => $entity_type) {
|
||||
foreach ($entity_type as $bundle_name => $bundle) {
|
||||
foreach ($bundle as $field_name => $field) {
|
||||
if(isset($field['settings'], $field['settings']['linkit'])) {
|
||||
$settings = &$field['settings']['linkit'];
|
||||
$settings['button_text'] = 'Search';
|
||||
unset($settings['insert_plugin']);
|
||||
if($settings['enable']) {
|
||||
$settings['profile'] = $weightest_profile->name;
|
||||
}
|
||||
else {
|
||||
$settings['profile'] = '';
|
||||
}
|
||||
field_update_instance($field);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Rebuild code registry so the LinkitProfile class is found.
|
||||
registry_rebuild();
|
||||
}
|
||||
|
||||
/**
|
||||
* Do nothing. Update the schema version.
|
||||
*/
|
||||
function linkit_update_7301() {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
/**
|
||||
* Set URL type to "Entity view page" to preserve current behavior.
|
||||
*/
|
||||
function linkit_update_7302() {
|
||||
require_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'linkit') . '/plugins/linkit_search/file.class.php';
|
||||
$profiles = linkit_profile_load_all();
|
||||
foreach ($profiles as $profile) {
|
||||
if (isset($profile->data['entity:file'])) {
|
||||
$profile->data['entity:file'] = LINKIT_FILE_URL_TYPE_ENTITY;
|
||||
ctools_export_crud_save('linkit_profiles', $profile);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Contains the Linkit profile export UI integration code.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Linktit profile export UI class handler for Views UI.
|
||||
*/
|
||||
class linkit_profiles extends ctools_export_ui {
|
||||
/**
|
||||
* @TODO: Can we call parent:list_build_row and then just add linkit specific
|
||||
* columns?
|
||||
*/
|
||||
function list_build_row($item, &$form_state, $operations) {
|
||||
// Set up sorting
|
||||
$name = $item->{$this->plugin['export']['key']};
|
||||
$schema = ctools_export_get_schema($this->plugin['schema']);
|
||||
|
||||
// Note: $item->{$schema['export']['export type string']} should have already been set up by export.inc so
|
||||
// we can use it safely.
|
||||
switch ($form_state['values']['order']) {
|
||||
case 'disabled':
|
||||
$this->sorts[$name] = empty($item->disabled) . $name;
|
||||
break;
|
||||
case 'title':
|
||||
$this->sorts[$name] = $item->{$this->plugin['export']['admin_title']};
|
||||
break;
|
||||
case 'name':
|
||||
$this->sorts[$name] = $name;
|
||||
break;
|
||||
case 'storage':
|
||||
$this->sorts[$name] = $item->{$schema['export']['export type string']} . $name;
|
||||
break;
|
||||
}
|
||||
|
||||
$this->rows[$name]['data'] = array();
|
||||
$this->rows[$name]['class'] = !empty($item->disabled) ? array('ctools-export-ui-disabled') : array('ctools-export-ui-enabled');
|
||||
|
||||
// If we have an admin title, make it the first row.
|
||||
if (!empty($this->plugin['export']['admin_title'])) {
|
||||
$this->rows[$name]['data'][] = array('data' => check_plain($item->{$this->plugin['export']['admin_title']}), 'class' => array('ctools-export-ui-title'));
|
||||
}
|
||||
|
||||
// Profile type.
|
||||
$this->rows[$name]['data'][] = array('data' => linkit_get_profile_type($item->profile_type), 'class' => array('linkit-export-ui-profile-type'));
|
||||
|
||||
// Storage.
|
||||
$this->rows[$name]['data'][] = array('data' => check_plain($item->{$schema['export']['export type string']}), 'class' => array('ctools-export-ui-storage'));
|
||||
|
||||
// Operations.
|
||||
$ops = theme('links__ctools_dropbutton', array('links' => $operations, 'attributes' => array('class' => array('links', 'inline'))));
|
||||
$this->rows[$name]['data'][] = array('data' => $ops, 'class' => array('ctools-export-ui-operations'));
|
||||
|
||||
// Add an automatic mouseover of the description if one exists.
|
||||
$this->rows[$name]['title'] = $item->{$this->plugin['export']['admin_description']};
|
||||
}
|
||||
|
||||
function list_table_header() {
|
||||
$header = array();
|
||||
$header[] = array('data' => t('Title'), 'class' => array('ctools-export-ui-title'));
|
||||
$header[] = array('data' => t('Profile type'), 'class' => array('ctools-export-ui-profile-type'));
|
||||
$header[] = array('data' => t('Storage'), 'class' => array('ctools-export-ui-storage'));
|
||||
$header[] = array('data' => t('Operations'), 'class' => array('ctools-export-ui-operations'));
|
||||
return $header;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,501 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Ctools export ui plugin to handle Linkit profiles.
|
||||
*/
|
||||
|
||||
$plugin = array(
|
||||
'schema' => 'linkit_profiles',
|
||||
'access' => 'administer linkit',
|
||||
|
||||
'menu' => array(
|
||||
'menu prefix' => 'admin/config/content',
|
||||
'menu item' => 'linkit',
|
||||
'menu title' => 'Linkit Profiles',
|
||||
'menu description' => t('Manage Linkit profiles.'),
|
||||
),
|
||||
|
||||
'title singular' => t('Linkit profile'),
|
||||
'title plural' => t('Linkit profiles'),
|
||||
'title singular proper' => t('Linkit profile'),
|
||||
'title plural proper' => t('Linkit profiles'),
|
||||
|
||||
'handler' => 'linkit_profiles',
|
||||
);
|
||||
|
||||
/**
|
||||
* Linkit profile settings form.
|
||||
*
|
||||
* @param $form
|
||||
* A nested array of form elements that comprise the form.
|
||||
* @param $form_state
|
||||
* An associative array containing the current state of the form.
|
||||
*/
|
||||
function linkit_profiles_form(&$form, &$form_state) {
|
||||
$profile = $form_state['item'];
|
||||
|
||||
// Set data as a tree.
|
||||
$form['data'] = array(
|
||||
'#tree' => TRUE,
|
||||
'#type' => 'vertical_tabs',
|
||||
);
|
||||
|
||||
// Append profile type settings to the profile form.
|
||||
_linkit_build_profile_type_form_fields($form, $profile);
|
||||
|
||||
// Append search plugin form element to the profile form.
|
||||
_linkit_build_search_plugin_form_fields($form, $profile);
|
||||
|
||||
// Append insert plugin form element to the profile form.
|
||||
_linkit_build_insert_plugin_form_fields($form, $profile);
|
||||
|
||||
// Append attribute plugin form element to the profile form.
|
||||
_linkit_build_attribute_plugin_form_fields($form, $profile);
|
||||
|
||||
// Append IMCE plugin form element to the profile form.
|
||||
if (module_exists('imce')) {
|
||||
_linkit_build_imce_form_fields($form, $profile);
|
||||
}
|
||||
|
||||
// Append autocomplete (BAC) form element to the profile form.
|
||||
_linkit_build_autocomplete_form_fields($form, $profile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Linkit profile validate callback.
|
||||
*
|
||||
* @see linkit_profiles_form()
|
||||
*/
|
||||
function linkit_profiles_form_validate(&$form, &$form_state) {
|
||||
// If the profile type is field, then insert plugin is required.
|
||||
if ($form_state['values']['profile_type'] == LINKIT_PROFILE_TYPE_FIELD
|
||||
&& empty($form_state['values']['data']['insert_plugin']['plugin'])) {
|
||||
form_set_error('data][insert_plugin][plugin', t('You have to select a insert plugin for profiles used with fields.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Linkit profile submit callback.
|
||||
*
|
||||
* @see linkit_profiles_form()
|
||||
*/
|
||||
function linkit_profiles_form_submit(&$form, &$form_state) {
|
||||
// Unset some variables used by "field profiles" to make it clean.
|
||||
if ($form_state['values']['profile_type'] == LINKIT_PROFILE_TYPE_EDITOR) {
|
||||
unset($form_state['values']['data']['insert_plugin']['plugin']);
|
||||
}
|
||||
|
||||
// Unset some variables used by "editor profiles" to make it clean.
|
||||
if ($form_state['values']['profile_type'] == LINKIT_PROFILE_TYPE_FIELD) {
|
||||
unset($form_state['values']['data']['text_formats']);
|
||||
}
|
||||
|
||||
// Unset active tab state, we dont need to save this.
|
||||
unset($form_state['values']['data']['data__active_tab']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append profile type form element to the profile form.
|
||||
*
|
||||
* @param LinkitProfile $profile
|
||||
* A profile object contains all settings for the profile.
|
||||
*
|
||||
* @see linkit_profiles_form()
|
||||
*/
|
||||
function _linkit_build_profile_type_form_fields(&$form, LinkitProfile $profile) {
|
||||
$form['data']['profile_type'] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => t('Profile type'),
|
||||
'#description' => t(''),
|
||||
'#collapsible' => TRUE,
|
||||
'#collapsed' => TRUE,
|
||||
'#tree' => FALSE,
|
||||
);
|
||||
|
||||
$form['data']['profile_type']['profile_type'] = array(
|
||||
'#title' => t("This profile will be used with"),
|
||||
'#type' => 'radios',
|
||||
'#title_display' => 'before',
|
||||
'#options' => array(
|
||||
LINKIT_PROFILE_TYPE_EDITOR => t('Editors'),
|
||||
LINKIT_PROFILE_TYPE_FIELD => t('Fields'),
|
||||
),
|
||||
LINKIT_PROFILE_TYPE_EDITOR => array(
|
||||
'#description' => t('This profile will be used with editors like CKeditor or TinyMCE.'),
|
||||
),
|
||||
LINKIT_PROFILE_TYPE_FIELD => array(
|
||||
'#description' => t('This profile will be used with fields that do not have any editors.'),
|
||||
),
|
||||
'#default_value' => isset($profile->profile_type) ? $profile->profile_type : 'editor',
|
||||
'#required' => TRUE,
|
||||
'#parents' => array('profile_type'),
|
||||
);
|
||||
|
||||
$formats = array();
|
||||
foreach(filter_formats() as $format) {
|
||||
$formats[$format->format] = $format->name;
|
||||
}
|
||||
|
||||
$form['data']['profile_type']['text_formats'] = array(
|
||||
'#type' => 'checkboxes',
|
||||
'#title' => t('Attach this profile to one or more text formats'),
|
||||
'#options' => $formats,
|
||||
'#empty_option' => t('- Select one or more text format- '),
|
||||
'#default_value' => isset($profile->data['text_formats'])
|
||||
? $profile->data['text_formats'] : array(),
|
||||
'#description' => t('.'),
|
||||
'#states' => array(
|
||||
'visible' => array(
|
||||
// While waiting for https://drupal.org/node/879580 to be commited, we
|
||||
// have to convert the constant to a string.
|
||||
'input[name="profile_type"]' => array('value' => (string) LINKIT_PROFILE_TYPE_EDITOR),
|
||||
),
|
||||
),
|
||||
'#parents' => array('data', 'text_formats'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append search plugin form element to the profile form.
|
||||
*
|
||||
* @param LinkitProfile $profile
|
||||
* A profile object contains all settings for the profile.
|
||||
*
|
||||
* @see linkit_profiles_form()
|
||||
*/
|
||||
function _linkit_build_search_plugin_form_fields(&$form, LinkitProfile $profile) {
|
||||
// Load all search plugins.
|
||||
$search_plugins = linkit_search_plugin_load_all();
|
||||
|
||||
$form['data']['search_plugins_fieldset'] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => t('Search plugins'),
|
||||
'#description' => t('Linkit is all about the search plugins. They define what content Linkit will present in the autocomplete search field.'),
|
||||
'#collapsible' => TRUE,
|
||||
'#collapsed' => TRUE,
|
||||
'#tree' => FALSE,
|
||||
);
|
||||
|
||||
$form['data']['search_plugins_fieldset']['search_plugins'] = array(
|
||||
'#markup' => '',
|
||||
'#tree' => TRUE,
|
||||
'#parents' => array('data', 'search_plugins'),
|
||||
'#theme' => 'linkit_plugin_form_table',
|
||||
);
|
||||
|
||||
$form['data']['search_plugins_fieldset']['search_plugins_settings'] = array(
|
||||
'#markup' => '',
|
||||
'#tree' => TRUE,
|
||||
'#parents' => array('data'),
|
||||
);
|
||||
|
||||
// Used to store plugin form elements temporary so we can use this to sort by
|
||||
// weight later.
|
||||
$tmp = array();
|
||||
|
||||
foreach ($search_plugins AS $name => $plugin_definition) {
|
||||
// Get a plugin instance.
|
||||
$plugin = LinkitSearchPlugin::factory($plugin_definition, $profile);
|
||||
|
||||
$tmp[$name]['name'] = array('#markup' => $plugin->ui_title());
|
||||
$tmp[$name]['description'] = array('#markup' => $plugin->ui_description());
|
||||
$tmp[$name]['enabled'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Enable @title', array('@title' => $plugin->ui_title())),
|
||||
'#title_display' => 'invisible',
|
||||
'#default_value' => isset($profile->data['search_plugins'][$name]['enabled'])
|
||||
? $profile->data['search_plugins'][$name]['enabled'] : FALSE,
|
||||
);
|
||||
$tmp[$name]['weight'] = array(
|
||||
'#type' => 'weight',
|
||||
'#title' => t('Weight for @title', array('@title' => $plugin->ui_title())),
|
||||
'#title_display' => 'invisible',
|
||||
'#default_value' => isset($profile->data['search_plugins'][$name]['weight'])
|
||||
? $profile->data['search_plugins'][$name]['weight'] : '',
|
||||
);
|
||||
|
||||
// Append the search plugin specific settings.
|
||||
$plugin_specific_settings = $plugin->buildSettingsForm();
|
||||
|
||||
if ($plugin_specific_settings) {
|
||||
// Add the handler settings form to the generic.
|
||||
$form['data']['search_plugins_fieldset']['search_plugins_settings'] += $plugin_specific_settings;
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by weight.
|
||||
uasort($tmp, '_linkit_sort_plugins_by_weight_default_value');
|
||||
|
||||
foreach ($tmp AS $name => $element) {
|
||||
$form['data']['search_plugins_fieldset']['search_plugins'][$name] = $element;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Append insert plugin form element to the profile form.
|
||||
*
|
||||
* @param LinkitProfile $profile
|
||||
* A profile object contains all settings for the profile.
|
||||
*
|
||||
* @see linkit_profiles_form()
|
||||
*
|
||||
* @TODO: Insert pluings should only be used when dealing with fields as it
|
||||
* makes no sence to have them when using a wysiwyg editor.
|
||||
*/
|
||||
function _linkit_build_insert_plugin_form_fields(&$form, LinkitProfile $profile) {
|
||||
// Make a list of all insert plugins.
|
||||
$insert_plugins = array();
|
||||
foreach (linkit_insert_plugin_load_all() as $name => $plugin) {
|
||||
$insert_plugins[$name] = $plugin['name'];
|
||||
}
|
||||
|
||||
$form['data']['insert_plugin'] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => t('Insert methods'),
|
||||
'#description' => t(''),
|
||||
'#collapsible' => TRUE,
|
||||
'#collapsed' => TRUE,
|
||||
'#tree' => TRUE,
|
||||
);
|
||||
|
||||
// Settings for insert plugins.
|
||||
$form['data']['insert_plugin']['plugin'] = array(
|
||||
'#type' => 'select',
|
||||
'#title' => t('Insert plugin'),
|
||||
'#options' => $insert_plugins,
|
||||
'#empty_option' => t('- Select an insert plugin -'),
|
||||
'#default_value' => isset($profile->data['insert_plugin']['plugin'])
|
||||
? $profile->data['insert_plugin'] : '',
|
||||
'#states' => array(
|
||||
'visible' => array(
|
||||
'input[name="profile_type"]' => array('value' => LINKIT_PROFILE_TYPE_FIELD),
|
||||
),
|
||||
'required' => array(
|
||||
'input[name="profile_type"]' => array('value' => LINKIT_PROFILE_TYPE_FIELD),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$form['data']['insert_plugin']['url_method'] = array(
|
||||
'#title' => t("Insert paths as:"),
|
||||
'#type' => 'radios',
|
||||
'#title_display' => 'before',
|
||||
'#options' => array(
|
||||
LINKIT_URL_METHOD_RAW => t('Raw paths'),
|
||||
LINKIT_URL_METHOD_RAW_SLASH => t('Raw paths, with a slash (/) in the beginning.'),
|
||||
),
|
||||
LINKIT_URL_METHOD_RAW => array(
|
||||
'#description' => t('This will insert Drupal internal paths like
|
||||
%example_link which is probably what you want if you are going to use
|
||||
this profile together with <strong>fields</strong>.
|
||||
<br/>If used in textareas, these paths typically need to be formatted
|
||||
using a input filter, but provides more flexibility in a multilingual
|
||||
site or when moving a site.', array('%example_link' => 'node/123')),
|
||||
),
|
||||
LINKIT_URL_METHOD_RAW_SLASH => array(
|
||||
'#description' => t('This is the default behavior, it will insert paths
|
||||
like %example_link.<br/>These will work <strong>without the need for a input filter</strong>,
|
||||
but might fail after moving a site, or on multilingual sites.',
|
||||
array('%example_link' => '/node/123')),
|
||||
),
|
||||
'#default_value' => isset($profile->data['insert_plugin']['url_method']) ?
|
||||
$profile->data['insert_plugin']['url_method'] : LINKIT_URL_METHOD_RAW_SLASH,
|
||||
'#description' => t('These will have no effect on already created links.'),
|
||||
);
|
||||
|
||||
if (module_exists('path')) {
|
||||
$form['data']['insert_plugin']['url_method']['#options'][LINKIT_URL_METHOD_ALIAS] = t('Alias paths');
|
||||
$form['data']['insert_plugin']['url_method'][LINKIT_URL_METHOD_ALIAS] = array(
|
||||
'#description' => t('This will insert the alias for the result item.
|
||||
<br/>The paths will be runned thru the <em>url()</em> function.', array('%example_link' => 'node/123')),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Append attribute plugin form element to the profile form.
|
||||
*
|
||||
* @param LinkitProfile $profile
|
||||
* A profile object contains all settings for the profile.
|
||||
*
|
||||
* @see linkit_profiles_form()
|
||||
*/
|
||||
function _linkit_build_attribute_plugin_form_fields(&$form, LinkitProfile $profile) {
|
||||
// Load all attribute plugins.
|
||||
$attributes = linkit_attribute_plugin_load_all();
|
||||
|
||||
$form['data']['attribute_plugins_fieldset'] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => t('Attributes'),
|
||||
'#description' => t('Attributes is HTML attributes that will be attached to
|
||||
the insert plugin.'),
|
||||
'#collapsible' => TRUE,
|
||||
'#collapsed' => TRUE,
|
||||
'#tree' => FALSE,
|
||||
);
|
||||
|
||||
$form['data']['attribute_plugins_fieldset']['attribute_plugins'] = array(
|
||||
'#markup' => '',
|
||||
'#tree' => TRUE,
|
||||
'#parents' => array('data', 'attribute_plugins'),
|
||||
'#theme' => 'linkit_plugin_form_table',
|
||||
);
|
||||
|
||||
// Used to store plugin form elements temporary so we can use this to sort by
|
||||
// weight later.
|
||||
$tmp = array();
|
||||
|
||||
foreach ($attributes AS $name => $attribute) {
|
||||
$tmp[$name]['name'] = array('#markup' => $attribute['name']);
|
||||
$tmp[$name]['enabled'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Enable @title', array('@title' => $attribute['name'])),
|
||||
'#title_display' => 'invisible',
|
||||
'#default_value' => isset($profile->data['attribute_plugins'][$name]['enabled'])
|
||||
? $profile->data['attribute_plugins'][$name]['enabled'] : FALSE,
|
||||
);
|
||||
$tmp[$name]['weight'] = array(
|
||||
'#type' => 'weight',
|
||||
'#title' => t('Weight for @title', array('@title' => $attribute['name'])),
|
||||
'#title_display' => 'invisible',
|
||||
'#default_value' => isset($profile->data['attribute_plugins'][$name]['weight'])
|
||||
? $profile->data['attribute_plugins'][$name]['weight'] : '',
|
||||
);
|
||||
}
|
||||
|
||||
// Sort by weight.
|
||||
uasort($tmp, '_linkit_sort_plugins_by_weight_default_value');
|
||||
|
||||
foreach ($tmp AS $name => $element) {
|
||||
$form['data']['attribute_plugins_fieldset']['attribute_plugins'][$name] = $element;
|
||||
}
|
||||
|
||||
$form['data']['attribute_plugins_fieldset']['attribute_plugins_info'] = array(
|
||||
'#markup' => 'Keep in mind that not all attributes can be used by all insert plugins.',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append IMCE plugin form element to the profile form.
|
||||
*
|
||||
* @param LinkitProfile $profile
|
||||
* A profile object contains all settings for the profile.
|
||||
*
|
||||
* @see linkit_profiles_form()
|
||||
*/
|
||||
function _linkit_build_imce_form_fields(&$form, LinkitProfile $profile) {
|
||||
|
||||
$form['data']['imce'] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => t('IMCE File Picker'),
|
||||
'#collapsible' => TRUE,
|
||||
'#collapsed' => TRUE,
|
||||
'#tree' => FALSE,
|
||||
'#weight' => 80,
|
||||
);
|
||||
|
||||
$form['data']['imce']['use_imce'] = array(
|
||||
'#title' => t('Enable !imce File Picker', array('!imce' => l(t('IMCE'),
|
||||
'admin/config/media/imce'))),
|
||||
'#type' => 'checkbox',
|
||||
'#description' => t('Note that only public files are supported.'),
|
||||
'#default_value' => isset($profile->data['imce'])
|
||||
? $profile->data['imce'] : FALSE,
|
||||
'#parents' => array('data', 'imce'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append autocomplete (BAC) form element to the profile form.
|
||||
*
|
||||
* @param LinkitProfile $profile
|
||||
* A profile object contains all settings for the profile.
|
||||
*
|
||||
* @see linkit_profiles_form()
|
||||
*/
|
||||
function _linkit_build_autocomplete_form_fields(&$form, LinkitProfile $profile) {
|
||||
$form['data']['autocomplete'] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => t('Autocomplete options'),
|
||||
'#description' => t('Linkit uses !bac which may be configured with focus on
|
||||
performance.', array('!bac' => l(t('Better Autocomplete'),
|
||||
'https://github.com/betamos/Better-Autocomplete'))),
|
||||
'#collapsible' => TRUE,
|
||||
'#collapsed' => TRUE,
|
||||
'#tree' => TRUE,
|
||||
'#weight' => 100,
|
||||
);
|
||||
|
||||
$form['data']['autocomplete']['charLimit'] = array(
|
||||
'#title' => t('Character limit'),
|
||||
'#type' => 'textfield',
|
||||
'#default_value' => isset($profile->data['autocomplete']['charLimit']) ? $profile->data['autocomplete']['charLimit'] : LINKIT_CHAR_LIMIT,
|
||||
'#size' => 5,
|
||||
'#description' => t('Minimum number of characters to perform server polling.
|
||||
A higher value reduces server load. <em>Default: @default</em>', array('@default' => LINKIT_CHAR_LIMIT)),
|
||||
'#element_validate' => array('linkit_isnumeric_validate'),
|
||||
);
|
||||
|
||||
$form['data']['autocomplete']['wait'] = array(
|
||||
'#title' => t('Key press delay'),
|
||||
'#type' => 'textfield',
|
||||
'#default_value' => isset($profile->data['autocomplete']['wait']) ? $profile->data['autocomplete']['wait'] : LINKIT_WAIT,
|
||||
'#size' => 5,
|
||||
'#description' => t('Time from last key press to poll the server. A higher
|
||||
value reduces server load. <em>Default: @default</em>', array('@default' => LINKIT_WAIT)),
|
||||
'#field_suffix' => t('ms'),
|
||||
'#element_validate' => array('linkit_isnumeric_validate'),
|
||||
);
|
||||
|
||||
$form['data']['autocomplete']['remoteTimeout'] = array(
|
||||
'#title' => t('Remote timeout'),
|
||||
'#type' => 'textfield',
|
||||
'#default_value' => isset($profile->data['autocomplete']['remoteTimeout']) ? $profile->data['autocomplete']['remoteTimeout'] : LINKIT_REMOTE_TIMEOUT,
|
||||
'#size' => 5,
|
||||
'#description' => t('Client side timeout for a request to the server.
|
||||
<em>Default: @default</em>', array('@default' => LINKIT_REMOTE_TIMEOUT)),
|
||||
'#field_suffix' => t('ms'),
|
||||
'#element_validate' => array('linkit_isnumeric_validate'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function used by uasort to sort plugin arrays by the weight default value.
|
||||
*/
|
||||
function _linkit_sort_plugins_by_weight_default_value($a, $b) {
|
||||
return $a["weight"]['#default_value'] >= $b["weight"]['#default_value'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract tokens that can be used by the $type.
|
||||
*
|
||||
* @param $type
|
||||
* A string with the entity type.
|
||||
*/
|
||||
function linkit_extract_tokens($type) {
|
||||
// token_info() has it own static cache, so we can call it as we like.
|
||||
$tokens = token_info();
|
||||
// If no tokens for the type exists, return an empty array.
|
||||
if (!isset($tokens['tokens'][$type])) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$available_tokens = array();
|
||||
foreach ($tokens['tokens'][$type] as $token_key => $token) {
|
||||
$available_tokens[] = '[' . $type . ':' . $token_key . ']';
|
||||
}
|
||||
|
||||
return $available_tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Element validate callback for fields that should be numeric.
|
||||
*
|
||||
* This function is assigned as an #element_validate callback in
|
||||
* linkit_profiles_form().
|
||||
*/
|
||||
function linkit_isnumeric_validate($element, &$form_state, $form) {
|
||||
if (!empty($element['#value']) && !is_numeric($element['#value'])) {
|
||||
form_error($element, $element['#title'] . ' should only contains numbers.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Define Linkit accesskey attribute plugin.
|
||||
*/
|
||||
|
||||
$plugin = array(
|
||||
'name' => t('Access key'),
|
||||
'callback' => 'linkit_attribute_plugin_accesskey',
|
||||
);
|
||||
|
||||
/**
|
||||
* Create a FAPI element.
|
||||
*
|
||||
* @param array $plugin
|
||||
* This is the plugin definition.
|
||||
*
|
||||
* @param array $settings
|
||||
* An array of settings for this attribute. This is from the profile settings.
|
||||
*
|
||||
* @return
|
||||
* An FAPI element.
|
||||
*
|
||||
* @see LinkitProfile::setAttributes()
|
||||
*/
|
||||
function linkit_attribute_plugin_accesskey($plugin, $settings = array()) {
|
||||
return array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => $plugin['name'],
|
||||
'#maxlength' => 255,
|
||||
'#size' => 40,
|
||||
'#default_value' => '',
|
||||
'#weight' => isset($settings['weight']) ? $settings['weight'] : 0,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Define Linkit class attribute plugin.
|
||||
*/
|
||||
|
||||
$plugin = array(
|
||||
'name' => t('Classes'),
|
||||
'callback' => 'linkit_attribute_plugin_class',
|
||||
);
|
||||
|
||||
/**
|
||||
* Create a FAPI element.
|
||||
*
|
||||
* @param array $plugin
|
||||
* This is the plugin definition.
|
||||
*
|
||||
* @param array $settings
|
||||
* An array of settings for this attribute. This is from the profile settings.
|
||||
*
|
||||
* @return
|
||||
* An FAPI element.
|
||||
*
|
||||
* @see LinkitProfile::setAttributes()
|
||||
*/
|
||||
function linkit_attribute_plugin_class($plugin, $settings = array()) {
|
||||
return array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => $plugin['name'],
|
||||
'#maxlength' => 255,
|
||||
'#size' => 40,
|
||||
'#default_value' => '',
|
||||
'#weight' => isset($settings['weight']) ? $settings['weight'] : 0,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Define Linkit id attribute plugin.
|
||||
*/
|
||||
|
||||
$plugin = array(
|
||||
'name' => t('Id'),
|
||||
'callback' => 'linkit_attribute_plugin_id',
|
||||
);
|
||||
|
||||
/**
|
||||
* Create a FAPI element.
|
||||
*
|
||||
* @param array $plugin
|
||||
* This is the plugin definition.
|
||||
*
|
||||
* @param array $settings
|
||||
* An array of settings for this attribute. This is from the profile settings.
|
||||
*
|
||||
* @return
|
||||
* An FAPI element.
|
||||
*
|
||||
* @see LinkitProfile::setAttributes()
|
||||
*/
|
||||
function linkit_attribute_plugin_id($plugin, $settings = array()) {
|
||||
return array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => $plugin['name'],
|
||||
'#maxlength' => 255,
|
||||
'#size' => 40,
|
||||
'#default_value' => '',
|
||||
'#weight' => isset($settings['weight']) ? $settings['weight'] : 0,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Define Linkit rel attribute plugin.
|
||||
*/
|
||||
|
||||
$plugin = array(
|
||||
'name' => t('Relationship'),
|
||||
'callback' => 'linkit_attribute_plugin_rel',
|
||||
);
|
||||
|
||||
/**
|
||||
* Create a FAPI element.
|
||||
*
|
||||
* @param array $plugin
|
||||
* This is the plugin definition.
|
||||
*
|
||||
* @param array $settings
|
||||
* An array of settings for this attribute. This is from the profile settings.
|
||||
*
|
||||
* @return
|
||||
* An FAPI element.
|
||||
*
|
||||
* @see LinkitProfile::setAttributes()
|
||||
*/
|
||||
function linkit_attribute_plugin_rel($plugin, $settings = array()) {
|
||||
return array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => $plugin['name'],
|
||||
'#maxlength' => 255,
|
||||
'#size' => 40,
|
||||
'#default_value' => '',
|
||||
'#weight' => isset($settings['weight']) ? $settings['weight'] : 0,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Define Linkit target attribute plugin.
|
||||
*/
|
||||
|
||||
$plugin = array(
|
||||
'name' => t('Target'),
|
||||
'callback' => 'linkit_attribute_plugin_target',
|
||||
);
|
||||
|
||||
/**
|
||||
* Create a FAPI element.
|
||||
*
|
||||
* @param array $plugin
|
||||
* This is the plugin definition.
|
||||
*
|
||||
* @param array $settings
|
||||
* An array of settings for this attribute. This is from the profile settings.
|
||||
*
|
||||
* @return
|
||||
* An FAPI element.
|
||||
*
|
||||
* @see LinkitProfile::setAttributes()
|
||||
*/
|
||||
function linkit_attribute_plugin_target($plugin, $settings = array()) {
|
||||
return array(
|
||||
'#type' => 'select',
|
||||
'#title' => $plugin['name'],
|
||||
'#options' => array(
|
||||
'' => '',
|
||||
'_blank' => t('New window (_blank)'),
|
||||
'_top' => t('Top window (_top)'),
|
||||
'_self' => t('Same window (_self)'),
|
||||
'_parent' => t('Parent window (_parent)')
|
||||
),
|
||||
'#weight' => isset($settings['weight']) ? $settings['weight'] : 0,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Define Linkit title attribute plugin.
|
||||
*/
|
||||
|
||||
$plugin = array(
|
||||
'name' => t('Title'),
|
||||
'callback' => 'linkit_attribute_plugin_title',
|
||||
);
|
||||
|
||||
/**
|
||||
* Create a FAPI element.
|
||||
*
|
||||
* @param array $plugin
|
||||
* This is the plugin definition.
|
||||
*
|
||||
* @param array $settings
|
||||
* An array of settings for this attribute. This is from the profile settings.
|
||||
*
|
||||
* @return
|
||||
* An FAPI element.
|
||||
*
|
||||
* @see LinkitProfile::setAttributes()
|
||||
*/
|
||||
function linkit_attribute_plugin_title($plugin, $settings = array()) {
|
||||
return array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => $plugin['name'],
|
||||
'#maxlength' => 255,
|
||||
'#size' => 40,
|
||||
'#default_value' => '',
|
||||
'#weight' => isset($settings['weight']) ? $settings['weight'] : 0,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Defines the HTML link insert plugin.
|
||||
*/
|
||||
|
||||
$plugin = array(
|
||||
'name' => t('HTML Link'),
|
||||
'javascript' => 'html_link.js',
|
||||
);
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* @file
|
||||
* HTML Link insert plugin for Linkit.
|
||||
*/
|
||||
(function ($) {
|
||||
|
||||
Drupal.linkit.registerInsertPlugin('html_link', {
|
||||
insert : function(data) {
|
||||
var text,
|
||||
selection = Drupal.settings.linkit.currentInstance.selection;
|
||||
|
||||
// Delete all attributes that are empty.
|
||||
for (name in data.attributes) {
|
||||
(data.attributes[name]) ? null : delete data.attributes[name];
|
||||
}
|
||||
|
||||
if (typeof selection != 'undefined' &&
|
||||
selection.text.length >= 1) {
|
||||
text = selection.text;
|
||||
}
|
||||
else {
|
||||
text = Drupal.settings.linkit.currentInstance.linkContent;
|
||||
}
|
||||
|
||||
// Use document.createElement as it is mush fasten then $('<a/>).
|
||||
return $(document.createElement('a'))
|
||||
.attr(data.attributes)
|
||||
.attr('href', data.path)
|
||||
.html(text)
|
||||
// Convert the element to a string.
|
||||
.get(0).outerHTML;
|
||||
}
|
||||
});
|
||||
|
||||
})(jQuery);
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Defines the markdown insert plugin.
|
||||
*/
|
||||
$plugin = array(
|
||||
'name' => t('Markdown'),
|
||||
'javascript' => drupal_get_path('module', 'linkit') . '/plugins/linkit_insert/markdown/markdown.js',
|
||||
);
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* @file
|
||||
* Markdown insert plugin for Linkit.
|
||||
*
|
||||
* Notes: Markdown dont support any attributes exept "title".
|
||||
* [An example](http://example.com/ "Title")
|
||||
*/
|
||||
(function ($) {
|
||||
|
||||
Drupal.linkit.registerInsertPlugin('markdown', {
|
||||
insert : function(data) {
|
||||
var pattern = '[!text](!url!title)',
|
||||
args = {
|
||||
'!url' : data.path,
|
||||
'!title' : data.attributes.title ? ' "' + data.attributes.title + '"' : ''
|
||||
},
|
||||
selection = Drupal.settings.linkit.currentInstance.selection;
|
||||
|
||||
if (typeof selection != 'undefined' &&
|
||||
selection.text.length >= 1) {
|
||||
args['!text'] = selection.text;
|
||||
}
|
||||
else {
|
||||
args['!text'] = Drupal.settings.linkit.currentInstance.linkContent;
|
||||
}
|
||||
|
||||
return Drupal.formatString(pattern, args);
|
||||
}
|
||||
});
|
||||
|
||||
})(jQuery);
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Defines the raw url insert plugin.
|
||||
*/
|
||||
|
||||
$plugin = array(
|
||||
'name' => t('Raw URL'),
|
||||
'javascript' => 'raw_url.js',
|
||||
);
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* @file
|
||||
* Raw HTML insert plugin for Linkit.
|
||||
*/
|
||||
(function ($) {
|
||||
Drupal.linkit.registerInsertPlugin('raw_url', {
|
||||
insert : function(data, settings) {
|
||||
return data.path;
|
||||
}
|
||||
});
|
||||
})(jQuery);
|
||||
@@ -0,0 +1,334 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Define Linkit entity search plugin class.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Reprecents a Linkit entity search plugin.
|
||||
*/
|
||||
class LinkitSearchPluginEntity extends LinkitSearchPlugin {
|
||||
|
||||
/**
|
||||
* Entity field query instance.
|
||||
*
|
||||
* @var EntityFieldQuery Resource
|
||||
*/
|
||||
var $query;
|
||||
|
||||
/**
|
||||
* The entity info array of an entity type.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
var $entity_info = array();
|
||||
|
||||
/**
|
||||
* The name of the property that contains the entity label.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
var $entity_field_label;
|
||||
|
||||
/**
|
||||
* The name of the property of the bundle object that contains the name of
|
||||
* the bundle object.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
var $entity_key_bundle;
|
||||
|
||||
/**
|
||||
* Plugin specific settings.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
var $conf = array();
|
||||
|
||||
/**
|
||||
* Overrides LinkitSearchPlugin::__construct().
|
||||
*
|
||||
* Initialize this plugin with the plugin, profile, and entity specific
|
||||
* variables.
|
||||
*
|
||||
* @param array $plugin
|
||||
* The plugin array.
|
||||
*
|
||||
* @param LinkitProfile object $profile
|
||||
* The Linkit profile to use.
|
||||
*/
|
||||
function __construct($plugin, LinkitProfile $profile) {
|
||||
parent::__construct($plugin, $profile);
|
||||
|
||||
// Load the corresponding entity info.
|
||||
$this->entity_info = entity_get_info($this->plugin['entity_type']);
|
||||
|
||||
// Set bundle key name.
|
||||
if (isset($this->entity_info['entity keys']['bundle']) &&
|
||||
!isset($this->entity_key_bundle)) {
|
||||
$this->entity_key_bundle = $this->entity_info['entity keys']['bundle'];
|
||||
}
|
||||
|
||||
// Set the label field name.
|
||||
if (!isset($this->entity_field_label)) {
|
||||
// Check that the entity has a label in entity keys.
|
||||
// If not, Linkit don't know what to search for.
|
||||
if (!isset($this->entity_info['entity keys']['label'])) {
|
||||
// This is only used when building the plugin list.
|
||||
$this->unusable = TRUE;
|
||||
}
|
||||
else {
|
||||
$this->entity_field_label = $this->entity_info['entity keys']['label'];
|
||||
}
|
||||
}
|
||||
|
||||
// Make a shortcut for the profile data settings for this plugin.
|
||||
$this->conf = isset($this->profile->data[$this->plugin['name']]) ?
|
||||
$this->profile->data[$this->plugin['name']] : array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a label of an entity.
|
||||
*
|
||||
* @param $entity
|
||||
* The entity to get the label from.
|
||||
*
|
||||
* @return
|
||||
* The entity label, or FALSE if not found.
|
||||
*/
|
||||
function createLabel($entity) {
|
||||
return check_plain(entity_label($this->plugin['entity_type'], $entity));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a search row description.
|
||||
*
|
||||
* If there is a "result_description", run it thro token_replace.
|
||||
*
|
||||
* @param object $data
|
||||
* An entity object that will be used in the token_place function.
|
||||
*
|
||||
* @return
|
||||
* A string containing the row description.
|
||||
*
|
||||
* @see token_replace()
|
||||
*/
|
||||
function createDescription($data) {
|
||||
$description = token_replace(check_plain($this->conf['result_description']), array(
|
||||
$this->plugin['entity_type'] => $data,
|
||||
), array('clear' => TRUE));
|
||||
return $description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an uri for an entity.
|
||||
*
|
||||
* @param $entity
|
||||
* The entity to get the path from.
|
||||
*
|
||||
* @return
|
||||
* A string containing the path of the entity, NULL if the entity has no
|
||||
* uri of its own.
|
||||
*/
|
||||
function createPath($entity) {
|
||||
// Create the URI for the entity.
|
||||
$uri = entity_uri($this->plugin['entity_type'], $entity);
|
||||
|
||||
$options = array();
|
||||
// Handle multilingual sites.
|
||||
if (isset($entity->language) && $entity->language != LANGUAGE_NONE && drupal_multilingual() && language_negotiation_get_any(LOCALE_LANGUAGE_NEGOTIATION_URL)) {
|
||||
$languages = language_list('enabled');
|
||||
// Only use enabled languages.
|
||||
$languages = $languages[1];
|
||||
|
||||
if ($languages && isset($languages[$entity->language])) {
|
||||
$options['language'] = $languages[$entity->language];
|
||||
}
|
||||
}
|
||||
// Process the uri with the insert pluing.
|
||||
$path = linkit_get_insert_plugin_processed_path($this->profile, $uri['path'], $options);
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a group text.
|
||||
*
|
||||
* @param $entity
|
||||
* The entity object.
|
||||
*
|
||||
* @return
|
||||
* When "group_by_bundle" is active, we need to add the bundle name to the
|
||||
* group, else just return the entity label.
|
||||
*/
|
||||
function createGroup($entity) {
|
||||
// Get the entity label.
|
||||
$group = $this->entity_info['label'];
|
||||
|
||||
// If the entities by this entity should be grouped by bundle, get the
|
||||
// name and append it to the group.
|
||||
if (isset($this->conf['group_by_bundle']) && $this->conf['group_by_bundle']) {
|
||||
$bundles = $this->entity_info['bundles'];
|
||||
$bundle_name = $bundles[$entity->{$this->entity_key_bundle}]['label'];
|
||||
$group .= ' - ' . check_plain($bundle_name);
|
||||
}
|
||||
return $group;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a row class to appaned to the search result row.
|
||||
*
|
||||
* @param $entity
|
||||
* The entity object.
|
||||
*
|
||||
* @return
|
||||
* A string to with classes.
|
||||
*/
|
||||
function createRowClass($entity) {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a new EntityFieldQuery instance.
|
||||
*/
|
||||
function getQueryInstance() {
|
||||
$this->query = new EntityFieldQuery();
|
||||
$this->query->entityCondition('entity_type', $this->plugin['entity_type']);
|
||||
|
||||
// Add the default sort on the enity label.
|
||||
$this->query->propertyOrderBy($this->entity_field_label, 'ASC');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements LinkitSearchPluginInterface::fetchResults().
|
||||
*/
|
||||
public function fetchResults($search_string) {
|
||||
// If the $search_string is not a string, something is wrong and an empty
|
||||
// array is returned.
|
||||
$matches = array();
|
||||
|
||||
// Get the EntityFieldQuery instance.
|
||||
$this->getQueryInstance();
|
||||
|
||||
// Add the search condition to the query object.
|
||||
$this->query->propertyCondition($this->entity_field_label,
|
||||
'%' . db_like($search_string) . '%', 'LIKE')
|
||||
->addTag('linkit_entity_autocomplete')
|
||||
->addTag('linkit_' . $this->plugin['entity_type'] . '_autocomplete');
|
||||
|
||||
// Add access tag for the query.
|
||||
// There is also a runtime access check that uses entity_access().
|
||||
$this->query->addTag($this->plugin['entity_type'] . '_access');
|
||||
|
||||
// Bundle check.
|
||||
if (isset($this->entity_key_bundle) && isset($this->conf['bundles']) ) {
|
||||
$bundles = array_filter($this->conf['bundles']);
|
||||
if ($bundles) {
|
||||
$this->query->propertyCondition($this->entity_key_bundle, $bundles, 'IN');
|
||||
}
|
||||
}
|
||||
|
||||
// Execute the query.
|
||||
$result = $this->query->execute();
|
||||
|
||||
if (!isset($result[$this->plugin['entity_type']])) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$ids = array_keys($result[$this->plugin['entity_type']]);
|
||||
|
||||
// Load all the entities with all the ids we got.
|
||||
$entities = entity_load($this->plugin['entity_type'], $ids);
|
||||
|
||||
foreach ($entities AS $entity) {
|
||||
// Check the access againt the definded entity access callback.
|
||||
if (entity_access('view', $this->plugin['entity_type'], $entity) === FALSE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$matches[] = array(
|
||||
'title' => $this->createLabel($entity),
|
||||
'description' => $this->createDescription($entity),
|
||||
'path' => $this->createPath($entity),
|
||||
'group' => $this->createGroup($entity),
|
||||
'addClass' => $this->createRowClass($entity),
|
||||
);
|
||||
|
||||
}
|
||||
return $matches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides LinkitSearchPlugin::buildSettingsForm().
|
||||
*/
|
||||
function buildSettingsForm() {
|
||||
$form[$this->plugin['name']] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => t('!type plugin settings', array('!type' => $this->ui_title())),
|
||||
'#collapsible' => TRUE,
|
||||
'#collapsed' => TRUE,
|
||||
'#tree' => TRUE,
|
||||
'#states' => array(
|
||||
'invisible' => array(
|
||||
'input[name="data[search_plugins][' . $this->plugin['name'] . '][enabled]"]' => array('checked' => FALSE),
|
||||
),
|
||||
),
|
||||
);
|
||||
// Get supported tokens for the entity type.
|
||||
$tokens = linkit_extract_tokens($this->plugin['entity_type']);
|
||||
|
||||
// A short description in within the search result for each row.
|
||||
$form[$this->plugin['name']]['result_description'] = array(
|
||||
'#title' => t('Result description'),
|
||||
'#type' => 'textfield',
|
||||
'#default_value' => isset($this->conf['result_description']) ? $this->conf['result_description'] : '',
|
||||
'#size' => 120,
|
||||
'#maxlength' => 255,
|
||||
'#description' => t('Available tokens: %tokens.', array('%tokens' => implode(', ', $tokens))),
|
||||
);
|
||||
|
||||
// If the token module is installed, lets make some fancy stuff with the
|
||||
// token chooser.
|
||||
if (module_exists('token')) {
|
||||
// Unset the regular description if token module is enabled.
|
||||
unset($form[$this->plugin['name']]['result_description']['#description']);
|
||||
|
||||
// Display the user documentation of placeholders.
|
||||
$form[$this->plugin['name']]['token_help'] = array(
|
||||
'#title' => t('Replacement patterns'),
|
||||
'#type' => 'fieldset',
|
||||
'#collapsible' => TRUE,
|
||||
'#collapsed' => TRUE,
|
||||
);
|
||||
$form[$this->plugin['name']]['token_help']['help'] = array(
|
||||
'#theme' => 'token_tree',
|
||||
'#token_types' => array($this->plugin['entity_type']),
|
||||
);
|
||||
}
|
||||
|
||||
// If there is bundles, add some default settings features.
|
||||
if (count($this->entity_info['bundles']) > 1) {
|
||||
$bundles = array();
|
||||
// Extract the bundle data.
|
||||
foreach ($this->entity_info['bundles'] as $bundle_name => $bundle) {
|
||||
$bundles[$bundle_name] = $bundle['label'];
|
||||
}
|
||||
|
||||
// Filter the possible bundles to use if the entity has bundles.
|
||||
$form[$this->plugin['name']]['bundles'] = array(
|
||||
'#title' => t('Type filter'),
|
||||
'#type' => 'checkboxes',
|
||||
'#options' => $bundles,
|
||||
'#default_value' => isset($this->conf['bundles']) ? $this->conf['bundles'] : array(),
|
||||
'#description' => t('If left blank, all types will appear in autocomplete results.'),
|
||||
);
|
||||
|
||||
// Group the results with this bundle.
|
||||
$form[$this->plugin['name']]['group_by_bundle'] = array(
|
||||
'#title' => t('Group by bundle'),
|
||||
'#type' => 'checkbox',
|
||||
'#default_value' => isset($this->conf['group_by_bundle']) ? $this->conf['group_by_bundle'] : 0,
|
||||
);
|
||||
}
|
||||
return $form;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Linkit Entity Search Plugin.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Plugin structure is an associative array containing:
|
||||
* - ui_title: The title of the plugin used in the admin UI.
|
||||
* - ui_description: A description used in the admin UI.
|
||||
* - entity_type: (Only required if the plugin extends LinkitPluginEntity) The
|
||||
* entity type this plugin will be used for.
|
||||
* - handler: Describes the class to be used for this plugin.
|
||||
*/
|
||||
$plugin = array(
|
||||
'get child' => 'linkit_entity_ctools_linkit_get_child',
|
||||
'get children' => 'linkit_entity_ctools_linkit_get_children',
|
||||
'handler' => array(
|
||||
'class' => 'LinkitSearchPluginEntity',
|
||||
'file' => 'entity.class.php',
|
||||
),
|
||||
);
|
||||
|
||||
/**
|
||||
* Get a single Linkit search plugin.
|
||||
*/
|
||||
function linkit_entity_ctools_linkit_get_child($plugin, $parent, $child) {
|
||||
$plugins = linkit_entity_ctools_linkit_get_children($plugin, $parent);
|
||||
return $plugins[$parent . ':' . $child];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all child Linkit search plugins.
|
||||
*/
|
||||
function linkit_entity_ctools_linkit_get_children($plugin, $parent) {
|
||||
$entities = entity_get_info();
|
||||
$plugins = array();
|
||||
|
||||
// The alternative plugins is extensions of the provided
|
||||
// LinkitSearchPluginEntity class provided by Linkit.
|
||||
$alternative_plugins = array(
|
||||
'entity:node',
|
||||
'entity:user',
|
||||
'entity:taxonomy_term',
|
||||
'entity:file',
|
||||
);
|
||||
|
||||
foreach ($entities as $entity_type => $entity) {
|
||||
// The entity must have an URI CALLBACK defined.
|
||||
if (!isset($entity['uri callback'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$plugin_child = $plugin;
|
||||
$plugin_child['ui_title'] = $entity['label'];
|
||||
$plugin_child['ui_description'] = t('Extend Linkit with @entity support.', array('@entity' => $entity_type));
|
||||
$plugin_child['name'] = $parent . ':' . $entity_type;
|
||||
$plugin_child['entity_type'] = $entity_type;
|
||||
|
||||
// If there is an alternative search class for this entity, change the handler.
|
||||
if (in_array('entity:' . $entity_type, $alternative_plugins)) {
|
||||
$handler = array(
|
||||
'class' => 'LinkitSearchPlugin' . drupal_ucfirst($entity_type),
|
||||
'file' => $entity_type . '.class.php',
|
||||
);
|
||||
$plugin_child['handler'] = $handler;
|
||||
}
|
||||
drupal_alter('linkit_search_plugin_entity', $plugin_child, $entity);
|
||||
$plugins[$parent . ':' . $entity_type] = $plugin_child;
|
||||
}
|
||||
|
||||
drupal_alter('linkit_search_plugin_entities', $plugins);
|
||||
|
||||
// Test the plugins.
|
||||
foreach ($plugins as $plugin_name => $plugin) {
|
||||
// In some cases the plugins isn't auto loaded. Ctools handle this for us.
|
||||
// This is typical when installing Linkit.
|
||||
if (!class_exists($plugin['handler']['class'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tested_plugin = new $plugin['handler']['class']($plugin, new LinkitProfile());
|
||||
if (isset($tested_plugin->unusable) && $tested_plugin->unusable) {
|
||||
unset($plugins[$plugin_name]);
|
||||
}
|
||||
}
|
||||
|
||||
return $plugins;
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Define Linkit file search plugin class.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Define that file urls should link directly to the file itself.
|
||||
*/
|
||||
define('LINKIT_FILE_URL_TYPE_DIRECT', 'direct');
|
||||
|
||||
/**
|
||||
* Define that file urls should force download of the file.
|
||||
*/
|
||||
define('LINKIT_FILE_URL_TYPE_DOWNLOAD', 'download');
|
||||
|
||||
/**
|
||||
* Define that file urls should link the file entity view page.
|
||||
*/
|
||||
define('LINKIT_FILE_URL_TYPE_ENTITY', 'entity');
|
||||
|
||||
/**
|
||||
* Reprecents a Linkit file search plugin.
|
||||
*/
|
||||
class LinkitSearchPluginFile extends LinkitSearchPluginEntity {
|
||||
|
||||
/**
|
||||
* Overrides LinkitSearchPlugin::ui_title().
|
||||
*/
|
||||
function ui_title() {
|
||||
return t('Managed files');
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides LinkitSearchPlugin::ui_description().
|
||||
*/
|
||||
function ui_description() {
|
||||
return t('Extend Linkit with file support (Managed files).');
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides LinkitSearchPluginEntity::createDescription().
|
||||
*
|
||||
* If the file is an image, a small thumbnail can be added to the description.
|
||||
* Also, image dimensions can be shown.
|
||||
*/
|
||||
function createDescription($data) {
|
||||
$description_array = array();
|
||||
// Get image info.
|
||||
$imageinfo = image_get_info($data->uri);
|
||||
|
||||
// Add small thumbnail to the description.
|
||||
if ($this->conf['image_extra_info']['thumbnail']) {
|
||||
$image = $imageinfo ? theme_image_style(array(
|
||||
'width' => $imageinfo['width'],
|
||||
'height' => $imageinfo['height'],
|
||||
'style_name' => 'linkit_thumb',
|
||||
'path' => $data->uri,
|
||||
)) : '';
|
||||
}
|
||||
|
||||
// Add image dimensions to the description.
|
||||
if ($this->conf['image_extra_info']['dimensions'] && !empty($imageinfo)) {
|
||||
$description_array[] = $imageinfo['width'] . 'x' . $imageinfo['height'] . 'px';
|
||||
}
|
||||
|
||||
$description_array[] = parent::createDescription($data);
|
||||
|
||||
// Add tiel files scheme to the description.
|
||||
if ($this->conf['show_scheme']) {
|
||||
$description_array[] = file_uri_scheme($data->uri) . '://';
|
||||
}
|
||||
|
||||
$description = (isset($image) ? $image : '') . implode('<br />' , $description_array);
|
||||
|
||||
return $description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides LinkitSearchPluginEntity::createGroup().
|
||||
*/
|
||||
function createGroup($entity) {
|
||||
// The the standard group name.
|
||||
$group = parent::createGroup($entity);
|
||||
|
||||
// Add the scheme.
|
||||
if ($this->conf['group_by_scheme']) {
|
||||
// Get all stream wrappers.
|
||||
$stream_wrapper = file_get_stream_wrappers();
|
||||
$group .= ' - ' . $stream_wrapper[file_uri_scheme($entity->uri)]['name'];
|
||||
}
|
||||
return $group;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides LinkitSearchPluginEntity::createPath().
|
||||
*
|
||||
* If 'Direct download' is enabled, make the link point to the file entity
|
||||
* download endpoint.
|
||||
*/
|
||||
function createPath($entity) {
|
||||
$url_type = isset($this->conf['url_type']) ? $this->conf['url_type'] : $this->getDefaultUrlType();
|
||||
|
||||
// We can only support the download type if we have version 2.x of the file_entity module.
|
||||
if ($url_type == LINKIT_FILE_URL_TYPE_DOWNLOAD && !(module_exists('file_entity') && function_exists('file_entity_download_uri'))) {
|
||||
$url_type = $this->getDefaultUrlType();
|
||||
}
|
||||
|
||||
switch ($url_type) {
|
||||
case LINKIT_FILE_URL_TYPE_DIRECT:
|
||||
// Check if this is a local file.
|
||||
$wrapper = file_stream_wrapper_get_instance_by_uri($entity->uri);
|
||||
if ($wrapper instanceof DrupalLocalStreamWrapper) {
|
||||
// Create a relative URL to the local file.
|
||||
// See https://www.drupal.org/node/837794.
|
||||
$path = $wrapper->getDirectoryPath() . '/' . file_uri_target($entity->uri);
|
||||
}
|
||||
else {
|
||||
$path = file_create_url($entity->uri);
|
||||
}
|
||||
// Process the uri with the insert plugin.
|
||||
return linkit_get_insert_plugin_processed_path($this->profile, $path, array('language' => (object) array('language' => FALSE)));
|
||||
|
||||
case LINKIT_FILE_URL_TYPE_DOWNLOAD:
|
||||
$uri = file_entity_download_uri($entity);
|
||||
// Hack for LINKIT_URL_METHOD_RAW, which won't include the options that
|
||||
// we pass to linkit_get_insert_plugin_processed_path().
|
||||
if (isset($uri['options']['query']['token']) && $this->profile->data['insert_plugin']['url_method'] == LINKIT_URL_METHOD_RAW) {
|
||||
return $uri['path'] . '?token=' . rawurlencode($uri['options']['query']['token']);
|
||||
}
|
||||
// Process the uri with the insert plugin.
|
||||
return linkit_get_insert_plugin_processed_path($this->profile, $uri['path'], $uri['options']);
|
||||
|
||||
case LINKIT_FILE_URL_TYPE_ENTITY:
|
||||
// Pass back to the parent if the user wants entity urls.
|
||||
return parent::createPath($entity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides LinkitSearchPluginEntity::getQueryInstance().
|
||||
*/
|
||||
function getQueryInstance() {
|
||||
// Call the parent getQueryInstance method.
|
||||
parent::getQueryInstance();
|
||||
// Only search for permanent files.
|
||||
$this->query->propertyCondition('status', FILE_STATUS_PERMANENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides LinkitSearchPluginEntity::buildSettingsForm().
|
||||
*/
|
||||
function buildSettingsForm() {
|
||||
$form = parent::buildSettingsForm();
|
||||
|
||||
$form['entity:file']['show_scheme'] = array(
|
||||
'#title' => t('Show file scheme'),
|
||||
'#type' => 'checkbox',
|
||||
'#default_value' => isset($this->conf['show_scheme']) ? $this->conf['show_scheme'] : '',
|
||||
);
|
||||
|
||||
$form['entity:file']['group_by_scheme'] = array(
|
||||
'#title' => t('Group files by scheme'),
|
||||
'#type' => 'checkbox',
|
||||
'#default_value' => isset($this->conf['group_by_scheme']) ? $this->conf['group_by_scheme'] : '',
|
||||
);
|
||||
|
||||
$form['entity:file']['url_type'] = array(
|
||||
'#title' => t('URL type'),
|
||||
'#type' => 'radios',
|
||||
'#options' => array(
|
||||
LINKIT_FILE_URL_TYPE_DIRECT => t('Direct file link'),
|
||||
LINKIT_FILE_URL_TYPE_DOWNLOAD => t('Download file link'),
|
||||
LINKIT_FILE_URL_TYPE_ENTITY => t('Entity view page'),
|
||||
),
|
||||
'#default_value' => isset($this->conf['url_type']) ? $this->conf['url_type'] : $this->getDefaultUrlType(),
|
||||
);
|
||||
// We can only support the download type if we have version 2.x of the file_entity module.
|
||||
if (!(module_exists('file_entity') && function_exists('file_entity_download_uri'))) {
|
||||
unset($form['entity:file']['url_type']['#options'][LINKIT_FILE_URL_TYPE_DOWNLOAD]);
|
||||
}
|
||||
|
||||
$image_extra_info_options = array(
|
||||
'thumbnail' => t('Show thumbnails <em>(using the image style !linkit_thumb_link)</em>', array('!linkit_thumb_link' => l(t('linkit_thumb'), 'admin/config/media/image-styles/edit/linkit_thumb'))),
|
||||
'dimensions' => t('Show pixel dimensions'),
|
||||
);
|
||||
|
||||
$form['entity:file']['image_extra_info'] = array(
|
||||
'#title' => t('Images'),
|
||||
'#type' => 'checkboxes',
|
||||
'#options' => $image_extra_info_options,
|
||||
'#default_value' => isset($this->conf['image_extra_info']) ? $this->conf['image_extra_info'] : array('thumbnail', 'dimensions'),
|
||||
);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default URL type to use if no URL type has been explicitly set.
|
||||
*
|
||||
* @return string The URL type
|
||||
*/
|
||||
protected function getDefaultUrlType() {
|
||||
$info = entity_get_info('file');
|
||||
$callback = $info['uri callback'];
|
||||
if ($callback == 'entity_metadata_uri_file') {
|
||||
// The Drupal core file URI callback would be used if we were to use the
|
||||
// "Entity view page" URL mode, which generates absolute URLs to files.
|
||||
// The "Direct link" URL mode is preferable in this case as it generates
|
||||
// relative links.
|
||||
return LINKIT_FILE_URL_TYPE_DIRECT;
|
||||
}
|
||||
else {
|
||||
// We use a custom URI callback such as in Media 1.x or File Entity 2.x.
|
||||
// The "Entity view page" URL mode is preferable in this case.
|
||||
return LINKIT_FILE_URL_TYPE_ENTITY;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Define Linkit node search plugin class.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Reprecents a Linkit node search plugin.
|
||||
*/
|
||||
class LinkitSearchPluginNode extends LinkitSearchPluginEntity {
|
||||
|
||||
/**
|
||||
* Overrides LinkitSearchPluginEntity::createRowClass().
|
||||
*
|
||||
* Adds an extra class if the node is unpublished.
|
||||
*/
|
||||
function createRowClass($entity) {
|
||||
if ($this->conf['include_unpublished'] && $entity->status == NODE_NOT_PUBLISHED) {
|
||||
return 'unpublished-node';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides LinkitSearchPluginEntity::getQueryInstance().
|
||||
*/
|
||||
function getQueryInstance() {
|
||||
// Call the parent getQueryInstance method.
|
||||
parent::getQueryInstance();
|
||||
// If we don't want to include unpublished nodes, add a condition on status.
|
||||
if ($this->conf['include_unpublished'] == 0) {
|
||||
$this->query->propertyCondition('status', NODE_PUBLISHED);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides LinkitSearchPlugin::buildSettingsForm().
|
||||
*/
|
||||
function buildSettingsForm() {
|
||||
// Get the parent settings form.
|
||||
$form = parent::buildSettingsForm();
|
||||
|
||||
$form[$this->plugin['name']]['include_unpublished'] = array(
|
||||
'#title' => t('Include unpublished nodes'),
|
||||
'#type' => 'checkbox',
|
||||
'#default_value' => isset($this->conf['include_unpublished']) ? $this->conf['include_unpublished'] : 0,
|
||||
'#description' => t('In order to see unpublished nodes, the user most also have permissions to do so. '),
|
||||
);
|
||||
|
||||
return $form;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Define Linkit term search plugin class.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Reprecents a Linkit term search plugin.
|
||||
*/
|
||||
class LinkitSearchPluginTaxonomy_term extends LinkitSearchPluginEntity {
|
||||
|
||||
/**
|
||||
* Overrides LinkitSearchPluginEntity::__construct().
|
||||
*/
|
||||
function __construct($plugin, $profile) {
|
||||
/**
|
||||
* The term entity doesn't use the same column name as in the entity keys
|
||||
* bundle definition, so lets add it our self.
|
||||
*/
|
||||
$this->entity_key_bundle = 'vid';
|
||||
parent::__construct($plugin, $profile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides LinkitSearchPluginEntity::createDescription().
|
||||
*/
|
||||
function createDescription($data) {
|
||||
$description = token_replace(check_plain($this->conf['result_description']), array(
|
||||
'term' => $data,
|
||||
), array('clear' => TRUE));
|
||||
return $description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides LinkitSearchPluginEntity::createGroup().
|
||||
*/
|
||||
function createGroup($entity) {
|
||||
// Get the entity label.
|
||||
$group = $this->entity_info['label'];
|
||||
|
||||
if (isset($this->conf['group_by_bundle']) && $this->conf['group_by_bundle']) {
|
||||
$bundles = $this->entity_info['bundles'];
|
||||
$bundle_name = $bundles[$entity->vocabulary_machine_name]['label'];
|
||||
$group .= ' - ' . check_plain($bundle_name);
|
||||
}
|
||||
return $group;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides LinkitSearchPluginEntity::fetchResults().
|
||||
*/
|
||||
function fetchResults($search_string) {
|
||||
// The term entity doesn't use the entity keys bundle definition, its using
|
||||
// the vid instead, so lets 'translate' the bundle names to vids.
|
||||
if (isset($this->entity_key_bundle) && isset($this->conf['bundles']) ) {
|
||||
$bundles = array_filter($this->conf['bundles']);
|
||||
|
||||
// Get all vocabularies.
|
||||
$vocabularies = taxonomy_vocabulary_get_names();
|
||||
// Temp storage for values.
|
||||
$tmp_bundles = array();
|
||||
foreach ($bundles as $bundle) {
|
||||
$tmp_bundles[] = $vocabularies[$bundle]->{$this->entity_key_bundle};
|
||||
}
|
||||
|
||||
// Assign the new values as the bundles.
|
||||
$this->conf['bundles'] = $tmp_bundles;
|
||||
}
|
||||
// Call the parent.
|
||||
return parent::fetchResults($search_string);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides LinkitSearchPlugin::buildSettingsForm().
|
||||
*/
|
||||
function buildSettingsForm() {
|
||||
$form = parent::buildSettingsForm();
|
||||
|
||||
// The entity plugin uses the entity name for the #token_types, but terms
|
||||
// is a special case, its name is "Taxonomy_term" and the tokens are defined
|
||||
// (in the taxonomy module) with just "term".
|
||||
|
||||
// If the token modules is installed.
|
||||
if (isset($form[$this->plugin['name']]['token_help']['help']['#token_types'])) {
|
||||
$form[$this->plugin['name']]['token_help']['help']['#token_types'] = array('term');
|
||||
}
|
||||
// If the token module is not installed, use the orignal tokens provided by
|
||||
// Drupal core.
|
||||
else {
|
||||
// Get supported tokens for the term entity type.
|
||||
$tokens = linkit_extract_tokens('term');
|
||||
$form[$this->plugin['name']]['result_description']['#description'] = t('Available tokens: %tokens.', array('%tokens' => implode(', ', $tokens)));
|
||||
}
|
||||
return $form;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Define Linkit user search plugin class.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Reprecents a Linkit user search plugin.
|
||||
*/
|
||||
class LinkitSearchPluginUser extends LinkitSearchPluginEntity {
|
||||
|
||||
/**
|
||||
* Overrides LinkitSearchPluginEntity::__construct().
|
||||
*/
|
||||
function __construct($plugin, $profile) {
|
||||
/**
|
||||
* The user entity doesn't add any label in their entity keys as they define a
|
||||
* "label callback" instead. Therefore we have to tell which field the user
|
||||
* entity have as label.
|
||||
*/
|
||||
$this->entity_field_label = 'name';
|
||||
parent::__construct($plugin, $profile);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Tests for Linkit module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Abstract class for Linkit testing.
|
||||
*/
|
||||
//abstract class LinkitTestCase extends SimpleTestCloneTestCase {
|
||||
abstract class LinkitTestCase extends DrupalWebTestCase {
|
||||
|
||||
/**
|
||||
* Permissions to apply to administers.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $admin_permissions = array(
|
||||
'administer linkit',
|
||||
);
|
||||
|
||||
/**
|
||||
* A user object.
|
||||
*
|
||||
* @var object
|
||||
*/
|
||||
public $account;
|
||||
|
||||
/**
|
||||
* A Linkit profile object.
|
||||
*
|
||||
* @var LinkitProfile object
|
||||
*/
|
||||
public $_profile;
|
||||
|
||||
function setUp($extra_modules = array()) {
|
||||
$modules = array('ctools', 'entity', 'linkit');
|
||||
$modules += $extra_modules;
|
||||
parent::setUp($modules);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function: Create a Linkit profile.
|
||||
*/
|
||||
protected function createProfile($extra = array()) {
|
||||
ctools_include('export');
|
||||
$profile = ctools_export_crud_new('linkit_profiles');
|
||||
|
||||
$profile->name = isset($extra['name']) ? $extra['name'] : 'test_profile';
|
||||
$profile->admin_title = isset($extra['admin_title']) ? $extra['admin_title'] : 'Test Profile';
|
||||
$profile->admin_description = isset($extra['admin_description']) ? $extra['admin_description'] : 'This is a description for the Test Profile.';
|
||||
$profile->data = array();
|
||||
|
||||
if (isset($extra['data'])) {
|
||||
$profile->data += $extra['data'];
|
||||
}
|
||||
|
||||
if (empty($profile->data['profile_type'])) {
|
||||
$profile->profile_type = LINKIT_PROFILE_TYPE_EDITOR;
|
||||
}
|
||||
|
||||
if (!isset($profile->data['insert_plugin']) && $profile->profile_type == LINKIT_PROFILE_TYPE_FIELD) {
|
||||
$profile->data['insert_plugin'] = array(
|
||||
'plugin' => 'raw_url',
|
||||
'url_method' => LINKIT_URL_METHOD_RAW,
|
||||
);
|
||||
}
|
||||
|
||||
// Save the profile.
|
||||
ctools_export_crud_save('linkit_profiles', $profile);
|
||||
|
||||
// Load and return the saved profile.
|
||||
$this->_profile = linkit_profile_load($profile->name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function: Update a Linkit profile.
|
||||
*/
|
||||
protected function updateProfile() {
|
||||
ctools_include('export');
|
||||
|
||||
// Save the changes to the profile.
|
||||
ctools_export_crud_save('linkit_profiles', $this->_profile);
|
||||
|
||||
// Load the saved profile.
|
||||
$this->_profile = linkit_profile_load($this->_profile->name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to process the autocomplete call.
|
||||
*/
|
||||
protected function autocompleteCall() {
|
||||
// Call the autocomplete.
|
||||
$path = 'linkit/autocomplete/' . $this->_profile->name;
|
||||
$response = $this->drupalGetAJAX($path, array('query' => array(LINKIT_BAC_QUERY_KEY => $this->search_string)));
|
||||
$this->assertResponse(200);
|
||||
|
||||
$this->assertNotNull($response, 'The JSON respone is seems to be ok.');
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Tests for Linkit module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Tests Linkit profile UI CRUD.
|
||||
*/
|
||||
class LinkitProfileUICRUDTestCase extends LinkitTestCase {
|
||||
|
||||
/**
|
||||
* Default profile values.
|
||||
*/
|
||||
protected $profile_defaults = array(
|
||||
'name' => 'test_profile',
|
||||
'admin_title' => 'Test Profile',
|
||||
'admin_description' => 'This is a description for the Test Profile.',
|
||||
'profile_type' => LINKIT_PROFILE_TYPE_EDITOR,
|
||||
);
|
||||
|
||||
/**
|
||||
* Definition.
|
||||
*/
|
||||
public static function getInfo() {
|
||||
return array(
|
||||
'name' => 'Linkit Profile',
|
||||
'description' => 'Test that a Linkit profile can be created/read(load)/updated/deleted.',
|
||||
'group' => 'Linkit'
|
||||
);
|
||||
}
|
||||
|
||||
function setUp($extra_modules = array()) {
|
||||
parent::setUp($extra_modules);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a profile.
|
||||
*/
|
||||
public function testCreateProfile() {
|
||||
// Create a user and login.
|
||||
$this->account = $this->drupalCreateUser($this->admin_permissions);
|
||||
$this->drupalLogin($this->account);
|
||||
|
||||
// Save the profile.
|
||||
$this->drupalPost('admin/config/content/linkit/add', $this->profile_defaults, t('Save'));
|
||||
|
||||
$this->assertRaw(t('!title has been created.', array('!title' => $this->profile_defaults['name'])), 'The new profile was created.');
|
||||
|
||||
// Go to the edit page.
|
||||
$this->drupalGet('admin/config/content/linkit/list/' . $this->profile_defaults['name'] . '/edit');
|
||||
$this->assertResponse(200);
|
||||
|
||||
// Test that the given values are saved.
|
||||
foreach ($this->profile_defaults as $form_key => $value) {
|
||||
$this->assertFieldByName($form_key, $value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a profile.
|
||||
*/
|
||||
public function testUpdateProfile() {
|
||||
// Create a user and login.
|
||||
$this->account = $this->drupalCreateUser($this->admin_permissions);
|
||||
$this->drupalLogin($this->account);
|
||||
|
||||
// Create a profile.
|
||||
$this->createProfile(array(
|
||||
'data' => array(
|
||||
'autocomplete' => array(
|
||||
'charLimit' => LINKIT_CHAR_LIMIT,
|
||||
'wait' => LINKIT_WAIT,
|
||||
'remoteTimeout' => LINKIT_REMOTE_TIMEOUT,
|
||||
),
|
||||
),
|
||||
));
|
||||
|
||||
// Go to the edit page.
|
||||
$this->drupalGet('admin/config/content/linkit/list/' . $this->_profile->name . '/edit');
|
||||
$this->assertResponse(200);
|
||||
|
||||
$profile_update = array(
|
||||
'admin_title' => 'Test Profile updated',
|
||||
'admin_description' => 'This is a updated description for the Test Profile updated.',
|
||||
'data[autocomplete][charLimit]' => 4,
|
||||
'data[autocomplete][wait]' => 3500,
|
||||
'data[autocomplete][remoteTimeout]' => 1000,
|
||||
);
|
||||
// Set some new values for the profile.
|
||||
$this->drupalPost(NULL, $profile_update, t('Save'));
|
||||
|
||||
// Go to the edit page again.
|
||||
$this->drupalGet('admin/config/content/linkit/list/' . $this->_profile->name . '/edit');
|
||||
$this->assertResponse(200);
|
||||
|
||||
// Test that the given values are saved.
|
||||
foreach ($profile_update as $form_key => $value) {
|
||||
$this->assertFieldByName($form_key, $value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a profile.
|
||||
*/
|
||||
public function testDeleteProfile() {
|
||||
// Create a user and login.
|
||||
$this->account = $this->drupalCreateUser($this->admin_permissions);
|
||||
$this->drupalLogin($this->account);
|
||||
|
||||
// Create a profile that we will delete.
|
||||
$this->createProfile();
|
||||
|
||||
// Delete the created profile.
|
||||
$this->drupalPost('/admin/config/content/linkit/list/' . $this->_profile->name . '/delete', array(), t('Delete'));
|
||||
|
||||
$this->assertRaw(t('The item has been deleted.'), 'The profile was deleted.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a profile that doesn't exists.
|
||||
*/
|
||||
public function testLoadNonExistingProfile() {
|
||||
// Load a profile that doesn't exists.
|
||||
$loaded_profile = linkit_profile_load('my_none_existing_profile');
|
||||
|
||||
$this->assertFalse($loaded_profile, 'FALSE is returned when loading a non existing profile.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a profile that exists.
|
||||
*/
|
||||
public function testLoadProfile() {
|
||||
// Create an user account so we can create a profile.
|
||||
$this->account = $this->drupalCreateUser($this->admin_permissions);
|
||||
$this->drupalLogin($this->account);
|
||||
|
||||
// Create a profile that we will load.
|
||||
$this->createProfile();
|
||||
|
||||
// Load the saved profile.
|
||||
$loaded_profile = linkit_profile_load($this->_profile->name);
|
||||
|
||||
$this->assertTrue($loaded_profile, 'Profile was successfully loaded.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Load all profiles when no profiles exists.
|
||||
*/
|
||||
public function testLoadProfilesNoProfilesExists() {
|
||||
// We have to clear the static variables here as we have in the
|
||||
// testLoadProfilesNoProfilesExists test function asked for profiles where
|
||||
// there were no profiles.
|
||||
drupal_static_reset('ctools_export_load_object');
|
||||
drupal_static_reset('ctools_export_load_object_all');
|
||||
|
||||
// Load all profiles.
|
||||
$loaded_profile = linkit_profile_load_all();
|
||||
$this->assertIdentical($loaded_profile, array(), 'Empty array is returned when loading all profiles and there are no profiles.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Load all profiles.
|
||||
*/
|
||||
public function testLoadAllProfiles() {
|
||||
// Create an user account so we can create profiles.
|
||||
$this->account = $this->drupalCreateUser($this->admin_permissions);
|
||||
$this->drupalLogin($this->account);
|
||||
|
||||
// Create profiles that we will load.
|
||||
$this->createProfile(array('name' => 'test_1'));
|
||||
$this->createProfile(array('name' => 'test_2'));
|
||||
$this->createProfile(array('name' => 'test_3'));
|
||||
|
||||
// We have to clear the static variables here as we have in the
|
||||
// testLoadProfilesNoProfilesExists test function asked for profiles where
|
||||
// there were no profiles.
|
||||
drupal_static_reset('ctools_export_load_object');
|
||||
drupal_static_reset('ctools_export_load_object_all');
|
||||
|
||||
// Load all profiles.
|
||||
$loaded_profiles = linkit_profile_load_all();
|
||||
$this->assertEqual(count($loaded_profiles), 3, 'All profile were successfully loaded.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Tests for Linkit module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Search plugin test helper class.
|
||||
*/
|
||||
abstract class LinkitsearchPluginTestCase extends LinkitTestCase {
|
||||
/**
|
||||
* The string to search for when doing autocomplete calls.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $search_string = 'lorem';
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Tests for Linkit search plugin node.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Test the the node search plugin.
|
||||
*/
|
||||
class LinkitsearchPluginNodeTestCase extends LinkitsearchPluginTestCase {
|
||||
|
||||
/**
|
||||
* Definition.
|
||||
*/
|
||||
public static function getInfo() {
|
||||
return array(
|
||||
'name' => 'Linkit Search plugin (Node)',
|
||||
'description' => 'Test the the node search plugin.',
|
||||
'group' => 'Linkit'
|
||||
);
|
||||
}
|
||||
|
||||
function setUp($extra_modules = array()) {
|
||||
parent::setUp($extra_modules);
|
||||
|
||||
// Create a basic profile.
|
||||
$this->createProfile(array(
|
||||
'data' => array(
|
||||
'search_plugins' => array(
|
||||
'entity:node' => array(
|
||||
'enabled' => 1,
|
||||
'weight' => 0,
|
||||
),
|
||||
),
|
||||
'entity:node' => array(
|
||||
'result_description' => '',
|
||||
'bundles' => array(),
|
||||
'group_by_bundle' => 0,
|
||||
'include_unpublished' => 0,
|
||||
),
|
||||
'insert_plugin' => array(
|
||||
'url_method' => LINKIT_URL_METHOD_RAW,
|
||||
),
|
||||
)
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that we get results back which is valid.
|
||||
*/
|
||||
public function testBasicResults() {
|
||||
// Create some nodes.
|
||||
$node_1 = $this->drupalCreateNode(array('type' => 'article', 'title' => $this->search_string . $this->randomName()));
|
||||
$node_2 = $this->drupalCreateNode(array('type' => 'article', 'title' => $this->search_string . $this->randomName()));
|
||||
$node_3 = $this->drupalCreateNode(array('type' => 'page', 'title' => $this->search_string . $this->randomName()));
|
||||
$node_4 = $this->drupalCreateNode(array('type' => 'page', 'title' => $this->search_string . $this->randomName()));
|
||||
|
||||
// Call the autocomplete helper method.
|
||||
$this->autocompleteCall();
|
||||
|
||||
// Assert that the node titles appears in the response.
|
||||
$this->assertRaw($node_1->title, 'Node was found in the result array.');
|
||||
$this->assertRaw($node_2->title, 'Node was found in the result array.');
|
||||
$this->assertRaw($node_3->title, 'Node was found in the result array.');
|
||||
$this->assertRaw($node_4->title, 'Node was found in the result array.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test how node states are handled.
|
||||
*/
|
||||
public function testUnpublishedItems() {
|
||||
// Create some nodes.
|
||||
$node_1 = $this->drupalCreateNode(array('type' => 'article', 'title' => $this->search_string . $this->randomName(), 'status' => NODE_NOT_PUBLISHED));
|
||||
$node_2 = $this->drupalCreateNode(array('type' => 'page', 'title' => $this->search_string . $this->randomName(), 'status' => NODE_NOT_PUBLISHED));
|
||||
|
||||
// Call the autocomplete helper method.
|
||||
$this->autocompleteCall();
|
||||
|
||||
// Assert that the unpublished node title doesn't appear in the response.
|
||||
$this->assertNoRaw($node_1->title, 'Unpublished node was not found in the result array.');
|
||||
$this->assertNoRaw($node_2->title, 'Unpublished node was not found in the result array.');
|
||||
|
||||
// Tell the profile to include unpublished nodes.
|
||||
$this->_profile->data['entity:node']['include_unpublished'] = 1;
|
||||
$this->updateProfile();
|
||||
|
||||
// In order to see unpublished nodes in the result, the user must also have
|
||||
// permission to see unpublished nodes.
|
||||
// We give the user 'bypass node access' permission as we don't crated the
|
||||
// nodes with this user.
|
||||
$this->account = $this->drupalCreateUser(array('bypass node access'));
|
||||
$this->drupalLogin($this->account);
|
||||
|
||||
// Call the autocomplete helper method.
|
||||
$this->autocompleteCall();
|
||||
|
||||
// Assert that the unpublished title appears in the response.
|
||||
$this->assertRaw($node_1->title, 'Unpublished node was found in the result array.');
|
||||
$this->assertRaw($node_2->title, 'Unpublished node was found in the result array.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test group by bundle.
|
||||
*
|
||||
* The actual grouping is made by the javascript later on.
|
||||
* We just test that the resons contains group info.
|
||||
*/
|
||||
public function testGroupbyBundle() {
|
||||
// Create some nodes.
|
||||
$node_1 = $this->drupalCreateNode(array('type' => 'article', 'title' => $this->search_string . $this->randomName()));
|
||||
$node_2 = $this->drupalCreateNode(array('type' => 'page', 'title' => $this->search_string . $this->randomName()));
|
||||
|
||||
// Get the bundle names.
|
||||
$node_type_name_1 = node_type_get_name($node_1);
|
||||
$node_type_name_2 = node_type_get_name($node_2);
|
||||
|
||||
// Update the profile to group by bundle.
|
||||
$this->_profile->data['entity:node']['group_by_bundle'] = 1;
|
||||
$this->updateProfile();
|
||||
|
||||
// Call the autocomplete helper method.
|
||||
$this->autocompleteCall();
|
||||
|
||||
// Assert that the bundle title appear in the response.
|
||||
$this->assertRaw('"group":"Node - ' . $node_type_name_1, 'Bundle name in group was found in the result array.');
|
||||
$this->assertRaw('"group":"Node - ' . $node_type_name_2, 'Bundle name in group was found in the result array.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test bundle filter.
|
||||
*/
|
||||
public function testBundleFilter() {
|
||||
// Create some nodes.
|
||||
$node_1 = $this->drupalCreateNode(array('type' => 'article', 'title' => $this->search_string . $this->randomName()));
|
||||
$node_2 = $this->drupalCreateNode(array('type' => 'page', 'title' => $this->search_string . $this->randomName()));
|
||||
|
||||
// Update the profile to filter by bundle.
|
||||
$this->_profile->data['entity:node']['bundles'] = array(
|
||||
'article' => 'article',
|
||||
);
|
||||
$this->updateProfile();
|
||||
|
||||
// Call the autocomplete helper method.
|
||||
$res = $this->autocompleteCall();
|
||||
|
||||
// Assert that the node title from the bundle included in the filter appear in the response.
|
||||
$this->assertRaw($node_1->title, 'Node that is included in the filter was found in the result array.');
|
||||
|
||||
// Assert that the node title from the bundle that is not included in the filter doesnät appear in the response.
|
||||
$this->assertNoRaw($node_2->title, 'Node that is not included in the filter was not found in the result array.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test result description.
|
||||
*
|
||||
* We just test one token.
|
||||
*/
|
||||
public function testDescription() {
|
||||
// Create some nodes.
|
||||
$node_1 = $this->drupalCreateNode(array('type' => 'article', 'title' => $this->search_string . $this->randomName()));
|
||||
$node_2 = $this->drupalCreateNode(array('type' => 'page', 'title' => $this->search_string . $this->randomName()));
|
||||
|
||||
// Update the profile with a user result description.
|
||||
$this->_profile->data['entity:node']['result_description'] = 'Created [node:created:raw]';
|
||||
$this->updateProfile();
|
||||
|
||||
// Call the autocomplete helper method.
|
||||
$this->autocompleteCall();
|
||||
|
||||
// Check that the result description appers in the result.
|
||||
$this->assertRaw('Created ' . $node_1->created, 'The result description was found in the result array.');
|
||||
$this->assertRaw('Created ' . $node_2->created, 'The result description was found in the result array.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Tests for Linkit search plugin taxonomy term.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Test the the taxonomy term search plugin.
|
||||
*/
|
||||
class LinkitsearchPluginTaxonomyTermTestCase extends LinkitsearchPluginTestCase {
|
||||
|
||||
/**
|
||||
* Definition.
|
||||
*/
|
||||
public static function getInfo() {
|
||||
return array(
|
||||
'name' => 'Linkit Search plugin (Taxonomy term)',
|
||||
'description' => 'Test the the taxonomy term search plugin.',
|
||||
'group' => 'Linkit'
|
||||
);
|
||||
}
|
||||
|
||||
function setUp($extra_modules = array()) {
|
||||
parent::setUp(array('taxonomy'));
|
||||
|
||||
// Create a basic profile.
|
||||
$this->createProfile(array(
|
||||
'data' => array(
|
||||
'search_plugins' => array(
|
||||
'entity:taxonomy_term' => array(
|
||||
'enabled' => 1,
|
||||
'weight' => 0,
|
||||
),
|
||||
),
|
||||
'entity:taxonomy_term' => array(
|
||||
'result_description' => '',
|
||||
'bundles' => array(),
|
||||
'group_by_bundle' => 0,
|
||||
),
|
||||
'insert_plugin' => array(
|
||||
'url_method' => LINKIT_URL_METHOD_RAW,
|
||||
),
|
||||
)
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and returns a new vocabulary.
|
||||
*/
|
||||
function createVocabulary() {
|
||||
// Create a vocabulary.
|
||||
$vocabulary = new stdClass();
|
||||
$vocabulary->name = $this->randomName();
|
||||
$vocabulary->description = $this->randomName();
|
||||
$vocabulary->machine_name = drupal_strtolower($this->randomName());
|
||||
$vocabulary->help = '';
|
||||
$vocabulary->nodes = array('article' => 'article');
|
||||
$vocabulary->weight = mt_rand(0, 10);
|
||||
taxonomy_vocabulary_save($vocabulary);
|
||||
return $vocabulary;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and returns a new term in vocabulary $vid.
|
||||
*/
|
||||
function createTerm($vocabulary) {
|
||||
$term = new stdClass();
|
||||
$term->name = $this->search_string . $this->randomName();
|
||||
$term->description = $this->randomName();
|
||||
// Use the first available text format.
|
||||
$term->format = 'plain_text';
|
||||
$term->vid = $vocabulary->vid;
|
||||
taxonomy_term_save($term);
|
||||
return $term;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that we get results back which is valid.
|
||||
*/
|
||||
public function testBasicResults() {
|
||||
// Create demo vocabulary.
|
||||
$vocabulary = $this->createVocabulary();
|
||||
|
||||
// Create demo terms.
|
||||
$term_1 = $this->createTerm($vocabulary);
|
||||
$term_2 = $this->createTerm($vocabulary);
|
||||
$term_3 = $this->createTerm($vocabulary);
|
||||
|
||||
// Call the autocomplete helper method.
|
||||
$this->autocompleteCall();
|
||||
|
||||
// Check that the term names appears in the response.
|
||||
$this->assertRaw($term_1->name, 'Term was found in the result array.');
|
||||
$this->assertRaw($term_2->name, 'Term was found in the result array.');
|
||||
$this->assertRaw($term_3->name, 'Term was found in the result array.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test group by bundle.
|
||||
*
|
||||
* The actual grouping is made by the javascript later on.
|
||||
* We just test that the resons contains group info.
|
||||
*/
|
||||
public function testGroupbyBundle() {
|
||||
// Create demo vocabulary.
|
||||
$vocabulary_1 = $this->createVocabulary();
|
||||
$vocabulary_2 = $this->createVocabulary();
|
||||
|
||||
// Create demo terms.
|
||||
$term_1 = $this->createTerm($vocabulary_1);
|
||||
$term_2 = $this->createTerm($vocabulary_1);
|
||||
$term_3 = $this->createTerm($vocabulary_2);
|
||||
$term_4 = $this->createTerm($vocabulary_2);
|
||||
|
||||
// Update the profile to group by bundle.
|
||||
$this->_profile->data['entity:taxonomy_term']['group_by_bundle'] = 1;
|
||||
$this->updateProfile();
|
||||
|
||||
// Call the autocomplete helper method.
|
||||
$this->autocompleteCall();
|
||||
|
||||
// Assert that the bundle title appear in the response.
|
||||
$this->assertRaw('"group":"Taxonomy term - ' . $vocabulary_1->name, 'Bundle name in group was found in the result array.');
|
||||
$this->assertRaw('"group":"Taxonomy term - ' . $vocabulary_1->name, 'Bundle name in group was found in the result array.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test bundle filter.
|
||||
*/
|
||||
public function testBundleFilter() {
|
||||
// Create demo vocabulary.
|
||||
$vocabulary_1 = $this->createVocabulary();
|
||||
$vocabulary_2 = $this->createVocabulary();
|
||||
|
||||
// Create demo terms.
|
||||
$term_1 = $this->createTerm($vocabulary_1);
|
||||
$term_2 = $this->createTerm($vocabulary_2);
|
||||
|
||||
// Update the profile to filter by bundle.
|
||||
$this->_profile->data['entity:taxonomy_term']['bundles'] = array(
|
||||
$vocabulary_1->machine_name => $vocabulary_1->machine_name,
|
||||
);
|
||||
$this->updateProfile();
|
||||
|
||||
// Call the autocomplete helper method.
|
||||
$this->autocompleteCall();
|
||||
|
||||
// Assert that the term title from the bundle included in the filter appear in the response.
|
||||
$this->assertRaw($term_1->name, 'Term that is included in the filter was found in the result array.');
|
||||
|
||||
// Assert that the term title from the bundle that is not included in the filter doesnät appear in the response.
|
||||
$this->assertNoRaw($term_2->name, 'Term that is not included in the filter was not found in the result array.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test result description.
|
||||
*
|
||||
* We just test one token.
|
||||
*/
|
||||
public function testDescription() {
|
||||
// Create demo vocabulary.
|
||||
$vocabulary_1 = $this->createVocabulary();
|
||||
$vocabulary_2 = $this->createVocabulary();
|
||||
|
||||
// Create demo terms.
|
||||
$term_1 = $this->createTerm($vocabulary_1);
|
||||
$term_2 = $this->createTerm($vocabulary_2);
|
||||
|
||||
// Update the profile with a taxonomy_term result description.
|
||||
$this->_profile->data['entity:taxonomy_term']['result_description'] = 'Term [term:vocabulary]';
|
||||
$this->updateProfile();
|
||||
|
||||
// Call the autocomplete helper method.
|
||||
$this->autocompleteCall();
|
||||
|
||||
// Check that the result description appers in the result.
|
||||
$this->assertRaw('Term ' . $vocabulary_1->name, 'The result description was found in the result array.');
|
||||
$this->assertRaw('Term ' . $vocabulary_2->name, 'The result description was found in the result array.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Tests for Linkit search plugin user.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Test the the user search plugin.
|
||||
*/
|
||||
class LinkitsearchPluginUserTestCase extends LinkitsearchPluginTestCase {
|
||||
|
||||
/**
|
||||
* Definition.
|
||||
*/
|
||||
public static function getInfo() {
|
||||
return array(
|
||||
'name' => 'Linkit Search plugin (User)',
|
||||
'description' => 'Test the the user search plugin.',
|
||||
'group' => 'Linkit'
|
||||
);
|
||||
}
|
||||
|
||||
function setUp($extra_modules = array()) {
|
||||
parent::setUp($extra_modules);
|
||||
|
||||
// Create a basic profile.
|
||||
$this->createProfile(array(
|
||||
'data' => array(
|
||||
'search_plugins' => array(
|
||||
'entity:user' => array(
|
||||
'enabled' => 1,
|
||||
'weight' => 0,
|
||||
),
|
||||
),
|
||||
'entity:user' => array(
|
||||
'result_description' => '',
|
||||
),
|
||||
'insert_plugin' => array(
|
||||
'url_method' => LINKIT_URL_METHOD_RAW,
|
||||
),
|
||||
)
|
||||
));
|
||||
|
||||
// Users need the 'access user profiles' permission to search for users.
|
||||
$this->account = $this->drupalCreateUser(array('access user profiles'));
|
||||
$this->drupalLogin($this->account);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a user.
|
||||
*
|
||||
* We can't use drupalCreateUser() as we want to set specific usernames.
|
||||
*/
|
||||
protected function createUser($extra = array()) {
|
||||
// Create a user assigned to that role.
|
||||
$edit = array();
|
||||
$edit += $extra;
|
||||
if (!isset($edit['name'])) {
|
||||
$edit['name'] = $this->randomName();
|
||||
}
|
||||
|
||||
$edit['mail'] = $edit['name'] . '@example.com';
|
||||
$edit['pass'] = user_password();
|
||||
$edit['status'] = 1;
|
||||
|
||||
$account = user_save(drupal_anonymous_user(), $edit);
|
||||
|
||||
$this->assertTrue(!empty($account->uid), t('User created with name %name and pass %pass', array('%name' => $edit['name'], '%pass' => $edit['pass'])), t('User login'));
|
||||
if (empty($account->uid)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Add the raw password so that we can log in as this user.
|
||||
$account->pass_raw = $edit['pass'];
|
||||
return $account;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that we get results back which is valid.
|
||||
*/
|
||||
public function testBasicResults() {
|
||||
// We don't use the drupalCreateUser() as we want to set specific usernames.
|
||||
$user_1 = $this->createUser(array('name' => $this->search_string . $this->randomName()));
|
||||
$user_2 = $this->createUser(array('name' => $this->search_string . $this->randomName()));
|
||||
$user_3 = $this->createUser(array('name' => $this->search_string . $this->randomName()));
|
||||
|
||||
// Call the autocomplete helper method.
|
||||
$this->autocompleteCall();
|
||||
|
||||
// Check that the usernames appears in the response.
|
||||
$this->assertRaw($user_1->name, 'Username was found in the result array.');
|
||||
$this->assertRaw($user_2->name, 'Username was found in the result array.');
|
||||
$this->assertRaw($user_3->name, 'Username was found in the result array.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test result description.
|
||||
*
|
||||
* We just test one token.
|
||||
*/
|
||||
public function testDescription() {
|
||||
// We don't use the drupalCreateUser() as we want to set specific usernames.
|
||||
$user_1 = $this->createUser(array('name' => $this->search_string . $this->randomName()));
|
||||
$user_2 = $this->createUser(array('name' => $this->search_string . $this->randomName()));
|
||||
|
||||
// Update the profile with a user result description.
|
||||
$this->_profile->data['entity:user']['result_description'] = 'Created [user:created:raw]';
|
||||
$this->updateProfile();
|
||||
|
||||
// Call the autocomplete helper method.
|
||||
$this->autocompleteCall();
|
||||
|
||||
// Check that the result description appers in the result.
|
||||
$this->assertRaw('Created ' . $user_1->created, 'The result description was found in the result array.');
|
||||
$this->assertRaw('Created ' . $user_2->created, 'The result description was found in the result array.');
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
Reference in New Issue
Block a user