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,152 @@
/**
* API documentation for the Media module.
*/
The Media module provides a robust API for developers to extend functionality
in useful and novel ways.
CAUTION: This is pretty old!
Architecture
------------
To understand the API, it is first important to understand the underlying
architecture and the decisions behind its current implementation.
Stream Wrappers
---------------
First, the Media module makes heavy use of the core Stream Wrappers now
provided with Drupal 7. Streams are classes that encapsulate PHP file
functions, allowing an automatic override when calling the basic function.
For instance, a wrapper implementing an Amazon S3 Stream might override the
file_get_contents() using ->stream_open(), or a Flickr Stream Wrapper might
implement its own ->getExternalUrl to return the HTML URI for a specific
Flickr image.
See http://api.drupal.org/file/includes/stream_wrappers.inc/7 for more
information about Stream Wrappers in Drupal.
Schemes
-------
All Stream Wrappers will be registered to handle a specific scheme, which is
the part of a URI before ://, such as core Drupal's public:// and private://
schemes. (Technically, a scheme only requires :, but a bug in PHP 5.2
requires ://, so Drupal currently keeps the same requirement.)
The target after the scheme:// will be a unique identifier that the
implementing Stream Wrapper will use to determine the file resource being
accessed. For instance, public://my-document.txt might refer to a local file
stored in the server at /sites/example.com/files/my-document.txt, while a URI
of youtube://fe3fg8u34i might be the video stored at
http://youtube.com/v/fe3fg8u34i, and flickr://photoset/224 might use a lookup
table to determine this is a photoset accessed at
http://flickr.com/user/markam/photoset/325hsd89.
All files in Drupal are stored in the database with this unique URI, in
addition to its unique serial FID. In general, file objects are accessed
and loaded by the URI, which will automatically use the correct stream wrapper
implementation for its scheme.
File Field Widget
-----------------
The Media module extends the core File field to make use of its browser for
editors, which is in turn exposed for other modules to extend. It does this
by create a new widget definition, defining a File (media) 'generic' file
widget, and suppressing the original File widget definition to avoid
confusion. All existing file fields will be converted to this new widget on
installation of the module, and if uninstalled, any defined media_generic file
fields will be reverted to their original definition to avoid loss of data.
Media Formatters
----------------
By default, the File (media) widget will use the original display behavior,
which is to display a generic icon followed by a link to the file. However, in
many (if not most) cases, a site administrator will want to display media in
very specific ways. For instance, they might want an image uploaded to be
displayed as a cropped thumbnail, as with any Flickr images attached to that
field, while a YouTube video might be displayed as a thumbnail that pops up
in a Shadowbox, and finally a PDF might be displayed in an iFrame. And that's
only when it's displayed in a node teaser...
To manage the various display formatting needs of an editor, the Media module
offers a Media Style API for other modules to implement and extend. Media
Styles of various mimetypes are grouped together and made available as new
formatters for field display, whether for a node, a comment, in a view, or
elsewhere.
To implement a new Media Style, a module needs to register itself for a
specific mimetype, with the following hook. Note that Media styles will be
properly namespaced by the module, to avoid conflicts.
function hook_media_styles($mimetype = NULL) {
switch ($mimetype) {
case 'video/x-youtube':
return array(
'youtube_video' => t('YouTube Video'),
'youtube_thumbnail' => t('YouTube Thumbnail'),
'youtube_shadowbox' => t('YouTube Shadowbox'),
'youtube_link' => t('Youtube (link to original)'),
case 'image/x-flickr':
$styles = array(
'flickr__original' => t('Flickr (Original size)'),
);
foreach (image_styles() as $style_name => $style) {
$styles[$style_name] = t('Flickr (@style)', array('@style' => $style->name));
}
return $styles;
}
}
These styles will be available from the Media Styles configuration page, where
multiple formatters can be grouped together to create new formatter Style
bundles, available and intelligently applied to files of the appropriate
mimetypes.
For instance, a new Media Style might be created, named 'Small image', which
will bundle an Image style for the various image mimetypes, another for Flickr
images, a shadowbox for YouTube thumbnails, and a fallback to a size styled
File icon. Then this style would be made available as a display formatter, and
the Media module will apply the correct style for the mimetype of the
particular instance.
A module may also define default Media styles to be made available, by
implementing hook_media_default_style_bundles(). Unlike the individual Media Styles,
Media Style bundles are not namespaced by module. If two or more modules use
the same Style bundle names, the behavior is undefined. Also note that the
administrator may override a specific Style bundle, for instance by replacing
the Style for a specific mimetype.
function hook_media_default_style_bundles() {
return array(
'shadowbox_images' => array(
'label' => t('Shadowbox images'),
'mimetypes' => array(
'image/*' => 'mymodule_image_shadowbox',
'video/*' => 'mymodule_video_shadowbox',
'*' => 'mymodule_default_shadowbox',
),
),
);
}
@TODO
convert existing file fields to media_generic on hook_install
convert existing media_generic to file fields on hook_uninstall
allow theme_file_icon file directory override in widget display settings
(Requires http://drupal.org/node/650732)
create new default file icons and set its folder as the new default
maybe instead/in addition have fallbacks if icon not present?
rename image style square_thumbnail to use the media_ namespace
review Media Styles for feasibility, usability, and architecture
create Media Styles UI .inc file
look at hook_field_info_alter to see if we can affect file widget that way
CLEAN CRUFT (lots of functions that no longer apply or need to be rethought)
why do we want to override the file field widget again?
should we also move 'media_styles' to its own module, perhaps file_styles?
in media_field_formatter_info(),
should 'field types' => array('media_generic', 'file'), be only one or other?

View File

@@ -0,0 +1 @@
This is outdated. For a changelog see the appropriate branch or tag on http://drupalcode.org/project/media.git/heads

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,7 @@
<?php
/**
* @todo Remove once usage statistics indicate that virtually everyone has
* updated to 1.0-beta5 or later. This is retained until then, so that
* update.php can bootstrap.
*/
include_once 'includes/MediaReadOnlyStreamWrapper.inc';

View File

@@ -0,0 +1,7 @@
/**
* @file
* README for the Media Module.
*/
See http://drupal.org/node/356802

View File

@@ -0,0 +1,372 @@
/* @override http://dev7/sites/all/modules/media/css/media.css?L */
/**
* @file
* Styles for the media library.
*/
/* @group media item list */
.item-list .media-display-switch {
float: right;
line-height: 0;
}
.item-list .media-display-switch li {
float: left;
list-style: none;
margin-left: -1px;
}
.item-list .media-display-switch li a {
background: #f2f1f1;
border: 1px solid;
border-color: #e4e4e4 #d2d2d2 #b4b4b4 #d3d3d3;
display: block;
padding: 7px 9px;
}
.item-list .media-display-switch li.first a {
-moz-border-radius-topleft: 4px;
-moz-border-radius-bottomleft: 4px;
-webkit-border-top-left-radius: 4px;
-webkit-border-bottom-left-radius: 4px;
}
.item-list .media-display-switch li.last a {
-moz-border-radius-topright: 4px;
-moz-border-radius-bottomright: 4px;
-webkit-border-top-right-radius: 4px;
-webkit-border-bottom-right-radius: 4px;
}
.item-list .media-display-switch li a.active {
position: relative;
background: #666;
border: 1px solid #555;
color: #fff;
}
/* Set the height to auto */
#media_content_browser .media-thumbnail {
height: auto;
width: 120px;
}
/* Push content under the tabs */
#media_content_browser_tabs {
margin-bottom: 30px;
}
/* This is some stuff to just get some basic mockup done on the
content navigator. Should be removed/revised soon */
.result_limit {
float: right;
}
.result_limit li {
display: inline;
list-style: none;
}
.item-list .media_content_navigator li {
display: inline;
list-style: none;
}
/** This is a massive hack. There must be a better way. See media.fields.inc **/
.media-widget .fid {
display:none;
}
.media-widget a.button + a.button {
margin-left: 15px;
display: none;
}
/* @end */
/* @group media item */
.media-item {
background: #FFF;
border: 1px solid #CCCCCC;
margin: 10px;
padding: 6px 6px 2px;
width: 100px;
}
.media-item.selected {
background: #F4ECC7;
}
.media-item a {
display: block;
}
.media-item img {
border: 2px solid transparent;
display: inline-block;
margin-left: -2px;
height: auto;
width: 100%;
}
.media-item img:hover {
border-color: #058AC5;
}
.media-item-icons img{
height:25px;
width:25px;
float:left;
}
.media-item .label-wrapper {
overflow: hidden;
margin-left: 14px;
}
.media-item .label-wrapper:hover {
border-bottom: 2px solid #CCC;
border-right: 2px solid #CCC;
display: inline-block;
line-height: 16px;
margin-bottom: -1px;
overflow: visible;
position: relative;
z-index: 10;
}
.preview .media-item .label-wrapper {
margin-left: 0;
}
.preview {
display: inline-block;
vertical-align: middle;
}
.media-item .media-filename {
background: #FFF;
color: #058AC5;
font-size: 10px;
padding: 0 3px;
white-space: nowrap;
}
.media-item .label-wrapper:hover .media-filename {
border: 1px solid #888;
display: inline-block;
margin: 0 0 -2px -1px;
}
.media-item.selected .media-filename {
background: #F4ECC7;
}
.media-item .media-filename:hover {
text-decoration: underline;
}
.media-modal-frame {
overflow: hidden;
}
.media-display-thumbnails .media-list-thumbnails {
margin: 0 -10px;
}
.media-list-thumbnails > li {
float: left;
list-style: none;
}
.media-list-thumbnails .form-type-checkbox {
margin: -35px 0 0 16px;
position: absolute;
}
.media-item .media-type-icon {
border: none;
margin: 0 0 -5px;
width: auto;
}
.ui-dialog.media-wrapper .ui-dialog-buttonpane {
display: none;
}
.ui-widget.ui-widget-content.media-wrapper {
background: none;
border: none;
}
/* @end */
/* @group media browser */
body.page-media-browser {
background: none;
overflow: hidden;
}
div#media-browser-tabs {
-moz-border-radius: 0;
-webkit-border-radius: 0;
border-radius: 0;
border: 0;
}
/* show the throbber on the page */
div#media-browser .throbber {
height:100%;
width:100%;
position:absolute;
background: transparent url('images/loading.gif') no-repeat center center;
}
#media-browser .ui-tabs-panel {
/**
* This sucks, I don't want to hardcode this, but don't know how else
* to keep the scrollbar INSIDE the iframe.
*/
height: 410px;
overflow-y: auto;
}
#media-tab-media_internet .media-provider img {
vertical-align: middle;
margin-right: 1em;
}
#media-browser-library-list .label-wrapper {
margin-left: 0;
}
#mediaBrowser body {
background: none;
}
#media-browser-tabset {
background: #FFF;
}
#media-browser-tabset .media-browser-tab {
border: 1px solid #aaa;
}
#media-browser-tabset .ui-tabs-nav {
border-left: 1px solid #aaa;
border-right: 1px solid #aaa;
border-top: 1px solid #aaa;
}
.fake-ok {
margin-right: 5px;
}
a.button {
display: inline-block;
line-height: 21px;
}
/* @end media browser */
/* @group media edit page */
.no-overflow {
overflow: hidden;
}
.media-image-left .field-name-file {
float: left;
margin-bottom: 2em;
margin-right: 2em;
padding-top: 12px;
}
.media-image-left .field-name-file .field-item {
width: 180px;
word-break: break-word;
}
.media-image-left .styles-field-file {
background-color: #FFFFFF;
border: 1px solid #CCCCCC;
padding: 2%;
width: 96%;
}
.media-image-left .field-name-file img {
height: auto;
width: 100%;
}
.media-image-left .form-actions {
clear: both;
}
/* @end media edit page */
/* @group media format form */
#media-format-form {
margin:30px;
}
#media-admin #edit-options {
clear: both;
margin: 0;
}
.media-clear {
clear: both;
}
.media-thumbnails-select {
float: left;
}
#edit-options .form-item-format label {
display: inline;
}
#media-format-form .media-item {
float: left;
margin-left: 0;
margin-top: 0;
}
#media-format-form .label-wrapper {
margin-left: 0;
}
.media-multiedit-form .media-edit-form {
border-bottom: 1px solid #aaa;
margin-bottom:1em;
margin-top:1.5em;
}
body.page-media-format-form {
background: none;
}
#media-browser-page {
background: #FFF;
padding: 1px 0;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
border-radius: 4px;
}
#media-browser-page .plupload_start {
display:none;
}
a.button {
display: inline-block;
margin: 10px 5px 0 0;
}
/* @end media format form */

View File

@@ -0,0 +1,185 @@
<?php
/**
* @file
* Administrative interface for file type configuration.
*/
/**
* Displays the file type admin overview page.
*/
function file_entity_list_types_page() {
$types = file_info_file_types();
$entity_info = entity_get_info('file');
$field_ui = module_exists('field_ui');
$header = array(
array('data' => t('Name')),
array('data' => t('Operations'), 'colspan' => $field_ui ? '3' : '1'),
);
$rows = array();
foreach ($types as $type => $info) {
$row = array(array('data' => theme('file_entity_file_type_overview', $info)));
$path = isset($entity_info['bundles'][$type]['admin']['real path']) ? $entity_info['bundles'][$type]['admin']['real path'] : NULL;
if ($field_ui) {
$row[] = array('data' => isset($path) ? l(t('manage fields'), $path . '/fields') : '');
$row[] = array('data' => isset($path) ? l(t('manage display'), $path . '/display') : '');
}
$row[] = array('data' => isset($path) ? l(t('manage file display'), $path . '/file-display') : '');
$rows[] = $row;
}
$build['file_type_table'] = array(
'#theme' => 'table',
'#header' => $header,
'#rows' => $rows,
'#empty' => t('No file types available.'),
);
return $build;
}
/**
* Form callback; presents file display settings for a given view mode.
*/
function file_entity_file_display_form($form, &$form_state, $bundle, $view_mode) {
$file_type = field_extract_bundle('file', $bundle);
$form['#file_type'] = $file_type;
$form['#view_mode'] = $view_mode;
$form['#tree'] = TRUE;
$form['#attached']['js'][] = drupal_get_path('module', 'file_entity') . '/file_entity.admin.js';
// Retrieve available formatters for this file type and load all configured
// filters for existing text formats.
$formatters = file_info_formatter_types();
foreach ($formatters as $name => $formatter) {
if (isset($formatter['file types']) && !in_array($file_type, $formatter['file types'])) {
unset ($formatters[$name]);
}
}
$current_displays = file_displays_load($file_type, $view_mode, TRUE);
foreach ($current_displays as $name => $display) {
$current_displays[$name] = (array) $display;
}
// Formatter status.
$form['displays']['status'] = array(
'#type' => 'item',
'#title' => t('Enabled displays'),
'#prefix' => '<div id="file-displays-status-wrapper">',
'#suffix' => '</div>',
);
$i=0;
foreach ($formatters as $name => $formatter) {
$form['displays']['status'][$name] = array(
'#type' => 'checkbox',
'#title' => $formatter['label'],
'#default_value' => !empty($current_displays[$name]['status']),
'#description' => isset($formatter['description']) ? $formatter['description'] : NULL,
'#parents' => array('displays', $name, 'status'),
'#weight' => $formatter['weight'] + $i/1000,
);
$i++;
}
// Formatter order (tabledrag).
$form['displays']['order'] = array(
'#type' => 'item',
'#title' => t('Display precedence order'),
'#theme' => 'file_entity_file_display_order',
);
foreach ($formatters as $name => $formatter) {
$form['displays']['order'][$name]['label'] = array(
'#markup' => check_plain($formatter['label']),
);
$form['displays']['order'][$name]['weight'] = array(
'#type' => 'weight',
'#title' => t('Weight for @title', array('@title' => $formatter['label'])),
'#title_display' => 'invisible',
'#delta' => 50,
'#default_value' => isset($current_displays[$name]['weight']) ? $current_displays[$name]['weight'] : 0,
'#parents' => array('displays', $name, 'weight'),
);
$form['displays']['order'][$name]['#weight'] = $form['displays']['order'][$name]['weight']['#default_value'];
}
// Formatter settings.
$form['display_settings_title'] = array(
'#type' => 'item',
'#title' => t('Display settings'),
);
$form['display_settings'] = array(
'#type' => 'vertical_tabs',
);
$i=0;
foreach ($formatters as $name => $formatter) {
if (isset($formatter['settings callback']) && ($function = $formatter['settings callback']) && function_exists($function)) {
$defaults = !empty($formatter['default settings']) ? $formatter['default settings'] : array();
$settings = !empty($current_displays[$name]['settings']) ? $current_displays[$name]['settings'] : array();
$settings += $defaults;
$settings_form = $function($form, $form_state, $settings, $name, $file_type, $view_mode);
if (!empty($settings_form)) {
$form['displays']['settings'][$name] = array(
'#type' => 'fieldset',
'#title' => $formatter['label'],
'#parents' => array('displays', $name, 'settings'),
'#group' => 'display_settings',
'#weight' => $formatter['weight'] + $i/1000,
) + $settings_form;
}
}
$i++;
}
$form['actions'] = array('#type' => 'actions');
$form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Save configuration'));
return $form;
}
/**
* Process file display settings form submissions.
*/
function file_entity_file_display_form_submit($form, &$form_state) {
$file_type = $form['#file_type'];
$view_mode = $form['#view_mode'];
$displays = isset($form_state['values']['displays']) ? $form_state['values']['displays'] : array();
$displays_original = file_displays_load($file_type, $view_mode, TRUE);
foreach ($displays as $formatter_name => $display) {
$display_original = isset($displays_original[$formatter_name]) ? $displays_original[$formatter_name] : file_display_new($file_type, $view_mode, $formatter_name);
$display += (array) $display_original;
file_display_save((object) $display);
}
drupal_set_message(t('Your settings have been saved.'));
}
/**
* Returns HTML for a file type label and description for the file type admin overview page.
*/
function theme_file_entity_file_type_overview($variables) {
return check_plain($variables['label']) . '<div class="description">' . $variables['description'] . '</div>';
}
/**
* Returns HTML for a file display's display order table.
*/
function theme_file_entity_file_display_order($variables) {
$element = $variables['element'];
$rows = array();
foreach (element_children($element, TRUE) as $name) {
$element[$name]['weight']['#attributes']['class'][] = 'file-display-order-weight';
$rows[] = array(
'data' => array(
drupal_render($element[$name]['label']),
drupal_render($element[$name]['weight']),
),
'class' => array('draggable'),
);
}
$output = drupal_render_children($element);
$output .= theme('table', array('rows' => $rows, 'attributes' => array('id' => 'file-displays-order')));
drupal_add_tabledrag('file-displays-order', 'order', 'sibling', 'file-display-order-weight', NULL, NULL, TRUE);
return $output;
}

View File

@@ -0,0 +1,45 @@
(function ($) {
Drupal.behaviors.fileDisplayStatus = {
attach: function (context, settings) {
$('#file-displays-status-wrapper input.form-checkbox', context).once('display-status', function () {
var $checkbox = $(this);
// Retrieve the tabledrag row belonging to this display.
var $row = $('#' + $checkbox.attr('id').replace(/-status$/, '-weight'), context).closest('tr');
// Retrieve the vertical tab belonging to this display.
var tab = $('#' + $checkbox.attr('id').replace(/-status$/, '-settings'), context).data('verticalTab');
// Bind click handler to this checkbox to conditionally show and hide the
// display's tableDrag row and vertical tab pane.
$checkbox.bind('click.displayStatusUpdate', function () {
if ($checkbox.is(':checked')) {
$row.show();
if (tab) {
tab.tabShow().updateSummary();
}
}
else {
$row.hide();
if (tab) {
tab.tabHide().updateSummary();
}
}
// Restripe table after toggling visibility of table row.
Drupal.tableDrag['file-displays-order'].restripeTable();
});
// Attach summary for configurable displays (only for screen-readers).
if (tab) {
tab.fieldset.drupalSetSummary(function (tabContext) {
return $checkbox.is(':checked') ? Drupal.t('Enabled') : Drupal.t('Disabled');
});
}
// Trigger our bound click handler to update elements to initial state.
$checkbox.triggerHandler('click.displayStatusUpdate');
});
}
};
})(jQuery);

View File

@@ -0,0 +1,140 @@
<?php
/**
* @file
* Hooks provided by the File Entity module.
*/
/**
* Define file types.
*
* @return
* An array whose keys are file type names and whose values are arrays
* describing the file type, with the following key/value pairs:
* - label: The human-readable name of the file type.
* - claim callback: The name of the function that returns if a given file is
* of this type. See hook_file_type_TYPE_claim() for details.
* - default view callback: (optional) The name of the function that returns a
* drupal_render() array for displaying the file. Used when there are no
* administrator configured file formatters, or none of the configured ones
* return a display. See hook_file_type_TYPE_default_view() for details.
* - description: (optional) A short description of the file type.
* - weight: (optional) A number defining the order in which the 'claim
* callback' function for this type is called relative to the claim
* callbacks of other defined types, when the type of a file needs to be
* determined. The type with the lowest weighted claim callback to return
* TRUE is assigned to the file. Also, on administrative pages listing file
* types, the types are ordered by weight.
* - admin: (optional) An array of information, to be added to the
* ['bundles'][TYPE]['admin'] entry for the 'file' entity type, thereby
* controlling the path at which Field UI pages are attached for this file
* type, and which users may access them. Defaults to attaching the Field UI
* pages to the admin/config/media/file-types/manage/TYPE path and requiring
* 'administer site configuration' permission. See hook_entity_info() for
* details about this array. This value can also be set to NULL to suppress
* Field UI pages from attaching at all for this file type.
*
* @see hook_file_type_info_alter()
*/
function hook_file_type_info() {
return array(
'image' => array(
'label' => t('Image'),
),
);
}
/**
* Perform alterations on file types.
*
* @param $info
* Array of information on file types exposed by hook_file_type_info()
* implementations.
*/
function hook_file_type_info_alter(&$info) {
// @todo Add example.
}
/**
* @todo Add documentation.
*
* Note: This is not really a hook. The function name is manually specified via
* 'claim callback' in hook_file_type_info(), with this recommended
* callback name pattern.
*/
function hook_file_type_TYPE_claim($file, $type) {
}
/**
* @todo Add documentation.
*
* Note: This is not really a hook. The function name is manually specified via
* 'default view callback' in hook_file_type_info(), with this recommended
* callback name pattern.
*/
function hook_file_type_TYPE_default_view($file, $view_mode, $langcode) {
}
/**
* Define file formatters.
*
* @return
* An array whose keys are file formatter names and whose values are arrays
* describing the formatter.
*
* @todo Document key/value pairs that comprise a formatter.
*
* @see hook_file_formatter_info_alter()
*/
function hook_file_formatter_info() {
// @todo Add example.
}
/**
* Perform alterations on file formatters.
*
* @param $info
* Array of information on file formatters exposed by
* hook_file_formatter_info() implementations.
*/
function hook_file_formatter_info_alter(&$info) {
// @todo Add example.
}
/**
* @todo Add documentation.
*
* Note: This is not really a hook. The function name is manually specified via
* 'view callback' in hook_file_formatter_info(), with this recommended callback
* name pattern.
*/
function hook_file_formatter_FORMATTER_view($file, $display, $langcode) {
}
/**
* @todo Add documentation.
*
* Note: This is not really a hook. The function name is manually specified via
* 'settings callback' in hook_file_formatter_info(), with this recommended
* callback name pattern.
*/
function hook_file_formatter_FORMATTER_settings($form, &$form_state, $settings) {
}
/**
* @todo Add documentation.
*/
function hook_file_displays_alter($displays, $file, $view_mode) {
}
/**
* @todo Add documentation.
*/
function hook_file_view($file, $view_mode, $langcode) {
}
/**
* @todo Add documentation.
*/
function hook_file_view_alter($build, $type) {
}

View File

@@ -0,0 +1,391 @@
<?php
/**
* @file
* API extensions of Drupal core's file.inc.
*/
/**
* The {file_managed}.type value when the file type has not yet been determined.
*/
define('FILE_TYPE_NONE', 'undefined');
/**
* Returns information about file types from hook_file_type_info().
*
* @param $file_type
* (optional) A file type name. If ommitted, all file types will be returned.
*
* @return
* Either a file type description, as provided by hook_file_type_info(), or an
* array of all existing file types, keyed by file type name.
*/
function file_info_file_types($file_type = NULL) {
$info = &drupal_static(__FUNCTION__);
if (!isset($info)) {
$info = module_invoke_all('file_type_info');
drupal_alter('file_type_info', $info);
_file_sort_array_by_weight($info);
}
if (isset($file_type)) {
if (isset($info[$file_type])) {
return $info[$file_type];
}
}
else {
return $info;
}
}
/**
* Returns an object with file type information, so that %file_type can be used in hook_menu() paths.
*
* In addition to the information returned by file_info_file_types(), the 'type'
* property is set for use by field_extract_bundle().
*/
function file_type_load($type) {
$info = file_info_file_types($type);
if (isset($info)) {
$info = (object) $info;
$info->type = $type;
return $info;
}
else {
return FALSE;
}
}
/**
* Determines the file type of a passed in file object.
*/
function file_get_type($file) {
foreach (file_info_file_types() as $type => $info) {
if (isset($info['claim callback']) && ($function = $info['claim callback']) && function_exists($function) && $function($file, $type)) {
return $type;
}
}
}
/**
* Returns information about file formatters from hook_file_formatter_info().
*
* @param $formatter_type
* (optional) A file formatter type name. If ommitted, all file formatter
* will be returned.
*
* @return
* Either a file formatter description, as provided by
* hook_file_formatter_info(), or an array of all existing file formatters,
* keyed by formatter type name.
*/
function file_info_formatter_types($formatter_type = NULL) {
$info = &drupal_static(__FUNCTION__);
if (!isset($info)) {
$info = module_invoke_all('file_formatter_info');
drupal_alter('file_formatter_info', $info);
_file_sort_array_by_weight($info);
}
if ($formatter_type) {
if (isset($info[$formatter_type])) {
return $info[$formatter_type];
}
}
else {
return $info;
}
}
/**
* Clears the file info cache.
*/
function file_info_cache_clear() {
drupal_static_reset('file_info_file_types');
drupal_static_reset('file_info_formatter_types');
}
/**
* Construct a drupal_render() style array from an array of loaded files.
*
* @param $files
* An array of files as returned by file_load_multiple().
* @param $view_mode
* View mode.
* @param $weight
* An integer representing the weight of the first file in the list.
* @param $langcode
* A string indicating the language field values are to be shown in. If no
* language is provided the current content language is used.
*
* @return
* An array in the format expected by drupal_render().
*/
function file_view_multiple($files, $view_mode = 'default', $weight = 0, $langcode = NULL) {
if (empty($files)) {
return array();
}
field_attach_prepare_view('file', $files, $view_mode);
entity_prepare_view('file', $files);
$build = array();
foreach ($files as $file) {
$build[$file->fid] = file_view($file, $view_mode, $langcode);
$build[$file->fid]['#weight'] = $weight;
$weight++;
}
$build['#sorted'] = TRUE;
return $build;
}
/**
* Generate an array for rendering the given file.
*
* @param $file
* A file object.
* @param $view_mode
* View mode.
* @param $langcode
* (optional) A language code to use for rendering. Defaults to the global
* content language of the current request.
*
* @return
* An array as expected by drupal_render().
*/
function file_view($file, $view_mode = 'default', $langcode = NULL) {
if (!isset($langcode)) {
$langcode = $GLOBALS['language_content']->language;
}
// Prepare the file object for viewing, in case file_view() was called by
// something other than file_view_multiple(). These functions exit quickly if
// they've already run, so it's okay to call them even if they've already been
// called by file_view_multiple().
field_attach_prepare_view('file', array($file->fid => $file), $view_mode);
entity_prepare_view('file', array($file->fid => $file));
// Create the render array with the file itself and with fields.
$build = array(
'#file' => $file,
'#view_mode' => $view_mode,
'#language' => $langcode,
);
$build += field_attach_view('file', $file, $view_mode, $langcode);
$build['file'] = file_view_file($file, $view_mode, $langcode);
// Allow modules to add and alter.
module_invoke_all('file_view', $file, $view_mode, $langcode);
module_invoke_all('entity_view', $file, 'file', $view_mode, $langcode);
$type = 'file';
drupal_alter(array('file_view', 'entity_view'), $build, $type);
return $build;
}
/**
* Generate an array for rendering just the file portion of a file entity.
*
* @param $file
* A file object.
* @param $displays
* Can be either:
* - the name of a view mode;
* - or an array of custom display settings, as returned by file_displays().
* @param $langcode
* (optional) A language code to use for rendering. Defaults to the global
* content language of the current request.
*
* @return
* An array as expected by drupal_render().
*/
function file_view_file($file, $displays = 'default', $langcode = NULL) {
if (!isset($langcode)) {
$langcode = $GLOBALS['language_content']->language;
}
// Prepare incoming display specifications.
if (is_string($displays)) {
$view_mode = $displays;
$displays = file_displays($file->type, $view_mode);
}
else {
$view_mode = '_custom_display';
}
drupal_alter('file_displays', $displays, $file, $view_mode);
_file_sort_array_by_weight($displays);
// Attempt to display the file with each of the possible displays. Stop after
// the first successful one. See file_displays() for details.
$element = NULL;
foreach ($displays as $formatter_type => $display) {
if (!empty($display['status'])) {
$formatter_info = file_info_formatter_types($formatter_type);
// Under normal circumstances, the UI prevents enabling formatters for
// incompatible file types. In case this was somehow circumvented (for
// example, a module updated its formatter definition without updating
// existing display settings), perform an extra check here.
if (isset($formatter_info['file types']) && !in_array($file->type, $formatter_info['file types'])) {
continue;
}
if (isset($formatter_info['view callback']) && ($function = $formatter_info['view callback']) && function_exists($function)) {
$display['type'] = $formatter_type;
if (!empty($formatter_info['default settings'])) {
if (empty($display['settings'])) {
$display['settings'] = array();
}
$display['settings'] += $formatter_info['default settings'];
}
$element = $function($file, $display, $langcode);
if (isset($element)) {
break;
}
}
}
}
// If none of the configured formatters were able to display the file, attempt
// to display the file using the file type's default view callback.
if (!isset($element)) {
$file_type_info = file_info_file_types($file->type);
if (isset($file_type_info['default view callback']) && ($function = $file_type_info['default view callback']) && function_exists($function)) {
$element = $function($file, $view_mode, $langcode);
}
}
// If a render element was returned by a formatter or the file type's default
// view callback, add some defaults to it and return it.
if (isset($element)) {
$element += array(
'#file' => $file,
'#view_mode' => $view_mode,
'#language' => $langcode,
);
return $element;
}
}
/**
* Returns an array of possible displays to use for a file type in a given view mode.
*
* It is common for a site to be configured with broadly defined file types
* (e.g., 'video'), and to have different files of this type require different
* displays (for example, the code required to display a YouTube video is
* different than the code required to display a local QuickTime video).
* Therefore, the site administrator can configure multiple displays for a given
* file type. This function returns all of the displays that the administrator
* enabled for the given file type in the given view mode. file_view_file() then
* invokes each of these, and passes the specific file to display. Each display
* implementation can inspect the file, and either return a render array (if it
* is capable of displaying the file), or return nothing (if it is incapable of
* displaying the file). The first render array returned is the one used.
*
* @param $file_type
* The type of file.
* @param $view_mode
* The view mode.
*
* @return
* An array keyed by the formatter type name. Each item in the array contains
* the following key/value pairs:
* - status: Whether this display is enabled. If not TRUE, file_view_file()
* skips over it.
* - weight: An integer that determines the order of precedence within the
* returned array. The lowest weight display capable of displaying the file
* is used.
* - settings: An array of key/value pairs specific to the formatter type. See
* hook_file_formatter_info() for details.
*
* @see hook_file_formatter_info()
* @see file_view_file()
*/
function file_displays($file_type, $view_mode = 'default') {
$cache = &drupal_static(__FUNCTION__, array());
// If the requested view mode isn't configured to use a custom display for its
// fields, then don't use a custom display for its file either.
if ($view_mode != 'default') {
$view_mode_settings = field_view_mode_settings('file', $file_type);
$view_mode = !empty($view_mode_settings[$view_mode]['custom_settings']) ? $view_mode : 'default';
}
if (!isset($cache[$file_type][$view_mode])) {
// Load the display configurations for the file type and view mode. If none
// exist for the view mode, use the default view mode.
$displays = file_displays_load($file_type, $view_mode, TRUE);
if (empty($displays) && $view_mode != 'default') {
$cache[$file_type][$view_mode] = file_displays($file_type, 'default');
}
else {
// Convert the display objects to arrays and remove unnecessary keys.
foreach ($displays as $formatter_name => $display) {
$displays[$formatter_name] = array_intersect_key((array) $display, drupal_map_assoc(array('status', 'weight', 'settings')));
}
$cache[$file_type][$view_mode] = $displays;
}
}
return $cache[$file_type][$view_mode];
}
/**
* Returns an array of {file_display} objects for the file type and view mode.
*/
function file_displays_load($file_type, $view_mode, $key_by_formatter_name = FALSE) {
ctools_include('export');
$display_names = array();
$prefix = $file_type . '__' . $view_mode . '__';
foreach (array_keys(file_info_formatter_types()) as $formatter_name) {
$display_names[] = $prefix . $formatter_name;
}
$displays = ctools_export_load_object('file_display', 'names', $display_names);
if ($key_by_formatter_name) {
$prefix_length = strlen($prefix);
$rekeyed_displays = array();
foreach ($displays as $name => $display) {
$rekeyed_displays[substr($name, $prefix_length)] = $display;
}
$displays = $rekeyed_displays;
}
return $displays;
}
/**
* Saves a {file_display} object to the database.
*/
function file_display_save($display) {
ctools_include('export');
ctools_export_crud_save('file_display', $display);
}
/**
* Creates a new {file_display} object.
*/
function file_display_new($file_type, $view_mode, $formatter_name) {
ctools_include('export');
$display = ctools_export_crud_new('file_display');
$display->name = implode('__', array($file_type, $view_mode, $formatter_name));
return $display;
}
/**
* Helper function to sort an array by the value of each item's 'weight' key, while preserving relative order of items that have equal weight.
*/
function _file_sort_array_by_weight(&$a) {
$i=0;
foreach ($a as $key => $item) {
if (!isset($a[$key]['weight'])) {
$a[$key]['weight'] = 0;
}
$original_weight[$key] = $a[$key]['weight'];
$a[$key]['weight'] += $i/1000;
$i++;
}
uasort($a, 'drupal_sort_weight');
foreach ($a as $key => $item) {
$a[$key]['weight'] = $original_weight[$key];
}
}

View File

@@ -0,0 +1,13 @@
name = File entity
description = "Extends Drupal file entities to be fieldable and viewable."
package = Media
core = 7.x
dependencies[] = field
dependencies[] = ctools
; Information added by drupal.org packaging script on 2012-03-23
version = "7.x-1.0"
core = "7.x"
project = "media"
datestamp = "1332537952"

View File

@@ -0,0 +1,207 @@
<?php
/**
* @file
* Install, update and uninstall functions for the file_entity module.
*/
/**
* Implements hook_schema().
*/
function file_entity_schema() {
$schema['file_display'] = array(
'description' => 'Stores configuration options for file displays.',
'fields' => array(
// @todo Can be refactored as a compond primary key after
// http://drupal.org/node/924236 is implemented.
'name' => array(
'description' => 'A combined string (FILE_TYPE__VIEW_MODE__FILE_FORMATTER) identifying a file display configuration. For integration with CTools Exportables, stored as a single string rather than as a compound primary key.',
'type' => 'varchar',
'length' => '255',
'not null' => TRUE,
),
'weight' => array(
'type' => 'int',
'not null' => TRUE,
'default' => 0,
'description' => 'Weight of formatter within the display chain for the associated file type and view mode. A file is rendered using the lowest weighted enabled display configuration that matches the file type and view mode and that is capable of displaying the file.',
),
'status' => array(
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
'size' => 'tiny',
'description' => 'The status of the display. (1 = enabled, 0 = disabled)',
),
'settings' => array(
'type' => 'blob',
'not null' => FALSE,
'size' => 'big',
'serialize' => TRUE,
'description' => 'A serialized array of name value pairs that store the formatter settings for the display.',
),
),
'primary key' => array('name'),
// Exportable support via CTools.
'export' => array(
'key' => 'name',
'key name' => 'Name',
'primary key' => 'name',
// The {file_display}.status field is used to control whether the display
// is active in the display chain. CTools-level disabling is something
// different, and it's not yet clear how to interpret it for file displays.
// Until that's figured out, prevent CTools-level disabling.
'can disable' => FALSE,
'default hook' => 'file_default_displays',
'identifier' => 'file_display',
'api' => array(
'owner' => 'file_entity',
'api' => 'file_default_displays',
'minimum_version' => 1,
'current_version' => 1,
),
),
);
return $schema;
}
/**
* Implements hook_schema_alter().
*/
function file_entity_schema_alter(&$schema) {
$schema['file_managed']['fields']['type'] = array(
'description' => 'The type of this file.',
'type' => 'varchar',
'length' => 50,
'not null' => TRUE,
// If the FILE_TYPE_NONE constant ever changes, then change the value here
// too, and add an update function to deal with existing records. The
// constant isn't used here, because there may be cases where this function
// runs without the module file loaded.
'default' => 'undefined',
);
$schema['file_managed']['indexes']['file_type'] = array('type');
}
/**
* Implements hook_install().
*/
function file_entity_install() {
$schema = array();
file_entity_schema_alter($schema);
$spec = $schema['file_managed']['fields']['type'];
$indexes_new = array('indexes' => $schema['file_managed']['indexes']);
// If another module (e.g., Media) had added a {file_managed}.type field,
// then change it to the expected specification. Otherwise, add the field.
if (db_field_exists('file_managed', 'type')) {
// db_change_field() will fail if any records have type=NULL, so update
// them to the new default value.
db_update('file_managed')->fields(array('type' => $spec['default']))->isNull('type')->execute();
// Indexes using a field being changed must be dropped prior to calling
// db_change_field(). However, the database API doesn't provide a way to do
// this without knowing what the old indexes are. Therefore, it is the
// responsibility of the module that added them to drop them prior to
// allowing this module to be installed.
db_change_field('file_managed', 'type', 'type', $spec, $indexes_new);
}
else {
db_add_field('file_managed', 'type', $spec, $indexes_new);
}
}
/**
* Implements hook_uninstall().
*/
function file_entity_uninstall() {
db_drop_field('file_managed', 'type');
}
/**
* Create the {file_display} database table.
*/
function file_entity_update_7000() {
$schema['file_display'] = array(
'description' => 'Stores configuration options for file displays.',
'fields' => array(
'name' => array(
'description' => 'A combined string (FILE_TYPE__VIEW_MODE__FILE_FORMATTER) identifying a file display configuration. For integration with CTools Exportables, stored as a single string rather than as a compound primary key.',
'type' => 'varchar',
'length' => '255',
'not null' => TRUE,
),
'weight' => array(
'type' => 'int',
'not null' => TRUE,
'default' => 0,
'description' => 'Weight of formatter within the display chain for the associated file type and view mode. A file is rendered using the lowest weighted enabled display configuration that matches the file type and view mode and that is capable of displaying the file.',
),
'status' => array(
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
'size' => 'tiny',
'description' => 'The status of the display. (1 = enabled, 0 = disabled)',
),
'settings' => array(
'type' => 'blob',
'not null' => FALSE,
'size' => 'big',
'serialize' => TRUE,
'description' => 'A serialized array of name value pairs that store the formatter settings for the display.',
),
),
'primary key' => array('name'),
);
db_create_table('file_display', $schema['file_display']);
}
/**
* Move file display configurations from the 'file_displays' variable to the
* {file_display} database table.
*/
function file_entity_update_7001() {
$file_displays = variable_get('file_displays');
if (!empty($file_displays)) {
foreach ($file_displays as $file_type => $file_type_displays) {
if (!empty($file_type_displays)) {
foreach ($file_type_displays as $view_mode => $view_mode_displays) {
if (!empty($view_mode_displays)) {
foreach ($view_mode_displays as $formatter_name => $display) {
if (!empty($display)) {
db_merge('file_display')
->key(array(
'name' => implode('__', array($file_type, $view_mode, $formatter_name)),
))
->fields(array(
'status' => isset($display['status']) ? $display['status'] : 0,
'weight' => isset($display['weight']) ? $display['weight'] : 0,
'settings' => isset($display['settings']) ? serialize($display['settings']) : NULL,
))
->execute();
}
}
}
}
}
}
}
variable_del('file_displays');
}
/**
* Drupal 7.8 disallows empty string as the value for a bundle key, so update
* empty {file_managed}.type records to 'undefined' instead.
*/
function file_entity_update_7002() {
db_update('file_managed')
// Using 'undefined' instead of FILE_TYPE_NONE, because update functions can
// run for disabled modules.
->fields(array('type' => 'undefined'))
->condition('type', '')
->execute();
}

View File

@@ -0,0 +1,467 @@
<?php
/**
* @file
* Extends Drupal file entities to be fieldable and viewable.
*/
/**
* As part of extending Drupal core's file entity API, this module adds some
* functions to the 'file' namespace. For organization, those are kept in the
* 'file_entity.file_api.inc' file.
*/
require_once dirname(__FILE__) . '/file_entity.file_api.inc';
/**
* Implements hook_help().
*/
function file_entity_help($path, $arg) {
switch ($path) {
case 'admin/config/media/file-types':
$output = '<p>' . t('When a file is uploaded to this website, it is assigned one of the following types, based on what kind of file it is.') . '</p>';
return $output;
}
}
/**
* Implements hook_menu().
*/
function file_entity_menu() {
$items['admin/config/media/file-types'] = array(
'title' => 'File types',
'description' => 'Manage files used on your site.',
'page callback' => 'file_entity_list_types_page',
'access arguments' => array('administer site configuration'),
'file' => 'file_entity.admin.inc',
);
$items['admin/config/media/file-types/manage/%'] = array(
'title' => 'Manage file types',
'description' => 'Manage files used on your site.',
);
// Attach a "Manage file display" tab to each file type in the same way that
// Field UI attaches "Manage fields" and "Manage display" tabs. Note that
// Field UI does not have to be enabled; we're just using the same IA pattern
// here for attaching the "Manage file display" page.
$entity_info = entity_get_info('file');
foreach ($entity_info['bundles'] as $file_type => $bundle_info) {
if (isset($bundle_info['admin'])) {
// Get the base path and access.
$path = $bundle_info['admin']['path'];
$access = array_intersect_key($bundle_info['admin'], drupal_map_assoc(array('access callback', 'access arguments')));
$access += array(
'access callback' => 'user_access',
'access arguments' => array('administer site configuration'),
);
// The file type must be passed to the page callbacks. It might be
// configured as a wildcard (multiple file types sharing the same menu
// router path).
$file_type_argument = isset($bundle_info['admin']['bundle argument']) ? $bundle_info['admin']['bundle argument'] : $file_type;
// Add the 'Manage file display' tab.
$items["$path/file-display"] = array(
'title' => 'Manage file display',
'page callback' => 'drupal_get_form',
'page arguments' => array('file_entity_file_display_form', $file_type_argument, 'default'),
'type' => MENU_LOCAL_TASK,
'weight' => 3,
'file' => 'file_entity.admin.inc',
) + $access;
// Add a secondary tab for each view mode.
$weight = 0;
$view_modes = array('default' => array('label' => t('Default'))) + $entity_info['view modes'];
foreach ($view_modes as $view_mode => $view_mode_info) {
$items["$path/file-display/$view_mode"] = array(
'title' => $view_mode_info['label'],
'page arguments' => array('file_entity_file_display_form', $file_type_argument, $view_mode),
'type' => ($view_mode == 'default' ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK),
'weight' => ($view_mode == 'default' ? -10 : $weight++),
'file' => 'file_entity.admin.inc',
// View modes for which the 'custom settings' flag isn't TRUE are
// disabled via this access callback. This needs to extend, rather
// than override normal $access rules.
'access callback' => '_file_entity_view_mode_menu_access',
'access arguments' => array_merge(array($file_type_argument, $view_mode, $access['access callback']), $access['access arguments']),
);
}
}
}
return $items;
}
/**
* Implements hook_theme().
*/
function file_entity_theme() {
return array(
'file_entity_file_type_overview' => array(
'variables' => array('label' => NULL, 'description' => NULL),
'file' => 'file_entity.admin.inc',
),
'file_entity_file_display_order' => array(
'render element' => 'element',
'file' => 'file_entity.admin.inc',
),
);
}
/**
* Implements hook_entity_info_alter().
*
* Extends the core file entity to be fieldable. Modules can define file types
* via hook_file_type_info(). For each defined type, create a bundle, so that
* fields can be configured per file type.
*/
function file_entity_entity_info_alter(&$entity_info) {
$entity_info['file']['fieldable'] = TRUE;
$entity_info['file']['entity keys']['bundle'] = 'type';
$entity_info['file']['bundle keys']['bundle'] = 'type';
$entity_info['file']['bundles'] = array();
foreach (file_info_file_types() as $type => $info) {
$info += array(
// Provide a default administration path for Field UI, but not if 'admin'
// has been explicitly set to NULL.
'admin' => array(
'path' => 'admin/config/media/file-types/manage/%file_type',
'real path' => 'admin/config/media/file-types/manage/' . $type,
'bundle argument' => 5,
),
);
$entity_info['file']['bundles'][$type] = array_intersect_key($info, drupal_map_assoc(array('label', 'admin')));
}
}
/**
* Implements hook_field_extra_fields().
*
* Adds 'file' as an extra field, so that its display and form component can be
* weighted relative to the fields that are added to file entity bundles.
*/
function file_entity_field_extra_fields() {
$return = array();
$info = entity_get_info('file');
foreach (array_keys($info['bundles']) as $bundle) {
$return['file'][$bundle] = array(
'form' => array(
'file' => array(
'label' => t('File'),
'description' => t('File preview'),
'weight' => 0,
),
),
'display' => array(
'file' => array(
'label' => t('File'),
'description' => t('File display'),
'weight' => 0,
),
),
);
}
return $return;
}
/**
* Implements hook_file_presave().
*/
function file_entity_file_presave($file) {
// The file type is a bundle key, so can't be NULL. file_entity_schema_alter()
// ensures that it isn't NULL after a file_load(). However, file_save() can be
// called on a new file object, so we apply the default here as well.
if (!isset($file->type)) {
$file->type = FILE_TYPE_NONE;
}
// If the file doesn't already have a real type, attempt to assign it one.
if ($file->type == FILE_TYPE_NONE && ($type = file_get_type($file))) {
$file->type = $type;
}
field_attach_presave('file', $file);
}
/**
* Implements hook_file_insert().
*/
function file_entity_file_insert($file) {
field_attach_insert('file', $file);
}
/**
* Implement hook_file_update().
*/
function file_entity_file_update($file) {
field_attach_update('file', $file);
}
/**
* Implements hook_file_delete().
*/
function file_entity_file_delete($file) {
field_attach_delete('file', $file);
}
/**
* Implements hook_file_formatter_info().
*/
function file_entity_file_formatter_info() {
$formatters = array();
// Allow file field formatters to be reused for displaying the file entity's
// file pseudo-field.
if (module_exists('file')) {
foreach (field_info_formatter_types() as $field_formatter_type => $field_formatter_info) {
if (in_array('file', $field_formatter_info['field types'])) {
$formatters['file_field_' . $field_formatter_type] = array(
'label' => $field_formatter_info['label'],
'view callback' => 'file_entity_file_formatter_file_field_view',
);
if (isset($field_formatter_info['settings'])) {
$formatters['file_field_' . $field_formatter_type] += array(
'default settings' => $field_formatter_info['settings'],
'settings callback' => 'file_entity_file_formatter_file_field_settings',
);
}
}
}
}
// Add a simple file formatter for displaying an image in a chosen style.
if (module_exists('image')) {
$formatters['file_image'] = array(
'label' => t('Image'),
'default settings' => array('image_style' => ''),
'view callback' => 'file_entity_file_formatter_file_image_view',
'settings callback' => 'file_entity_file_formatter_file_image_settings',
);
}
return $formatters;
}
/**
* Implements hook_file_formatter_FORMATTER_view().
*
* This function provides a bridge to the field formatter API, so that file
* field formatters can be reused for displaying the file entity's file
* pseudo-field.
*/
function file_entity_file_formatter_file_field_view($file, $display, $langcode) {
if (strpos($display['type'], 'file_field_') === 0) {
$field_formatter_type = substr($display['type'], strlen('file_field_'));
$field_formatter_info = field_info_formatter_types($field_formatter_type);
if (isset($field_formatter_info['module'])) {
// Set $display['type'] to what hook_field_formatter_*() expects.
$display['type'] = $field_formatter_type;
// Set $items to what file field formatters expect. See file_field_load(),
// and note that, here, $file is already a fully loaded entity.
$items = array((array) $file);
// Invoke hook_field_formatter_prepare_view() and
// hook_field_formatter_view(). Note that we are reusing field formatter
// functions, but we are not displaying a Field API field, so we set
// $field and $instance accordingly, and do not invoke
// hook_field_prepare_view(). This assumes that the formatter functions do
// not rely on $field or $instance. A module that implements formatter
// functions that rely on $field or $instance (and therefore, can only be
// used for real fields) can prevent this formatter from being used on the
// pseudo-field by removing it within hook_file_formatter_info_alter().
$field = $instance = NULL;
if (($function = ($field_formatter_info['module'] . '_field_formatter_prepare_view')) && function_exists($function)) {
$fid = $file->fid;
// hook_field_formatter_prepare_view() alters $items by reference.
$grouped_items = array($fid => &$items);
$function('file', array($fid => $file), $field, array($fid => $instance), $langcode, $grouped_items, array($fid => $display));
}
if (($function = ($field_formatter_info['module'] . '_field_formatter_view')) && function_exists($function)) {
$element = $function('file', $file, $field, $instance, $langcode, $items, $display);
// We passed the file as $items[0], so return the corresponding element.
if (isset($element[0])) {
return $element[0];
}
}
}
}
}
/**
* Implements hook_file_formatter_FORMATTER_settings().
*
* This function provides a bridge to the field formatter API, so that file
* field formatters can be reused for displaying the file entity's file
* pseudo-field.
*/
function file_entity_file_formatter_file_field_settings($form, &$form_state, $settings, $formatter_type, $file_type, $view_mode) {
if (strpos($formatter_type, 'file_field_') === 0) {
$field_formatter_type = substr($formatter_type, strlen('file_field_'));
$field_formatter_info = field_info_formatter_types($field_formatter_type);
// Invoke hook_field_formatter_settings_form(). We are reusing field
// formatter functions, but we are not working with a Field API field, so
// set $field accordingly. Unfortunately, the API is for $settings to be
// transfered via the $instance parameter, so we must mock it.
if (isset($field_formatter_info['module']) && ($function = ($field_formatter_info['module'] . '_field_formatter_settings_form')) && function_exists($function)) {
$field = NULL;
$mock_instance['display'][$view_mode] = array(
'type' => $field_formatter_type,
'settings' => $settings,
);
return $function($field, $mock_instance, $view_mode, $form, $form_state);
}
}
}
/**
* Implements hook_file_formatter_FORMATTER_view().
*
* Returns a drupal_render() array to display an image of the chosen style.
*
* This formatter is only capable of displaying local images. If the passed in
* file is either not local or not an image, nothing is returned, so that
* file_view_file() can try another formatter.
*/
function file_entity_file_formatter_file_image_view($file, $display, $langcode) {
// Prevent PHP notices when trying to read empty files.
// @see http://drupal.org/node/681042
if (!filesize($file->uri)) {
return;
}
// Do not bother proceeding if this file does not have an image mime type.
if (strpos($file->filemime, 'image/') !== 0) {
return;
}
if (file_entity_file_is_local($file) && $image = image_load($file->uri)) {
if (!empty($display['settings']['image_style'])) {
$element = array(
'#theme' => 'image_style',
'#style_name' => $display['settings']['image_style'],
'#path' => $file->uri,
'#width' => $image->info['width'],
'#height' => $image->info['height'],
);
}
else {
$element = array(
'#theme' => 'image',
'#path' => $file->uri,
'#width' => $image->info['width'],
'#height' => $image->info['height'],
);
}
return $element;
}
}
/**
* Implements hook_file_formatter_FORMATTER_settings().
*
* Returns form elements for configuring the 'file_image' formatter.
*/
function file_entity_file_formatter_file_image_settings($form, &$form_state, $settings) {
$element = array();
$element['image_style'] = array(
'#title' => t('Image style'),
'#type' => 'select',
'#options' => image_style_options(FALSE),
'#default_value' => $settings['image_style'],
'#empty_option' => t('None (original image)'),
);
return $element;
}
/**
* Menu access callback for the 'view mode file display settings' pages.
*
* Based on _field_ui_view_mode_menu_access(), but the Field UI module might not
* be enabled.
*/
function _file_entity_view_mode_menu_access($bundle, $view_mode, $access_callback) {
// Deny access if the view mode isn't configured to use custom display
// settings.
$file_type = field_extract_bundle('file', $bundle);
$view_mode_settings = field_view_mode_settings('file', $file_type);
$visibility = ($view_mode == 'default') || !empty($view_mode_settings[$view_mode]['custom_settings']);
if (!$visibility) {
return FALSE;
}
// Otherwise, continue to an $access_callback check.
$args = array_slice(func_get_args(), 3);
$callback = empty($access_callback) ? 0 : trim($access_callback);
if (is_numeric($callback)) {
return (bool) $callback;
}
elseif (function_exists($access_callback)) {
return call_user_func_array($access_callback, $args);
}
}
/**
* Implements hook_modules_enabled().
*/
function file_entity_modules_enabled($modules) {
file_info_cache_clear();
}
/**
* Implements hook_modules_disabled().
*/
function file_entity_modules_disabled($modules) {
file_info_cache_clear();
}
/**
* Implements hook_file_mimetype_mapping_alter().
*/
function file_entity_file_mimetype_mapping_alter(&$mapping) {
// Fix the mime type mapping for ogg.
// @todo Remove when http://drupal.org/node/1239376 is fixed in core.
$new_mappings['ogg'] = 'audio/ogg';
// Add support for m4v.
// @todo Remove when http://drupal.org/node/1290486 is fixed in core.
$new_mappings['m4v'] = 'video/x-m4v';
// Add support for mka and mkv.
// @todo Remove when http://drupal.org/node/1293140 is fixed in core.
$new_mappings['mka'] = 'audio/x-matroska';
$new_mappings['mkv'] = 'video/x-matroska';
// Add support for weba, webm, and webp.
// @todo Remove when http://drupal.org/node/1347624 is fixed in core.
$new_mappings['weba'] = 'audio/webm';
$new_mappings['webm'] = 'video/webm';
$new_mappings['webp'] = 'image/webp';
foreach ($new_mappings as $extension => $mime_type) {
if (!in_array($mime_type, $mapping['mimetypes'])) {
// If the mime type does not already exist, add it.
$mapping['mimetypes'][] = $mime_type;
}
// Get the index of the mime type and assign the extension to that key.
$index = array_search($mime_type, $mapping['mimetypes']);
$mapping['extensions'][$extension] = $index;
}
}
/**
* Check if a file entity is considered local or not.
*
* @param object $file
* A file entity object from file_load().
*
* @return
* TRUE if the file is using a local stream wrapper, or FALSE otherwise.
*/
function file_entity_file_is_local($file) {
$scheme = file_uri_scheme($file->uri);
$wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_LOCAL);
return !empty($wrappers[$scheme]) && empty($wrappers[$scheme]['remote']);
}

View File

@@ -0,0 +1,13 @@
name = "File Entity Test"
description = "Support module for File Entity tests."
package = Testing
core = 7.x
dependencies[] = file_entity
hidden = TRUE
; Information added by drupal.org packaging script on 2012-03-23
version = "7.x-1.0"
core = "7.x"
project = "media"
datestamp = "1332537952"

View File

@@ -0,0 +1,100 @@
<?php
/**
* @file
* File Entity Test
*/
/**
* Implements hook_menu().
*/
function file_entity_test_menu() {
$items = array();
$items['file-entity-test/file/add'] = array(
'title' => 'Add file',
'page callback' => 'drupal_get_form',
'page arguments' => array('file_entity_test_add_form'),
'access arguments' => array('administer site configuration'),
'file' => 'file_entity_test.pages.inc',
);
$items['file-entity-test/file/%file'] = array(
'title' => 'View file',
'page callback' => 'file_entity_test_view_page',
'page arguments' => array(2),
'access arguments' => array('administer site configuration'),
'file' => 'file_entity_test.pages.inc',
);
$items['file-entity-test/file/%file/view'] = array(
'title' => 'View',
'type' => MENU_DEFAULT_LOCAL_TASK,
'weight' => -10,
);
$items['file-entity-test/file/%file/preview'] = array(
'title' => 'Preview',
'page callback' => 'file_entity_test_preview_page',
'page arguments' => array(2),
'access arguments' => array('administer site configuration'),
'weight' => 0,
'type' => MENU_LOCAL_TASK,
'file' => 'file_entity_test.pages.inc',
);
$items['file-entity-test/file/%file/edit'] = array(
'title' => 'Edit',
'page callback' => 'drupal_get_form',
'page arguments' => array('file_entity_test_edit_form', 2),
'access arguments' => array('administer site configuration'),
'weight' => 1,
'type' => MENU_LOCAL_TASK,
'file' => 'file_entity_test.pages.inc',
);
return $items;
}
/**
* Implements hook_file_type_info().
*/
function file_entity_test_file_type_info() {
return array(
'file_entity_test' => array(
'label' => t('Test'),
'description' => t('A file type defined by the File Entity Test module. Used for testing only.'),
'claim callback' => 'file_entity_test_file_type_file_entity_test_claim',
'default view callback' => 'file_entity_test_file_type_file_entity_test_default_view',
'weight' => 100,
),
);
}
/**
* Implements hook_file_type_TYPE_claim().
*
* Returns TRUE if the passed in file should be assigned the 'file_entity_test'
* file type.
*/
function file_entity_test_file_type_file_entity_test_claim($file) {
return TRUE;
}
/**
* Implements hook_file_type_TYPE_default_view().
*/
function file_entity_test_file_type_file_entity_test_default_view($file, $view_mode, $langcode) {
return array(
'#type' => 'link',
'#title' => $file->filename,
'#href' => file_create_url($file->uri),
);
}
/**
* Implements hook_entity_info_alter().
*/
function file_entity_test_entity_info_alter(&$entity_info) {
$entity_info['file']['view modes']['file_entity_test_preview'] = array(
'label' => t('Test Preview'),
'custom settings' => TRUE,
);
}

View File

@@ -0,0 +1,93 @@
<?php
/**
* @file
* Test pages for the File Entity Test module.
*/
/**
* Form callback; upload a file.
*/
function file_entity_test_add_form($form, &$form_state) {
$form['file'] = array(
'#type' => 'managed_file',
'#required' => TRUE,
'#title' => 'File',
'#upload_location' => 'public://',
);
$form['actions'] = array('#type' => 'actions');
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => t('Save'),
);
return $form;
}
/**
* Form submit callback; save the uploaded file.
*/
function file_entity_test_add_form_submit($form, &$form_state) {
$file = file_load($form_state['values']['file']);
if (!$file->status) {
$file->status = FILE_STATUS_PERMANENT;
file_save($file);
}
drupal_set_message(t('Your file has been saved.'));
$form_state['redirect'] = 'file-entity-test/file/' . $file->fid;
}
/**
* Page callback; view a file.
*/
function file_entity_test_view_page($file) {
return file_view($file, 'default');
}
/**
* Page callback; preview a file.
*/
function file_entity_test_preview_page($file) {
return file_view($file, 'file_entity_test_preview');
}
/**
* Form callback; edit a file.
*/
function file_entity_test_edit_form($form, &$form_state, $file) {
$form_state['file'] = $file;
field_attach_form('file', $file, $form, $form_state);
$form['file'] = file_view($file, 'file_entity_test_preview');
// Add internal file properties needed by
// file_entity_test_edit_form_validate().
foreach (array('fid', 'type') as $key) {
$form[$key] = array('#type' => 'value', '#value' => $file->$key);
}
$form['actions'] = array('#type' => 'actions');
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => t('Save'),
);
return $form;
}
/**
* Form validation handler for the file edit form.
*/
function file_entity_test_edit_form_validate($form, &$form_state) {
entity_form_field_validate('file', $form, $form_state);
}
/**
* Form submit handler for the file edit form
*/
function file_entity_test_edit_form_submit($form, &$form_state) {
$file = $form_state['file'];
entity_form_submit_build_entity('file', $file, $form, $form_state);
file_save($file);
drupal_set_message(t('Your changes to the file have been saved.'));
$form_state['redirect'] = 'file-entity-test/file/' . $file->fid;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 B

View File

@@ -0,0 +1,494 @@
<?php
/**
* A base class for Resource Stream Wrappers.
*
* This class provides a complete stream wrapper implementation. It passes
* incoming URL's through an interpolation method then recursively calls
* the invoking PHP filesystem function.
*
* MediaReadOnlyStreamWrapper implementations need to override at least the
* interpolateUrl method to rewrite the URL before is it passed back into the
* calling function.
*/
abstract class MediaReadOnlyStreamWrapper implements DrupalStreamWrapperInterface {
protected $parameters = array();
protected $base_url = NULL;
private $_DEBUG_MODE = NULL;
public function get_parameters() {
return $this->parameters;
}
// As part of the inode protection mode returned by stat(), identifies the
// file as a regular file, as opposed to a directory, symbolic link, or other
// type of "file".
// @see http://linux.die.net/man/2/stat
const S_IFREG = 0100000;
/**
* "Template" for stat calls. All elements must be initialized.
* @var array
*/
protected $_stat = array(
0 => 0, // device number
'dev' => 0,
1 => 0, // inode number
'ino' => 0,
// inode protection mode. file_unmanaged_delete() requires is_file() to
// return TRUE.
2 => self::S_IFREG,
'mode' => self::S_IFREG,
3 => 0, // number of links
'nlink' => 0,
4 => 0, // userid of owner
'uid' => 0,
5 => 0, // groupid of owner
'gid' => 0,
6 => -1, // device type, if inode device *
'rdev' => -1,
7 => 0, // size in bytes
'size' => 0,
8 => 0, // time of last access (Unix timestamp)
'atime' => 0,
9 => 0, // time of last modification (Unix timestamp)
'mtime' => 0,
10 => 0, // time of last inode change (Unix timestamp)
'ctime' => 0,
11 => -1, // blocksize of filesystem IO
'blksize' => -1,
12 => -1, // number of blocks allocated
'blocks' => -1,
);
function interpolateUrl() {
if ($parameters = $this->get_parameters()) {
return $this->base_url . '?' . http_build_query($parameters);
}
}
/**
* Returns a web accessible URL for the resource.
*
* This function should return a URL that can be embedded in a web page
* and accessed from a browser. For example, the external URL of
* "youtube://xIpLd0WQKCY" might be
* "http://www.youtube.com/watch?v=xIpLd0WQKCY".
*
* @return
* Returns a string containing a web accessible URL for the resource.
*/
public function getExternalUrl() {
return $this->interpolateUrl();
}
/**
* Base implementation of getMimeType().
*/
static function getMimeType($uri, $mapping = NULL) {
return 'application/octet-stream';
}
/**
* Base implementation of realpath().
*/
function realpath() {
return $this->getExternalUrl();
}
/**
* Stream context resource.
*
* @var Resource
*/
public $context;
/**
* A generic resource handle.
*
* @var Resource
*/
public $handle = NULL;
/**
* Instance URI (stream).
*
* A stream is referenced as "scheme://target".
*
* @var String
*/
protected $uri;
/**
* Base implementation of setUri().
*/
function setUri($uri) {
$this->uri = $uri;
$this->parameters = $this->_parse_url($uri);
}
/**
* Base implementation of getUri().
*/
function getUri() {
return $this->uri;
}
/**
* Report an error.
* @param $message
* The untranslated string to report.
* @param $options
* An optional array of options to send to t().
* @param $display
* If TRUE, then we display the error to the user.
* @return
* We return FALSE, since we sometimes pass that back from the reporting
* function.
*/
private function _report_error($message, $options = array(), $display = FALSE) {
watchdog('resource', $message, $options, WATCHDOG_ERROR);
if ($display) {
drupal_set_message(t($message, $options), 'error');
}
return FALSE;
}
private function _debug($message, $type = 'status') {
if ($this->_DEBUG_MODE) {
drupal_set_message($message, $type);
}
}
/**
* Returns an array of any parameters stored in the URL's path.
* @param $url
* The URL to parse, such as youtube://v/[video-code]/t/[tags+more-tags].
* @return
* An associative array of all the parameters in the path,
* or FALSE if the $url is ill-formed.
*/
protected function _parse_url($url) {
$path = explode('://', $url);
$parts = explode('/', $path[1]);
$params = array();
$count = 0;
$total = count($parts);
if (!$total || ($total % 2)) {
// If we have no parts, or an odd number of parts, it's malformed.
return FALSE;
}
while ($count < $total) {
// We iterate count for each step of the assignment to keep us honest.
$params[$parts[$count++]] = $parts[$count++];
}
return $params;
}
/**
* Support for fopen(), file_get_contents(), file_put_contents() etc.
*
* @param $path
* A string containing the path to the file to open.
* @param $mode
* The file mode ("r", "wb" etc.).
* @param $options
* A bit mask of STREAM_USE_PATH and STREAM_REPORT_ERRORS.
* @param &$opened_path
* A string containing the path actually opened.
* @return
* TRUE if file was opened successfully.
*/
public function stream_open($url, $mode, $options, &$opened_url) {
$this->_debug(t('Stream open: %url', array('%url' => $url)));
// We only handle Read-Only mode by default.
if ($mode != 'r' && $mode != 'rb') {
return $this->_report_error('Attempted to open %url as mode: %mode.', array('%url' => $url, '%mode' => $mode), ($options & STREAM_REPORT_ERRORS));
}
// We parse a URL as youtube://v/dsyiufo34/t/cats+dogs to store
// the relevant code(s) in our private array of parameters.
$this->parameters = $this->_parse_url($url);
if ($this->parameters === FALSE) {
return $this->_report_error('Attempted to parse an ill-formed url: %url.', array('%url' => $url), ($options & STREAM_REPORT_ERRORS));
}
if ((bool)$this->parameters && ($options & STREAM_USE_PATH)) {
$opened_url = $url;
}
$this->_debug(t('Stream opened: %parameters', array('%parameters' => print_r($this->parameters, TRUE))));
return (bool)$this->parameters;
}
// Undocumented PHP stream wrapper method.
function stream_lock($operation) {
return FALSE;
}
/**
* Support for fread(), file_get_contents() etc.
*
* @param $count
* Maximum number of bytes to be read.
* @return
* The string that was read, or FALSE in case of an error.
*/
public function stream_read($count) {
return FALSE;
}
/**
* Support for fwrite(), file_put_contents() etc.
*
* @param $data
* The string to be written.
* @return
* The number of bytes written.
*/
public function stream_write($data) {
return FALSE;
}
/**
* Support for feof().
*
* @return
* TRUE if end-of-file has been reached.
*/
public function stream_eof() {
return FALSE;
}
/**
* Support for fseek().
*
* @param $offset
* The byte offset to got to.
* @param $whence
* SEEK_SET, SEEK_CUR, or SEEK_END.
* @return
* TRUE on success
*/
public function stream_seek($offset, $whence) {
return FALSE;
}
/**
* Support for fflush().
*
* @return
* TRUE if data was successfully stored (or there was no data to store).
*/
public function stream_flush() {
return FALSE;
}
/**
* Support for ftell().
*
* @return
* The current offset in bytes from the beginning of file.
*/
public function stream_tell() {
return FALSE;
}
/**
* Support for fstat().
*
* @return
* An array with file status, or FALSE in case of an error - see fstat()
* for a description of this array.
*/
public function stream_stat() {
return $this->_stat;
}
/**
* Support for fclose().
*
* @return
* TRUE if stream was successfully closed.
*/
public function stream_close() {
return TRUE;
}
/**
* Support for unlink().
*
* @param $uri
* A string containing the uri to the resource to delete.
* @return
* TRUE if resource was successfully deleted.
* @see http://php.net/manual/en/streamwrapper.unlink.php
*/
// public function unlink($uri) {
// $this->uri = $uri;
// return unlink($this->getLocalPath());
// }
/**
* Support for rename().
*
* @param $from_uri,
* The uri to the file to rename.
* @param $to_uri
* The new uri for file.
* @return
* TRUE if file was successfully renamed.
* @see http://php.net/manual/en/streamwrapper.rename.php
*/
// public function rename($from_uri, $to_uri) {
// return rename($this->getLocalPath($from_uri), $this->getLocalPath($to_uri));
// }
/**
* Support for mkdir().
*
* @param $uri
* A string containing the URI to the directory to create.
* @param $mode
* Permission flags - see mkdir().
* @param $options
* A bit mask of STREAM_REPORT_ERRORS and STREAM_MKDIR_RECURSIVE.
* @return
* TRUE if directory was successfully created.
* @see http://php.net/manual/en/streamwrapper.mkdir.php
*/
// public function mkdir($uri, $mode, $options) {
// $this->uri = $uri;
// $recursive = (bool)($options & STREAM_MKDIR_RECURSIVE);
// if ($recursive) {
// // $this->getLocalPath() fails if $uri has multiple levels of directories
// // that do not yet exist.
// $localpath = $this->getDirectoryPath() . '/' . file_uri_target($uri);
// }
// else {
// $localpath = $this->getLocalPath($uri);
// }
// if ($options & STREAM_REPORT_ERRORS) {
// return mkdir($localpath, $mode, $recursive);
// }
// else {
// return @mkdir($localpath, $mode, $recursive);
// }
// }
/**
* Support for rmdir().
*
* @param $uri
* A string containing the URI to the directory to delete.
* @param $options
* A bit mask of STREAM_REPORT_ERRORS.
* @return
* TRUE if directory was successfully removed.
* @see http://php.net/manual/en/streamwrapper.rmdir.php
*/
// public function rmdir($uri, $options) {
// $this->uri = $uri;
// if ($options & STREAM_REPORT_ERRORS) {
// return rmdir($this->getLocalPath());
// }
// else {
// return @rmdir($this->getLocalPath());
// }
// }
/**
* Support for stat().
*
* @param $url
* A string containing the url to get information about.
* @param $flags
* A bit mask of STREAM_URL_STAT_LINK and STREAM_URL_STAT_QUIET.
* @return
* An array with file status, or FALSE in case of an error - see fstat()
* for a description of this array.
*/
public function url_stat($url, $flags) {
return $this->stream_stat();
}
/**
* Support for opendir().
*
* @param $url
* A string containing the url to the directory to open.
* @param $options
* Unknown (parameter is not documented in PHP Manual).
* @return
* TRUE on success.
*/
public function dir_opendir($url, $options) {
return FALSE;
}
/**
* Support for readdir().
*
* @return
* The next filename, or FALSE if there are no more files in the directory.
*/
public function dir_readdir() {
return FALSE;
}
/**
* Support for rewinddir().
*
* @return
* TRUE on success.
*/
public function dir_rewinddir() {
return FALSE;
}
/**
* Support for closedir().
*
* @return
* TRUE on success.
*/
public function dir_closedir() {
return FALSE;
}
public function getDirectoryPath() {
return '';
}
/**
* DrupalStreamWrapperInterface requires that these methods be implemented,
* but none of them apply to a read-only stream wrapper. On failure they
* are expected to return FALSE.
*/
public function unlink($uri) {
// Although the remote file itself can't be deleted, return TRUE so that
// file_delete() can remove the file record from the database.
return TRUE;
}
public function rename($from_uri, $to_uri) {
return FALSE;
}
public function mkdir($uri, $mode, $options) {
return FALSE;
}
public function rmdir($uri, $options) {
return FALSE;
}
public function chmod($mode) {
return FALSE;
}
public function dirname($uri = NULL) {
return FALSE;
}
}

View File

@@ -0,0 +1,660 @@
<?php
/**
* @file
* This file contains the admin functions for the Media module.
*/
/**
* Include media.pages.inc since it has some form definitions we will use.
*/
require_once dirname(__FILE__) . '/media.pages.inc';
/**
* Display the list or thumbnails media admin display.
*/
function media_admin($form, $form_state) {
global $user;
$path = drupal_get_path('module', 'media');
$form['#attached'] = array(
'js' => array($path . '/js/media.admin.js'),
'css' => array($path . '/css/media.css'),
);
if (isset($form_state['values']['operation']) && $form_state['values']['operation'] == 'delete') {
$form['#attributes']['class'][] = "media-list-operation";
return media_multiple_delete_confirm($form, $form_state, array_filter($form_state['values']['files']), 'admin/content/media', 'admin/content/media');
}
require_once dirname(__FILE__) . '/media.browser.inc';
media_attach_browser_js($form);
$types = media_display_types();
if (arg(3)) {
$display = arg(3);
if (!in_array($display, array_keys($types))) {
exit(drupal_not_found());
}
// Save their preference.
db_merge('media_list_type')
->key(array('uid' => $user->uid))
->fields(array(
'type' => $display,
))
->execute();
}
else {
$display = db_query("SELECT type FROM {media_list_type} WHERE uid = :uid", array(':uid' => $user->uid))->fetch();
if (!$display) {
$display = 'list';
}
else {
$display = $display->type;
}
}
// Build the display switch.
$form['switch'] = media_admin_display_switch(array('active display' => $display));
// Build the 'Media operations' form.
$options = array();
foreach (module_invoke_all('media_operations') as $operation => $array) {
$options[$operation] = $array['label'];
}
$form['options'] = array(
'#type' => 'fieldset',
'#title' => t('Operations'),
'#prefix' => '<div class="container-inline">',
'#suffix' => '</div>',
'#access' => !empty($options),
);
$form['options']['operation'] = array(
'#type' => 'select',
'#options' => $options,
'#default_value' => 'delete',
);
$form['options']['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
'#submit' => array('media_admin_submit'),
'#validate' => array('media_admin_validate'),
);
include_once $types[$display]['file'];
$form['admin'] = $types[$display]['callback']($form);
return $form;
}
/**
* Form builder: Builds the media list administration overview.
*/
function media_admin_list(&$parent_form) {
// @todo Change to media_variable_get('admin_pager_limit') for consistency
// with browser_pager_limit?
$limit = variable_get('media_admin_limit', 50);
// Build the sortable table header.
$header = array(
'title' => array('data' => t('Title'), 'field' => 'f.filename'),
'type' => array('data' => t('Type'), 'field' => 'f.filemime'),
'size' => array('data' => t('Size'), 'field' => 'f.filesize'),
'author' => array('data' => t('Author'), 'field' => 'u.name'),
'timestamp' => array('data' => t('Updated'), 'field' => 'f.timestamp', 'sort' => 'desc'),
'operations' => array('data' => t('Operations')),
);
$query = db_select('file_managed', 'f')->extend('PagerDefault')->extend('TableSort');
$query->join('users', 'u', 'f.uid = u.uid');
$query->fields('f', array('fid'));
$query->fields('u', array('uid'));
$query->condition('f.status', FILE_STATUS_PERMANENT);
$query->limit($limit);
$query->orderByHeader($header);
foreach (array_keys(media_get_hidden_stream_wrappers()) as $name) {
$query->condition('f.uri', $name . '%', 'NOT LIKE');
}
// Result array keys are file IDs, values are the file owner's UIDs.
$result = $query->execute()->fetchAllKeyed();
// Hide the operations form if there are no files to operate on.
$parent_form['options']['#access'] &= !empty($result);
// Load all the file entities.
$files = $form['#files'] = file_load_multiple(array_keys($result));
// Load all the file owner user entities to display usernames.
$accounts = $form['#accounts'] = user_load_multiple(array_unique($result));
$destination = drupal_get_destination();
$options = array();
foreach ($files as $file) {
$options[$file->fid] = array(
'title' => theme('media_link', array('file' => $file)),
'type' => check_plain($file->filemime),
'size' => format_size($file->filesize),
'author' => theme('username', array('account' => $accounts[$file->uid])),
'timestamp' => format_date($file->timestamp, 'short'),
);
$options[$file->fid]['operations'] = l(t('Edit'), 'media/' . $file->fid . '/edit', array('query' => $destination));
}
$form['files'] = array(
'#type' => 'tableselect',
'#header' => $header,
'#options' => $options,
'#empty' => t('No media available.'),
'#attributes' => array('class' => array('media-display-table', 'media-clear')),
);
$form['pager'] = array('#markup' => theme('pager', array('tags' => NULL)));
return $form;
}
/**
* Form builder: Builds the media thumbnails administration overview.
*/
function media_admin_thumbnails(&$parent_form) {
// @todo Change to media_variable_get('admin_pager_limit') for consistency
// with browser_pager_limit?
$limit = variable_get('media_admin_limit', 50);
$query = db_select('file_managed', 'f')->extend('PagerDefault');
$query->fields('f', array('fid'));
$query->condition('f.status', FILE_STATUS_PERMANENT);
$query->limit($limit);
$query->orderBy('f.timestamp', 'DESC');
foreach (array_keys(media_get_hidden_stream_wrappers()) as $name) {
$query->condition('f.uri', $name . '%', 'NOT LIKE');
}
$fids = $query->execute()->fetchCol();
$files = file_load_multiple($fids);
$files = array();
// Hide the operations form if there are no files to operate on.
$parent_form['options']['#access'] &= !empty($files);
if (empty($files)) {
// Display empty text if there are no files.
$form['files'] = array(
'#markup' => '<p>' . t('No media available.') . '</p>',
);
}
else {
$form['files'] = array(
'#tree' => TRUE,
'#prefix' => '<div class="media-display-thumbnails media-clear clearfix"><ul class="media-list-thumbnails">',
'#suffix' => '</ul></div>',
);
foreach ($files as $file) {
$preview = media_get_thumbnail_preview($file, TRUE);
if (!isset($preview['#file'])) { dpm($file); }
$form['files'][$file->fid] = array(
'#type' => 'checkbox',
'#title' => '',
'#prefix' => '<li>' . drupal_render($preview),
'#suffix' => '</li>',
);
}
$form['pager'] = array('#markup' => theme('pager', array('tags' => NULL)));
}
return $form;
}
/**
* Build the display switch portion of the file listings form.
*/
function media_admin_display_switch($options = array()) {
$options += array(
'form location' => 'admin/content/media',
'active display' => 'list',
);
$display_types = media_display_types();
// Build the item list.
$display_items = array();
foreach ($display_types as $delta => $item) {
$attributes = array('title' => $item['description']);
// Set a seperate icon for the active item.
if ($delta == $options['active display']) {
$icon = $item['icon_active'];
$attributes['class'][] = 'active';
}
else {
$icon = $item['icon'];
}
$display_items[] = array(
'data' => l(theme('image', array('path' => $icon, 'alt' => $item['title'])),
$options['form location'] . '/' . $delta,
array(
'html' => TRUE,
'attributes' => $attributes,
)),
);
}
return array(
'#type' => 'markup',
'#markup' => theme('item_list', array(
'items' => $display_items,
'attributes' => array('class' => 'media-display-switch'),
)
),
);
}
/**
* Validate media_admin_list form submissions.
*
* Check if any files have been selected to perform the chosen
* 'Update option' on.
*/
function media_admin_validate($form, &$form_state) {
$files = array_filter($form_state['values']['files']);
if (count($files) == 0) {
form_set_error('', t('No items selected.'));
}
}
/**
* Process media_admin_list form submissions.
*
* Execute the chosen 'Update option' on the selected files.
*/
function media_admin_submit($form, &$form_state) {
$operations = module_invoke_all('media_operations');
$operation = $operations[$form_state['values']['operation']];
// Filter out unchecked nodes
$files = array_filter($form_state['values']['files']);
if ($function = $operation['callback']) {
// Add in callback arguments if present.
if (isset($operation['callback arguments'])) {
$args = array_merge(array($files), $operation['callback arguments']);
}
else {
$args = array($files);
}
call_user_func_array($function, $args);
cache_clear_all();
}
elseif (!empty($operation['redirect'])) {
$fids = implode(' ', array_keys(array_filter($form_state['values']['files'])));
$form_state['redirect'] = array(str_replace('%fids', $fids, $operation['redirect']), array('query' => array('destination' => 'admin/content/media')));
}
else {
// We need to rebuild the form to go to a second step. For example, to
// show the confirmation form for the deletion of nodes.
$form_state['rebuild'] = TRUE;
}
}
/**
* The administration form for managing media types.
*/
function media_admin_type_manage_form($form, &$form_state, $media_type) {
$form = array();
$form['media_type'] = array(
'#type' => 'value',
'#value' => $media_type->name,
);
// If this Media type is handled by us, then we can put in some default
// options. Otherwise, we leave it to the implementing module to form_alter.
if ($media_type->type_callback == 'media_is_type') {
// Options for match_type.
$options = array(
'all' => t('All'),
'any' => t('Any'),
'other' => t('Other'),
);
if ($media_type->type_callback_args['match_type'] && isset($options[$media_type->type_callback_args['match_type']])) {
$default_value = $media_type->type_callback_args['match_type'];
$other_default_value = '';
}
else {
$default_value = 'other';
$other_default_value = $media_type->type_callback_args['match_type'];
}
$form['match_type'] = array(
'#type' => 'radios',
'#title' => t('Match type'),
'#options' => $options,
'#default_value' => $default_value,
);
$form['match_type_other'] = array(
'#type' => 'textfield',
'#title' => t('Other match type value'),
'#default_value' => $other_default_value,
'#attached' => array(
'js' => array(drupal_get_path('module', 'media') . '/js/media.admin.js'),
),
);
// Options for allowed Streams.
$options = array('public' => t('Public files'), 'private' => t('Private files'));
foreach (file_get_stream_wrappers() as $stream => $wrapper) {
$options[$stream] = $wrapper['name'];
}
unset($options['temporary']);
$default_value = array();
if (isset($media_type->type_callback_args['streams'])) {
foreach ($media_type->type_callback_args['streams'] as $stream) {
$default_value[$stream] = $stream;
}
}
$form['streams'] = array(
'#type' => 'checkboxes',
'#title' => t('Allowed streams'),
'#options' => $options,
'#default_value' => $default_value,
);
// Options for allowed mimetypes & extensions.
$default_value = isset($media_type->type_callback_args['mimetypes']) ? implode(' ', $media_type->type_callback_args['mimetypes']) : '';
$form['mimetypes'] = array(
'#type' => 'textfield',
'#title' => t('Allowed mimetypes'),
'#description' => t('You may optionally enter one or more allowed file mimetypes for this Media type, if appropriate, separating each with a space. You may use a regular expression for matching, such as %image_match (which would match any mimetype beginning with %image) or %any_match, which would match any file mimetype.', array('%image_match' => '/^image/', '%image' => t('image'), '%any_match' => '/.*/')),
'#default_value' => check_plain($default_value),
);
$default_value = isset($media_type->type_callback_args['extensions']) ? implode(' ', $media_type->type_callback_args['extensions']) : '';
$form['extensions'] = array(
'#type' => 'textfield',
'#title' => t('Allowed extensions'),
'#description' => t('You may optionally enter one or more allowed file extensions for this Media type, if appropriate, separating each with a space (and no dots).'),
'#default_value' => check_plain($default_value),
);
}
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Save'),
'#weight' => 100,
);
return $form;
}
function media_admin_type_manage_form_submit($form, &$form_state) {
$media_type = media_type_load($form_state['values']['media_type']);
// Reset all values to empty.
$media_type->type_callback_args = array();
// What is the logic of the match (AND / OR).
if ($form_state['values']['match_type']) {
$media_type->type_callback_args['match_type'] = $form_state['values']['match_type'];
}
else {
$media_type->type_callback_args['match_type'] = $form_state['values']['match_type_other'];
}
// Which streams are valid for this type.
$media_type->type_callback_args['streams'] = array();
foreach ($form_state['values']['streams'] as $stream) {
if ($stream) {
$media_type->type_callback_args['streams'][] = $stream;
}
}
// Which mimetypes are valid for this type.
if (trim($form_state['values']['mimetypes'])) {
$media_type->type_callback_args['mimetypes'] = explode(' ', $form_state['values']['mimetypes']);
array_walk($media_type->type_callback_args['mimetypes'], 'trim');
array_filter($media_type->type_callback_args['mimetypes']);
}
// Which file extensions are valid for this type.
if (trim($form_state['values']['extensions'])) {
$media_type->type_callback_args['extensions'] = explode(' ', $form_state['values']['extensions']);
array_walk($media_type->type_callback_args['extensions'], 'trim');
array_filter($media_type->type_callback_args['extensions']);
}
media_type_save($media_type);
drupal_set_message(t('The @label media type has been saved.', array('@label' => $media_type->label)));
}
/**
* Form callback for mass import.
*/
function media_import($form, &$form_state) {
if (!isset($form_state['storage']['files'])) {
$form_state['storage']['step'] = 'choose';
$form_state['storage']['next_step'] = 'preview';
$form['directory'] = array(
'#type' => 'textfield',
'#title' => t('Directory'),
'#required' => TRUE,
);
$form['pattern'] = array(
'#type' => 'textfield',
'#title' => t('Pattern'),
'#description' => 'Only files matching this pattern will be imported. For example, to import all jpg and gif files, the pattern would be <em>*.jpg|*.gif</em>.',
);
$form['actions'] = array('#type' => 'actions');
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => t('Preview')
);
$form['actions']['cancel'] = array(
'#type' => 'link',
'#title' => t('Cancel'),
'#href' => isset($_GET['destination']) ? $_GET['destination'] : 'admin/content/media',
);
}
else {
$form['preview'] = array(
'#markup' => theme('item_list', array('items' => $form_state['storage']['files'])),
);
$form = confirm_form($form, t('Import these files?'), 'admin/content/media/import');
}
return $form;
}
/**
* Validate handler for media_import().
*/
function media_import_validate($form, &$form_state) {
if ($form_state['values']['op'] != t('Confirm')) {
$directory = $form_state['values']['directory'];
$pattern = $form_state['values']['pattern'];
if (!is_dir($directory)) {
form_set_error('directory', t('The provided directory does not exist.'));
}
$pattern = !empty($pattern) ? $pattern : '*';
$files = glob("$directory/$pattern");
if (empty($files)) {
form_set_error('pattern', t('No files were found in %directory matching %pattern', array('%directory' => $directory, '%pattern' => $pattern)));
}
$form_state['storage']['files'] = $files;
}
}
/**
* Submit handler for media_import().
*/
function media_import_submit($form, &$form_state) {
if ($form_state['values']['op'] == t('Confirm')) {
$files = $form_state['storage']['files'];
$batch = array(
'title' => t('Importing'),
'operations' => array(
array('media_import_batch_import_files', array($files)),
),
'finished' => 'media_import_batch_import_complete',
'file' => drupal_get_path('module', 'media') . '/includes/media.admin.inc',
);
batch_set($batch);
return;
}
$form_state['rebuild'] = TRUE;
}
/**
* BatchAPI callback op for media import.
*/
function media_import_batch_import_files($files, &$context) {
if (!isset($context['sandbox']['files'])) {
// This runs the first time the batch runs.
// This is stupid, but otherwise, I don't think it will work...
$context['results'] = array('success' => array(), 'errors' => array());
$context['sandbox']['max'] = count($files);
$context['sandbox']['files'] = $files;
}
$files =& $context['sandbox']['files'];
// Take a cut of files. Let's do 10 at a time.
$length = (count($files) > media_variable_get('import_batch_size')) ? media_variable_get('import_batch_size') : count($files);
$to_process = array_splice($files, 0, $length);
$image_in_message = '';
foreach ($to_process as $file) {
try {
$file_obj = media_parse_to_file($file);
$context['results']['success'][] = $file;
if (!$image_in_message) {
// @todo Is this load step really necessary? When there's time, test
// this, and either remove it, or comment why it's needed.
$loaded_file = file_load($file_obj->fid);
$image_in_message = file_view_file($loaded_file, 'media_preview');
}
}
catch (Exception $e) {
$context['results']['errors'][] = $file . " Reason: " . $e->getMessage();
}
}
$context['message'] = "Importing " . theme('item_list', array('items' => $to_process));
$context['message'] .= drupal_render($image_in_message); // Just for kicks, show an image we are importing
$context['finished'] = ($context['sandbox']['max'] - count($files)) / $context['sandbox']['max'];
}
/**
* BatchAPI complete callback for media import.
*/
function media_import_batch_import_complete($success, $results, $operations) {
if ($results['errors']) {
drupal_set_message(theme('item_list', array('items' => $results['errors'])), 'error');
}
if ($results['success']) {
drupal_set_message(theme('item_list', array('items' => $results['success'])));
}
}
/**
* Admin configruation form for media browser settings.
*/
function media_admin_config_browser($form, &$form_state) {
$theme_options = array();
$theme_options[NULL] = 'Default administration theme';
foreach (list_themes() as $key => $theme) {
if ($theme->status) {
$theme_options[$key] = $theme->info['name'];
}
}
$form[media_variable_name('dialog_theme')] = array(
'#type' => 'select',
'#title' => t('Media browser theme'),
'#options' => $theme_options,
'#description' => t("This theme will be used for all media related dialogs. It can be different from your site's theme because many site themes do not work well in the small windows which media uses."),
'#default_value' => media_variable_get('dialog_theme'),
);
$form[media_variable_name('file_extensions')] = array(
'#type' => 'textfield',
'#title' => t('Allowed file extensions'),
'#default_value' => media_variable_get('file_extensions'),
'#description' => t('File extensions which are accepted in the media browser. Use spaces to separate (e.g. "jpg gif pdf doc"). <br/><em>Note that this can be overriden on a per-field basis when creating multimedia asset fields and files of certain extensions cannot be embedded at this time.</em>'),
);
$form['#submit'][] = 'media_admin_config_browser_pre_submit';
return system_settings_form($form);
}
function media_admin_config_browser_pre_submit(&$form, &$form_state) {
if (!$form_state['values'][media_variable_name('dialog_theme')]) {
media_variable_del('dialog_theme');
unset($form_state['values'][media_variable_name('dialog_theme')]);
}
}
/**
* Confirmation form for rebuliding the file_managed table to include type
* in rows where there is no type.
*/
function media_admin_rebuild_types_form($form, &$form_state) {
$total = media_type_invalid_files_count();
if ($total == 0) {
media_variable_del('show_file_type_rebuild_nag');
// @TODO: Make this not sound stupid.
drupal_set_message(t('All files in the system have been assigned types. Media installation complete.'));
drupal_goto('admin');
}
$form['total'] = array('#type' => 'value', '#value' => $total);
return confirm_form(
$form,
t('Update types for existing files'),
'admin/config/media',
t('This process is required when installing media on an existing site. Media needs to scan through existing files and identify the file type. <br/><strong>Update types for @file files?</strong>', array('@total' => $total))
);
}
/**
* @see media_admin_rebuild_types_form()
*/
function media_admin_rebuild_types_form_submit(&$form, &$form_state) {
$total = $form_state['values']['total'];
$batch = array(
'title' => t('Rebuilding type information for ' . $total . ' files'),
'operations' => array(
array('media_admin_rebuild_types_batch_op', array($total)),
),
'finished' => 'media_admin_rebuild_types_batch_complete',
'file' => drupal_get_path('module', 'media') . '/includes/media.admin.inc',
);
batch_set($batch);
}
/**
* Batch operation for fixing the file_managed table for media, adding type values
* where no value exists.
*/
function media_admin_rebuild_types_batch_op($total, &$context) {
$per_run = media_variable_get('media_type_batch_update_per_run', 100);
$context['results'] = array_merge($context['results'], media_type_batch_update(FALSE, $per_run));
$context['finished'] = count($context['results']) / $total;
}
/**
* Sets a message informing the user how many file records were updated.
*/
function media_admin_rebuild_types_batch_complete($success, $results, $operations) {
if ($success) {
$message = format_plural(count($results), 'One file identified and given a type.', '@count files identified and given a type.');
media_variable_del('show_file_type_rebuild_nag');
}
drupal_set_message($message);
}

View File

@@ -0,0 +1,367 @@
<?php
/**
* @file
* Media Browser page callback
*/
function media_browser($selected = NULL) {
$output = array();
$output['#attached']['library'][] = array('media', 'media_browser_page');
$params = drupal_get_query_parameters();
array_walk_recursive($params, '_media_recursive_check_plain');
$params = media_set_browser_params($params);
// If one or more files have been selected, the browser interaction is now
// complete. Return empty page content to the dialog which now needs to close,
// but populate Drupal.settings with information about the selected files.
if (isset($params['fid'])) {
$fids = is_array($params['fid']) ? $params['fid'] : array($params['fid']);
if (!is_numeric($fids[0])) {
throw new Exception('Error selecting media, fid param is not an fid or an array of fids');
}
$files = file_load_multiple($fids);
foreach ($files as $file) {
media_browser_build_media_item($file);
}
$setting = array('media' => array('selectedMedia' => array_values($files)));
drupal_add_js($setting, 'setting');
return $output;
}
// Normal browser operation.
foreach (module_implements('media_browser_plugin_info') as $module) {
foreach (module_invoke($module, 'media_browser_plugin_info') as $key => $plugin_data) {
$plugins[$key] = $plugin_data + array(
'#module' => $module,
'#weight' => 0,
);
$plugins[$key]['#weight'] += count($plugins)/1000;
}
}
// Only the plugins in this array are loaded.
if (!empty($params['enabledPlugins'])) {
$plugins = array_intersect_key($plugins, array_fill_keys($params['enabledPlugins'], 1));
}
elseif (!empty($params['disabledPlugins'])) {
$plugins = array_diff_key($plugins, array_fill_keys($params['disabledPlugins'], 1));
}
foreach ($plugins as $key => &$plugin) {
$plugin += module_invoke($plugin['#module'], 'media_browser_plugin_view', $key, $params);
}
// Allow modules to change the tab names or whatever else they want to change
// before we render. Perhaps this should be an alter on the theming function
// that we should write to be making the tabs.
drupal_alter('media_browser_plugins', $plugins);
$tabs = array(); // List of tabs to render.
$settings = array('media' => array('browser' => array()));
$browser_settings =& $settings['media']['browser'];
//@todo: replace with Tabs module if it gets upgraded.
foreach (element_children($plugins, TRUE) as $key) {
$plugin =& $plugins[$key];
//Add any JS settings
$browser_settings[$key] = isset($plugin['#settings']) ? $plugin['#settings'] : array();
// If this is a "ajax" style tab, add the href, otherwise an id.
$href = isset($plugin['#callback']) ? $plugin['#callback'] : "#media-tab-$key";
$tabs[] = "<a href='$href'><span>{$plugin['#title']}</span></a>";
// Create a div for each tab's content.
$plugin['#prefix'] = <<<EOS
<div class="media-browser-tab" id="media-tab-$key">
EOS;
$plugin['#suffix'] = <<<EOS
</div>
<!-- End #media-tab-$key -->
EOS;
}
drupal_add_js($settings, 'setting');
$output['tabset'] = array(
'#prefix' => '<div id="media-browser-tabset">',
'#suffix' => '</div>',
);
$output['tabset']['list'] = array(
'#markup' => '<ul><li>' . implode('</li><li>', $tabs) . '</li></ul>'
);
$output['tabset']['plugins'] = $plugins;
return $output;
}
/**
* Provides a singleton of the params passed to the media browser.
*
* This is useful in situations like form alters because callers can pass
* id="wysiywg_form" or whatever they want, and a form alter could pick this up.
* We may want to change the hook_media_browser_plugin_view() implementations to
* use this function instead of being passed params for consistency.
*
* It also offers a chance for some meddler to meddle with them.
*
* @param array $params
* An array of parameters provided when a media_browser is launched.
*
* @see media_browser()
*/
function media_set_browser_params(array $params = NULL) {
$stored_params = &drupal_static(__FUNCTION__, array());
if (isset($params)) {
$stored_params = $params;
// Allow modules to alter the parameters.
drupal_alter('media_browser_params', $stored_params);
}
return $stored_params;
}
/**
* For sanity in grammar.
*
* @see media_set_browser_params()
*/
function media_get_browser_params() {
return media_set_browser_params();
}
/**
* AJAX Callback function to return a list of media files
*/
function media_browser_list() {
$params = drupal_get_query_parameters();
// How do we validate these? I don't know.
// I think PDO should protect them, but I'm not 100% certain.
array_walk_recursive($params, '_media_recursive_check_plain');
$remote_types = !empty($params['types']) ? $params['types'] : NULL;
$url_include_patterns = !empty($params['url_include_patterns']) ? $params['url_include_patterns'] : NULL;
$url_exclude_patterns = !empty($params['url_exclude_patterns']) ? $params['url_exclude_patterns'] : NULL;
$allowed_schemes = !empty($params['schemes']) ? array_filter($params['schemes']) : array();
$start = isset($params['start']) ? $params['start'] : 0;
$limit = isset($params['limit']) ? $params['limit'] : media_variable_get('browser_pager_limit');
$query = db_select('file_managed', 'f');
$query->fields('f', array('fid'));
$query->range($start, $limit);
$query->orderBy('f.timestamp', 'DESC');
// Add conditions based on remote file type *or* local allowed extensions.
$or_condition = db_or();
// Include local files with the allowed extensions.
if (!empty($params['file_extensions'])) {
$extensions = array_filter(explode(' ', $params['file_extensions']));
$local_wrappers = array_intersect_key(media_get_local_stream_wrappers(), $allowed_schemes);
if (!empty($local_wrappers) && !empty($extensions)) {
$local_condition = db_or();
foreach (array_keys($local_wrappers) as $scheme) {
foreach ($extensions as $extension) {
$local_condition->condition('f.uri', db_like($scheme . '://') . '%' . db_like('.' . $extension), 'LIKE');
}
}
$or_condition->condition($local_condition);
}
}
// Include remote files with the allowed file types.
if (!empty($remote_types)) {
$remote_wrappers = array_intersect_key(media_get_remote_stream_wrappers(), $allowed_schemes);
if (!empty($remote_wrappers)) {
$remote_condition = db_and();
$wrapper_condition = db_or();
foreach (array_keys($remote_wrappers) as $scheme) {
$wrapper_condition->condition('f.uri', db_like($scheme . '://') . '%', 'LIKE');
}
$remote_condition->condition($wrapper_condition);
$remote_condition->condition('f.type', $remote_types, 'IN');
$or_condition->condition($remote_condition);
}
}
if ($or_condition->count()) {
$query->condition($or_condition);
}
if ($url_include_patterns) {
$query->condition('f.uri', '%' . db_like($url_include_patterns) . '%', 'LIKE');
// Insert stream related restrictions here.
}
if ($url_exclude_patterns) {
$query->condition('f.uri', '%' . db_like($url_exclude_patterns) . '%', 'NOT LIKE');
}
// @todo Implement granular editorial access: http://drupal.org/node/696970.
// In the meantime, protect information about private files from being
// discovered by unprivileged users. See also media_view_page().
if (!user_access('administer media')) {
$query->condition('f.uri', db_like('private://') . '%', 'NOT LIKE');
}
$query->condition('f.status', FILE_STATUS_PERMANENT);
foreach (array_keys(media_get_hidden_stream_wrappers()) as $name) {
$query->condition('f.uri', db_like($name . '://') . '%', 'NOT LIKE');
}
$fids = $query->execute()->fetchCol();
$files = file_load_multiple($fids);
foreach ($files as $file) {
media_browser_build_media_item($file);
}
drupal_json_output(array('media' => array_values($files)));
exit();
}
/**
* Silly function to recursively run check_plain on an array.
*
* There is probably something in core I am not aware of that does this.
*
* @param $value
* @param $key
*/
function _media_recursive_check_plain(&$value, $key) {
$value = check_plain($value);
}
/**
* Attaches media browser javascript to an element.
*
* @param $element
* The element array to attach to.
*/
function media_attach_browser_js(&$element) {
$javascript = media_browser_js();
foreach ($javascript as $key => $definitions) {
foreach ($definitions as $definition) {
$element['#attached'][$key][] = $definition;
}
}
}
/**
* Helper function to define browser javascript.
*/
function media_browser_js() {
$settings = array(
'browserUrl' => url('media/browser',
array('query' => array('render' => 'media-popup'))),
'styleSelectorUrl' => url('media/-media_id-/format-form',
array('query' => array('render' => 'media-popup'))),
);
$js = array(
'library' => array(
array('media', 'media_browser'),
),
'js' => array(
array(
'data' => array('media' => $settings),
'type' => 'setting',
),
),
);
return $js;
}
/**
* Menu callback for testing the media browser
*/
function media_browser_testbed($form) {
media_attach_browser_js($form);
$form['test_element'] = array(
'#type' => 'media',
'#media_options' => array(
'global' => array(
'types' => array('video', 'audio'),
),
)
);
$launcher = '<a href="#" id="launcher"> Launch Media Browser</a>';
$form['options'] = array(
'#type' => 'textarea',
'#title' => 'Options (JSON)',
'#rows' => 10,
);
$form['launcher'] = array(
'#markup' => $launcher,
);
$form['result'] = array(
'#type' => 'textarea',
'#title' => 'Result',
);
$js = <<<EOF
Drupal.behaviors.mediaTest = {
attach: function(context, settings) {
var delim = "---";
var recentOptions = [];
var recentOptionsCookie = jQuery.cookie("recentOptions");
if (recentOptionsCookie) {
recentOptions = recentOptionsCookie.split("---");
}
var recentSelectBox = jQuery('<select id="recent_options" style="width:100%"></select>').change(function() { jQuery('#edit-options').val(jQuery(this).val())});
jQuery('.form-item-options').append('<label for="recent_options">Recent</a>');
jQuery('.form-item-options').append(recentSelectBox);
jQuery('.form-item-options').append(jQuery('<a href="#">Reset</a>').click(function() {alert('reset'); jQuery.cookie("recentOptions", null); window.location.reload(); }));
jQuery.each(recentOptions, function (idx, val) {
recentSelectBox.append(jQuery('<option></option>').val(val).html(val));
});
jQuery('#launcher').click(function () {
jQuery('#edit-result').val('');
var options = {};
var optionsTxt = jQuery('#edit-options').val();
if (optionsTxt) {
// Store it in the recent box
recentOptionsCookie += "---" + optionsTxt
jQuery.cookie("recentOptions", recentOptionsCookie, { expires: 7 });
recentSelectBox.append(jQuery('<option></option>').val(optionsTxt).html(optionsTxt));
options = eval('(' + optionsTxt + ')');
}
Drupal.media.popups.mediaBrowser(Drupal.behaviors.mediaTest.mediaSelected, options);
return false;
});
},
mediaSelected: function(selectedMedia) {
var result = JSON.stringify(selectedMedia);
jQuery('#edit-result').val(result);
}
}
EOF;
drupal_add_js($js, array('type' => 'inline'));
return $form;
}
/**
* Adds properties to the passed in file that are needed by the media browser JS code.
*/
function media_browser_build_media_item($file) {
$preview = media_get_thumbnail_preview($file);
$file->preview = drupal_render($preview);
$file->url = file_create_url($file->uri);
}

View File

@@ -0,0 +1,485 @@
<?php
/**
* @file: Provides a "Multimedia asset" field to the fields API
*/
/**
* Implement hook_field_info().
*/
function media_field_info() {
return array(
'media' => array(
'label' => t('Multimedia asset (deprecated)'),
'description' => t('This field stores a reference to a multimedia asset.'),
'settings' => array(),
'instance_settings' => array(
'file_extensions' => media_variable_get('file_extensions'),
),
'default_widget' => 'media_generic',
'default_formatter' => 'media_large',
'property_type' => 'field_item_file',
'property_callbacks' => array('entity_metadata_field_file_callback'),
),
);
}
/**
* Implements hook_field_is_empty().
*/
function media_field_is_empty($item, $field) {
return empty($item['fid']);
}
/**
* Implements hook_field_instance_settings_form().
*/
function media_field_instance_settings_form($field, $instance) {
$settings = $instance['settings'];
// Make the extension list a little more human-friendly by comma-separation.
$extensions = str_replace(' ', ', ', $settings['file_extensions']);
$form['file_extensions'] = array(
'#type' => 'textfield',
'#title' => t('Allowed file extensions for uploaded files'),
'#default_value' => $extensions,
'#description' => t('Separate extensions with a space or comma and do not include the leading dot.'),
'#element_validate' => array('_file_generic_settings_extensions'),
// By making this field required, we prevent a potential security issue
// that would allow files of any type to be uploaded.
'#required' => TRUE,
'#maxlength' => 255,
);
return $form;
}
/**
* Implement hook_field_widget_info().
*/
function media_field_widget_info() {
return array(
'media_generic' => array(
'label' => t('Media file selector'),
'field types' => array('media', 'file', 'image'),
'settings' => array(
'progress_indicator' => 'throbber',
'allowed_types' => array('image'),
'allowed_schemes' => array('public', 'private'),
),
'behaviors' => array(
'multiple values' => FIELD_BEHAVIOR_DEFAULT,
'default value' => FIELD_BEHAVIOR_NONE,
),
),
);
}
/**
* Implements hook_field_formatter_info().
*/
function media_field_formatter_info() {
$formatters = array(
'media_large_icon' => array(
'label' => t('Large filetype icon'),
'field types' => array('file'),
),
// This was originally used when media entities contained file fields. The
// current file entity architecture no longer needs this, but people may
// have used this formatter for other file fields on their website.
// @todo Some day, remove this.
'media' => array(
'label' => t('Media'),
'field types' => array('media'),
'settings' => array('file_view_mode' => 'default'),
),
);
return $formatters;
}
/**
* Implements hook_field_formatter_settings_form().
*/
function media_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
$display = $instance['display'][$view_mode];
$settings = $display['settings'];
$element = array();
if ($display['type'] == 'media') {
$entity_info = entity_get_info('file');
$options = array('default' => t('Default'));
foreach ($entity_info['view modes'] as $file_view_mode => $file_view_mode_info) {
$options[$file_view_mode] = $file_view_mode_info['label'];
}
$element['file_view_mode'] = array(
'#title' => t('File view mode'),
'#type' => 'select',
'#default_value' => $settings['file_view_mode'],
'#options' => $options,
);
}
return $element;
}
/**
* Implements hook_field_formatter_settings_summary().
*/
function media_field_formatter_settings_summary($field, $instance, $view_mode) {
$display = $instance['display'][$view_mode];
$settings = $display['settings'];
$summary = '';
if ($display['type'] == 'media') {
$entity_info = entity_get_info('file');
$file_view_mode_label = isset($entity_info['view modes'][$settings['file_view_mode']]) ? $entity_info['view modes'][$settings['file_view_mode']]['label'] : t('Default');
$summary = t('File view mode: @view_mode', array('@view_mode' => $file_view_mode_label));
}
return $summary;
}
/**
* Implements hook_field_prepare_view().
*/
function media_field_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items) {
// Collect all file IDs that need loading.
$fids = array();
foreach ($entities as $id => $entity) {
// Load the files from the files table.
foreach ($items[$id] as $delta => $item) {
if (!empty($item['fid'])) {
$fids[] = $item['fid'];
}
}
}
// Load the file entities.
$files = file_load_multiple($fids);
// Add the loaded file entities to the field item array.
foreach ($entities as $id => $entity) {
foreach ($items[$id] as $delta => $item) {
// If the file does not exist, mark the entire item as empty.
if (empty($files[$item['fid']])) {
unset($items[$id][$delta]);
}
else {
$items[$id][$delta]['file'] = $files[$item['fid']];
}
}
}
}
/**
* Implement hook_field_formatter_view().
*/
function media_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
$element = array();
if ($display['type'] == 'media_large_icon') {
foreach ($items as $delta => $item) {
$element[$delta] = array(
'#theme' => 'media_formatter_large_icon',
'#file' => (object) $item,
);
}
}
// Legacy support for the extra formatter added to file fields. See
// media_field_formatter_info().
if ($display['type'] == 'media') {
$files = array();
foreach ($items as $delta => $item) {
if (!empty($item['file'])) {
$files[$item['fid']] = $item['file'];
}
}
if (!empty($files)) {
$element = file_view_multiple($files, $display['settings']['file_view_mode'], 0, $langcode);
}
}
return $element;
}
/**
* Implement hook_field_widget_settings_form().
*/
function media_field_widget_settings_form($field, $instance) {
$widget = $instance['widget'];
$settings = $widget['settings'];
$form = array();
// Setup type selection form
$types = media_type_get_types();
$options = array();
foreach ($types as $key => $definition) {
$options[$key] = $definition->label;
}
$streams = file_get_stream_wrappers();
$form['allowed_types'] = array(
'#type' => 'checkboxes',
'#title' => t('Allowed remote media types'),
'#options' => $options,
'#default_value' => $settings['allowed_types'],
'#description' => t('Media types which are allowed for this field when using remote streams.'),
'#weight' => 1,
'#access' => count(file_get_stream_wrappers(STREAM_WRAPPERS_LOCAL)) != count($streams),
);
$options = array();
unset($streams['temporary']);
foreach ($streams as $scheme => $data) {
$options[$scheme] = t('@scheme (@name)', array('@scheme' => $scheme . '://', '@name' => $data['name']));
}
$form['allowed_schemes'] = array(
'#type' => 'checkboxes',
'#title' => t('Allowed URI schemes'),
'#options' => $options,
'#default_value' => $settings['allowed_schemes'],
'#description' => t('URI schemes include public:// and private:// which are the Drupal files directories, and may also refer to remote sites.'),
'#weight' => 2,
);
return $form;
}
/**
* Implements hook_field_widget_form().
*/
function media_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
$field_settings = $instance['settings'];
$widget_settings = $instance['widget']['settings'];
// @todo The Field API supports automatic serialization / unserialization, so
// this should no longer be needed. After verifying with a module that uses
// the 'data' column, remove this.
// @see media_field_widget_value()
$current_value = array();
if (isset($items[$delta])) {
$current_value = $items[$delta];
// @todo $items[$delta] is sometimes a loaded media entity (an object)
// rather than an array. This conflicts with Field API expectations (for
// example, it results in fatal errors when previewing a node with a
// multi-valued media field), so should be fixed. In the meantime, don't
// assume that $current_value is an array.
if (is_array($current_value) && isset($current_value['data']) && is_string($current_value['data'])) {
$current_value['data'] = unserialize($current_value['data']);
}
}
$element += array(
'#type' => 'media', // Would like to make this a fieldset, but throws some weird warning about element_children... not sure what it is about yet.
'#collapsed' => TRUE,
'#default_value' => $current_value,
'#required' => $instance['required'],
'#media_options' => array(
'global' => array(
'types' => array_filter($widget_settings['allowed_types']),
'schemes' => $widget_settings['allowed_schemes'],
'file_directory' => isset($field_settings['file_directory']) ? $field_settings['file_directory'] : '',
'file_extensions' => isset($field_settings['file_extensions']) ? $field_settings['file_extensions'] : media_variable_get('file_extensions'),
'max_filesize' => isset($field_settings['max_filesize']) ? $field_settings['max_filesize'] : 0,
'uri_scheme' => !empty($field['settings']['uri_scheme']) ? $field['settings']['uri_scheme'] : file_default_scheme(),
),
),
);
if ($field['type'] == 'file') {
$element['display'] = array(
'#type' => 'value',
'#value' => 1,
);
}
// Add image field specific validators.
if ($field['type'] == 'image') {
if ($field_settings['min_resolution'] || $field_settings['max_resolution']) {
$element['#media_options']['global']['min_resolution'] = $field_settings['min_resolution'];
$element['#media_options']['global']['max_resolution'] = $field_settings['max_resolution'];
}
}
return $element;
}
/**
* Implements hook_field_validate().
*
* Possible error codes:
* - 'media_remote_file_type_not_allowed': The remote file is not an allowed
* file type.
*/
function media_field_validate($obj_type, $object, $field, $instance, $langcode, $items, &$errors) {
$allowed_types = array_keys(array_filter($instance['widget']['settings']['allowed_types']));
// @TODO: merge in stuff from media_uri_value
foreach ($items as $delta => $item) {
if (empty($item['fid'])) {
return TRUE;
//@TODO: make support for submiting with just a URI here?
}
$file = file_load($item['fid']);
// Only validate allowed types if the file is remote and not local.
if (!file_entity_file_is_local($file)) {
if (!in_array($file->type, $allowed_types)) {
$errors[$field['field_name']][$langcode][$delta][] = array(
'error' => 'media_remote_file_type_not_allowed',
'message' => t('%name: Only remote files with the following types are allowed: %types-allowed.', array('%name' => t($instance['label']), '%types-allowed' => !empty($allowed_types) ? implode(', ', $allowed_types) : t('no file types selected'))),
);
}
}
}
}
/**
* Implements_hook_field_widget_error().
*/
function media_field_widget_error($element, $error, $form, &$form_state) {
form_error($element['fid'], $error['message']);
}
/**
* @todo Is this function ever called? If not, remove it. The Field API now
* supports automatic serialization / unserialization, so this should no
* longer be needed. After verifying with a module that uses the 'data'
* column, remove this.
*
* @see media_field_widget_form()
*/
function media_field_widget_value($element, $input, $form_state) {
$return = $input;
if (!is_array($return)) {
$return = array();
}
if (isset($return['data'])) {
$return['data'] = serialize($return['data']);
}
$return += array(
'fid' => 0,
'title' => '',
'data' => NULL,
);
return $return;
}
/**
* @todo The following hook_field_(insert|update|delete|delete_revision)
* implementations are nearly identical to the File module implementations of
* the same field hooks. The only differences are:
* - We pass 'media' rather than 'file' as the module argument to the
* file_usage_(add|delete)() functions.
* - We do not delete the file / media entity when its usage count goes to 0.
* We should submit a core patch to File module to make it flexible with
* respect to the above, so that we can reuse its implementation rather than
* duplicating it.
*/
/**
* Implements hook_field_insert().
*/
function media_field_insert($entity_type, $entity, $field, $instance, $langcode, &$items) {
list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);
// Add a new usage of each uploaded file.
foreach ($items as $item) {
$file = (object) $item;
file_usage_add($file, 'media', $entity_type, $id);
}
}
/**
* Implements hook_field_update().
*
* Checks for files that have been removed from the object.
*/
function media_field_update($entity_type, $entity, $field, $instance, $langcode, &$items) {
list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);
// On new revisions, all files are considered to be a new usage and no
// deletion of previous file usages are necessary.
if (!empty($entity->revision)) {
foreach ($items as $item) {
$file = (object) $item;
file_usage_add($file, 'media', $entity_type, $id);
}
return;
}
// Build a display of the current FIDs.
$current_fids = array();
foreach ($items as $item) {
$current_fids[] = $item['fid'];
}
// Compare the original field values with the ones that are being saved.
$original_fids = array();
if (!empty($entity->original->{$field['field_name']}[$langcode])) {
foreach ($entity->original->{$field['field_name']}[$langcode] as $original_item) {
$original_fids[] = $original_item['fid'];
if (isset($original_item['fid']) && !in_array($original_item['fid'], $current_fids)) {
// Decrement the file usage count by 1.
$file = (object) $original_item;
file_usage_delete($file, 'media', $entity_type, $id, 1);
}
}
}
// Add new usage entries for newly added files.
foreach ($items as $item) {
if (!in_array($item['fid'], $original_fids)) {
$file = (object) $item;
file_usage_add($file, 'media', $entity_type, $id);
}
}
}
/**
* Implements hook_field_delete().
*/
function media_field_delete($entity_type, $entity, $field, $instance, $langcode, &$items) {
list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);
// Delete all file usages within this entity.
foreach ($items as $delta => $item) {
$file = (object) $item;
file_usage_delete($file, 'media', $entity_type, $id, 0);
}
}
/**
* Implements hook_field_delete_revision().
*/
function media_field_delete_revision($entity_type, $entity, $field, $instance, $langcode, &$items) {
list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);
foreach ($items as $delta => $item) {
// @TODO: Not sure if this is correct
$file = (object)$item;
if (file_usage_delete($file, 'media', $entity_type, $id, 1)) {
$items[$delta] = NULL;
}
}
}
/**
* Implements hook_field_instance_update().
*/
function media_field_update_instance($instance, $prior_instance) {
// Clear the filter cache when updating instance settings for a media entity.
if ($instance['entity_type'] == 'media') {
media_filter_invalidate_caches();
}
}

View File

@@ -0,0 +1,478 @@
<?php
/**
* @file
* Functions related to the WYSIWYG editor and the media input filter.
*
* @TODO: Rename this file?
*/
/**
* Implements hook_wysiwyg_include_directory().
*/
function media_wysiwyg_include_directory($type) {
switch ($type) {
case 'plugins':
return 'wysiwyg_plugins';
break;
}
}
/**
* Filter callback for media markup filter.
*
* @TODO check for security probably pass text through filter_xss
* @return unknown_type
*/
function media_filter($text) {
$text = ' ' . $text . ' ';
$text = preg_replace_callback("/\[\[.*?\]\]/s", 'media_token_to_markup', $text);
return $text;
}
/**
* Filter callback for media url filter.
* @TODO: There are currently problems with this. For instance, if a file is
* to be loaded from a remote location here, it will be recreated multiple
* times, each time this filter is called. If we want to continue supporting
* this feature, we would need to probably create a new stream or other way
* to lookup a remote file w/ its local version. Probably best as a contributed
* module because of this difficulty. ~ aaron.
*/
function media_url_filter($text, $filter) {
$text = ' ' . $text . ' ';
// Need to attach the variables to the callback after the regex.
$callback = _media_url_curry('_media_url_parse_full_links', 1);
// Match absolute URLs.
$text = preg_replace_callback("`(<p>|<li>|<br\s*/?>|[ \n\r\t\(])((http://|https://)([a-zA-Z0-9@:%_+*~#?&=.,/;-]*[a-zA-Z0-9@:%_+*~#&=/;-]))([.,?!]*?)(?=(</p>|</li>|<br\s*/?>|[ \n\r\t\)]))`i", $callback, $text);
return $text;
}
/**
* If one of our allowed providers knows what to do with the url,
* then let it embed the video.
*
* @param int $filter
* The filter id.
* @param array $match
* The matched text from our regex.
*
* @return string
* The replacement text for the url.
*/
function _media_url_parse_full_links($match) {
// Get just the URL.
$match[2] = check_url(decode_entities($match[2]));
try {
$file = media_parse_to_file($match[2]);
}
catch (Exception $e) {
// Ignore errors; pass the original text for other filters to deal with.
return $match[0];
}
if ($file->fid) {
$file = file_load($file->fid);
// Generate a preview of the file
// @TODO: Allow user to change the formatter in the filter settings.
$preview = file_view_file($file, 'media_large');
$preview['#show_names'] = TRUE;
return drupal_render($preview);
}
// Nothing was parsed; return the original text.
return $match[0];
}
function _media_url_curry($func, $arity) {
return create_function('', "
\$args = func_get_args();
if(count(\$args) >= $arity)
return call_user_func_array('$func', \$args);
\$args = var_export(\$args, 1);
return create_function('','
\$a = func_get_args();
\$z = ' . \$args . ';
\$a = array_merge(\$z,\$a);
return call_user_func_array(\'$func\', \$a);
');
");
}
/**
* Parses the contents of a CSS declaration block and returns a keyed array of property names and values.
*
* @param $declarations
* One or more CSS declarations delimited by a semicolon. The same as a CSS
* declaration block (see http://www.w3.org/TR/CSS21/syndata.html#rule-sets),
* but without the opening and closing curly braces. Also the same as the
* value of an inline HTML style attribute.
*
* @return
* A keyed array. The keys are CSS property names, and the values are CSS
* property values.
*/
function media_parse_css_declarations($declarations) {
$properties = array();
foreach (array_map('trim', explode(";", $declarations)) as $declaration) {
if ($declaration != '') {
list($name, $value) = array_map('trim', explode(':', $declaration, 2));
$properties[strtolower($name)] = $value;
}
}
return $properties;
}
/**
* Replace callback to convert a media file tag into HTML markup.
*
* @param string $match
* Takes a match of tag code
* @param boolean $wysiwyg
* Set to TRUE if called from within the WYSIWYG text area editor.
* @return
* The HTML markup representation of the tag, or an empty string on failure.
*
* @see media_get_file_without_label()
* @see hook_media_token_to_markup_alter()
*/
function media_token_to_markup($match, $wysiwyg = FALSE) {
$settings = array();
$match = str_replace("[[", "", $match);
$match = str_replace("]]", "", $match);
$tag = $match[0];
try {
if (!is_string($tag)) {
throw new Exception('Unable to find matching tag');
}
$tag_info = drupal_json_decode($tag);
if (!isset($tag_info['fid'])) {
throw new Exception('No file Id');
}
if (!isset($tag_info['view_mode'])) {
// Should we log or throw an exception here instead?
// Do we need to validate the view mode for fields API?
$tag_info['view_mode'] = media_variable_get('wysiwyg_default_view_mode');
}
$file = file_load($tag_info['fid']);
if (!$file) {
throw new Exception('Could not load media object');
}
$tag_info['file'] = $file;
// Track the fid of this file in the {media_filter_usage} table.
media_filter_track_usage($file->fid);
$attributes = is_array($tag_info['attributes']) ? $tag_info['attributes'] : array();
$attribute_whitelist = media_variable_get('wysiwyg_allowed_attributes');
$settings['attributes'] = array_intersect_key($attributes, array_flip($attribute_whitelist));
// Many media formatters will want to apply width and height independently
// of the style attribute or the corresponding HTML attributes, so pull
// these two out into top-level settings. Different WYSIWYG editors have
// different behavior with respect to whether they store user-specified
// dimensions in the HTML attributes or the style attribute, so check both.
// Per http://www.w3.org/TR/html5/the-map-element.html#attr-dim-width, the
// HTML attributes are merely hints: CSS takes precedence.
if (isset($settings['attributes']['style'])) {
$css_properties = media_parse_css_declarations($settings['attributes']['style']);
foreach (array('width', 'height') as $dimension) {
if (isset($css_properties[$dimension]) && substr($css_properties[$dimension], -2) == 'px') {
$settings[$dimension] = substr($css_properties[$dimension], 0, -2);
}
elseif (isset($settings['attributes'][$dimension])) {
$settings[$dimension] = $settings['attributes'][$dimension];
}
}
}
if ($wysiwyg) {
$settings['wysiwyg'] = $wysiwyg;
}
}
catch (Exception $e) {
watchdog('media', 'Unable to render media from %tag. Error: %error', array('%tag' => $tag, '%error' => $e->getMessage()));
return '';
}
$element = media_get_file_without_label($file, $tag_info['view_mode'], $settings);
drupal_alter('media_token_to_markup', $element, $tag_info, $settings);
return drupal_render($element);
}
/**
* Builds a map of media tags in the element being rendered to their rendered HTML.
*
* The map is stored in JS, so we can transform them when the editor is being displayed.
*
* @param array $element
*/
function media_pre_render_text_format($element) {
// filter_process_format() copies properties to the expanded 'value' child
// element.
if (!isset($element['format'])) {
return $element;
}
$field = &$element['value'];
$settings = array(
'field' => $field['#id'],
);
$tagmap = _media_generate_tagMap($field['#value']);
if (isset($tagmap)) {
drupal_add_js(array('tagmap' => $tagmap), 'setting');
}
return $element;
}
/**
* Generates an array of [inline tags] => <html> to be used in filter
* replacement and to add the mapping to JS.
* @param
* The String containing text and html markup of textarea
* @return
* An associative array with tag code as key and html markup as the value.
*
* @see media_process_form()
* @see media_token_to_markup()
*/
function _media_generate_tagMap($text) {
// Making $tagmap static as this function is called many times and
// adds duplicate markup for each tag code in Drupal.settings JS,
// so in media_process_form it adds something like tagCode:<markup>,
// <markup> and when we replace in attach see two duplicate images
// for one tagCode. Making static would make function remember value
// between function calls. Since media_process_form is multiple times
// with same form, this function is also called multiple times.
static $tagmap = array();
preg_match_all("/\[\[.*?\]\]/s", $text, $matches, PREG_SET_ORDER);
foreach ($matches as $match) {
// We see if tagContent is already in $tagMap, if not we add it
// to $tagmap. If we return an empty array, we break embeddings of the same
// media multiple times.
if (empty($tagmap[$match[0]])) {
// @TODO: Total HACK, but better than nothing.
// We should find a better way of cleaning this up.
if ($markup_for_media = media_token_to_markup($match, TRUE)) {
$tagmap[$match[0]] = $markup_for_media;
}
else {
$missing = file_create_url(drupal_get_path('module', 'media') . '/images/icons/default/image-x-generic.png');
$tagmap[$match[0]] = '<div><img src="' . $missing . '" width="100px" height="100px"/></div>';
}
}
}
return $tagmap;
}
/**
* Form callback used when embedding media.
*
* Allows the user to pick a format for their media file.
* Can also have additional params depending on the media type.
*/
function media_format_form($form, $form_state, $file) {
$form = array();
$form['#media'] = $file;
$entity_info = entity_get_info('file');
$view_modes = $entity_info['view modes'];
drupal_alter('media_wysiwyg_allowed_view_modes', $view_modes, $file);
$formats = $options = array();
foreach ($view_modes as $view_mode => $view_mode_info) {
// Don't present the user with an option to choose a view mode in which the
// file is hidden.
$extra_fields = field_extra_fields_get_display('file', $file->type, $view_mode);
if (!$extra_fields['file']['visible']) {
continue;
}
//@TODO: Display more verbose information about which formatter and what it does.
$options[$view_mode] = $view_mode_info['label'];
$element = media_get_file_without_label($file, $view_mode, array('wysiwyg' => TRUE));
// Make a pretty name out of this.
$formats[$view_mode] = drupal_render($element);
}
// Add the previews back into the form array so they can be altered.
$form['#formats'] = &$formats;
if (!count($formats)) {
throw new Exception('Unable to continue, no available formats for displaying media.');
return;
}
$default_view_mode = media_variable_get('wysiwyg_default_view_mode');
if (!isset($formats[$default_view_mode])) {
$default_view_mode = key($formats);
}
// Add the previews by reference so that they can easily be altered by
// changing $form['#formats'].
$settings['media']['formatFormFormats'] = &$formats;
$form['#attached']['js'][] = array('data' => $settings, 'type' => 'setting');
// Add the required libraries, JavaScript and CSS for the form.
$form['#attached']['library'][] = array('media', 'media_base');
$form['#attached']['library'][] = array('system', 'form');
$form['#attached']['js'][] = drupal_get_path('module', 'media') . '/js/media.format_form.js';
$form['#attached']['css'][] = drupal_get_path('module', 'media') . '/css/media-format-form.css';
$form['heading'] = array(
'#type' => 'markup',
'#prefix' => '<h1 class="title">',
'#suffix' => '</h1>',
'#markup' => t('Embedding %filename', array('%filename' => $file->filename)),
);
$preview = media_get_thumbnail_preview($file);
$form['preview'] = array(
'#type' => 'markup',
'#title' => check_plain(basename($file->uri)),
'#markup' => drupal_render($preview),
);
// These will get passed on to WYSIWYG
$form['options'] = array(
'#type' => 'fieldset',
'#title' => t('options'),
);
$form['options']['format'] = array(
'#type' => 'select',
'#title' => t('Current format is'),
'#options' => $options,
'#default_value' => $default_view_mode
);
// Similar to a form_alter, but we want this to run first so that media.types.inc
// can add the fields specific to a given type (like alt tags on media).
// If implemented as an alter, this might not happen, making other alters not
// be able to work on those fields.
// @TODO: We need to pass in existing values for those attributes.
drupal_alter('media_format_form_prepare', $form, $form_state, $file);
if (!element_children($form['options'])) {
$form['options']['#attributes'] = array('style' => 'display:none');
}
return $form;
}
/**
* Returns a drupal_render() array for just the file portion of a file entity.
*
* Optional custom settings can override how the file is displayed.
*/
function media_get_file_without_label($file, $view_mode, $settings = array()) {
$file->override = $settings;
// Legacy support for Styles module plugins that expect overridden HTML
// attributes in $file->override rather than $file->override['attributes'].
if (isset($settings['attributes'])) {
$file->override += $settings['attributes'];
}
$element = file_view_file($file, $view_mode);
// The formatter invoked by file_view_file() can use $file->override to
// customize the returned render array to match the requested settings. To
// support simple formatters that don't do this, set the element attributes to
// what was requested, but not if the formatter applied its own logic for
// element attributes.
if (!isset($element['#attributes']) && isset($settings['attributes'])) {
$element['#attributes'] = $settings['attributes'];
// While this function may be called for any file type, images are a common
// use-case. theme_image() and theme_image_style() require the 'alt'
// attribute to be passed separately from the 'attributes' array (see
// http://drupal.org/node/999338). Until that's fixed, implement this
// special-case logic. Image formatters using other theme functions are
// responsible for their own 'alt' attribute handling. See
// theme_media_formatter_large_icon() for an example.
if (isset($settings['attributes']['alt']) && !isset($element['#alt']) && isset($element['#theme']) && in_array($element['#theme'], array('image', 'image_style'))) {
$element['#alt'] = $settings['attributes']['alt'];
}
}
return $element;
}
/**
* Clears caches that may be affected by the media filter.
*
* The media filter calls file_load(). This means that if a file object
* is updated, the check_markup() and field caches could return stale content.
* There are several possible approaches to deal with this:
* - Disable filter caching in media_filter_info(), this was found to cause a
* 30% performance hit from profiling four node teasers, due to both the
* media filter itself, and other filters that can't be cached.
* - Clear the filter and field caches whenever any media node is updated, this
* would ensure cache coherency but would reduce the effectiveness of those
* caches on high traffic sites with lots of media content updates.
* - The approach taken here: Record the fid of all media objects that are
* referenced by the media filter. Only clear the filter and field caches
* when one of these is updated, as opposed to all media objects.
* - @todo: consider an EntityFieldQuery to limit cache clearing to only those
* entities that use a text format with the media filter, possibly checking
* the contents of those fields to further limit this to fields referencing
* the media object being updated. This would need to be implemented
* carefully to avoid scalability issues with large result sets, and may
* not be worth the effort.
*
* @param $fid
* Optional media fid being updated. If not given, the cache will be cleared
* as long as any file is referenced.
*/
function media_filter_invalidate_caches($fid = FALSE) {
// If fid is passed, confirm that it has previously been referenced by the
// media filter. If not, clear the cache if the {media_filter_usage} has any
// valid records.
if (($fid && db_query('SELECT fid FROM {media_filter_usage} WHERE fid = :fid', array(':fid' => $fid))->fetchField()) || (!$fid && media_filter_usage_has_records())) {
// @todo: support entity cache, either via a hook, or using module_exists().
cache_clear_all('*', 'cache_filter', TRUE);
cache_clear_all('*', 'cache_field', TRUE);
}
}
/**
* Determines if the {media_filter_usage} table has any entries.
*/
function media_filter_usage_has_records() {
return (bool) db_query_range('SELECT 1 FROM {media_filter_usage} WHERE fid > :fid', 0, 1, array(':fid' => 0))->fetchField();
}
/**
* Tracks usage of media fids by the media filter.
*
* @param $fid
* The media fid.
*/
function media_filter_track_usage($fid) {
// This function only tracks when fids are found by the media filter.
// It would be impractical to check when formatted text is edited to remove
// references to fids, however by keeping a timestamp, we can implement
// rudimentary garbage collection in hook_flush_caches().
// However we only need to track that an fid has ever been referenced,
// not every time, so avoid updating this table more than once per month,
// per fid.
$timestamp = db_query('SELECT timestamp FROM {media_filter_usage} WHERE fid = :fid', array(':fid' => $fid))->fetchField();
if (!$timestamp || $timestamp <= REQUEST_TIME - 86400 * 30) {
db_merge('media_filter_usage')->key(array('fid' => $fid))->fields(array('fid' => $fid, 'timestamp' => REQUEST_TIME))->execute();
}
}

View File

@@ -0,0 +1,436 @@
<?php
/**
* @file
* Common pages for the Media module.
*/
/**
* Menu callback; view a single file entity.
*/
function media_view_page($file) {
// @todo Implement granular editorial access: http://drupal.org/node/696970.
// In the meantime, protect information about private files from being
// discovered by unprivileged users. File IDs are autoincrement, so one can
// attempt discovery by trying to access different media/ID paths. See also
// media_browser_list(). This logic potentially belongs within
// media_access(), but that would require extending that function's
// signature to accept a $file paramter, and this is temporary code anyway.
if (!user_access('administer media') && (file_uri_scheme($file->uri) === 'private')) {
return MENU_ACCESS_DENIED;
}
drupal_set_title($file->filename);
return file_view($file, 'media_original');
}
/**
* Menu callback; presents the Media editing form.
*/
function media_page_edit($file) {
drupal_set_title(t('<em>Edit @type</em> @title', array('@type' => ($file->type != FILE_TYPE_NONE ? $file->type : ''), '@title' => $file->filename)), PASS_THROUGH);
return drupal_get_form('media_edit', $file);
}
/**
* Menu callback; presents the Media editing form for multiple file entities.
*/
function media_page_multiedit($files) {
if (!module_exists('multiform')) {
drupal_set_message(t('To edit multiple media items, you must install the multiform module.'));
}
$i = 0;
$forms = array();
foreach ($files as $file) {
// To maintain unique form_ids, increment this counter.
// @see media_forms().
$i++;
$forms[] = array("media_edit_$i", $file);
}
$form = call_user_func_array('multiform_get_form', $forms);
$form['#attributes']['class'][] = 'media-multiedit-form';
unset($form['buttons']['Delete']);
// Would be nice to add this to show a message, but not working.
// Can debug.
//$form['buttons']['Save']['#submit'][] = 'media_page_multiedit_submit';
drupal_set_title(t('Editing multiple media files'));
return $form;
}
/**
* Menu callback; shows delete confirmation form.
*/
function media_page_delete($file) {
drupal_set_title(t('<em>Delete @type</em> @title', array('@type' => ($file->type != FILE_TYPE_NONE ? $file->type : ''), '@title' => $file->filename)), PASS_THROUGH);
// Don't bother showing the form if the item is in use, since we won't allow
// them to delete it anyway.
$references = file_usage_list($file);
if (!empty($references)) {
return t('The file %title is in use and cannot be deleted.', array('%title' => $file->filename));
}
else {
$files = array($file->fid => $file);
return drupal_get_form('media_multiple_delete_confirm', $files, '<front>', 'media/' . $file->fid);
}
}
/**
* Confirm the request to delete files.
*/
function media_multiple_delete_confirm($form, &$form_state, $files, $redirect_on_success = NULL, $redirect_on_cancel = NULL) {
$form['files'] = array('#tree' => TRUE);
$form['file_titles'] = array('#theme' => 'item_list');
foreach ($files as $fid => $value) {
$title = db_query('SELECT filename FROM {file_managed} WHERE fid = :fid', array(':fid' => $fid))->fetchField();
$form['files'][$fid] = array(
'#type' => 'value',
'#value' => $fid,
);
$form['file_titles']['#items'][] = check_plain($title);
}
$form['operation'] = array('#type' => 'hidden', '#value' => 'delete');
if (isset($redirect_on_success)) {
$form['redirect_on_success'] = array(
'#type' => 'value',
'#value' => $redirect_on_success,
);
}
$form['#submit'][] = 'media_multiple_delete_confirm_submit';
$confirm_question = format_plural(count($files),
'Are you sure you want to delete this item?',
'Are you sure you want to delete these items?');
return confirm_form($form,
$confirm_question,
isset($redirect_on_cancel) ? $redirect_on_cancel : current_path(),
t('This action cannot be undone.'),
t('Delete'),
t('Cancel'));
}
/**
* Attempt to delete files and notify the user of the result.
*/
function media_multiple_delete_confirm_submit($form, &$form_state) {
if ($form_state['values']['confirm']) {
$results = array();
$files = array_keys($form_state['values']['files']);
foreach ($files as $fid) {
$file = file_load($fid);
$files[$fid] = $file;
$results[$fid] = file_delete($file);
}
// The result of file_delete can be an array if the file is in use, or TRUE/FALSE.
foreach ($results as $fid => $result) {
if (is_array($result)) {
drupal_set_message(t('The file @title is in use and cannot be deleted.', array('@title' => $files[$fid]->filename)), 'warning');
}
elseif (!$result) {
drupal_set_message(t('The file @title was not deleted due to an error.', array('@title' => $files[$fid]->filename)), 'error');
}
else {
$message = t('File @title was deleted', array('@title' => $files[$fid]->filename));
watchdog('media', $message);
drupal_set_message($message);
}
}
if (isset($form_state['values']['redirect_on_success'])) {
$form_state['redirect'] = $form_state['values']['redirect_on_success'];
}
}
}
/**
* Form callback for adding media via an upload form.
* @todo: should use the AJAX uploader
*/
function media_add_upload($form, &$form_state, $params = array()) {
// Set up file upload validators.
$validators = array();
// Validate file extensions. If there are no file extensions in $params and
// there are no Media defaults, there is no file extension validation.
if (!empty($params['file_extensions'])) {
$validators['file_validate_extensions'] = array($params['file_extensions']);
}
elseif ($tmp = media_variable_get('file_extensions')) {
$validators['file_validate_extensions'] = array($tmp);
}
// Validate file size but do not allow anything higher than file_upload_max_size().
$max_filesize = file_upload_max_size();
if (!empty($params['max_filesize']) && $params['max_filesize'] < $max_filesize) {
$validators['file_validate_size'] = array(parse_size($params['max_filesize']));
}
elseif (($tmp = media_variable_get('max_filesize')) && $tmp < $max_filesize) {
$validators['file_validate_size'] = array(parse_size($tmp));
}
else {
$validators['file_validate_size'] = array($max_filesize);
}
// Add image validators.
$params += array('min_resolution' => 0, 'max_resolution' => 0);
if ($params['min_resolution'] || $params['max_resolution']) {
$validators['file_validate_image_resolution'] = array($params['max_resolution'], $params['min_resolution']);
}
$form['#validators'] = $validators;
$form['upload'] = array(
'#type' => 'file',
'#title' => t('Upload a new file'),
'#description' => theme('file_upload_help', array('description' => '', 'upload_validators' => $validators)),
'#upload_validators' => $validators,
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
return $form;
}
/**
* Validate the generic file upload with the global media settings.
*/
function media_add_upload_validate($form, &$form_state) {
// Save the file as a temporary file.
$file = file_save_upload('upload', $form['#validators']);
if ($file === NULL) {
form_set_error('upload', t("No file appears to have been selected."));
}
elseif ($file === FALSE) {
form_set_error('upload', t('File upload error.'));
}
else {
$form_state['values']['upload'] = $file;
}
}
/**
* Upload a file.
*/
function media_add_upload_submit($form, &$form_state) {
$params = $form_state['build_info']['args'][0];
$file = $form_state['values']['upload'];
// The media browser widget does not use the 'display' field.
$file->display = TRUE;
// Change the file status from temporary to permanent.
_media_save_file_permenently($file);
// Determine what URI scheme this file should use.
$scheme = !empty($params['uri_scheme']) && file_stream_wrapper_valid_scheme($params['uri_scheme']) ? $params['uri_scheme'] : file_default_scheme();
$scheme .= '://';
// Prepare the file's subdirectory path.
$directory = '';
if (!empty($params['file_directory'])) {
$directory = token_replace($params['file_directory']) . '/';
// If the directory isn't writable, or doesn't exist and can't be created,
// the upload will fail.
$prepare_directory = file_stream_wrapper_uri_normalize($scheme . $directory);
if (!file_prepare_directory($prepare_directory, FILE_CREATE_DIRECTORY)) {
drupal_set_message(t('The file directory @dir does not exist or is not writable. Please contact an administrator.', array('@dir' => $prepare_directory)), 'error');
return;
}
}
// Compose the file's permanent destination.
$destination = file_stream_wrapper_uri_normalize($scheme . $directory . $file->filename);
// Save the uploaded file to the permanent location.
$file = file_move($file, $destination, FILE_EXISTS_RENAME);
if ($file) {
drupal_set_message(t('The file @name was uploaded', array('@name' => $file->filename)));
}
else {
drupal_set_message(t('An error occurred and no file was uploaded.'), 'error');
return;
}
$form_state['redirect'] = array('media/browser', array('query' => array('render' => 'media-popup', 'fid' => $file->fid)));
}
function media_add_upload_multiple($form, &$form_state, $params = array()) {
$form = media_add_upload($form, $form_state, $params);
unset($form['upload']['#title']);
// The validators will be set from plupload anyway. This isn't pretty, but don't
// it to show up twice.
unset($form['upload']['#description']);
$form['upload']['#type'] = 'plupload';
$form['submit']['#value'] = t('Start upload');
return $form;
}
function media_add_upload_multiple_submit($form, &$form_state) {
$scheme = variable_get('file_default_scheme', 'public') . '://';
$saved_files = array();
// We can't use file_save_upload() because of http://www.jacobsingh.name/content/tight-coupling-no-not
foreach ($form_state['values']['upload'] as $uploaded_file) {
if ($uploaded_file['status'] == 'done') {
$source = $uploaded_file['tmppath'];
$destination = file_stream_wrapper_uri_normalize($scheme . $uploaded_file['name']);
// Rename it to its original name, and put it in its final home.
// Note - not using file_move here because if we call file_get_mime
// (in file_uri_to_object) while it has a .tmp extension, it horks.
$destination = file_unmanaged_move($source, $destination, FILE_EXISTS_RENAME);
$file = file_uri_to_object($destination);
file_save($file);
_media_save_file_permenently($file);
$saved_files[] = $file;
}
else {
// @todo: move this to element validate or something.
form_set_error('pud', t('The specified file %name could not be uploaded.', array('%name' => $uploaded_file['name'])));
}
}
// Get a list of fids to pass back.
$fids = array();
foreach ($saved_files as $file) {
$fids[] = $file->fid;
}
$form_state['redirect'] = array('media/browser', array('query' => array('render' => 'media-popup', 'fid' => $fids)));
}
/**
* Form builder: Builds the edit file form.
*/
function media_edit($form, $form_state, $file) {
$form_state['file'] = $file;
field_attach_form('file', $file, $form, $form_state);
$form['#attached'] = array(
'css' => array(drupal_get_path('module', 'media') . '/css/media.css'),
);
// Not sure about this class name, seems to indicate the style.
$form['#attributes']['class'][] = 'media-image-left';
$form['#attributes']['class'][] = 'media-edit-form';
$form['preview'] = file_view_file($file, 'media_preview');
$form['preview']['#weight'] = -10;
$form['preview']['#suffix'] = '<div class="no-overflow">';
// Add the buttons.
$form['actions'] = array('#type' => 'actions');
$form['actions']['#prefix'] = '</div>';
$form['actions']['delete'] = array(
'#type' => 'submit',
'#value' => t('Delete'),
'#weight' => 15,
'#submit' => array('media_edit_delete_submit'),
);
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => t('Save'),
'#weight' => 5,
'#submit' => array('media_edit_submit'),
);
// Add internal file properties needed by media_edit_validate().
foreach (array('fid', 'type') as $key) {
$form[$key] = array('#type' => 'value', '#value' => $file->$key);
}
return $form;
}
/**
* Form validation handler for the media edit form.
*/
function media_edit_validate($form, &$form_state) {
entity_form_field_validate('file', $form, $form_state);
}
/**
* Form submit handler for the media submit form.
*/
function media_edit_submit($form, &$form_state) {
$file = $form_state['file'];
entity_form_submit_build_entity('file', $file, $form, $form_state);
file_save($file);
$form_state['redirect'] = 'media/' . $file->fid;
}
/**
* Form submit handler for the Delete button on the media edit form.
*/
function media_edit_delete_submit($form, &$form_state) {
$fid = $form_state['values']['fid'];
$destination = array();
if (isset($_GET['destination'])) {
$destination = drupal_get_destination();
unset($_GET['destination']);
}
$form_state['redirect'] = array('media/' . $fid . '/delete', array('query' => $destination));
}
function media_add_remote($form, &$form_state) {
// Totally prototyping code to show designs.
$form['sources'] = array(
'#type' => 'vertical_tabs',
'#title' => 'Sources',
);
$form['sources']['paste'] = array(
'#type' => 'fieldset',
'#title' => 'Paste URL or embed code',
);
$providers = '';
$providers .= '<img style="height:50px; margin:20px" src="http://www.macuser.com/2008/10/09/top_youtube_logo_31_Dec_06.jpg">';
$providers .= '<img style="height:50px; margin:20px" src="http://jasonhilldesign.com/FlikrLogo.jpg">';
$form['sources']['paste']['code'] = array(
'#type' => 'textarea',
'#title' => t('URL or embed code'),
'#description' => t('The following providers are supported: <br/>' . $providers),
);
$form['sources']['youtube'] = array(
'#type' => 'fieldset',
'#title' => 'YouTube',
'#description' => t(''),
'#attributes' => array('style' => 'height: 300px; overflow:auto'),
);
$form['sources']['flikr'] = array(
'#type' => 'fieldset',
'#title' => 'Flikr',
);
$box = '<div style="width:100px; height:100px; border:1px solid blue; padding:10px; float:left; margin:5px;"> Video </div>';
$boxes = '';
for ($i = 0; $i < 10; $i++) {
$boxes .= $box;
}
$form['sources']['youtube']['stupid'] = array(
'#markup' => $boxes,
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Import'),
'#attributes' => array('style' => 'float:right'),
'#suffix' => '<br style="clear:both" />',
);
return $form;
}

View File

@@ -0,0 +1,332 @@
<?php
/**
* @file
* Media Theming
*
* Theming functions for the Media module.
*/
/**
* Display the media file browser.
* @TODO this is depreciated I think
* @param array $element
* The form element.
* @return string
*/
function theme_media_file_browser($element) {
// Add the CSS for our display.
$output = '<div class="media browser">' . $element . '</div>';
return $output;
}
/**
* Display a media file list.
* @TODO this is depreciated I think
* @param array $element
* The form element.
* @return string
*/
function theme_media_file_list($element) {
// Add the CSS for our display.
return '<div class="media-file-list">' . theme('form_element', $element, $element['#children']) . '</div>';
}
/**
* Display a browser pane.
* @TODO this is depreciated I think
*
* @param array $form
* The form element.
* @return string
*/
function theme_media_browser_pane($form) {
return;
$output = array();
// render the drawers
$output[] = '<div' . drupal_attributes($form['#attributes']) . '>';
// render the drawer list
$output[] = ' <div class="browser drawers">';
$output[] = drupal_render_form(null, $form['drawers']);
$output[] = ' </div>';
// render the drawer displays
$output[] = drupal_render_form(null, $form);
$output[] = '</div>';
return implode("\n", $output);
}
/**
* Default theming function for creating the browser frame.
* Assumes an array of file objects as $files and an
* array of $parameters
* @param $variables
* array of variables
* @return unknown_type
*/
function theme_media_browser_content_frame($variables) {
// Pull out all the variables into a usable form
extract($variables);
// Did we get any files back?
if (! count($files)) {
// @TODO display no files found
}
$html = array();
// On the first invocation, load javascript and build the browser frame
if ($invoke) {
}
// Render the results limiter
$html[] = theme('media_browser_control_result_limit', array('parameters' => $parameters));
// Render the actual content
$form = drupal_get_form('media_file_listing_form', $files, $parameters);
$html[] = drupal_render($form);
// Make sure to close the wrapping div
if ($invoke) {
$html[] = '</div>';
}
return implode("\n", $html);
}
/**
* Display a item list of files as thumbnails. Implements
* the admin thumbnail theme for now- serves as a wrapper
*
* @param $files
* An array of file objects to display.
* @return
*/
function theme_media_browser_thumbnails($variables) {
$files = $variables['files'];
$style_name = $variables['style_name'];
$thumbnails = array();
foreach ($files as $file) {
$thumbnails[] = theme('media_admin_thumbnail', array('file' => $file, 'style_name' => $style_name));
}
return theme('item_list', array('items' => $thumbnails, 'attributes' => array('class' => 'media_content_navigator results')));
}
/**
* Theme a thumbnail.
* @param $variables
* array items being passed in
*/
function theme_media_admin_thumbnail($variables) {
$path = drupal_get_path('module', 'media');
$file = $variables['file'];
$style_name = $variables['style_name'];
if (isset($file)) {
$file_url = file_create_url($file->uri);
}
else {
return '';
}
$output = '';
if (module_exists('styles')) {
$thumbnail = theme('styles',
array(
'field_type' => 'file',
'style_name' => $style_name,
'uri' => $file->uri,
'description' => t('Thumbnail for !filename.', array('!filename' => $file->filename)),
'object' => $variables['file'],
));
}
else {
// Display a thumbnail for images.
if (strstr($file->filemime, 'image')) {
$thumbnail = theme('image_style',
array(
'style_name' => 'thumbnail',
'path' => $file->uri,
'alt' => t('Thumbnail for !filename.', array('!filename' => $file->filename)),
)
);
}
// Display the 'unknown' icon for other file types.
else {
$thumbnail = theme('image',
array(
'path' => $path . '/images/file-unknown.png',
'alt' => t('Thumbnail for !filename.', array('!filename' => $file->filename)),
'attributes' => array('class' => 'file-unknown'),
));
}
}
$output .= l($thumbnail,
$file_url,
array(
'html' => TRUE,
'attributes' => array('class' => 'media-thumbnail'),
));
return $output;
}
/**
* Theme operations for a thumbnail.
*/
function theme_media_admin_thumbnail_operations($variables) {
$destination = drupal_get_destination();
$file = $variables['file'];
$output = l(t('edit'), 'media/' . $file->fid . '/edit', array('query' => $destination));
return $output;
}
/**
* Add messages to the page.
*/
function template_preprocess_media_dialog_page(&$variables) {
$variables['messages'] = theme('status_messages');
}
/* ******************************************** */
/* Content navigation controls */
/* ******************************************** */
/**
* Theme function to display the results limiting- 10, 30, 50 results
* per page.
*
* @param $variables
* array parameters
* @return unknown
*/
function theme_media_browser_control_result_limit($variables) {
// Pull out all the variables into a usable form
extract($variables);
if (!isset($limits)) {
$limits = array(10, 30, 50);
}
// @NOTE these do not need to be aware of the current
// page because clicking them will reset the
// display to 1 -> $limit
$parameters['page'] = 0;
// save the active limit
$current_limit = $parameters['limit'];
$per_display = array();
foreach ($limits as $limit) {
if ($limit == $current_limit) {
$class = 'active';
}
else {
$class = '';
}
// set the value of this limit parameter to this limit value
$parameters['limit'] = $limit;
$per_display[] = l($limit, $limit, array('query' => $parameters, 'attributes' => array('class' => $class)));
}
return theme('item_list', array('items' => $per_display, 'attributes' => array('class' => 'result_limit')));
}
/**
* Stolen from file.module theme_file_link
*
* @param $variables
* An associative array containing:
* - file: A file object to which the link will be created.
*/
function theme_media_link($variables) {
$file = $variables['file'];
$url = 'media/' . $file->fid;
$icon = theme('file_icon', array('file' => $file));
// Set options as per anchor format described at
// http://microformats.org/wiki/file-format-examples
$options = array(
'attributes' => array(
'type' => $file->filemime . '; length=' . $file->filesize,
),
);
// Use the description as the link text if available.
if (empty($file->description)) {
$link_text = check_plain($file->filename);
}
else {
$link_text = check_plain($file->description);
$options['attributes']['title'] = check_plain($file->filename);
}
return '<span class="file">' . $icon . ' ' . l($link_text, $url, $options) . '</span>';
}
/**
* Adds a wrapper around a preview of a media file.
* @param unknown_type $element
* @return unknown_type
*/
function theme_media_thumbnail($variables) {
$label = '';
$element = $variables['element'];
$destination = drupal_get_destination();
// Wrappers to go around the thumbnail
$prefix = '<div class="media-item"><div class="media-thumbnail">';
$suffix = '</div></div>';
// Arguments for the thumbnail link
$thumb = $element['#children'];
$target = 'media/' . $element['#file']->fid . '/edit';
$options = array('query' => $destination, 'html' => TRUE, 'attributes' => array('title' => t('Click to edit details')));
// Element should be a field renderable array... This is a little wonky - admitted.
if (!empty($element['#show_names']) && $element['#name']) {
$label = '<div class="label-wrapper"><label class="media-filename">' . $element['#name'] . '</label></div>';
}
// How can I attach CSS here?
//$element['#attached']['css'][] = drupal_get_path('module', 'media') . '/css/media.css';
drupal_add_css(drupal_get_path('module', 'media') . '/css/media.css');
$output = $prefix;
if (!empty($element['#add_link'])) {
$output .= l($thumb, $target, $options);
}
else {
$output .= $thumb;
}
$output .= $label . $suffix;
return $output;
}
function template_preprocess_media_thumbnail(&$variables) {
// Set the name for the thumbnail to be the filename. This is done here so
// that other modules can hijack the name displayed if it should not be the
// filename.
$variables['element']['#name'] = isset($variables['element']['#file']->filename) ? check_plain($variables['element']['#file']->filename) : NULL;
}
/**
* Field formatter for displaying a file as a large icon.
*/
function theme_media_formatter_large_icon($variables) {
$file = $variables['file'];
$icon_dir = media_variable_get('icon_base_directory') . '/' . media_variable_get('icon_set');
$icon = file_icon_path($file, $icon_dir);
$variables['path'] = $icon;
// theme_image() requires the 'alt' attribute passed as its own variable.
// @see http://drupal.org/node/999338
if (!isset($variables['alt']) && isset($variables['attributes']['alt'])) {
$variables['alt'] = $variables['attributes']['alt'];
}
return theme('image', $variables);
}

View File

@@ -0,0 +1,321 @@
<?php
/**
* @file
* Helper functions related to media types. CRUD for saving their settings mainly.
*
* Also contains the media entity class definition.
* @see media.install for a list of the base types.
*/
/**
* Implements hook_file_type_info().
*/
function media_file_type_info() {
$types = array();
foreach (media_type_get_types() as $type => $type_object) {
$types[$type] = array(
'label' => $type_object->label,
'weight' => $type_object->weight,
'claim callback' => 'media_file_type_media_claim',
'default view callback' => 'media_file_type_media_default_view',
);
}
return $types;
}
/**
* Implements hook_file_type_TYPE_claim().
*
* Returns TRUE if the passed in file should be assigned the passed in type.
*/
function media_file_type_media_claim($file, $type) {
if (($type_object = media_type_load($type)) && ($function = $type_object->type_callback) && function_exists($function) && $function($file, $type_object->type_callback_args)) {
return TRUE;
}
}
/**
* Implements hook_file_type_TYPE_default_view().
*
* Returns a drupal_render() array for displaying the file when there are no
* administrator-configured formatters, or none of the configured ones return a
* display.
*/
function media_file_type_media_default_view($file, $view_mode, $langcode) {
// During preview, or when custom attribute values are needed on the displayed
// element, use a media icon.
if ($view_mode == 'media_preview' || isset($file->override)) {
return array(
'#theme' => 'media_formatter_large_icon',
'#file' => $file,
);
}
// Finally, fall back to File module's generic file display.
return array(
'#theme' => 'file_link',
'#file' => $file,
);
}
/**
* Update an existing media type or create a new one.
*
* The default media types are currently 'Audio', 'Image', 'Video', and
* 'Other', which are defined in media_install().
*
* @param object &$type
* $type is an object with the following fields:
* ->name => The name of the media asset type, such as 'video';
* ->label => The human readable name;
* ->base => boolean: If the media type cannot be removed.
* ->type_callback => Function call to filter an instance to its bundle.
* ->type_callback_args => An array of args to be passed to type_callback.
* @return void;
*/
function media_type_save(&$type) {
if (empty($type->name)) {
throw new Exception('Enable to add type, name not provided');
}
$type = media_type_set_defaults($type);
if (!is_array($type->type_callback_args)) {
throw new Exception('type_callback_args should be an array');
}
$type->type_callback_args = serialize($type->type_callback_args);
$ret = db_merge('media_type')
->key(array('name' => $type->name))
->fields((array)$type)
->execute();
// Clear the caches
drupal_static_reset('media_type_get_types');
drupal_static_reset('media_type_get_mime_map');
return;
}
/**
* @todo Remove this function after ensuring that nothing (including update
* functions) call it. It is deprecated with the change from media entity
* containing a file field to just a file entity.
*/
function media_type_configure_formatters($name, $view_modes_to_formatters) {
}
/**
* Loads a media type based on its machine name.
*
* @param string $name
* @return StdClass
*/
function media_type_load($name) {
$types = media_type_get_types();
if (isset($types[$name])) {
return $types[$name];
}
}
/**
* Loads all media types into an array keyed by machine name and sorted
* and weighted lexographically.
*
* @return array
* Media types keyed by machine name.
*/
function media_type_get_types() {
$types =& drupal_static(__FUNCTION__);
if (!$types) {
$types = db_select('media_type', 'mt')
->orderBy('weight')
->fields('mt')
->execute()
->fetchAllAssoc('name'); // Will key by the name field.
foreach ($types as &$type) {
// I really hate this.
$type->type_callback_args = unserialize($type->type_callback_args);
}
}
return $types;
}
/**
* Create the basic class and defaults for a media entity bundle type.
*/
function media_type_set_defaults($info) {
$type = new stdClass();
// This is used to filter a file to the proper bundle.
$type->type_callback = 'media_is_type';
$type->type_callback_args = array();
$type->weight = 0;
foreach ($info as $k => $v) {
$type->{$k} = $v;
}
return $type;
}
/**
* Determines the type of media a passed in $file is.
*
* @todo: integrate this properly with other APIs in media when fields is done
* @param unknown_type $file
* @return unknown_type
*/
function media_get_type($file) {
$types = media_type_get_types();
foreach ($types as $name => $type) {
if (call_user_func_array($type->type_callback, array($file, $type->type_callback_args))) {
return $name;
}
}
throw new Exception('Unable to determine type of media from ' . var_export($file, 1));
}
/**
* Default callback used to determine if a file is of a given type.
*
* @TODO: document 'any' and 'all' matching.
*
* @param $file
* The file object.
* @param $args
*
* @return unknown_type
*/
function media_is_type($file, $args) {
$match_type = !empty($args['match_type']) ? 'any' : $args['match_type'];
$match_all = $match_type == 'all';
if (!empty($args['mimetypes'])) {
foreach ($args['mimetypes'] as $expression) {
if (preg_match($expression, $file->filemime)) {
if (!$match_all) {
return TRUE;
}
}
}
// Was not matched, so return
if ($match_all) {
return FALSE;
}
}
if (!empty($args['extensions'])) {
if (in_array(pathinfo($file->uri, PATHINFO_EXTENSION), $args['extensions'])) {
if (!$match_all) {
return TRUE;
}
}
// Was not matched, so return
if ($match_all) {
return FALSE;
}
}
if (!empty($args['streams'])) {
}
}
/**
* Implements hook_media_format_form_prepare_alter().
*/
function media_media_format_form_prepare_alter(&$form, &$form_state, $file) {
switch ($file->type) {
case 'image':
$form['options']['alt'] = array(
'#type' => 'textfield',
'#title' => t('Description'),
'#default_value' => $file->filename,
'#description' => t('Alternate text a user will see if the image is not available'),
);
break;
}
}
/**
* Returns the number of files that need to be converted to media.
*/
function media_type_invalid_files_count() {
return db_select('file_managed', 'fm')
->condition('type', FILE_TYPE_NONE)
->countQuery()
->execute()
->fetchField();
}
/**
* Adds a value for the type column in files_managed.
*
* If $update_existing is TRUE, will update the type of files with an existing type value.
*
* @param boolean $update_existing
* @param integer $no_to_update
* @param integer $offset
*
* @return array
* A list of updated file ids
*/
function media_type_batch_update($update_existing = FALSE, $no_to_update = NULL, $offset = 0) {
$results = array();
$query = db_select('file_managed', 'fm')
->fields('fm', array('fid'));
if (!$update_existing) {
$query->condition('type', FILE_TYPE_NONE);
}
if ($no_to_update) {
$query->range($offset, $no_to_update);
}
elseif ($offset) {
$query->range($offset);
}
$fids = $query->execute()->fetchCol();
foreach ($fids as $fid) {
$file = file_load($fid);
if (!$file->fid) {
throw new Exception('Unable to continue, file was not loaded.');
}
file_save($file);
$results[] = $fid;
}
return $results;
}
/**
* Implements hook_file_default_displays().
*
* Provides default display configurations for media types.
*
* @see file_entity_schema()
*/
function media_file_default_displays() {
$default_displays = array();
$default_image_styles = array(
'media_preview' => 'square_thumbnail',
'media_large' => 'large',
'media_original' => ''
);
foreach ($default_image_styles as $view_mode => $image_style) {
$display_name = 'image__' . $view_mode . '__file_image';
$default_displays[$display_name] = (object) array(
'api_version' => 1,
'name' => $display_name,
'status' => 1,
'weight' => 5,
'settings' => array('image_style' => $image_style),
);
}
return $default_displays;
}

View File

@@ -0,0 +1,165 @@
<?php
/**
* @file
* Contains the variables used by media and their defaults.
*/
/* ***************************************** */
/* CONSTANTS */
/* ***************************************** */
/**
* @TODO: iz_cruft?
* @var unknown_type
*/
define('MEDIA_RESOURCE_URI_DEFAULT', 'public://');
define('MEDIA_TYPES_DEFAULT', '*');
/**
* Define constants.
*/
define('MEDIA_VARIABLE_NAMESPACE', 'media__');
/**
* Wrapper for variable_get() that uses the Media variable registry.
*
* @param string $name
* The variable name to retrieve. Note that it will be namespaced by
* pre-pending MEDIA_VARIABLE_NAMESPACE, as to avoid variable collisions with
* other modules.
* @param unknown $default
* An optional default variable to return if the variable hasn't been set
* yet. Note that within this module, all variables should already be set
* in the media_variable_default() function.
* @return unknown
* Returns the stored variable or its default.
*
* @see media_variable_set()
* @see media_variable_del()
* @see media_variable_default()
*/
function media_variable_get($name, $default = NULL) {
// Allow for an override of the default.
// Useful when a variable is required (like $path), but namespacing still desired.
if (!isset($default)) {
$default = media_variable_default($name);
}
// Namespace all variables
$variable_name = MEDIA_VARIABLE_NAMESPACE . $name;
return variable_get($variable_name, $default);
}
/**
* Wrapper for variable_set() that uses the Media variable registry.
*
* @param string $name
* The variable name to set. Note that it will be namespaced by
* pre-pending MEDIA_VARIABLE_NAMESPACE, as to avoid variable collisions with
* other modules.
* @param unknown $value
* The value for which to set the variable.
* @return unknown
* Returns the stored variable after setting.
*
* @see media_variable_get()
* @see media_variable_del()
* @see media_variable_default()
*/
function media_variable_set($name, $value) {
$variable_name = MEDIA_VARIABLE_NAMESPACE . $name;
return variable_set($variable_name, $value);
}
/**
* Wrapper for variable_del() that uses the Media variable registry.
*
* @param string $name
* The variable name to delete. Note that it will be namespaced by
* pre-pending MEDIA_VARIABLE_NAMESPACE, as to avoid variable collisions with
* other modules.
*
* @see media_variable_get()
* @see media_variable_set()
* @see media_variable_default()
*/
function media_variable_del($name) {
$variable_name = MEDIA_VARIABLE_NAMESPACE . $name;
variable_del($variable_name);
}
/**
* Returns the final machine name of a Media namespaced variable.
*/
function media_variable_name($name) {
return MEDIA_VARIABLE_NAMESPACE . $name;
}
/**
* The default variables within the Media namespace.
*
* @param string $name
* Optional variable name to retrieve the default. Note that it has not yet
* been pre-pended with the MEDIA_VARIABLE_NAMESPACE namespace at this time.
* @return unknown
* The default value of this variable, if it's been set, or NULL, unless
* $name is NULL, in which case we return an array of all default values.
*
* @see media_variable_get()
* @see media_variable_set()
* @see media_variable_del()
*/
function media_variable_default($name = NULL) {
static $defaults;
if (!isset($defaults)) {
$defaults = array(
'wysiwyg_title' => 'Media browser',
'wysiwyg_icon_title' => 'Add media',
//@todo: We should do this per type actually. For "other" it should be a link.
'wysiwyg_default_view_mode' => 'media_large',
// Types which can be selected when embedding media vs wysiwyg.
'wysiwyg_allowed_types' => array('audio', 'image', 'video'),
// Attributes which can be modified via the wysiwyg and persist.
'wysiwyg_allowed_attributes' => array('height', 'width', 'hspace', 'vspace', 'border', 'align', 'style', 'alt', 'title', 'class', 'id'),
'field_select_media_text' => 'Select media',
'field_remove_media_text' => 'Remove media',
// Name of the theme to use in media popup dialogs, defaults to admin_theme
'dialog_theme' => '',
// @TODO: Make a configuration form with this.
'file_extensions' => 'jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp mp3 mov m4v mp4 mpeg avi ogg oga ogv wmv ico',
'max_filesize' => '',
'debug' => FALSE,
'file_list_size' => 10,
// Used in media.xml.inc: how long to cache retrieved remote data.
'xml_cache_expire' => 3600,
// Browser defaults in media.browser.inc.
'browser_viewtype_default' => 'thumbnails',
'browser_pager_limit' => 40,
'browser_library_empty_message' => 'There are currently no media in this library to select.',
'import_batch_size' => 20,
'fromurl_supported_schemes' => array('http', 'https', 'ftp', 'smb', 'ftps'),
'type_icon_directory' => drupal_get_path('module', 'media') . '/images/types',
'icon_base_directory' => drupal_get_path('module', 'media') . '/images/icons',
'icon_set' => 'default',
// This is set in media_enable(). It will show a persistant dsm on every page
// until the user runs the batch operation provided by media_admin_rebuild_types_form().
'show_file_type_rebuild_nag' => FALSE,
);
}
if (!isset($name)) {
return $defaults;
}
if (isset($defaults[$name])) {
return $defaults[$name];
}
}

View File

@@ -0,0 +1,25 @@
<?php
/**
* @file
* Provide Views data and handlers for media.module
*/
/**
* Implements hook_field_views_data().
*/
function media_field_views_data($field) {
$data = field_views_field_default_views_data($field);
foreach ($data as $table_name => $table_data) {
// Add the relationship only on the fid field.
$data[$table_name][$field['field_name'] . '_fid']['relationship'] = array(
'handler' => 'views_handler_relationship',
'base' => 'file_managed',
'entity type' => 'file',
'base field' => 'fid',
'label' => t('file from !field_name', array('!field_name' => $field['field_name'])),
);
}
return $data;
}

View File

@@ -0,0 +1,80 @@
<?php
/**
* @file
* XML data retrieval and storage API for Media.
*/
/**
* A wrapper around simplexml to retrieve a given XML file.
*
* @param $url
* The URL to the XML to retrieve.
* @param $display_errors
* Optional; if TRUE, then we'll display errors to the end user. They'll be
* logged to the watchdog in any case.
* @param $refresh
* Optional; if TRUE, then we'll force a new load of the XML. Otherwise,
* a cached version will be retrieved if possible.
* @return
* A fully populated object, or FALSE on an error.
*/
function _media_retrieve_xml($url, $display_errors = FALSE, $refresh = FALSE) {
$xmls = &drupal_static(__FUNCTION__, array());
// Return our cached XML if allowed, and it exists.
if (!$refresh && isset($xmls[$url])) {
return $xmls[$url];
}
elseif (!$refresh && $cache = cache_get('media:xml:' . $url, 'cache_media_xml')) {
$xmls[$url] = $cache->data;
return $xmls[$url];
}
// Enable user error handling.
libxml_use_internal_errors(TRUE);
// Load the document
$xml = simplexml_load_file($url);
if (!$xml) {
foreach (libxml_get_errors() as $error) {
$params = array('%url' => $url, '%error' => $error->message);
// Handle errors here.
if ($display_errors) {
drupal_set_message(t('Error retrieving XML from %url: %error', $params), 'error');
}
watchdog('media', 'Error retrieving XML from %url: %error', $params, WATCHDOG_WARNING);
}
// Clear our error cache.
libxml_clear_errors();
// Set the static cache, but not Drupal's cache, so we can attempt to
// retrieve the file another time if possible.
$xmls[$url] = FALSE;
}
else {
$xmls[$url] = _media_unserialize_xml($xml);
cache_set('media:xml:' . $url, $xmls[$url], 'cache_media_xml', media_variable_get('xml_cache_expire', 3600));
}
return $xmls[$url];
}
/**
* Recursively converts a SimpleXMLElement object into an array.
* @param $xml
* The original XML object.
*/
function _media_unserialize_xml($xml) {
if ($xml instanceof SimpleXMLElement) {
$xml = (array) $xml;
}
if (is_array($xml)) {
foreach ($xml as $key => $item) {
$xml[$key] = _media_unserialize_xml($item);
}
}
return $xml;
}

View File

@@ -0,0 +1,144 @@
/**
* @file
* Javascript for the interface at admin/content/media and also for interfaces
* related to setting up media fields and for media type administration.
*
* Basically, if it's on the /admin path, it's probably here.
*/
(function ($) {
/**
* Functionality for the thumbnail display
*/
Drupal.behaviors.mediaAdmin = {
attach: function (context) {
// Show a javascript confirmation dialog if a user has files selected and
// they try to switch between the "Thumbnail" and "List" local tasks.
$('.media-display-switch a').bind('click', function () {
if ($(':checkbox:checked', $('form#media-admin')).length != 0) {
return confirm(Drupal.t('If you switch views, you will lose your selection.'));
}
});
// Configure the "Add file" link to fire the media browser popup.
var $launcherLink = $('<a class="media-launcher" href="#"></a>').html(Drupal.t('Add file'));
$launcherLink.bind('click', function () {
// This option format needs *serious* work.
// Not even bothering documenting it because it needs to be thrown.
// See media.browser.js and media.browser.inc - media_browser()
// For how it gets passed.
var options = {
disabledPlugins: ['library'],
multiselect: true
};
Drupal.media.popups.mediaBrowser(function (mediaFiles) {
// When the media browser succeeds, we refresh
// @TODO: Should jump to the new media file and perhaps highlight it.
parent.window.location.reload();
return false;
}, options);
});
$('ul.action-links', context).prepend($('<li></li>').append($launcherLink));
if ($('.media-display-thumbnails').length) {
// Implements 'select all/none' for thumbnail view.
// @TODO: Support grabbing more than one page of thumbnails.
var allLink = $('<a href="#">' + Drupal.t('all') + '</a>')
.click(function () {
$('.media-display-thumbnails', $(this).parents('form')).find(':checkbox').attr('checked', true).change();
return false;
});
var noneLink = $('<a href="#">' + Drupal.t('none') + '</a>')
.click(function () {
$('.media-display-thumbnails', $(this).parents('form')).find(':checkbox').attr('checked', false).change();
return false;
});
$('<div class="media-thumbnails-select" />')
.append('<strong>' + Drupal.t('Select') + ':</strong> ')
.append(allLink)
.append(', ')
.append(noneLink)
.prependTo('#media-admin > div')
// If the media item is clicked anywhere other than on the image itself
// check the checkbox. For the record, JS thinks this is wonky.
$('.media-item').bind('click', function (e) {
if ($(e.target).is('img, a')) {
return;
}
var checkbox = $(this).parent().find(':checkbox');
if (checkbox.is(':checked')) {
checkbox.attr('checked', false).change();
} else {
checkbox.attr('checked', true).change();
}
});
// Add an extra class to selected thumbnails.
$('.media-display-thumbnails :checkbox').each(function () {
var checkbox = $(this);
if (checkbox.is(':checked')) {
$(checkbox.parents('li').find('.media-item')).addClass('selected');
}
checkbox.bind('change.media', function () {
if (checkbox.is(':checked')) {
$(checkbox.parents('li').find('.media-item')).addClass('selected');
}
else {
$(checkbox.parents('li').find('.media-item')).removeClass('selected');
}
});
});
}
// When any checkboxes are clicked on this form check to see if any are checked.
// If any checkboxes are checked, show the edit options (@todo rename to edit-actions).
$('#media-admin :checkbox').bind('change', function () {
Drupal.behaviors.mediaAdmin.showOrHideEditOptions();
});
Drupal.behaviors.mediaAdmin.showOrHideEditOptions();
},
// Checks if any checkboxes on the form are checked, if so it will show the
// edit-options panel.
showOrHideEditOptions: function() {
var fieldset = $('#edit-options');
if (!$('#media-admin input[type=checkbox]:checked').size()) {
fieldset.slideUp('fast');
}
else {
fieldset.slideDown('fast');
}
}
};
/**
* JavaScript for the Media types administrative form.
*/
Drupal.behaviors.mediaTypesAdmin = {
attach: function (context) {
if ($('.form-item-match-type', context).length == 0) {
return;
}
// Toggle the 'other' text field on Match type.
if ($('.form-item-match-type input:checked').val() != 'other') {
$('.form-item-match-type-other').hide();
}
$('.form-item-match-type input').change(function () {
if ($(this).val() == 'other') {
$('.form-item-match-type-other').slideDown('fast');
}
else {
$('.form-item-match-type-other').slideUp('fast');
}
});
}
};
})(jQuery);

View File

@@ -0,0 +1,100 @@
(function ($) {
namespace('Drupal.media.browser');
Drupal.media.browser.selectedMedia = [];
Drupal.media.browser.mediaAdded = function () {};
Drupal.media.browser.selectionFinalized = function (selectedMedia) {
// This is intended to be overridden if a callee wants to be triggered
// when the media selection is finalized from inside the browser.
// This is used for the file upload form for instance.
};
Drupal.behaviors.experimentalMediaBrowser = {
attach: function (context) {
if (Drupal.settings.media.selectedMedia) {
Drupal.media.browser.selectMedia(Drupal.settings.media.selectedMedia);
// Fire a confirmation of some sort.
Drupal.media.browser.finalizeSelection();
}
$('#media-browser-tabset').tabs({
show: Drupal.media.browser.resizeIframe
});
$('.media-browser-tab').each( Drupal.media.browser.validateButtons );
}
// Wait for additional params to be passed in.
};
Drupal.media.browser.launch = function () {
};
Drupal.media.browser.validateButtons = function() {
// The media browser runs in an IFRAME. The Drupal.media.popups.mediaBrowser()
// function sets up the IFRAME and "OK" and "Cancel" buttons that are outside
// of the IFRAME, so that their click handlers can destroy the IFRAME while
// retaining information about what media items were selected. However,
// Drupal UI convention is to place all action buttons on the same "line"
// at the bottom of the form, so if the form within the IFRAME contains a
// "Submit" button or other action buttons, then the "OK" and "Cancel"
// buttons below the IFRAME break this convention and confuse the user.
// Therefore, we add "Submit" and "Cancel" buttons inside the IFRAME, and
// have their click action trigger the click action of the corresonding
// "OK" and "Cancel" buttons that are outside the IFRAME. media.css contains
// CSS rules that hide the outside buttons.
//
// We don't add a "Submit" button if the form already has one, since in these
// cases, another round-trip to the server is needed before the user's
// selection is finalized. For these cases, when the form's real Submit
// button is clicked, the server either returns another form for the user to
// fill out, or else a completion page that contains or sets the
// Drupal.media.browser.selectedMedia variable. If the latter, then
// Drupal.media.popups.mediaBrowser.mediaBrowserOnLoad() auto-triggers the
// "OK" button action to finalize the selection and remove the IFRAME.
//
// @todo An alternate, less hacky solution would be most welcome.
if (!($('.form-submit', this).length > 0)) {
$('<a class="button button-yes fake-ok">' + Drupal.t('Submit') + '</a>').appendTo(this).bind('click', Drupal.media.browser.submit);
if (!($('.fake-cancel', this).length > 0)) {
$('<a class="button button-no fake-cancel">' + Drupal.t('Cancel') + '</a>').appendTo(this).bind('click', Drupal.media.browser.submit);
}
} else if (!($('.fake-cancel', this).length > 0)) {
var parent = $('.form-actions', this);
if (!parent.length) {
parent = $('form > div', this);
}
$('<a class="button button-no fake-cancel">' + Drupal.t('Cancel') + '</a>').appendTo(parent).bind('click', Drupal.media.browser.submit);
}
};
Drupal.media.browser.submit = function () {
// @see Drupal.media.browser.validateButtons().
var buttons = $(parent.window.document.body).find('#mediaBrowser').parent('.ui-dialog').find('.ui-dialog-buttonpane button');
if ($(this).hasClass('fake-cancel')) {
buttons[1].click();
} else {
buttons[0].click();
}
}
Drupal.media.browser.selectMedia = function (selectedMedia) {
Drupal.media.browser.selectedMedia = selectedMedia;
};
Drupal.media.browser.finalizeSelection = function () {
if (!Drupal.media.browser.selectedMedia) {
throw new exception(Drupal.t('Cannot continue, nothing selected'));
}
else {
Drupal.media.browser.selectionFinalized(Drupal.media.browser.selectedMedia);
}
};
Drupal.media.browser.resizeIframe = function (event) {
var h = $('body').height();
$(parent.window.document).find('#mediaBrowser').height(h);
};
}(jQuery));

View File

@@ -0,0 +1,19 @@
/**
* Creates a namespace.
*
* @return
* The created namespace object.
*/
function namespace () {
var a=arguments, o=null, i, j, d;
for (i=0; i<a.length; i=i+1) {
d=a[i].split(".");
o=window;
for (j=0; j<d.length; j=j+1) {
o[d[j]]=o[d[j]] || {};
o=o[d[j]];
}
}
return o;
};

View File

@@ -0,0 +1,48 @@
/**
* @file
* Attach behaviors to formatter radio select when selecting a media's display
* formatter.
*/
(function ($) {
namespace('Drupal.media.formatForm');
Drupal.media.mediaFormatSelected = {};
Drupal.behaviors.mediaFormatForm = {
attach: function (context, settings) {
// Add "Submit" and "Cancel" buttons inside the IFRAME that trigger the
// behavior of the hidden "OK" and "Cancel" buttons that are outside the
// IFRAME. See Drupal.media.browser.validateButtons() for more details.
$('<a class="button fake-ok">' + Drupal.t('Submit') + '</a>').appendTo($('#media-format-form')).bind('click', Drupal.media.formatForm.submit);
$('<a class="button fake-cancel">' + Drupal.t('Cancel') + '</a>').appendTo($('#media-format-form')).bind('click', Drupal.media.formatForm.submit);
if (Drupal.settings.media_format_form.autosubmit) {
$('.button.fake-ok').click();
}
}
};
Drupal.media.formatForm.getOptions = function () {
// Get all the values
var ret = {}; $.each($('#media-format-form fieldset#edit-options *').serializeArray(), function (i, field) { ret[field.name] = field.value; });
return ret;
};
Drupal.media.formatForm.getFormattedMedia = function () {
var formatType = $("select#edit-format option:selected").val();
return { type: formatType, options: Drupal.media.formatForm.getOptions(), html: Drupal.settings.media.formatFormFormats[formatType] };
};
Drupal.media.formatForm.submit = function () {
// @see Drupal.behaviors.mediaFormatForm.attach().
var buttons = $(parent.window.document.body).find('#mediaStyleSelector').parent('.ui-dialog').find('.ui-dialog-buttonpane button');
if ($(this).hasClass('fake-cancel')) {
buttons[1].click();
} else {
buttons[0].click();
}
}
})(jQuery);

View File

@@ -0,0 +1,75 @@
/**
* @file
* This file handles the JS for Media Module functions.
*/
(function ($) {
/**
* Loads media browsers and callbacks, specifically for media as a field.
*/
Drupal.behaviors.mediaElement = {
attach: function (context, settings) {
// Options set from media.fields.inc for the types, etc to show in the browser.
// For each widget (in case of multi-entry)
$('.media-widget', context).once('mediaBrowserLaunch', function () {
var options = settings.media.elements[this.id];
globalOptions = {};
if (options.global != undefined) {
var globalOptions = options.global;
}
//options = Drupal.settings.media.fields[this.id];
var fidField = $('.fid', this);
var previewField = $('.preview', this);
var removeButton = $('.remove', this); // Actually a link, but looks like a button.
// Show the Remove button if there's an already selected media.
if (fidField.val() != 0) {
removeButton.css('display', 'inline-block');
}
// When someone clicks the link to pick media (or clicks on an existing thumbnail)
$('.launcher', this).bind('click', function () {
// Launch the browser, providing the following callback function
// @TODO: This should not be an anomyous function.
Drupal.media.popups.mediaBrowser(function (mediaFiles) {
if (mediaFiles.length < 0) {
return;
}
var mediaFile = mediaFiles[0];
// Set the value of the filefield fid (hidden).
fidField.val(mediaFile.fid);
// Set the preview field HTML.
previewField.html(mediaFile.preview);
// Show the Remove button.
removeButton.show();
}, globalOptions);
return false;
});
// When someone clicks the Remove button.
$('.remove', this).bind('click', function () {
// Set the value of the filefield fid (hidden).
fidField.val(0);
// Set the preview field HTML.
previewField.html('');
// Hide the Remove button.
removeButton.hide();
return false;
});
$('.media-edit-link', this).bind('click', function () {
var fid = fidField.val();
if (fid) {
Drupal.media.popups.mediaFieldEditor(fid, function (r) { alert(r); });
}
return false;
});
});
}
};
})(jQuery);

View File

@@ -0,0 +1,358 @@
/**
* @file: Popup dialog interfaces for the media project.
*
* Drupal.media.popups.mediaBrowser
* Launches the media browser which allows users to pick a piece of media.
*
* Drupal.media.popups.mediaStyleSelector
* Launches the style selection form where the user can choose
* what format / style they want their media in.
*
*/
(function ($) {
namespace('Drupal.media.popups');
/**
* Media browser popup. Creates a media browser dialog.
*
* @param {function}
* onSelect Callback for when dialog is closed, received (Array
* media, Object extra);
* @param {Object}
* globalOptions Global options that will get passed upon initialization of the browser.
* @see Drupal.media.popups.mediaBrowser.getDefaults();
*
* @param {Object}
* pluginOptions Options for specific plugins. These are passed
* to the plugin upon initialization. If a function is passed here as
* a callback, it is obviously not passed, but is accessible to the plugin
* in Drupal.settings.variables.
*
* Example
* pluginOptions = {library: {url_include_patterns:'/foo/bar'}};
*
* @param {Object}
* widgetOptions Options controlling the appearance and behavior of the
* modal dialog.
* @see Drupal.media.popups.mediaBrowser.getDefaults();
*/
Drupal.media.popups.mediaBrowser = function (onSelect, globalOptions, pluginOptions, widgetOptions) {
var options = Drupal.media.popups.mediaBrowser.getDefaults();
options.global = $.extend({}, options.global, globalOptions);
options.plugins = pluginOptions;
options.widget = $.extend({}, options.widget, widgetOptions);
// Create it as a modal window.
var browserSrc = options.widget.src;
if ($.isArray(browserSrc) && browserSrc.length) {
browserSrc = browserSrc[browserSrc.length - 1];
}
// Params to send along to the iframe. WIP.
var params = {};
$.extend(params, options.global);
params.plugins = options.plugins;
browserSrc += '&' + $.param(params);
var mediaIframe = Drupal.media.popups.getPopupIframe(browserSrc, 'mediaBrowser');
// Attach the onLoad event
mediaIframe.bind('load', options, options.widget.onLoad);
/**
* Setting up the modal dialog
*/
var ok = 'OK';
var cancel = 'Cancel';
var notSelected = 'You have not selected anything!';
if (Drupal && Drupal.t) {
ok = Drupal.t(ok);
cancel = Drupal.t(cancel);
notSelected = Drupal.t(notSelected);
}
// @todo: let some options come through here. Currently can't be changed.
var dialogOptions = options.dialog;
dialogOptions.buttons[ok] = function () {
var selected = this.contentWindow.Drupal.media.browser.selectedMedia;
if (selected.length < 1) {
alert(notSelected);
return;
}
onSelect(selected);
$(this).dialog("destroy");
$(this).remove();
};
dialogOptions.buttons[cancel] = function () {
$(this).dialog("destroy");
$(this).remove();
};
Drupal.media.popups.setDialogPadding(mediaIframe.dialog(dialogOptions));
// Remove the title bar.
mediaIframe.parents(".ui-dialog").find(".ui-dialog-titlebar").remove();
Drupal.media.popups.overlayDisplace(mediaIframe.parents(".ui-dialog"));
return mediaIframe;
};
Drupal.media.popups.mediaBrowser.mediaBrowserOnLoad = function (e) {
var options = e.data;
if (this.contentWindow.Drupal.media.browser.selectedMedia.length > 0) {
var ok = $(this).dialog('option', 'buttons')['OK'];
ok.call(this);
return;
}
};
Drupal.media.popups.mediaBrowser.getDefaults = function () {
return {
global: {
types: [], // Types to allow, defaults to all.
activePlugins: [] // If provided, a list of plugins which should be enabled.
},
widget: { // Settings for the actual iFrame which is launched.
src: Drupal.settings.media.browserUrl, // Src of the media browser (if you want to totally override it)
onLoad: Drupal.media.popups.mediaBrowser.mediaBrowserOnLoad // Onload function when iFrame loads.
},
dialog: Drupal.media.popups.getDialogOptions()
};
};
Drupal.media.popups.mediaBrowser.finalizeSelection = function () {
var selected = this.contentWindow.Drupal.media.browser.selectedMedia;
if (selected.length < 1) {
alert(notSelected);
return;
}
onSelect(selected);
$(this).dialog("destroy");
$(this).remove();
}
/**
* Style chooser Popup. Creates a dialog for a user to choose a media style.
*
* @param mediaFile
* The mediaFile you are requesting this formatting form for.
* @todo: should this be fid? That's actually all we need now.
*
* @param Function
* onSubmit Function to be called when the user chooses a media
* style. Takes one parameter (Object formattedMedia).
*
* @param Object
* options Options for the mediaStyleChooser dialog.
*/
Drupal.media.popups.mediaStyleSelector = function (mediaFile, onSelect, options) {
var defaults = Drupal.media.popups.mediaStyleSelector.getDefaults();
// @todo: remove this awful hack :(
defaults.src = defaults.src.replace('-media_id-', mediaFile.fid);
options = $.extend({}, defaults, options);
// Create it as a modal window.
var mediaIframe = Drupal.media.popups.getPopupIframe(options.src, 'mediaStyleSelector');
// Attach the onLoad event
mediaIframe.bind('load', options, options.onLoad);
/**
* Set up the button text
*/
var ok = 'OK';
var cancel = 'Cancel';
var notSelected = 'Very sorry, there was an unknown error embedding media.';
if (Drupal && Drupal.t) {
ok = Drupal.t(ok);
cancel = Drupal.t(cancel);
notSelected = Drupal.t(notSelected);
}
// @todo: let some options come through here. Currently can't be changed.
var dialogOptions = Drupal.media.popups.getDialogOptions();
dialogOptions.buttons[ok] = function () {
var formattedMedia = this.contentWindow.Drupal.media.formatForm.getFormattedMedia();
if (!formattedMedia) {
alert(notSelected);
return;
}
onSelect(formattedMedia);
$(this).dialog("destroy");
$(this).remove();
};
dialogOptions.buttons[cancel] = function () {
$(this).dialog("destroy");
$(this).remove();
};
Drupal.media.popups.setDialogPadding(mediaIframe.dialog(dialogOptions));
// Remove the title bar.
mediaIframe.parents(".ui-dialog").find(".ui-dialog-titlebar").remove();
Drupal.media.popups.overlayDisplace(mediaIframe.parents(".ui-dialog"));
return mediaIframe;
};
Drupal.media.popups.mediaStyleSelector.mediaBrowserOnLoad = function (e) {
};
Drupal.media.popups.mediaStyleSelector.getDefaults = function () {
return {
src: Drupal.settings.media.styleSelectorUrl,
onLoad: Drupal.media.popups.mediaStyleSelector.mediaBrowserOnLoad
};
};
/**
* Style chooser Popup. Creates a dialog for a user to choose a media style.
*
* @param mediaFile
* The mediaFile you are requesting this formatting form for.
* @todo: should this be fid? That's actually all we need now.
*
* @param Function
* onSubmit Function to be called when the user chooses a media
* style. Takes one parameter (Object formattedMedia).
*
* @param Object
* options Options for the mediaStyleChooser dialog.
*/
Drupal.media.popups.mediaFieldEditor = function (fid, onSelect, options) {
var defaults = Drupal.media.popups.mediaFieldEditor.getDefaults();
// @todo: remove this awful hack :(
defaults.src = defaults.src.replace('-media_id-', fid);
options = $.extend({}, defaults, options);
// Create it as a modal window.
var mediaIframe = Drupal.media.popups.getPopupIframe(options.src, 'mediaFieldEditor');
// Attach the onLoad event
// @TODO - This event is firing too early in IE on Windows 7,
// - so the height being calculated is too short for the content.
mediaIframe.bind('load', options, options.onLoad);
/**
* Set up the button text
*/
var ok = 'OK';
var cancel = 'Cancel';
var notSelected = 'Very sorry, there was an unknown error embedding media.';
if (Drupal && Drupal.t) {
ok = Drupal.t(ok);
cancel = Drupal.t(cancel);
notSelected = Drupal.t(notSelected);
}
// @todo: let some options come through here. Currently can't be changed.
var dialogOptions = Drupal.media.popups.getDialogOptions();
dialogOptions.buttons[ok] = function () {
alert('hell yeah');
return "poo";
var formattedMedia = this.contentWindow.Drupal.media.formatForm.getFormattedMedia();
if (!formattedMedia) {
alert(notSelected);
return;
}
onSelect(formattedMedia);
$(this).dialog("destroy");
$(this).remove();
};
dialogOptions.buttons[cancel] = function () {
$(this).dialog("destroy");
$(this).remove();
};
Drupal.media.popups.setDialogPadding(mediaIframe.dialog(dialogOptions));
// Remove the title bar.
mediaIframe.parents(".ui-dialog").find(".ui-dialog-titlebar").remove();
Drupal.media.popups.overlayDisplace(mediaIframe.parents(".ui-dialog"));
return mediaIframe;
};
Drupal.media.popups.mediaFieldEditor.mediaBrowserOnLoad = function (e) {
};
Drupal.media.popups.mediaFieldEditor.getDefaults = function () {
return {
// @todo: do this for real
src: '/media/-media_id-/edit?render=media-popup',
onLoad: Drupal.media.popups.mediaFieldEditor.mediaBrowserOnLoad
};
};
/**
* Generic functions to both the media-browser and style selector
*/
/**
* Returns the commonly used options for the dialog.
*/
Drupal.media.popups.getDialogOptions = function () {
return {
buttons: {},
dialogClass: 'media-wrapper',
modal: true,
draggable: false,
resizable: false,
minWidth: 600,
width: 800,
height: 550,
position: 'center',
overlay: {
backgroundColor: '#000000',
opacity: 0.4
}
};
};
/**
* Created padding on a dialog
*
* @param jQuery dialogElement
* The element which has .dialog() attached to it.
*/
Drupal.media.popups.setDialogPadding = function (dialogElement) {
// @TODO: Perhaps remove this hardcoded reference to height.
// - It's included to make IE on Windows 7 display the dialog without
// collapsing. 550 is the height that displays all of the tab panes
// within the Add Media overlay. This is either a bug in the jQuery
// UI library, a bug in IE on Windows 7 or a bug in the way the
// dialog is instantiated. Or a combo of the three.
// All browsers except IE on Win7 ignore these defaults and adjust
// the height of the iframe correctly to match the content in the panes
dialogElement.height(dialogElement.dialog('option', 'height'));
dialogElement.width(dialogElement.dialog('option', 'width'));
};
/**
* Get an iframe to serve as the dialog's contents. Common to both plugins.
*/
Drupal.media.popups.getPopupIframe = function (src, id, options) {
var defaults = {width: '800px', scrolling: 'no'};
var options = $.extend({}, defaults, options);
return $('<iframe class="media-modal-frame"/>')
.attr('src', src)
.attr('width', options.width)
.attr('id', id)
.attr('scrolling', options.scrolling);
};
Drupal.media.popups.overlayDisplace = function (dialog) {
if (parent.window.Drupal.overlay) {
var overlayDisplace = parent.window.Drupal.overlay.getDisplacement('top');
if (dialog.offset().top < overlayDisplace) {
dialog.css('top', overlayDisplace);
}
}
}
})(jQuery);

View File

@@ -0,0 +1,29 @@
(function ($) {
namespace('Drupal.media.browser.plugin');
Drupal.media.browser.plugin.fromurl = function (mediaBrowser, options) {
return {
init: function () {
tabset = mediaBrowser.getTabset();
tabset.tabs('add', '#fromurl', 'From URL');
mediaBrowser.listen('tabs.show', function (e, id) {
if (id == 'fromurl') {
// We only need to set this once.
// We probably could set it upon load.
if (mediaBrowser.getActivePanel().html() == '') {
mediaBrowser.getActivePanel().html(options.fromUrlForm);
}
}
});
}
};
};
// For now, I guess self registration makes sense.
// Really though, we should be doing it via drupal_add_js and some settings
// from the drupal variable.
// @todo: needs a review.
Drupal.media.browser.register('fromurl', Drupal.media.browser.plugin.fromurl, {});
})(jQuery);

View File

@@ -0,0 +1,23 @@
ul#media-browser-library-list {
list-style: none;
margin: -10px 0 0 0;
padding: 0px;
background-color: white;
}
#media-tab-library #container{
padding:20px 0;
}
#media-tab-library #scrollbox{
height: 300px;
overflow:auto;
overflow-x:hidden;
}
#scrollbox #status {
clear: both;
text-align: center;
margin-top: 10px;
}

View File

@@ -0,0 +1,184 @@
(function ($) {
namespace('Drupal.media.browser');
Drupal.behaviors.mediaLibrary = {
attach: function (context, settings) {
var library = new Drupal.media.browser.library(Drupal.settings.media.browser.library);
$('#media-browser-tabset').bind('tabsshow', function (event, ui) {
if (ui.tab.hash === '#media-tab-library') {
// Grab the parameters from the Drupal.settings object
var params = {};
for (var parameter in Drupal.settings.media.browser.library) {
params[parameter] = Drupal.settings.media.browser.library[parameter];
}
library.start($(ui.panel), params);
$('#scrollbox').bind('scroll', library, library.scrollUpdater);
}
});
}
};
Drupal.media.browser.library = function (settings) {
this.settings = Drupal.media.browser.library.getDefaults();
$.extend(this.settings, settings);
this.done = false; // Keeps track of if the last request for media returned 0 results.
this.cursor = 0; // keeps track of what the last requested media object was.
this.mediaFiles = []; // An array of loaded media files from the server.
this.selectedMediaFiles = [];
};
Drupal.media.browser.library.getDefaults = function () {
return {
emtpyMessage: Drupal.t('There is nothing in your media library. Select the Upload tab above to add a file.'),
limit: 15
};
};
Drupal.media.browser.library.prototype.start = function (renderElement, params) {
this.renderElement = renderElement;
this.params = params;
// Change the behavior dependent on multiselect
if (params.multiselect) {
this.clickFunction = this.multiSelect;
} else {
this.clickFunction = this.singleSelect;
}
this.loadMedia();
};
/**
* Appends more media onto the list
*/
Drupal.media.browser.library.prototype.loadMedia = function () {
var that = this;
$('#status').text(Drupal.t('Loading...')).show();
$.extend(this.params, {start: this.cursor, limit: this.settings.limit});
var gotMedia = function (data, status) {
$('#status').text('').hide();
if (data.media.length < that.params.limit) {
// We remove the scroll event listener, nothing more to load after this.
$('#scrollbox').unbind('scroll');
}
that.mediaFiles = that.mediaFiles.concat(data.media);
that.render(that.renderElement);
// Remove the flag that prevents loading of more media
that.loading = false;
};
var errorCallback = function () {
alert(Drupal.t('Error getting media.'));
};
$.ajax({
url: this.settings.getMediaUrl,
type: 'GET',
dataType: 'json',
data: this.params,
error: errorCallback,
success: gotMedia
});
};
Drupal.media.browser.library.prototype.scrollUpdater = function (e){
if (!e.data.loading) {
var scrollbox = $('#scrollbox');
var scrolltop = scrollbox.attr('scrollTop');
var scrollheight = scrollbox.attr('scrollHeight');
var windowheight = scrollbox.attr('clientHeight');
var scrolloffset = 20;
if(scrolltop >= (scrollheight - (windowheight + scrolloffset))) {
// Set a flag so we don't make multiple concurrent AJAX calls
e.data.loading = true;
// Fetch new items
e.data.loadMedia();
}
}
};
/**
* Fetches the next media object and increments the cursor.
*/
Drupal.media.browser.library.prototype.getNextMedia = function () {
if (this.cursor >= this.mediaFiles.length) {
return false;
}
var ret = this.mediaFiles[this.cursor];
this.cursor += 1;
return ret;
};
Drupal.media.browser.library.prototype.render = function (renderElement) {
if (this.mediaFiles.length < 1) {
$('<div id="media-empty-message" class="media-empty-message"></div>').appendTo(renderElement)
.html(this.emptyMessage);
return;
}
else {
var mediaList = $('#media-browser-library-list', renderElement);
// If the list doesn't exist, bail.
if (mediaList.length === 0) {
throw('Cannot continue, list element is missing');
}
}
while (this.cursor < this.mediaFiles.length) {
var mediaFile = this.getNextMedia();
var data = {};
data.obj = this;
data.file = mediaFile;
var listItem = $('<li></li>').appendTo(mediaList)
.attr('id', 'media-item-' + mediaFile.fid)
.html(mediaFile.preview)
.bind('click', data, this.clickFunction);
}
};
Drupal.media.browser.library.prototype.mediaSelected = function (media) {
Drupal.media.browser.selectMedia(media);
};
Drupal.media.browser.library.prototype.singleSelect = function (event) {
var lib = event.data.obj;
var file = event.data.file;
event.preventDefault();
event.stopPropagation();
$('.media-item').removeClass('selected');
$('.media-item', $(this)).addClass('selected');
lib.mediaSelected([event.data.file]);
return false;
}
Drupal.media.browser.library.prototype.multiSelect = function (event) {
var lib = event.data.obj
var file = event.data.file;
event.preventDefault();
event.stopPropagation();
// Turn off or on the selection of this item
$('.media-item', $(this)).toggleClass('selected');
// Add or remove the media file from the array
var index = $.inArray(file, lib.selectedMediaFiles);
if (index == -1) {
// Media file isn't selected, add it
lib.selectedMediaFiles.push(file);
} else {
// Media file has previously been selected, remove it
lib.selectedMediaFiles.splice(index, 1);
}
// Pass the array of selected media files to the invoker
lib.mediaSelected(lib.selectedMediaFiles);
return false;
}
}(jQuery));

View File

@@ -0,0 +1,30 @@
(function ($) {
namespace('Drupal.media.browser.plugin');
Drupal.media.browser.plugin.upload = function (mediaBrowser, options) {
return {
/* Abstract */
init: function () {
tabset = mediaBrowser.getTabset();
tabset.tabs('add', '#upload', Drupal.t('Upload'));
mediaBrowser.listen('tabs.show', function (e, id) {
if (id == 'upload') {
// We only need to set this once.
// We probably could set it upon load.
if (mediaBrowser.getActivePanel().html() == '') {
mediaBrowser.getActivePanel().html(options.uploadForm);
}
}
});
}
};
};
// For now, I guess self registration makes sense.
// Really though, we should be doing it via drupal_add_js and some settings
// from the drupal variable.
// @todo: needs a review.
Drupal.media.browser.register('upload', Drupal.media.browser.plugin.upload, {});
})(jQuery);

View File

@@ -0,0 +1,21 @@
Drupal.behaviors.mediaUploadMultiple = {};
Drupal.behaviors.mediaUploadMultiple.attach = function (context, settings) {
// When the plupload element initializes, it expands the size of the elements
// it has created, so we need to resize the browser iframe after it's done.
var uploader = jQuery('#edit-upload').pluploadQueue();
if (uploader) {
// Handle the case in which the uploader has already finished initializing.
Drupal.media.browser.resizeIframe();
// Handle the case in which the uploader has not yet initialized.
uploader.bind("PostInit", Drupal.media.browser.resizeIframe);
uploader.bind('StateChanged', Drupal.behaviors.mediaUploadMultiple.submit);
}
};
Drupal.behaviors.mediaUploadMultiple.submit = function (uploader, file) {
if (uploader.state == 2) {
jQuery('#media-add-upload-multiple .form-submit').val(Drupal.t('Loading...'));
}
};

View File

@@ -0,0 +1,12 @@
/*
* debug - v0.3 - 6/8/2009
* http://benalman.com/projects/javascript-debug-console-log/
*
* Copyright (c) 2009 "Cowboy" Ben Alman
* Licensed under the MIT license
* http://benalman.com/about/license/
*
* With lots of help from Paul Irish!
* http://paulirish.com/
*/
window.debug=(function(){var c=this,e=Array.prototype.slice,b=c.console,i={},f,g,j=9,d=["error","warn","info","debug","log"],m="assert clear count dir dirxml group groupEnd profile profileEnd time timeEnd trace".split(" "),k=m.length,a=[];while(--k>=0){(function(n){i[n]=function(){j!==0&&b&&b[n]&&b[n].apply(b,arguments)}})(m[k])}k=d.length;while(--k>=0){(function(n,o){i[o]=function(){var q=e.call(arguments),p=[o].concat(q);a.push(p);h(p);if(!b||!l(n)){return}b.firebug?b[o].apply(c,q):b[o]?b[o](q):b.log(q)}})(k,d[k])}function h(n){if(f&&(g||!b||!b.log)){f.apply(c,n)}}i.setLevel=function(n){j=typeof n==="number"?n:9};function l(n){return j>0?j>n:d.length+j<=n}i.setCallback=function(){var o=e.call(arguments),n=a.length,p=n;f=o.shift()||null;g=typeof o[0]==="boolean"?o.shift():false;p-=typeof o[0]==="number"?o.shift():n;while(p<n){h(a[p++])}};return i})();

View File

@@ -0,0 +1,481 @@
/*
http://www.JSON.org/json2.js
2009-09-29
Public Domain.
NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
See http://www.JSON.org/js.html
This code should be minified before deployment.
See http://javascript.crockford.com/jsmin.html
USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
NOT CONTROL.
This file creates a global JSON object containing two methods: stringify
and parse.
JSON.stringify(value, replacer, space)
value any JavaScript value, usually an object or array.
replacer an optional parameter that determines how object
values are stringified for objects. It can be a
function or an array of strings.
space an optional parameter that specifies the indentation
of nested structures. If it is omitted, the text will
be packed without extra whitespace. If it is a number,
it will specify the number of spaces to indent at each
level. If it is a string (such as '\t' or '&nbsp;'),
it contains the characters used to indent at each level.
This method produces a JSON text from a JavaScript value.
When an object value is found, if the object contains a toJSON
method, its toJSON method will be called and the result will be
stringified. A toJSON method does not serialize: it returns the
value represented by the name/value pair that should be serialized,
or undefined if nothing should be serialized. The toJSON method
will be passed the key associated with the value, and this will be
bound to the value
For example, this would serialize Dates as ISO strings.
Date.prototype.toJSON = function (key) {
function f(n) {
// Format integers to have at least two digits.
return n < 10 ? '0' + n : n;
}
return this.getUTCFullYear() + '-' +
f(this.getUTCMonth() + 1) + '-' +
f(this.getUTCDate()) + 'T' +
f(this.getUTCHours()) + ':' +
f(this.getUTCMinutes()) + ':' +
f(this.getUTCSeconds()) + 'Z';
};
You can provide an optional replacer method. It will be passed the
key and value of each member, with this bound to the containing
object. The value that is returned from your method will be
serialized. If your method returns undefined, then the member will
be excluded from the serialization.
If the replacer parameter is an array of strings, then it will be
used to select the members to be serialized. It filters the results
such that only members with keys listed in the replacer array are
stringified.
Values that do not have JSON representations, such as undefined or
functions, will not be serialized. Such values in objects will be
dropped; in arrays they will be replaced with null. You can use
a replacer function to replace those with JSON values.
JSON.stringify(undefined) returns undefined.
The optional space parameter produces a stringification of the
value that is filled with line breaks and indentation to make it
easier to read.
If the space parameter is a non-empty string, then that string will
be used for indentation. If the space parameter is a number, then
the indentation will be that many spaces.
Example:
text = JSON.stringify(['e', {pluribus: 'unum'}]);
// text is '["e",{"pluribus":"unum"}]'
text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
// text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
text = JSON.stringify([new Date()], function (key, value) {
return this[key] instanceof Date ?
'Date(' + this[key] + ')' : value;
});
// text is '["Date(---current time---)"]'
JSON.parse(text, reviver)
This method parses a JSON text to produce an object or array.
It can throw a SyntaxError exception.
The optional reviver parameter is a function that can filter and
transform the results. It receives each of the keys and values,
and its return value is used instead of the original value.
If it returns what it received, then the structure is not modified.
If it returns undefined then the member is deleted.
Example:
// Parse the text. Values that look like ISO date strings will
// be converted to Date objects.
myData = JSON.parse(text, function (key, value) {
var a;
if (typeof value === 'string') {
a =
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
if (a) {
return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
+a[5], +a[6]));
}
}
return value;
});
myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
var d;
if (typeof value === 'string' &&
value.slice(0, 5) === 'Date(' &&
value.slice(-1) === ')') {
d = new Date(value.slice(5, -1));
if (d) {
return d;
}
}
return value;
});
This is a reference implementation. You are free to copy, modify, or
redistribute.
*/
/*jslint evil: true, strict: false */
/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
lastIndex, length, parse, prototype, push, replace, slice, stringify,
test, toJSON, toString, valueOf
*/
// Create a JSON object only if one does not already exist. We create the
// methods in a closure to avoid creating global variables.
if (!this.JSON) {
this.JSON = {};
}
(function () {
function f(n) {
// Format integers to have at least two digits.
return n < 10 ? '0' + n : n;
}
if (typeof Date.prototype.toJSON !== 'function') {
Date.prototype.toJSON = function (key) {
return isFinite(this.valueOf()) ?
this.getUTCFullYear() + '-' +
f(this.getUTCMonth() + 1) + '-' +
f(this.getUTCDate()) + 'T' +
f(this.getUTCHours()) + ':' +
f(this.getUTCMinutes()) + ':' +
f(this.getUTCSeconds()) + 'Z' : null;
};
String.prototype.toJSON =
Number.prototype.toJSON =
Boolean.prototype.toJSON = function (key) {
return this.valueOf();
};
}
var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
gap,
indent,
meta = { // table of character substitutions
'\b': '\\b',
'\t': '\\t',
'\n': '\\n',
'\f': '\\f',
'\r': '\\r',
'"' : '\\"',
'\\': '\\\\'
},
rep;
function quote(string) {
// If the string contains no control characters, no quote characters, and no
// backslash characters, then we can safely slap some quotes around it.
// Otherwise we must also replace the offending characters with safe escape
// sequences.
escapable.lastIndex = 0;
return escapable.test(string) ?
'"' + string.replace(escapable, function (a) {
var c = meta[a];
return typeof c === 'string' ? c :
'\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
}) + '"' :
'"' + string + '"';
}
function str(key, holder) {
// Produce a string from holder[key].
var i, // The loop counter.
k, // The member key.
v, // The member value.
length,
mind = gap,
partial,
value = holder[key];
// If the value has a toJSON method, call it to obtain a replacement value.
if (value && typeof value === 'object' &&
typeof value.toJSON === 'function') {
value = value.toJSON(key);
}
// If we were called with a replacer function, then call the replacer to
// obtain a replacement value.
if (typeof rep === 'function') {
value = rep.call(holder, key, value);
}
// What happens next depends on the value's type.
switch (typeof value) {
case 'string':
return quote(value);
case 'number':
// JSON numbers must be finite. Encode non-finite numbers as null.
return isFinite(value) ? String(value) : 'null';
case 'boolean':
case 'null':
// If the value is a boolean or null, convert it to a string. Note:
// typeof null does not produce 'null'. The case is included here in
// the remote chance that this gets fixed someday.
return String(value);
// If the type is 'object', we might be dealing with an object or an array or
// null.
case 'object':
// Due to a specification blunder in ECMAScript, typeof null is 'object',
// so watch out for that case.
if (!value) {
return 'null';
}
// Make an array to hold the partial results of stringifying this object value.
gap += indent;
partial = [];
// Is the value an array?
if (Object.prototype.toString.apply(value) === '[object Array]') {
// The value is an array. Stringify every element. Use null as a placeholder
// for non-JSON values.
length = value.length;
for (i = 0; i < length; i += 1) {
partial[i] = str(i, value) || 'null';
}
// Join all of the elements together, separated with commas, and wrap them in
// brackets.
v = partial.length === 0 ? '[]' :
gap ? '[\n' + gap +
partial.join(',\n' + gap) + '\n' +
mind + ']' :
'[' + partial.join(',') + ']';
gap = mind;
return v;
}
// If the replacer is an array, use it to select the members to be stringified.
if (rep && typeof rep === 'object') {
length = rep.length;
for (i = 0; i < length; i += 1) {
k = rep[i];
if (typeof k === 'string') {
v = str(k, value);
if (v) {
partial.push(quote(k) + (gap ? ': ' : ':') + v);
}
}
}
} else {
// Otherwise, iterate through all of the keys in the object.
for (k in value) {
if (Object.hasOwnProperty.call(value, k)) {
v = str(k, value);
if (v) {
partial.push(quote(k) + (gap ? ': ' : ':') + v);
}
}
}
}
// Join all of the member texts together, separated with commas,
// and wrap them in braces.
v = partial.length === 0 ? '{}' :
gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
mind + '}' : '{' + partial.join(',') + '}';
gap = mind;
return v;
}
}
// If the JSON object does not yet have a stringify method, give it one.
if (typeof JSON.stringify !== 'function') {
JSON.stringify = function (value, replacer, space) {
// The stringify method takes a value and an optional replacer, and an optional
// space parameter, and returns a JSON text. The replacer can be a function
// that can replace values, or an array of strings that will select the keys.
// A default replacer method can be provided. Use of the space parameter can
// produce text that is more easily readable.
var i;
gap = '';
indent = '';
// If the space parameter is a number, make an indent string containing that
// many spaces.
if (typeof space === 'number') {
for (i = 0; i < space; i += 1) {
indent += ' ';
}
// If the space parameter is a string, it will be used as the indent string.
} else if (typeof space === 'string') {
indent = space;
}
// If there is a replacer, it must be a function or an array.
// Otherwise, throw an error.
rep = replacer;
if (replacer && typeof replacer !== 'function' &&
(typeof replacer !== 'object' ||
typeof replacer.length !== 'number')) {
throw new Error('JSON.stringify');
}
// Make a fake root object containing our value under the key of ''.
// Return the result of stringifying the value.
return str('', {'': value});
};
}
// If the JSON object does not yet have a parse method, give it one.
if (typeof JSON.parse !== 'function') {
JSON.parse = function (text, reviver) {
// The parse method takes a text and an optional reviver function, and returns
// a JavaScript value if the text is a valid JSON text.
var j;
function walk(holder, key) {
// The walk method is used to recursively walk the resulting structure so
// that modifications can be made.
var k, v, value = holder[key];
if (value && typeof value === 'object') {
for (k in value) {
if (Object.hasOwnProperty.call(value, k)) {
v = walk(value, k);
if (v !== undefined) {
value[k] = v;
} else {
delete value[k];
}
}
}
}
return reviver.call(holder, key, value);
}
// Parsing happens in four stages. In the first stage, we replace certain
// Unicode characters with escape sequences. JavaScript handles many characters
// incorrectly, either silently deleting them, or treating them as line endings.
cx.lastIndex = 0;
if (cx.test(text)) {
text = text.replace(cx, function (a) {
return '\\u' +
('0000' + a.charCodeAt(0).toString(16)).slice(-4);
});
}
// In the second stage, we run the text against regular expressions that look
// for non-JSON patterns. We are especially concerned with '()' and 'new'
// because they can cause invocation, and '=' because it can cause mutation.
// But just to be safe, we want to reject all unexpected forms.
// We split the second stage into 4 regexp operations in order to work around
// crippling inefficiencies in IE's and Safari's regexp engines. First we
// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
// replace all simple value tokens with ']' characters. Third, we delete all
// open brackets that follow a colon or comma or that begin the text. Finally,
// we look to see that the remaining characters are only whitespace or ']' or
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
if (/^[\],:{}\s]*$/.
test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
// In the third stage we use the eval function to compile the text into a
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
// in JavaScript: it can begin a block or an object literal. We wrap the text
// in parens to eliminate the ambiguity.
j = eval('(' + text + ')');
// In the optional fourth stage, we recursively walk the new structure, passing
// each name/value pair to a reviver function for possible transformation.
return typeof reviver === 'function' ?
walk({'': j}, '') : j;
}
// If the text is not JSON parseable, then a SyntaxError is thrown.
throw new SyntaxError('JSON.parse');
};
}
}());

View File

@@ -0,0 +1,369 @@
/**
* @file
* Attach Media WYSIWYG behaviors.
*/
(function ($) {
Drupal.media = Drupal.media || {};
// Define the behavior.
Drupal.wysiwyg.plugins.media = {
/**
* Initializes the tag map.
*/
initializeTagMap: function () {
if (typeof Drupal.settings.tagmap == 'undefined') {
Drupal.settings.tagmap = { };
}
},
/**
* Execute the button.
* @TODO: Debug calls from this are never called. What's its function?
*/
invoke: function (data, settings, instanceId) {
if (data.format == 'html') {
Drupal.media.popups.mediaBrowser(function (mediaFiles) {
Drupal.wysiwyg.plugins.media.mediaBrowserOnSelect(mediaFiles, instanceId);
}, settings['global']);
}
},
/**
* Respond to the mediaBrowser's onSelect event.
* @TODO: Debug calls from this are never called. What's its function?
*/
mediaBrowserOnSelect: function (mediaFiles, instanceId) {
var mediaFile = mediaFiles[0];
var options = {};
Drupal.media.popups.mediaStyleSelector(mediaFile, function (formattedMedia) {
Drupal.wysiwyg.plugins.media.insertMediaFile(mediaFile, formattedMedia.type, formattedMedia.html, formattedMedia.options, Drupal.wysiwyg.instances[instanceId]);
}, options);
return;
},
insertMediaFile: function (mediaFile, viewMode, formattedMedia, options, wysiwygInstance) {
this.initializeTagMap();
// @TODO: the folks @ ckeditor have told us that there is no way
// to reliably add wrapper divs via normal HTML.
// There is some method of adding a "fake element"
// But until then, we're just going to embed to img.
// This is pretty hacked for now.
//
var imgElement = $(this.stripDivs(formattedMedia));
this.addImageAttributes(imgElement, mediaFile.fid, viewMode, options);
var toInsert = this.outerHTML(imgElement);
// Create an inline tag
var inlineTag = Drupal.wysiwyg.plugins.media.createTag(imgElement);
// Add it to the tag map in case the user switches input formats
Drupal.settings.tagmap[inlineTag] = toInsert;
wysiwygInstance.insert(toInsert);
},
/**
* Gets the HTML content of an element
*
* @param jQuery element
*/
outerHTML: function (element) {
return $('<div>').append( element.eq(0).clone() ).html();
},
addImageAttributes: function (imgElement, fid, view_mode, additional) {
// imgElement.attr('fid', fid);
// imgElement.attr('view_mode', view_mode);
// Class so we can find this image later.
imgElement.addClass('media-image');
this.forceAttributesIntoClass(imgElement, fid, view_mode, additional);
if (additional) {
for (k in additional) {
if (additional.hasOwnProperty(k)) {
if (k === 'attr') {
imgElement.attr(k, additional[k]);
}
}
}
}
},
/**
* Due to problems handling wrapping divs in ckeditor, this is needed.
*
* Going forward, if we don't care about supporting other editors
* we can use the fakeobjects plugin to ckeditor to provide cleaner
* transparency between what Drupal will output <div class="field..."><img></div>
* instead of just <img>, for now though, we're going to remove all the stuff surrounding the images.
*
* @param String formattedMedia
* Element containing the image
*
* @return HTML of <img> tag inside formattedMedia
*/
stripDivs: function (formattedMedia) {
// Check to see if the image tag has divs to strip
var stripped = null;
if ($(formattedMedia).is('img')) {
stripped = this.outerHTML($(formattedMedia));
} else {
stripped = this.outerHTML($('img', $(formattedMedia)));
}
// This will fail if we pass the img tag without anything wrapping it, like we do when re-enabling WYSIWYG
return stripped;
},
/**
* Attach function, called when a rich text editor loads.
* This finds all [[tags]] and replaces them with the html
* that needs to show in the editor.
*
*/
attach: function (content, settings, instanceId) {
var matches = content.match(/\[\[.*?\]\]/g);
this.initializeTagMap();
var tagmap = Drupal.settings.tagmap;
if (matches) {
var inlineTag = "";
for (i = 0; i < matches.length; i++) {
inlineTag = matches[i];
if (tagmap[inlineTag]) {
// This probably needs some work...
// We need to somehow get the fid propogated here.
// We really want to
var tagContent = tagmap[inlineTag];
var mediaMarkup = this.stripDivs(tagContent); // THis is <div>..<img>
var _tag = inlineTag;
_tag = _tag.replace('[[','');
_tag = _tag.replace(']]','');
try {
mediaObj = JSON.parse(_tag);
}
catch(err) {
mediaObj = null;
}
if(mediaObj) {
var imgElement = $(mediaMarkup);
this.addImageAttributes(imgElement, mediaObj.fid, mediaObj.view_mode);
var toInsert = this.outerHTML(imgElement);
content = content.replace(inlineTag, toInsert);
}
}
else {
debug.debug("Could not find content for " + inlineTag);
}
}
}
return content;
},
/**
* Detach function, called when a rich text editor detaches
*/
detach: function (content, settings, instanceId) {
// Replace all Media placeholder images with the appropriate inline json
// string. Using a regular expression instead of jQuery manipulation to
// prevent <script> tags from being displaced.
// @see http://drupal.org/node/1280758.
if (matches = content.match(/<img[^>]+class=([\'"])media-image[^>]*>/gi)) {
for (var i = 0; i < matches.length; i++) {
var imageTag = matches[i];
var inlineTag = Drupal.wysiwyg.plugins.media.createTag($(imageTag));
Drupal.settings.tagmap[inlineTag] = imageTag;
content = content.replace(imageTag, inlineTag);
}
}
return content;
},
/**
* @param jQuery imgNode
* Image node to create tag from
*/
createTag: function (imgNode) {
// Currently this is the <img> itself
// Collect all attribs to be stashed into tagContent
var mediaAttributes = {};
var imgElement = imgNode[0];
var sorter = [];
// @todo: this does not work in IE, width and height are always 0.
for (i=0; i< imgElement.attributes.length; i++) {
var attr = imgElement.attributes[i];
if (attr.specified == true) {
if (attr.name !== 'class') {
sorter.push(attr);
}
else {
// Exctract expando properties from the class field.
var attributes = this.getAttributesFromClass(attr.value);
for (var name in attributes) {
if (attributes.hasOwnProperty(name)) {
var value = attributes[name];
if (value.type && value.type === 'attr') {
sorter.push(value);
}
}
}
}
}
}
sorter.sort(this.sortAttributes);
for (var prop in sorter) {
mediaAttributes[sorter[prop].name] = sorter[prop].value;
}
// The following 5 ifs are dedicated to IE7
// If the style is null, it is because IE7 can't read values from itself
if (jQuery.browser.msie && jQuery.browser.version == '7.0') {
if (mediaAttributes.style === "null") {
var imgHeight = imgNode.css('height');
var imgWidth = imgNode.css('width');
mediaAttributes.style = {
height: imgHeight,
width: imgWidth
}
if (!mediaAttributes['width']) {
mediaAttributes['width'] = imgWidth;
}
if (!mediaAttributes['height']) {
mediaAttributes['height'] = imgHeight;
}
}
// If the attribute width is zero, get the CSS width
if (Number(mediaAttributes['width']) === 0) {
mediaAttributes['width'] = imgNode.css('width');
}
// IE7 does support 'auto' as a value of the width attribute. It will not
// display the image if this value is allowed to pass through
if (mediaAttributes['width'] === 'auto') {
delete mediaAttributes['width'];
}
// If the attribute height is zero, get the CSS height
if (Number(mediaAttributes['height']) === 0) {
mediaAttributes['height'] = imgNode.css('height');
}
// IE7 does support 'auto' as a value of the height attribute. It will not
// display the image if this value is allowed to pass through
if (mediaAttributes['height'] === 'auto') {
delete mediaAttributes['height'];
}
}
// Remove elements from attribs using the blacklist
for (var blackList in Drupal.settings.media.blacklist) {
delete mediaAttributes[Drupal.settings.media.blacklist[blackList]];
}
tagContent = {
"type": 'media',
// @todo: This will be selected from the format form
"view_mode": attributes['view_mode'].value,
"fid" : attributes['fid'].value,
"attributes": mediaAttributes
};
return '[[' + JSON.stringify(tagContent) + ']]';
},
/**
* Forces custom attributes into the class field of the specified image.
*
* Due to a bug in some versions of Firefox
* (http://forums.mozillazine.org/viewtopic.php?f=9&t=1991855), the
* custom attributes used to share information about the image are
* being stripped as the image markup is set into the rich text
* editor. Here we encode these attributes into the class field so
* the data survives.
*
* @param imgElement
* The image
* @fid
* The file id.
* @param view_mode
* The view mode.
* @param additional
* Additional attributes to add to the image.
*/
forceAttributesIntoClass: function (imgElement, fid, view_mode, additional) {
var wysiwyg = imgElement.attr('wysiwyg');
if (wysiwyg) {
imgElement.addClass('attr__wysiwyg__' + wysiwyg);
}
var format = imgElement.attr('format');
if (format) {
imgElement.addClass('attr__format__' + format);
}
var typeOf = imgElement.attr('typeof');
if (typeOf) {
imgElement.addClass('attr__typeof__' + typeOf);
}
if (fid) {
imgElement.addClass('img__fid__' + fid);
}
if (view_mode) {
imgElement.addClass('img__view_mode__' + view_mode);
}
if (additional) {
for (var name in additional) {
if (additional.hasOwnProperty(name)) {
if (name !== 'alt') {
imgElement.addClass('attr__' + name + '__' + additional[name]);
}
}
}
}
},
/**
* Retrieves encoded attributes from the specified class string.
*
* @param classString
* A string containing the value of the class attribute.
* @return
* An array containing the attribute names as keys, and an object
* with the name, value, and attribute type (either 'attr' or
* 'img', depending on whether it is an image attribute or should
* be it the attributes section)
*/
getAttributesFromClass: function (classString) {
var actualClasses = [];
var otherAttributes = [];
var classes = classString.split(' ');
var regexp = new RegExp('^(attr|img)__([^\S]*)__([^\S]*)$');
for (var index = 0; index < classes.length; index++) {
var matches = classes[index].match(regexp);
if (matches && matches.length === 4) {
otherAttributes[matches[2]] = {name: matches[2], value: matches[3], type: matches[1]};
}
else {
actualClasses.push(classes[index]);
}
}
if (actualClasses.length > 0) {
otherAttributes['class'] = {name: 'class', value: actualClasses.join(' '), type: 'attr'};
}
return otherAttributes;
},
/*
*
*/
sortAttributes: function (a, b) {
var nameA = a.name.toLowerCase();
var nameB = b.name.toLowerCase();
if (nameA < nameB) {
return -1;
}
if (nameA > nameB) {
return 1;
}
return 0;
}
};
})(jQuery);

View File

@@ -0,0 +1,114 @@
<?php
/**
* @file
* Hook provided by the media module.
*/
/**
* Return an array of plugins for the media browser.
*
* Implementors are expected to return a renderable element.
*
* Each element will be a jQuery tab on the media browser.
*
* Some elements are special:
* - #title: The title that goes on the tab
* - #settings: Drupal.settings.media.browser.$key (where key is the array key).
* - #callback: If provided, will make the tab an "ajax" tab.
*
* Example:
* $plugins['library'] = array(
* '#title' => t('Library'),
* '#attached' => array(
* 'js' => array(
* $path . '/js/plugins/media.library.js',
* ),
* ),
* '#settings' => array(
* 'viewMode' => 'thumbnails',
* 'getMediaUrl' => url('media/browser/list'),
* ),
* '#markup' => '<div> Library goes here</div>',
* );
*
* @param $plugin_name
* The name of the plugin to view
*
* @param $params
* An array of parameters which came in is $_GET['params'].
* The expected parameters is still being defined.
* - types: Array of media types to support
* - multiselect: Boolean enabling or disabling multiselect
*
* @return
* Renderable array.
*/
function hook_media_browser_plugin_view($plugin_name, $params) {
}
/**
* Returns a list of plugins for the media browser.
*
* Plugins are defined in a multi-dimensional associative
* array format with the following keys:
*
* - #weight (optional): Weight of the plugin in relation to other plugins
* when being displayed, e.g. tabs in the browser.
*
* @example
* <code>
* array(
* 'unique_plugin_name' => array(
* '#weight' => 42,
* ),
* );
* </code>
*/
function hook_media_browser_plugin_info() {
}
/**
* Returns an array of operations which can be taken on media items.
*
* This is used on the admin/content/media page so users can select multiple
* items and do something with them.
*
* The return format is an array or arrays with the following keys:
* - label: The string to be shown to the user.
* - callback (optional): A callback to be called when the media items are selected.
* Media items will be passed in as an argument.
* - redirect (optional): A path to redirect to. %fids should be in the path
* It will be replaced with the fids selected delimited by "+".
* i.e. mymodule/%fids/something -> mymodule/1+3+2/something if media items
* 1, 3 and 2 were selected.
*/
function media_media_operations() {
}
/**
* Alter the output generated by Media filter tags.
*
* @param array &$element
* The renderable array of output generated for the filter tag.
* @param array $tag
* The filter tag converted into an associative array by
* media_token_to_markup() with the following elements:
* - 'fid': The ID of the media file being rendered.
* - 'file': The object from file_load() of the media file being rendered.
* - 'view_mode': The view mode being used to render the file.
* - 'attributes': An additional array of attributes that could be output
* with media_get_file_without_label().
* @param array $settings
* An additional array of settings.
* - 'wysiwyg': A boolean if the output is for the WYSIWYG preview or FALSE
* if for normal rendering.
*/
function hook_media_token_to_markup_alter(array &$element, array $tag, array $settings) {
if (empty($settings['wysiwyg'])) {
$element['#attributes']['alt'] = t('This media has been output using the @mode view mode.', array('@mode' => $tag['view_mode']));
}
}

View File

@@ -0,0 +1,16 @@
name = Media
description = Provides the core Media API
package = Media
core = 7.x
dependencies[] = file_entity
dependencies[] = image
files[] = includes/MediaReadOnlyStreamWrapper.inc
files[] = test/media.types.test
files[] = test/media.entity.test
; Information added by drupal.org packaging script on 2012-03-23
version = "7.x-1.0"
core = "7.x"
project = "media"
datestamp = "1332537952"

View File

@@ -0,0 +1,681 @@
<?php
/**
* @file
* Install, update and uninstall functions for the Media module.
*/
define('MEDIA_UPDATE_RECORDS_ON_INSTALL', 200);
/**
* Implements hook_schema().
*/
function media_schema() {
$schema['media_type'] = array(
'description' => 'Stores the settings for media types.',
'fields' => array(
'name' => array(
'description' => 'The machine name of the media type.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'label' => array(
'description' => 'The label of the media type.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'base' => array(
'description' => 'If this is a base type (i.e. cannot be deleted)',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
'size' => 'tiny',
),
'weight' => array(
'description' => 'Weight of media type. Determines which one wins when claiming a piece of media (first wins)',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
'size' => 'normal',
),
'type_callback' => array(
'description' => 'Callback to determine if provided media is of this type.',
'type' => 'varchar',
'length' => 255,
'not null' => FALSE,
'default' => '',
),
'type_callback_args' => array(
'type' => 'text',
'not null' => FALSE,
'size' => 'big',
'serialize' => TRUE,
'description' => 'A serialized array of name value pairs that will be passed to the callback function',
),
),
'primary key' => array('name'),
);
$schema['media_list_type'] = array(
'description' => 'Stores the user preference for whether to list as table or images.',
'fields' => array(
'uid' => array(
'description' => 'The {user}.uid of the user.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
'type' => array(
'description' => 'The type of display (table or images).',
'type' => 'varchar',
'length' => 32,
'not null' => TRUE,
'default' => '',
),
),
'primary key' => array('uid'),
);
$schema['media_filter_usage'] = array(
'description' => 'Stores fids that have been included in the media tag in formatted textareas.',
'fields' => array(
'fid' => array(
'description' => 'The media {file_managed}.fid.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
'timestamp' => array(
'description' => 'The timestamp the fid was last recorded by media_filter()',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
),
'foreign keys' => array(
'file_managed' => array(
'table' => 'file_managed',
'columns' => array('fid' => 'fid'),
),
),
'primary key' => array('fid'),
'indexes' => array(
'timestamp' => array('timestamp'),
),
);
$schema['cache_media_xml'] = drupal_get_schema_unprocessed('system', 'cache');
$schema['cache_media_xml']['description'] = 'Cache table for the the results of retreived XML documents for remote streams.';
return $schema;
}
/**
* Implements hook_field_schema().
*/
function media_field_schema($field) {
return array(
'columns' => array(
'fid' => array(
'type' => 'int',
'unsigned' => TRUE,
'not null' => FALSE,
),
'title' => array(
'type' => 'varchar',
'length' => 255,
'not null' => FALSE,
),
'data' => array(
'type' => 'text',
'not null' => FALSE,
'size' => 'big',
'serialize' => TRUE,
//'description' => 'Used for storing additional information. Can be harnessed by widgets',
),
),
'indexes' => array(
'fid' => array('fid'),
),
);
}
/**
* Implements hook_install().
*/
function media_install() {
// @todo We may need to disable the media bundle & field in hook_disable.
// Define the default type to be used if no other type is found. Give it a
// high weight to ensure it runs last.
$types['default'] = new stdClass();
$types['default']->name = 'default';
$types['default']->label = "Other";
$types['default']->base = TRUE;
$types['default']->weight = 1000;
$types['default']->type_callback_args = array(
'match_type' => 'any',
'mimetypes' => array('/.*/'),
);
// Define the common media types: image, audio, and video.
$types['image'] = new stdClass();
$types['image']->name = 'image';
$types['image']->label = "Image";
$types['image']->base = TRUE;
$types['image']->type_callback_args = array(
'match_type' => 'all',
'mimetypes' => array('/^image/'),
'extensions' => array('jpg', 'jpeg', 'gif', 'png', 'tiff'),
'streams' => array('public', 'private'),
);
$types['audio'] = new stdClass();
$types['audio']->name = 'audio';
$types['audio']->label = "Audio";
$types['audio']->base = TRUE;
$types['audio']->type_callback_args = array(
'match_type' => 'all',
'mimetypes' => array('/^audio/'),
'extensions' => array('mp3', 'ogg', 'wma'),
'streams' => array('public', 'private'),
);
$types['video'] = new stdClass();
$types['video']->name = 'video';
$types['video']->label = "Video";
$types['video']->base = TRUE;
$types['video']->type_callback_args = array(
'match_type' => 'all',
'mimetypes' => array('/^video/'),
'extensions' => array('mov', 'mp4', 'avi'),
'streams' => array('public', 'private'),
);
// Create the defined types.
foreach ($types as $name => $type) {
media_type_save($type);
// @todo By default, we hide the file display in the 'small' view mode for
// legacy reasons. Does it still make sense to do so? See
// http://drupal.org/node/1051090.
$bundle_settings = field_bundle_settings('file', $name);
$bundle_settings['extra_fields']['display']['file']['media_small'] = array('weight' => 0, 'visible' => FALSE);
field_bundle_settings('file', $name, $bundle_settings);
}
// Set permissions.
$roles = user_roles();
foreach ($roles as $rid => $role) {
user_role_grant_permissions($rid, array('view media'));
}
// Updates the type field for the first MEDIA_UPDATE_RECORDS_ON_INSTALL files.
$invalid_files = media_type_invalid_files_count();
if ($invalid_files <= MEDIA_UPDATE_RECORDS_ON_INSTALL) {
media_type_batch_update(FALSE, MEDIA_UPDATE_RECORDS_ON_INSTALL);
}
$invalid_files = media_type_invalid_files_count();
if ($invalid_files > 0) {
// Not all files could be converted. Display a persistant nag message on
// every page for the administrator, urging them to finish the process.
media_variable_set('show_file_type_rebuild_nag', TRUE);
}
}
/**
* Implements hook_uninstall().
*/
function media_uninstall() {
drupal_load('module', 'media');
foreach (media_variable_default() as $name => $value) {
media_variable_del($name);
}
}
/**
* Create the media_list_type table.
*/
function media_update_7000() {
$schema['media_list_type'] = array(
'description' => 'Stores the user preference for whether to list as table or images.',
'fields' => array(
'uid' => array(
'description' => 'The {user}.uid of the user.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
'type' => array(
'description' => 'The type of display (table or images).',
'type' => 'varchar',
'length' => 32,
'not null' => TRUE,
'default' => '',
),
),
'primary key' => array('uid'),
);
db_create_table('media_list_type', $schema['media_list_type']);
}
/**
* Create the cache_media_xml table.
*/
function media_update_7001() {
$schema['cache_media_xml'] = drupal_get_schema_unprocessed('system', 'cache');
$schema['cache_media_xml']['description'] = 'Cache table for the the results of retreived XML documents for remote streams.';
db_create_table('cache_media_xml', $schema['cache_media_xml']);
}
/**
* Create the media_type table from the media_types variable.
*/
function media_update_7002() {
$schema['media_type'] = array(
'description' => 'Stores the settings for media types.',
'fields' => array(
'name' => array(
'description' => 'The machine name of the media type.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'label' => array(
'description' => 'The label of the media type.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'base' => array(
'description' => 'If this is a base type (i.e. cannot be deleted)',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
'size' => 'tiny',
),
'weight' => array(
'description' => 'Weight of media type. Determines which one wins when claiming a piece of media (first wins)',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
'size' => 'normal',
),
'type_callback' => array(
'description' => 'Callback to determine if provided media is of this type.',
'type' => 'varchar',
'length' => 255,
'not null' => FALSE,
'default' => '',
),
'type_callback_args' => array(
'type' => 'text',
'not null' => FALSE,
'size' => 'big',
'serialize' => TRUE,
'description' => 'A serialized array of name value pairs that will be passed to the callback function',
),
),
'primary key' => array('name'),
);
db_create_table('media_type', $scheme['media_type']);
drupal_load('module', 'media');
$old_types = variable_get('media_types');
foreach ($old_types as $type) {
// Was an error in the original creation
if (isset($type->callbacks)) {
unset($type->callbacks);
}
$type->name = $type->machine_name;
unset($type->machine_name);
media_type_save($type);
}
variable_del('media_types');
}
/**
* We now prefix media namespaced variables with media__, so fix old variables.
*/
function media_update_7003() {
drupal_load('module', 'media');
foreach (media_variable_default() as $variable => $value) {
if (($test = variable_get('media_' . $variable, TRUE)) == variable_get('media_' . $variable, FALSE)) {
media_variable_set($variable, $test);
variable_del('media_' . $variable);
}
}
}
/**
* Removed /media from the menu.
*/
function media_update_7004() {
// Do nothing. Running an update function will trigger a menu rebuild.
}
/**
* Deprecated update function.
*/
function media_update_7005() {
}
/**
* Rename the file table to file_managed in case head2head was used.
*/
function media_update_7006() {
if (db_table_exists('file') && !db_table_exists('file_managed')) {
db_rename_table('file', 'file_managed');
}
}
/**
* Changes the preview formatter for non-image types to the icon view.
*/
function media_update_7007() {
// @todo media_type_configure_formatters() is a deprecated function, so remove
// this code entirely?
drupal_load('module', 'media');
drupal_load('module', 'field');
foreach (media_type_get_types() as $type => $info) {
if ($type != 'image') {
media_type_configure_formatters($type, array('media_preview' => 'media_large_icon'));
}
}
}
/**
* Give all users view media perm by default
*/
function media_update_7008() {
$roles = user_roles();
foreach ($roles as $rid => $role) {
user_role_grant_permissions($rid, array('view media'));
}
}
/**
* Changes the preview formatter for video types to a square thumbnail, like for images.
*/
function media_update_7009() {
// @todo media_type_configure_formatters() is a deprecated function, so remove
// this code entirely?
drupal_load('module', 'media');
drupal_load('module', 'field');
media_type_configure_formatters('video', array('media_preview' => 'styles_file_square_thumbnail'));
}
/**
* Changes the large formatter for video types to the large file style.
*/
function media_update_7010() {
// @todo media_type_configure_formatters() is a deprecated function, so remove
// this code entirely?
// This formatter association was added to media_enable() at one point, but
// without a corresponding update function, so here's that update function.
drupal_load('module', 'media');
drupal_load('module', 'field');
media_type_configure_formatters('video', array('media_large' => 'styles_file_large'));
}
/**
* Empty update function.
*/
function media_update_7011() {
}
/**
* Create the media_filter_usage table.
*/
function media_update_7012() {
$schema['media_filter_usage'] = array(
'description' => 'Stores fids that have been included in the media tag in formatted textareas.',
'fields' => array(
'fid' => array(
'description' => 'The media {file_managed}.fid.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
'timestamp' => array(
'description' => 'The timestamp the fid was last recorded by media_filter()',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
),
'foreign keys' => array(
'file_managed' => array(
'table' => 'file_managed',
'columns' => array('fid' => 'fid'),
),
),
'primary key' => array('fid'),
'indexes' => array(
'timestamp' => array('timestamp'),
),
);
db_create_table('media_filter_usage', $schema['media_filter_usage']);
}
/**
* Work around a core bug where text format cacheability is not updated.
*
* @see http://drupal.org/node/993230
*/
function media_update_7013() {
$formats = filter_formats();
foreach ($formats as $format) {
$format->filters = filter_list_format($format->format);
// filter_format_save() expects filters to be an array, however
// filter_list_format() gives us objects.
foreach ($format->filters as $key => $value) {
$format->filters[$key] = (array) $value;
}
filter_format_save($format);
}
}
/**
* Rename the media__dialog_get_theme_name variable to media__dialog_theme.
*/
function media_update_7014() {
if ($old_value = variable_get('media__dialog_get_theme_name')) {
variable_del('media__dialog_get_theme_name');
variable_set('media__dialog_theme', $old_value);
}
}
/**
* Empty update function to trigger a registry rebuild.
*/
function media_update_7015() {
}
/**
* Convert Media entities to File entities.
*/
function media_update_7016() {
// Allow File Entity module to take over the {file_managed}.type field. It
// will create new indexes as it needs to, but it doesn't know about old ones,
// so delete them.
if (db_index_exists('file_managed', 'file_type')) {
db_drop_index('file_managed', 'file_type');
}
module_enable(array('file_entity'));
// Move all field instances from Media entity to File entity.
$instances = field_read_instances(array('entity_type' => 'media'), array('include_inactive' => TRUE, 'include_deleted' => TRUE));
foreach ($instances as $instance) {
// Skip the old self-referencing file field. It will be deleted later in
// this function.
if ($instance['field_name'] === 'file') {
continue;
}
// @todo Convert this to use _update_7000_field_read_fields()
$fields = field_read_fields(array('id' => $instance['field_id']), array('include_inactive' => TRUE, 'include_deleted' => TRUE));
$field = $fields[$instance['field_id']];
// There is no API for updating the entity_type foreign key within field
// data storage. We can do a direct db_update() for when the default SQL
// storage back-end is being used, but must skip updating fields that use a
// different storage type.
// @todo What else should be added here (e.g., logging and/or a message to
// the administrator)?
if ($field['storage']['type'] !== 'field_sql_storage' || !module_exists('field_sql_storage') || !$field['storage']['active']) {
$messages[] = t('Cannot update field %id (%field_name) because it does not use the field_sql_storage storage type.', array(
'%id' => $field['id'],
'%field_name' => $field['field_name'],
));
continue;
}
// Update the data tables.
$table_name = _field_sql_storage_tablename($field);
$revision_name = _field_sql_storage_revision_tablename($field);
db_update($table_name)
->fields(array('entity_type' => 'file'))
->condition('entity_type', 'media')
->condition('bundle', $instance['bundle'])
->execute();
db_update($revision_name)
->fields(array('entity_type' => 'file'))
->condition('entity_type', 'media')
->condition('bundle', $instance['bundle'])
->execute();
// Once all the data has been updated, update the {field_config_instance}
// record.
db_update('field_config_instance')
->fields(array('entity_type' => 'file'))
->condition('id', $instance['id'])
->execute();
}
// Update the field_bundle_settings configuration variable: move media bundle
// settings to file bundles, and move settings of the old self-referencing
// file field to the new file pseudo-field.
$settings = variable_get('field_bundle_settings', array());
if (!isset($settings['file'])) {
$settings['file'] = array();
}
if (isset($settings['media'])) {
$settings['file'] = array_merge($settings['file'], $settings['media']);
unset($settings['media']);
}
foreach ($instances as $instance) {
if ($instance['field_name'] === 'file' && !$instance['deleted']) {
if (isset($instance['widget']['weight'])) {
$settings['file'][$instance['bundle']]['extra_fields']['form']['file']['weight'] = $instance['widget']['weight'];
}
if (isset($instance['display'])) {
foreach ($instance['display'] as $view_mode => $display) {
if (isset($display['weight'])) {
$settings['file'][$instance['bundle']]['extra_fields']['display']['file'][$view_mode]['weight'] = $display['weight'];
}
if (isset($display['type'])) {
$settings['file'][$instance['bundle']]['extra_fields']['display']['file'][$view_mode]['visible'] = ($display['type'] != 'hidden');
}
}
}
}
}
variable_set('field_bundle_settings', $settings);
// Copy field formatter settings of old self-referencing file field to file
// pseudo-field formatter settings.
$file_displays = variable_get('file_displays', array());
foreach ($instances as $instance) {
if ($instance['field_name'] === 'file' && !$instance['deleted']) {
if (isset($instance['display'])) {
foreach ($instance['display'] as $view_mode => $display) {
if (isset($display['type']) && $display['type'] != 'hidden') {
$file_formatter = 'file_field_' . $display['type'];
$file_displays[$instance['bundle']][$view_mode][$file_formatter]['status'] = TRUE;
if (isset($display['settings'])) {
$file_displays[$instance['bundle']][$view_mode][$file_formatter]['settings'] = $display['settings'];
}
}
}
}
}
}
variable_set('file_displays', $file_displays);
// Delete the old self-referencing file field instances. If all instances are
// deleted, field_delete_instance() will delete the field too.
foreach ($instances as $instance) {
if ($instance['field_name'] === 'file' && !$instance['deleted']) {
field_delete_instance($instance);
}
}
field_cache_clear();
}
/**
* Move file display configurations from the 'file_displays' variable to the
* {file_display} table.
*/
function media_update_7017() {
// If the {file_display} table doesn't exist, then the File Entity module's
// update functions will automatically take care of migrating the
// configurations. However, if file_entity_update_7001() has already run
// prior to media_update_7016(), run it again in order to capture those
// configurations too.
if (db_table_exists('file_display')) {
file_entity_update_7001();
}
}
/**
* Empty update function to trigger a menu rebuild.
*/
function media_update_7018() {
}
/**
* Update old per-view-mode media field formatters to the generic media formatter with a setting.
*/
function media_update_7019() {
$instances = array();
$fields = field_read_fields(array('type' => 'media'), array('include_inactive' => TRUE));
foreach ($fields as $field) {
$instances = array_merge($instances, field_read_instances(array('field_id' => $field['id']), array('include_inactive' => TRUE)));
}
foreach ($instances as $instance) {
$update_instance = FALSE;
foreach ($instance['display'] as $view_mode => $display) {
if (in_array($display['type'], array('media_link', 'media_preview', 'media_small', 'media_large', 'media_original'))) {
$update_instance = TRUE;
$instance['display'][$view_mode]['type'] = 'media';
$instance['display'][$view_mode]['settings'] = array('file_view_mode' => $display['type']);
}
}
if ($update_instance) {
field_update_instance($instance);
}
}
}
/**
* Delete the wysiwyg_allowed_types variable if it is the same as default.
*/
function media_update_7020() {
if (variable_get('media__wysiwyg_allowed_types') == array('image', 'video')) {
variable_del('media__wysiwyg_allowed_types');
}
}

View File

@@ -0,0 +1,142 @@
<?php
/**
* @file
* Media module integration for the Media module.
*/
/**
* Implements hook_media_browser_plugin_info().
*/
function media_media_browser_plugin_info() {
$plugins = array();
// @TODO: Should we add a permission around this?
$plugins['library'] = array(
'#weight' => 10,
);
if (user_access('administer media') || user_access('edit media')) {
$plugins['upload'] = array(
'#weight' => -10,
);
}
return $plugins;
}
/**
* Implements hook_media_browser_plugin_view().
*/
function media_media_browser_plugin_view($plugin_name, $params) {
$path = drupal_get_path('module', 'media');
$params += array(
'types' => array(),
'multiselect' => FALSE,
);
// The multiselect parameter is a string. So we check to see if it is set and
// adjust the local variable accordingly.
if ($params['multiselect'] != 'false' && $params['multiselect'] !== FALSE) {
$params['multiselect'] = TRUE;
}
$redirect = array('media/browser', array('query' => array('render' => 'media-popup')));
switch ($plugin_name) {
case 'upload':
$attached = array();
if ($params['multiselect'] && module_exists('plupload')) {
$upload_form_id = 'media_add_upload_multiple';
$attached['js'] = array($path . '/js/plugins/media.upload_multiple.js');
}
else {
$upload_form_id = 'media_add_upload';
}
module_load_include('inc', 'media', 'includes/media.pages');
$upload_form = drupal_get_form($upload_form_id, $params);
return array(
'#title' => t('Upload'),
'form' => array($upload_form),
'#attached' => $attached,
);
break;
case 'library':
return array(
'#title' => t('Library'),
'#attached' => array(
'js' => array(
$path . '/js/plugins/media.library.js',
),
'css' => array(
//@todo: should move this.
$path . '/js/plugins/media.library.css',
),
),
'#settings' => array(
'viewMode' => 'thumbnails',
'getMediaUrl' => url('media/browser/list'),
// We should probably change this to load dynamically when requested
// via the JS file.
) + $params,
'#markup' => '<div id="container"><div id="scrollbox"><ul id="media-browser-library-list" class="media-list-thumbnails"></ul><div id="status"></div></div></div>',
);
break;
}
return array();
}
/**
* Implements hook_media_display_types().
*
* This is used to display media in different ways on the admin section.
* Perhaps should be merged in with the browser display.
*/
function media_media_display_types() {
$path = drupal_get_path('module', 'media');
$display_types = array();
$display_types['list'] = array(
'title' => t('List'),
'description' => t('Display as a list.'),
'icon' => $path . '/images/display-list.png',
'icon_active' => $path . '/images/display-list-active.png',
'callback' => 'media_admin_list',
'file' => drupal_get_path('module', 'media') . '/includes/media.admin.inc',
);
$display_types['thumbnails'] = array(
'title' => t('Thumbnails'),
'description' => t('Display as thumbnails.'),
'icon' => $path . '/images/display-thumbnails.png',
'icon_active' => $path . '/images/display-thumbnails-active.png',
'callback' => 'media_admin_thumbnails',
'file' => drupal_get_path('module', 'media') . '/includes/media.admin.inc',
);
return $display_types;
}
/**
* Implements hook_media_operations().
*/
function media_media_operations() {
$operations = array(
'delete' => array(
'label' => t('Delete'),
'callback' => NULL,
),
'edit' => array(
'label' => t('Edit'),
'callback' => NULL,
'redirect' => 'media/%fids/multiedit'
),
);
if (!module_exists('multiform')) {
// If the multiform module is not installed, do not show this option.
unset($operations['edit']);
}
return $operations;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,24 @@
<?php
/**
* @file
* Hooks provided by the media_internet module.
*/
/**
* Implementors return an multidim array, keyed by a class name
* with the following elements:
*
* - title
* - image (optional)
* - hidden: bool If the logo should be shown on form. (optional)
* - weight (optional)
*/
function hook_media_internet_providers() {
return array(
'youtube' => array(
'title' => 'youtube',
'image' => 'youtube.jpg'
),
);
}

View File

@@ -0,0 +1,13 @@
name = Media Internet Sources
description = Provides an API for accessing media on various internet services
package = Media
core = 7.x
dependencies[] = media
files[] = media_internet.module
; Information added by drupal.org packaging script on 2012-03-23
version = "7.x-1.0"
core = "7.x"
project = "media"
datestamp = "1332537952"

View File

@@ -0,0 +1,65 @@
<?php
/**
* @file
* Media module integration for the Media internet module.
*/
/**
* Implements hook_media_browser_plugin_info().
*/
function media_internet_media_browser_plugin_info() {
$plugins = array();
if (user_access('administer media') || user_access('add media from remote sources')) {
$plugins['media_internet'] = array(
'#weight' => -10,
);
}
return $plugins;
}
/**
* Implements hook_media_browser_plugin_view().
*/
function media_internet_media_browser_plugin_view($plugin_name, $params) {
$path = drupal_get_path('module', 'media');
$types = isset($params['types']) ? $params['types'] : array();
$multiselect = isset($params['multiselect']) ? $params['multiselect'] : FALSE;
$redirect = array('media/browser', array('query' => array('render' => 'media-popup')));
switch ($plugin_name) {
case 'media_internet':
// @todo: implement the multiselect argument here.
$from_web_form = drupal_get_form('media_internet_add', $types, $multiselect);
return array(
'#title' => t('Web'),
'form' => array($from_web_form),
'#attached' => array(
//'js' => array($path . '/js/plugins/media.fromurl.js'),
),
);
break;
}
return array();
}
/**
* Implements hook_media_internet_providers();
*
* Provides a very basic handler which copies files from remote sources to the
* local files directory.
*/
function media_internet_media_internet_providers() {
return array(
'MediaInternetFileHandler' => array(
'title' => 'Files',
'hidden' => TRUE,
// Make it go last.
'weight' => 10000,
),
);
}

View File

@@ -0,0 +1,350 @@
<?php
/**
* Implements hook_hook_info().
*/
function media_internet_hook_info() {
$hooks = array(
'media_internet_providers',
);
return array_fill_keys($hooks, array('group' => 'media'));
}
/**
* Implement hook_permission().
*/
function media_internet_permission() {
return array(
'add media from remote sources' => array(
'title' => t('Add media from remote services'),
'description' => t('Add media from remote sources such as other websites, YouTube, etc'),
),
);
}
/**
* Provides a form for adding media items from 3rd party sources.
*/
function media_internet_add($form, &$form_state = array(), $types = NULL) {
$form['embed_code'] = array(
'#type' => 'textfield',
'#title' => t('URL or Embed code'),
'#description' => t('Input a url or embed code from one of the listed providers.'),
'#attributes' => array('class' => array('media-add-from-url')),
// There is no standard specifying a maximum length for a URL. Internet
// Explorer supports upto 2083 (http://support.microsoft.com/kb/208427), so
// we assume publicly available media URLs are within this limit.
'#maxlength' => 2083,
);
// @todo:
// Add live previews back (currently broken)
//$form['preview'] = array(
// '#type' => 'item',
// '#title' => t('Preview'),
// '#markup' => '<div id="media-add-from-url-preview"></div>'
//);
$form['#validators'] = array();
if ($types) {
$form['#validators']['media_file_validate_types'] = array($types);
}
$form['providers'] = array();
$form['providers']['header'] = array('#markup' => '<h2>' . t('Supported Providers') . '</h2>');
foreach (media_internet_get_providers() as $key => $provider) {
if (empty($provider['hidden']) || $provider['hidden'] != TRUE) {
if (isset($provider['image'])) {
$form['providers'][$key] = array('#markup' => theme('image', array('path' => $provider['image'], 'title' => $provider['title'])));
}
else {
$form['providers'][$key] = array('#markup' => $provider['title']);
}
// Wrap the provider in a div so we can make a nice list
$form['providers'][$key]['#prefix'] = '<div class="media-provider">';
$form['providers'][$key]['#suffix'] = '</div>';
}
}
if (count($form['providers']) == 1) {
// Just the header, no actual providers
unset($form['providers']['header']);
}
$form['actions'] = array('#type' => 'actions');
$form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Submit'));
return $form;
}
/**
* Allow stream wrappers to have their chance at validation.
*
* Any module that implements hook_media_parse will have an
* opportunity to validate this.
*
* @see media_parse_to_uri()
*/
function media_internet_add_validate($form, &$form_state) {
// Supporting providers can now claim this input. It might be a URL, but it
// might be an embed code as well.
$embed_code = $form_state['values']['embed_code'];
try {
$provider = media_internet_get_provider($embed_code);
$provider->validate();
} catch (MediaInternetNoHandlerException $e) {
form_set_error('url', $e->getMessage());
return;
} catch (MediaInternetValidationException $e) {
form_set_error('url', $e->getMessage());
return;
}
$validators = $form['#validators'];
$file = $provider->getFileObject();
if ($validators) {
try {
$file = $provider->getFileObject();
} catch (Exception $e) {
form_set_error('url', $e->getMessage());
return;
}
// Check for errors. @see media_add_upload_validate calls file_save_upload().
// this code is ripped from file_save_upload because we just want the validation part.
// Call the validation functions specified by this function's caller.
$errors = file_validate($file, $validators);
if (!empty($errors)) {
$message = t('%url could not be added.', array('%url' => $embed_code));
if (count($errors) > 1) {
$message .= theme('item_list', array('items' => $errors));
}
else {
$message .= ' ' . array_pop($errors);
}
form_set_error('url', $message);
return FALSE;
}
}
// @TODO: Validate that if we have no $uri that this is a valid file to
// save. For instance, we may only be interested in images, and it would
// be helpful to let the user know they passed the HTML page containing
// the image accidentally. That would also save us from saving the file
// in the submit step.
// This is kinda a hack of the same.
// This should use the file_validate routines that the upload form users.
// We need to fix the media_parse_to_file routine to allow for a validation.
}
/**
* Upload a file from a URL.
*
* This will copy a file from a remote location and store it locally.
*
* @see media_parse_to_uri()
* @see media_parse_to_file()
*/
function media_internet_add_submit($form, &$form_state) {
$embed_code = $form_state['values']['embed_code'];
try {
// Save the remote file
$provider = media_internet_get_provider($embed_code);
// Providers decide if they need to save locally or somewhere else.
// This method returns a file object
$file = $provider->save();
}
catch (Exception $e) {
form_set_error('url', $e->getMessage());
return;
}
if (!$file->fid) {
form_set_error('url', t('The file %file could not be saved. An unknown error has occurred.', array('%file' => $embed_code)));
return;
}
$form_state['redirect'] = array('media/browser', array('query' => array('render' => 'media-popup', 'fid' => $file->fid)));
}
/**
* Gets the list of providers.
*
* A "Provider" is a bit of meta-data like a title and a logo and a class which
* can handle saving remote files. Each provider is able to parse an embed code or URL
* and store it as a file object in file_managed.
*/
function media_internet_get_providers() {
$providers = &drupal_static(__FUNCTION__);
if (!isset($providers)) {
$cid = 'media:internet:providers';
if ($cache = cache_get($cid)) {
$providers = $cache->data;
}
else {
$providers = array();
foreach (module_implements('media_internet_providers') as $module) {
foreach (module_invoke($module, 'media_internet_providers') as $key => $provider) {
// Store the module here too for convinience.
$providers[$key] = $provider;
$providers[$key]['module'] = $module;
if (!isset($providers[$key]['weight'])) {
$providers[$key]['weight'] = 0;
}
}
}
uasort($providers, 'drupal_sort_weight');
cache_set($cid, $providers);
}
}
return $providers;
}
/**
* Finds the appropriate provider for a given URL or embed_string
*
* Each provider has a claim() method which it uses to tell media_internet
* that it should handle this input. We cycle through all providers to find
* the right one.
*
* @todo: Make this into a normal hook or something because we have to instantiate
* each class to test and that's not right.
*/
function media_internet_get_provider($embed_string) {
foreach (media_internet_get_providers() as $class_name => $nothing) {
$p = new $class_name($embed_string);
if ($p->claim($embed_string)) {
return $p;
}
}
throw new MediaInternetNoHandlerException(t('Unable to handle the provided embed string or URL.'));
}
class MediaInternetFileHandler extends MediaInternetBaseHandler {
public $fileObject;
public function preSave(&$file_obj) {
// Coppies the remote file locally.
$remote_uri = $file_obj->uri;
//@TODO: we should follow redirection here an save the final filename, not just the basename.
$local_filename = basename($remote_uri);
$local_filename = file_munge_filename($local_filename, media_variable_get('file_extensions'), FALSE);
$local_uri = file_stream_wrapper_uri_normalize('temporary://' . $local_filename);
if (!@copy($remote_uri, $local_uri)) {
throw new Exception('Unable to add file ' . $remote_uri);
return;
}
// Make the current fileObject point to the local_uri, not the remote one.
$file_obj = file_uri_to_object($local_uri);
}
public function postSave(&$file_obj) {
$scheme = variable_get('file_default_scheme', 'public') . '://';
$uri = file_stream_wrapper_uri_normalize($scheme . $file_obj->filename);
// Now to its new home.
$file_obj = file_move($file_obj, $uri, FILE_EXISTS_RENAME);
}
public function getFileObject() {
if (!$this->fileObject) {
$this->fileObject = file_uri_to_object($this->embedCode);
}
return $this->fileObject;
}
public function claim($embedCode) {
// Claim only valid URLs using a supported scheme.
if (!valid_url($embedCode, TRUE) || !in_array(file_uri_scheme($embedCode), media_variable_get('fromurl_supported_schemes'))) {
return FALSE;
}
// This handler is intended for regular files, so don't claim URLs
// containing query strings or fragments.
if (preg_match('/[\?\#]/', $embedCode)) {
return FALSE;
}
// Since this handler copies the remote file to the local web server, do not
// claim a URL with an extension disallowed for media uploads.
$regex = '/\.(' . preg_replace('/ +/', '|', preg_quote(media_variable_get('file_extensions'))) . ')$/i';
if (!preg_match($regex, basename($embedCode))) {
return FALSE;
}
return TRUE;
}
}
abstract class MediaInternetBaseHandler {
public function __construct($embedCode) {
$this->embedCode = $embedCode;
}
/**
* If required, implementors can validate the embedCode.
*/
public function validate() {}
/**
* Returns a file object which can be used for validation
*
* @return StdClass
*/
abstract public function getFileObject();
/**
* Saves a file to the file_managed table (with file_save)
*
* @return StdClass
*/
public function save() {
$file_obj = $this->getFileObject();
$this->preSave($file_obj);
file_save($file_obj);
$this->postSave($file_obj);
return $file_obj;
}
/**
* After the file has been saved, implementors may do additional operations.
*
* @param $file_obj;
*/
public function postSave(&$file_obj) {
}
/**
* Before the file has been saved, implementors may do additional operations.
*/
public function preSave(&$file_obj) {
}
/**
* Recognize if this handler should take the the item with the embed
* coded passed as argument.
*
* @param string $embed_code
*
* @return boolean
* Whether or not this handler is resposible for the give embed code.
*/
abstract public function claim($embed_code);
}
class MediaInternetValidationException extends Exception {
}
class MediaInternetNoHandlerException extends Exception {
}

View File

@@ -0,0 +1,73 @@
<?php
/**
* @file
* Default theme implementation to display a single Drupal page.
*
* Available variables:
*
* General utility variables:
* - $base_path: The base URL path of the Drupal installation. At the very
* least, this will always default to /.
* - $directory: The directory the template is located in, e.g. modules/system
* or themes/garland.
* - $is_front: TRUE if the current page is the front page.
* - $logged_in: TRUE if the user is registered and signed in.
* - $is_admin: TRUE if the user has permission to access administration pages.
*
* Site identity:
* - $front_page: The URL of the front page. Use this instead of $base_path,
* when linking to the front page. This includes the language domain or
* prefix.
* - $logo: The path to the logo image, as defined in theme configuration.
* - $site_name: The name of the site, empty when display has been disabled
* in theme settings.
* - $site_slogan: The slogan of the site, empty when display has been disabled
* in theme settings.
*
* Navigation:
* - $main_menu (array): An array containing the Main menu links for the
* site, if they have been configured.
* - $secondary_menu (array): An array containing the Secondary menu links for
* the site, if they have been configured.
* - $breadcrumb: The breadcrumb trail for the current page.
*
* Page content (in order of occurrence in the default page.tpl.php):
* - $title_prefix (array): An array containing additional output populated by
* modules, intended to be displayed in front of the main title tag that
* appears in the template.
* - $title: The page title, for use in the actual HTML content.
* - $title_suffix (array): An array containing additional output populated by
* modules, intended to be displayed after the main title tag that appears in
* the template.
* - $messages: HTML for status and error messages. Should be displayed
* prominently.
* - $tabs (array): Tabs linking to any sub-pages beneath the current page
* (e.g., the view and edit tabs when displaying a node).
* - $action_links (array): Actions local to the page, such as 'Add menu' on the
* menu administration interface.
* - $feed_icons: A string of all feed icons for the current page.
* - $node: The node object, if there is an automatically-loaded node
* associated with the page, and the node ID is the second argument
* in the page's path (e.g. node/12345 and node/12345/revisions, but not
* comment/reply/12345).
*
* Regions:
* - $page['help']: Dynamic help text, mostly for admin pages.
* - $page['highlight']: Items for the highlighted content region.
* - $page['content']: The main content of the current page.
* - $page['sidebar_first']: Items for the first sidebar.
* - $page['sidebar_second']: Items for the second sidebar.
* - $page['header']: Items for the header region.
* - $page['footer']: Items for the footer region.
*
* @see template_preprocess()
* @see template_preprocess_page()
* @see template_process()
*/
?>
<div id="media-browser-page-wrapper"><div id="media-browser-page">
<?php if (isset($messages)) { print $messages; } ?>
<?php print render($page['content']); ?>
</div></div> <!-- /#page, /#page-wrapper -->

View File

@@ -0,0 +1,64 @@
<?php
/**
* @file
* Tests for media entity controllers.
*/
/**
* Test media type creation and management.
*/
class MediaEntityTest extends DrupalWebTestCase {
public static function getInfo() {
return array(
'name' => 'Media entity',
'description' => 'Tests media entity handling',
'group' => 'Media',
'dependencies' => array('ctools'),
);
}
function setUp() {
parent::setUp('media');
// Nice, TDD FTW. #totalsarcasm
variable_set('simpletest_verbose', TRUE);
}
/**
* Test the ability to create and query media items.
*/
function testQueryMedia() {
$text_files = $this->drupalGetTestFiles('text');
$images = $this->drupalGetTestFiles('image');
// Moves serveral images to public://
foreach (array_merge($text_files, $images) as $file) {
$file = file_uri_to_object($file->uri);
file_save($file);
}
$query = new EntityFieldQuery();
$query->entityCondition('entity_type', 'file');
$query->propertyCondition('uri', 'public://%', 'LIKE');
$result = $query->execute();
$this->assertEqual(count($result['file']), count($images) + count($text_files), "Returned results as expected for like % condition");
$query = new EntityFieldQuery();
$query->entityCondition('entity_type', 'file');
$query->propertyCondition('uri', '%.jpg', 'LIKE');
$result = $query->execute();
$this->assertEqual(count($result['file']), 2, "Returned 2 results as expected for jpgs");
$query = new EntityFieldQuery();
$query->entityCondition('entity_type', 'file');
$query->propertyCondition('type', 'image');
$result = $query->execute();
$this->assertEqual(count($result['file']), count($images), "Returned expected results for type query");
$query = new EntityFieldQuery();
$query->entityCondition('entity_type', 'file');
$query->propertyCondition('uri', 'http://%', 'LIKE');
$result = $query->execute();
$this->assertEqual(count($result), 0, "Got no results for http scheme uris");
}
}

View File

@@ -0,0 +1,84 @@
<?php
/**
* @file
* Tests for media types.
*/
/**
* Test media type creation and management.
*/
class MediaTypeTest extends DrupalWebTestCase {
public static function getInfo() {
return array(
'name' => 'Media types',
'description' => 'Tests media types',
'group' => 'Media',
'dependencies' => array('ctools'),
);
}
function setUp() {
parent::setUp('media');
// Nice, TDD FTW. #totalsarcasm
variable_set('simpletest_verbose', TRUE);
}
private function createType($overrides) {
$type = new stdClass();
$type->name = 'test';
$type->label = "Test";
$type->base = TRUE;
// $type->view_mode_defaults = array(
// 'media_preview' => 'styles_file_square_thumbnail',
// 'media_original' => 'file_default',
// );
$type->type_callback_args =
array(
'match_type' => 'all',
'mimetypes' => array('/^test/'),
'extensions' => array('jpg', 'jpeg', 'gif', 'png', 'tiff'),
'streams' => array('public', 'private'),
);
foreach ($overrides as $k => $v) {
$type->$k = $v;
}
media_type_save($type);
return $type;
}
/**
* Test creating a new type. Basic CRUD.
*/
function testCreate() {
$type_machine_name = 'foo';
$type = $this->createType(array('name' => $type_machine_name, 'label' => 'foobar'));
$loaded_type = media_type_load($type_machine_name);
$this->assertEqual($loaded_type->label, 'foobar', "Was able to create a type and retreive it");
}
/**
* Ensures that the weight is respected when types are created.
* @return unknown_type
*/
function testOrder() {
$type = $this->createType(array('name' => 'last', 'label' => 'Last', 'weight' => 100));
$type = $this->createType(array('name' => 'first', 'label' => 'First'));
$types = media_type_get_types();
$keys = array_keys($types);
$this->assertTrue(isset($types['last']) && isset($types['first']), "Both types saved");
$this->assertTrue(array_search('last', $keys) > array_search('first', $keys), 'Type which was supposed to be first came first');
}
/**
* Test view mode assignment. Currently fails, don't know why.
* @return unknown_type
*/
function testViewModesAssigned() {
}
}

View File

@@ -0,0 +1,85 @@
<?php
/**
* @file
* Define the WYSIWYG browser plugin.
*/
/**
* Implementation of WYSIWYG's hook_INCLUDE_plugin().
*/
function media_media_plugin() {
// Include the required browser JS.
// @todo: wyswiyg should allow libraries and multiple js files
// to be defined by this hook.
// @see http://drupal.org/node/1039076
media_include_browser_js();
// Plugin definition
$plugins['media'] = array(
'title' => t(media_variable_get('wysiwyg_title')),
'vendor url' => 'http://drupal.org/project/media',
'icon path' => drupal_get_path('module', 'media') . '/images',
'icon file' => 'wysiwyg-media.gif',
'icon title' => t(media_variable_get('wysiwyg_icon_title')),
// @todo: move this to the plugin directory for the wysiwyg plugin.
'js path' => drupal_get_path('module', 'media') . '/js',
'js file' => 'wysiwyg-media.js',
'css file' => NULL,
'css path' => NULL,
'settings' => array(
'global' => array(
'types' => media_variable_get('wysiwyg_allowed_types'),
'id' => 'media_wysiwyg',
),
),
);
return $plugins;
}
/**
* Prepares the page to be able to launch the media browser.
*
* Defines default variables.
*/
function media_include_browser_js() {
static $included;
if ($included) {
return;
}
$included = TRUE;
module_load_include('inc', 'media', 'includes/media.browser');
$javascript = media_browser_js();
foreach ($javascript as $key => $definitions) {
foreach ($definitions as $definition) {
$function = 'drupal_add_' . $key;
// Since the arguments to pass are variable, use call_user_func_array().
// This will not handle all potential drupal_add_*() functions directly
// but covers the js and library needed here, which are unlikely to be
// expanded since this function is only a workaround for a wysiwyg limitation.
call_user_func_array($function, $definition);
}
}
// Add wysiwyg-specific settings.
$settings = array('blacklist' => array('src', 'fid', 'view_mode', 'format'));
drupal_add_js(array('media' => $settings), 'setting');
}
/**
* Element validate callback for the media WYSIWYG button.
*/
function media_wysiwyg_button_element_validate($element, &$form_state) {
if (!empty($element['#value'])) {
$format = filter_format_load($form_state['build_info']['args'][0]->format);
$filters = filter_list_format($format->format);
if (empty($filters['media_filter']->status)) {
form_error($element, t('The <em>Convert Media tags to markup</em> filter must be enabled for the <a href="@format-link">@format format</a> in order to use the Media browser WYSIWYG button.', array(
'@format-link' => url('admin/config/content/formats/' . $format->format, array('query' => array('destination' => $_GET['q']))),
'@format' => $format->name,
)));
}
}
return $element;
}