Skip to content
Snippets Groups Projects
Commit a965edeb authored by Chris Gross's avatar Chris Gross
Browse files

7.x-1.14 Release Candidate 1

parent 187759e5
No related branches found
Tags 7.x-1.14-rc1
No related merge requests found
Showing
with 3409 additions and 0 deletions
WCM Base 7.x-1.14-rc1, 2019-04-03
---------------------------------
- WCM Base: Added WCM Timeline custom module.
- WCM Omega: Added styles for WCM Timeline.
WCM Base 7.x-1.13, 2019-03-28
-----------------------------
- WCM Base: Updated Panopoly to 1.65 per SA-CONTRIB-2019-042.
......
......@@ -40,6 +40,7 @@ projects[ocio_wysiwyg][options][working-copy] = TRUE
projects[wcm_front_page][options][working-copy] = TRUE
projects[wcm_media_gallery][options][working-copy] = TRUE
projects[wcm_tile_panes][options][working-copy] = TRUE
projects[wcm_timeline][options][working-copy] = TRUE
projects[wcm_user_contact][options][working-copy] = TRUE
projects[wcm_user_leadership][options][working-copy] = TRUE
projects[wcm_user_profile][options][working-copy] = TRUE
......
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.
<?php
/**
* @file
* Entity implementation for the paragraphs entity.
*/
/**
* Class for paragraphs_item entities.
*/
class ParagraphsItemEntity extends Entity {
/**
* paragraphs field info.
*
* @var array
*/
protected $fieldInfo;
/**
* The host entity object.
*
* @var object
*/
protected $hostEntity;
/**
* The host entity ID.
*
* @var integer
*/
protected $hostEntityId;
/**
* The host entity revision ID if this is not the default revision.
*
* @var integer
*/
protected $hostEntityRevisionId;
/**
* The host entity type.
*
* @var string
*/
protected $hostEntityType;
/**
* The host entity bundle.
*
* @var string
*/
protected $hostEntityBundle;
/**
* The language under which the paragraphs item is stored.
*
* @var string
*/
protected $langcode = LANGUAGE_NONE;
/**
* Entity ID.
*
* @var integer
*/
public $item_id;
/**
* paragraphs revision ID.
*
* @var integer
*/
public $revision_id;
/**
* The name of the paragraphs field this item is associated with.
*
* @var string
*/
public $field_name;
/**
* Whether this revision is the default revision.
*
* @var bool
*/
public $default_revision = TRUE;
/**
* Whether the paragraphs item is archived, i.e. not in use.
*
* @see ParagraphsItemEntity::isInUse()
* @var bool
*/
public $archived = FALSE;
/**
* The name of the paragraphs field this item is associated with.
*
* @var string
*/
private $fetchedHostEntityDetails = NULL;
/**
* Constructs the entity object.
*/
public function __construct(array $values = array(), $entityType = NULL) {
parent::__construct($values, 'paragraphs_item');
if (isset($this->field_name)) {
// Ok, we have the field name property, we can proceed and check the field's type
$field_info = $this->fieldInfo();
// Check if we have field info, if not, our field is deleted.
if (!$field_info) {
return FALSE;
}
// We only allow paragraphs type field for this entity.
if ($field_info['type'] != 'paragraphs') {
throw new Exception("Invalid field name given: {$this->field_name} is not a paragraphs field.");
}
}
}
/**
* Provides info about the field on the host entity, which embeds this
* paragraphs item.
*/
public function fieldInfo() {
return field_info_field($this->field_name);
}
/**
* Provides info of the field instance containing the reference to this
* paragraphs item.
*/
public function instanceInfo() {
return field_info_instance($this->hostEntityType(), $this->field_name, $this->hostEntityBundle());
}
/**
* Returns the field instance label translated to interface language.
*/
public function translatedInstanceLabel($langcode = NULL) {
if ($info = $this->instanceInfo()) {
if (module_exists('i18n_field')) {
return i18n_string("field:{$this->field_name}:{$info['bundle']}:label", $info['label'], array('langcode' => $langcode));
}
return $info['label'];
}
}
/**
* Specifies the default label, which is picked up by label() by default.
*/
public function defaultLabel() {
if ($this->fetchHostDetails()) {
// Don't show a label, our parent field already shows an label
// If the user decides he wants to show one.
return '';
}
// Should only happen when there is something wrong
return t('Unconnected paragraphs item');
}
/**
* Returns the path used to view the entity.
*/
public function path() {
return;
}
/**
* Returns the URI as returned by entity_uri().
*/
public function defaultUri() {
return array(
'path' => $this->path(),
);
}
/**
* Sets the host entity. Only possible during creation of a item.
*
* @param $entity_type
* The entity type of the host.
* @param $entity
* The host entity.
* @param $langcode
* (optional) The field language code we should use for host entity.
* @param $create_link
* (optional) Whether a field-item linking the host entity to the field
* collection item should be created.
* @throws Exception
* When you try to set the host when the item has already been created.
*/
public function setHostEntity($entity_type, $entity, $langcode = LANGUAGE_NONE, $create_link = TRUE) {
$this->hostEntityType = $entity_type;
$this->hostEntity = $entity;
$this->langcode = $langcode;
list($this->hostEntityId, $this->hostEntityRevisionId, $this->hostEntityBundle) = entity_extract_ids($this->hostEntityType, $this->hostEntity);
// If the host entity is not saved yet, set the id to FALSE. So
// fetchHostDetails() does not try to load the host entity details.
if (!isset($this->hostEntityId)) {
$this->hostEntityId = FALSE;
}
// We are create a new paragraphs for a non-default entity, thus
// set archived to TRUE.
if (!entity_revision_is_default($entity_type, $entity)) {
$this->hostEntityId = FALSE;
$this->archived = TRUE;
}
if ($create_link) {
$entity->{$this->field_name}[$this->langcode][] = array('entity' => $this);
}
$this->fetchedHostEntityDetails = TRUE;
}
/**
* Function to force change the host entity of this paragraph item.
*
* @param $entity
* The entity to force the host to.
*/
public function forceHostEntity($entity) {
$this->hostEntity = $entity;
}
/**
* Function to force change the host entity type of this paragraph item.
*
* @param $entity_type
* The entity type to force the host to.
*/
public function forceHostEntityType($entity_type) {
$this->hostEntityType = $entity_type;
}
/**
* Returns the host entity, which embeds this paragraph item.
*/
public function hostEntity() {
$this->fetchHostDetails();
return $this->hostEntity;
}
/**
* Returns the entity type of the host entity, which embeds this
* paragraph item.
*/
public function hostEntityType() {
return $this->hostEntityType;
}
/**
* Returns the id of the host entity, which embeds this paragraph item.
*/
public function hostEntityId() {
return $this->hostEntityId;
}
/**
* Returns the revisions id of the host entity, which embeds this paragraph item.
*/
public function hostEntityRevisionId() {
return $this->hostEntityRevisionId;
}
/**
* Returns the bundle of the host entity, which embeds this paragraphs
* item.
*/
public function hostEntityBundle() {
return $this->hostEntityBundle;
}
/**
* Fetches details of the host and saves it in the entity.
*
* @return bool
* Whether the fetching was successful.
*/
protected function fetchHostDetails() {
if ($this->fetchedHostEntityDetails === NULL) {
if ($this->item_id) {
// For saved paragraphs, query the field data to determine the
// right host entity.
$query = new EntityFieldQuery();
$query->fieldCondition($this->fieldInfo(), 'revision_id', $this->revision_id);
if (!$this->isInUse()) {
$query->age(FIELD_LOAD_REVISION);
}
$result = $query->execute();
list($this->hostEntityType, $data) = each($result);
if ($data) {
$data_values = array_shift($data);
$host_entity_info = entity_get_info($this->hostEntityType);
$host_entity_keys = $host_entity_info['entity keys'];
$this->hostEntityId = (isset($data_values->{$host_entity_keys['id']}) ? $data_values->{$host_entity_keys['id']} : NULL);
$this->hostEntityRevisionId = (isset($data_values->{$host_entity_keys['revision']}) ? $data_values->{$host_entity_keys['revision']} : NULL);
$this->hostEntityBundle = (isset($data_values->{$host_entity_keys['bundle']}) ? $data_values->{$host_entity_keys['bundle']} : NULL);
if ($this->isInUse()) {
$this->hostEntity = entity_load_single($this->hostEntityType, $this->hostEntityId);
}
elseif ($this->hostEntityRevisionId) {
$this->hostEntity = entity_revision_load($this->hostEntityType, $this->hostEntityRevisionId);
}
$this->fetchedHostEntityDetails = TRUE;
}
else {
$this->hostEntityId = FALSE;
$this->hostEntityRevisionId = FALSE;
$this->hostEntityBundle = FALSE;
$this->fetchedHostEntityDetails = FALSE;
}
}
else {
// No host entity available yet.
$this->hostEntityId = FALSE;
$this->hostEntityRevisionId = FALSE;
$this->hostEntityBundle = FALSE;
$this->fetchedHostEntityDetails = FALSE;
}
}
return $this->fetchedHostEntityDetails;
}
/**
* Determines the $delta of the reference pointing to this paragraph
* item.
*/
public function delta() {
if (($entity = $this->hostEntity()) && isset($entity->{$this->field_name})) {
foreach ($entity->{$this->field_name} as $langcode => &$data) {
foreach ($data as $delta => $item) {
if (isset($item['value']) && $item['value'] == $this->item_id) {
$this->langcode = $langcode;
return $delta;
}
elseif (isset($item['entity']) && $item['entity'] === $this) {
$this->langcode = $langcode;
return $delta;
}
}
}
}
}
/**
* Determines the language code under which the item is stored.
*/
public function langcode() {
if ($this->delta() !== NULL) {
return $this->langcode;
}
}
/**
* Determines whether this paragraphs item revision is in use.
*
* paragraphs items may be contained in from non-default host entity
* revisions. If the paragraphs item does not appear in the default
* host entity revision, the item is actually not used by default and so
* marked as 'archived'.
* If the paragraphs item appears in the default revision of the host
* entity, the default revision of the paragraphs item is in use there
* and the collection is not marked as archived.
*/
public function isInUse() {
return $this->default_revision && !$this->archived;
}
/**
* Save the paragraphs item.
*
* By default, always save the host entity, so modules are able to react
* upon changes to the content of the host and any 'last updated' dates of
* entities get updated.
*
* For creating an item a host entity has to be specified via setHostEntity()
* before this function is invoked. For the link between the entities to be
* fully established, the host entity object has to be updated to include a
* reference on this paragraphs item during saving. So do not skip
* saving the host for creating items.
*
* @param $skip_host_save
* (internal) If TRUE is passed, the host entity is not saved automatically
* and therefore no link is created between the host and the item or
* revision updates might be skipped. Use with care.
*/
public function save($skip_host_save = FALSE) {
// Only save directly if we are told to skip saving the host entity. Else,
// we always save via the host as saving the host might trigger saving
// paragraphs items anyway (for example, if a new revision is created).
if ($skip_host_save) {
return entity_get_controller($this->entityType)->save($this);
}
else {
$host_entity = $this->hostEntity();
if (!$host_entity) {
throw new Exception("Unable to save a paragraph without a valid reference to a host entity.");
}
// If this is creating a new revision, also do so for the host entity.
if (!empty($this->revision) || !empty($this->is_new_revision)) {
$host_entity->revision = TRUE;
if (!empty($this->default_revision)) {
entity_revision_set_default($this->hostEntityType, $host_entity);
}
}
// Set the host entity reference, so the item will be saved with the host.
// @see paragraphs_field_presave()
$delta = $this->delta();
if (isset($delta)) {
$host_entity->{$this->field_name}[$this->langcode][$delta] = array('entity' => $this);
}
else {
$host_entity->{$this->field_name}[$this->langcode][] = array('entity' => $this);
}
return entity_save($this->hostEntityType, $host_entity);
}
}
/**
* Deletes the host entity's reference of the paragraphs item.
*/
protected function deleteHostEntityReference() {
$delta = $this->delta();
if ($this->item_id && isset($delta)) {
unset($this->hostEntity->{$this->field_name}[$this->langcode][$delta]);
entity_save($this->hostEntityType, $this->hostEntity);
}
}
/**
* Intelligently delete a paragraphs item revision.
*
* If a host entity is revisioned with its paragraphs items, deleting
* a paragraphs item on the default revision of the host should not
* delete the collection item from archived revisions too. Instead, we delete
* the current default revision and archive the paragraph.
*
*/
public function deleteRevision($skip_host_update = FALSE) {
if (!$this->revision_id) {
return;
}
if (!$skip_host_update) {
// Just remove the item from the host, which cares about deleting the
// item (depending on whether the update creates a new revision).
$this->deleteHostEntityReference();
}
if (!$this->isDefaultRevision()) {
entity_revision_delete('paragraphs_item', $this->revision_id);
}
// If deleting the default revision, take care!
else {
$row = db_select('paragraphs_item_revision', 'r')
->fields('r')
->condition('item_id', $this->item_id)
->condition('revision_id', $this->revision_id, '<>')
->execute()
->fetchAssoc();
// If no other revision is left, delete. Else archive the item.
if (!$row) {
$this->delete();
}
else {
// Make the other revision the default revision and archive the item.
db_update('paragraphs_item')
->fields(array('archived' => 1, 'revision_id' => $row['revision_id']))
->condition('item_id', $this->item_id)
->execute();
entity_get_controller('paragraphs_item')->resetCache(array($this->item_id));
entity_revision_delete('paragraphs_item', $this->revision_id);
}
}
}
/**
* Export the paragraphs item.
*
* Since paragraphs entities are not directly exportable (that is, do not
* have 'exportable' set to TRUE in hook_entity_info()) and since Features
* calls this method when exporting the paragraphs as a field attached
* to another entity, we return the export in the format expected by
* Features, rather than in the normal Entity::export() format.
*/
public function export($prefix = '') {
// Based on code in EntityDefaultFeaturesController::export_render().
$export = "entity_import('" . $this->entityType() . "', '";
$export .= addcslashes(parent::export(), '\\\'');
$export .= "')";
return $export;
}
/**
* Ensure file fields on the entity have their URIs loaded for previews.
* Copied from #1447338-7, thanks!
*/
public function view($view_mode = 'full', $langcode = NULL, $page = NULL) {
if ($langcode == NULL) {
$langcode = LANGUAGE_NONE;
}
// Iterate over fields in the collection to add URIs for image fields.
$field_instances = field_info_instances($this->entityType, $this->bundle);
foreach ($field_instances as $field_name => $field) {
$info = field_info_field($field_name);
if (is_array($info) && $info['type'] == 'image' && $image_field = &$this->{$field_name}) {
// Add the URI to the field on the entity for display.
if (isset($image_field[$langcode])) {
foreach ($image_field[$langcode] as &$field_to_be_updated) {
if (!isset($field_to_be_updated['uri']) && isset($field_to_be_updated['fid'])) {
$image = file_load($field_to_be_updated['fid']);
if ($image) {
$field_to_be_updated['uri'] = $image->uri;
}
}
}
}
}
}
return entity_get_controller($this->entityType)->view(array($this), $view_mode, $langcode, $page);
}
/**
* Magic method to only serialize what's necessary.
*/
public function __sleep() {
$vars = get_object_vars($this);
unset($vars['entityInfo'], $vars['idKey'], $vars['nameKey'], $vars['statusKey']);
unset($vars['fieldInfo']);
// Also do not serialize the host entity.
// We add our hostEntity in code.
unset($vars['hostEntity']);
// We unset our host entity, we have to let our object know.
unset($vars['fetchedHostEntityDetails']);
// Also key the returned array with the variable names so the method may
// be easily overridden and customized.
return drupal_map_assoc(array_keys($vars));
}
/**
* Magic method to invoke setUp() on unserialization.
*
* @todo: Remove this once it appears in a released entity API module version.
*/
public function __wakeup() {
$this->setUp();
}
}
<?php
/**
* @file
* Entity metadata implementation for the paragraphs entity.
*/
/**
* Class ParagraphsItemMetadataController
*/
class ParagraphsItemMetadataController extends EntityDefaultMetadataController {
public function entityPropertyInfo() {
$info = parent::entityPropertyInfo();
$properties = &$info['paragraphs']['properties'];
$properties['field_name']['label'] = t('Field name');
$properties['field_name']['description'] = t('The machine-readable name of the paragraphs field containing this item.');
$properties['field_name']['required'] = TRUE;
$properties['host_entity'] = array(
'label' => t('Host entity'),
'type' => 'entity',
'description' => t('The entity containing the paragraphs field.'),
'getter callback' => 'paragraphs_item_get_host_entity',
'setter callback' => 'paragraphs_item_set_host_entity',
'required' => TRUE,
);
return $info;
}
}
README.txt
==========
Paragraph is a module to create paragraphs in your content.
You can create bundles(with own display and fields) as paragraph types.
When you add a paragraph field to your node/entity, you can select the allowed bundles, and when using the field, you can select a paragraph type from the allowed bundles to use different fields/display per paragraph.
This module module might be considered a clone of field_collection (on which it is based), but this module has some advantages:
* Different fields per paragraph bundle
* Using different paragraph bundles in a single paragraph field
* Displays per paragraph bundle
Bundles are exportable with features.
<?php
/**
* @file
* Provides basic class for importing Paragraphs via Migrate API.
*/
/**
* Destination class implementing migration into field_collection.
*/
class MigrateDestinationParagraphsItem extends MigrateDestinationEntity {
/**
* The bundle (node type, vocabulary, etc.) of the destination.
*
* @var string
*/
protected $field_name;
public function getFieldName() {
return $this->field_name;
}
static public function getKeySchema() {
return array(
'item_id' => array(
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'description' => 'ID of field collection item',
),
);
}
/**
* Basic initialization.
*
* @param array $options
* (optional) Options applied to collections.
*/
public function __construct($bundle, array $options = array()) {
parent::__construct('paragraphs_item', $bundle, $options);
$this->field_name = isset($options['field_name']) ? $options['field_name'] : '';
}
/**
* Return an options array for node destinations.
*
* @param string $language
* Default language for nodes created via this destination class.
* @param string $text_format
* Default text format for nodes created via this destination class.
*/
static public function options($language, $text_format) {
return compact('language', 'text_format');
}
/**
* Returns a list of fields available to be mapped for the node type (bundle)
*
* @param Migration $migration
* Optionally, the migration containing this destination.
* @return array
* Keys: machine names of the fields (to be passed to addFieldMapping)
* Values: Human-friendly descriptions of the fields.
*/
public function fields($migration = NULL) {
$fields = array();
$fields['field_name'] = t('Field name');
$fields['archived'] = t('Archived status of the paragraph item');
$fields += migrate_handler_invoke_all('Entity', 'fields', $this->entityType, $this->bundle, $migration);
$fields += migrate_handler_invoke_all('ParagraphsItem', 'fields', $this->entityType, $this->bundle, $migration);
return $fields;
}
/**
* Delete a batch of nodes at once.
*
* @param $nids
* Array of node IDs to be deleted.
*/
public function bulkRollback(array $item_ids) {
migrate_instrument_start('paragraphs_item_delete_multiple');
$this->prepareRollback($item_ids);
entity_delete_multiple('paragraphs_item', $item_ids);
$this->completeRollback($item_ids);
migrate_instrument_stop('paragraphs_item_delete_multiple');
}
/**
* Import a single node.
*
* @param $node
* Node object to build. Prefilled with any fields mapped in the Migration.
* @param $row
* Raw source data object - passed through to prepare/complete handlers.
* @return array
* Array of key fields (nid only in this case) of the node that was saved if
* successful. FALSE on failure.
*/
public function import(stdClass $paragraphs_item, stdClass $row) {
$migration = Migration::currentMigration();
// Updating previously-migrated content?
if (isset($row->migrate_map_destid1)) {
if (isset($paragraphs_item->item_id)) {
if ($paragraphs_item->item_id != $row->migrate_map_destid1) {
throw new MigrateException(t("Incoming item_id !item_id and map destination item_id !destid1 don't match",
array('!item_id' => $paragraphs_item->item_id, '!destid1' => $row->migrate_map_destid1)));
}
}
else {
$paragraphs_item->item_id = $row->migrate_map_destid1;
$paragraphs_item->is_new = FALSE;
$paragraphs_item->is_new_revision = FALSE;
// Get the existing revision_id so updates don't generate notices.
$values = db_select('paragraphs_item', 'p')
->fields('p', array('revision_id'))
->condition('item_id', $paragraphs_item->item_id)
->execute()
->fetchAssoc();
if (empty($values)) {
throw new MigrateException(t("Incoming paragraphs ID !item_id no longer exists",
array('!item_id' => $paragraphs_item->item_id)));
}
$paragraphs_item->revision_id = $values['revision_id'];
}
}
// When updating, we make sure that id exists.
if ($migration->getSystemOfRecord() == Migration::DESTINATION) {
if (!isset($paragraphs_item->item_id)) {
throw new MigrateException(t('System-of-record is DESTINATION, but no destination item_id provided'));
}
// Hold raw original values for later.
$raw_paragraph = $paragraphs_item;
// This entity will be the one, we party on.
$entity = paragraphs_item_load($paragraphs_item->item_id);
if (empty($entity)) {
throw new MigrateException(t('System-of-record is DESTINATION, but paragraphs item !item_id does not exist',
array('!item_id' => $paragraphs_item->item_id)));
}
}
// Provide defaults for SOURCE d
else {
// Set some default properties.
$defaults = array(
'language' => $this->language,
'bundle' => $this->bundle,
'field_name' => $this->field_name,
'archived' => 0,
);
foreach ($defaults as $field => $value) {
if (!isset($paragraphs_item->$field)) {
$paragraphs_item->$field = $value;
}
}
}
$this->prepare($paragraphs_item, $row);
if ($migration->getSystemOfRecord() == Migration::DESTINATION) {
foreach ($raw_paragraph as $field => $value) {
$entity->$field = $paragraphs_item->$field;
}
}
else {
// This will be the entity we party on.
$entity = entity_create('paragraphs_item', (array) $paragraphs_item);
}
if (isset($entity->item_id) && $entity->item_id) {
$updating = TRUE;
}
else {
$updating = FALSE;
}
migrate_instrument_start('paragraphs_item_save');
$entity->save(TRUE);
migrate_instrument_stop('paragraphs_item_save');
$this->complete($entity, $row);
if (isset($entity->item_id) && $entity->item_id > 0) {
$return = array($entity->item_id);
if ($updating) {
$this->numUpdated++;
}
else {
$this->numCreated++;
}
}
else {
$return = FALSE;
}
return $return;
}
}
<?php
/**
* Class ParagraphsMigrateParagraphsFieldHandler
*
* Provides migrate field handler for paragraphs field.
*/
class ParagraphsMigrateParagraphsFieldHandler extends MigrateSimpleFieldHandler {
/**
* @inheritdoc
*/
public function __construct() {
parent::__construct(array(
'value_key' => 'value',
'skip_empty' => TRUE,
));
$this->registerTypes(array('paragraphs'));
}
/**
* Provide additional fields for migration.
*
* @param $type
* @param $instance
* @param null $migration
*
* @return array
*/
public function fields($type, $instance, $migration = NULL) {
return array(
'revision_id' => t('Option: Provide optional revision id.'),
);
}
/**
* @inheritdoc
*/
protected function notNull($value) {
return !is_null($value) && $value !== FALSE;
}
/**
* @inheritdoc
*/
public function prepare($entity, array $field_info, array $instance, array $values) {
$arguments = array();
if (isset($values['arguments'])) {
$arguments = $values['arguments'];
unset($values['arguments']);
}
$language = $this->getFieldLanguage($entity, $field_info, $arguments);
// Let the derived class skip empty values.
if ($this->skipEmpty) {
$values = array_filter($values, array($this, 'notNull'));
}
// Do not proceed if we got no values.
if (empty($values)) {
return NULL;
}
$revision_ids = $this->getRevisionIds($values, $arguments);
// Setup the Field API array for saving.
$delta = 0;
foreach ($values as $value) {
if (is_array($language)) {
$current_language = $language[$delta];
}
else {
$current_language = $language;
}
$return[$current_language][] = array(
$this->fieldValueKey => $value,
'revision_id' => $revision_ids[$delta],
);
$delta++;
}
return isset($return) ? $return : NULL;
}
/**
* Helper to get set of revision ids for import.
*
* @param $values
* @param $arguments
*/
protected function getRevisionIds($values, $arguments) {
$return = array();
if (!isset($arguments['revision_id'])) {
$arguments['revision_id'] = array();
}
elseif (!is_array($arguments['revision_id'])) {
$arguments['revision_id'] = array($arguments['revision_id']);
}
$revision_ids = db_select('paragraphs_item', 'p')
->fields('p', array('item_id', 'revision_id'))
->condition('item_id', $values)
->execute()
->fetchAllKeyed();
foreach ($values as $delta => $item_id) {
// Get revision ID provided by the migration.
if (!empty($arguments['revision_id'][$delta])) {
$return[$delta] = $arguments['revision_id'][$delta];
}
// Provide latest revision id.
else {
$return[$delta] = $revision_ids[$item_id];
}
}
return $return;
}
}
name = Paragraphs Bundle Permissions
description = Add view / create / update / delete permissions for all paragraph bundles.
core = 7.x
package = Paragraphs
dependencies[] = paragraphs
; Information added by Drupal.org packaging script on 2017-02-16
version = "7.x-1.0-rc5"
core = "7.x"
project = "paragraphs"
datestamp = "1487261293"
<?php
/**
* @file
* Add view / create / update / delete permissions for all paragraph bundles.
*/
/**
* Implements hook_paragraphs_item_access().
*
* Check whether a user may perform the operation on the paragraph item.
*
* @param $entity
* Entity to check the access against.
* @param $op
* The operation to be performed on the paragraph item. Possible values are:
* - "view"
* - "update"
* - "delete"
* - "create"
* @param $account
* Optional, a user object representing the user for whom the operation is to
* be performed. Determines access for a user other than the current user.
*
* @return bool
* TRUE if the operation may be performed, FALSE otherwise.
*/
function paragraphs_bundle_permissions_paragraphs_item_access($entity, $op, $account) {
$permissions = &drupal_static(__FUNCTION__, array());
if (!in_array($op, array('view', 'update', 'delete', 'create'), TRUE)) {
// If there was no bundle to check against, or the $op was not one of the
// supported ones, we return access denied.
return PARAGRAPHS_ITEM_ACCESS_IGNORE;
}
$bundle = $entity->bundle;
// Set static cache id to use the bundle machine name.
$cid = $bundle;
// If we've already checked access for this bundle, user and op, return from
// cache.
if (isset($permissions[$account->uid][$cid][$op])) {
return $permissions[$account->uid][$cid][$op];
}
if (user_access('bypass paragraphs bundle content access', $account)) {
$permissions[$account->uid][$cid][$op] = PARAGRAPHS_ITEM_ACCESS_ALLOW;
return PARAGRAPHS_ITEM_ACCESS_ALLOW;
}
if (user_access($op . ' paragraph content ' . $bundle, $account)) {
$permissions[$account->uid][$cid][$op] = PARAGRAPHS_ITEM_ACCESS_ALLOW;
return PARAGRAPHS_ITEM_ACCESS_ALLOW;
}
$permissions[$account->uid][$cid][$op] = PARAGRAPHS_ITEM_ACCESS_DENY;
return $permissions[$account->uid][$cid][$op];
}
/**
* Implements hook_permission().
*/
function paragraphs_bundle_permissions_permission() {
$perms = array(
'bypass paragraphs bundle content access' => array(
'title' => t('Bypass paragraphs bundle content access control'),
'description' => t('Is able to administer content for all paragraph bundles'),
),
);
// Add permissions for each bundle.
$bundles = paragraphs_bundle_load();
foreach ($bundles as $machine_name => $bundle) {
$perms += array(
'view paragraph content ' .$machine_name => array(
'title' => t('%type_name: View content', array('%type_name' => $bundle->name)),
'description' => t('Is able to view paragraphs content of bundle %type_name', array('%type_name' => $bundle->name)),
),
'create paragraph content ' . $machine_name => array(
'title' => t('%type_name: Create content', array('%type_name' => $bundle->name)),
'description' => t('Is able to create paragraphs content of bundle %type_name', array('%type_name' => $bundle->name)),
),
'update paragraph content ' . $machine_name => array(
'title' => t('%type_name: Edit content', array('%type_name' => $bundle->name)),
'description' => t('Is able to update paragraphs content of bundle %type_name', array('%type_name' => $bundle->name)),
),
'delete paragraph content ' . $machine_name => array(
'title' => t('%type_name: Delete content', array('%type_name' => $bundle->name)),
'description' => t('Is able to delete paragraphs content of bundle %type_name', array('%type_name' => $bundle->name)),
),
);
}
return $perms;
}
name = Paragraphs - Internationalization
description = Allow paragraph bundles to be duplicate for translation.
core = 7.x
package = Paragraphs
dependencies[] = paragraphs
dependencies[] = replicate
dependencies[] = replicate_paragraphs
; Information added by Drupal.org packaging script on 2017-02-16
version = "7.x-1.0-rc5"
core = "7.x"
project = "paragraphs"
datestamp = "1487261293"
<?php
/**
* Implements hook_field_prepare_translation().
*
* @see field_attach_prepare_translation()
*/
function paragraphs_i18n_field_prepare_translation($entity_type, $entity, $field, $instance, $langcode, &$items, $source_entity, $source_langcode) {
list($id, , ) = entity_extract_ids($entity_type, $entity);
if (empty($id)) {
$news_items = array();
foreach ($items as $key => &$item) {
if ($current_entity = paragraphs_field_get_entity($item)) {
$current_entity = replicate_clone_entity('paragraphs_item', $current_entity);
$current_entity->setHostEntity($entity_type, $source_entity, $langcode, FALSE);
$current_entity->save(TRUE);
$news_items[] = array(
'value' => $current_entity->item_id,
'revision_id' => $current_entity->revision_id,
);
}
}
$items = $news_items;
}
}
\ No newline at end of file
<?php
/**
* @file
* Admin functions for the paragraphs module.
*/
/**
* Page callback to show the bundle overview page.
*
* @return null|string
* Rendered table of bundles.
*
* @throws Exception
*/
function paragraphs_admin_bundle_overview() {
$page = array();
$bundles = paragraphs_bundle_load();
$field_ui = module_exists('field_ui');
$header = array(
t('Bundle name'),
array('data' => t('Operations'), 'colspan' => $field_ui ? '4' : '2')
);
$rows = array();
foreach ($bundles as $bundle) {
$type_url_str = strtr($bundle->bundle, array('_' => '-'));
$row = array(
array(
'data' => format_string('@bundle_name (@bundle_machine_name)', array(
'@bundle_name' => $bundle->name,
'@bundle_machine_name' => $bundle->bundle,
)),
)
);
if ($field_ui) {
// Manage fields.
$row[] = array(
'data' => l(t('manage fields'), 'admin/structure/paragraphs/' . $type_url_str . '/fields')
);
// Display fields.
$row[] = array(
'data' => l(t('manage display'), 'admin/structure/paragraphs/' . $type_url_str . '/display')
);
}
// Manage bundle.
$row[] = array(
'data' => l(t('edit bundle'), 'admin/structure/paragraphs/' . $type_url_str . '/edit')
);
// Delete bundle.
$row[] = array(
'data' => l(t('delete bundle'), 'admin/structure/paragraphs/' . $type_url_str . '/delete')
);
$rows[$bundle->bundle] = $row;
}
// Sort rows by bundle.
ksort($rows);
// Render paragraphs bundle table.
$page['paragraphs_bundle_table'] = array(
'#theme' => 'table',
'#header' => $header,
'#rows' => $rows,
'#empty' => t('No paragraph bundles have been defined yet.'),
);
return $page;
}
/**
* Form to create or edit an paragraph bundle.
*/
function paragraphs_admin_bundle_form($form, &$form_state, $bundle = NULL) {
if (!isset($bundle) && !$bundle) {
// This is a new bundle
$bundle = new stdClass();
$bundle->name = '';
$bundle->bundle = '';
$bundle->locked = 0;
}
else {
if (!$bundle) {
drupal_set_message(t('Could not load bundle'), 'error');
drupal_goto('admin/structure/paragraphs');
}
}
$form['#paragraphs_bundle'] = $bundle;
$form['name'] = array(
'#title' => t('Name'),
'#type' => 'textfield',
'#default_value' => $bundle->name,
'#description' => t('The human-readable name of this bundle. It is recommended that this name begin with a capital letter and contain only letters, numbers, and spaces. This name must be unique.'),
'#required' => TRUE,
'#size' => 30,
);
if (!$bundle->locked) {
$form['bundle'] = array(
'#type' => 'machine_name',
'#default_value' => $bundle->bundle,
'#maxlength' => 32,
'#disabled' => $bundle->locked,
'#machine_name' => array(
'exists' => 'paragraphs_bundle_load',
),
'#description' => t('A unique machine-readable name for this paragraph bundle. It must only contain lowercase letters, numbers, and underscores.'),
);
}
$form['locked'] = array(
'#type' => 'value',
'#value' => $bundle->locked,
);
$form['actions'] = array('#type' => 'actions');
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => t('Save Paragraph bundle'),
'#weight' => 40,
);
return $form;
}
/**
* Form validation handler for paragraphs_admin_bundle_form().
*
* @see paragraphs_admin_bundle_form_submit()
*/
function paragraphs_admin_bundle_form_validate($form, &$form_state) {
$bundle = new stdClass();
$bundle->name = trim($form_state['values']['name']);
if (!$form_state['values']['locked']) {
$bundle->bundle = trim($form_state['values']['bundle']);
// 'theme' conflicts with theme_node_form().
// '0' is invalid, since elsewhere we check it using empty().
if (in_array($bundle->bundle, array('0', 'theme'))) {
form_set_error('type', t("Invalid machine-readable name. Enter a name other than %invalid.", array('%invalid' => $bundle->bundle)));
}
}
}
/**
* Submit handler for paragraphs_admin_bundle_form().
*
* @see paragraphs_admin_bundle_form()
*/
function paragraphs_admin_bundle_form_submit($form, &$form_state) {
$bundle = new stdClass();
if (!$form_state['values']['locked']) {
$bundle->bundle = trim($form_state['values']['bundle']);
}
else {
$bundle->bundle = $form['#paragraphs_bundle']->bundle;
}
$bundle->locked = 1;
$bundle->name = trim($form_state['values']['name']);
$variables = $form_state['values'];
// Remove everything that's been saved already - whatever's left is assumed
// to be a persistent variable.
foreach ($variables as $key => $value) {
if (isset($bundle->$key)) {
unset($variables[$key]);
}
}
unset($variables['form_token'], $variables['op'], $variables['submit'], $variables['delete'], $variables['reset'], $variables['form_id'], $variables['form_build_id']);
$status = paragraphs_bundle_save($bundle);
$t_args = array('%name' => $bundle->name);
if ($status == SAVED_UPDATED) {
drupal_set_message(t('The paragraph bundle %name has been updated.', $t_args));
}
elseif ($status == SAVED_NEW) {
drupal_set_message(t('The paragraph bundle %name has been added.', $t_args));
watchdog('node', 'Added paragraph bundle %name.', $t_args, WATCHDOG_NOTICE, l(t('view'), 'admin/structure/paragraphs'));
}
$form_state['redirect'] = 'admin/structure/paragraphs';
return;
}
/**
* Menu callback; delete a single paragraph bundle
*
* @ingroup forms
*/
function paragraphs_admin_bundle_delete_form($form, &$form_state, $bundle) {
if (!$bundle) {
drupal_set_message(t('Could not load bundle'), 'error');
drupal_goto('admin/structure/paragraphs');
}
$form['type'] = array('#type' => 'value', '#value' => $bundle->bundle);
$form['name'] = array('#type' => 'value', '#value' => $bundle->name);
$message = t('Are you sure you want to delete the paragraph bundle %bundle?', array('%bundle' => $bundle->name));
$caption = '<p>' . t('This action cannot be undone. Content using the bundle will be broken.') . '</p>';
return confirm_form($form, filter_xss_admin($message), 'admin/structure/paragraphs', filter_xss_admin($caption), t('Delete'));
}
/**
* Process paragraph bundle delete confirm submissions.
*
* @see paragraphs_admin_bundle_delete_form()
*/
function paragraphs_admin_bundle_delete_form_submit($form, &$form_state) {
paragraphs_bundle_delete($form_state['values']['type']);
$t_args = array('%name' => $form_state['values']['name']);
drupal_set_message(t('The paragraph bundle %name has been deleted.', $t_args));
watchdog('node', 'Deleted paragraph bundle %name.', $t_args, WATCHDOG_NOTICE);
$form_state['redirect'] = 'admin/structure/paragraphs';
return;
}
/**
* Helper to get the title of a bundle.
*
* @param $bundle
* The bundle.
*/
function paragraphs_bundle_title_callback($bundle) {
return t('Edit Paragraph Bundle !name', array('!name' => $bundle->name));
}
\ No newline at end of file
<?php
/**
* @file
* Provides callbacks for ajax endpoints.
*/
/**
* Page callback to handle AJAX for editing a paragraphs item.
*
* This is a direct page callback. The actual job of deleting the item is
* done in the submit handler for the button, so all we really need to
* do is process the form and then generate output. We generate this
* output by doing a replace command on the id of the entire form element.
*/
function paragraphs_edit_js() {
// drupal_html_id() very helpfully ensures that all html IDS are unique
// on a page. Unfortunately what it doesn't realize is that the IDs
// we are generating are going to replace IDs that already exist, so
// this actually works against us.
if (isset($_POST['ajax_html_ids'])) {
unset($_POST['ajax_html_ids']);
}
list($form, $form_state) = ajax_get_form();
drupal_process_form($form['#form_id'], $form, $form_state);
// Get the information on what we're removing.
$button = $form_state['triggering_element'];
// Go two levels up in the form, to the whole widget.
$element = drupal_array_get_nested_value($form, array_slice($button['#array_parents'], 0, -4));
// Now send back the proper AJAX command to replace it.
$return = array(
'#type' => 'ajax',
'#commands' => array(
ajax_command_replace('#' . $element['#id'], drupal_render($element))
),
);
// Because we're doing this ourselves, messages aren't automatic. We have
// to add them.
$messages = theme('status_messages');
if ($messages) {
$return['#commands'][] = ajax_command_prepend('#' . $element['#id'], $messages);
}
return $return;
}
/**
* Page callback to handle AJAX for collapse a paragraphs item.
*
* This is a direct page callback. The actual job of deleting the item is
* done in the submit handler for the button, so all we really need to
* do is process the form and then generate output. We generate this
* output by doing a replace command on the id of the entire form element.
*/
function paragraphs_collapse_js() {
// drupal_html_id() very helpfully ensures that all html IDS are unique
// on a page. Unfortunately what it doesn't realize is that the IDs
// we are generating are going to replace IDs that already exist, so
// this actually works against us.
if (isset($_POST['ajax_html_ids'])) {
unset($_POST['ajax_html_ids']);
}
list($form, $form_state) = ajax_get_form();
drupal_process_form($form['#form_id'], $form, $form_state);
// Get the information on what we're removing.
$button = $form_state['triggering_element'];
// Go two levels up in the form, to the whole widget.
$element = drupal_array_get_nested_value($form, array_slice($button['#array_parents'], 0, -4));
// Now send back the proper AJAX command to replace it.
$return = array(
'#type' => 'ajax',
'#commands' => array(
ajax_command_replace('#' . $element['#id'], drupal_render($element))
),
);
// Because we're doing this ourselves, messages aren't automatic. We have
// to add them.
$messages = theme('status_messages');
if ($messages) {
$return['#commands'][] = ajax_command_prepend('#' . $element['#id'], $messages);
}
return $return;
}
/**
* Page callback to handle AJAX for removing a paragraphs item.
*
* This is a direct page callback. The actual job of deleting the item is
* done in the submit handler for the button, so all we really need to
* do is process the form and then generate output. We generate this
* output by doing a replace command on the id of the entire form element.
*/
function paragraphs_remove_js() {
// drupal_html_id() very helpfully ensures that all html IDS are unique
// on a page. Unfortunately what it doesn't realize is that the IDs
// we are generating are going to replace IDs that already exist, so
// this actually works against us.
if (isset($_POST['ajax_html_ids'])) {
unset($_POST['ajax_html_ids']);
}
list($form, $form_state) = ajax_get_form();
drupal_process_form($form['#form_id'], $form, $form_state);
// Get the information on what we're removing.
$button = $form_state['triggering_element'];
// Go two levels up in the form, to the whole widget.
$element = drupal_array_get_nested_value($form, array_slice($button['#array_parents'], 0, -4));
// Now send back the proper AJAX command to replace it.
$return = array(
'#type' => 'ajax',
'#commands' => array(
ajax_command_replace('#' . $element['#id'], drupal_render($element))
),
);
// Because we're doing this ourselves, messages aren't automatic. We have
// to add them.
$messages = theme('status_messages');
if ($messages) {
$return['#commands'][] = ajax_command_prepend('#' . $element['#id'], $messages);
}
return $return;
}
/**
* Page callback to handle AJAX for removing a paragraphs item.
*
* This is a direct page callback. The actual job of deleting the item is
* done in the submit handler for the button, so all we really need to
* do is process the form and then generate output. We generate this
* output by doing a replace command on the id of the entire form element.
*/
function paragraphs_deleteconfirm_js() {
// drupal_html_id() very helpfully ensures that all html IDS are unique
// on a page. Unfortunately what it doesn't realize is that the IDs
// we are generating are going to replace IDs that already exist, so
// this actually works against us.
if (isset($_POST['ajax_html_ids'])) {
unset($_POST['ajax_html_ids']);
}
list($form, $form_state) = ajax_get_form();
drupal_process_form($form['#form_id'], $form, $form_state);
// Get the information on what we're removing.
$button = $form_state['triggering_element'];
// Go two levels up in the form, to the whole widget.
$element = drupal_array_get_nested_value($form, array_slice($button['#array_parents'], 0, -4));
// Now send back the proper AJAX command to replace it.
$return = array(
'#type' => 'ajax',
'#commands' => array(
ajax_command_replace('#' . $element['#id'], drupal_render($element))
),
);
// Because we're doing this ourselves, messages aren't automatic. We have
// to add them.
$messages = theme('status_messages');
if ($messages) {
$return['#commands'][] = ajax_command_prepend('#' . $element['#id'], $messages);
}
return $return;
}
/**
* Page callback to handle AJAX for restoring a paragraphs item.
*
* This is a direct page callback. The actual job of deleting the item is
* done in the submit handler for the button, so all we really need to
* do is process the form and then generate output. We generate this
* output by doing a replace command on the id of the entire form element.
*/
function paragraphs_restore_js() {
// drupal_html_id() very helpfully ensures that all html IDS are unique
// on a page. Unfortunately what it doesn't realize is that the IDs
// we are generating are going to replace IDs that already exist, so
// this actually works against us.
if (isset($_POST['ajax_html_ids'])) {
unset($_POST['ajax_html_ids']);
}
list($form, $form_state) = ajax_get_form();
drupal_process_form($form['#form_id'], $form, $form_state);
// Get the information on what we're removing.
$button = $form_state['triggering_element'];
// Go two levels up in the form, to the whole widget.
$element = drupal_array_get_nested_value($form, array_slice($button['#array_parents'], 0, -4));
// Now send back the proper AJAX command to replace it.
$return = array(
'#type' => 'ajax',
'#commands' => array(
ajax_command_replace('#' . $element['#id'], drupal_render($element))
),
);
// Because we're doing this ourselves, messages aren't automatic. We have
// to add them.
$messages = theme('status_messages');
if ($messages) {
$return['#commands'][] = ajax_command_prepend('#' . $element['#id'], $messages);
}
return $return;
}
<?php
/**
* @file
* Holds features implementation.
*/
/**
* Implements hook_features_export_options().
*/
function paragraphs_features_export_options() {
$bundles = paragraphs_bundle_load();
$names = array();
foreach ($bundles as $key => $value) {
$names[$key] = $value->name;
}
return $names;
}
/**
* Implements hook_features_export().
*/
function paragraphs_features_export($data, &$export, $module_name = '') {
$pipe = array();
$map = features_get_default_map('paragraphs');
foreach ($data as $type) {
if ($info = paragraphs_bundle_load($type)) {
$export['features']['paragraphs'][$type] = $type;
$export['dependencies']['paragraphs'] = 'paragraphs';
$export['dependencies']['features'] = 'features';
$fields = field_info_instances('paragraphs_item', $type);
foreach ($fields as $name => $field) {
$pipe['field_instance'][] = "paragraphs_item-{$field['bundle']}-{$field['field_name']}";
}
}
}
return $pipe;
}
/**
* Implements hook_features_export_render().
*/
function paragraphs_features_export_render($module, $data, $export = NULL) {
$elements = array(
'name' => FALSE,
'bundle' => FALSE,
'locked' => FALSE,
);
$output = array();
$output[] = ' $items = array(';
foreach ($data as $type) {
if ($info = paragraphs_bundle_load($type)) {
$output[] = " '{$type}' => array(";
foreach ($elements as $key => $t) {
if ($t) {
$text = str_replace("'", "\'", $info->$key);
$text = !empty($text) ? "t('{$text}')" : "''";
$output[] = " '{$key}' => {$text},";
}
else {
$output[] = " '{$key}' => '{$info->$key}',";
}
}
$output[] = " ),";
}
}
$output[] = ' );';
$output[] = ' return $items;';
$output = implode("\n", $output);
return array('paragraphs_info' => $output);
}
/**
* Implements hook_features_revert().
*
* @param $module
* name of module to revert content for
*/
function paragraphs_features_revert($module = NULL) {
if ($default_types = features_get_default('paragraphs', $module)) {
foreach ($default_types as $type_name => $type_info) {
db_delete('paragraphs_bundle')
->condition('bundle', $type_name)
->execute();
$bundle = new stdClass();
$bundle->bundle = $type_info['bundle'];
$bundle->locked = $type_info['locked'];
$bundle->name = $type_info['name'];
paragraphs_bundle_save($bundle);
}
paragraphs_bundle_load(NULL, TRUE);
menu_rebuild();
}
}
/**
* Implements hook_features_rebuild().
*/
function paragraphs_features_rebuild($module_name) {
paragraphs_features_revert($module_name);
}
<?php
/**
* @file
* Holds relevant functions for paragraph field formatters.
*/
/**
* Implements hook_field_formatter_info().
*/
function paragraphs_field_formatter_info() {
return array(
'paragraphs_view' => array(
'label' => t('Paragraphs items'),
'field types' => array('paragraphs'),
'settings' => array(
'view_mode' => 'full',
),
),
);
}
/**
* Implements hook_field_formatter_settings_form().
*/
function paragraphs_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
$display = $instance['display'][$view_mode];
$settings = $display['settings'];
$elements = array();
if ($display['type'] == 'paragraphs_view') {
$entity_type = entity_get_info('paragraphs_item');
$options = array();
foreach ($entity_type['view modes'] as $mode => $info) {
$options[$mode] = $info['label'];
}
$elements['view_mode'] = array(
'#type' => 'select',
'#title' => t('View mode'),
'#options' => $options,
'#default_value' => $settings['view_mode'],
'#description' => t('Select the view mode'),
);
}
return $elements;
}
/**
* Implements hook_field_formatter_settings_summary().
*/
function paragraphs_field_formatter_settings_summary($field, $instance, $view_mode) {
$display = $instance['display'][$view_mode];
$settings = $display['settings'];
$output = array();
if ($display['type'] == 'paragraphs_view') {
$entity_type = entity_get_info('paragraphs_item');
if (!empty($entity_type['view modes'][$settings['view_mode']]['label'])) {
$output[] = t('View mode: @mode', array('@mode' => $entity_type['view modes'][$settings['view_mode']]['label']));
}
else {
$output[] = ' ';
}
}
return implode('<br>', $output);
}
/**
* Implements hook_field_formatter_view().
*/
function paragraphs_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
$element = array();
$settings = $display['settings'];
switch ($display['type']) {
case 'paragraphs_view':
// Prevent displaying useless markup if we don't have any values.
if (empty($items)) {
return $element;
}
// Get view mode from entity.
$display_view_mode = empty($display['settings']['view_mode']) ? 'full' : $display['settings']['view_mode'];
// Get view mode from field instance (if configured).
$view_mode = empty($instance['display'][$display_view_mode]['settings']['view_mode']) ? $display_view_mode : $instance['display'][$display_view_mode]['settings']['view_mode'];
$element['#theme_wrappers'] = array('paragraphs_items');
$element['#attributes']['class'][] = drupal_clean_css_identifier('paragraphs-items');
$element['#attributes']['class'][] = drupal_clean_css_identifier('paragraphs-items-view-mode-' . $view_mode);
$element['#attributes']['class'][] = drupal_clean_css_identifier('paragraphs-items-field-' . $instance['field_name']);
$element['#view_mode'] = $view_mode;
foreach ($items as $delta => $item) {
if ($paragraph = paragraphs_field_get_entity($item)) {
$paragraph->setHostEntity($entity_type, $entity, $langcode);
if (entity_access('view', 'paragraphs_item', $paragraph)) {
$element[$delta]['entity'] = $paragraph->view($view_mode);
}
}
}
break;
}
return $element;
}
This diff is collapsed.
name = Paragraphs
description = Paragraphs module to control your content flow.
core = 7.x
package = Paragraphs
dependencies[] = entity
test_dependencies[] = ctools
test_dependencies[] = entity
test_dependencies[] = features
test_dependencies[] = panelizer
test_dependencies[] = strongarm
files[] = ParagraphsItemEntity.inc
files[] = ParagraphsItemMetadataController.inc
files[] = migrate/destinations/MigrateDestinationParagraphsItem.inc
files[] = migrate/fields/ParagraphsMigrateParagraphsFieldHandler.inc
files[] = views/paragraphs_handler_relationship.inc
files[] = tests/paragraphs.test
; Information added by Drupal.org packaging script on 2017-02-16
version = "7.x-1.0-rc5"
core = "7.x"
project = "paragraphs"
datestamp = "1487261293"
<?php
/**
* @file
* Install, update and uninstall functions for the paragraphs module.
*/
/**
* Implements hook_schema().
*/
function paragraphs_schema() {
$schema = array();
$schema['paragraphs_bundle'] = array(
'description' => 'Stores information about paragraphs bundles.',
'fields' => array(
'bundle' => array(
'description' => 'The machine-readable name of this bundle.',
'type' => 'varchar',
'length' => 32,
'not null' => TRUE,
),
'name' => array(
'description' => 'The human-readable name of this bundle.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
'translatable' => TRUE,
),
'locked' => array(
'description' => 'A boolean indicating whether the administrator can change the machine name of this bundle.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
'size' => 'tiny',
),
),
'primary key' => array('bundle'),
);
$schema['paragraphs_item'] = array(
'description' => 'Stores information about paragraph items.',
'fields' => array(
'item_id' => array(
'type' => 'serial',
'not null' => TRUE,
'description' => 'Primary Key: Unique paragraph item ID.',
),
'revision_id' => array(
'type' => 'int',
'not null' => TRUE,
'description' => 'Default revision ID.',
),
'bundle' => array(
'description' => 'The bundle of this paragraph item.',
'type' => 'varchar',
'length' => 32,
'not null' => TRUE,
),
'field_name' => array(
'description' => 'Field name of the host entity.',
'type' => 'varchar',
'length' => 32,
'not null' => TRUE,
),
'archived' => array(
'description' => 'Boolean indicating whether the paragraph item is archived.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
),
'primary key' => array('item_id'),
);
$schema['paragraphs_item_revision'] = array(
'description' => 'Stores revision information about paragraph items.',
'fields' => array(
'revision_id' => array(
'type' => 'serial',
'not null' => TRUE,
'description' => 'Primary Key: Unique revision ID.',
),
'item_id' => array(
'type' => 'int',
'not null' => TRUE,
'description' => 'Paragraph item ID.',
),
),
'primary key' => array('revision_id'),
'indexes' => array(
'item_id' => array('item_id'),
),
'foreign keys' => array(
'versioned_paragraphs_item' => array(
'table' => 'paragraphs_item',
'columns' => array('item_id' => 'item_id'),
),
),
);
return $schema;
}
/**
* Implements hook_field_schema().
*/
function paragraphs_field_schema($field) {
$columns = array();
if ($field['type'] == 'paragraphs') {
$columns = array(
'value' => array(
'type' => 'int',
'not null' => FALSE,
'description' => 'The paragraph item id.',
),
'revision_id' => array(
'type' => 'int',
'not null' => FALSE,
'description' => 'The paragraph item revision id.',
),
);
}
return array(
'columns' => $columns,
);
}
/**
* Make sure all paragraph fields have the new index on revision_id.
*/
function paragraphs_update_7100() {
// Update the paragraphs_field_schema columns for all tables.
foreach (field_read_fields(array('type' => 'paragraphs')) as $field) {
field_update_field($field);
}
}
/**
* Make sure the entitycache table exists.
*/
function paragraphs_update_7101() {
if (module_exists('entitycache') && !db_table_exists("cache_entity_paragraphs_item")) {
drupal_load('module', 'entitycache');
$cache_schema = drupal_get_schema_unprocessed('system', 'cache');
$cache_schema['description'] = "Cache table used to store paragraphs_item entity records.";
db_create_table("cache_entity_paragraphs_item", $cache_schema);
}
}
/**
* @file
* Provides JavaScript for Paragraphs.
*/
(function ($) {
/**
* Allows submit buttons in entity forms to trigger uploads by undoing
* work done by Drupal.behaviors.fileButtons.
*/
Drupal.behaviors.paragraphs = {
attach: function (context) {
if (Drupal.file) {
$('input.paragraphs-add-more-submit', context).unbind('mousedown', Drupal.file.disableFields);
}
},
detach: function (context) {
if (Drupal.file) {
$('input.form-submit', context).bind('mousedown', Drupal.file.disableFields);
}
}
};
})(jQuery);
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment