first import

This commit is contained in:
Bachir Soussi Chiadmi
2015-04-08 11:40:19 +02:00
commit 1bc61b12ad
8435 changed files with 1582817 additions and 0 deletions

View File

@@ -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.

View File

@@ -0,0 +1,156 @@
Entity API module
-----------------
by Wolfgang Ziegler, nuppla@zites.net
This module extends the entity API of Drupal core in order to provide a unified
way to deal with entities and their properties. Additionally, it provides an
entity CRUD controller, which helps simplifying the creation of new entity types.
This is an API module. You only need to enable it if a module depends on it or
you are interested in using it for development.
This README is for interested developers. If you are not interested in
developing, you may stop reading now.
--------------------------------------------------------------------------------
Entity API
--------------------------------------------------------------------------------
* The module provides API functions allowing modules to create, save, delete
or to determine access for entities based on any entity type, for which the
necessary metadata is available. The module comes with integration for all
core entity types, as well as for entities provided via the Entity CRUD API
(see below). However, for any other entity type implemented by a contrib
module, the module integration has to be provided by the contrib module
itself.
* Thus the module provides API functions like entity_save(), entity_create(),
entity_delete(), entity_revision_delete(), entity_view() and entity_access()
among others.
entity_load(), entity_label() and entity_uri() are already provided by
Drupal core.
* For more information about how to provide this metadata, have a look at the
API documentation, i.e. entity_metadata_hook_entity_info().
--------------------------------------------------------------------------------
Entity CRUD API - Providing new entity types
--------------------------------------------------------------------------------
* This API helps you when defining a new entity type. It provides an entity
controller, which implements full CRUD functionality for your entities.
* To make use of the CRUD functionality you may just use the API functions
entity_create(), entity_delete() and entity_save().
Alternatively you may specify a class to use for your entities, for which the
"Entity" class is provided. In particular, it is useful to extend this class
in order to easily customize the entity type, e.g. saving.
* The controller supports fieldable entities and revisions. There is also a
controller which supports implementing exportable entities.
* The Entity CRUD API helps with providing additional module integration too,
e.g. exportable entities are automatically integrated with the Features
module. These module integrations are implemented in separate controller
classes, which may be overridden and deactivated on their own.
* There is also an optional ui controller class, which assists with providing
an administrative UI for managing entities of a certain type.
* For more details check out the documentation in the drupal.org handbook
http://drupal.org/node/878804 as well as the API documentation, i.e.
entity_crud_hook_entity_info().
Basic steps to add a new entity type:
---------------------------------------
* You might want to study the code of the "entity_test.module".
* Describe your entities db table as usual in hook_schema().
* Just use the "Entity" directly or extend it with your own class.
To see how to provide a separate class have a look at the "EntityClass" from
the "entity_test.module".
* Implement hook_entity_info() for your entity. At least specifiy the
controller class (EntityAPIController, EntityAPIControllerExportable or your
own), your db table and your entity's keys.
Again just look at "entity_test.module"'s hook_entity_info() for guidance.
* If you want your entity to be fieldable just set 'fieldable' in
hook_entity_info() to TRUE. The field API attachers are then called
automatically in the entity CRUD functions.
* The entity API is able to deal with bundle objects too (e.g. the node type
object). For that just specify another entity type for the bundle objects
and set the 'bundle of' property for it.
Again just look at "entity_test.module"'s hook_entity_info() for guidance.
* Schema fields marked as 'serialized' are automatically unserialized upon
loading as well as serialized on saving. If the 'merge' attribute is also
set to TRUE the unserialized data is automatically "merged" into the entity.
* Further details can be found at http://drupal.org/node/878804.
--------------------------------------------------------------------------------
Entity Properties & Entity metadata wrappers
--------------------------------------------------------------------------------
* This module introduces a unique place for metadata about entity properties:
hook_entity_property_info(), whereas hook_entity_property_info() may be
placed in your module's {YOUR_MODULE}.info.inc include file. For details
have a look at the API documentation, i.e. hook_entity_property_info() and
at http://drupal.org/node/878876.
* The information about entity properties contains the data type and callbacks
for how to get and set the data of the property. That way the data of an
entity can be easily re-used, e.g. to export it into other data formats like
XML.
* For making use of this information (metadata) the module provides some
wrapper classes which ease getting and setting values. The wrapper supports
chained usage for retrieving wrappers of entity properties, e.g. to get a
node author's mail address one could use:
$wrapper = entity_metadata_wrapper('node', $node);
$wrapper->author->mail->value();
To update the user's mail address one could use
$wrapper->author->mail->set('sepp@example.com');
or
$wrapper->author->mail = 'sepp@example.com';
The wrappers always return the data as described in the property
information, which may be retrieved directly via entity_get_property_info()
or from the wrapper:
$mail_info = $wrapper->author->mail->info();
In order to force getting a textual value sanitized for output one can use,
e.g.
$wrapper->title->value(array('sanitize' => TRUE));
to get the sanitized node title. When a property is already returned
sanitized by default, like the node body, one possibly wants to get the
not-sanitized data as it would appear in a browser for other use-cases.
To do so one can enable the 'decode' option, which ensures for any sanitized
data the tags are stripped and HTML entities are decoded before the property
is returned:
$wrapper->body->value->value(array('decode' => TRUE));
That way one always gets the data as shown to the user. However if you
really want to get the raw, unprocessed value, even for sanitized textual
data, you can do so via:
$wrapper->body->value->raw();

View File

@@ -0,0 +1,133 @@
<?php
/**
* @file
* Content type plugin to expose rendered entities, view mode configuration
* still available.
*/
$plugin = array(
'title' => t('Rendered entity'),
'defaults' => array('view_mode' => 'full'),
'content type' => 'entity_entity_view_content_type_info',
);
/**
* Get the entity content type info.
*/
function entity_entity_view_content_type_info($entity_type) {
$types = entity_entity_view_content_type_content_types();
if (isset($types[$entity_type])) {
return $types[$entity_type];
}
}
/**
* Implements hook_PLUGIN_content_type_content_types().
*
* Rendered entity use entity types machine name as subtype name.
*/
function entity_entity_view_content_type_content_types() {
$types = array();
$entities = entity_get_info();
foreach ($entities as $entity_type => $info) {
if (entity_type_supports($entity_type, 'view')) {
$types[$entity_type] = array(
'title' => t('Rendered @entity_type', array('@entity_type' => $info['label'])),
'category' => t('Entity'),
'required context' => new ctools_context_required(t('Entity'), $entity_type),
);
}
}
return $types;
}
/**
* Returns an edit form for a entity.
*
* Rendered entity use entity types machine name as subtype name.
*
* @see entity_entity_view_get_content_types()
*/
function entity_entity_view_content_type_edit_form($form, &$form_state) {
$conf = $form_state['conf'];
$entity_type = $form_state['subtype_name'];
$entity_info = entity_get_info($entity_type);
$options = array();
if (!empty($entity_info['view modes'])) {
foreach ($entity_info['view modes'] as $mode => $settings) {
$options[$mode] = $settings['label'];
}
}
if (count($options) > 1) {
$form['view_mode'] = array(
'#type' => 'select',
'#options' => $options,
'#title' => t('View mode'),
'#default_value' => $conf['view_mode'],
);
}
else {
$form['view_mode_info'] = array(
'#type' => 'item',
'#title' => t('View mode'),
'#description' => t('Only one view mode is available for this entity type.'),
'#markup' => $options ? current($options) : t('Default'),
);
$form['view_mode'] = array(
'#type' => 'value',
'#value' => $options ? key($options) : 'default',
);
}
return $form;
}
/**
* Save selected view mode.
*/
function entity_entity_view_content_type_edit_form_submit(&$form, &$form_state) {
if (isset($form_state['values']['view_mode'])) {
$form_state['conf']['view_mode'] = $form_state['values']['view_mode'];
}
}
/**
* Implements hook_PLUGIN_content_type_render().
*
* Ctools requires us to return a block.
*
* @see ctools_content_render()
*/
function entity_entity_view_content_type_render($entity_type, $conf, $panel_args, $context) {
if ($context->empty) {
return;
}
$block = new stdClass();
$block->module = 'entity';
$block->delta = $entity_type . '-' . str_replace('-', '_', $conf['view_mode']);
$entity_id = $context->argument;
$entity = entity_load_single($entity_type, $entity_id);
$block->content = entity_view($entity_type, array($entity_id => $entity), $conf['view_mode']);
return $block;
}
/**
* Implements hook_PLUGIN_content_type_admin_title().
*
* Returns the administrative title for a type.
*/
function entity_entity_view_content_type_admin_title($entity_type, $conf, $contexts) {
$entity_info = entity_get_info($entity_type);
$view_mode = $conf['view_mode'];
if (isset($entity_info['view modes'][$view_mode])) {
$view_mode = $entity_info['view modes'][$view_mode]['label'];
}
return t('Rendered @entity_type using view mode "@view_mode"', array('@entity_type' => $entity_info['label'], '@view_mode' => $view_mode));
}

View File

@@ -0,0 +1,456 @@
<?php
/**
* @file
* Hooks provided by the entity API.
*/
/**
* @addtogroup hooks
* @{
*/
/**
* Provide an entity type via the entity CRUD API.
*
* This is a placeholder for describing further keys for hook_entity_info(),
* which are introduced the entity API for providing a new entity type with the
* entity CRUD API. For that the entity API provides two controllers:
* - EntityAPIController: A regular CRUD controller.
* - EntityAPIControllerExportable: Extends the regular controller to
* additionally support exportable entities and/or entities making use of a
* name key.
* See entity_metadata_hook_entity_info() for the documentation of additional
* keys for hook_entity_info() as introduced by the entity API and supported for
* any entity type.
*
* The entity CRUD API supports the following keys:
* - entity class: (optional) A class the controller will use for instantiating
* entities. It is suggested to make use of the provided "Entity" class or to
* extend it.
* - bundle of: (optional) Entity types can be used as bundles for
* other entity types. To enable this functionality, use the 'bundle of' key
* to indicate which entity type this entity serves as a bundle for. But note
* that the other entity type will still need to declare entities of this
* type as bundles, as described by the documentation of hook_entity_info().
* If the other entity type is fieldable, the entity API controller takes
* care of invoking the field API bundle attachers. Note that
* field_attach_delete_bundle() has to be invoked manually upon module
* uninstallation. See entity_test_entity_info() and entity_test_uninstall()
* for examples.
* - module: (optional) The module providing the entity type. This is optional,
* but strongly suggested.
* - exportable: (optional) Whether the entity is exportable. Defaults to FALSE.
* If enabled, a name key should be specified and db columns for the module
* and status key as defined by entity_exportable_schema_fields() have to
* exist in the entity's base table. Also see 'entity keys' below.
* This option requires the EntityAPIControllerExportable to work.
* - entity keys: An array of keys as defined by Drupal core. The following
* additional keys are used by the entity CRUD API:
* - name: (optional) The key of the entity property containing the unique,
* machine readable name of the entity. If specified, this is used as
* identifier of the entity, while the usual 'id' key is still required and
* may be used when modules deal with entities generically, or to refer to
* the entity internally, i.e. in the database.
* If a name key is given, the name is used as entity identifier by the
* entity API module, metadata wrappers and entity-type specific hooks.
* However note that for consistency all generic entity hooks like
* hook_entity_load() are invoked with the entities keyed by numeric id,
* while entity-type specific hooks like hook_{entity_type}_load() are
* invoked with the entities keyed by name.
* Also, just as entity_load_single() entity_load() may be called
* with names passed as the $ids parameter, while the results of
* entity_load() are always keyed by numeric id. Thus, it is suggested to
* make use of entity_load_multiple_by_name() to implement entity-type
* specific loading functions like {entity_type}_load_multiple(), as this
* function returns the entities keyed by name. See entity_test_get_types()
* for an example.
* For exportable entities, it is strongly recommended to make use of a
* machine name as names are portable across systems.
* This option requires the EntityAPIControllerExportable to work.
* - module: (optional) A key for the module property used by the entity CRUD
* API to save the source module name for exportable entities that have been
* provided in code. Defaults to 'module'.
* - status: (optional) The name of the entity property used by the entity
* CRUD API to save the exportable entity status using defined bit flags.
* Defaults to 'status'. See entity_has_status().
* - default revision: (optional) The name of the entity property used by
* the entity CRUD API to determine if a newly-created revision should be
* set as the default revision. Defaults to 'default_revision'.
* Note that on entity insert the created revision will be always default
* regardless of the value of this entity property.
* - export: (optional) An array of information used for exporting. For ctools
* exportables compatibility any export-keys supported by ctools may be added
* to this array too.
* - default hook: What hook to invoke to find exportable entities that are
* currently defined. This hook is automatically called by the CRUD
* controller during entity_load(). Defaults to 'default_' . $entity_type.
* - admin ui: (optional) An array of optional information used for providing an
* administrative user interface. To enable the UI at least the path must be
* given. Apart from that, the 'access callback' (see below) is required for
* the entity, as well as the 'ENTITY_TYPE_form' for editing, adding and
* cloning. The form gets the entity and the operation ('edit', 'add' or
* 'clone') passed. See entity_ui_get_form() for more details.
* Known keys are:
* - path: A path where the UI should show up as expected by hook_menu().
* - controller class: (optional) A controller class name for providing the
* UI. Defaults to EntityDefaultUIController, which implements an admin UI
* suiting for managing configuration entities.
* For customizing the UI inherit from the default class and overide methods
* as suiting and specify your class as controller class.
* - file: (optional) The name of the file in which the entity form resides
* as it is required by hook_menu().
* - file path: (optional) The path to the file as required by hook_menu. If
* not set, it defaults to entity type's module's path, thus the entity
* types 'module' key is required.
* - menu wildcard: The wildcard to use in paths of the hook_menu() items.
* Defaults to %entity_object which is the loader provided by Entity API.
* - rules controller class: (optional) A controller class for providing Rules
* integration, or FALSE to disable this feature. The given class has to
* inherit from the class EntityDefaultRulesController, which serves as
* default in case the entity type is not marked as configuration. For
* configuration entities it defaults to FALSE.
* - metadata controller class: (optional) A controller class for providing
* entity property info. By default some info is generated out of the
* information provided in your hook_schema() implementation, while only read
* access is granted to that properties by default. Based upon that the
* Entity tokens module also generates token replacements for your entity
* type, once activated.
* Override the controller class to adapt the defaults and to improve and
* complete the generated metadata. Set it to FALSE to disable this feature.
* Defaults to the EntityDefaultMetadataController class.
* - features controller class: (optional) A controller class for providing
* Features module integration for exportable entities. The given class has to
* inherit from the default class being EntityDefaultFeaturesController. Set
* it to FALSE to disable this feature.
* - i18n controller class: (optional) A controller class for providing
* i18n module integration for (exportable) entities. The given class has to
* inherit from the class EntityDefaultI18nStringController. Defaults to
* FALSE (disabled). See EntityDefaultI18nStringController for more
* information.
* - views controller class: (optional) A controller class for providing views
* integration. The given class has to inherit from the class
* EntityDefaultViewsController, which is set as default in case the providing
* module has been specified (see 'module') and the module does not provide
* any views integration. Else it defaults to FALSE, which disables this
* feature. See EntityDefaultViewsController.
* - access callback: (optional) Specify a callback that returns access
* permissions for the operations 'create', 'update', 'delete' and 'view'.
* The callback gets optionally the entity and the user account to check for
* passed. See entity_access() for more details on the arguments and
* entity_metadata_no_hook_node_access() for an example.
* This is optional, but suggested for the Rules integration, and required for
* the admin ui (see above).
* - form callback: (optional) Specfiy a callback that returns a fully built
* edit form for your entity type. See entity_form().
* In case the 'admin ui' is used, no callback needs to be specified.
* - entity cache: (optional) Whether entities should be cached using the cache
* system. Requires the entitycache module to be installed and enabled. As
* cached entities are only retrieved by id key, the cache would not apply to
* exportable entities retrieved by name key. If enabled, 'field cache' is
* obsolete and should be disabled. Defaults to FALSE.
*
* @see hook_entity_info()
* @see entity_metadata_hook_entity_info()
*/
function entity_crud_hook_entity_info() {
$return = array(
'entity_test' => array(
'label' => t('Test Entity'),
'entity class' => 'Entity',
'controller class' => 'EntityAPIController',
'base table' => 'entity_test',
'module' => 'entity_test',
'fieldable' => TRUE,
'entity keys' => array(
'id' => 'pid',
'name' => 'name',
'bundle' => 'type',
),
'bundles' => array(),
),
);
foreach (entity_test_get_types() as $name => $info) {
$return['entity_test']['bundles'][$name] = array(
'label' => $info['label'],
);
}
return $return;
}
/**
* Provide additional metadata for entities.
*
* This is a placeholder for describing further keys for hook_entity_info(),
* which are introduced the entity API in order to support any entity type; e.g.
* to make entity_save(), entity_create(), entity_view() and others work.
* See entity_crud_hook_entity_info() for the documentation of additional keys
* for hook_entity_info() as introduced by the entity API for providing new
* entity types with the entity CRUD API.
*
* Additional keys are:
* - plural label: (optional) The human-readable, plural name of the entity
* type. As 'label' it should start capitalized.
* - description: (optional) A human-readable description of the entity type.
* - access callback: (optional) Specify a callback that returns access
* permissions for the operations 'create', 'update', 'delete' and 'view'.
* The callback gets optionally the entity and the user account to check for
* passed. See entity_access() for more details on the arguments and
* entity_metadata_no_hook_node_access() for an example.
* - creation callback: (optional) A callback that creates a new instance of
* this entity type. See entity_metadata_create_node() for an example.
* - save callback: (optional) A callback that permanently saves an entity of
* this type.
* - deletion callback: (optional) A callback that permanently deletes an
* entity of this type.
* - revision deletion callback: (optional) A callback that deletes a revision
* of the entity.
* - view callback: (optional) A callback to render a list of entities.
* See entity_metadata_view_node() as example.
* - form callback: (optional) A callback that returns a fully built edit form
* for the entity type.
* - token type: (optional) A type name to use for token replacements. Set it
* to FALSE if there aren't any token replacements for this entity type.
* - configuration: (optional) A boolean value that specifies whether the entity
* type should be considered as configuration. Modules working with entities
* may use this value to decide whether they should deal with a certain entity
* type. Defaults to TRUE to for entity types that are exportable, else to
* FALSE.
*
* @see hook_entity_info()
* @see entity_crud_hook_entity_info()
* @see entity_access()
* @see entity_create()
* @see entity_save()
* @see entity_delete()
* @see entity_view()
* @see entity_form()
*/
function entity_metadata_hook_entity_info() {
return array(
'node' => array(
'label' => t('Node'),
'access callback' => 'entity_metadata_no_hook_node_access',
// ...
),
);
}
/**
* Allow modules to define metadata about entity properties.
*
* Modules providing properties for any entities defined in hook_entity_info()
* can implement this hook to provide metadata about this properties.
* For making use of the metadata have a look at the provided wrappers returned
* by entity_metadata_wrapper().
* For providing property information for fields see entity_hook_field_info().
*
* @return
* An array whose keys are entity type names and whose values are arrays
* containing the keys:
* - properties: The array describing all properties for this entity. Entries
* are keyed by the property name and contain an array of metadata for each
* property. The name may only contain alphanumeric lowercase characters
* and underscores. Known keys are:
* - label: A human readable, translated label for the property.
* - description: (optional) A human readable, translated description for
* the property.
* - type: The data type of the property. To make the property actually
* useful it is important to map your properties to one of the known data
* types, which currently are:
* - text: Any text.
* - token: A string containing only lowercase letters, numbers, and
* underscores starting with a letter; e.g. this type is useful for
* machine readable names.
* - integer: A usual PHP integer value.
* - decimal: A PHP float or integer.
* - date: A full date and time, as timestamp.
* - duration: A duration as number of seconds.
* - boolean: A usual PHP boolean value.
* - uri: An absolute URI or URL.
* - entities - You may use the type of each entity known by
* hook_entity_info(), e.g. 'node' or 'user'. Internally entities are
* represented by their identifieres. In case of single-valued
* properties getter callbacks may return full entity objects as well,
* while a value of FALSE is interpreted like a NULL value as "property
* is not set".
* - entity: A special type to be used generically for entities where the
* entity type is not known beforehand. The entity has to be
* represented using an EntityMetadataWrapper.
* - struct: This as well as any else not known type may be used for
* supporting arbitrary data structures. For that additional metadata
* has to be specified with the 'property info' key. New type names
* have to be properly prefixed with the module name.
* - list: A list of values, represented as numerically indexed array.
* The list<TYPE> notation may be used to specify the type of the
* contained items, where TYPE may be any valid type expression.
* - bundle: (optional) If the property is an entity, you may specify the
* bundle of the referenced entity.
* - options list: (optional) A callback that returns a list of possible
* values for the property. The callback has to return an array as
* used by hook_options_list().
* Note that it is possible to return a different set of options depending
* whether they are used in read or in write context. See
* EntityMetadataWrapper::optionsList() for more details on that.
* - getter callback: (optional) A callback used to retrieve the value of
* the property. Defaults to entity_property_verbatim_get().
* It is important that your data is represented, as documented for your
* data type, e.g. a date has to be a timestamp. Thus if necessary, the
* getter callback has to do the necessary conversion. In case of an empty
* or not set value, the callback has to return NULL.
* - setter callback: (optional) A callback used to set the value of the
* property. In many cases entity_property_verbatim_set() can be used.
* - validation callback: (optional) A callback that returns whether the
* passed data value is valid for the property. May be used to implement
* additional validation checks, such as to ensure the value is a valid
* mail address.
* - access callback: (optional) An access callback to allow for checking
* 'view' and 'edit' access for the described property. If no callback
* is specified, a 'setter permission' may be specified instead.
* - setter permission: (optional) A permission that describes whether
* a user has permission to set ('edit') this property. This permission
* is only be taken into account, if no 'access callback' is given.
* - schema field: (optional) In case the property is directly based upon
* a field specified in the entity's hook_schema(), the name of the field.
* - queryable: (optional) Whether a property is queryable with
* EntityFieldQuery. Defaults to TRUE if a 'schema field' is specified, or
* if the deprecated 'query callback' is set to
* 'entity_metadata_field_query'. Otherwise it defaults to FALSE.
* - query callback: (deprecated) A callback for querying for entities
* having the given property value. See entity_property_query().
* Generally, properties should be queryable via EntityFieldQuery. If
* that is the case, just set 'queryable' to TRUE.
* - required: (optional) Whether this property is required for the creation
* of a new instance of its entity. See
* entity_property_values_create_entity().
* - field: (optional) A boolean indicating whether a property is stemming
* from a field.
* - computed: (optional) A boolean indiciating whether a property is
* computed, i.e. the property value is not stored or loaded by the
* entity's controller but determined on the fly by the getter callback.
* Defaults to FALSE.
* - entity views field: (optional) If enabled, the property is
* automatically exposed as views field available to all views query
* backends listing this entity-type. As the property value will always be
* generated from a loaded entity object, this is particularly useful for
* 'computed' properties. Defaults to FALSE.
* - sanitized: (optional) For textual properties only, whether the text is
* already sanitized. In this case you might want to also specify a raw
* getter callback. Defaults to FALSE.
* - sanitize: (optional) For textual properties, that are not sanitized
* yet, specify a function for sanitizing the value. Defaults to
* check_plain().
* - raw getter callback: (optional) For sanitized textual properties, a
* separate callback which can be used to retrieve the raw, unprocessed
* value.
* - clear: (optional) An array of property names, of which the cache should
* be cleared too once this property is updated. As a rule of thumb any
* duplicated properties should be avoided though.
* - property info: (optional) An array of info for an arbitrary data
* structure together with any else not defined type, see data type
* 'struct'. Specify metadata in the same way as defined for this hook.
* - property info alter: (optional) A callback for altering the property
* info before it is used by the metadata wrappers.
* - property defaults: (optional) An array of property info defaults for
* each property derived of the wrapped data item (e.g. an entity).
* Applied by the metadata wrappers.
* - auto creation: (optional) Properties of type 'struct' may specify
* this callback which is used to automatically create the data structure
* (e.g. an array) if necessary. This is necessary in order to support
* setting a property of a not yet initialized data structure.
* See entity_metadata_field_file_callback() for an example.
* - translatable: (optional) Whether the property is translatable, defaults
* to FALSE.
* - entity token: (optional) If Entity tokens module is enabled, the
* module provides a token for the property if one does not exist yet.
* Specify FALSE to disable this functionality for the property.
* - bundles: An array keyed by bundle name containing further metadata
* related to the bundles only. This array may contain the key 'properties'
* with an array of info about the bundle specific properties, structured in
* the same way as the entity properties array.
*
* @see hook_entity_property_info_alter()
* @see entity_metadata_get_info()
* @see entity_metadata_wrapper()
*/
function hook_entity_property_info() {
$info = array();
$properties = &$info['node']['properties'];
$properties['nid'] = array(
'label' => t("Content ID"),
'type' => 'integer',
'description' => t("The unique content ID."),
);
return $info;
}
/**
* Allow modules to alter metadata about entity properties.
*
* @see hook_entity_property_info()
*/
function hook_entity_property_info_alter(&$info) {
$properties = &$info['node']['bundles']['poll']['properties'];
$properties['poll-votes'] = array(
'label' => t("Poll votes"),
'description' => t("The number of votes that have been cast on a poll node."),
'type' => 'integer',
'getter callback' => 'entity_property_poll_node_get_properties',
);
}
/**
* Provide entity property information for fields.
*
* This is a placeholder for describing further keys for hook_field_info(),
* which are introduced by the entity API.
*
* For providing entity property info for fields each field type may specify a
* property type to map to using the key 'property_type'. With that info in
* place useful defaults are generated, which suffice for a lot of field
* types.
* However it is possible to specify further callbacks that may alter the
* generated property info. To do so use the key 'property_callbacks' and set
* it to an array of function names. Apart from that any property info provided
* for a field instance using the key 'property info' is added in too.
*
* @see entity_field_info_alter()
* @see entity_metadata_field_text_property_callback()
*/
function entity_hook_field_info() {
return array(
'text' => array(
'label' => t('Text'),
'property_type' => 'text',
// ...
),
);
}
/**
* Alter the handlers used by the data selection tables provided by this module.
*
* @param array $field_handlers
* An array of the field handler classes to use for specific types. The keys
* are the types, mapped to their respective classes. Contained types are:
* - All primitive types known by the entity API (see
* hook_entity_property_info()).
* - options: Special type for fields having an options list.
* - field: Special type for Field API fields.
* - entity: Special type for entity-valued fields.
* - relationship: Views relationship handler to use for relationships.
* Values for all specific entity types can be additionally added.
*
* @see entity_views_field_definition()
* @see entity_views_get_field_handlers()
*/
function hook_entity_views_field_handlers_alter(array &$field_handlers) {
$field_handlers['duration'] = 'example_duration_handler';
$field_handlers['node'] = 'example_node_handler';
}
/**
* @} End of "addtogroup hooks".
*/

View File

@@ -0,0 +1,174 @@
<?php
/**
* @file
* Provides Features integration for entity types using the CRUD API.
*/
/**
* Returns the configured entity features controller.
*/
function entity_features_get_controller($type) {
$static = &drupal_static(__FUNCTION__);
if (!isset($static[$type])) {
$info = entity_get_info($type);
$info += array('features controller class' => 'EntityDefaultFeaturesController');
$static[$type] = $info['features controller class'] ? new $info['features controller class']($type) : FALSE;
}
return $static[$type];
}
/**
* Default controller handling features integration.
*/
class EntityDefaultFeaturesController {
protected $type, $info;
public function __construct($type) {
$this->type = $type;
$this->info = entity_get_info($type);
$this->info['entity keys'] += array('module' => 'module', 'status' => 'status');
$this->statusKey = $this->info['entity keys']['status'];
$this->moduleKey = $this->info['entity keys']['module'];
if (!empty($this->info['bundle of'])) {
$entity_info = entity_get_info($this->info['bundle of']);
$this->bundleKey = $entity_info['bundle keys']['bundle'];
}
}
/**
* Defines the result for hook_features_api().
*/
public function api() {
return array(
// The entity type has to be the features component name.
$this->type => array(
'name' => $this->info['label'],
'feature_source' => TRUE,
'default_hook' => isset($this->info['export']['default hook']) ? $this->info['export']['default hook'] : 'default_' . $this->type,
// Use the provided component callbacks making use of the controller.
'base' => 'entity',
'file' => drupal_get_path('module', 'entity') . '/entity.features.inc',
),
);
}
/**
* Generates the result for hook_features_export_options().
*/
public function export_options() {
$options = array();
foreach (entity_load_multiple_by_name($this->type, FALSE) as $name => $entity) {
$options[$name] = entity_label($this->type, $entity);
}
return $options;
}
/**
* Generates the result for hook_features_export().
*/
public function export($data, &$export, $module_name = '') {
$pipe = array();
foreach (entity_load_multiple_by_name($this->type, $data) as $name => $entity) {
// If this entity is provided by a different module, add it as dependency.
if (($entity->{$this->statusKey} & ENTITY_IN_CODE) && $entity->{$this->moduleKey} != $module_name) {
$module = $entity->{$this->moduleKey};
$export['dependencies'][$module] = $module;
}
// Otherwise export the entity.
else {
$export['features'][$this->type][$name] = $name;
// If this is a bundle of a fieldable entity, add its fields to the pipe.
if (!empty($this->info['bundle of'])) {
$fields = field_info_instances($this->info['bundle of'], $entity->{$this->bundleKey});
foreach ($fields as $name => $field) {
$pipe['field'][] = "{$field['entity_type']}-{$field['bundle']}-{$field['field_name']}";
}
}
}
}
// Add the module providing the entity type as dependency.
if ($data && !empty($this->info['module'])) {
$export['dependencies'][$this->info['module']] = $this->info['module'];
// In case entity is not already an indirect dependency, add it.
// We can do so without causing redundant dependencies because,
// if entity is an indirect dependency, Features will filter it out.
$export['dependencies']['entity'] = 'entity';
}
return $pipe;
}
/**
* Generates the result for hook_features_export_render().
*/
function export_render($module, $data, $export = NULL) {
$output = array();
$output[] = ' $items = array();';
foreach (entity_load_multiple_by_name($this->type, $data) as $name => $entity) {
$export = " \$items['$name'] = entity_import('{$this->type}', '";
// Make sure to escape the characters \ and '.
$export .= addcslashes(entity_export($this->type, $entity, ' '), '\\\'');
$export .= "');";
$output[] = $export;
}
$output[] = ' return $items;';
$output = implode("\n", $output);
$hook = isset($this->info['export']['default hook']) ? $this->info['export']['default hook'] : 'default_' . $this->type;
return array($hook => $output);
}
/**
* Generates the result for hook_features_revert().
*/
function revert($module = NULL) {
if ($defaults = features_get_default($this->type, $module)) {
foreach ($defaults as $name => $entity) {
entity_delete($this->type, $name);
}
}
}
}
/**
* Implements of hook_features_api().
*/
function entity_features_api() {
$items = array();
foreach (entity_crud_get_info() as $type => $info) {
if (!empty($info['exportable']) && $controller = entity_features_get_controller($type)) {
$items += $controller->api();
}
}
return $items;
}
/**
* Features component callback.
*/
function entity_features_export_options($entity_type) {
return entity_features_get_controller($entity_type)->export_options();
}
/**
* Features component callback.
*/
function entity_features_export($data, &$export, $module_name = '', $entity_type) {
return entity_features_get_controller($entity_type)->export($data, $export, $module_name);
}
/**
* Features component callback.
*/
function entity_features_export_render($module, $data, $export = NULL, $entity_type) {
return entity_features_get_controller($entity_type)->export_render($module, $data, $export);
}
/**
* Features component callback.
*/
function entity_features_revert($module = NULL, $entity_type) {
return entity_features_get_controller($entity_type)->revert($module);
}

View File

@@ -0,0 +1,210 @@
<?php
/**
* @file
* Internationalization (i18n) integration.
*/
/**
* Gets the i18n controller for a given entity type.
*
* @return EntityDefaultI18nStringController|array|false
* If a type is given, the controller for the given entity type. Else an array
* of all enabled controllers keyed by entity type is returned.
*/
function entity_i18n_controller($type = NULL) {
$static = &drupal_static(__FUNCTION__);
if (!isset($type)) {
// Invoke the function for each type to ensure we have fully populated the
// static variable.
foreach (entity_get_info() as $entity_type => $info) {
entity_i18n_controller($entity_type);
}
return array_filter($static);
}
if (!isset($static[$type])) {
$info = entity_get_info($type);
// Do not activate it by default. Modules have to explicitly enable it by
// specifying EntityDefaultI18nStringController or their customization.
$class = isset($info['i18n controller class']) ? $info['i18n controller class'] : FALSE;
$static[$type] = $class ? new $class($type, $info) : FALSE;
}
return $static[$type];
}
/**
* Implements hook_i18n_string_info().
*/
function entity_i18n_string_info() {
$groups = array();
foreach (entity_i18n_controller() as $entity_type => $controller) {
$groups += $controller->hook_string_info();
}
return $groups;
}
/**
* Implements hook_i18n_object_info().
*/
function entity_i18n_object_info() {
$info = array();
foreach (entity_i18n_controller() as $entity_type => $controller) {
$info += $controller->hook_object_info();
}
return $info;
}
/**
* Implements hook_i18n_string_objects().
*/
function entity_i18n_string_objects($type) {
if ($controller = entity_i18n_controller($type)) {
return $controller->hook_string_objects();
}
}
/**
* Default controller handling i18n integration.
*
* Implements i18n string translation for all non-field properties marked as
* 'translatable' and having the flag 'i18n string' set. This translation
* approach fits in particular for translating configuration, i.e. exportable
* entities.
*
* Requirements for the default controller:
* - The entity type providing module must be specified using the 'module' key
* in hook_entity_info().
* - An 'entity class' derived from the provided class 'Entity' must be used.
* - Properties must be declared as 'translatable' and the 'i18n string' flag
* must be set to TRUE using hook_entity_property_info().
* - i18n must be notified about changes manually by calling
* i18n_string_object_update(), i18n_string_object_remove() and
* i18n_string_update_context(). Ideally, this is done in a small integration
* module depending on the entity API and i18n_string. Look at the provided
* testing module "entity_test_i18n" for an example.
* - If the entity API admin UI is used, the "translate" tab will be
* automatically enabled and linked from the UI.
* - There are helpers for getting translated values which work regardless
* whether the i18n_string module is enabled, i.e. entity_i18n_string()
* and Entity::getTranslation().
*
* Current limitations:
* - Translatable property values cannot be updated via the metadata wrapper,
* however reading works fine. See Entity::getTranslation().
*/
class EntityDefaultI18nStringController {
protected $entityType, $entityInfo;
/**
* The i18n textgroup we are using.
*/
protected $textgroup;
public function __construct($type) {
$this->entityType = $type;
$this->entityInfo = entity_get_info($type);
// By default we go with the module name as textgroup.
$this->textgroup = $this->entityInfo['module'];
}
/**
* Implements hook_i18n_string_info() via entity_i18n_string_info().
*/
public function hook_string_info() {
$list = system_list('module_enabled');
$info = $list[$this->textgroup]->info;
$groups[$this->textgroup] = array(
'title' => $info['name'],
'description' => !empty($info['description']) ? $info['description'] : NULL,
'format' => FALSE,
'list' => TRUE,
);
return $groups;
}
/**
* Implements hook_i18n_object_info() via entity_i18n_object_info().
*
* Go with the same default values as the admin UI as far as possible.
*/
public function hook_object_info() {
$wildcard = $this->menuWildcard();
$id_key = !empty($this->entityInfo['entity keys']['name']) ? $this->entityInfo['entity keys']['name'] : $this->entityInfo['entity keys']['id'];
$info[$this->entityType] = array(
// Generic object title.
'title' => $this->entityInfo['label'],
// The object key field.
'key' => $id_key,
// Placeholders for automatic paths.
'placeholders' => array(
$wildcard => $id_key,
),
// Properties for string translation.
'string translation' => array(
// Text group that will handle this object's strings.
'textgroup' => $this->textgroup,
// Object type property for string translation.
'type' => $this->entityType,
// Translatable properties of these objects.
'properties' => $this->translatableProperties(),
),
);
// Integrate the translate tab into the admin-UI if enabled.
if ($base_path = $this->menuBasePath()) {
$info[$this->entityType] += array(
// To produce edit links automatically.
'edit path' => $base_path . '/manage/' . $wildcard,
// Auto-generate translate tab.
'translate tab' => $base_path . '/manage/' . $wildcard . '/translate',
);
$info[$this->entityType]['string translation'] += array(
// Path to translate strings to every language.
'translate path' => $base_path . '/manage/' . $wildcard . '/translate/%i18n_language',
);
}
return $info;
}
/**
* Defines the menu base path used by self::hook_object_info().
*/
protected function menuBasePath() {
return !empty($this->entityInfo['admin ui']['path']) ? $this->entityInfo['admin ui']['path'] : FALSE;
}
/**
* Defines the menu wildcard used by self::hook_object_info().
*/
protected function menuWildcard() {
return isset($this->entityInfo['admin ui']['menu wildcard']) ? $this->entityInfo['admin ui']['menu wildcard'] : '%entity_object';
}
/**
* Defines translatable properties used by self::hook_object_info().
*/
protected function translatableProperties() {
$list = array();
foreach (entity_get_all_property_info($this->entityType) as $name => $info) {
if (!empty($info['translatable']) && !empty($info['i18n string'])) {
$list[$name] = array(
'title' => $info['label'],
);
}
}
return $list;
}
/**
* Implements hook_i18n_string_objects() via entity_i18n_string_objects().
*/
public function hook_string_objects() {
return entity_load_multiple_by_name($this->entityType, FALSE);
}
}

View File

@@ -0,0 +1,33 @@
name = Entity API
description = Enables modules to work with any entity type and to provide entities.
core = 7.x
files[] = entity.features.inc
files[] = entity.i18n.inc
files[] = entity.info.inc
files[] = entity.rules.inc
files[] = entity.test
files[] = includes/entity.inc
files[] = includes/entity.controller.inc
files[] = includes/entity.ui.inc
files[] = includes/entity.wrapper.inc
files[] = views/entity.views.inc
files[] = views/handlers/entity_views_field_handler_helper.inc
files[] = views/handlers/entity_views_handler_area_entity.inc
files[] = views/handlers/entity_views_handler_field_boolean.inc
files[] = views/handlers/entity_views_handler_field_date.inc
files[] = views/handlers/entity_views_handler_field_duration.inc
files[] = views/handlers/entity_views_handler_field_entity.inc
files[] = views/handlers/entity_views_handler_field_field.inc
files[] = views/handlers/entity_views_handler_field_numeric.inc
files[] = views/handlers/entity_views_handler_field_options.inc
files[] = views/handlers/entity_views_handler_field_text.inc
files[] = views/handlers/entity_views_handler_field_uri.inc
files[] = views/handlers/entity_views_handler_relationship_by_bundle.inc
files[] = views/handlers/entity_views_handler_relationship.inc
files[] = views/plugins/entity_views_plugin_row_entity_view.inc
; Information added by drupal.org packaging script on 2012-12-25
version = "7.x-1.0"
core = "7.x"
project = "entity"
datestamp = "1356471145"

View File

@@ -0,0 +1,181 @@
<?php
/**
* @file
* Provides basic entity property info for entities provided via the CRUD API,
* as well as property info for all entity types defined by core. For that
* the respective modules/MODULE.info.inc files are included.
*/
/**
* Implements hook_entity_property_info().
*/
function entity_entity_property_info() {
$items = array();
// Add in info about entities provided by the CRUD API.
foreach (entity_crud_get_info() as $type => $info) {
// Automatically enable the controller only if the module does not implement
// the hook itself.
if (!isset($info['metadata controller class']) && !empty($info['base table']) && (!isset($info['module']) || !module_hook($info['module'], 'entity_property_info'))) {
$info['metadata controller class'] = 'EntityDefaultMetadataController';
}
if (!empty($info['metadata controller class'])) {
$controller = new $info['metadata controller class']($type);
$items += $controller->entityPropertyInfo();
}
}
// Add in info for all core entities.
foreach (_entity_metadata_core_modules() as $module) {
module_load_include('inc', 'entity', "modules/$module.info");
if (function_exists($function = "entity_metadata_{$module}_entity_property_info")) {
if ($return = $function()) {
$items = array_merge_recursive($items, $return);
}
}
}
return $items;
}
/**
* Implements hook_entity_property_info_alter().
*/
function entity_entity_property_info_alter(&$entity_info) {
// Add in info for all core entities.
foreach (_entity_metadata_core_modules() as $module) {
module_load_include('inc', 'entity', "modules/$module.info");
if (function_exists($function = "entity_metadata_{$module}_entity_property_info_alter")) {
$function($entity_info);
}
}
}
function _entity_metadata_core_modules() {
return array_filter(array('book', 'comment', 'field', 'locale', 'node', 'taxonomy', 'user', 'system', 'statistics'), 'module_exists');
}
/**
* Default controller for generating some basic metadata for CRUD entity types.
*/
class EntityDefaultMetadataController {
protected $type, $info;
public function __construct($type) {
$this->type = $type;
$this->info = entity_get_info($type);
}
public function entityPropertyInfo() {
$entity_label = drupal_strtolower($this->info['label']);
// Provide defaults based on the schema.
$info['properties'] = $this->convertSchema();
foreach ($info['properties'] as $name => &$property) {
// Add a description.
$property['description'] = t('@entity "@property" property.', array('@entity' => drupal_ucfirst($entity_label), '@property' => $name));
}
// Set better metadata for known entity keys.
$id_key = $this->info['entity keys']['id'];
if (!empty($this->info['entity keys']['name']) && $key = $this->info['entity keys']['name']) {
$info['properties'][$key]['type'] = 'token';
$info['properties'][$key]['label'] = t('Machine-readable name');
$info['properties'][$key]['description'] = t('The machine-readable name identifying this @entity.', array('@entity' => $entity_label));
$info['properties'][$id_key]['label'] = t('Internal, numeric @entity ID', array('@entity' => $entity_label));
$info['properties'][$id_key]['description'] = t('The ID used to identify this @entity internally.', array('@entity' => $entity_label));
}
else {
$info['properties'][$id_key]['label'] = t('@entity ID', array('@entity' => drupal_ucfirst($entity_label)));
$info['properties'][$id_key]['description'] = t('The unique ID of the @entity.', array('@entity' => $entity_label));
}
// Care for the bundle.
if (!empty($this->info['entity keys']['bundle']) && $key = $this->info['entity keys']['bundle']) {
$info['properties'][$key]['type'] = 'token';
$info['properties'][$key]['options list'] = array(get_class($this), 'bundleOptionsList');
}
// Care for the label.
if (!empty($this->info['entity keys']['label']) && $key = $this->info['entity keys']['label']) {
$info['properties'][$key]['label'] = t('Label');
$info['properties'][$key]['description'] = t('The human readable label.');
}
// Add a computed property for the entity URL and expose it to views.
if (empty($info['properties']['url']) && !empty($this->info['uri callback'])) {
$info['properties']['url'] = array(
'label' => t('URL'),
'description' => t('The URL of the entity.'),
'getter callback' => 'entity_metadata_entity_get_properties',
'type' => 'uri',
'computed' => TRUE,
'entity views field' => TRUE,
);
}
return array($this->type => $info);
}
/**
* A options list callback returning all bundles for an entity type.
*/
public static function bundleOptionsList($name, $info) {
if (!empty($info['parent']) && $type = $info['parent']) {
$entity_info = $info['parent']->entityInfo();
$options = array();
foreach ($entity_info['bundles'] as $name => $bundle_info) {
$options[$name] = $bundle_info['label'];
}
return $options;
}
}
/**
* Return a set of properties for an entity based on the schema definition
*/
protected function convertSchema() {
return entity_metadata_convert_schema($this->info['base table']);
}
}
/**
* Converts the schema information available for the given table to property info.
*
* @param $table
* The name of the table as used in hook_schema().
* @return
* An array of property info as suiting for hook_entity_property_info().
*/
function entity_metadata_convert_schema($table) {
$schema = drupal_get_schema($table);
$properties = array();
foreach ($schema['fields'] as $name => $info) {
if ($type = _entity_metadata_convert_schema_type($info['type'])) {
$properties[$name] = array(
'type' => $type,
'label' => drupal_ucfirst($name),
'schema field' => $name,
// As we cannot know about any setter access, leave out the setter
// callback. For getting usually no further access callback is needed.
);
if ($info['type'] == 'serial') {
$properties[$name]['validation callback'] = 'entity_metadata_validate_integer_positive';
}
}
}
return $properties;
}
function _entity_metadata_convert_schema_type($type) {
switch ($type) {
case 'int':
case 'serial':
return 'integer';
case 'float':
case 'numeric':
return 'decimal';
case 'char':
case 'varchar':
case 'text':
return 'text';
}
}

View File

@@ -0,0 +1,28 @@
<?php
/**
* @file
* Install file for the entity API.
*/
/**
* The entity API modules have been merged into a single module.
*/
function entity_update_7000() {
// This empty update is required such that all caches are cleared as
// necessary.
}
/**
* Remove the deprecated 'entity_defaults_built' variable.
*/
function entity_update_7001() {
variable_del('entity_defaults_built');
}
/**
* Clear caches and rebuild registry.
*/
function entity_update_7002() {
// Do nothing, update.php clears cache for us in case there is an update.
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,118 @@
<?php
/**
* @file
* Provides Rules integration for entities provided via the CRUD API.
*
* Rules automatically provides us with actions for CRUD and a suiting entity
* data type. For events the controller automatically invokes Rules events once
* Rules is active, so we just have to provide the appropriate info.
*/
/**
* Default controller for generating Rules integration.
*/
class EntityDefaultRulesController {
protected $type, $info;
public function __construct($type) {
$this->type = $type;
$this->info = entity_get_info($type);
}
public function eventInfo() {
$info = $this->info;
$type = $this->type;
$label = $info['label'];
$defaults = array(
'module' => isset($info['module']) ? $info['module'] : 'entity',
'group' => $label,
'access callback' => 'entity_rules_integration_event_access',
);
$items[$type . '_insert'] = $defaults + array(
'label' => t('After saving a new @entity', array('@entity' => drupal_strtolower($label))),
'variables' => entity_rules_events_variables($type, t('created @entity', array('@entity' => drupal_strtolower($label)))),
);
$items[$type . '_update'] = $defaults + array(
'label' => t('After updating an existing @entity', array('@entity' => drupal_strtolower($label))),
'variables' => entity_rules_events_variables($type, t('updated @entity', array('@entity' => drupal_strtolower($label))), TRUE),
);
$items[$type . '_presave'] = $defaults + array(
'label' => t('Before saving a @entity', array('@entity' => drupal_strtolower($label))),
'variables' => entity_rules_events_variables($type, t('saved @entity', array('@entity' => drupal_strtolower($label))), TRUE),
);
$items[$type . '_delete'] = $defaults + array(
'label' => t('After deleting a @entity', array('@entity' => drupal_strtolower($label))),
'variables' => entity_rules_events_variables($type, t('deleted @entity', array('@entity' => drupal_strtolower($label)))),
);
if (count($info['view modes'])) {
$items[$type . '_view'] = $defaults + array(
'label' => t('@entity is viewed', array('@entity' => $label)),
'variables' => entity_rules_events_variables($type, t('viewed @entity', array('@entity' => drupal_strtolower($label)))) + array(
'view_mode' => array(
'type' => 'text',
'label' => t('view mode'),
'options list' => 'rules_get_entity_view_modes',
// Add the entity-type for the options list callback.
'options list entity type' => $type,
),
),
);
}
// Specify that on presave the entity is saved anyway.
$items[$type . '_presave']['variables'][$type]['skip save'] = TRUE;
return $items;
}
}
/**
* Returns some parameter info suiting for the specified entity type.
*/
function entity_rules_events_variables($type, $label, $update = FALSE) {
$args = array(
$type => array('type' => $type, 'label' => $label),
);
if ($update) {
$args += array(
$type . '_unchanged' => array(
'type' => $type,
'label' => t('unchanged entity'),
'handler' => 'rules_events_entity_unchanged',
),
);
}
return $args;
}
/**
* Implements hook_rules_event_info().
*/
function entity_rules_event_info() {
$items = array();
foreach (entity_crud_get_info() as $type => $info) {
// By default we enable the controller only for non-configuration.
$configuration = !empty($info['configuration']) || !empty($info['exportable']);
$info += array('rules controller class' => $configuration ? FALSE : 'EntityDefaultRulesController');
if ($info['rules controller class']) {
$controller = new $info['rules controller class']($type);
$items += $controller->eventInfo();
}
}
return $items;
}
/**
* Rules integration access callback.
*/
function entity_rules_integration_event_access($type, $event_name) {
// Cut of _insert/_update/.. from the event name.
$entity_type = substr($event_name, 0, strrpos($event_name, '_'));
$result = entity_access('view', $entity_type);
// If no access callback is given, just grant access for viewing.
return isset($result) ? $result : TRUE;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,48 @@
<?php
/**
* @file
* Default theme implementation for entities.
*
* Available variables:
* - $content: An array of comment items. Use render($content) to print them all, or
* print a subset such as render($content['field_example']). Use
* hide($content['field_example']) to temporarily suppress the printing of a
* given element.
* - $title: The (sanitized) entity label.
* - $url: Direct url of the current entity if specified.
* - $page: Flag for the full page state.
* - $classes: String of classes that can be used to style contextually through
* CSS. It can be manipulated through the variable $classes_array from
* preprocess functions. By default the following classes are available, where
* the parts enclosed by {} are replaced by the appropriate values:
* - entity-{ENTITY_TYPE}
* - {ENTITY_TYPE}-{BUNDLE}
*
* Other variables:
* - $classes_array: Array of html class attribute values. It is flattened
* into a string within the variable $classes.
*
* @see template_preprocess()
* @see template_preprocess_entity()
* @see template_process()
*/
?>
<div class="<?php print $classes; ?> clearfix"<?php print $attributes; ?>>
<?php if (!$page): ?>
<h2<?php print $title_attributes; ?>>
<?php if ($url): ?>
<a href="<?php print $url; ?>"><?php print $title; ?></a>
<?php else: ?>
<?php print $title; ?>
<?php endif; ?>
</h2>
<?php endif; ?>
<div class="content"<?php print $content_attributes; ?>>
<?php
print render($content);
?>
</div>
</div>

View File

@@ -0,0 +1,13 @@
name = Entity tokens
description = Provides token replacements for all properties that have no tokens and are known to the entity API.
core = 7.x
files[] = entity_token.tokens.inc
files[] = entity_token.module
dependencies[] = entity
; Information added by drupal.org packaging script on 2012-12-25
version = "7.x-1.0"
core = "7.x"
project = "entity"
datestamp = "1356471145"

View File

@@ -0,0 +1,6 @@
<?php
/**
* @file
* Module file for the entity tokens module. Drupal needs this file.
*/

View File

@@ -0,0 +1,341 @@
<?php
/**
* @file
* Provides tokens for entity properties which have no token yet.
*/
/**
* Defines the types of properties to be added as token.
*
* @return
* An array mapping token types to the usual (entity) type names.
*/
function entity_token_types() {
$return = entity_token_types_chained();
return $return + drupal_map_assoc(array('text', 'integer', 'decimal', 'duration', 'boolean', 'uri'));
}
/**
* Defines a list of token types that need to be chained.
*
* @return
* If a (token) type is given, whether the given type needs to be chained.
* Else a full list of token types to be chained as returned by
* entity_token_token_types().
*/
function entity_token_types_chained($type = NULL) {
// This functions gets called rather often when replacing tokens, thus
// we statically cache $types using the advanced drupal static pattern.
static $drupal_static_fast;
if (!isset($drupal_static_fast)) {
$drupal_static_fast['types'] = &drupal_static(__FUNCTION__, array());
}
$types = &$drupal_static_fast['types'];
if (!$types) {
// Add entities.
foreach (entity_get_info() as $entity_type => $info) {
if ($token_type = isset($info['token type']) ? $info['token type'] : $entity_type) {
$types[$token_type] = $entity_type;
}
}
// Add 'date' and 'site' tokens.
$types['date'] = 'date';
$types['site'] = 'site';
// Add a 'struct' type.
$types['struct'] = 'struct';
}
if (isset($type)) {
return isset($types[$type]) || entity_property_list_extract_type($type);
}
return $types;
}
/**
* Gets the right token type for a given property info array.
*/
function _entity_token_map_to_token_type($property_info) {
$lookup = &drupal_static(__FUNCTION__);
if (!$lookup) {
// Initialize a lookup array mapping property types to token types.
$lookup = array_flip(entity_token_types());
}
$type = isset($property_info['type']) ? $property_info['type'] : 'text';
// Just use the type 'struct' for all structures.
if (!empty($property_info['property info'])) {
$type = 'struct';
}
if ($item_type = entity_property_list_extract_type($type)) {
return isset($lookup[$item_type]) ? "list<$lookup[$item_type]>" : FALSE;
}
return isset($lookup[$type]) ? $lookup[$type] : FALSE;
}
/**
* Implements hook_token_info_alter().
*/
function entity_token_token_info_alter(&$info) {
$entity_info = entity_get_info();
$token_types = entity_token_types_chained();
// Loop over all chain-able token types, as those may contain further tokens,
// e.g. entity types or 'site'.
foreach ($token_types as $token_type => $type) {
// Just add all properties regardless whether it's in a bundle, but only if
// there is no token of the property yet.
foreach (entity_get_all_property_info($type) as $name => $property) {
$name = str_replace('_', '-', $name);
$property += array('type' => 'text', 'description' => $property['label']);
$property_token_type = _entity_token_map_to_token_type($property);
if (!isset($info['tokens'][$token_type][$name]) && $property_token_type) {
$info['tokens'][$token_type][$name] = array(
'name' => $property['label'],
'description' => $property['description'],
'type' => $property_token_type,
// Mark the token so we know we have to provide the value afterwards.
'entity-token' => TRUE,
);
}
if ($property_token_type == 'struct' && !empty($property['property info'])) {
$info['tokens'][$token_type][$name]['dynamic'] = TRUE;
$help = array();
foreach ($property['property info'] as $key => $property_info) {
$help[] = $key . ' (' . $property_info['label'] . ')';
}
$info['tokens'][$token_type][$name]['description'] .= ' ' . t('The following properties may be appended to the token: @keys',
array('@keys' => implode(', ', $help))
);
}
}
}
// Make sure all chain-able token types we support are registered.
foreach ($token_types as $token_type => $type) {
if (!empty($info['tokens'][$token_type]) && !isset($info['types'][$token_type])) {
if (isset($entity_info[$type])) {
$info['types'][$token_type] = array(
'name' => $entity_info[$type]['label'],
'description' => t('Tokens related to the "@name" entities.', array('@name' => $entity_info[$type]['label'])),
'needs-data' => $token_type,
);
}
else {
$info['types'][$token_type] = array(
'name' => drupal_strtoupper($token_type),
'description' => t('@name tokens.', array('@name' => drupal_strtoupper($token_type))),
'needs-data' => $token_type,
);
}
}
if (!empty($info['tokens'][$token_type]) && !isset($info['types']["list<$token_type>"]) && $token_type != 'site') {
if (isset($entity_info[$type])) {
$info['types']["list<$token_type>"] = array(
'name' => t('List of @entities', array('@entities' => isset($entity_info[$type]['plural label']) ? $entity_info[$type]['plural label'] : $entity_info[$type]['label'] . 's')),
'description' => t('Tokens related to the "@name" entities.', array('@name' => $entity_info[$type]['label'])),
'needs-data' => "list<$token_type>",
);
}
else {
$info['types']["list<$token_type>"] = array(
'name' => t('List of @type values', array('@type' => $token_type)),
'description' => t('Tokens for lists of @type values.', array('@type' => $token_type)),
'needs-data' => "list<$token_type>",
);
}
// Also add some basic token replacements for lists...
for ($i = 0; $i < 4; $i++) {
$info['tokens']["list<$token_type>"][$i] = array(
'name' => t('@type with delta @delta', array('@delta' => $i, '@type' => $info['types'][$token_type]['name'])),
'description' => t('The list item with delta @delta. Delta values start from 0 and are incremented by one per list item.', array('@delta' => $i)),
'type' => $token_type,
);
}
}
}
}
/**
* Implements hook_tokens().
*/
function entity_token_tokens($type, $tokens, array $data = array(), array $options = array()) {
$token_types = entity_token_types_chained();
$replacements = array();
if (isset($token_types[$type]) && (!empty($data[$type]) || $type == 'site')) {
$data += array($type => FALSE);
// Make use of token module's token cache if available.
$info = module_exists('token') ? token_get_info() : token_info();
foreach ($tokens as $name => $original) {
// Provide the token for all properties marked to stem from us.
if (!empty($info['tokens'][$type][$name]['entity-token']) || $type == 'struct') {
$wrapper = !isset($wrapper) ? _entity_token_wrap_data($type, $token_types[$type], $data[$type], $options) : $wrapper;
$property_name = str_replace('-', '_', $name);
try {
$replacement = _entity_token_get_token($wrapper->$property_name, $options);
if (isset($replacement)) {
$replacements[$original] = $replacement;
}
}
catch (EntityMetadataWrapperException $e) {
// If tokens for not existing values are requested, just do nothing.
}
}
}
// Properly chain everything of a type marked as needs chaining.
$info['tokens'] += array($type => array());
foreach ($info['tokens'][$type] as $name => $token_info) {
if (!empty($token_info['entity-token']) && isset($token_info['type']) && entity_token_types_chained($token_info['type'])) {
if ($chained_tokens = token_find_with_prefix($tokens, $name)) {
$wrapper = !isset($wrapper) ? _entity_token_wrap_data($type, $token_types[$type], $data[$type], $options) : $wrapper;
$property_name = str_replace('-', '_', $name);
try {
// Pass on 'struct' properties wrapped, else un-wrap the data.
$value = ($token_info['type'] == 'struct') ? $wrapper->$property_name : $wrapper->$property_name->value();
$replacements += token_generate($token_info['type'], $chained_tokens, array($token_info['type'] => $value), $options);
}
catch (EntityMetadataWrapperException $e) {
// If tokens for not existing values are requested, just do nothing.
}
}
}
}
}
// Add support for evaluating tokens for "list<type"> types.
elseif ($item_token_type = entity_property_list_extract_type($type)) {
foreach ($tokens as $name => $original) {
// Care about getting entries of a list.
if (is_numeric($name)) {
$wrapper = !isset($wrapper) ? _entity_token_wrap_data($type, "list<$token_types[$item_token_type]>", $data[$type], $options) : $wrapper;
try {
$replacement = _entity_token_get_token($wrapper->get($name), $options);
if (isset($replacement)) {
$replacements[$original] = $replacement;
}
}
catch (EntityMetadataWrapperException $e) {
// If tokens for not existing values are requested, just do nothing.
}
}
// Care about generating chained tokens for list-items.
else {
$parts = explode(':', $name, 2);
$delta = $parts[0];
if (is_numeric($delta) && $chained_tokens = token_find_with_prefix($tokens, $delta)) {
$wrapper = !isset($wrapper) ? _entity_token_wrap_data($type, "list<$token_types[$item_token_type]>", $data[$type], $options) : $wrapper;
try {
$replacements += token_generate($item_token_type, $chained_tokens, array($item_token_type => $wrapper->get($delta)->value()), $options);
}
catch (EntityMetadataWrapperException $e) {
// If tokens for not existing values are requested, just do nothing.
}
}
}
}
}
// Add support for chaining struct data. As struct data has no registered
// tokens, we have to chain based upon wrapper property info.
if ($type == 'struct') {
$wrapper = $data[$type];
foreach ($wrapper as $name => $property) {
$token_type = _entity_token_map_to_token_type($property->info());
if (entity_token_types_chained($token_type) && $chained_tokens = token_find_with_prefix($tokens, $name)) {
try {
// Pass on 'struct' properties wrapped, else un-wrap the data.
$value = ($token_type == 'struct') ? $property : $property->value();
$replacements += token_generate($token_type, $chained_tokens, array($token_type => $value), $options);
}
catch (EntityMetadataWrapperException $e) {
// If tokens for not existing values are requested, just do nothing.
}
}
}
}
return $replacements;
}
/**
* Wraps the given data by correctly obeying the options.
*/
function _entity_token_wrap_data($token_type, $type, $data, $options) {
if ($type == 'site') {
$wrapper = entity_metadata_site_wrapper();
}
elseif ($type == 'struct') {
// 'struct' data items are passed on wrapped.
$wrapper = $data;
}
else {
$wrapper = entity_metadata_wrapper($type, $data);
}
if (isset($options['language']) && $wrapper instanceof EntityStructureWrapper) {
$wrapper->language($options['language']->language);
}
return $wrapper;
}
/**
* Gets the token replacement by correctly obeying the options.
*/
function _entity_token_get_token($wrapper, $options) {
if ($wrapper->value() === NULL) {
// Do not provide a replacement if there is no value.
return NULL;
}
if (empty($options['sanitize'])) {
// When we don't need sanitized tokens decode already sanitizied texts.
$options['decode'] = TRUE;
}
$langcode = isset($options['language']) ? $options['language']->language : NULL;
// If there is a label for a property, e.g. defined by an options list or an
// entity label, make use of it.
if ($label = $wrapper->label()) {
return empty($options['sanitize']) ? $label : check_plain($label);
}
switch ($wrapper->type()) {
case 'integer':
return $wrapper->value();
case 'decimal':
return number_format($wrapper->value(), 2);
case 'date':
return format_date($wrapper->value(), 'medium', '', NULL, $langcode);
case 'duration':
return format_interval($wrapper->value(), 2, $langcode);
case 'boolean':
return $wrapper->value() ? t('true') : t('false');
case 'uri':
case 'text':
return $wrapper->value($options);
}
// Care for outputing list values.
if ($wrapper instanceof EntityListWrapper) {
$output = array();
foreach ($wrapper as $item) {
$output[] = _entity_token_get_token($item, $options);
}
return implode(', ', $output);
}
// Else we do not have a good string to output, e.g. for struct values. Just
// output the string representation of the wrapper.
return (string) $wrapper;
}

View File

@@ -0,0 +1,944 @@
<?php
/**
* @file
* Provides a controller building upon the core controller but providing more
* features like full CRUD functionality.
*/
/**
* Interface for EntityControllers compatible with the entity API.
*/
interface EntityAPIControllerInterface extends DrupalEntityControllerInterface {
/**
* Delete permanently saved entities.
*
* In case of failures, an exception is thrown.
*
* @param $ids
* An array of entity IDs.
*/
public function delete($ids);
/**
* Invokes a hook on behalf of the entity. For hooks that have a respective
* field API attacher like insert/update/.. the attacher is called too.
*/
public function invoke($hook, $entity);
/**
* Permanently saves the given entity.
*
* In case of failures, an exception is thrown.
*
* @param $entity
* The entity to save.
*
* @return
* SAVED_NEW or SAVED_UPDATED is returned depending on the operation
* performed.
*/
public function save($entity);
/**
* Create a new entity.
*
* @param array $values
* An array of values to set, keyed by property name.
* @return
* A new instance of the entity type.
*/
public function create(array $values = array());
/**
* Exports an entity as serialized string.
*
* @param $entity
* The entity to export.
* @param $prefix
* An optional prefix for each line.
*
* @return
* The exported entity as serialized string. The format is determined by
* the controller and has to be compatible with the format that is accepted
* by the import() method.
*/
public function export($entity, $prefix = '');
/**
* Imports an entity from a string.
*
* @param string $export
* An exported entity as serialized string.
*
* @return
* An entity object not yet saved.
*/
public function import($export);
/**
* Builds a structured array representing the entity's content.
*
* The content built for the entity will vary depending on the $view_mode
* parameter.
*
* @param $entity
* An entity object.
* @param $view_mode
* View mode, e.g. 'full', 'teaser'...
* @param $langcode
* (optional) A language code to use for rendering. Defaults to the global
* content language of the current request.
* @return
* The renderable array.
*/
public function buildContent($entity, $view_mode = 'full', $langcode = NULL);
/**
* Generate an array for rendering the given entities.
*
* @param $entities
* An array of entities to render.
* @param $view_mode
* View mode, e.g. 'full', 'teaser'...
* @param $langcode
* (optional) A language code to use for rendering. Defaults to the global
* content language of the current request.
* @param $page
* (optional) If set will control if the entity is rendered: if TRUE
* the entity will be rendered without its title, so that it can be embeded
* in another context. If FALSE the entity will be displayed with its title
* in a mode suitable for lists.
* If unset, the page mode will be enabled if the current path is the URI
* of the entity, as returned by entity_uri().
* This parameter is only supported for entities which controller is a
* EntityAPIControllerInterface.
* @return
* The renderable array, keyed by entity name or numeric id.
*/
public function view($entities, $view_mode = 'full', $langcode = NULL, $page = NULL);
}
/**
* Interface for EntityControllers of entities that support revisions.
*/
interface EntityAPIControllerRevisionableInterface extends EntityAPIControllerInterface {
/**
* Delete an entity revision.
*
* Note that the default revision of an entity cannot be deleted.
*
* @param $revision_id
* The ID of the revision to delete.
*
* @return boolean
* TRUE if the entity revision could be deleted, FALSE otherwise.
*/
public function deleteRevision($revision_id);
}
/**
* A controller implementing EntityAPIControllerInterface for the database.
*/
class EntityAPIController extends DrupalDefaultEntityController implements EntityAPIControllerRevisionableInterface {
protected $cacheComplete = FALSE;
protected $bundleKey;
protected $defaultRevisionKey;
/**
* Overridden.
* @see DrupalDefaultEntityController#__construct()
*/
public function __construct($entityType) {
parent::__construct($entityType);
// If this is the bundle of another entity, set the bundle key.
if (isset($this->entityInfo['bundle of'])) {
$info = entity_get_info($this->entityInfo['bundle of']);
$this->bundleKey = $info['bundle keys']['bundle'];
}
$this->defaultRevisionKey = !empty($this->entityInfo['entity keys']['default revision']) ? $this->entityInfo['entity keys']['default revision'] : 'default_revision';
}
/**
* Overrides DrupalDefaultEntityController::buildQuery().
*/
protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) {
$query = parent::buildQuery($ids, $conditions, $revision_id);
if ($this->revisionKey) {
// Compare revision id of the base and revision table, if equal then this
// is the default revision.
$query->addExpression('base.' . $this->revisionKey . ' = revision.' . $this->revisionKey, $this->defaultRevisionKey);
}
return $query;
}
/**
* Builds and executes the query for loading.
*
* @return The results in a Traversable object.
*/
public function query($ids, $conditions, $revision_id = FALSE) {
// Build the query.
$query = $this->buildQuery($ids, $conditions, $revision_id);
$result = $query->execute();
if (!empty($this->entityInfo['entity class'])) {
$result->setFetchMode(PDO::FETCH_CLASS, $this->entityInfo['entity class'], array(array(), $this->entityType));
}
return $result;
}
/**
* Overridden.
* @see DrupalDefaultEntityController#load($ids, $conditions)
*
* In contrast to the parent implementation we factor out query execution, so
* fetching can be further customized easily.
*/
public function load($ids = array(), $conditions = array()) {
$entities = array();
// Revisions are not statically cached, and require a different query to
// other conditions, so separate the revision id into its own variable.
if ($this->revisionKey && isset($conditions[$this->revisionKey])) {
$revision_id = $conditions[$this->revisionKey];
unset($conditions[$this->revisionKey]);
}
else {
$revision_id = FALSE;
}
// Create a new variable which is either a prepared version of the $ids
// array for later comparison with the entity cache, or FALSE if no $ids
// were passed. The $ids array is reduced as items are loaded from cache,
// and we need to know if it's empty for this reason to avoid querying the
// database when all requested entities are loaded from cache.
$passed_ids = !empty($ids) ? array_flip($ids) : FALSE;
// Try to load entities from the static cache.
if ($this->cache && !$revision_id) {
$entities = $this->cacheGet($ids, $conditions);
// If any entities were loaded, remove them from the ids still to load.
if ($passed_ids) {
$ids = array_keys(array_diff_key($passed_ids, $entities));
}
}
// Support the entitycache module if activated.
if (!empty($this->entityInfo['entity cache']) && !$revision_id && $ids && !$conditions) {
$cached_entities = EntityCacheControllerHelper::entityCacheGet($this, $ids, $conditions);
// If any entities were loaded, remove them from the ids still to load.
$ids = array_diff($ids, array_keys($cached_entities));
$entities += $cached_entities;
// Add loaded entities to the static cache if we are not loading a
// revision.
if ($this->cache && !empty($cached_entities) && !$revision_id) {
$this->cacheSet($cached_entities);
}
}
// Load any remaining entities from the database. This is the case if $ids
// is set to FALSE (so we load all entities), if there are any ids left to
// load or if loading a revision.
if (!($this->cacheComplete && $ids === FALSE && !$conditions) && ($ids === FALSE || $ids || $revision_id)) {
$queried_entities = array();
foreach ($this->query($ids, $conditions, $revision_id) as $record) {
// Skip entities already retrieved from cache.
if (isset($entities[$record->{$this->idKey}])) {
continue;
}
// For DB-based entities take care of serialized columns.
if (!empty($this->entityInfo['base table'])) {
$schema = drupal_get_schema($this->entityInfo['base table']);
foreach ($schema['fields'] as $field => $info) {
if (!empty($info['serialize']) && isset($record->$field)) {
$record->$field = unserialize($record->$field);
// Support automatic merging of 'data' fields into the entity.
if (!empty($info['merge']) && is_array($record->$field)) {
foreach ($record->$field as $key => $value) {
$record->$key = $value;
}
unset($record->$field);
}
}
}
}
$queried_entities[$record->{$this->idKey}] = $record;
}
}
// Pass all entities loaded from the database through $this->attachLoad(),
// which attaches fields (if supported by the entity type) and calls the
// entity type specific load callback, for example hook_node_load().
if (!empty($queried_entities)) {
$this->attachLoad($queried_entities, $revision_id);
$entities += $queried_entities;
}
// Entitycache module support: Add entities to the entity cache if we are
// not loading a revision.
if (!empty($this->entityInfo['entity cache']) && !empty($queried_entities) && !$revision_id) {
EntityCacheControllerHelper::entityCacheSet($this, $queried_entities);
}
if ($this->cache) {
// Add entities to the cache if we are not loading a revision.
if (!empty($queried_entities) && !$revision_id) {
$this->cacheSet($queried_entities);
// Remember if we have cached all entities now.
if (!$conditions && $ids === FALSE) {
$this->cacheComplete = TRUE;
}
}
}
// Ensure that the returned array is ordered the same as the original
// $ids array if this was passed in and remove any invalid ids.
if ($passed_ids && $passed_ids = array_intersect_key($passed_ids, $entities)) {
foreach ($passed_ids as $id => $value) {
$passed_ids[$id] = $entities[$id];
}
$entities = $passed_ids;
}
return $entities;
}
/**
* Overrides DrupalDefaultEntityController::resetCache().
*/
public function resetCache(array $ids = NULL) {
$this->cacheComplete = FALSE;
parent::resetCache($ids);
// Support the entitycache module.
if (!empty($this->entityInfo['entity cache'])) {
EntityCacheControllerHelper::resetEntityCache($this, $ids);
}
}
/**
* Implements EntityAPIControllerInterface.
*/
public function invoke($hook, $entity) {
// entity_revision_delete() invokes hook_entity_revision_delete() and
// hook_field_attach_delete_revision() just as node module does. So we need
// to adjust the name of our revision deletion field attach hook in order to
// stick to this pattern.
$field_attach_hook = ($hook == 'revision_delete' ? 'delete_revision' : $hook);
if (!empty($this->entityInfo['fieldable']) && function_exists($function = 'field_attach_' . $field_attach_hook)) {
$function($this->entityType, $entity);
}
if (!empty($this->entityInfo['bundle of']) && entity_type_is_fieldable($this->entityInfo['bundle of'])) {
$type = $this->entityInfo['bundle of'];
// Call field API bundle attachers for the entity we are a bundle of.
if ($hook == 'insert') {
field_attach_create_bundle($type, $entity->{$this->bundleKey});
}
elseif ($hook == 'delete') {
field_attach_delete_bundle($type, $entity->{$this->bundleKey});
}
elseif ($hook == 'update' && $entity->original->{$this->bundleKey} != $entity->{$this->bundleKey}) {
field_attach_rename_bundle($type, $entity->original->{$this->bundleKey}, $entity->{$this->bundleKey});
}
}
// Invoke the hook.
module_invoke_all($this->entityType . '_' . $hook, $entity);
// Invoke the respective entity level hook.
if ($hook == 'presave' || $hook == 'insert' || $hook == 'update' || $hook == 'delete') {
module_invoke_all('entity_' . $hook, $entity, $this->entityType);
}
// Invoke rules.
if (module_exists('rules')) {
rules_invoke_event($this->entityType . '_' . $hook, $entity);
}
}
/**
* Implements EntityAPIControllerInterface.
*
* @param $transaction
* Optionally a DatabaseTransaction object to use. Allows overrides to pass
* in their transaction object.
*/
public function delete($ids, DatabaseTransaction $transaction = NULL) {
$entities = $ids ? $this->load($ids) : FALSE;
if (!$entities) {
// Do nothing, in case invalid or no ids have been passed.
return;
}
// This transaction causes troubles on MySQL, see
// http://drupal.org/node/1007830. So we deactivate this by default until
// is shipped in a point release.
// $transaction = isset($transaction) ? $transaction : db_transaction();
try {
$ids = array_keys($entities);
db_delete($this->entityInfo['base table'])
->condition($this->idKey, $ids, 'IN')
->execute();
if (isset($this->revisionTable)) {
db_delete($this->revisionTable)
->condition($this->idKey, $ids, 'IN')
->execute();
}
// Reset the cache as soon as the changes have been applied.
$this->resetCache($ids);
foreach ($entities as $id => $entity) {
$this->invoke('delete', $entity);
}
// Ignore slave server temporarily.
db_ignore_slave();
}
catch (Exception $e) {
if (isset($transaction)) {
$transaction->rollback();
}
watchdog_exception($this->entityType, $e);
throw $e;
}
}
/**
* Implements EntityAPIControllerRevisionableInterface::deleteRevision().
*/
public function deleteRevision($revision_id) {
if ($entity_revision = entity_revision_load($this->entityType, $revision_id)) {
// Prevent deleting the default revision.
if (entity_revision_is_default($this->entityType, $entity_revision)) {
return FALSE;
}
db_delete($this->revisionTable)
->condition($this->revisionKey, $revision_id)
->execute();
$this->invoke('revision_delete', $entity_revision);
return TRUE;
}
return FALSE;
}
/**
* Implements EntityAPIControllerInterface.
*
* @param $transaction
* Optionally a DatabaseTransaction object to use. Allows overrides to pass
* in their transaction object.
*/
public function save($entity, DatabaseTransaction $transaction = NULL) {
$transaction = isset($transaction) ? $transaction : db_transaction();
try {
// Load the stored entity, if any.
if (!empty($entity->{$this->idKey}) && !isset($entity->original)) {
// In order to properly work in case of name changes, load the original
// entity using the id key if it is available.
$entity->original = entity_load_unchanged($this->entityType, $entity->{$this->idKey});
}
$entity->is_new = !empty($entity->is_new) || empty($entity->{$this->idKey});
$this->invoke('presave', $entity);
if ($entity->is_new) {
$return = drupal_write_record($this->entityInfo['base table'], $entity);
if ($this->revisionKey) {
$this->saveRevision($entity);
}
$this->invoke('insert', $entity);
}
else {
// Update the base table if the entity doesn't have revisions or
// we are updating the default revision.
if (!$this->revisionKey || !empty($entity->{$this->defaultRevisionKey})) {
$return = drupal_write_record($this->entityInfo['base table'], $entity, $this->idKey);
}
if ($this->revisionKey) {
$return = $this->saveRevision($entity);
}
$this->resetCache(array($entity->{$this->idKey}));
$this->invoke('update', $entity);
// Field API always saves as default revision, so if the revision saved
// is not default we have to restore the field values of the default
// revision now by invoking field_attach_update() once again.
if ($this->revisionKey && !$entity->{$this->defaultRevisionKey} && !empty($this->entityInfo['fieldable'])) {
field_attach_update($this->entityType, $entity->original);
}
}
// Ignore slave server temporarily.
db_ignore_slave();
unset($entity->is_new);
unset($entity->is_new_revision);
unset($entity->original);
return $return;
}
catch (Exception $e) {
$transaction->rollback();
watchdog_exception($this->entityType, $e);
throw $e;
}
}
/**
* Saves an entity revision.
*
* @param Entity $entity
* Entity revision to save.
*/
protected function saveRevision($entity) {
// Convert the entity into an array as it might not have the same properties
// as the entity, it is just a raw structure.
$record = (array) $entity;
// File fields assumes we are using $entity->revision instead of
// $entity->is_new_revision, so we also support it and make sure it's set to
// the same value.
$entity->is_new_revision = !empty($entity->is_new_revision) || !empty($entity->revision) || $entity->is_new;
$entity->revision = &$entity->is_new_revision;
$entity->{$this->defaultRevisionKey} = !empty($entity->{$this->defaultRevisionKey}) || $entity->is_new;
// When saving a new revision, set any existing revision ID to NULL so as to
// ensure that a new revision will actually be created.
if ($entity->is_new_revision && isset($record[$this->revisionKey])) {
$record[$this->revisionKey] = NULL;
}
if ($entity->is_new_revision) {
drupal_write_record($this->revisionTable, $record);
$update_default_revision = $entity->{$this->defaultRevisionKey};
}
else {
drupal_write_record($this->revisionTable, $record, $this->revisionKey);
// @todo: Fix original entity to be of the same revision and check whether
// the default revision key has been set.
$update_default_revision = $entity->{$this->defaultRevisionKey} && $entity->{$this->revisionKey} != $entity->original->{$this->revisionKey};
}
// Make sure to update the new revision key for the entity.
$entity->{$this->revisionKey} = $record[$this->revisionKey];
// Mark this revision as the default one.
if ($update_default_revision) {
db_update($this->entityInfo['base table'])
->fields(array($this->revisionKey => $record[$this->revisionKey]))
->condition($this->idKey, $entity->{$this->idKey})
->execute();
}
return $entity->is_new_revision ? SAVED_NEW : SAVED_UPDATED;
}
/**
* Implements EntityAPIControllerInterface.
*/
public function create(array $values = array()) {
// Add is_new property if it is not set.
$values += array('is_new' => TRUE);
if (isset($this->entityInfo['entity class']) && $class = $this->entityInfo['entity class']) {
return new $class($values, $this->entityType);
}
return (object) $values;
}
/**
* Implements EntityAPIControllerInterface.
*
* @return
* A serialized string in JSON format suitable for the import() method.
*/
public function export($entity, $prefix = '') {
$vars = get_object_vars($entity);
unset($vars['is_new']);
return entity_var_json_export($vars, $prefix);
}
/**
* Implements EntityAPIControllerInterface.
*
* @param $export
* A serialized string in JSON format as produced by the export() method.
*/
public function import($export) {
$vars = drupal_json_decode($export);
if (is_array($vars)) {
return $this->create($vars);
}
return FALSE;
}
/**
* Implements EntityAPIControllerInterface.
*
* @param $content
* Optionally. Allows pre-populating the built content to ease overridding
* this method.
*/
public function buildContent($entity, $view_mode = 'full', $langcode = NULL, $content = array()) {
// Remove previously built content, if exists.
$entity->content = $content;
$langcode = isset($langcode) ? $langcode : $GLOBALS['language_content']->language;
// Add in fields.
if (!empty($this->entityInfo['fieldable'])) {
// Perform the preparation tasks if they have not been performed yet.
// An internal flag prevents the operation from running twice.
$key = isset($entity->{$this->idKey}) ? $entity->{$this->idKey} : NULL;
field_attach_prepare_view($this->entityType, array($key => $entity), $view_mode);
$entity->content += field_attach_view($this->entityType, $entity, $view_mode, $langcode);
}
// Invoke hook_ENTITY_view() to allow modules to add their additions.
if (module_exists('rules')) {
rules_invoke_all($this->entityType . '_view', $entity, $view_mode, $langcode);
}
else {
module_invoke_all($this->entityType . '_view', $entity, $view_mode, $langcode);
}
module_invoke_all('entity_view', $entity, $this->entityType, $view_mode, $langcode);
$build = $entity->content;
unset($entity->content);
return $build;
}
/**
* Implements EntityAPIControllerInterface.
*/
public function view($entities, $view_mode = 'full', $langcode = NULL, $page = NULL) {
// For Field API and entity_prepare_view, the entities have to be keyed by
// (numeric) id.
$entities = entity_key_array_by_property($entities, $this->idKey);
if (!empty($this->entityInfo['fieldable'])) {
field_attach_prepare_view($this->entityType, $entities, $view_mode);
}
entity_prepare_view($this->entityType, $entities);
$langcode = isset($langcode) ? $langcode : $GLOBALS['language_content']->language;
$view = array();
foreach ($entities as $entity) {
$build = entity_build_content($this->entityType, $entity, $view_mode, $langcode);
$build += array(
// If the entity type provides an implementation, use this instead the
// generic one.
// @see template_preprocess_entity()
'#theme' => 'entity',
'#entity_type' => $this->entityType,
'#entity' => $entity,
'#view_mode' => $view_mode,
'#language' => $langcode,
'#page' => $page,
);
// Allow modules to modify the structured entity.
drupal_alter(array($this->entityType . '_view', 'entity_view'), $build, $this->entityType);
$key = isset($entity->{$this->idKey}) ? $entity->{$this->idKey} : NULL;
$view[$this->entityType][$key] = $build;
}
return $view;
}
}
/**
* A controller implementing exportables stored in the database.
*/
class EntityAPIControllerExportable extends EntityAPIController {
protected $entityCacheByName = array();
protected $nameKey, $statusKey, $moduleKey;
/**
* Overridden.
*
* Allows specifying a name key serving as uniform identifier for this entity
* type while still internally we are using numeric identifieres.
*/
public function __construct($entityType) {
parent::__construct($entityType);
// Use the name key as primary identifier.
$this->nameKey = isset($this->entityInfo['entity keys']['name']) ? $this->entityInfo['entity keys']['name'] : $this->idKey;
if (!empty($this->entityInfo['exportable'])) {
$this->statusKey = isset($this->entityInfo['entity keys']['status']) ? $this->entityInfo['entity keys']['status'] : 'status';
$this->moduleKey = isset($this->entityInfo['entity keys']['module']) ? $this->entityInfo['entity keys']['module'] : 'module';
}
}
/**
* Support loading by name key.
*/
protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) {
// Add the id condition ourself, as we might have a separate name key.
$query = parent::buildQuery(array(), $conditions, $revision_id);
if ($ids) {
// Support loading by numeric ids as well as by machine names.
$key = is_numeric(reset($ids)) ? $this->idKey : $this->nameKey;
$query->condition("base.$key", $ids, 'IN');
}
return $query;
}
/**
* Overridden to support passing numeric ids as well as names as $ids.
*/
public function load($ids = array(), $conditions = array()) {
$entities = array();
// Only do something if loaded by names.
if (!$ids || $this->nameKey == $this->idKey || is_numeric(reset($ids))) {
return parent::load($ids, $conditions);
}
// Revisions are not statically cached, and require a different query to
// other conditions, so separate the revision id into its own variable.
if ($this->revisionKey && isset($conditions[$this->revisionKey])) {
$revision_id = $conditions[$this->revisionKey];
unset($conditions[$this->revisionKey]);
}
else {
$revision_id = FALSE;
}
$passed_ids = !empty($ids) ? array_flip($ids) : FALSE;
// Care about the static cache.
if ($this->cache && !$revision_id) {
$entities = $this->cacheGetByName($ids, $conditions);
}
// If any entities were loaded, remove them from the ids still to load.
if ($entities) {
$ids = array_keys(array_diff_key($passed_ids, $entities));
}
$entities_by_id = parent::load($ids, $conditions);
$entities += entity_key_array_by_property($entities_by_id, $this->nameKey);
// Ensure that the returned array is keyed by numeric id and ordered the
// same as the original $ids array and remove any invalid ids.
$return = array();
foreach ($passed_ids as $name => $value) {
if (isset($entities[$name])) {
$return[$entities[$name]->{$this->idKey}] = $entities[$name];
}
}
return $return;
}
/**
* Overridden.
* @see DrupalDefaultEntityController::cacheGet()
*/
protected function cacheGet($ids, $conditions = array()) {
if (!empty($this->entityCache) && $ids !== array()) {
$entities = $ids ? array_intersect_key($this->entityCache, array_flip($ids)) : $this->entityCache;
return $this->applyConditions($entities, $conditions);
}
return array();
}
/**
* Like cacheGet() but keyed by name.
*/
protected function cacheGetByName($names, $conditions = array()) {
if (!empty($this->entityCacheByName) && $names !== array() && $names) {
// First get the entities by ids, then apply the conditions.
// Generally, we make use of $this->entityCache, but if we are loading by
// name, we have to use $this->entityCacheByName.
$entities = array_intersect_key($this->entityCacheByName, array_flip($names));
return $this->applyConditions($entities, $conditions);
}
return array();
}
protected function applyConditions($entities, $conditions = array()) {
if ($conditions) {
foreach ($entities as $key => $entity) {
$entity_values = (array) $entity;
// We cannot use array_diff_assoc() here because condition values can
// also be arrays, e.g. '$conditions = array('status' => array(1, 2))'
foreach ($conditions as $condition_key => $condition_value) {
if (is_array($condition_value)) {
if (!isset($entity_values[$condition_key]) || !in_array($entity_values[$condition_key], $condition_value)) {
unset($entities[$key]);
}
}
elseif (!isset($entity_values[$condition_key]) || $entity_values[$condition_key] != $condition_value) {
unset($entities[$key]);
}
}
}
}
return $entities;
}
/**
* Overridden.
* @see DrupalDefaultEntityController::cacheSet()
*/
protected function cacheSet($entities) {
$this->entityCache += $entities;
// If we have a name key, also support static caching when loading by name.
if ($this->nameKey != $this->idKey) {
$this->entityCacheByName += entity_key_array_by_property($entities, $this->nameKey);
}
}
/**
* Overridden.
* @see DrupalDefaultEntityController::attachLoad()
*
* Changed to call type-specific hook with the entities keyed by name if they
* have one.
*/
protected function attachLoad(&$queried_entities, $revision_id = FALSE) {
// Attach fields.
if ($this->entityInfo['fieldable']) {
if ($revision_id) {
field_attach_load_revision($this->entityType, $queried_entities);
}
else {
field_attach_load($this->entityType, $queried_entities);
}
}
// Call hook_entity_load().
foreach (module_implements('entity_load') as $module) {
$function = $module . '_entity_load';
$function($queried_entities, $this->entityType);
}
// Call hook_TYPE_load(). The first argument for hook_TYPE_load() are
// always the queried entities, followed by additional arguments set in
// $this->hookLoadArguments.
// For entities with a name key, pass the entities keyed by name to the
// specific load hook.
if ($this->nameKey != $this->idKey) {
$entities_by_name = entity_key_array_by_property($queried_entities, $this->nameKey);
}
else {
$entities_by_name = $queried_entities;
}
$args = array_merge(array($entities_by_name), $this->hookLoadArguments);
foreach (module_implements($this->entityInfo['load hook']) as $module) {
call_user_func_array($module . '_' . $this->entityInfo['load hook'], $args);
}
}
public function resetCache(array $ids = NULL) {
$this->cacheComplete = FALSE;
if (isset($ids)) {
foreach (array_intersect_key($this->entityCache, array_flip($ids)) as $id => $entity) {
unset($this->entityCacheByName[$this->entityCache[$id]->{$this->nameKey}]);
unset($this->entityCache[$id]);
}
}
else {
$this->entityCache = array();
$this->entityCacheByName = array();
}
}
/**
* Overridden to care about reverted entities.
*/
public function delete($ids, DatabaseTransaction $transaction = NULL) {
$entities = $ids ? $this->load($ids) : FALSE;
if ($entities) {
parent::delete($ids, $transaction);
foreach ($entities as $id => $entity) {
if (entity_has_status($this->entityType, $entity, ENTITY_IN_CODE)) {
entity_defaults_rebuild(array($this->entityType));
break;
}
}
}
}
/**
* Overridden to care about reverted bundle entities and to skip Rules.
*/
public function invoke($hook, $entity) {
if ($hook == 'delete') {
// To ease figuring out whether this is a revert, make sure that the
// entity status is updated in case the providing module has been
// disabled.
if (entity_has_status($this->entityType, $entity, ENTITY_IN_CODE) && !module_exists($entity->{$this->moduleKey})) {
$entity->{$this->statusKey} = ENTITY_CUSTOM;
}
$is_revert = entity_has_status($this->entityType, $entity, ENTITY_IN_CODE);
}
if (!empty($this->entityInfo['fieldable']) && function_exists($function = 'field_attach_' . $hook)) {
$function($this->entityType, $entity);
}
if (isset($this->entityInfo['bundle of']) && $type = $this->entityInfo['bundle of']) {
// Call field API bundle attachers for the entity we are a bundle of.
if ($hook == 'insert') {
field_attach_create_bundle($type, $entity->{$this->bundleKey});
}
elseif ($hook == 'delete' && !$is_revert) {
field_attach_delete_bundle($type, $entity->{$this->bundleKey});
}
elseif ($hook == 'update' && $id = $entity->{$this->nameKey}) {
if ($entity->original->{$this->bundleKey} != $entity->{$this->bundleKey}) {
field_attach_rename_bundle($type, $entity->original->{$this->bundleKey}, $entity->{$this->bundleKey});
}
}
}
// Invoke the hook.
module_invoke_all($this->entityType . '_' . $hook, $entity);
// Invoke the respective entity level hook.
if ($hook == 'presave' || $hook == 'insert' || $hook == 'update' || $hook == 'delete') {
module_invoke_all('entity_' . $hook, $entity, $this->entityType);
}
}
/**
* Overridden to care exportables that are overridden.
*/
public function save($entity, DatabaseTransaction $transaction = NULL) {
// Preload $entity->original by name key if necessary.
if (!empty($entity->{$this->nameKey}) && empty($entity->{$this->idKey}) && !isset($entity->original)) {
$entity->original = entity_load_unchanged($this->entityType, $entity->{$this->nameKey});
}
// Update the status for entities getting overridden.
if (entity_has_status($this->entityType, $entity, ENTITY_IN_CODE) && empty($entity->is_rebuild)) {
$entity->{$this->statusKey} |= ENTITY_CUSTOM;
}
return parent::save($entity, $transaction);
}
/**
* Overridden.
*/
public function export($entity, $prefix = '') {
$vars = get_object_vars($entity);
unset($vars[$this->statusKey], $vars[$this->moduleKey], $vars['is_new']);
if ($this->nameKey != $this->idKey) {
unset($vars[$this->idKey]);
}
return entity_var_json_export($vars, $prefix);
}
/**
* Implements EntityAPIControllerInterface.
*/
public function view($entities, $view_mode = 'full', $langcode = NULL, $page = NULL) {
$view = parent::view($entities, $view_mode, $langcode, $page);
if ($this->nameKey != $this->idKey) {
// Re-key the view array to be keyed by name.
$return = array();
foreach ($view[$this->entityType] as $id => $content) {
$key = isset($content['#entity']->{$this->nameKey}) ? $content['#entity']->{$this->nameKey} : NULL;
$return[$this->entityType][$key] = $content;
}
$view = $return;
}
return $view;
}
}

View File

@@ -0,0 +1,310 @@
<?php
/**
* @file
* Provides a base class for entities.
*/
/**
* A common class for entities.
*
* It's suggested, but not required, to extend this class and to override
* __construct() in order to specify a fixed entity type.
*
* For providing an entity label and URI it is suggested to override the
* defaultLabel() and defaultUri() methods, and to specify the
* entity_class_label() and entity_class_uri() as respective callbacks in
* hook_entity_info(). That way modules are able to override your defaults
* by altering the hook_entity_info() callbacks, while $entity->label() and
* $entity->uri() reflect this changes as well.
*
* Defaults for entity properties can be easily defined by adding class
* properties, e.g.:
* @code
* public $name = '';
* public $count = 0;
* @endcode
*/
class Entity {
protected $entityType;
protected $entityInfo;
protected $idKey, $nameKey, $statusKey;
/**
* Creates a new entity.
*
* @see entity_create()
*/
public function __construct(array $values = array(), $entityType = NULL) {
if (empty($entityType)) {
throw new Exception('Cannot create an instance of Entity without a specified entity type.');
}
$this->entityType = $entityType;
$this->setUp();
// Set initial values.
foreach ($values as $key => $value) {
$this->$key = $value;
}
}
/**
* Set up the object instance on construction or unserializiation.
*/
protected function setUp() {
$this->entityInfo = entity_get_info($this->entityType);
$this->idKey = $this->entityInfo['entity keys']['id'];
$this->nameKey = isset($this->entityInfo['entity keys']['name']) ? $this->entityInfo['entity keys']['name'] : $this->idKey;
$this->statusKey = empty($info['entity keys']['status']) ? 'status' : $info['entity keys']['status'];
}
/**
* Returns the internal, numeric identifier.
*
* Returns the numeric identifier, even if the entity type has specified a
* name key. In the latter case, the numeric identifier is supposed to be used
* when dealing generically with entities or internally to refer to an entity,
* i.e. in a relational database. If unsure, use Entity:identifier().
*/
public function internalIdentifier() {
return isset($this->{$this->idKey}) ? $this->{$this->idKey} : NULL;
}
/**
* Returns the entity identifier, i.e. the entities name or numeric id.
*
* @return
* The identifier of the entity. If the entity type makes use of a name key,
* the name is returned, else the numeric id.
*
* @see entity_id()
*/
public function identifier() {
return isset($this->{$this->nameKey}) ? $this->{$this->nameKey} : NULL;
}
/**
* Returns the info of the type of the entity.
*
* @see entity_get_info()
*/
public function entityInfo() {
return $this->entityInfo;
}
/**
* Returns the type of the entity.
*/
public function entityType() {
return $this->entityType;
}
/**
* Returns the bundle of the entity.
*
* @return
* The bundle of the entity. Defaults to the entity type if the entity type
* does not make use of different bundles.
*/
public function bundle() {
return !empty($this->entityInfo['entity keys']['bundle']) ? $this->{$this->entityInfo['entity keys']['bundle']} : $this->entityType;
}
/**
* Returns the label of the entity.
*
* Modules may alter the label by specifying another 'label callback' using
* hook_entity_info_alter().
*
* @see entity_label()
*/
public function label() {
if (isset($this->entityInfo['label callback']) && $this->entityInfo['label callback'] == 'entity_class_label') {
return $this->defaultLabel();
}
return entity_label($this->entityType, $this);
}
/**
* Defines the entity label if the 'entity_class_label' callback is used.
*
* Specify 'entity_class_label' as 'label callback' in hook_entity_info() to
* let the entity label point to this method. Override this in order to
* implement a custom default label.
*/
protected function defaultLabel() {
// Add in the translated specified label property.
return $this->getTranslation($this->entityInfo['entity keys']['label']);
}
/**
* Returns the uri of the entity just as entity_uri().
*
* Modules may alter the uri by specifying another 'uri callback' using
* hook_entity_info_alter().
*
* @see entity_uri()
*/
public function uri() {
if (isset($this->entityInfo['uri callback']) && $this->entityInfo['uri callback'] == 'entity_class_uri') {
return $this->defaultUri();
}
return entity_uri($this->entityType, $this);
}
/**
* Override this in order to implement a custom default URI and specify
* 'entity_class_uri' as 'uri callback' hook_entity_info().
*/
protected function defaultUri() {
return array('path' => 'default/' . $this->identifier());
}
/**
* Checks if the entity has a certain exportable status.
*
* @param $status
* A status constant, i.e. one of ENTITY_CUSTOM, ENTITY_IN_CODE,
* ENTITY_OVERRIDDEN or ENTITY_FIXED.
*
* @return
* For exportable entities TRUE if the entity has the status, else FALSE.
* In case the entity is not exportable, NULL is returned.
*
* @see entity_has_status()
*/
public function hasStatus($status) {
if (!empty($this->entityInfo['exportable'])) {
return isset($this->{$this->statusKey}) && ($this->{$this->statusKey} & $status) == $status;
}
}
/**
* Permanently saves the entity.
*
* @see entity_save()
*/
public function save() {
return entity_get_controller($this->entityType)->save($this);
}
/**
* Permanently deletes the entity.
*
* @see entity_delete()
*/
public function delete() {
$id = $this->identifier();
if (isset($id)) {
entity_get_controller($this->entityType)->delete(array($id));
}
}
/**
* Exports the entity.
*
* @see entity_export()
*/
public function export($prefix = '') {
return entity_get_controller($this->entityType)->export($this, $prefix);
}
/**
* Generate an array for rendering the entity.
*
* @see entity_view()
*/
public function view($view_mode = 'full', $langcode = NULL, $page = NULL) {
return entity_get_controller($this->entityType)->view(array($this), $view_mode, $langcode, $page);
}
/**
* Builds a structured array representing the entity's content.
*
* @see entity_build_content()
*/
public function buildContent($view_mode = 'full', $langcode = NULL) {
return entity_get_controller($this->entityType)->buildContent($this, $view_mode, $langcode);
}
/**
* Gets the raw, translated value of a property or field.
*
* Supports retrieving field translations as well as i18n string translations.
*
* Note that this returns raw data values, which might not reflect what
* has been declared for hook_entity_property_info() as no 'getter callbacks'
* are invoked or no referenced entities are loaded. For retrieving values
* reflecting the property info make use of entity metadata wrappers, see
* entity_metadata_wrapper().
*
* @param $property_name
* The name of the property to return; e.g., 'title'.
* @param $langcode
* (optional) The language code of the language to which the value should
* be translated. If set to NULL, the default display language is being
* used.
*
* @return
* The raw, translated property value; or the raw, un-translated value if no
* translation is available.
*
* @todo Implement an analogous setTranslation() method for updating.
*/
public function getTranslation($property, $langcode = NULL) {
$all_info = entity_get_all_property_info($this->entityType);
$property_info = $all_info[$property];
if (!empty($property_info['translatable'])) {
if (!empty($property_info['field'])) {
return field_get_items($this->entityType, $this, $property, $langcode);
}
elseif (!empty($property_info['i18n string'])) {
$name = $this->entityInfo['module'] . ':' . $this->entityType . ':' . $this->identifier() . ':' . $property;
return entity_i18n_string($name, $this->$property, $langcode);
}
}
return $this->$property;
}
/**
* Checks whether the entity is the default revision.
*
* @return Boolean
*
* @see entity_revision_is_default()
*/
public function isDefaultRevision() {
if (!empty($this->entityInfo['entity keys']['revision'])) {
$key = !empty($this->entityInfo['entity keys']['default revision']) ? $this->entityInfo['entity keys']['default revision'] : 'default_revision';
return !empty($this->$key);
}
return TRUE;
}
/**
* 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']);
// 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.
*/
public function __wakeup() {
$this->setUp();
}
}
/**
* These classes are deprecated by "Entity" and are only here for backward
* compatibility reasons.
*/
class EntityDB extends Entity {}
class EntityExtendable extends Entity {}
class EntityDBExtendable extends Entity {}

View File

@@ -0,0 +1,646 @@
<?php
/**
* @file
* Provides API functions around hook_entity_property_info(). Also see
* entity.info.inc, which cares for providing entity property info for all core
* entity types.
*/
/**
* Get the entity property info array of an entity type.
*
* @param $entity_type
* The entity type, e.g. node, for which the info shall be returned, or NULL
* to return an array with info about all types.
*
* @see hook_entity_property_info()
* @see hook_entity_property_info_alter()
*/
function entity_get_property_info($entity_type = NULL) {
// Use the advanced drupal_static() pattern, since this is called very often.
static $drupal_static_fast;
if (!isset($drupal_static_fast)) {
$drupal_static_fast['info'] = &drupal_static(__FUNCTION__);
}
$info = &$drupal_static_fast['info'];
// hook_entity_property_info() includes translated strings, so each language
// is cached separately.
$langcode = $GLOBALS['language']->language;
if (empty($info)) {
if ($cache = cache_get("entity_property_info:$langcode")) {
$info = $cache->data;
}
else {
$info = module_invoke_all('entity_property_info');
// Let other modules alter the entity info.
drupal_alter('entity_property_info', $info);
cache_set("entity_property_info:$langcode", $info);
}
}
return empty($entity_type) ? $info : (isset($info[$entity_type]) ? $info[$entity_type] : array());
}
/**
* Returns the default information for an entity property.
*
* @return
* An array of optional property information keys mapped to their defaults.
*
* @see hook_entity_property_info()
*/
function entity_property_info_defaults() {
return array(
'type' => 'text',
'getter callback' => 'entity_property_verbatim_get',
);
}
/**
* Gets an array of info about all properties of a given entity type.
*
* In contrast to entity_get_property_info(), this function returns info about
* all properties the entity might have, thus it adds an all properties assigned
* to entity bundles.
*
* @param $entity_type
* (optiona) The entity type to return properties for.
*
* @return
* An array of info about properties. If the type is ommitted, all known
* properties are returned.
*/
function entity_get_all_property_info($entity_type = NULL) {
if (!isset($entity_type)) {
// Retrieve all known properties.
$properties = array();
foreach (entity_get_info() as $entity_type => $info) {
$properties += entity_get_all_property_info($entity_type);
}
return $properties;
}
// Else retrieve the properties of the given entity type only.
$info = entity_get_property_info($entity_type);
$info += array('properties' => array(), 'bundles' => array());
// Add all bundle properties.
foreach ($info['bundles'] as $bundle => $bundle_info) {
$bundle_info += array('properties' => array());
$info['properties'] += $bundle_info['properties'];
}
return $info['properties'];
}
/**
* Queries for entities having the given property value.
*
* @param $entity_type
* The type of the entity.
* @param $property
* The name of the property to query for.
* @param $value
* A single property value or an array of possible values to query for.
* @param $limit
* Limit the numer of results. Defaults to 30.
*
* @return
* An array of entity ids or NULL if there is no information how to query for
* the given property.
*/
function entity_property_query($entity_type, $property, $value, $limit = 30) {
$properties = entity_get_all_property_info($entity_type);
$info = $properties[$property] + array('type' => 'text', 'queryable' => !empty($properties[$property]['schema field']));
// We still support the deprecated query callback, so just add in EFQ-based
// callbacks in case 'queryable' is set to TRUE and make use of the callback.
if ($info['queryable'] && empty($info['query callback'])) {
$info['query callback'] = !empty($info['field']) ? 'entity_metadata_field_query' : 'entity_metadata_table_query';
}
$type = $info['type'];
// Make sure an entity or a list of entities are passed on as identifiers
// with the help of the wrappers. For that ensure the data type matches the
// passed on value(s).
if (is_array($value) && !entity_property_list_extract_type($type)) {
$type = 'list<' . $type . '>';
}
elseif (!is_array($value) && entity_property_list_extract_type($type)) {
$type = entity_property_list_extract_type($type);
}
$wrapper = entity_metadata_wrapper($type, $value);
$value = $wrapper->value(array('identifier' => TRUE));
if (!empty($info['query callback'])) {
return $info['query callback']($entity_type, $property, $value, $limit);
}
}
/**
* Resets the cached information of hook_entity_property_info().
*/
function entity_property_info_cache_clear() {
drupal_static_reset('entity_get_property_info');
// Clear all languages.
cache_clear_all('entity_property_info:', 'cache', TRUE);
}
/**
* Implements hook_hook_info().
*/
function entity_hook_info() {
$hook_info['entity_property_info'] = array(
'group' => 'info',
);
$hook_info['entity_property_info_alter'] = array(
'group' => 'info',
);
return $hook_info;
}
/**
* Implements hook_field_info_alter().
* Defines default property types for core field types.
*/
function entity_field_info_alter(&$field_info) {
if (module_exists('number')) {
$field_info['number_integer']['property_type'] = 'integer';
$field_info['number_decimal']['property_type'] = 'decimal';
$field_info['number_float']['property_type'] = 'decimal';
}
if (module_exists('text')) {
$field_info['text']['property_type'] = 'text';
$field_info['text']['property_callbacks'][] = 'entity_metadata_field_text_property_callback';
$field_info['text_long']['property_type'] = 'text';
$field_info['text_long']['property_callbacks'][] = 'entity_metadata_field_text_property_callback';
$field_info['text_with_summary']['property_type'] = 'field_item_textsummary';
$field_info['text_with_summary']['property_callbacks'][] = 'entity_metadata_field_text_property_callback';
}
if (module_exists('list')) {
$field_info['list_integer']['property_type'] = 'integer';
$field_info['list_boolean']['property_type'] = 'boolean';
$field_info['list_float']['property_type'] = 'decimal';
$field_info['list_text']['property_type'] = 'text';
}
if (module_exists('taxonomy')) {
$field_info['taxonomy_term_reference']['property_type'] = 'taxonomy_term';
$field_info['taxonomy_term_reference']['property_callbacks'][] = 'entity_metadata_field_term_reference_callback';
}
if (module_exists('file')) {
// The callback specifies a custom data structure matching the file field
// items. We introduce a custom type name for this data structure.
$field_info['file']['property_type'] = 'field_item_file';
$field_info['file']['property_callbacks'][] = 'entity_metadata_field_file_callback';
}
if (module_exists('image')) {
// The callback specifies a custom data structure matching the image field
// items. We introduce a custom type name for this data structure.
$field_info['image']['property_type'] = 'field_item_image';
$field_info['image']['property_callbacks'][] = 'entity_metadata_field_file_callback';
$field_info['image']['property_callbacks'][] = 'entity_metadata_field_image_callback';
}
}
/**
* Implements hook_field_create_instance().
* Clear the cache when a field instance changed.
*/
function entity_field_create_instance() {
entity_property_info_cache_clear();
}
/**
* Implements hook_field_delete_instance().
* Clear the cache when a field instance changed.
*/
function entity_field_delete_instance() {
entity_property_info_cache_clear();
}
/**
* Implements hook_field_update_instance().
* Clear the cache when a field instance changed.
*/
function entity_field_update_instance() {
entity_property_info_cache_clear();
}
/**
* Verifies that the given data can be safely used as the given type regardless
* of the PHP variable type of $data. Example: the string "15" is a valid
* integer, but "15nodes" is not.
*
* @return
* Whether the data is valid for the given type.
*/
function entity_property_verify_data_type($data, $type) {
// As this may be called very often statically cache the entity info using
// the fast pattern.
static $drupal_static_fast;
if (!isset($drupal_static_fast)) {
// Make use of the same static as entity info.
entity_get_info();
$drupal_static_fast['entity_info'] = &drupal_static('entity_get_info');
}
$info = &$drupal_static_fast['entity_info'];
// First off check for entities, which may be represented by their ids too.
if (isset($info[$type])) {
if (is_object($data)) {
return TRUE;
}
elseif (isset($info[$type]['entity keys']['name'])) {
return entity_property_verify_data_type($data, 'token');
}
return entity_property_verify_data_type($data, empty($info[$type]['fieldable']) ? 'text' : 'integer');
}
switch ($type) {
case 'site':
case 'unknown':
return TRUE;
case 'date':
case 'duration':
case 'integer':
return is_numeric($data) && strpos($data, '.') === FALSE;
case 'decimal':
return is_numeric($data);
case 'text':
return is_scalar($data);
case 'token':
return is_scalar($data) && preg_match('!^[a-z][a-z0-9_]*$!', $data);
case 'boolean':
return is_scalar($data) && (is_bool($data) || $data == 0 || $data == 1);
case 'uri':
return valid_url($data, TRUE);
case 'list':
return (is_array($data) && array_values($data) == $data) || (is_object($data) && $data instanceof EntityMetadataArrayObject);
case 'entity':
return is_object($data) && $data instanceof EntityDrupalWrapper;
default:
case 'struct':
return is_object($data) || is_array($data);
}
}
/**
* Creates the entity object for an array of given property values.
*
* @param $entity_type
* The entity type to create an entity for.
* @param $values
* An array of values as described by the entity's property info. All entity
* properties of the given entity type that are marked as required, must be
* present.
* If the passed values have no matching property, their value will be
* assigned to the entity directly, without the use of the metadata-wrapper
* property.
*
* @return EntityDrupalWrapper
* An EntityDrupalWrapper wrapping the newly created entity or FALSE, if
* there were no information how to create the entity.
*/
function entity_property_values_create_entity($entity_type, $values = array()) {
if (entity_type_supports($entity_type, 'create')) {
$info = entity_get_info($entity_type);
// Create the initial entity by passing the values for all 'entity keys'
// to entity_create().
$entity_keys = array_filter($info['entity keys']);
$creation_values = array_intersect_key($values, array_flip($entity_keys));
// In case the bundle key does not match the property that sets it, ensure
// the bundle key is initialized somehow, so entity_extract_ids()
// does not bail out during wrapper creation.
if (!empty($info['entity keys']['bundle'])) {
$creation_values += array($info['entity keys']['bundle'] => FALSE);
}
$entity = entity_create($entity_type, $creation_values);
// Now set the remaining values using the wrapper.
$wrapper = entity_metadata_wrapper($entity_type, $entity);
foreach ($values as $key => $value) {
if (!in_array($key, $info['entity keys'])) {
if (isset($wrapper->$key)) {
$wrapper->$key->set($value);
}
else {
$entity->$key = $value;
}
}
}
// @todo: Once we require Drupal 7.7 or later, verify the entity has
// now a valid bundle and throw the EntityMalformedException if not.
return $wrapper;
}
return FALSE;
}
/**
* Extracts the contained type for a list type string like list<date>.
*
* @return
* The contained type or FALSE, if the given type string is no list.
*/
function entity_property_list_extract_type($type) {
if (strpos($type, 'list<') === 0 && $type[strlen($type)-1] == '>') {
return substr($type, 5, -1);
}
return FALSE;
}
/**
* Extracts the innermost type for a type string like list<list<date>>.
*
* @param $type
* The type to examine.
*
* @return
* For list types, the innermost type. The type itself otherwise.
*/
function entity_property_extract_innermost_type($type) {
while (strpos($type, 'list<') === 0 && $type[strlen($type)-1] == '>') {
$type = substr($type, 5, -1);
}
return $type;
}
/**
* Gets the property just as it is set in the data.
*/
function entity_property_verbatim_get($data, array $options, $name, $type, $info) {
$name = isset($info['schema field']) ? $info['schema field'] : $name;
if ((is_array($data) || (is_object($data) && $data instanceof ArrayAccess)) && isset($data[$name])) {
return $data[$name];
}
elseif (is_object($data) && isset($data->$name)) {
// Incorporate i18n_string translations. We may rely on the entity class
// here as its usage is required by the i18n integration.
if (isset($options['language']) && !empty($info['i18n string'])) {
return $data->getTranslation($name, $options['language']->language);
}
else {
return $data->$name;
}
}
return NULL;
}
/**
* Date values are converted from ISO strings to timestamp if needed.
*/
function entity_property_verbatim_date_get($data, array $options, $name, $type, $info) {
$name = isset($info['schema field']) ? $info['schema field'] : $name;
return is_numeric($data[$name]) ? $data[$name] : strtotime($data[$name], REQUEST_TIME);
}
/**
* Sets the property to the given value. May be used as 'setter callback'.
*/
function entity_property_verbatim_set(&$data, $name, $value, $langcode, $type, $info) {
$name = isset($info['schema field']) ? $info['schema field'] : $name;
if (is_array($data) || (is_object($data) && $data instanceof ArrayAccess)) {
$data[$name] = $value;
}
elseif (is_object($data)) {
$data->$name = $value;
}
}
/**
* Gets the property using the getter method (named just like the property).
*/
function entity_property_getter_method($object, array $options, $name) {
// Remove any underscores as classes are expected to use CamelCase.
$method = strtr($name, array('_' => ''));
return $object->$method();
}
/**
* Sets the property to the given value using the setter method. May be used as
* 'setter callback'.
*/
function entity_property_setter_method($object, $name, $value) {
// Remove any underscores as classes are expected to use CamelCase.
$method = 'set' . strtr($name, array('_' => ''));
// Invoke the setProperty() method where 'Property' is the property name.
$object->$method($value);
}
/**
* Getter callback for getting an array. Makes sure it's numerically indexed.
*/
function entity_property_get_list($data, array $options, $name) {
return isset($data->$name) ? array_values($data->$name) : array();
}
/**
* A validation callback ensuring the passed integer is positive.
*/
function entity_property_validate_integer_positive($value) {
return $value > 0;
}
/**
* A validation callback ensuring the passed integer is non-negative.
*/
function entity_property_validate_integer_non_negative($value) {
return $value >= 0;
}
/**
* A simple auto-creation callback for array based data structures.
*/
function entity_property_create_array($property_name, $context) {
return array();
}
/**
* Flattens the given options in single dimensional array.
* We don't depend on options module, so we cannot use options_array_flatten().
*
* @see options_array_flatten()
*/
function entity_property_options_flatten($options) {
$result = array();
foreach ($options as $key => $value) {
if (is_array($value)) {
$result += $value;
}
else {
$result[$key] = $value;
}
}
return $result;
}
/**
* Defines info for the properties of the text_formatted data structure.
*/
function entity_property_text_formatted_info() {
return array(
'value' => array(
'type' => 'text',
'label' => t('Text'),
'sanitized' => TRUE,
'getter callback' => 'entity_metadata_field_text_get',
'setter callback' => 'entity_property_verbatim_set',
'setter permission' => 'administer nodes',
'raw getter callback' => 'entity_property_verbatim_get',
),
'summary' => array(
'type' => 'text',
'label' => t('Summary'),
'sanitized' => TRUE,
'getter callback' => 'entity_metadata_field_text_get',
'setter callback' => 'entity_property_verbatim_set',
'setter permission' => 'administer nodes',
'raw getter callback' => 'entity_property_verbatim_get',
),
'format' => array(
'type' => 'token',
'label' => t('Text format'),
'options list' => 'entity_metadata_field_text_formats',
'getter callback' => 'entity_property_verbatim_get',
),
);
}
/**
* Defines info for the properties of the field_item_textsummary data structure.
*/
function entity_property_field_item_textsummary_info() {
return array(
'value' => array(
'type' => 'text',
'label' => t('Text'),
'setter callback' => 'entity_property_verbatim_set',
),
'summary' => array(
'type' => 'text',
'label' => t('Summary'),
'setter callback' => 'entity_property_verbatim_set',
),
);
}
/**
* Defines info for the properties of the file-field item data structure.
*/
function entity_property_field_item_file_info() {
$properties['file'] = array(
'type' => 'file',
'label' => t('The file.'),
'getter callback' => 'entity_metadata_field_file_get',
'setter callback' => 'entity_metadata_field_file_set',
'required' => TRUE,
);
$properties['description'] = array(
'type' => 'text',
'label' => t('The file description'),
'setter callback' => 'entity_property_verbatim_set',
);
$properties['display'] = array(
'type' => 'boolean',
'label' => t('Whether the file is being displayed.'),
'setter callback' => 'entity_property_verbatim_set',
);
return $properties;
}
/**
* Defines info for the properties of the image-field item data structure.
*/
function entity_property_field_item_image_info() {
$properties['file'] = array(
'type' => 'file',
'label' => t('The image file.'),
'getter callback' => 'entity_metadata_field_file_get',
'setter callback' => 'entity_metadata_field_file_set',
'required' => TRUE,
);
$properties['alt'] = array(
'type' => 'text',
'label' => t('The "Alt" attribute text'),
'setter callback' => 'entity_property_verbatim_set',
);
$properties['title'] = array(
'type' => 'text',
'label' => t('The "Title" attribute text'),
'setter callback' => 'entity_property_verbatim_set',
);
return $properties;
}
/**
* Previously, hook_entity_property_info() has been provided by the removed
* entity metadata module. To provide backward compatibility for provided
* helpers that may be specified in hook_entity_property_info(), the following
* (deprecated) functions are provided.
*/
/**
* Deprecated.
* Do not make use of this function, instead use the new one.
*/
function entity_metadata_verbatim_get($data, array $options, $name) {
return entity_property_verbatim_get($data, $options, $name);
}
/**
* Deprecated.
* Do not make use of this function, instead use the new one.
*/
function entity_metadata_verbatim_set($data, $name, $value) {
return entity_property_verbatim_set($data, $name, $value);
}
/**
* Deprecated.
* Do not make use of this function, instead use the new one.
*/
function entity_metadata_getter_method($object, array $options, $name) {
return entity_property_getter_method($object, $options, $name);
}
/**
* Deprecated.
* Do not make use of this function, instead use the new one.
*/
function entity_metadata_setter_method($object, $name, $value) {
entity_property_setter_method($object, $name, $value);
}
/**
* Deprecated.
* Do not make use of this function, instead use the new one.
*/
function entity_metadata_get_list($data, array $options, $name) {
return entity_property_get_list($data, $options, $name);
}
/**
* Deprecated.
* Do not make use of this function, instead use the new one.
*/
function entity_metadata_validate_integer_positive($value) {
return entity_property_validate_integer_positive($value);
}
/**
* Deprecated.
* Do not make use of this function, instead use the new one.
*/
function entity_metadata_validate_integer_non_negative($value) {
return entity_property_validate_integer_non_negative($value);
}
/**
* Deprecated.
* Do not make use of this function, instead use the new one.
*/
function entity_metadata_text_formatted_properties() {
return entity_property_text_formatted_info();
}

View File

@@ -0,0 +1,672 @@
<?php
/**
* @file
* Provides a controller for building an entity overview form.
*/
/**
* Default controller for providing UI.
*/
class EntityDefaultUIController {
protected $entityType;
protected $entityInfo, $path;
/**
* Defines the number of entries to show per page in overview table.
*/
public $overviewPagerLimit = 25;
public function __construct($entity_type, $entity_info) {
$this->entityType = $entity_type;
$this->entityInfo = $entity_info;
$this->path = $this->entityInfo['admin ui']['path'];
$this->statusKey = empty($this->entityInfo['entity keys']['status']) ? 'status' : $this->entityInfo['entity keys']['status'];
}
/**
* Provides definitions for implementing hook_menu().
*/
public function hook_menu() {
$items = array();
$id_count = count(explode('/', $this->path));
$wildcard = isset($this->entityInfo['admin ui']['menu wildcard']) ? $this->entityInfo['admin ui']['menu wildcard'] : '%entity_object';
$plural_label = isset($this->entityInfo['plural label']) ? $this->entityInfo['plural label'] : $this->entityInfo['label'] . 's';
$items[$this->path] = array(
'title' => $plural_label,
'page callback' => 'drupal_get_form',
'page arguments' => array($this->entityType . '_overview_form', $this->entityType),
'description' => 'Manage ' . $plural_label . '.',
'access callback' => 'entity_access',
'access arguments' => array('view', $this->entityType),
'file' => 'includes/entity.ui.inc',
);
$items[$this->path . '/list'] = array(
'title' => 'List',
'type' => MENU_DEFAULT_LOCAL_TASK,
'weight' => -10,
);
$items[$this->path . '/add'] = array(
'title callback' => 'entity_ui_get_action_title',
'title arguments' => array('add', $this->entityType),
'page callback' => 'entity_ui_get_form',
'page arguments' => array($this->entityType, NULL, 'add'),
'access callback' => 'entity_access',
'access arguments' => array('create', $this->entityType),
'type' => MENU_LOCAL_ACTION,
);
$items[$this->path . '/manage/' . $wildcard] = array(
'title' => 'Edit',
'title callback' => 'entity_label',
'title arguments' => array($this->entityType, $id_count + 1),
'page callback' => 'entity_ui_get_form',
'page arguments' => array($this->entityType, $id_count + 1),
'load arguments' => array($this->entityType),
'access callback' => 'entity_access',
'access arguments' => array('update', $this->entityType, $id_count + 1),
);
$items[$this->path . '/manage/' . $wildcard . '/edit'] = array(
'title' => 'Edit',
'load arguments' => array($this->entityType),
'type' => MENU_DEFAULT_LOCAL_TASK,
);
// Clone form, a special case for the edit form.
$items[$this->path . '/manage/' . $wildcard . '/clone'] = array(
'title' => 'Clone',
'page callback' => 'entity_ui_get_form',
'page arguments' => array($this->entityType, $id_count + 1, 'clone'),
'load arguments' => array($this->entityType),
'access callback' => 'entity_access',
'access arguments' => array('create', $this->entityType),
);
// Menu item for operations like revert and delete.
$items[$this->path . '/manage/' . $wildcard . '/%'] = array(
'page callback' => 'drupal_get_form',
'page arguments' => array($this->entityType . '_operation_form', $this->entityType, $id_count + 1, $id_count + 2),
'load arguments' => array($this->entityType),
'access callback' => 'entity_access',
'access arguments' => array('delete', $this->entityType, $id_count + 1),
'file' => 'includes/entity.ui.inc',
);
if (!empty($this->entityInfo['exportable'])) {
// Menu item for importing an entity.
$items[$this->path . '/import'] = array(
'title callback' => 'entity_ui_get_action_title',
'title arguments' => array('import', $this->entityType),
'page callback' => 'drupal_get_form',
'page arguments' => array($this->entityType . '_operation_form', $this->entityType, NULL, 'import'),
'access callback' => 'entity_access',
'access arguments' => array('create', $this->entityType),
'file' => 'includes/entity.ui.inc',
'type' => MENU_LOCAL_ACTION,
);
}
if (!empty($this->entityInfo['admin ui']['file'])) {
// Add in the include file for the entity form.
foreach (array("/manage/$wildcard", "/manage/$wildcard/clone", '/add') as $path_end) {
$items[$this->path . $path_end]['file'] = $this->entityInfo['admin ui']['file'];
$items[$this->path . $path_end]['file path'] = isset($this->entityInfo['admin ui']['file path']) ? $this->entityInfo['admin ui']['file path'] : drupal_get_path('module', $this->entityInfo['module']);
}
}
return $items;
}
/**
* Provides definitions for implementing hook_forms().
*
* Use per bundle form ids if possible, such that easy per bundle alterations
* are supported too.
*
* Note that for performance reasons, this method is only invoked for forms,
* which receive the entity_type as first argument. Thus any forms added, must
* follow that pattern.
*
* @see entity_forms()
*/
public function hook_forms() {
// The overview and the operation form are implemented by the controller,
// the callback and validation + submit handlers just invoke the controller.
$forms[$this->entityType . '_overview_form'] = array(
'callback' => 'entity_ui_overview_form',
'wrapper_callback' => 'entity_ui_form_defaults',
);
$forms[$this->entityType . '_operation_form'] = array(
'callback' => 'entity_ui_operation_form',
'wrapper_callback' => 'entity_ui_form_defaults',
);
// The entity form (ENTITY_TYPE_form) handles editing, adding and cloning.
// For that form, the wrapper callback entity_ui_main_form_defaults() gets
// directly invoked via entity_ui_get_form().
// If there are bundles though, we use form ids that include the bundle name
// (ENTITY_TYPE_edit_BUNDLE_NAME_form) to enable per bundle alterations
// as well as alterations based upon the base form id (ENTITY_TYPE_form).
if (!(count($this->entityInfo['bundles']) == 1 && isset($this->entityInfo['bundles'][$this->entityType]))) {
foreach ($this->entityInfo['bundles'] as $bundle => $bundle_info) {
$forms[$this->entityType . '_edit_' . $bundle . '_form']['callback'] = $this->entityType . '_form';
// Again the wrapper callback is invoked by entity_ui_get_form() anyway.
}
}
return $forms;
}
/**
* Builds the entity overview form.
*/
public function overviewForm($form, &$form_state) {
// By default just show a simple overview for all entities.
$form['table'] = $this->overviewTable();
$form['pager'] = array('#theme' => 'pager');
return $form;
}
/**
* Overview form validation callback.
*
* @param $form
* The form array of the overview form.
* @param $form_state
* The overview form state which will be used for validating.
*/
public function overviewFormValidate($form, &$form_state) {}
/**
* Overview form submit callback.
*
* @param $form
* The form array of the overview form.
* @param $form_state
* The overview form state which will be used for submitting.
*/
public function overviewFormSubmit($form, &$form_state) {}
/**
* Generates the render array for a overview table for arbitrary entities
* matching the given conditions.
*
* @param $conditions
* An array of conditions as needed by entity_load().
* @return Array
* A renderable array.
*/
public function overviewTable($conditions = array()) {
$query = new EntityFieldQuery();
$query->entityCondition('entity_type', $this->entityType);
// Add all conditions to query.
foreach ($conditions as $key => $value) {
$query->propertyCondition($key, $value);
}
if ($this->overviewPagerLimit) {
$query->pager($this->overviewPagerLimit);
}
$results = $query->execute();
$ids = isset($results[$this->entityType]) ? array_keys($results[$this->entityType]) : array();
$entities = $ids ? entity_load($this->entityType, $ids) : array();
ksort($entities);
$rows = array();
foreach ($entities as $entity) {
$rows[] = $this->overviewTableRow($conditions, entity_id($this->entityType, $entity), $entity);
}
$render = array(
'#theme' => 'table',
'#header' => $this->overviewTableHeaders($conditions, $rows),
'#rows' => $rows,
'#empty' => t('None.'),
);
return $render;
}
/**
* Generates the table headers for the overview table.
*/
protected function overviewTableHeaders($conditions, $rows, $additional_header = array()) {
$header = $additional_header;
array_unshift($header, t('Label'));
if (!empty($this->entityInfo['exportable'])) {
$header[] = t('Status');
}
// Add operations with the right colspan.
$header[] = array('data' => t('Operations'), 'colspan' => $this->operationCount());
return $header;
}
/**
* Returns the operation count for calculating colspans.
*/
protected function operationCount() {
$count = 3;
$count += !empty($this->entityInfo['bundle of']) && entity_type_is_fieldable($this->entityInfo['bundle of']) && module_exists('field_ui') ? 2 : 0;
$count += !empty($this->entityInfo['exportable']) ? 1 : 0;
$count += !empty($this->entityInfo['i18n controller class']) ? 1 : 0;
return $count;
}
/**
* Generates the row for the passed entity and may be overridden in order to
* customize the rows.
*
* @param $additional_cols
* Additional columns to be added after the entity label column.
*/
protected function overviewTableRow($conditions, $id, $entity, $additional_cols = array()) {
$entity_uri = entity_uri($this->entityType, $entity);
$row[] = array('data' => array(
'#theme' => 'entity_ui_overview_item',
'#label' => entity_label($this->entityType, $entity),
'#name' => !empty($this->entityInfo['exportable']) ? entity_id($this->entityType, $entity) : FALSE,
'#url' => $entity_uri ? $entity_uri : FALSE,
'#entity_type' => $this->entityType),
);
// Add in any passed additional cols.
foreach ($additional_cols as $col) {
$row[] = $col;
}
// Add a row for the exportable status.
if (!empty($this->entityInfo['exportable'])) {
$row[] = array('data' => array(
'#theme' => 'entity_status',
'#status' => $entity->{$this->statusKey},
));
}
// In case this is a bundle, we add links to the field ui tabs.
$field_ui = !empty($this->entityInfo['bundle of']) && entity_type_is_fieldable($this->entityInfo['bundle of']) && module_exists('field_ui');
// For exportable entities we add an export link.
$exportable = !empty($this->entityInfo['exportable']);
// If i18n integration is enabled, add a link to the translate tab.
$i18n = !empty($this->entityInfo['i18n controller class']);
// Add operations depending on the status.
if (entity_has_status($this->entityType, $entity, ENTITY_FIXED)) {
$row[] = array('data' => l(t('clone'), $this->path . '/manage/' . $id . '/clone'), 'colspan' => $this->operationCount());
}
else {
$row[] = l(t('edit'), $this->path . '/manage/' . $id);
if ($field_ui) {
$row[] = l(t('manage fields'), $this->path . '/manage/' . $id . '/fields');
$row[] = l(t('manage display'), $this->path . '/manage/' . $id . '/display');
}
if ($i18n) {
$row[] = l(t('translate'), $this->path . '/manage/' . $id . '/translate');
}
if ($exportable) {
$row[] = l(t('clone'), $this->path . '/manage/' . $id . '/clone');
}
if (empty($this->entityInfo['exportable']) || !entity_has_status($this->entityType, $entity, ENTITY_IN_CODE)) {
$row[] = l(t('delete'), $this->path . '/manage/' . $id . '/delete', array('query' => drupal_get_destination()));
}
elseif (entity_has_status($this->entityType, $entity, ENTITY_OVERRIDDEN)) {
$row[] = l(t('revert'), $this->path . '/manage/' . $id . '/revert', array('query' => drupal_get_destination()));
}
else {
$row[] = '';
}
}
if ($exportable) {
$row[] = l(t('export'), $this->path . '/manage/' . $id . '/export');
}
return $row;
}
/**
* Builds the operation form.
*
* For the export operation a serialized string of the entity is directly
* shown in the form (no submit function needed).
*/
public function operationForm($form, &$form_state, $entity, $op) {
switch ($op) {
case 'revert':
$label = entity_label($this->entityType, $entity);
$confirm_question = t('Are you sure you want to revert the %entity %label?', array('%entity' => $this->entityInfo['label'], '%label' => $label));
return confirm_form($form, $confirm_question, $this->path);
case 'delete':
$label = entity_label($this->entityType, $entity);
$confirm_question = t('Are you sure you want to delete the %entity %label?', array('%entity' => $this->entityInfo['label'], '%label' => $label));
return confirm_form($form, $confirm_question, $this->path);
case 'export':
if (!empty($this->entityInfo['exportable'])) {
$export = entity_export($this->entityType, $entity);
$form['export'] = array(
'#type' => 'textarea',
'#title' => t('Export'),
'#description' => t('For importing copy the content of the text area and paste it into the import page.'),
'#rows' => 25,
'#default_value' => $export,
);
return $form;
}
case 'import':
$form['import'] = array(
'#type' => 'textarea',
'#title' => t('Import'),
'#description' => t('Paste an exported %entity_type here.', array('%entity_type' => $this->entityInfo['label'])),
'#rows' => 20,
);
$form['overwrite'] = array(
'#title' => t('Overwrite'),
'#type' => 'checkbox',
'#description' => t('If checked, any existing %entity with the same identifier will be replaced by the import.', array('%entity' => $this->entityInfo['label'])),
'#default_value' => FALSE,
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Import'),
);
return $form;
}
drupal_not_found();
exit;
}
/**
* Operation form validation callback.
*/
public function operationFormValidate($form, &$form_state) {
if ($form_state['op'] == 'import') {
if ($entity = entity_import($this->entityType, $form_state['values']['import'])) {
// Store the successfully imported entity in $form_state.
$form_state[$this->entityType] = $entity;
if (!$form_state['values']['overwrite']) {
// Check for existing entities with the same identifier.
$id = entity_id($this->entityType, $entity);
$entities = entity_load($this->entityType, array($id));
if (!empty($entities)) {
$label = entity_label($this->entityType, $entity);
$vars = array('%entity' => $this->entityInfo['label'], '%label' => $label);
form_set_error('import', t('Import of %entity %label failed, a %entity with the same machine name already exists. Check the overwrite option to replace it.', $vars));
}
}
}
else {
form_set_error('import', t('Import failed.'));
}
}
}
/**
* Operation form submit callback.
*/
public function operationFormSubmit($form, &$form_state) {
$msg = $this->applyOperation($form_state['op'], $form_state[$this->entityType]);
drupal_set_message($msg);
$form_state['redirect'] = $this->path;
}
/**
* Applies an operation to the given entity.
*
* Note: the export operation is directly carried out by the operationForm()
* method.
*
* @param string $op
* The operation (revert, delete or import).
* @param $entity
* The entity to manipulate.
*
* @return
* The status message of what has been applied.
*/
public function applyOperation($op, $entity) {
$label = entity_label($this->entityType, $entity);
$vars = array('%entity' => $this->entityInfo['label'], '%label' => $label);
$id = entity_id($this->entityType, $entity);
$edit_link = l(t('edit'), $this->path . '/manage/' . $id . '/edit');
switch ($op) {
case 'revert':
entity_delete($this->entityType, $id);
watchdog($this->entityType, 'Reverted %entity %label to the defaults.', $vars, WATCHDOG_NOTICE, $edit_link);
return t('Reverted %entity %label to the defaults.', $vars);
case 'delete':
entity_delete($this->entityType, $id);
watchdog($this->entityType, 'Deleted %entity %label.', $vars);
return t('Deleted %entity %label.', $vars);
case 'import':
// First check if there is any existing entity with the same ID.
$id = entity_id($this->entityType, $entity);
$entities = entity_load($this->entityType, array($id));
if ($existing_entity = reset($entities)) {
// Copy DB id and remove the new indicator to overwrite the DB record.
$idkey = $this->entityInfo['entity keys']['id'];
$entity->{$idkey} = $existing_entity->{$idkey};
unset($entity->is_new);
}
entity_save($this->entityType, $entity);
watchdog($this->entityType, 'Imported %entity %label.', $vars);
return t('Imported %entity %label.', $vars);
default:
return FALSE;
}
}
/**
* Entity submit builder invoked via entity_ui_form_submit_build_entity().
*
* Extracts the form values and updates the entity.
*
* The provided implementation makes use of the helper function
* entity_form_submit_build_entity() provided by core, which already invokes
* the field API attacher for fieldable entities.
*
* @return
* The updated entity.
*
* @see entity_ui_form_submit_build_entity()
*/
public function entityFormSubmitBuildEntity($form, &$form_state) {
// Add the bundle property to the entity if the entity type supports bundles
// and the form provides a value for the bundle key. Especially new entities
// need to have their bundle property pre-populated before we invoke
// entity_form_submit_build_entity().
if (!empty($this->entityInfo['entity keys']['bundle']) && isset($form_state['values'][$this->entityInfo['entity keys']['bundle']])) {
$form_state[$this->entityType]->{$this->entityInfo['entity keys']['bundle']} = $form_state['values'][$this->entityInfo['entity keys']['bundle']];
}
entity_form_submit_build_entity($this->entityType, $form_state[$this->entityType], $form, $form_state);
return $form_state[$this->entityType];
}
}
/**
* Form builder function for the overview form.
*
* @see EntityDefaultUIController::overviewForm()
*/
function entity_ui_overview_form($form, &$form_state, $entity_type) {
return entity_ui_controller($entity_type)->overviewForm($form, $form_state);
}
/**
* Form builder for the entity operation form.
*
* @see EntityDefaultUIController::operationForm()
*/
function entity_ui_operation_form($form, &$form_state, $entity_type, $entity, $op) {
$form_state['op'] = $op;
return entity_ui_controller($entity_type)->operationForm($form, $form_state, $entity, $op);
}
/**
* Form wrapper the main entity form.
*
* @see entity_ui_form_defaults()
*/
function entity_ui_main_form_defaults($form, &$form_state, $entity = NULL, $op = NULL) {
// Now equals entity_ui_form_defaults() but is still here to keep backward
// compatability.
return entity_ui_form_defaults($form, $form_state, $form_state['entity_type'], $entity, $op);
}
/**
* Clones the entity object and makes sure it will get saved as new entity.
*
* @return
* The cloned entity object.
*/
function entity_ui_clone_entity($entity_type, $entity) {
// Clone the entity and make sure it will get saved as a new entity.
$entity = clone $entity;
$entity_info = entity_get_info($entity_type);
$entity->{$entity_info['entity keys']['id']} = FALSE;
if (!empty($entity_info['entity keys']['name'])) {
$entity->{$entity_info['entity keys']['name']} = FALSE;
}
$entity->is_new = TRUE;
// Make sure the status of a cloned exportable is custom.
if (!empty($entity_info['exportable'])) {
$status_key = isset($entity_info['entity keys']['status']) ? $entity_info['entity keys']['status'] : 'status';
$entity->$status_key = ENTITY_CUSTOM;
}
return $entity;
}
/**
* Form wrapper callback for all entity ui forms.
*
* This callback makes sure the form state is properly initialized and sets
* some useful default titles.
*
* @see EntityDefaultUIController::hook_forms()
*/
function entity_ui_form_defaults($form, &$form_state, $entity_type, $entity = NULL, $op = NULL) {
$defaults = array(
'entity_type' => $entity_type,
);
if (isset($entity)) {
$defaults[$entity_type] = $entity;
}
if (isset($op)) {
$defaults['op'] = $op;
}
$form_state += $defaults;
if (isset($op)) {
drupal_set_title(entity_ui_get_page_title($op, $entity_type, $entity), PASS_THROUGH);
}
// Add in handlers pointing to the controller for the forms implemented by it.
if (isset($form_state['build_info']['base_form_id']) && $form_state['build_info']['base_form_id'] != $entity_type . '_form') {
$form['#validate'][] = 'entity_ui_controller_form_validate';
$form['#submit'][] = 'entity_ui_controller_form_submit';
}
return $form;
}
/**
* Validation callback for forms implemented by the UI controller.
*/
function entity_ui_controller_form_validate($form, &$form_state) {
// Remove 'entity_ui_' prefix and the '_form' suffix.
$base = substr($form_state['build_info']['base_form_id'], 10, -5);
$method = $base . 'FormValidate';
entity_ui_controller($form_state['entity_type'])->$method($form, $form_state);
}
/**
* Submit callback for forms implemented by the UI controller.
*/
function entity_ui_controller_form_submit($form, &$form_state) {
// Remove 'entity_ui_' prefix and the '_form' suffix.
$base = substr($form_state['build_info']['base_form_id'], 10, -5);
$method = $base . 'FormSubmit';
entity_ui_controller($form_state['entity_type'])->$method($form, $form_state);
}
/**
* Gets the page title for the passed operation.
*/
function entity_ui_get_page_title($op, $entity_type, $entity = NULL) {
$label = entity_label($entity_type, $entity);
switch ($op) {
case 'edit':
return t('Edit @label', array('@label' => $label));
case 'clone':
return t('Clone @label', array('@label' => $label));
case 'revert':
return t('Revert @label', array('@label' => $label));
case 'delete':
return t('Delete @label', array('@label' => $label));
case 'export':
return t('Export @label', array('@label' => $label));
}
return entity_ui_get_action_title($op, $entity_type);
}
/**
* Gets the page/menu title for local action operations.
*/
function entity_ui_get_action_title($op, $entity_type) {
$info = entity_get_info($entity_type);
switch ($op) {
case 'add':
return t('Add @entity_type', array('@entity_type' => drupal_strtolower($info['label'])));
case 'import':
return t('Import @entity_type', array('@entity_type' => drupal_strtolower($info['label'])));
}
}
/**
* Submit builder for the main entity form, which extracts the form values and updates the entity.
*
* This is a helper function for entities making use of the entity UI
* controller.
*
* @return
* The updated entity.
*
* @see EntityDefaultUIController::hook_forms()
* @see EntityDefaultUIController::entityFormSubmitBuildEntity()
*/
function entity_ui_form_submit_build_entity($form, &$form_state) {
return entity_ui_controller($form_state['entity_type'])->entityFormSubmitBuildEntity($form, $form_state);
}
/**
* Validation callback for machine names of exportables.
*
* We don't allow numeric machine names, as entity_load() treats them as the
* numeric identifier and they are easily confused with ids in general.
*/
function entity_ui_validate_machine_name($element, &$form_state) {
if (is_numeric($element['#value'])) {
form_error($element, t('Machine-readable names must not consist of numbers only.'));
}
}
/**
* Returns HTML for an entity on the entity overview listing.
*
* @ingroup themeable
*/
function theme_entity_ui_overview_item($variables) {
$output = $variables['url'] ? l($variables['label'], $variables['url']['path'], $variables['url']['options']) : check_plain($variables['label']);
if ($variables['name']) {
$output .= ' <small> (' . t('Machine name') . ': ' . check_plain($variables['name']) . ')</small>';
}
return $output;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,23 @@
<?php
/**
* @file
* Provides info about book nodes.
*/
/**
* Implements hook_entity_property_info_alter() on top of book module.
*
* @see entity_entity_property_info_alter()
*/
function entity_metadata_book_entity_property_info_alter(&$info) {
// Add meta-data about the added node properties.
$properties = &$info['node']['properties'];
$properties['book'] = array(
'label' => t("Book"),
'type' => 'node',
'description' => t("If part of a book, the book to which this book page belongs."),
'getter callback' => 'entity_metadata_book_get_properties',
);
}

View File

@@ -0,0 +1,954 @@
<?php
/**
* @file
* Provides various callbacks for the whole core module integration.
*/
/**
* Callback for getting properties of an entity.
*/
function entity_metadata_entity_get_properties($entity, array $options, $name, $entity_type) {
if ($name == 'url') {
$return = entity_uri($entity_type, $entity);
return url($return['path'], $return['options'] + $options);
}
}
/**
* Callback for getting book node properties.
* @see entity_metadata_book_entity_info_alter()
*/
function entity_metadata_book_get_properties($node, array $options, $name, $entity_type) {
if (!isset($node->book['bid'])) {
throw new EntityMetadataWrapperException('This node is no book page.');
}
return $node->book['bid'];
}
/**
* Callback for getting comment properties.
* @see entity_metadata_comment_entity_info_alter()
*/
function entity_metadata_comment_get_properties($comment, array $options, $name) {
switch ($name) {
case 'name':
return $comment->name;
case 'mail':
if ($comment->uid != 0) {
$account = user_load($comment->uid);
return $account->mail;
}
return $comment->mail;
case 'edit_url':
return url('comment/edit/' . $comment->cid, $options);
case 'parent':
if (!empty($comment->pid)) {
return $comment->pid;
}
// There is no parent comment.
return NULL;
}
}
/**
* Callback for setting comment properties.
* @see entity_metadata_comment_entity_info_alter()
*/
function entity_metadata_comment_setter($comment, $name, $value) {
switch ($name) {
case 'node':
$comment->nid = $value;
// Also set the bundle name.
$node = node_load($value);
$comment->node_type = 'comment_node_' . $node->type;
break;
}
}
/**
* Callback for getting comment related node properties.
* @see entity_metadata_comment_entity_info_alter()
*/
function entity_metadata_comment_get_node_properties($node, array $options, $name, $entity_type) {
switch ($name) {
case 'comment_count':
return isset($node->comment_count) ? $node->comment_count : 0;
case 'comment_count_new':
return comment_num_new($node->nid);
}
}
/**
* Getter callback for getting global languages.
*/
function entity_metadata_locale_get_languages($data, array $options, $name) {
return isset($GLOBALS[$name]) ? $GLOBALS[$name]->language : NULL;
}
/**
* Getter callback for getting the preferred user language.
*/
function entity_metadata_locale_get_user_language($account, array $options, $name) {
return user_preferred_language($account)->language;
}
/**
* Return the options lists for the node and comment status property.
*/
function entity_metadata_status_options_list() {
return array(
NODE_PUBLISHED => t('Published'),
NODE_NOT_PUBLISHED => t('Unpublished'),
);
}
/**
* Callback for getting node properties.
*
* @see entity_metadata_node_entity_info_alter()
*/
function entity_metadata_node_get_properties($node, array $options, $name, $entity_type) {
switch ($name) {
case 'is_new':
return empty($node->nid) || !empty($node->is_new);
case 'source':
if (!empty($node->tnid) && $source = node_load($node->tnid)) {
return $source;
}
return NULL;
case 'edit_url':
return url('node/' . $node->nid . '/edit', $options);
}
}
/**
* Callback for determing access for node revision related properties.
*/
function entity_metadata_node_revision_access($op, $name, $entity = NULL, $account = NULL) {
return $op == 'view' ? user_access('view revisions', $account) : user_access('administer nodes', $account);
}
/**
* Callback for getting poll properties.
* @see entity_metadata_poll_entity_info_alter()
*/
function entity_metadata_poll_node_get_properties($node, array $options, $name) {
$total_votes = $highest_votes = 0;
foreach ($node->choice as $choice) {
if ($choice['chvotes'] > $highest_votes) {
$winner = $choice;
$highest_votes = $choice['chvotes'];
}
$total_votes = $total_votes + $choice['chvotes'];
}
if ($name == 'poll_duration') {
return $node->runtime;
}
elseif ($name == 'poll_votes') {
return $total_votes;
}
elseif (!isset($winner)) {
// There is no poll winner yet.
return NULL;
}
switch ($name) {
case 'poll_winner_votes':
return $winner['chvotes'];
case 'poll_winner':
return $winner['chtext'];
case 'poll_winner_percent':
return ($winner['chvotes'] / $total_votes) * 100;
}
}
/**
* Callback for getting statistics properties.
* @see entity_metadata_statistics_entity_info_alter()
*/
function entity_metadata_statistics_node_get_properties($node, array $options, $name) {
$statistics = (array) statistics_get($node->nid);
$statistics += array('totalcount' => 0, 'daycount' => 0, 'timestamp' => NULL);
switch ($name) {
case 'views':
return $statistics['totalcount'];
case 'day_views':
return $statistics['daycount'];
case 'last_view':
return $statistics['timestamp'];
}
}
/**
* Callback for getting site-wide properties.
* @see entity_metadata_system_entity_info_alter()
*/
function entity_metadata_system_get_properties($data = FALSE, array $options, $name) {
switch ($name) {
case 'name':
return variable_get('site_name', 'Drupal');
case 'url':
return url('<front>', $options);
case 'login_url':
return url('user', $options);
case 'current_user':
return $GLOBALS['user']->uid ? $GLOBALS['user']->uid : drupal_anonymous_user();
case 'current_date':
return REQUEST_TIME;
case 'current_page':
// Subsequent getters of the struct retrieve the actual values.
return array();
default:
return variable_get('site_' . $name, '');
}
}
/**
* Callback for getting properties for the current page request.
* @see entity_metadata_system_entity_info_alter()
*/
function entity_metadata_system_get_page_properties($data = array(), array $options, $name) {
switch ($name) {
case 'url':
return $GLOBALS['base_root'] . request_uri();
}
}
/**
* Callback for getting file properties.
* @see entity_metadata_system_entity_info_alter()
*/
function entity_metadata_system_get_file_properties($file, array $options, $name) {
switch ($name) {
case 'name':
return $file->filename;
case 'mime':
return $file->filemime;
case 'size':
return $file->filesize;
case 'url':
return url(file_create_url($file->uri), $options);
case 'owner':
return $file->uid;
}
}
/**
* Callback for getting term properties.
*
* @see entity_metadata_taxonomy_entity_info_alter()
*/
function entity_metadata_taxonomy_term_get_properties($term, array $options, $name) {
switch ($name) {
case 'node_count':
return count(taxonomy_select_nodes($term->tid));
case 'description':
return check_markup($term->description, isset($term->format) ? $term->format : NULL, '', TRUE);
case 'parent':
if (isset($term->parent[0]) && !is_array(isset($term->parent[0]))) {
return $term->parent;
}
return array_keys(taxonomy_get_parents($term->tid));
case 'parents_all':
// We have to return an array of ids.
$tids = array();
foreach (taxonomy_get_parents_all($term->tid) as $parent) {
$tids[] = $parent->tid;
}
return $tids;
}
}
/**
* Callback for setting term properties.
*
* @see entity_metadata_taxonomy_entity_info_alter()
*/
function entity_metadata_taxonomy_term_setter($term, $name, $value) {
switch ($name) {
case 'vocabulary':
// Make sure to update the taxonomy bundle key, so load the vocabulary.
// Support both, loading by name or ID.
$vocabulary = is_numeric($value) ? taxonomy_vocabulary_load($value) : taxonomy_vocabulary_machine_name_load($value);
$term->vocabulary_machine_name = $vocabulary->machine_name;
return $term->vid = $vocabulary->vid;
case 'parent':
return $term->parent = $value;
}
}
/**
* Callback for getting vocabulary properties.
* @see entity_metadata_taxonomy_entity_info_alter()
*/
function entity_metadata_taxonomy_vocabulary_get_properties($vocabulary, array $options, $name) {
switch ($name) {
case 'term_count':
$sql = "SELECT COUNT (1) FROM {taxonomy_term_data} td WHERE td.vid = :vid";
return db_query($sql, array(':vid' => $vocabulary->vid))->fetchField();
}
}
/**
* Callback for getting user properties.
* @see entity_metadata_user_entity_info_alter()
*/
function entity_metadata_user_get_properties($account, array $options, $name, $entity_type) {
switch ($name) {
case 'last_access':
// In case there was no access the value is 0, but we have to return NULL.
return empty($account->access) ? NULL : $account->access;
case 'last_login':
return empty($account->login) ? NULL : $account->login;
case 'name':
return empty($account->uid) ? variable_get('anonymous', t('Anonymous')) : $account->name;
case 'url':
if (empty($account->uid)) {
return NULL;
}
$return = entity_uri('user', $account);
return $return ? url($return['path'], $return['options'] + $options) : '';
case 'edit_url':
return empty($account->uid) ? NULL : url("user/$account->uid/edit", $options);
case 'roles':
return isset($account->roles) ? array_keys($account->roles) : array();
case 'theme':
return empty($account->theme) ? variable_get('theme_default', 'bartik') : $account->theme;
}
}
/**
* Callback for setting user properties.
* @see entity_metadata_user_entity_info_alter()
*/
function entity_metadata_user_set_properties($account, $name, $value) {
switch ($name) {
case 'roles':
$account->roles = array_intersect_key(user_roles(), array_flip($value));
break;
}
}
/**
* Options list callback returning all user roles.
*/
function entity_metadata_user_roles($property_name = 'roles', $info = array(), $op = 'edit') {
$roles = user_roles();
if ($op == 'edit') {
unset($roles[DRUPAL_AUTHENTICATED_RID], $roles[DRUPAL_ANONYMOUS_RID]);
}
return $roles;
}
/**
* Return the options lists for user status property.
*/
function entity_metadata_user_status_options_list() {
return array(
0 => t('Blocked'),
1 => t('Active'),
);
}
/**
* Callback defining an options list for language properties.
*/
function entity_metadata_language_list() {
$list = array();
$list[LANGUAGE_NONE] = t('Language neutral');
foreach (language_list() as $language) {
$list[$language->language] = $language->name;
}
return $list;
}
/**
* Callback for getting field property values.
*/
function entity_metadata_field_property_get($entity, array $options, $name, $entity_type, $info) {
$field = field_info_field($name);
$columns = array_keys($field['columns']);
$langcode = isset($options['language']) ? $options['language']->language : LANGUAGE_NONE;
$langcode = entity_metadata_field_get_language($entity_type, $entity, $field, $langcode, TRUE);
$values = array();
if (isset($entity->{$name}[$langcode])) {
foreach ($entity->{$name}[$langcode] as $delta => $data) {
$values[$delta] = $data[$columns[0]];
if ($info['type'] == 'boolean' || $info['type'] == 'list<boolean>') {
// Ensure that we have a clean boolean data type.
$values[$delta] = (boolean) $values[$delta];
}
}
}
// For an empty single-valued field, we have to return NULL.
return $field['cardinality'] == 1 ? ($values ? reset($values) : NULL) : $values;
}
/**
* Callback for setting field property values.
*/
function entity_metadata_field_property_set($entity, $name, $value, $langcode, $entity_type) {
$field = field_info_field($name);
$columns = array_keys($field['columns']);
$langcode = entity_metadata_field_get_language($entity_type, $entity, $field, $langcode);
$values = $field['cardinality'] == 1 ? array($value) : (array) $value;
$items = array();
foreach ($values as $delta => $value) {
if (isset($value)) {
$items[$delta][$columns[0]] = $value;
}
}
$entity->{$name}[$langcode] = $items;
// Empty the static field language cache, so the field system picks up any
// possible new languages.
drupal_static_reset('field_language');
}
/**
* Callback returning the options list of a field.
*/
function entity_metadata_field_options_list($name, $info) {
$field_property_info = $info;
if (is_numeric($name) && isset($info['parent'])) {
// The options list is to be returned for a single item of a multiple field.
$field_property_info = $info['parent']->info();
$name = $field_property_info['name'];
}
if (($field = field_info_field($name)) && isset($field_property_info['parent'])) {
// Retrieve the wrapped entity holding the field.
$wrapper = $field_property_info['parent'];
try {
$entity = $wrapper->value();
}
catch (EntityMetadataWrapperException $e) {
// No data available.
$entity = NULL;
}
$instance = $wrapper->getBundle() ? field_info_instance($wrapper->type(), $name, $wrapper->getBundle()) : NULL;
return (array) module_invoke($field['module'], 'options_list', $field, $instance, $wrapper->type(), $entity);
}
}
/**
* Callback to verbatim get the data structure of a field. Useful for fields
* that add metadata for their own data structure.
*/
function entity_metadata_field_verbatim_get($entity, array $options, $name, $entity_type, &$context) {
// Set contextual info useful for getters of any child properties.
$context['instance'] = field_info_instance($context['parent']->type(), $name, $context['parent']->getBundle());
$context['field'] = field_info_field($name);
$langcode = isset($options['language']) ? $options['language']->language : LANGUAGE_NONE;
$langcode = entity_metadata_field_get_language($entity_type, $entity, $context['field'], $langcode, TRUE);
if ($context['field']['cardinality'] == 1) {
return isset($entity->{$name}[$langcode][0]) ? $entity->{$name}[$langcode][0] : NULL;
}
return isset($entity->{$name}[$langcode]) ? $entity->{$name}[$langcode] : array();
}
/**
* Writes the passed field items in the object. Useful as field level setter
* to set the whole data structure at once.
*/
function entity_metadata_field_verbatim_set($entity, $name, $items, $langcode, $entity_type) {
$field = field_info_field($name);
$langcode = entity_metadata_field_get_language($entity_type, $entity, $field, $langcode);
$value = $field['cardinality'] == 1 ? array($items) : (array) $items;
// Filter out any items set to NULL.
$entity->{$name}[$langcode] = array_filter($value);
// Empty the static field language cache, so the field system picks up any
// possible new languages.
drupal_static_reset('field_language');
}
/**
* Helper for determining the field language to be used.
*
* Note that we cannot use field_language() as we are not about to display
* values, but generally read/write values.
*
* @param $fallback
* (optional) Whether to fall back to the entity default language, if no
* value is available for the given language code yet.
*
* @return
* The language code to use.
*/
function entity_metadata_field_get_language($entity_type, $entity, $field, $langcode = LANGUAGE_NONE, $fallback = FALSE) {
// Try to figure out the default language used by the entity.
// With Drupal >= 7.15 we can use entity_language().
if (function_exists('entity_language')) {
$default_langcode = entity_language($entity_type, $entity);
}
else {
$default_langcode = !empty($entity->language) ? $entity->language : LANGUAGE_NONE;
}
// Determine the right language to use.
if ($default_langcode != LANGUAGE_NONE && field_is_translatable($entity_type, $field)) {
$langcode = ($langcode != LANGUAGE_NONE) ? field_valid_language($langcode, $default_langcode) : $default_langcode;
if (!isset($entity->{$field['field_name']}[$langcode]) && $fallback) {
$langcode = $default_langcode;
}
return $langcode;
}
else {
return LANGUAGE_NONE;
}
}
/**
* Callback for getting the sanitized text of 'text_formatted' properties.
* This callback is used for both the 'value' and the 'summary'.
*/
function entity_metadata_field_text_get($item, array $options, $name, $type, $context) {
// $name is either 'value' or 'summary'.
if (!isset($item['safe_' . $name])) {
// Apply input formats.
$langcode = isset($options['language']) ? $options['language']->language : '';
$format = isset($item['format']) ? $item['format'] : filter_default_format();
$item['safe_' . $name] = check_markup($item[$name], $format, $langcode);
// To speed up subsequent calls, update $item with the 'safe_value'.
$context['parent']->set($item);
}
return $item['safe_' . $name];
}
/**
* Defines the list of all available text formats.
*/
function entity_metadata_field_text_formats() {
foreach (filter_formats() as $key => $format) {
$formats[$key] = $format->name;
}
return $formats;
}
/**
* Callback for getting the file entity of file fields.
*/
function entity_metadata_field_file_get($item) {
return $item['fid'];
}
/**
* Callback for setting the file entity of file fields.
*/
function entity_metadata_field_file_set(&$item, $property_name, $value) {
$item['fid'] = $value;
}
/**
* Callback for auto-creating file field $items.
*/
function entity_metadata_field_file_create_item($property_name, $context) {
// 'fid' is required, so 'file' has to be set as initial property.
return array('display' => $context['field']['settings']['display_default']);
}
/**
* Callback for validating file field $items.
*/
function entity_metadata_field_file_validate_item($items, $context) {
// Allow NULL values.
if (!isset($items)) {
return TRUE;
}
// Stream-line $items for multiple vs non-multiple fields.
$items = !entity_property_list_extract_type($context['type']) ? array($items) : (array) $items;
foreach ($items as $item) {
// File-field items require a valid file.
if (!isset($item['fid']) || !file_load($item['fid'])) {
return FALSE;
}
if (isset($context['property info']['display']) && !isset($item['display'])) {
return FALSE;
}
}
return TRUE;
}
/**
* Access callback for the node entity.
*
* This function does not implement hook_node_access(), thus it may not be
* called entity_metadata_node_access().
*/
function entity_metadata_no_hook_node_access($op, $node = NULL, $account = NULL) {
if (isset($node)) {
// If a non-default revision is given, incorporate revision access.
$default_revision = node_load($node->nid);
if ($node->vid != $default_revision->vid) {
return _node_revision_access($node, $op);
}
else {
return node_access($op, $node, $account);
}
}
// Is access to all nodes allowed?
if (!user_access('access content', $account)) {
return FALSE;
}
if (user_access('bypass node access', $account) || (!isset($account) && $op == 'view' && node_access_view_all_nodes())) {
return TRUE;
}
return FALSE;
}
/**
* Access callback for the user entity.
*/
function entity_metadata_user_access($op, $entity = NULL, $account = NULL, $entity_type) {
$account = isset($account) ? $account : $GLOBALS['user'];
// Grant access to the users own user account and to the anonymous one.
if (isset($entity) && $op != 'delete' && (($entity->uid == $account->uid && $entity->uid) || (!$entity->uid && $op == 'view'))) {
return TRUE;
}
if (user_access('administer users', $account) || user_access('access user profiles', $account) && $op == 'view' && $entity->status) {
return TRUE;
}
return FALSE;
}
/**
* Access callback for restricted user properties.
*/
function entity_metadata_user_properties_access($op, $property, $entity = NULL, $account = NULL) {
if (user_access('administer users', $account)) {
return TRUE;
}
$account = isset($account) ? $account : $GLOBALS['user'];
// Flag to indicate if this user entity is the own user account.
$is_own_account = isset($entity) && $account->uid == $entity->uid;
switch ($property) {
case 'name':
// Allow view access to anyone with access to the entity.
if ($op == 'view') {
return TRUE;
}
// Allow edit access for own user name if the permission is satisfied.
return $is_own_account && user_access('change own username', $account);
case 'mail':
// Allow access to own mail address.
return $is_own_account;
case 'roles':
// Allow view access for own roles.
return ($op == 'view' && $is_own_account);
}
return FALSE;
}
/**
* Access callback for the comment entity.
*/
function entity_metadata_comment_access($op, $entity = NULL, $account = NULL) {
if (isset($entity) && !isset($account) && comment_access($op, $entity)) {
return TRUE;
}
if (user_access('administer comments', $account) || user_access('access comments', $account) && $op == 'view') {
return TRUE;
}
return FALSE;
}
/**
* Access callback for the taxonomy entities.
*/
function entity_metadata_taxonomy_access($op, $entity = NULL, $account = NULL, $entity_type) {
if ($entity_type == 'taxonomy_vocabulary') {
return user_access('administer taxonomy', $account);
}
if (isset($entity) && $op == 'update' && !isset($account) && taxonomy_term_edit_access($entity)) {
return TRUE;
}
if (user_access('administer taxonomy', $account) || user_access('access content', $account) && $op == 'view') {
return TRUE;
}
return FALSE;
}
/**
* Access callback for file entities.
*/
function entity_metadata_file_access($op, $file = NULL, $account = NULL, $entity_type) {
// We can only check access for the current user, so return FALSE on other accounts.
global $user;
if ($op == 'view' && isset($file) && (!isset($account) || $user->uid == $account->uid)) {
// Invoke hook_file_download() to obtain access information.
foreach (module_implements('file_download') as $module) {
$result = module_invoke($module, 'file_download', $file->uri);
if ($result == -1) {
return FALSE;
}
}
return TRUE;
}
return FALSE;
}
/**
* Callback to determine access for properties which are fields.
*/
function entity_metadata_field_access_callback($op, $name, $entity = NULL, $account = NULL, $entity_type) {
$field = field_info_field($name);
return field_access($op, $field, $entity_type, $entity, $account);
}
/**
* Callback to create entity objects.
*/
function entity_metadata_create_object($values = array(), $entity_type) {
$info = entity_get_info($entity_type);
// Make sure at least the bundle and label properties are set.
if (isset($info['entity keys']['bundle']) && $key = $info['entity keys']['bundle']) {
$values += array($key => NULL);
}
if (isset($info['entity keys']['label']) && $key = $info['entity keys']['label']) {
$values += array($key => NULL);
}
$entity = (object) $values;
$entity->is_new = TRUE;
return $entity;
}
/**
* Callback to create a new comment.
*/
function entity_metadata_create_comment($values = array()) {
$comment = (object) ($values + array(
'status' => COMMENT_PUBLISHED,
'pid' => 0,
'subject' => '',
'uid' => 0,
'language' => LANGUAGE_NONE,
'node_type' => NULL,
'is_new' => TRUE,
));
$comment->cid = FALSE;
return $comment;
}
/**
* Callback to create a new node.
*/
function entity_metadata_create_node($values = array()) {
$node = (object) array(
'type' => $values['type'],
'language' => LANGUAGE_NONE,
'is_new' => TRUE,
);
// Set some defaults.
$node_options = variable_get('node_options_' . $node->type, array('status', 'promote'));
foreach (array('status', 'promote', 'sticky') as $key) {
$node->$key = (int) in_array($key, $node_options);
}
if (module_exists('comment') && !isset($node->comment)) {
$node->comment = variable_get("comment_$node->type", COMMENT_NODE_OPEN);
}
// Apply the given values.
foreach ($values as $key => $value) {
$node->$key = $value;
}
return $node;
}
/**
* Callback to save a user account.
*/
function entity_metadata_user_save($account) {
$edit = (array) $account;
// Don't save the hashed password as password.
unset($edit['pass']);
user_save($account, $edit);
}
/**
* Callback to delete a file.
* Watch out to not accidentilly implement hook_file_delete().
*/
function entity_metadata_delete_file($fid) {
file_delete(file_load($fid), TRUE);
}
/**
* Callback to view nodes.
*/
function entity_metadata_view_node($entities, $view_mode = 'full', $langcode = NULL) {
$result = node_view_multiple($entities, $view_mode, 0, $langcode);
// Make sure to key the result with 'node' instead of 'nodes'.
return array('node' => reset($result));
}
/**
* Callback to view comments.
*/
function entity_metadata_view_comment($entities, $view_mode = 'full', $langcode = NULL) {
$build = array();
$nodes = array();
// The comments, indexed by nid and then by cid.
$nid_comments = array();
foreach ($entities as $cid => $comment) {
$nid = $comment->nid;
$nodes[$nid] = $nid;
$nid_comments[$nid][$cid] = $comment;
}
$nodes = node_load_multiple(array_keys($nodes));
foreach ($nid_comments as $nid => $comments) {
$node = isset($nodes[$nid]) ? $nodes[$nid] : NULL;
$build += comment_view_multiple($comments, $node, $view_mode, 0, $langcode);
}
return array('comment' => $build);
}
/**
* Callback to view an entity, for which just ENTITYTYPE_view() is available.
*/
function entity_metadata_view_single($entities, $view_mode = 'full', $langcode = NULL, $entity_type) {
$function = $entity_type . '_view';
$build = array();
foreach ($entities as $key => $entity) {
$build[$entity_type][$key] = $function($entity, $view_mode, $langcode);
}
return $build;
}
/**
* Callback to get the form of a node.
*/
function entity_metadata_form_node($node) {
// Pre-populate the form-state with the right form include.
$form_state['build_info']['args'] = array($node);
form_load_include($form_state, 'inc', 'node', 'node.pages');
return drupal_build_form($node->type . '_node_form', $form_state);
}
/**
* Callback to get the form of a comment.
*/
function entity_metadata_form_comment($comment) {
if (!isset($comment->node_type)) {
$node = node_load($comment->nid);
$comment->node_type = 'comment_node_' . $node->type;
}
return drupal_get_form($comment->node_type . '_form', $comment);
}
/**
* Callback to get the form of a user account.
*/
function entity_metadata_form_user($account) {
// Pre-populate the form-state with the right form include.
$form_state['build_info']['args'] = array($account);
form_load_include($form_state, 'inc', 'user', 'user.pages');
return drupal_build_form('user_profile_form', $form_state);
}
/**
* Callback to get the form of a term.
*/
function entity_metadata_form_taxonomy_term($term) {
// Pre-populate the form-state with the right form include.
$form_state['build_info']['args'] = array($term);
form_load_include($form_state, 'inc', 'taxonomy', 'taxonomy.admin');
return drupal_build_form('taxonomy_form_term', $form_state);
}
/**
* Callback to get the form of a vocabulary.
*/
function entity_metadata_form_taxonomy_vocabulary($term) {
// Pre-populate the form-state with the right form include.
$form_state['build_info']['args'] = array($term);
form_load_include($form_state, 'inc', 'taxonomy', 'taxonomy.admin');
return drupal_build_form('taxonomy_form_term', $form_state);
}
/**
* Callback to get the form for entities using the entity API admin ui.
*/
function entity_metadata_form_entity_ui($entity, $entity_type) {
$info = entity_get_info($entity_type);
$form_state = form_state_defaults();
// Add in the include file as the form API does else with the include file
// specified for the active menu item.
if (!empty($info['admin ui']['file'])) {
$path = isset($info['admin ui']['file path']) ? $info['admin ui']['file path'] : drupal_get_path('module', $info['module']);
$form_state['build_info']['files']['entity_ui'] = $path . '/' . $info['admin ui']['file'];
// Also load the include file.
if (file_exists($form_state['build_info']['files']['entity_ui'])) {
require_once DRUPAL_ROOT . '/' . $form_state['build_info']['files']['entity_ui'];
}
}
return entity_ui_get_form($entity_type, $entity, $op = 'edit', $form_state);
}
/**
* Callback for querying entity properties having their values stored in the
* entities main db table.
*/
function entity_metadata_table_query($entity_type, $property, $value, $limit) {
$properties = entity_get_all_property_info($entity_type);
$info = $properties[$property] + array('schema field' => $property);
$query = new EntityFieldQuery();
$query->entityCondition('entity_type', $entity_type, '=')
->propertyCondition($info['schema field'], $value, is_array($value) ? 'IN' : '=')
->range(0, $limit);
$result = $query->execute();
return !empty($result[$entity_type]) ? array_keys($result[$entity_type]) : array();
}
/**
* Callback for querying entities by field values. This function just queries
* for the value of the first specified column. Also it is only suitable for
* fields that don't process the data, so it's stored the same way as returned.
*/
function entity_metadata_field_query($entity_type, $property, $value, $limit) {
$query = new EntityFieldQuery();
$field = field_info_field($property);
$columns = array_keys($field['columns']);
$query->entityCondition('entity_type', $entity_type, '=')
->fieldCondition($field, $columns[0], $value, is_array($value) ? 'IN' : '=')
->range(0, $limit);
$result = $query->execute();
return !empty($result[$entity_type]) ? array_keys($result[$entity_type]) : array();
}

View File

@@ -0,0 +1,164 @@
<?php
/**
* @file
* Provides info about the comment entity.
*/
/**
* Implements hook_entity_property_info() on top of comment module.
*
* @see entity_entity_property_info()
*/
function entity_metadata_comment_entity_property_info() {
$info = array();
// Add meta-data about the basic comment properties.
$properties = &$info['comment']['properties'];
$properties['cid'] = array(
'label' => t("Comment ID"),
'type' => 'integer',
'description' => t("The unique ID of the comment."),
'schema field' => 'cid',
);
$properties['hostname'] = array(
'label' => t("IP Address"),
'description' => t("The IP address of the computer the comment was posted from."),
'schema field' => 'hostname',
);
$properties['name'] = array(
'label' => t("Name"),
'description' => t("The name left by the comment author."),
'getter callback' => 'entity_metadata_comment_get_properties',
'setter callback' => 'entity_property_verbatim_set',
'setter permission' => 'administer comments',
'sanitize' => 'filter_xss',
'schema field' => 'name',
);
$properties['mail'] = array(
'label' => t("Email address"),
'description' => t("The email address left by the comment author."),
'getter callback' => 'entity_metadata_comment_get_properties',
'setter callback' => 'entity_property_verbatim_set',
'setter permission' => 'administer comments',
'validation callback' => 'valid_email_address',
'schema field' => 'mail',
);
$properties['homepage'] = array(
'label' => t("Home page"),
'description' => t("The home page URL left by the comment author."),
'sanitize' => 'filter_xss_bad_protocol',
'setter callback' => 'entity_property_verbatim_set',
'setter permission' => 'administer comments',
'schema field' => 'homepage',
);
$properties['subject'] = array(
'label' => t("Subject"),
'description' => t("The subject of the comment."),
'setter callback' => 'entity_property_verbatim_set',
'setter permission' => 'administer comments',
'sanitize' => 'filter_xss',
'required' => TRUE,
'schema field' => 'subject',
);
$properties['url'] = array(
'label' => t("URL"),
'description' => t("The URL of the comment."),
'getter callback' => 'entity_metadata_entity_get_properties',
'type' => 'uri',
'computed' => TRUE,
);
$properties['edit_url'] = array(
'label' => t("Edit URL"),
'description' => t("The URL of the comment's edit page."),
'getter callback' => 'entity_metadata_comment_get_properties',
'type' => 'uri',
'computed' => TRUE,
);
$properties['created'] = array(
'label' => t("Date created"),
'description' => t("The date the comment was posted."),
'type' => 'date',
'setter callback' => 'entity_property_verbatim_set',
'setter permission' => 'administer comments',
'schema field' => 'created',
);
$properties['parent'] = array(
'label' => t("Parent"),
'description' => t("The comment's parent, if comment threading is active."),
'type' => 'comment',
'getter callback' => 'entity_metadata_comment_get_properties',
'schema field' => 'pid',
);
$properties['node'] = array(
'label' => t("Node"),
'description' => t("The node the comment was posted to."),
'type' => 'node',
'setter callback' => 'entity_metadata_comment_setter',
'setter permission' => 'administer comments',
'required' => TRUE,
'schema field' => 'nid',
);
$properties['author'] = array(
'label' => t("Author"),
'description' => t("The author of the comment."),
'type' => 'user',
'setter callback' => 'entity_property_verbatim_set',
'setter permission' => 'administer comments',
'required' => TRUE,
'schema field' => 'uid',
);
$properties['status'] = array(
'label' => t("Status"),
'description' => t("Whether the comment is published or unpublished."),
'setter callback' => 'entity_property_verbatim_set',
// Although the status is expected to be boolean, its schema suggests
// it is an integer, so we follow the schema definition.
'type' => 'integer',
'options list' => 'entity_metadata_status_options_list',
'setter permission' => 'administer comments',
'schema field' => 'status',
);
return $info;
}
/**
* Implements hook_entity_property_info_alter() on top of comment module.
* @see entity_entity_property_info_alter()
*/
function entity_metadata_comment_entity_property_info_alter(&$info) {
// Add info about comment module related properties to the node entity.
$properties = &$info['node']['properties'];
$properties['comment'] = array(
'label' => t("Comments allowed"),
'description' => t("Whether comments are allowed on this node: 0 = no, 1 = closed (read only), 2 = open (read/write)."),
'setter callback' => 'entity_property_verbatim_set',
'setter permission' => 'administer comments',
'type' => 'integer',
);
$properties['comment_count'] = array(
'label' => t("Comment count"),
'description' => t("The number of comments posted on a node."),
'getter callback' => 'entity_metadata_comment_get_node_properties',
'type' => 'integer',
);
$properties['comment_count_new'] = array(
'label' => t("New comment count"),
'description' => t("The number of comments posted on a node since the reader last viewed it."),
'getter callback' => 'entity_metadata_comment_get_node_properties',
'type' => 'integer',
);
// The comment body field is usually available for all bundles, so add it
// directly to the comment entity.
$info['comment']['properties']['comment_body'] = array(
'type' => 'text_formatted',
'label' => t('The main body text'),
'getter callback' => 'entity_metadata_field_verbatim_get',
'setter callback' => 'entity_metadata_field_verbatim_set',
'property info' => entity_property_text_formatted_info(),
'field' => TRUE,
'required' => TRUE,
);
unset($info['comment']['properties']['comment_body']['property info']['summary']);
}

View File

@@ -0,0 +1,170 @@
<?php
/**
* @file
* Provides info for fields.
*/
/**
* Implements hook_entity_property_info() on top of field module.
*
* @see entity_field_info_alter()
* @see entity_entity_property_info()
*/
function entity_metadata_field_entity_property_info() {
$info = array();
// Loop over all field instances and add them as property.
foreach (field_info_fields() as $field_name => $field) {
$field += array('bundles' => array());
if ($field_type = field_info_field_types($field['type'])) {
// Add in our default callback as the first one.
$field_type += array('property_callbacks' => array());
array_unshift($field_type['property_callbacks'], 'entity_metadata_field_default_property_callback');
foreach ($field['bundles'] as $entity_type => $bundles) {
foreach ($bundles as $bundle) {
$instance = field_info_instance($entity_type, $field_name, $bundle);
if ($instance && empty($instance['deleted'])) {
foreach ($field_type['property_callbacks'] as $callback) {
$callback($info, $entity_type, $field, $instance, $field_type);
}
}
}
}
}
}
return $info;
}
/**
* Callback to add in property info defaults per field instance.
* @see entity_metadata_field_entity_property_info().
*/
function entity_metadata_field_default_property_callback(&$info, $entity_type, $field, $instance, $field_type) {
if (!empty($field_type['property_type'])) {
if ($field['cardinality'] != 1) {
$field_type['property_type'] = 'list<' . $field_type['property_type'] . '>';
}
// Add in instance specific property info, if given and apply defaults.
$name = $field['field_name'];
$property = &$info[$entity_type]['bundles'][$instance['bundle']]['properties'][$name];
$instance += array('property info' => array());
$property = $instance['property info'] + array(
'label' => $instance['label'],
'type' => $field_type['property_type'],
'description' => t('Field "@name".', array('@name' => $name)),
'getter callback' => 'entity_metadata_field_property_get',
'setter callback' => 'entity_metadata_field_property_set',
'access callback' => 'entity_metadata_field_access_callback',
'query callback' => 'entity_metadata_field_query',
'translatable' => !empty($field['translatable']),
// Specify that this property stems from a field.
'field' => TRUE,
'required' => !empty($instance['required']),
);
// For field types of the list module add in the options list callback.
if (strpos($field['type'], 'list') === 0) {
$property['options list'] = 'entity_metadata_field_options_list';
}
}
}
/**
* Additional callback to adapt the property info for text fields. If a text
* field is processed we make use of a separate data structure so that format
* filters are available too. For the text value the sanitized, thus processed
* value is returned by default.
*
* @see entity_metadata_field_entity_property_info()
* @see entity_field_info_alter()
* @see entity_property_text_formatted_info()
*/
function entity_metadata_field_text_property_callback(&$info, $entity_type, $field, $instance, $field_type) {
if (!empty($instance['settings']['text_processing']) || $field['type'] == 'text_with_summary') {
// Define a data structure for dealing with text that is formatted or has
// a summary.
$property = &$info[$entity_type]['bundles'][$instance['bundle']]['properties'][$field['field_name']];
$property['getter callback'] = 'entity_metadata_field_verbatim_get';
$property['setter callback'] = 'entity_metadata_field_verbatim_set';
unset($property['query callback']);
if (empty($instance['settings']['text_processing'])) {
$property['property info'] = entity_property_field_item_textsummary_info();
}
else {
// For formatted text we use the type name 'text_formatted'.
$property['type'] = ($field['cardinality'] != 1) ? 'list<text_formatted>' : 'text_formatted';
$property['property info'] = entity_property_text_formatted_info();
}
// Enable auto-creation of the item, so that it is possible to just set
// the textual or summary value.
$property['auto creation'] = 'entity_property_create_array';
if ($field['type'] != 'text_with_summary') {
unset($property['property info']['summary']);
}
}
}
/**
* Additional callback to adapt the property info for term reference fields.
* @see entity_metadata_field_entity_property_info().
*/
function entity_metadata_field_term_reference_callback(&$info, $entity_type, $field, $instance, $field_type) {
$property = &$info[$entity_type]['bundles'][$instance['bundle']]['properties'][$field['field_name']];
if (count($field['settings']['allowed_values']) == 1) {
$settings = reset($field['settings']['allowed_values']);
$property['bundle'] = $settings['vocabulary'];
}
// Only add the options list callback for controlled vocabularies, thus
// vocabularies not using the autocomplete widget.
if ($instance['widget']['type'] != 'taxonomy_autocomplete') {
$property['options list'] = 'entity_metadata_field_options_list';
}
}
/**
* Additional callback to adapt the property info for file fields.
* @see entity_metadata_field_entity_property_info().
*/
function entity_metadata_field_file_callback(&$info, $entity_type, $field, $instance, $field_type) {
$property = &$info[$entity_type]['bundles'][$instance['bundle']]['properties'][$field['field_name']];
// Define a data structure so it's possible to deal with files and their
// descriptions.
$property['getter callback'] = 'entity_metadata_field_verbatim_get';
$property['setter callback'] = 'entity_metadata_field_verbatim_set';
// Auto-create the field $items as soon as a property is set.
$property['auto creation'] = 'entity_metadata_field_file_create_item';
$property['validation callback'] = 'entity_metadata_field_file_validate_item';
$property['property info'] = entity_property_field_item_file_info();
if (empty($instance['settings']['description_field'])) {
unset($property['property info']['description']);
}
if (empty($field['settings']['display_field'])) {
unset($property['property info']['display']);
}
unset($property['query callback']);
}
/**
* Additional callback to adapt the property info for image fields.
* This callback gets invoked after entity_metadata_field_file_callback().
* @see entity_metadata_field_entity_property_info().
*/
function entity_metadata_field_image_callback(&$info, $entity_type, $field, $instance, $field_type) {
$property = &$info[$entity_type]['bundles'][$instance['bundle']]['properties'][$field['field_name']];
// Update the property info with the info for image fields.
$property['property info'] = entity_property_field_item_image_info();
if (empty($instance['settings']['alt_field'])) {
unset($property['property info']['alt']);
}
if (empty($field['settings']['title_field'])) {
unset($property['property info']['title']);
}
}

View File

@@ -0,0 +1,40 @@
<?php
/**
* @file
* Provides locale-related properties.
*/
/**
* Implements hook_entity_property_info_alter() on top of locale module.
*
* @see entity_entity_property_info_alter()
*/
function entity_metadata_locale_entity_property_info_alter(&$info) {
$info['user']['properties']['language'] = array(
'label' => t("Language"),
'description' => t("This account's default language for e-mails, and preferred language for site presentation."),
'type' => 'token',
'getter callback' => 'entity_metadata_locale_get_user_language',
'setter callback' => 'entity_property_verbatim_set',
'options list' => 'entity_metadata_language_list',
'schema field' => 'language',
'setter permission' => 'administer users',
);
$info['site']['properties']['current_page']['property info']['language'] = array(
'label' => t("Interface language"),
'description' => t("The language code of the current user interface language."),
'type' => 'token',
'getter callback' => 'entity_metadata_locale_get_languages',
'options list' => 'entity_metadata_language_list',
);
$info['site']['properties']['current_page']['property info']['language_content'] = array(
'label' => t("Content language"),
'description' => t("The language code of the current content language."),
'type' => 'token',
'getter callback' => 'entity_metadata_locale_get_languages',
'options list' => 'entity_metadata_language_list',
);
}

View File

@@ -0,0 +1,165 @@
<?php
/**
* @file
* Provides info about the node entity.
*/
/**
* Implements hook_entity_property_info() on top of node module.
*
* @see entity_entity_property_info()
*/
function entity_metadata_node_entity_property_info() {
$info = array();
// Add meta-data about the basic node properties.
$properties = &$info['node']['properties'];
$properties['nid'] = array(
'label' => t("Node ID"),
'type' => 'integer',
'description' => t("The unique ID of the node."),
'schema field' => 'nid',
);
$properties['vid'] = array(
'label' => t("Revision ID"),
'type' => 'integer',
'description' => t("The unique ID of the node's revision."),
'schema field' => 'vid',
);
$properties['is_new'] = array(
'label' => t("Is new"),
'type' => 'boolean',
'description' => t("Whether the node is new and not saved to the database yet."),
'getter callback' => 'entity_metadata_node_get_properties',
);
$properties['type'] = array(
'label' => t("Content type"),
'type' => 'token',
'description' => t("The type of the node."),
'setter callback' => 'entity_property_verbatim_set',
'setter permission' => 'administer nodes',
'options list' => 'node_type_get_names',
'required' => TRUE,
'schema field' => 'type',
);
$properties['title'] = array(
'label' => t("Title"),
'description' => t("The title of the node."),
'setter callback' => 'entity_property_verbatim_set',
'schema field' => 'title',
'required' => TRUE,
);
$properties['language'] = array(
'label' => t("Language"),
'type' => 'token',
'description' => t("The language the node is written in."),
'setter callback' => 'entity_property_verbatim_set',
'options list' => 'entity_metadata_language_list',
'schema field' => 'language',
'setter permission' => 'administer nodes',
);
$properties['url'] = array(
'label' => t("URL"),
'description' => t("The URL of the node."),
'getter callback' => 'entity_metadata_entity_get_properties',
'type' => 'uri',
'computed' => TRUE,
);
$properties['edit_url'] = array(
'label' => t("Edit URL"),
'description' => t("The URL of the node's edit page."),
'getter callback' => 'entity_metadata_node_get_properties',
'type' => 'uri',
'computed' => TRUE,
);
$properties['status'] = array(
'label' => t("Status"),
'description' => t("Whether the node is published or unpublished."),
// Although the status is expected to be boolean, its schema suggests
// it is an integer, so we follow the schema definition.
'type' => 'integer',
'options list' => 'entity_metadata_status_options_list',
'setter callback' => 'entity_property_verbatim_set',
'setter permission' => 'administer nodes',
'schema field' => 'status',
);
$properties['promote'] = array(
'label' => t("Promoted to frontpage"),
'description' => t("Whether the node is promoted to the frontpage."),
'setter callback' => 'entity_property_verbatim_set',
'setter permission' => 'administer nodes',
'schema field' => 'promote',
'type' => 'boolean',
);
$properties['sticky'] = array(
'label' => t("Sticky in lists"),
'description' => t("Whether the node is displayed at the top of lists in which it appears."),
'setter callback' => 'entity_property_verbatim_set',
'setter permission' => 'administer nodes',
'schema field' => 'sticky',
'type' => 'boolean',
);
$properties['created'] = array(
'label' => t("Date created"),
'type' => 'date',
'description' => t("The date the node was posted."),
'setter callback' => 'entity_property_verbatim_set',
'setter permission' => 'administer nodes',
'schema field' => 'created',
);
$properties['changed'] = array(
'label' => t("Date changed"),
'type' => 'date',
'schema field' => 'changed',
'description' => t("The date the node was most recently updated."),
);
$properties['author'] = array(
'label' => t("Author"),
'type' => 'user',
'description' => t("The author of the node."),
'setter callback' => 'entity_property_verbatim_set',
'setter permission' => 'administer nodes',
'required' => TRUE,
'schema field' => 'uid',
);
$properties['source'] = array(
'label' => t("Translation source node"),
'type' => 'node',
'description' => t("The original-language version of this node, if one exists."),
'getter callback' => 'entity_metadata_node_get_properties',
);
$properties['log'] = array(
'label' => t("Revision log message"),
'type' => 'text',
'description' => t("In case a new revision is to be saved, the log entry explaining the changes for this version."),
'setter callback' => 'entity_property_verbatim_set',
'access callback' => 'entity_metadata_node_revision_access',
);
$properties['revision'] = array(
'label' => t("Creates revision"),
'type' => 'boolean',
'description' => t("Whether saving this node creates a new revision."),
'setter callback' => 'entity_property_verbatim_set',
'access callback' => 'entity_metadata_node_revision_access',
);
return $info;
}
/**
* Implements hook_entity_property_info_alter() on top of node module.
* @see entity_metadata_entity_property_info_alter()
*/
function entity_metadata_node_entity_property_info_alter(&$info) {
// Move the body property to the node by default, as its usually there this
// makes dealing with it more convenient.
$info['node']['properties']['body'] = array(
'type' => 'text_formatted',
'label' => t('The main body text'),
'getter callback' => 'entity_metadata_field_verbatim_get',
'setter callback' => 'entity_metadata_field_verbatim_set',
'property info' => entity_property_text_formatted_info(),
'auto creation' => 'entity_property_create_array',
'field' => TRUE,
);
}

View File

@@ -0,0 +1,50 @@
<?php
/**
* @file
* Provides info about poll nodes.
*/
/**
* Implements hook_entity_property_info_alter() on top of poll module.
*
* @see entity_entity_property_info_alter()
*/
function entity_metadata_poll_entity_property_info_alter(&$info) {
$properties = &$info['node']['bundles']['poll']['properties'];
$properties['poll_votes'] = array(
'label' => t("Poll votes"),
'description' => t("The number of votes that have been cast on a poll node."),
'type' => 'integer',
'getter callback' => 'entity_metadata_poll_node_get_properties',
'computed' => TRUE,
);
$properties['poll_winner'] = array(
'label' => t("Poll winner"),
'description' => t("The winning poll answer."),
'getter callback' => 'entity_metadata_poll_node_get_properties',
'sanitize' => 'filter_xss',
'computed' => TRUE,
);
$properties['poll_winner_votes'] = array(
'label' => t("Poll winner votes"),
'description' => t("The number of votes received by the winning poll answer."),
'type' => 'integer',
'getter callback' => 'entity_metadata_poll_node_get_properties',
'computed' => TRUE,
);
$properties['poll_winner_percent'] = array(
'label' => t("Poll winner percent"),
'description' => t("The percentage of votes received by the winning poll answer."),
'getter callback' => 'entity_metadata_poll_node_get_properties',
'type' => 'decimal',
'computed' => TRUE,
);
$properties['poll_duration'] = array(
'label' => t("Poll duration"),
'description' => t("The length of time the poll node is set to run."),
'getter callback' => 'entity_metadata_poll_node_get_properties',
'type' => 'duration',
);
}

View File

@@ -0,0 +1,37 @@
<?php
/**
* @file
* Provides info about statistics.
*/
/**
* Implements hook_entity_property_info_alter() on top of statistics module.
*
* @see entity_entity_property_info_alter()
*/
function entity_metadata_statistics_entity_property_info_alter(&$info) {
$properties = &$info['node']['properties'];
$properties['views'] = array(
'label' => t("Number of views"),
'description' => t("The number of visitors who have read the node."),
'type' => 'integer',
'getter callback' => 'entity_metadata_statistics_node_get_properties',
'computed' => TRUE,
);
$properties['day_views'] = array(
'label' => t("Views today"),
'description' => t("The number of visitors who have read the node today."),
'type' => 'integer',
'getter callback' => 'entity_metadata_statistics_node_get_properties',
'computed' => TRUE,
);
$properties['last_view'] = array(
'label' => t("Last view"),
'description' => t("The date on which a visitor last read the node."),
'type' => 'date',
'getter callback' => 'entity_metadata_statistics_node_get_properties',
'computed' => TRUE,
);
}

View File

@@ -0,0 +1,132 @@
<?php
/**
* @file
* Provides info about system-wide entities.
*/
/**
* Implements hook_entity_property_info() on top of system module.
*
* @see entity_entity_property_info()
* @see entity_metadata_site_wrapper()
*/
function entity_metadata_system_entity_property_info() {
$info = array();
// There is no site entity, but still add metadata for global site properties
// here. That way modules can alter and add further properties at this place.
// In order to make use of this metadata modules may use the wrapper returned
// by entity_metadata_site_wrapper().
$properties = &$info['site']['properties'];
$properties['name'] = array(
'label' => t("Name"),
'description' => t("The name of the site."),
'getter callback' => 'entity_metadata_system_get_properties',
'sanitize' => 'check_plain',
);
$properties['slogan'] = array(
'label' => t("Slogan"),
'description' => t("The slogan of the site."),
'getter callback' => 'entity_metadata_system_get_properties',
'sanitize' => 'check_plain',
);
$properties['mail'] = array(
'label' => t("Email"),
'description' => t("The administrative email address for the site."),
'getter callback' => 'entity_metadata_system_get_properties',
);
$properties['url'] = array(
'label' => t("URL"),
'description' => t("The URL of the site's front page."),
'getter callback' => 'entity_metadata_system_get_properties',
'type' => 'uri',
);
$properties['login_url'] = array(
'label' => t("Login page"),
'description' => t("The URL of the site's login page."),
'getter callback' => 'entity_metadata_system_get_properties',
'type' => 'uri',
);
$properties['current_user'] = array(
'label' => t("Logged in user"),
'description' => t("The currently logged in user."),
'getter callback' => 'entity_metadata_system_get_properties',
'type' => 'user',
);
$properties['current_date'] = array(
'label' => t("Current date"),
'description' => t("The current date and time."),
'getter callback' => 'entity_metadata_system_get_properties',
'type' => 'date',
);
$properties['current_page'] = array(
'label' => t("Current page"),
'description' => t("Information related to the current page request."),
'getter callback' => 'entity_metadata_system_get_properties',
'type' => 'struct',
'property info' => array(
'path' => array(
'label' => t("Path"),
'description' => t("The internal Drupal path of the current page request."),
'getter callback' => 'current_path',
'type' => 'text',
),
'url' => array(
'label' => t("URL"),
'description' => t("The full URL of the current page request."),
'getter callback' => 'entity_metadata_system_get_page_properties',
'type' => 'uri',
),
),
);
// Files.
$properties = &$info['file']['properties'];
$properties['fid'] = array(
'label' => t("File ID"),
'description' => t("The unique ID of the uploaded file."),
'type' => 'integer',
'validation callback' => 'entity_metadata_validate_integer_positive',
'schema field' => 'fid',
);
$properties['name'] = array(
'label' => t("File name"),
'description' => t("The name of the file on disk."),
'getter callback' => 'entity_metadata_system_get_file_properties',
'schema field' => 'filename',
);
$properties['mime'] = array(
'label' => t("MIME type"),
'description' => t("The MIME type of the file."),
'getter callback' => 'entity_metadata_system_get_file_properties',
'sanitize' => 'filter_xss',
'schema field' => 'filemime',
);
$properties['size'] = array(
'label' => t("File size"),
'description' => t("The size of the file, in kilobytes."),
'getter callback' => 'entity_metadata_system_get_file_properties',
'type' => 'integer',
'schema field' => 'filesize',
);
$properties['url'] = array(
'label' => t("URL"),
'description' => t("The web-accessible URL for the file."),
'getter callback' => 'entity_metadata_system_get_file_properties',
);
$properties['timestamp'] = array(
'label' => t("Timestamp"),
'description' => t("The date the file was most recently changed."),
'type' => 'date',
'schema field' => 'timestamp',
);
$properties['owner'] = array(
'label' => t("Owner"),
'description' => t("The user who originally uploaded the file."),
'type' => 'user',
'getter callback' => 'entity_metadata_system_get_file_properties',
'schema field' => 'uid',
);
return $info;
}

View File

@@ -0,0 +1,124 @@
<?php
/**
* @file
* Provides info about the taxonomy entity.
*/
/**
* Implements hook_entity_property_info() on top of taxonomy module.
*
* @see entity_entity_property_info()
*/
function entity_metadata_taxonomy_entity_property_info() {
$info = array();
// Add meta-data about the basic taxonomy properties.
$properties = &$info['taxonomy_term']['properties'];
$properties['tid'] = array(
'label' => t("Term ID"),
'description' => t("The unique ID of the taxonomy term."),
'type' => 'integer',
'schema field' => 'tid',
);
$properties['name'] = array(
'label' => t("Name"),
'description' => t("The name of the taxonomy term."),
'setter callback' => 'entity_property_verbatim_set',
'required' => TRUE,
'schema field' => 'name',
);
$properties['description'] = array(
'label' => t("Description"),
'description' => t("The optional description of the taxonomy term."),
'sanitized' => TRUE,
'raw getter callback' => 'entity_property_verbatim_get',
'getter callback' => 'entity_metadata_taxonomy_term_get_properties',
'setter callback' => 'entity_property_verbatim_set',
'schema field' => 'description',
);
$properties['weight'] = array(
'label' => t("Weight"),
'type' => 'integer',
'description' => t('The weight of the term, which is used for ordering terms during display.'),
'setter callback' => 'entity_property_verbatim_set',
'schema field' => 'weight',
);
$properties['node_count'] = array(
'label' => t("Node count"),
'type' => 'integer',
'description' => t("The number of nodes tagged with the taxonomy term."),
'getter callback' => 'entity_metadata_taxonomy_term_get_properties',
'computed' => TRUE,
);
$properties['url'] = array(
'label' => t("URL"),
'description' => t("The URL of the taxonomy term."),
'getter callback' => 'entity_metadata_entity_get_properties',
'type' => 'uri',
'computed' => TRUE,
);
$properties['vocabulary'] = array(
'label' => t("Vocabulary"),
'description' => t("The vocabulary the taxonomy term belongs to."),
'setter callback' => 'entity_metadata_taxonomy_term_setter',
'type' => 'taxonomy_vocabulary',
'required' => TRUE,
'schema field' => 'vid',
);
$properties['parent'] = array(
'label' => t("Parent terms"),
'description' => t("The parent terms of the taxonomy term."),
'getter callback' => 'entity_metadata_taxonomy_term_get_properties',
'setter callback' => 'entity_metadata_taxonomy_term_setter',
'type' => 'list<taxonomy_term>',
);
$properties['parents_all'] = array(
'label' => t("All parent terms"),
'description' => t("Ancestors of the term, i.e. parent of all above hierarchy levels."),
'getter callback' => 'entity_metadata_taxonomy_term_get_properties',
'type' => 'list<taxonomy_term>',
'computed' => TRUE,
);
// Add meta-data about the basic vocabulary properties.
$properties = &$info['taxonomy_vocabulary']['properties'];
// Taxonomy vocabulary related variables.
$properties['vid'] = array(
'label' => t("Vocabulary ID"),
'description' => t("The unique ID of the taxonomy vocabulary."),
'type' => 'integer',
'schema field' => 'vid',
);
$properties['name'] = array(
'label' => t("Name"),
'description' => t("The name of the taxonomy vocabulary."),
'setter callback' => 'entity_property_verbatim_set',
'required' => TRUE,
'schema field' => 'name',
);
$properties['machine_name'] = array(
'label' => t("Machine name"),
'type' => 'token',
'description' => t("The machine name of the taxonomy vocabulary."),
'setter callback' => 'entity_property_verbatim_set',
'required' => TRUE,
'schema field' => 'machine_name',
);
$properties['description'] = array(
'label' => t("Description"),
'description' => t("The optional description of the taxonomy vocabulary."),
'setter callback' => 'entity_property_verbatim_set',
'sanitize' => 'filter_xss',
'schema field' => 'description',
);
$properties['term_count'] = array(
'label' => t("Term count"),
'type' => 'integer',
'description' => t("The number of terms belonging to the taxonomy vocabulary."),
'getter callback' => 'entity_metadata_taxonomy_vocabulary_get_properties',
'computed' => TRUE,
);
return $info;
}

View File

@@ -0,0 +1,108 @@
<?php
/**
* @file
* Provides info about the user entity.
*/
/**
* Implements hook_entity_property_info() on top of user module.
*
* @see entity_entity_property_info()
*/
function entity_metadata_user_entity_property_info() {
$info = array();
// Add meta-data about the user properties.
$properties = &$info['user']['properties'];
$properties['uid'] = array(
'label' => t("User ID"),
'type' => 'integer',
'description' => t("The unique ID of the user account."),
'schema field' => 'uid',
);
$properties['name'] = array(
'label' => t("Name"),
'description' => t("The login name of the user account."),
'getter callback' => 'entity_metadata_user_get_properties',
'setter callback' => 'entity_property_verbatim_set',
'sanitize' => 'filter_xss',
'required' => TRUE,
'access callback' => 'entity_metadata_user_properties_access',
'schema field' => 'name',
);
$properties['mail'] = array(
'label' => t("Email"),
'description' => t("The email address of the user account."),
'setter callback' => 'entity_property_verbatim_set',
'validation callback' => 'valid_email_address',
'required' => TRUE,
'access callback' => 'entity_metadata_user_properties_access',
'schema field' => 'mail',
);
$properties['url'] = array(
'label' => t("URL"),
'description' => t("The URL of the account profile page."),
'getter callback' => 'entity_metadata_user_get_properties',
'type' => 'uri',
'computed' => TRUE,
);
$properties['edit_url'] = array(
'label' => t("Edit URL"),
'description' => t("The url of the account edit page."),
'getter callback' => 'entity_metadata_user_get_properties',
'type' => 'uri',
'computed' => TRUE,
);
$properties['last_access'] = array(
'label' => t("Last access"),
'description' => t("The date the user last accessed the site."),
'getter callback' => 'entity_metadata_user_get_properties',
'type' => 'date',
'schema field' => 'access',
);
$properties['last_login'] = array(
'label' => t("Last login"),
'description' => t("The date the user last logged in to the site."),
'getter callback' => 'entity_metadata_user_get_properties',
'type' => 'date',
'schema field' => 'login',
);
$properties['created'] = array(
'label' => t("Created"),
'description' => t("The date the user account was created."),
'type' => 'date',
'schema field' => 'created',
);
$properties['roles'] = array(
'label' => t("User roles"),
'description' => t("The roles of the user."),
'type' => 'list<integer>',
'getter callback' => 'entity_metadata_user_get_properties',
'setter callback' => 'entity_metadata_user_set_properties',
'setter permission' => 'administer users',
'options list' => 'entity_metadata_user_roles',
'access callback' => 'entity_metadata_user_properties_access',
);
$properties['status'] = array(
'label' => t("Status"),
'description' => t("Whether the user is active or blocked."),
'setter callback' => 'entity_property_verbatim_set',
// Although the status is expected to be boolean, its schema suggests
// it is an integer, so we follow the schema definition.
'type' => 'integer',
'options list' => 'entity_metadata_user_status_options_list',
'setter permission' => 'administer users',
'schema field' => 'status',
);
$properties['theme'] = array(
'label' => t("Default theme"),
'description' => t("The user's default theme."),
'getter callback' => 'entity_metadata_user_get_properties',
'setter callback' => 'entity_property_verbatim_set',
'access callback' => 'entity_metadata_user_properties_access',
'schema field' => 'theme',
);
return $info;
}

View File

@@ -0,0 +1,14 @@
name = Entity feature module
description = Provides some entities in code.
version = VERSION
core = 7.x
files[] = entity_feature.module
dependencies[] = entity_test
hidden = TRUE
; Information added by drupal.org packaging script on 2012-12-25
version = "7.x-1.0"
core = "7.x"
project = "entity"
datestamp = "1356471145"

View File

@@ -0,0 +1,32 @@
<?php
/**
* @file
* Test module providing some entities in code.
*/
/**
* Implements hook_default_entity_test_type().
*/
function entity_feature_default_entity_test_type() {
$types['main'] = entity_create('entity_test_type', array(
'name' => 'main',
'label' => t('Main test type'),
'weight' => 0,
'locked' => TRUE,
));
// Types used during CRUD testing.
$types['test'] = entity_create('entity_test_type', array(
'name' => 'test',
'label' => 'label',
'weight' => 0,
));
$types['test2'] = entity_create('entity_test_type', array(
'name' => 'test2',
'label' => 'label2',
'weight' => 2,
));
return $types;
}

View File

@@ -0,0 +1,15 @@
name = Entity CRUD test module
description = Provides entity types based upon the CRUD API.
version = VERSION
core = 7.x
files[] = entity_test.module
files[] = entity_test.install
dependencies[] = entity
hidden = TRUE
; Information added by drupal.org packaging script on 2012-12-25
version = "7.x-1.0"
core = "7.x"
project = "entity"
datestamp = "1356471145"

View File

@@ -0,0 +1,164 @@
<?php
/**
* @file
* Install, update and uninstall functions for the entity_test module.
*/
/**
* Implements hook_uninstall().
*/
function entity_test_uninstall() {
// Bypass entity_load() as we cannot use it here.
$types = db_select('entity_test_type', 'et')
->fields('et')
->execute()
->fetchAllAssoc('name');
foreach ($types as $name => $type) {
field_attach_delete_bundle('entity_test', $name);
}
}
/**
* Implements hook_schema().
*/
function entity_test_schema() {
$schema['entity_test'] = array(
'description' => 'Stores entity_test items.',
'fields' => array(
'pid' => array(
'type' => 'serial',
'not null' => TRUE,
'description' => 'Primary Key: Unique entity_test item ID.',
),
'name' => array(
'description' => 'The name of the entity_test.',
'type' => 'varchar',
'length' => 32,
'not null' => TRUE,
'default' => '',
),
'uid' => array(
'type' => 'int',
'unsigned' => TRUE,
'not null' => FALSE,
'default' => NULL,
'description' => "The {users}.uid of the associated user.",
),
),
'indexes' => array(
'uid' => array('uid'),
),
'foreign keys' => array(
'uid' => array('users' => 'uid'),
'name' => array('entity_test_types' => 'name'),
),
'primary key' => array('pid'),
);
$schema['entity_test_type'] = array(
'description' => 'Stores information about all defined entity_test types.',
'fields' => array(
'id' => array(
'type' => 'serial',
'not null' => TRUE,
'description' => 'Primary Key: Unique entity_test type ID.',
),
'name' => array(
'description' => 'The machine-readable name of this entity_test type.',
'type' => 'varchar',
'length' => 32,
'not null' => TRUE,
),
'label' => array(
'description' => 'The human-readable name of this entity_test type.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'weight' => array(
'type' => 'int',
'not null' => TRUE,
'default' => 0,
'size' => 'tiny',
'description' => 'The weight of this entity_test type in relation to others.',
),
'locked' => array(
'description' => 'A boolean indicating whether the administrator may delete this type.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
'size' => 'tiny',
),
'data' => array(
'type' => 'text',
'not null' => FALSE,
'size' => 'big',
'serialize' => TRUE,
'description' => 'A serialized array of additional data related to this entity_test type.',
'merge' => TRUE,
),
'status' => array(
'type' => 'int',
'not null' => TRUE,
// Set the default to ENTITY_CUSTOM without using the constant as it is
// not safe to use it at this point.
'default' => 0x01,
'size' => 'tiny',
'description' => 'The exportable status of the entity.',
),
'module' => array(
'description' => 'The name of the providing module if the entity has been defined in code.',
'type' => 'varchar',
'length' => 255,
'not null' => FALSE,
),
),
'primary key' => array('id'),
'unique keys' => array(
'name' => array('name'),
),
);
// Add schema for the revision-test-entity.
$schema['entity_test2'] = $schema['entity_test'];
$schema['entity_test2']['fields']['revision_id'] = array(
'type' => 'int',
'unsigned' => TRUE,
'not null' => FALSE,
'default' => NULL,
'description' => 'The ID of the entity\'s default revision.',
);
$schema['entity_test2']['fields']['title'] = array(
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
);
$schema['entity_test2_revision'] = $schema['entity_test'];
$schema['entity_test2_revision']['fields']['revision_id'] = array(
'type' => 'serial',
'not null' => TRUE,
'description' => 'Primary Key: Unique revision ID.',
);
$schema['entity_test2_revision']['fields']['pid'] = array(
'type' => 'int',
'unsigned' => TRUE,
'not null' => FALSE,
'default' => NULL,
'description' => 'The ID of the attached entity.',
);
$schema['entity_test2_revision']['fields']['title'] = array(
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
);
$schema['entity_test2_revision']['primary key'] = array('revision_id');
return $schema;
}

View File

@@ -0,0 +1,280 @@
<?php
/**
* @file
* Test moduel for the entity API.
*/
/**
* Implements hook_entity_info().
*/
function entity_test_entity_info() {
$return = array(
'entity_test' => array(
'label' => t('Test Entity'),
'plural label' => t('Test Entities'),
'description' => t('An entity type used by the entity API tests.'),
'entity class' => 'EntityClass',
'controller class' => 'EntityAPIController',
'base table' => 'entity_test',
'fieldable' => TRUE,
'entity keys' => array(
'id' => 'pid',
'bundle' => 'name',
),
// Make use the class' label() and uri() implementation by default.
'label callback' => 'entity_class_label',
'uri callback' => 'entity_class_uri',
'bundles' => array(),
'bundle keys' => array(
'bundle' => 'name',
),
'module' => 'entity_test',
),
'entity_test_type' => array(
'label' => t('Test entity type'),
'entity class' => 'Entity',
'controller class' => 'EntityAPIControllerExportable',
'base table' => 'entity_test_type',
'fieldable' => FALSE,
'bundle of' => 'entity_test',
'exportable' => TRUE,
'entity keys' => array(
'id' => 'id',
'name' => 'name',
),
'module' => 'entity_test',
),
'entity_test2' => array(
'label' => t('Test Entity (revision support)'),
'entity class' => 'EntityClassRevision',
'controller class' => 'EntityAPIController',
'base table' => 'entity_test2',
'revision table' => 'entity_test2_revision',
'fieldable' => TRUE,
'entity keys' => array(
'id' => 'pid',
'revision' => 'revision_id',
),
// Make use of the class label() and uri() implementation by default.
'label callback' => 'entity_class_label',
'uri callback' => 'entity_class_uri',
'bundles' => array(),
'bundle keys' => array(
'bundle' => 'name',
),
),
);
// Add bundle info but bypass entity_load() as we cannot use it here.
$types = db_select('entity_test_type', 'et')
->fields('et')
->execute()
->fetchAllAssoc('name');
foreach ($types as $name => $type) {
$return['entity_test']['bundles'][$name] = array(
'label' => $type->label,
);
}
// Support entity cache module.
if (module_exists('entitycache')) {
$return['entity_test']['field cache'] = FALSE;
$return['entity_test']['entity cache'] = TRUE;
}
return $return;
}
/**
* Gets an array of all test entity types, keyed by the name.
*
* @param $name
* If set, the type with the given name is returned.
*/
function entity_test_get_types($name = NULL) {
$types = entity_load_multiple_by_name('entity_test_type', isset($name) ? array($name) : FALSE);
return isset($name) ? reset($types) : $types;
}
/**
* Load multiple test entities based on certain conditions.
*
* @param $pids
* An array of entity IDs.
* @param $conditions
* An array of conditions to match against the {entity} table.
* @param $reset
* A boolean indicating that the internal cache should be reset.
* @return
* An array of test entity objects, indexed by pid.
*/
function entity_test_load_multiple($pids = array(), $conditions = array(), $reset = FALSE) {
return entity_load('entity_test', $pids, $conditions, $reset);
}
/**
* Delete multiple test entities.
*
* @param $pids
* An array of test entity IDs.
*/
function entity_test_delete_multiple(array $pids) {
entity_get_controller('entity_test')->delete($pids);
}
/**
* Main class for test entities.
*/
class EntityClass extends Entity {
public function __construct(array $values = array(), $entityType = NULL) {
parent::__construct($values, 'entity_test');
}
/**
* Override buildContent() to add the username to the output.
*/
public function buildContent($view_mode = 'full', $langcode = NULL) {
$content['user'] = array(
'#markup' => "User: ". format_username(user_load($this->uid)),
);
return entity_get_controller($this->entityType)->buildContent($this, $view_mode, $langcode, $content);
}
/**
* Specifies the default label, which is picked up by label() by default.
*/
protected function defaultLabel() {
$type = entity_test_get_types($this->name);
return $type->label;
}
/**
* Specifies the default uri, which is picked up by uri() by default.
*/
protected function defaultURI() {
return array('path' => 'custom/' . $this->identifier());
}
}
/**
* Main class for test entities (with revision support).
*/
class EntityClassRevision extends EntityClass {
public function __construct(array $values = array(), $entityType = NULL) {
Entity::__construct($values, 'entity_test2');
}
}
/**
*
*
* Some hook implementations used by the tests.
*
*
*/
/**
* Implements hook_entity_insert().
*/
function entity_test_entity_insert($entity, $entity_type) {
if ($entity_type == 'entity_test_type') {
$_SESSION['entity_hook_test']['entity_insert'][] = entity_id($entity_type, $entity);
}
}
/**
* Implements hook_entity_update().
*/
function entity_test_entity_update($entity, $entity_type) {
$_SESSION['entity_hook_test']['entity_update'][] = entity_id($entity_type, $entity);
}
/**
* Implements hook_entity_delete().
*/
function entity_test_entity_delete($entity, $entity_type) {
if ($entity_type == 'entity_test_type') {
$_SESSION['entity_hook_test']['entity_delete'][] = entity_id($entity_type, $entity);
}
}
/**
* Implements hook_entity_test_type_insert().
*/
function entity_test_entity_test_type_insert($entity) {
$_SESSION['entity_hook_test']['entity_test_type_insert'][] = $entity->identifier();
}
/**
* Implements hook_entity_test_type_update().
*/
function entity_test_entity_test_type_update($entity) {
$_SESSION['entity_hook_test']['entity_test_type_update'][] = $entity->identifier();
// Determine changes on update.
if (!empty($entity->original) && $entity->original->label == 'test_changes') {
if ($entity->original->label != $entity->label) {
$entity->label .= '_update';
}
}
}
/**
* Implements hook_entity_test_type_delete().
*/
function entity_test_entity_test_type_delete($entity) {
$_SESSION['entity_hook_test']['entity_test_type_delete'][] = $entity->identifier();
}
/**
* Implements hook_entity_test_type_presave().
*/
function entity_test_entity_test_type_presave($entity) {
// Determine changes.
if (!empty($entity->original) && $entity->original->label == 'test_changes') {
if ($entity->original->label != $entity->label) {
$entity->label .= '_presave';
}
}
}
/**
* Implements hook_entity_property_info_alter() for testing an property of type
* 'entity'.
*/
function entity_test_entity_property_info_alter(&$info) {
$info['node']['properties']['reference'] = array(
'label' => t('Test reference'),
'description' => t('A generic entity reference.'),
'getter callback' => 'entity_test_entity_getter',
'setter callback' => 'entity_test_entity_setter',
'type' => 'entity',
);
}
/**
* Getter callback for the 'reference' property.
*/
function entity_test_entity_getter($node) {
if (empty($node->entity)) {
$node->entity = array('type' => 'user', 'id' => $node->uid);
}
// We have to return the entity wrapped.
return entity_metadata_wrapper($node->entity['type'], $node->entity['id']);
}
/**
* Setter callback for the 'reference' property.
*/
function entity_test_entity_setter($node, $property_name, $wrapper) {
// The entity has to be passed wrapped.
$node->entity = array('type' => $wrapper->type(), 'id' => $wrapper->getIdentifier());
}

View File

@@ -0,0 +1,13 @@
name = Entity-test type translation
description = Allows translating entity-test types.
dependencies[] = entity_test
dependencies[] = i18n_string
package = Multilingual - Internationalization
core = 7.x
hidden = TRUE
; Information added by drupal.org packaging script on 2012-12-25
version = "7.x-1.0"
core = "7.x"
project = "entity"
datestamp = "1356471145"

View File

@@ -0,0 +1,53 @@
<?php
/**
* @file
* Entity-test i18n integration module via entity API i18n support.
*
* @see EntityDefaultI18nController
*/
/**
* Implements hook_entity_info_alter().
*/
function entity_test_i18n_entity_info_alter(&$info) {
// Enable i18n support via the entity API.
$info['entity_test_type']['i18n controller class'] = 'EntityDefaultI18nStringController';
}
/**
* Implements hook_entity_property_info_alter().
*/
function entity_test_i18n_entity_property_info_alter(&$info) {
// Mark some properties as translatable, but also denote that translation
// works with i18n_string.
foreach (array('label') as $name) {
$info['entity_test_type']['properties'][$name]['translatable'] = TRUE;
$info['entity_test_type']['properties'][$name]['i18n string'] = TRUE;
}
}
/**
* Implements hook_{entity_test_type}_insert().
*/
function entity_test_i18n_entity_test_type_insert($test_type) {
i18n_string_object_update('entity_test_type', $test_type);
}
/**
* Implements hook_{entity_test_type}_update().
*/
function entity_test_i18n_entity_test_type_update($test_type) {
// Account for name changes.
if ($test_type->original->name != $test_type->name) {
i18n_string_update_context("entity_test:entity_test_type:{$test_type->original->name}:*", "entity_test:entity_test_type:{$test_type->name}:*");
}
i18n_string_object_update('entity_test_type', $test_type);
}
/**
* Implements hook_{entity_test_type}_delete().
*/
function entity_test_i18n_entity_test_type_delete($test_type) {
i18n_string_object_remove('entity_test_type', $test_type);
}

View File

@@ -0,0 +1,635 @@
<?php
/**
* @file
* Provide views data for modules making use of the entity CRUD API.
*/
/**
* Implements hook_views_data().
*
* Provides Views integration for entities if they satisfy one of these
* conditions:
* - hook_entity_info() specifies a 'views controller class' key.
* - hook_entity_info() specifies a 'module' key, and the module does not
* implement hook_views_data().
*
* @see entity_crud_hook_entity_info()
* @see entity_views_table_definition()
*/
function entity_views_data() {
$data = array();
foreach (entity_crud_get_info() as $type => $info) {
// Provide default integration with the basic controller class if we know
// the module providing the entity and it does not provide views integration.
if (!isset($info['views controller class'])) {
$info['views controller class'] = isset($info['module']) && !module_hook($info['module'], 'views_data') ? 'EntityDefaultViewsController' : FALSE;
}
if ($info['views controller class']) {
$controller = new $info['views controller class']($type);
// Relationship data may return views data for already existing tables,
// so merge results on the second level.
foreach ($controller->views_data() as $table => $table_data) {
$data += array($table => array());
$data[$table] = array_merge($data[$table], $table_data);
}
}
}
// Add tables based upon data selection "queries" for all entity types.
foreach (entity_get_info() as $type => $info) {
$table = entity_views_table_definition($type);
if ($table) {
$data['entity_' . $type] = $table;
}
// Generally expose properties marked as 'entity views field'.
$data['views_entity_' . $type] = array();
foreach (entity_get_all_property_info($type) as $key => $property) {
if (!empty($property['entity views field'])) {
entity_views_field_definition($key, $property, $data['views_entity_' . $type]);
}
}
}
// Expose generally usable entity-related fields.
foreach (entity_get_info() as $entity_type => $info) {
if (entity_type_supports($entity_type, 'view')) {
// Expose a field allowing to display the rendered entity.
$data['views_entity_' . $entity_type]['rendered_entity'] = array(
'title' => t('Rendered @entity-type', array('@entity-type' => $info['label'])),
'help' => t('The @entity-type of the current relationship rendered using a view mode.', array('@entity-type' => $info['label'])),
'field' => array(
'handler' => 'entity_views_handler_field_entity',
'type' => $entity_type,
// The EntityFieldHandlerHelper treats the 'entity object' data
// selector as special case for loading the base entity.
'real field' => 'entity object',
),
);
}
}
$data['entity__global']['table']['group'] = t('Entity');
$data['entity__global']['table']['join'] = array(
// #global let's it appear all the time.
'#global' => array(),
);
$data['entity__global']['entity'] = array(
'title' => t('Rendered entity'),
'help' => t('Displays a single chosen entity.'),
'area' => array(
'handler' => 'entity_views_handler_area_entity',
),
);
return $data;
}
/**
* Helper function for getting data selection based entity Views table definitions.
*
* This creates extra tables for each entity type that are not associated with a
* query plugin (and thus are not base tables) and just rely on the entities to
* retrieve the displayed data. To obtain the entities corresponding to a
* certain result set, the field handlers defined on the table use a generic
* interface defined for query plugins that are based on entity handling, and
* which is described in the entity_views_example_query class.
*
* These tables are called "data selection tables".
*
* Other modules providing Views integration with new query plugins that are
* based on entities can then use these tables as a base for their own tables
* (by directly using this method and modifying the returned table) and/or by
* specifying relationships to them. The tables returned here already specify
* relationships to each other wherever an entity contains a reference to
* another (e.g., the node author constructs a relationship from nodes to
* users).
*
* As filtering and other query manipulation is potentially more plugin-specific
* than the display, only field handlers and relationships are provided with
* these tables. By providing a add_selector_orderby() method, the query plugin
* can, however, support click-sorting for the field handlers in these tables.
*
* For a detailed discussion see http://drupal.org/node/1266036
*
* For example use see the Search API views module in the Search API project:
* http://drupal.org/project/search_api
*
* @param $type
* The entity type whose table definition should be returned.
* @param $exclude
* Whether properties already exposed as 'entity views field' should be
* excluded. Defaults to TRUE, as they are available for all views tables for
* the entity type anyways.
*
* @return
* An array containing the data selection Views table definition for the
* entity type.
*
* @see entity_views_field_definition()
*/
function entity_views_table_definition($type, $exclude = TRUE) {
// As other modules might want to copy these tables as a base for their own
// Views integration, we statically cache the tables to save some time.
$tables = &drupal_static(__FUNCTION__, array());
if (!isset($tables[$type])) {
// Work-a-round to fix updating, see http://drupal.org/node/1330874.
// Views data might be rebuilt on update.php before the registry is rebuilt,
// thus the class cannot be auto-loaded.
if (!class_exists('EntityFieldHandlerHelper')) {
module_load_include('inc', 'entity', 'views/handlers/entity_views_field_handler_helper');
}
$info = entity_get_info($type);
$tables[$type]['table'] = array(
'group' => $info['label'],
'entity type' => $type,
);
foreach (entity_get_all_property_info($type) as $key => $property) {
if (!$exclude || empty($property['entity views field'])) {
entity_views_field_definition($key, $property, $tables[$type]);
}
}
}
return $tables[$type];
}
/**
* Helper function for adding a Views field definition to data selection based Views tables.
*
* @param $field
* The data selector of the field to add. E.g. "title" would derive the node
* title property, "body:summary" the node body's summary.
* @param array $property_info
* The property information for which to create a field definition.
* @param array $table
* The table into which the definition should be inserted.
* @param $title_prefix
* Internal use only.
*
* @see entity_views_table_definition()
*/
function entity_views_field_definition($field, array $property_info, array &$table, $title_prefix = '') {
$additional = array();
$additional_field = array();
// Create a valid Views field identifier (no colons, etc.). Keep the original
// data selector as real field though.
$key = _entity_views_field_identifier($field, $table);
if ($key != $field) {
$additional['real field'] = $field;
}
$field_name = EntityFieldHandlerHelper::get_selector_field_name($field);
$field_handlers = entity_views_get_field_handlers();
$property_info += entity_property_info_defaults();
$type = entity_property_extract_innermost_type($property_info['type']);
$title = $title_prefix . $property_info['label'];
if ($info = entity_get_info($type)) {
$additional['relationship'] = array(
'handler' => $field_handlers['relationship'],
'base' => 'entity_' . $type,
'base field' => $info['entity keys']['id'],
'relationship field' => $field,
'label' => $title,
);
if ($property_info['type'] != $type) {
// This is a list of entities, so we should mark the relationship as such.
$additional['relationship']['multiple'] = TRUE;
}
// Implementers of the field handlers alter hook could add handlers for
// specific entity types.
if (!isset($field_handlers[$type])) {
$type = 'entity';
}
}
elseif (!empty($property_info['field'])) {
$type = 'field';
// Views' Field API field handler needs some extra definitions to work.
$additional_field['field_name'] = $field_name;
$additional_field['entity_tables'] = array();
$additional_field['entity type'] = $table['table']['entity type'];
$additional_field['is revision'] = FALSE;
}
// Copied from EntityMetadataWrapper::optionsList()
elseif (isset($property_info['options list']) && is_callable($property_info['options list'])) {
// If this is a nested property, we need to get rid of all prefixes first.
$type = 'options';
$additional_field['options callback'] = array(
'function' => $property_info['options list'],
'info' => $property_info,
);
}
elseif ($type == 'decimal') {
$additional_field['float'] = TRUE;
}
if (isset($field_handlers[$type])) {
$table += array($key => array());
$table[$key] += array(
'title' => $title,
'help' => empty($property_info['description']) ? t('(No information available)') : $property_info['description'],
'field' => array(),
);
$table[$key]['field'] += array(
'handler' => $field_handlers[$type],
'type' => $property_info['type'],
);
$table[$key] += $additional;
$table[$key]['field'] += $additional_field;
}
if (!empty($property_info['property info'])) {
foreach ($property_info['property info'] as $nested_key => $nested_property) {
entity_views_field_definition($field . ':' . $nested_key, $nested_property, $table, $title . ' » ');
}
}
}
/**
* @return array
* The handlers to use for the data selection based Views tables.
*
* @see hook_entity_views_field_handlers_alter()
*/
function entity_views_get_field_handlers() {
$field_handlers = drupal_static(__FUNCTION__);
if (!isset($field_handlers)) {
// Field handlers for the entity tables, by type.
$field_handlers = array(
'text' => 'entity_views_handler_field_text',
'token' => 'entity_views_handler_field_text',
'integer' => 'entity_views_handler_field_numeric',
'decimal' => 'entity_views_handler_field_numeric',
'date' => 'entity_views_handler_field_date',
'duration' => 'entity_views_handler_field_duration',
'boolean' => 'entity_views_handler_field_boolean',
'uri' => 'entity_views_handler_field_uri',
'options' => 'entity_views_handler_field_options',
'field' => 'entity_views_handler_field_field',
'entity' => 'entity_views_handler_field_entity',
'relationship' => 'entity_views_handler_relationship',
);
drupal_alter('entity_views_field_handlers', $field_handlers);
}
return $field_handlers;
}
/**
* Helper function for creating valid Views field identifiers out of data selectors.
*
* Uses $table to test whether the identifier is already used, and also
* recognizes if a definition for the same field is already present and returns
* that definition's identifier.
*
* @return string
* A valid Views field identifier that is not yet used as a key in $table.
*/
function _entity_views_field_identifier($field, array $table) {
$key = $base = preg_replace('/[^a-zA-Z0-9]+/S', '_', $field);
$i = 0;
// The condition checks whether this sanitized field identifier is already
// used for another field in this table (and whether the identifier is
// "table", which can never be used).
// If $table[$key] is set, the identifier is already used, but this might be
// already for the same field. To test that, we need the original field name,
// which is either $table[$key]['real field'], if set, or $key. If this
// original field name is equal to $field, we can use that key. Otherwise, we
// append numeric suffixes until we reach an unused key.
while ($key == 'table' || (isset($table[$key]) && (isset($table[$key]['real field']) ? $table[$key]['real field'] : $key) != $field)) {
$key = $base . '_' . ++$i;
}
return $key;
}
/**
* Implements hook_views_plugins().
*/
function entity_views_plugins() {
// Have views cache the table list for us so it gets
// cleared at the appropriate times.
$data = views_cache_get('entity_base_tables', TRUE);
if (!empty($data->data)) {
$base_tables = $data->data;
}
else {
$base_tables = array();
foreach (views_fetch_data() as $table => $data) {
if (!empty($data['table']['entity type']) && !empty($data['table']['base'])) {
$base_tables[] = $table;
}
}
views_cache_set('entity_base_tables', $base_tables, TRUE);
}
if (!empty($base_tables)) {
return array(
'module' => 'entity',
'row' => array(
'entity' => array(
'title' => t('Rendered entity'),
'help' => t('Renders a single entity in a specific view mode (e.g. teaser).'),
'handler' => 'entity_views_plugin_row_entity_view',
'uses fields' => FALSE,
'uses options' => TRUE,
'type' => 'normal',
'base' => $base_tables,
),
),
);
}
}
/**
* Default controller for generating basic views integration.
*
* The controller tries to generate suiting views integration for the entity
* based upon the schema information of its base table and the provided entity
* property information.
* For that it is possible to map a property name to its schema/views field
* name by adding a 'schema field' key with the name of the field as value to
* the property info.
*/
class EntityDefaultViewsController {
protected $type, $info, $relationships;
public function __construct($type) {
$this->type = $type;
$this->info = entity_get_info($type);
}
/**
* Defines the result for hook_views_data().
*/
public function views_data() {
$data = array();
$this->relationships = array();
if (!empty($this->info['base table'])) {
$table = $this->info['base table'];
// Define the base group of this table. Fields that don't
// have a group defined will go into this field by default.
$data[$table]['table']['group'] = drupal_ucfirst($this->info['label']);
$data[$table]['table']['entity type'] = $this->type;
// If the plural label isn't available, use the regular label.
$label = isset($this->info['plural label']) ? $this->info['plural label'] : $this->info['label'];
$data[$table]['table']['base'] = array(
'field' => $this->info['entity keys']['id'],
'title' => drupal_ucfirst($label),
'help' => isset($this->info['description']) ? $this->info['description'] : '',
);
$data[$table]['table']['entity type'] = $this->type;
$data[$table] += $this->schema_fields();
// Add in any reverse-relationships which have been determined.
$data += $this->relationships;
}
return $data;
}
/**
* Try to come up with some views fields with the help of the schema and
* the entity property information.
*/
protected function schema_fields() {
$schema = drupal_get_schema($this->info['base table']);
$properties = entity_get_property_info($this->type) + array('properties' => array());
$data = array();
foreach ($properties['properties'] as $name => $property_info) {
if (isset($property_info['schema field']) && isset($schema['fields'][$property_info['schema field']])) {
if ($views_info = $this->map_from_schema_info($name, $schema['fields'][$property_info['schema field']], $property_info)) {
$data[$name] = $views_info;
}
}
}
return $data;
}
/**
* Comes up with views information based on the given schema and property
* info.
*/
protected function map_from_schema_info($property_name, $schema_field_info, $property_info) {
$type = isset($property_info['type']) ? $property_info['type'] : 'text';
$views_field_name = $property_info['schema field'];
$return = array();
if (!empty($schema_field_info['serialize'])) {
return FALSE;
}
$description = array(
'title' => $property_info['label'],
'help' => isset($property_info['description']) ? $property_info['description'] : NULL,
);
// Add in relationships to related entities.
if (($info = entity_get_info($type)) && !empty($info['base table'])) {
// Prepare reversed relationship data.
$label_lowercase = drupal_strtolower($this->info['label'][0]) . drupal_substr($this->info['label'], 1);
$property_label_lowercase = drupal_strtolower($property_info['label'][0]) . drupal_substr($property_info['label'], 1);
// We name the field of the first reverse-relationship just with the
// base table to be backward compatible, for subsequents relationships we
// append the views field name in order to get a unique name.
$name = !isset($this->relationships[$info['base table']][$this->info['base table']]) ? $this->info['base table'] : $this->info['base table'] . '_' . $views_field_name;
$this->relationships[$info['base table']][$name] = array(
'title' => $this->info['label'],
'help' => t("Associated @label via the @label's @property.", array('@label' => $label_lowercase, '@property' => $property_label_lowercase)),
'relationship' => array(
'label' => $this->info['label'],
'handler' => $this->getRelationshipHandlerClass($this->type, $type),
'base' => $this->info['base table'],
'base field' => $views_field_name,
'relationship field' => isset($info['entity keys']['name']) ? $info['entity keys']['name'] : $info['entity keys']['id'],
),
);
$return['relationship'] = array(
'label' => drupal_ucfirst($info['label']),
'handler' => $this->getRelationshipHandlerClass($type, $this->type),
'base' => $info['base table'],
'base field' => isset($info['entity keys']['name']) ? $info['entity keys']['name'] : $info['entity keys']['id'],
'relationship field' => $views_field_name,
);
// Add in direct field/filters/sorts for the id itself too.
$type = isset($info['entity keys']['name']) ? 'token' : 'integer';
// Append the views-field-name to the title if it is different to the
// property name.
if ($property_name != $views_field_name) {
$description['title'] .= ' ' . $views_field_name;
}
}
switch ($type) {
case 'token':
case 'text':
$return += $description + array(
'field' => array(
'real field' => $views_field_name,
'handler' => 'views_handler_field',
'click sortable' => TRUE,
),
'sort' => array(
'real field' => $views_field_name,
'handler' => 'views_handler_sort',
),
'filter' => array(
'real field' => $views_field_name,
'handler' => 'views_handler_filter_string',
),
'argument' => array(
'real field' => $views_field_name,
'handler' => 'views_handler_argument_string',
),
);
break;
case 'decimal':
case 'integer':
$return += $description + array(
'field' => array(
'real field' => $views_field_name,
'handler' => 'views_handler_field_numeric',
'click sortable' => TRUE,
'float' => ($type == 'decimal'),
),
'sort' => array(
'real field' => $views_field_name,
'handler' => 'views_handler_sort',
),
'filter' => array(
'real field' => $views_field_name,
'handler' => 'views_handler_filter_numeric',
),
'argument' => array(
'real field' => $views_field_name,
'handler' => 'views_handler_argument_numeric',
),
);
break;
case 'date':
$return += $description + array(
'field' => array(
'real field' => $views_field_name,
'handler' => 'views_handler_field_date',
'click sortable' => TRUE,
),
'sort' => array(
'real field' => $views_field_name,
'handler' => 'views_handler_sort_date',
),
'filter' => array(
'real field' => $views_field_name,
'handler' => 'views_handler_filter_date',
),
'argument' => array(
'real field' => $views_field_name,
'handler' => 'views_handler_argument_date',
),
);
break;
case 'uri':
$return += $description + array(
'field' => array(
'real field' => $views_field_name,
'handler' => 'views_handler_field_url',
'click sortable' => TRUE,
),
'sort' => array(
'real field' => $views_field_name,
'handler' => 'views_handler_sort',
),
'filter' => array(
'real field' => $views_field_name,
'handler' => 'views_handler_filter_string',
),
'argument' => array(
'real field' => $views_field_name,
'handler' => 'views_handler_argument_string',
),
);
break;
case 'boolean':
$return += $description + array(
'field' => array(
'real field' => $views_field_name,
'handler' => 'views_handler_field_boolean',
'click sortable' => TRUE,
),
'sort' => array(
'real field' => $views_field_name,
'handler' => 'views_handler_sort',
),
'filter' => array(
'real field' => $views_field_name,
'handler' => 'views_handler_filter_boolean_operator',
),
'argument' => array(
'real field' => $views_field_name,
'handler' => 'views_handler_argument_string',
),
);
break;
}
// If there is an options list callback, add to the filter and field.
if (isset($return['filter']) && !empty($property_info['options list'])) {
$return['filter']['handler'] = 'views_handler_filter_in_operator';
$return['filter']['options callback'] = array('EntityDefaultViewsController', 'optionsListCallback');
$return['filter']['options arguments'] = array($this->type, $property_name, 'view');
}
// @todo: This class_exists is needed until views 3.2.
if (isset($return['field']) && !empty($property_info['options list']) && class_exists('views_handler_field_machine_name')) {
$return['field']['handler'] = 'views_handler_field_machine_name';
$return['field']['options callback'] = array('EntityDefaultViewsController', 'optionsListCallback');
$return['field']['options arguments'] = array($this->type, $property_name, 'view');
}
return $return;
}
/**
* Determines the handler to use for a relationship to an entity type.
*
* @param $entity_type
* The entity type to join to.
* @param $left_type
* The data type from which to join.
*/
function getRelationshipHandlerClass($entity_type, $left_type) {
// Look for an entity type which is used as bundle for the given entity
// type. If there is one, allow filtering the relation by bundle by using
// our own handler.
foreach (entity_get_info() as $type => $info) {
// In case we already join from the bundle entity we do not need to filter
// by bundle entity any more, so we stay with the general handler.
if (!empty($info['bundle of']) && $info['bundle of'] == $entity_type && $type != $left_type) {
return 'entity_views_handler_relationship_by_bundle';
}
}
return 'views_handler_relationship';
}
/**
* A callback returning property options, suitable to be used as views options callback.
*/
public static function optionsListCallback($type, $selector, $op = 'view') {
$wrapper = entity_metadata_wrapper($type, NULL);
$parts = explode(':', $selector);
foreach ($parts as $part) {
$wrapper = $wrapper->get($part);
}
return $wrapper->optionsList($op);
}
}

View File

@@ -0,0 +1,88 @@
<?php
/**
* @file
* Contains an example for a Views query plugin that could use the data selection tables.
*/
/**
* Describes the additional methods looked for on a query plugin if data selection based tables or fields are used.
*
* Only get_result_entities() needs to be present, so results can be retrieved.
* The other methods are optional.
*
* If the table does not contain entities, however, the get_result_wrappers()
* method is necessary, too. If this is the case and there are no relations to
* entity tables, the get_result_entities() method is not needed.
*
* @see entity_views_table_definition()
*/
abstract class entity_views_example_query extends views_plugin_query {
/**
* Add a sort to the query.
*
* This is used to add a sort based on an Entity API data selector instead
* of a field alias.
*
* This method has to be present if click-sorting on fields should be allowed
* for some fields using the default Entity API field handlers.
*
* @param $selector
* The field to sort on, as an Entity API data selector.
* @param $order
* The order to sort items in - either 'ASC' or 'DESC'. Defaults to 'ASC'.
*/
public abstract function add_selector_orderby($selector, $order = 'ASC');
/**
* Returns the according entity objects for the given query results.
*
* This is compatible to the get_result_entities() method used by Views.
*
* The method is responsible for resolving the relationship and returning the
* entity objects for that relationship. The helper methods
* EntityFieldHandlerHelper::construct_property_selector() and
* EntityFieldHandlerHelper::extract_property_multiple() can be used to do
* this.
*
* @param $results
* The results of the query, as returned by this query plugin.
* @param $relationship
* (optional) A relationship for which the entities should be returned.
* @param $field
* (optional) The field for which the entity should be returned. This is
* only needed in case a field is derived via a referenced entity without
* using a relationship. For example, if the node's field "author:name" is
* used, the user entity would be returned instead of the node entity.
*
* @return
* A numerically indexed array containing two items: the entity type of
* entities returned by this method; and the array of entities, keyed by the
* same indexes as the results.
*
* @see EntityFieldHandlerHelper::extract_property_multiple()
*/
public abstract function get_result_entities($results, $relationship = NULL, $field = NULL);
/**
* Returns the according metadata wrappers for the given query results.
*
* This can be used if no entities for the results can be given, but entity
* metadata wrappers can be constructed for them.
*
* @param $results
* The results of the query, as returned by this query plugin.
* @param $relationship
* (optional) A relationship for which the wrappers should be returned.
* @param $field
* (optional) The field of which a wrapper should be returned.
*
* @return
* A numerically indexed array containing two items: the data type of
* the wrappers returned by this method; and the array of retrieved
* EntityMetadataWrapper objects, keyed by the same indexes as the results.
*/
public abstract function get_result_wrappers($results, $relationship = NULL, $field = NULL);
}

View File

@@ -0,0 +1,521 @@
<?php
/**
* @file
* Contains the EntityFieldHandlerHelper class.
*/
/**
* Helper class containing static implementations of common field handler methods.
*
* Used by the data selection entity field handlers to avoid code duplication.
*
* @see entity_views_table_definition()
*/
class EntityFieldHandlerHelper {
/**
* Provide appropriate default options for a handler.
*/
public static function option_definition($handler) {
if (entity_property_list_extract_type($handler->definition['type'])) {
$options['list']['contains']['mode'] = array('default' => 'collapse');
$options['list']['contains']['separator'] = array('default' => ', ');
$options['list']['contains']['type'] = array('default' => 'ul');
}
$options['link_to_entity'] = array('default' => FALSE);
return $options;
}
/**
* Provide an appropriate default option form for a handler.
*/
public static function options_form($handler, &$form, &$form_state) {
if (entity_property_list_extract_type($handler->definition['type'])) {
$form['list']['mode'] = array(
'#type' => 'select',
'#title' => t('List handling'),
'#options' => array(
'collapse' => t('Concatenate values using a seperator'),
'list' => t('Output values as list'),
'first' => t('Show first (if present)'),
'count' => t('Show item count'),
),
'#default_value' => $handler->options['list']['mode'],
);
$form['list']['separator'] = array(
'#type' => 'textfield',
'#title' => t('List seperator'),
'#default_value' => $handler->options['list']['separator'],
'#dependency' => array('edit-options-list-mode' => array('collapse')),
);
$form['list']['type'] = array(
'#type' => 'select',
'#title' => t('List type'),
'#options' => array(
'ul' => t('Unordered'),
'ol' => t('Ordered'),
),
'#default_value' => $handler->options['list']['type'],
'#dependency' => array('edit-options-list-mode' => array('list')),
);
}
$form['link_to_entity'] = array(
'#type' => 'checkbox',
'#title' => t('Link this field to its entity'),
'#description' => t("When using this, you should not set any other link on the field."),
'#default_value' => $handler->options['link_to_entity'],
);
}
/**
* Add the field for the entity ID (if necessary).
*/
public static function query($handler) {
// Copied over from views_handler_field_entity::query().
// Sets table_alias (entity table), base_field (entity id field) and
// field_alias (the field's alias).
$handler->table_alias = $base_table = $handler->view->base_table;
$handler->base_field = $handler->view->base_field;
if (!empty($handler->relationship)) {
foreach ($handler->view->relationship as $relationship) {
if ($relationship->alias == $handler->relationship) {
$base_table = $relationship->definition['base'];
$handler->table_alias = $relationship->alias;
$table_data = views_fetch_data($base_table);
$handler->base_field = empty($relationship->definition['base field']) ? $table_data['table']['base']['field'] : $relationship->definition['base field'];
}
}
}
// Add the field if the query back-end implements an add_field() method,
// just like the default back-end.
if (method_exists($handler->query, 'add_field')) {
$handler->field_alias = $handler->query->add_field($handler->table_alias, $handler->base_field, '');
}
else {
// To ensure there is an alias just set the field alias to the real field.
$handler->field_alias = $handler->real_field;
}
}
/**
* Extracts the innermost field name from a data selector.
*
* @param $selector
* The data selector.
*
* @return
* The last component of the data selector.
*/
public static function get_selector_field_name($selector) {
return ltrim(substr($selector, strrpos($selector, ':')), ':');
}
/**
* Adds a click-sort to the query.
*
* @param $order
* Either 'ASC' or 'DESC'.
*/
public static function click_sort($handler, $order) {
// The normal orderby() method for this usually won't work here. So we need
// query plugins to provide their own method for this.
if (method_exists($handler->query, 'add_selector_orderby')) {
$selector = self::construct_property_selector($handler, TRUE);
$handler->query->add_selector_orderby($selector, $order);
}
}
/**
* Load the entities for all rows that are about to be displayed.
*
* Automatically takes care of relationships, including data selection
* relationships. Results are written into @code $handler->wrappers @endcode
* and @code $handler->entity_type @endcode is set.
*/
public static function pre_render($handler, &$values, $load_always = FALSE) {
if (empty($values)) {
return;
}
if (!$load_always && empty($handler->options['link_to_entity'])) {
// Check whether we even need to load the entities.
$selector = self::construct_property_selector($handler, TRUE);
$load = FALSE;
foreach ($values as $row) {
if (empty($row->_entity_properties) || !array_key_exists($selector, $row->_entity_properties)) {
$load = TRUE;
break;
}
}
if (!$load) {
return;
}
}
if (method_exists($handler->query, 'get_result_wrappers')) {
list($handler->entity_type, $handler->wrappers) = $handler->query->get_result_wrappers($values, $handler->relationship, $handler->real_field);
}
else {
list($handler->entity_type, $entities) = $handler->query->get_result_entities($values, $handler->relationship, $handler->real_field);
$handler->wrappers = array();
foreach ($entities as $id => $entity) {
$handler->wrappers[$id] = entity_metadata_wrapper($handler->entity_type, $entity);
}
}
}
/**
* Return an Entity API data selector for the given handler's relationship.
*
* A data selector is a concatenation of properties which should be followed
* to arrive at a desired property that may be nested in related entities or
* structures. The separate properties are herein concatenated with colons.
*
* For instance, a data selector of "author:roles" would mean to first
* access the "author" property of the given wrapper, and then for this new
* wrapper to access and return the "roles" property.
*
* Lists of entities are handled automatically by always returning only the
* first entity.
*
* @param $handler
* The handler for which to construct the selector.
* @param $complete
* If TRUE, the complete selector for the field is returned, not just the
* one for its parent. Defaults to FALSE.
*
* @return
* An Entity API data selector for the given handler's relationship.
*/
public static function construct_property_selector($handler, $complete = FALSE) {
$return = '';
if ($handler->relationship) {
$current_handler = $handler;
$view = $current_handler->view;
while (!empty($current_handler->relationship) && !empty($view->relationship[$current_handler->relationship])) {
$current_handler = $view->relationship[$current_handler->relationship];
$return = $current_handler->real_field . ($return ? ":$return" : '');
}
}
if ($complete) {
$return .= ($return ? ':' : '') . $handler->real_field;
}
elseif ($pos = strrpos($handler->real_field, ':')) {
// If we have a selector as the real_field, append this to the returned
// relationship selector.
$return .= ($return ? ':' : '') . substr($handler->real_field, 0, $pos);
}
return $return;
}
/**
* Extracts data from several metadata wrappers based on a data selector.
*
* All metadata wrappers passed to this function have to be based on the exact
* same property information. The data will be returned wrapped by one or more
* metadata wrappers.
*
* Can be used in query plugins for the get_result_entities() and
* get_result_wrappers() methods.
*
* @param array $wrappers
* The EntityMetadataWrapper objects from which to extract data.
* @param $selector
* The selector specifying the data to extract.
*
* @return array
* An array with numeric indices, containing the type of the extracted
* wrappers in the first element. The second element of the array contains
* the extracted property value(s) for each wrapper, keyed to the same key
* that was used for the respecive wrapper in $wrappers. All extracted
* properties are returned as metadata wrappers.
*/
public static function extract_property_multiple(array $wrappers, $selector) {
$parts = explode(':', $selector, 2);
$name = $parts[0];
$results = array();
$entities = array();
$type = '';
foreach ($wrappers as $i => $wrapper) {
try {
$property = $wrapper->$name;
$type = $property->type();
if ($property instanceof EntityDrupalWrapper) {
// Remember the entity IDs to later load all at once (so as to
// properly utilize multiple load functionality).
$id = $property->getIdentifier();
// Only accept valid ids. $id can be FALSE for entity values that are
// NULL.
if ($id) {
$entities[$type][$i] = $id;
}
}
elseif ($property instanceof EntityStructureWrapper) {
$results[$i] = $property;
}
elseif ($property instanceof EntityListWrapper) {
foreach ($property as $item) {
$results[$i] = $item;
$type = $item->type();
break;
}
}
// Do nothing in case it cannot be applied.
}
catch (EntityMetadataWrapperException $e) {
// Skip single empty properties.
}
}
if ($entities) {
// Map back the loaded entities back to the results array.
foreach ($entities as $type => $id_map) {
$loaded = entity_load($type, $id_map);
foreach ($id_map as $i => $id) {
if (isset($loaded[$id])) {
$results[$i] = entity_metadata_wrapper($type, $loaded[$id]);
}
}
}
}
// If there are no further parts in the selector, we are done now.
if (empty($parts[1])) {
return array($type, $results);
}
return self::extract_property_multiple($results, $parts[1]);
}
/**
* Get the value of a certain data selector.
*
* Uses $values->_entity_properties to look for already extracted properties.
*
* @param $handler
* The field handler for which to return a value.
* @param $values
* The values for the current row retrieved from the Views query, as an
* object.
* @param $field
* The field to extract. If no value is given, the field of the given
* handler is used instead. The special "entity object" value can be used to
* get the base entity instead of a special field.
* @param $default
* The value to return if the entity or field are not present.
*/
public static function get_value($handler, $values, $field = NULL, $default = NULL) {
// There is a value cache on each handler so parent handlers rendering a
// single field value from a list will get the single value, not the whole
// list.
if (!isset($field) && isset($handler->current_value)) {
return $handler->current_value;
}
$field = isset($field) ? $field : self::get_selector_field_name($handler->real_field);
$selector = self::construct_property_selector($handler);
$selector = $selector ? "$selector:$field" : $field;
if (!isset($values->_entity_properties)) {
$values->_entity_properties = array();
}
if (!array_key_exists($selector, $values->_entity_properties)) {
if (!isset($handler->wrappers[$handler->view->row_index])) {
$values->_entity_properties[$selector] = $default;
}
elseif (is_array($handler->wrappers[$handler->view->row_index])) {
$values->_entity_properties[$selector] = self::extract_list_wrapper_values($handler->wrappers[$handler->view->row_index], $field);
}
else {
$wrapper = $handler->wrappers[$handler->view->row_index];
try {
if ($field === 'entity object') {
$values->_entity_properties[$selector] = $wrapper->value();
}
else {
$values->_entity_properties[$selector] = isset($wrapper->$field) ? $wrapper->$field->value(array('identifier' => TRUE)) : $default;
}
}
catch (EntityMetadataWrapperException $e) {
$values->_entity_properties[$selector] = $default;
}
}
}
return $values->_entity_properties[$selector];
}
/**
* Helper method for extracting the values from an array of wrappers.
*
* Nested arrays of wrappers are also handled, the values are returned in a
* flat (not nested) array.
*/
public static function extract_list_wrapper_values(array $wrappers, $field) {
$return = array();
foreach ($wrappers as $wrapper) {
if (is_array($wrapper)) {
$values = self::extract_list_wrapper_values($wrapper, $field);
if ($values) {
$return = array_merge($return, $values);
}
}
else {
try {
if ($field == 'entity object') {
$return[] = $wrapper->value();
}
elseif (isset($wrapper->$field)) {
$return[] = $wrapper->$field->value(array('identifier' => TRUE));
}
}
catch (EntityMetadataWrapperException $e) {
// An exception probably signifies a non-present property, so we just
// ignore it.
}
}
}
return $return;
}
/**
* Render the field.
*
* Implements the entity link functionality and list handling. Basic handling
* of the single values is delegated back to the field handler.
*
* @param $handler
* The field handler whose field should be rendered.
* @param $values
* The values for the current row retrieved from the Views query, as an
* object.
*
* @return
* The rendered value for the field.
*/
public static function render($handler, $values) {
$value = $handler->get_value($values);
if (is_array($value)) {
return self::render_list($handler, $value, $values);
}
return self::render_entity_link($handler, $value, $values);
}
/**
* Render a list of values.
*
* @param $handler
* The field handler whose field is rendered.
* @param $list
* The list of values to render.
* @param $values
* The values for the current row retrieved from the Views query, as an
* object.
*
* @return
* The rendered value for the given list.
*/
public static function render_list($handler, $list, $values) {
// Allow easy overriding of this behaviour in the specific field handler.
if (method_exists($handler, 'render_list')) {
return $handler->render_list($list, $values);
}
$mode = isset($handler->options['list']['mode']) ? $handler->options['list']['mode'] : NULL;
switch ($mode) {
case 'first':
$list = count($list) ? array_shift($list) : NULL;
if (is_array($list)) {
return self::render_list($handler, $list, $values);
}
elseif (isset($list)) {
return self::render_entity_link($handler, $list, $values);
}
return NULL;
case 'count':
return count($list);
// Handles both collapse and list output. Fallback is to collapse.
default:
$inner_values = array();
foreach ($list as $value) {
$value = is_array($value) ? self::render_list($handler, $value, $values) : self::render_entity_link($handler, $value, $values);
if ($value) {
$inner_values[] = $value;
}
}
// Format output as list.
if ($mode == 'list') {
$type = isset($handler->options['list']['type']) ? $handler->options['list']['type'] : 'ul';
return theme('item_list', array(
'items' => $inner_values,
'type' => $type,
));
}
$separator = isset($handler->options['list']['separator']) ? $handler->options['list']['separator'] : ', ';
return implode($separator, $inner_values);
}
}
/**
* Render a single value as a link to the entity if applicable.
*
* @param $handler
* The field handler whose field is rendered.
* @param $value
* The single value to render.
* @param $values
* The values for the current row retrieved from the Views query, as an
* object.
*
* @return
* The rendered value.
*/
public static function render_entity_link($handler, $value, $values) {
// Allow easy overriding of this behaviour in the specific field handler.
if (method_exists($handler, 'render_entity_link')) {
return $handler->render_entity_link($value, $values);
}
$render = self::render_single_value($handler, $value, $values);
if (!$handler->options['link_to_entity']) {
return $render;
}
$entity = $handler->get_value($values, 'entity object');
if (is_object($entity) && ($url = entity_uri($handler->entity_type, $entity))) {
return l($render, $url['path'], array('html' => TRUE) + $url['options']);
}
return $render;
}
/**
* Render a single value.
*
* @param $handler
* The field handler whose field is rendered.
* @param $value
* The single value to render.
* @param $values
* The values for the current row retrieved from the Views query, as an
* object.
*
* @return
* The rendered value.
*/
public static function render_single_value($handler, $value, $values) {
// Try to use the method in the specific field handler.
if (method_exists($handler, 'render_single_value')) {
$handler->current_value = $value;
$return = $handler->render_single_value($value, $values);
unset($handler->current_value);
return $return;
}
// Default fallback in case the field handler doesn't provide the method.
return is_scalar($value) ? check_plain($value) : nl2br(check_plain(print_r($value, TRUE)));
}
}

View File

@@ -0,0 +1,111 @@
<?php
/**
* @file
* Renders a full entity in a views area.
*/
class entity_views_handler_area_entity extends views_handler_area {
public function option_definition() {
$options = parent::option_definition();
$options['entity_type'] = array('default' => 'node');
$options['entity_id'] = array('default' => '');
$options['view_mode'] = array('default' => 'full');
return $options;
}
function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
$entity_type_options = array();
foreach (entity_get_info() as $entity_type => $entity_info) {
$entity_type_options[$entity_type] = $entity_info['label'];
}
$entity_type = $this->options['entity_type'];
$form['entity_type'] = array(
'#type' => 'select',
'#title' => t('Entity type'),
'#options' => $entity_type_options,
'#description' => t('Choose the entity type you want to display in the area.'),
'#default_value' => $entity_type,
'#ajax' => array(
'path' => views_ui_build_form_url($form_state),
),
'#submit' => array('views_ui_config_item_form_submit_temporary'),
'#executes_submit_callback' => TRUE,
);
$form['entity_id'] = array(
'#type' => 'textfield',
'#title' => t('Entity id'),
'#description' => t('Choose the entity you want to display in the area.'),
'#default_value' => $this->options['entity_id'],
);
if ($entity_type) {
$entity_info = entity_get_info($entity_type);
$options = array();
if (!empty($entity_info['view modes'])) {
foreach ($entity_info['view modes'] as $mode => $settings) {
$options[$mode] = $settings['label'];
}
}
if (count($options) > 1) {
$form['view_mode'] = array(
'#type' => 'select',
'#options' => $options,
'#title' => t('View mode'),
'#default_value' => $this->options['view_mode'],
);
}
else {
$form['view_mode_info'] = array(
'#type' => 'item',
'#title' => t('View mode'),
'#description' => t('Only one view mode is available for this entity type.'),
'#markup' => $options ? current($options) : t('Default'),
);
$form['view_mode'] = array(
'#type' => 'value',
'#value' => $options ? key($options) : 'default',
);
}
}
return $form;
}
public function admin_summary() {
$label = parent::admin_summary();
if (!empty($this->options['entity_id'])) {
return t('@label @entity_type:@entity_id', array(
'@label' => $label,
'@entity_type' => $this->options['entity_type'],
'@entity_id' => $this->options['entity_id'],
));
}
}
public function render($empty = FALSE) {
if (!$empty || !empty($this->options['empty'])) {
return $this->render_entity($this->options['entity_type'], $this->options['entity_id'], $this->options['view_mode']);
}
return '';
}
/**
* Render an entity using the view mode.
*/
public function render_entity($entity_type, $entity_id, $view_mode) {
if (!empty($entity_type) && !empty($entity_id) && !empty($view_mode)) {
$entities = entity_load($entity_type, array($entity_id));
$render = entity_view($entity_type, $entities, $view_mode);
$render_entity = reset($render);
return drupal_render($render_entity);
}
else {
return '';
}
}
}

View File

@@ -0,0 +1,99 @@
<?php
/**
* @file
* Contains the entity_views_handler_field_boolean class.
*/
/**
* A handler to provide proper displays for booleans.
*
* Overrides the default Views handler to retrieve the data from an entity via
* data selection.
*
* This handler may only be used in conjunction with data selection based Views
* tables or other base tables using a query plugin that supports data
* selection.
*
* @see entity_views_field_definition()
* @ingroup views_field_handlers
*/
class entity_views_handler_field_boolean extends views_handler_field_boolean {
/**
* Stores the entity type of the result entities.
*/
public $entity_type;
/**
* Stores the result entities' metadata wrappers.
*/
public $wrappers = array();
/**
* Stores the current value when rendering list fields.
*/
public $current_value;
/**
* Overridden to add the field for the entity ID (if necessary).
*/
public function query() {
EntityFieldHandlerHelper::query($this);
}
/**
* Adds a click-sort to the query.
*/
public function click_sort($order) {
EntityFieldHandlerHelper::click_sort($this, $order);
}
/**
* Load the entities for all rows that are about to be displayed.
*/
public function pre_render(&$values) {
parent::pre_render($values);
EntityFieldHandlerHelper::pre_render($this, $values);
}
/**
* Overridden to use a metadata wrapper.
*/
public function get_value($values, $field = NULL) {
return EntityFieldHandlerHelper::get_value($this, $values, $field);
}
/**
* Provide options for this handler.
*/
public function option_definition() {
return parent::option_definition() + EntityFieldHandlerHelper::option_definition($this);
}
/**
* Provide a options form for this handler.
*/
public function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
EntityFieldHandlerHelper::options_form($this, $form, $form_state);
}
/**
* Render the field.
*
* @param $values
* The values retrieved from the database.
*/
public function render($values) {
return EntityFieldHandlerHelper::render($this, $values);
}
/**
* Render a single field value.
*/
public function render_single_value($value, $values) {
return parent::render($values);
}
}

View File

@@ -0,0 +1,99 @@
<?php
/**
* @file
* Contains the entity_views_handler_field_date class.
*/
/**
* A handler to provide proper displays for dates.
*
* Overrides the default Views handler to retrieve the data from an entity via
* data selection.
*
* This handler may only be used in conjunction with data selection based Views
* tables or other base tables using a query plugin that supports data
* selection.
*
* @see entity_views_field_definition()
* @ingroup views_field_handlers
*/
class entity_views_handler_field_date extends views_handler_field_date {
/**
* Stores the entity type of the result entities.
*/
public $entity_type;
/**
* Stores the result entities' metadata wrappers.
*/
public $wrappers = array();
/**
* Stores the current value when rendering list fields.
*/
public $current_value;
/**
* Overridden to add the field for the entity ID (if necessary).
*/
public function query() {
EntityFieldHandlerHelper::query($this);
}
/**
* Adds a click-sort to the query.
*/
public function click_sort($order) {
EntityFieldHandlerHelper::click_sort($this, $order);
}
/**
* Load the entities for all rows that are about to be displayed.
*/
public function pre_render(&$values) {
parent::pre_render($values);
EntityFieldHandlerHelper::pre_render($this, $values);
}
/**
* Overridden to use a metadata wrapper.
*/
public function get_value($values, $field = NULL) {
return EntityFieldHandlerHelper::get_value($this, $values, $field);
}
/**
* Provide options for this handler.
*/
public function option_definition() {
return parent::option_definition() + EntityFieldHandlerHelper::option_definition($this);
}
/**
* Provide a options form for this handler.
*/
public function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
EntityFieldHandlerHelper::options_form($this, $form, $form_state);
}
/**
* Render the field.
*
* @param $values
* The values retrieved from the database.
*/
public function render($values) {
return EntityFieldHandlerHelper::render($this, $values);
}
/**
* Render a single field value.
*/
public function render_single_value($value, $values) {
return parent::render($values);
}
}

View File

@@ -0,0 +1,130 @@
<?php
/**
* @file
* Contains the entity_views_handler_field_duration class.
*/
/**
* A handler to provide proper displays for duration properties retrieved via data selection.
*
* This handler may only be used in conjunction with data selection based Views
* tables or other base tables using a query plugin that supports data
* selection.
*
* @see entity_views_field_definition()
* @ingroup views_field_handlers
*/
class entity_views_handler_field_duration extends views_handler_field {
/**
* Stores the entity type of the result entities.
*/
public $entity_type;
/**
* Stores the result entities' metadata wrappers.
*/
public $wrappers = array();
/**
* Stores the current value when rendering list fields.
*/
public $current_value;
/**
* Overridden to add the field for the entity ID (if necessary).
*/
public function query() {
EntityFieldHandlerHelper::query($this);
}
/**
* Adds a click-sort to the query.
*/
public function click_sort($order) {
EntityFieldHandlerHelper::click_sort($this, $order);
}
/**
* Load the entities for all rows that are about to be displayed.
*/
public function pre_render(&$values) {
parent::pre_render($values);
EntityFieldHandlerHelper::pre_render($this, $values);
}
/**
* Overridden to use a metadata wrapper.
*/
public function get_value($values, $field = NULL) {
return EntityFieldHandlerHelper::get_value($this, $values, $field);
}
public function option_definition() {
$options = parent::option_definition();
$options += EntityFieldHandlerHelper::option_definition($this);
$options['format_interval'] = array('default' => TRUE);
$options['granularity'] = array('default' => 2);
$options['prefix'] = array('default' => '', 'translatable' => TRUE);
$options['suffix'] = array('default' => '', 'translatable' => TRUE);
return $options;
}
public function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
EntityFieldHandlerHelper::options_form($this, $form, $form_state);
$form['format_interval'] = array(
'#type' => 'checkbox',
'#title' => t('Format interval'),
'#description' => t('If checked, the value will be formatted as a time interval. Otherwise, just the number of seconds will be displayed.'),
'#default_value' => $this->options['format_interval'],
);
$form['granularity'] = array(
'#type' => 'textfield',
'#title' => t('Granularity'),
'#default_value' => $this->options['granularity'],
'#description' => t('Specify how many different units to display.'),
'#dependency' => array('edit-options-format-interval' => array(TRUE)),
'#size' => 2,
);
$form['prefix'] = array(
'#type' => 'textfield',
'#title' => t('Prefix'),
'#default_value' => $this->options['prefix'],
'#description' => t('Text to put before the duration text.'),
);
$form['suffix'] = array(
'#type' => 'textfield',
'#title' => t('Suffix'),
'#default_value' => $this->options['suffix'],
'#description' => t('Text to put after the duration text.'),
);
}
/**
* Render the field.
*
* @param $values
* The values retrieved from the database.
*/
public function render($values) {
return EntityFieldHandlerHelper::render($this, $values);
}
/**
* Render a single field value.
*/
public function render_single_value($value, $values) {
if ($this->options['format_interval']) {
$value = format_interval($value, (int) $this->options['granularity']);
}
return $this->sanitize_value($this->options['prefix'], 'xss') .
$this->sanitize_value($value) .
$this->sanitize_value($this->options['suffix'], 'xss');
}
}

View File

@@ -0,0 +1,199 @@
<?php
/**
* @file
* Contains the entity_views_handler_field_entity class.
*/
/**
* A handler to provide proper displays for entities retrieved via data selection.
*
* This handler may only be used in conjunction with data selection based Views
* tables or other base tables using a query plugin that supports data
* selection.
*
* @see entity_views_field_definition()
* @ingroup views_field_handlers
*/
class entity_views_handler_field_entity extends views_handler_field {
/**
* Stores the entity type of the result entities.
*/
public $entity_type;
/**
* Stores the result entities' metadata wrappers.
*/
public $wrappers = array();
/**
* The entity type of the entity displayed by this field.
*/
public $field_entity_type;
/**
* Stores the current value when rendering list fields.
*/
public $current_value;
/**
* Initialize the entity type with the field's entity type.
*/
public function init(&$view, &$options) {
parent::init($view, $options);
$this->field_entity_type = entity_property_extract_innermost_type($this->definition['type']);
}
/**
* Overridden to add the field for the entity ID (if necessary).
*/
public function query() {
EntityFieldHandlerHelper::query($this);
}
/**
* Adds a click-sort to the query.
*/
public function click_sort($order) {
EntityFieldHandlerHelper::click_sort($this, $order);
}
/**
* Load the entities for all rows that are about to be displayed.
*/
public function pre_render(&$values) {
EntityFieldHandlerHelper::pre_render($this, $values);
}
/**
* Overridden to use a metadata wrapper.
*/
public function get_value($values, $field = NULL) {
return EntityFieldHandlerHelper::get_value($this, $values, $field);
}
public function option_definition() {
$options = parent::option_definition();
$options += EntityFieldHandlerHelper::option_definition($this);
$options['display'] = array('default' => 'label');
$options['link_to_entity']['default'] = TRUE;
$options['view_mode'] = array('default' => 'default');
return $options;
}
public function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
EntityFieldHandlerHelper::options_form($this, $form, $form_state);
// We want a different form field at a different place.
unset($form['link_to_entity']);
$options = array(
'label' => t('Show entity label'),
'id' => t('Show entity ID'),
'view' => t('Show complete entity'),
);
$form['display'] = array(
'#type' => 'select',
'#title' => t('Display'),
'#description' => t('Decide how this field will be displayed.'),
'#options' => $options,
'#default_value' => $this->options['display'],
);
$form['link_to_entity'] = array(
'#type' => 'checkbox',
'#title' => t('Link to entity'),
'#description' => t('Link this field to the entity.'),
'#default_value' => $this->options['link_to_entity'],
'#dependency' => array('edit-options-display' => array('label', 'id')),
);
// Stolen from entity_views_plugin_row_entity_view.
$entity_info = entity_get_info($this->field_entity_type);
$options = array();
if (!empty($entity_info['view modes'])) {
foreach ($entity_info['view modes'] as $mode => $settings) {
$options[$mode] = $settings['label'];
}
}
if (count($options) > 1) {
$form['view_mode'] = array(
'#type' => 'select',
'#options' => $options,
'#title' => t('View mode'),
'#default_value' => $this->options['view_mode'],
'#dependency' => array('edit-options-display' => array('view')),
);
}
else {
$form['view_mode'] = array(
'#type' => 'value',
'#value' => $options ? key($options) : 'default',
);
}
}
public function render($values) {
return EntityFieldHandlerHelper::render($this, $values);
}
/**
* Render a value as a link to the entity if applicable.
*
* @param $value
* The value to render.
* @param $values
* The values for the current row retrieved from the Views query, as an
* object.
*/
public function render_entity_link($entity, $values) {
$type = $this->field_entity_type;
if (!is_object($entity) && isset($entity) && $entity !== FALSE) {
$entity = entity_load_single($type, $entity);
}
if (!$entity) {
return '';
}
$render = $this->render_single_value($entity, $values);
if (!$this->options['link_to_entity'] || $this->options['display'] == 'view') {
return $render;
}
if (is_object($entity) && ($url = entity_uri($type, $entity))) {
return l($render, $url['path'], array('html' => TRUE) + $url['options']);
}
return $render;
}
/**
* Render a single field value.
*/
public function render_single_value($entity, $values) {
$type = $this->field_entity_type;
if (!is_object($entity) && isset($entity) && $entity !== FALSE) {
$entity = entity_load_single($type, $entity);
}
if (!$entity) {
return '';
}
if ($this->options['display'] === 'view') {
$entity_view = entity_view($type, array($entity), $this->options['view_mode']);
return render($entity_view);
}
if ($this->options['display'] == 'label') {
$value = entity_label($type, $entity);
}
// Either $options[display] == 'id', or we have no label.
if (empty($value)) {
$value = entity_id($type, $entity);
}
$value = $this->sanitize_value($value);
return $value;
}
}

View File

@@ -0,0 +1,105 @@
<?php
/**
* @file
* Contains the entity_views_handler_field_field class.
*/
/**
* A handler to provide proper displays for Field API fields.
*
* Overrides the default Views handler to retrieve the data from an entity via
* data selection.
*
* This handler may only be used in conjunction with data selection based Views
* tables or other base tables using a query plugin that supports data
* selection.
*
* @see entity_views_field_definition()
* @ingroup views_field_handlers
*/
class entity_views_handler_field_field extends views_handler_field_field {
/**
* Stores the entity type of the result entities.
*/
public $entity_type;
/**
* Stores the result entities' metadata wrappers.
*/
public $wrappers = array();
/**
* The entity for which this field is currently rendered.
*/
public $entity;
/**
* Return TRUE if the user has access to view this field.
*/
public function access() {
return field_access('view', $this->field_info, $this->definition['entity type']);
}
/**
* Overridden to add the field for the entity ID (if necessary).
*/
public function query($use_groupby = FALSE) {
EntityFieldHandlerHelper::query($this);
}
/**
* Adds a click-sort to the query.
*/
public function click_sort($order) {
EntityFieldHandlerHelper::click_sort($this, $order);
}
/**
* Override so it doesn't do any harm (or, anything at all).
*/
public function post_execute(&$values) { }
/**
* Load the entities for all rows that are about to be displayed.
*/
public function pre_render(&$values) {
parent::pre_render($values);
EntityFieldHandlerHelper::pre_render($this, $values, TRUE);
}
/**
* Overridden to get the items our way.
*/
public function get_items($values) {
$items = array();
// Set the entity type for the parent handler.
$values->_field_data[$this->field_alias]['entity_type'] = $this->entity_type;
// We need special handling for lists of entities as the base.
$entities = EntityFieldHandlerHelper::get_value($this, $values, 'entity object');
if (!is_array($entities)) {
$entities = $entities ? array($entities) : array();
}
foreach ($entities as $entity) {
// Only try to render the field if it is even present on this bundle.
// Otherwise, field_view_field() will trigger a fatal.
list (, , $bundle) = entity_extract_ids($this->entity_type, $entity);
if (field_info_instance($this->entity_type, $this->definition['field_name'], $bundle)) {
// Set the currently rendered entity.
$values->_field_data[$this->field_alias]['entity'] = $entity;
$items = array_merge($items, $this->set_items($values, $this->view->row_index));
}
}
return $items;
}
/**
* Overridden to force displaying multiple values in a single row.
*/
function multiple_options_form(&$form, &$form_state) {
parent::multiple_options_form($form, $form_state);
$form['group_rows']['#default_value'] = TRUE;
$form['group_rows']['#disabled'] = TRUE;
}
}

View File

@@ -0,0 +1,99 @@
<?php
/**
* @file
* Contains the entity_views_handler_field_numeric class.
*/
/**
* Render a field as a numeric value.
*
* Overrides the default Views handler to retrieve the data from an entity via
* data selection.
*
* This handler may only be used in conjunction with data selection based Views
* tables or other base tables using a query plugin that supports data
* selection.
*
* @see entity_views_field_definition()
* @ingroup views_field_handlers
*/
class entity_views_handler_field_numeric extends views_handler_field_numeric {
/**
* Stores the entity type of the result entities.
*/
public $entity_type;
/**
* Stores the result entities' metadata wrappers.
*/
public $wrappers = array();
/**
* Stores the current value when rendering list fields.
*/
public $current_value;
/**
* Overridden to add the field for the entity ID (if necessary).
*/
public function query() {
EntityFieldHandlerHelper::query($this);
}
/**
* Adds a click-sort to the query.
*/
public function click_sort($order) {
EntityFieldHandlerHelper::click_sort($this, $order);
}
/**
* Load the entities for all rows that are about to be displayed.
*/
public function pre_render(&$values) {
parent::pre_render($values);
EntityFieldHandlerHelper::pre_render($this, $values);
}
/**
* Overridden to use a metadata wrapper.
*/
public function get_value($values, $field = NULL) {
return EntityFieldHandlerHelper::get_value($this, $values, $field);
}
/**
* Provide options for this handler.
*/
public function option_definition() {
return parent::option_definition() + EntityFieldHandlerHelper::option_definition($this);
}
/**
* Provide a options form for this handler.
*/
public function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
EntityFieldHandlerHelper::options_form($this, $form, $form_state);
}
/**
* Render the field.
*
* @param $values
* The values retrieved from the database.
*/
public function render($values) {
return EntityFieldHandlerHelper::render($this, $values);
}
/**
* Render a single field value.
*/
public function render_single_value($value, $values) {
return parent::render($values);
}
}

View File

@@ -0,0 +1,119 @@
<?php
/**
* @file
* Contains the entity_views_handler_field_options class.
*/
/**
* A handler to provide proper displays for values chosen from a set of options.
*
* This handler may only be used in conjunction with data selection based Views
* tables or other base tables using a query plugin that supports data
* selection.
*
* @see entity_views_field_definition()
* @ingroup views_field_handlers
*/
class entity_views_handler_field_options extends views_handler_field {
/**
* Stores the entity type of the result entities.
*/
public $entity_type;
/**
* Stores the result entities' metadata wrappers.
*/
public $wrappers = array();
/**
* Stores the current value when rendering list fields.
*/
public $current_value;
/**
* The key / name mapping for the options.
*/
public $option_list;
/**
* Overridden to add the field for the entity ID (if necessary).
*/
public function query() {
EntityFieldHandlerHelper::query($this);
}
/**
* Adds a click-sort to the query.
*/
public function click_sort($order) {
EntityFieldHandlerHelper::click_sort($this, $order);
}
/**
* Load the entities for all rows that are about to be displayed.
*/
public function pre_render(&$values) {
parent::pre_render($values);
EntityFieldHandlerHelper::pre_render($this, $values);
}
/**
* Overridden to use a metadata wrapper.
*/
public function get_value($values, $field = NULL) {
return EntityFieldHandlerHelper::get_value($this, $values, $field);
}
/**
* Specifies the options this handler uses.
*/
public function option_definition() {
$options = parent::option_definition();
$options += EntityFieldHandlerHelper::option_definition($this);
$options['format_name'] = array('default' => TRUE);
return $options;
}
/**
* Returns an option form for setting this handler's options.
*/
public function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
EntityFieldHandlerHelper::options_form($this, $form, $form_state);
$form['format_name'] = array(
'#title' => t('Use human-readable name'),
'#type' => 'checkbox',
'#description' => t("If this is checked, the values' names will be displayed instead of their internal identifiers."),
'#default_value' => $this->options['format_name'],
'#weight' => -5,
);
}
public function render($values) {
return EntityFieldHandlerHelper::render($this, $values);
}
/**
* Render a single field value.
*/
public function render_single_value($value, $values) {
if (!isset($this->option_list)) {
$this->option_list = array();
$callback = $this->definition['options callback'];
if (is_callable($callback['function'])) {
// If a selector is used, get the name of the selected field.
$field_name = EntityFieldHandlerHelper::get_selector_field_name($this->real_field);
$this->option_list = call_user_func($callback['function'], $field_name, $callback['info'], 'view');
}
}
if ($this->options['format_name'] && isset($this->option_list[$value])) {
$value = $this->option_list[$value];
}
return $this->sanitize_value($value);
}
}

View File

@@ -0,0 +1,99 @@
<?php
/**
* @file
* Contains the entity_views_handler_field_text class.
*/
/**
* A handler to display text data.
*
* Overrides the default Views handler to retrieve the data from an entity via
* data selection.
*
* This handler may only be used in conjunction with data selection based Views
* tables or other base tables using a query plugin that supports data
* selection.
*
* @see entity_views_field_definition()
* @ingroup views_field_handlers
*/
class entity_views_handler_field_text extends views_handler_field {
/**
* Stores the entity type of the result entities.
*/
public $entity_type;
/**
* Stores the result entities' metadata wrappers.
*/
public $wrappers = array();
/**
* Stores the current value when rendering list fields.
*/
public $current_value;
/**
* Overridden to add the field for the entity ID (if necessary).
*/
public function query() {
EntityFieldHandlerHelper::query($this);
}
/**
* Adds a click-sort to the query.
*/
public function click_sort($order) {
EntityFieldHandlerHelper::click_sort($this, $order);
}
/**
* Load the entities for all rows that are about to be displayed.
*/
public function pre_render(&$values) {
parent::pre_render($values);
EntityFieldHandlerHelper::pre_render($this, $values);
}
/**
* Overridden to use a metadata wrapper.
*/
public function get_value($values, $field = NULL) {
return EntityFieldHandlerHelper::get_value($this, $values, $field);
}
/**
* Provide options for this handler.
*/
public function option_definition() {
return parent::option_definition() + EntityFieldHandlerHelper::option_definition($this);
}
/**
* Provide a options form for this handler.
*/
public function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
EntityFieldHandlerHelper::options_form($this, $form, $form_state);
}
/**
* Render the field.
*
* @param $values
* The values retrieved from the database.
*/
public function render($values) {
return EntityFieldHandlerHelper::render($this, $values);
}
/**
* Render a single field value.
*/
public function render_single_value($value, $values) {
return $this->sanitize_value($value, 'xss');
}
}

View File

@@ -0,0 +1,99 @@
<?php
/**
* @file
* Contains the entity_views_handler_field_uri class.
*/
/**
* Field handler to provide simple renderer that turns a URL into a clickable link.
*
* Overrides the default Views handler to retrieve the data from an entity via
* data selection.
*
* This handler may only be used in conjunction with data selection based Views
* tables or other base tables using a query plugin that supports data
* selection.
*
* @see entity_views_field_definition()
* @ingroup views_field_handlers
*/
class entity_views_handler_field_uri extends views_handler_field_url {
/**
* Stores the entity type of the result entities.
*/
public $entity_type;
/**
* Stores the result entities' metadata wrappers.
*/
public $wrappers = array();
/**
* Stores the current value when rendering list fields.
*/
public $current_value;
/**
* Overridden to add the field for the entity ID (if necessary).
*/
public function query() {
EntityFieldHandlerHelper::query($this);
}
/**
* Adds a click-sort to the query.
*/
public function click_sort($order) {
EntityFieldHandlerHelper::click_sort($this, $order);
}
/**
* Load the entities for all rows that are about to be displayed.
*/
public function pre_render(&$values) {
parent::pre_render($values);
EntityFieldHandlerHelper::pre_render($this, $values);
}
/**
* Overridden to use a metadata wrapper.
*/
public function get_value($values, $field = NULL) {
return EntityFieldHandlerHelper::get_value($this, $values, $field);
}
/**
* Provide options for this handler.
*/
public function option_definition() {
return parent::option_definition() + EntityFieldHandlerHelper::option_definition($this);
}
/**
* Provide a options form for this handler.
*/
public function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
EntityFieldHandlerHelper::options_form($this, $form, $form_state);
}
/**
* Render the field.
*
* @param $values
* The values retrieved from the database.
*/
public function render($values) {
return EntityFieldHandlerHelper::render($this, $values);
}
/**
* Render a single field value.
*/
public function render_single_value($value, $values) {
return parent::render($values);
}
}

View File

@@ -0,0 +1,51 @@
<?php
/**
* @file
* Contains the entity_views_handler_relationship class.
*/
/**
* Relationship handler for data selection tables.
*
* This handler may only be used in conjunction with data selection based Views
* tables or other base tables using a query plugin that supports data
* selection.
*
* @see entity_views_field_definition()
* @ingroup views_field_handlers
*/
class entity_views_handler_relationship extends views_handler_relationship {
/**
* Slightly modify the options form provided by the parent handler.
*/
public function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
// This won't work with data selector-based relationships, as we only
// inspect those *after* the results are known.
$form['required']['#access'] = FALSE;
// Notify the user of our restrictions regarding lists of entities, if
// appropriate.
if (!empty($this->definition['multiple'])) {
$form['multiple_note'] = array(
'#markup' => t('<strong>Note:</strong> This is a multi-valued relationship, which is currently not supported. ' .
'Only the first related entity will be shown.'),
'#weight' => -5,
);
}
}
/**
* Called to implement a relationship in a query.
*
* As we don't add any data to the query itself, we don't have to do anything
* here. Views just don't thinks we have been called unless we set our
* $alias property. Otherwise, this override is just here to keep PHP from
* blowing up by calling inexistent methods on the query plugin.
*/
public function query() {
$this->alias = $this->options['id'];
}
}

View File

@@ -0,0 +1,117 @@
<?php
/**
* @file
* Contains the entity_views_handler_relationship_by_bundle class.
*/
/**
* Relationship handler for entity relationships that may limit the join to one or more bundles.
*
* This handler is only applicable for entities that are using bundle entities,
* i.e. entities having the 'bundle of' entity info key set.
*
* For example, this allows a relationship from users to profiles of a certain
* profile type.
*
* @see entity_crud_hook_entity_info()
* @ingroup views_field_handlers
*/
class entity_views_handler_relationship_by_bundle extends views_handler_relationship {
function option_definition() {
$options = parent::option_definition();
$options['bundle_types'] = array('default' => array());
return $options;
}
/**
* Add an entity type option.
*/
function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
// Get the entity type and info from the table data for the base on the
// right hand side of the relationship join.
$table_data = views_fetch_data($this->definition['base']);
$entity_type = $table_data['table']['entity type'];
$entity_info = entity_get_info($entity_type);
// Get the info of the bundle entity.
foreach (entity_get_info() as $type => $info) {
if (isset($info['bundle of']) && $info['bundle of'] == $entity_type) {
$entity_bundle_info = $info;
break;
}
}
$plural_label = isset($entity_bundle_info['plural label']) ? $entity_bundle_info['plural label'] : $entity_bundle_info['label'] . 's';
$bundle_options = array();
foreach ($entity_info['bundles'] as $name => $info) {
$bundle_options[$name] = $info['label'];
}
$form['bundle_types'] = array(
'#title' => $plural_label,
'#type' => 'checkboxes',
'#description' => t('Restrict this relationship to one or more @bundles.', array('@bundles' => strtolower($entity_bundle_info['plural label']))),
'#options' => $bundle_options,
'#default_value' => $this->options['bundle_types'],
);
}
/**
* Make sure only checked bundle types are left.
*/
function options_submit(&$form, &$form_state) {
$form_state['values']['options']['bundle_types'] = array_filter($form_state['values']['options']['bundle_types']);
parent::options_submit($form, $form_state);
}
/**
* Called to implement a relationship in a query.
*
* Mostly the same as the parent method, except we add an extra clause to
* the join.
*/
function query() {
$table_data = views_fetch_data($this->definition['base']);
$base_field = empty($this->definition['base field']) ? $table_data['table']['base']['field'] : $this->definition['base field'];
$this->ensure_my_table();
$def = $this->definition;
$def['table'] = $this->definition['base'];
$def['field'] = $base_field;
$def['left_table'] = $this->table_alias;
$def['left_field'] = $this->field;
if (!empty($this->options['required'])) {
$def['type'] = 'INNER';
}
// Add an extra clause to the join if there are bundle types selected.
if ($this->options['bundle_types']) {
$entity_info = entity_get_info($table_data['table']['entity type']);
$def['extra'] = array(
array(
// The table and the IN operator are implicit.
'field' => $entity_info['bundle keys']['bundle'],
'value' => $this->options['bundle_types'],
),
);
}
if (!empty($def['join_handler']) && class_exists($def['join_handler'])) {
$join = new $def['join_handler'];
}
else {
$join = new views_join();
}
$join->definition = $def;
$join->construct();
$join->adjusted = TRUE;
// Use a short alias for this.
$alias = $def['table'] . '_' . $this->table;
$this->alias = $this->query->add_relationship($alias, $join, $this->definition['base'], $this->relationship);
}
}

View File

@@ -0,0 +1,95 @@
<?php
/**
* @file
* Row style plugin for displaying the results as entities.
*/
/**
* Plugin class for displaying Views results with entity_view.
*/
class entity_views_plugin_row_entity_view extends views_plugin_row {
protected $entity_type, $entities;
public function init(&$view, &$display, $options = NULL) {
parent::init($view, $display, $options);
// Initialize the entity-type used.
$table_data = views_fetch_data($this->view->base_table);
$this->entity_type = $table_data['table']['entity type'];
// Set base table and field information as used by views_plugin_row to
// select the entity id if used with default query class.
$info = entity_get_info($this->entity_type);
if (!empty($info['base table']) && $info['base table'] == $this->view->base_table) {
$this->base_table = $info['base table'];
$this->base_field = $info['entity keys']['id'];
}
}
public function option_definition() {
$options = parent::option_definition();
$options['view_mode'] = array('default' => 'full');
return $options;
}
public function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
$entity_info = entity_get_info($this->entity_type);
$options = array();
if (!empty($entity_info['view modes'])) {
foreach ($entity_info['view modes'] as $mode => $settings) {
$options[$mode] = $settings['label'];
}
}
if (count($options) > 1) {
$form['view_mode'] = array(
'#type' => 'select',
'#options' => $options,
'#title' => t('View mode'),
'#default_value' => $this->options['view_mode'],
);
}
else {
$form['view_mode_info'] = array(
'#type' => 'item',
'#title' => t('View mode'),
'#description' => t('Only one view mode is available for this entity type.'),
'#markup' => $options ? current($options) : t('Default'),
);
$form['view_mode'] = array(
'#type' => 'value',
'#value' => $options ? key($options) : 'default',
);
}
return $form;
}
public function pre_render($values) {
if (!empty($values)) {
list($this->entity_type, $this->entities) = $this->view->query->get_result_entities($values, !empty($this->relationship) ? $this->relationship : NULL, isset($this->field_alias) ? $this->field_alias : NULL);
}
// Render the entities.
if ($this->entities) {
$render = entity_view($this->entity_type, $this->entities, $this->options['view_mode']);
// Remove the first level of the render array.
$this->rendered_content = reset($render);
}
}
/**
* Overridden to return the entity object.
*/
function get_value($values, $field = NULL) {
return isset($this->entities[$this->view->row_index]) ? $this->entities[$this->view->row_index] : FALSE;
}
public function render($values) {
if ($entity = $this->get_value($values)) {
$render = $this->rendered_content[entity_id($this->entity_type, $entity)];
return drupal_render($render);
}
}
}