Compare commits

..
66 Commits
Author SHA1 Message Date
Bachir Soussi Chiadmi e36279d8b1 changed materio_serach_api, query configuration by query 2016-03-16 12:35:55 +01:00
Bachir Soussi Chiadmi 0b6e0c10cb updated search feature 2016-03-02 10:15:50 +01:00
Bachir Soussi Chiadmi ba1ec7113e updated search_api and search_api_solr 2016-03-01 16:23:45 +01:00
Bachir Soussi Chiadmi deb2bff572 print fine tuning 2016-01-21 12:02:55 +01:00
Bachir Soussi Chiadmi 538c791bae fine tunning print fiche 2016-01-20 18:15:38 +01:00
Bachir Soussi Chiadmi df107a0443 fixed printed material
WARNING : we are nox using gulp instead of grunt, watch for bugs
2016-01-19 19:23:16 +01:00
Bachir Soussi Chiadmi c9b16289ef added remove duplicates modules 2016-01-19 15:10:33 +01:00
Bachir Soussi Chiadmi b4acd7d8bb updated video filter module to handle https on embeded videos 2015-12-20 10:56:50 +01:00
Bachir Soussi Chiadmi 3af02617b4 added html5 boiler plate mime headers to htaccess 2015-12-19 21:36:40 +01:00
Bachir Soussi Chiadmi 0fdb4abd71 ajax autocomplete fixe 2015-12-03 12:23:53 +01:00
Bachir Soussi Chiadmi 90be34eef1 updated htaccess 2015-12-02 17:17:06 +01:00
Bachir Soussi Chiadmi 3e984dc6c5 updated htaccess 2015-12-02 17:13:13 +01:00
Bachir Soussi Chiadmi a0cb0b2171 updated htaccess 2015-12-02 16:57:17 +01:00
Bachir Soussi Chiadmi f6346b0195 updated htaccess 2015-12-02 16:52:56 +01:00
Bachir Soussi Chiadmi 300f698989 updated htaccess 2015-12-02 16:46:36 +01:00
Bachir Soussi Chiadmi 8cbb5f8c4c updated htaccess 2015-12-02 16:45:51 +01:00
Bachir Soussi Chiadmi a106b98209 updated htaccess 2015-12-02 16:30:59 +01:00
Bachir Soussi Chiadmi 8fe92dbcc7 updated htaccess 2015-12-02 16:29:02 +01:00
Bachir Soussi Chiadmi 60f2c5c7fa updated htaccess 2015-12-02 15:20:02 +01:00
Bachir Soussi Chiadmi d96c17454b updated htaccess 2015-12-02 11:40:34 +01:00
Bachir Soussi Chiadmi ed6ae84181 added ubercart ssl module 2015-12-02 11:36:55 +01:00
Bachir Soussi Chiadmi 35e914df58 created admon taxo view 2015-11-25 11:34:49 +01:00
Bachir Soussi Chiadmi 6b46869a16 htaccess : all redirects are handle by claranet 2015-11-25 09:47:27 +01:00
Bachir Soussi Chiadmi 892dba801f added entity_translation_export_import module 2015-11-25 00:53:33 +01:00
Bachir Soussi Chiadmi 50dfd2d89b removed i18n_taxonomy module from features dependencies
fixed some bugs
2015-11-24 23:50:19 +01:00
Bachir Soussi Chiadmi 9a18821b7e fixed search api materio settings 2015-11-24 18:17:34 +01:00
Bachir Soussi Chiadmi 82bf36a5eb recreated features 2015-11-24 18:05:48 +01:00
Bachir Soussi Chiadmi 3eb20676ca search api autocomplet t() for transmlate term name 2015-11-24 16:51:48 +01:00
Bachir Soussi Chiadmi 2155a66eb1 added special menu items module 2015-11-24 15:29:54 +01:00
Bachir Soussi Chiadmi 62fef652fc removed redirects from htaccess, claranet will handle this on vhosts 2015-11-24 15:15:10 +01:00
Bachir Soussi Chiadmi 951c9114a2 added code for remove all http on file address 2015-11-24 13:42:37 +01:00
Bachir Soussi Chiadmi edba04f103 htaccess fixe 2015-11-24 13:23:18 +01:00
Bachir Soussi Chiadmi 26ab9aa56b htaccess fixe 2015-11-24 13:22:11 +01:00
Bachir Soussi Chiadmi 5716acf5bb htaccess fixe 2015-11-24 13:20:51 +01:00
Bachir Soussi Chiadmi 12fe570fbd htaccess fixe 2015-11-24 13:20:11 +01:00
Bachir Soussi Chiadmi 66ee9c6657 htaccess fixe 2015-11-24 13:19:01 +01:00
Bachir Soussi Chiadmi ca5231f1fe htaccess fixe 2015-11-24 13:09:04 +01:00
Bachir Soussi Chiadmi b9a8db8140 htaccess fixe 2015-11-24 13:08:22 +01:00
Bachir Soussi Chiadmi d25e7428bc htaccess fixe 2015-11-24 12:55:37 +01:00
Bachir Soussi Chiadmi 3027b3985b htaccess fixe 2015-11-24 12:55:09 +01:00
Bachir Soussi Chiadmi d214a44291 htaccess fixe 2015-11-24 12:49:06 +01:00
Bachir Soussi Chiadmi fc7753f7a0 htaccess fixe 2015-11-24 12:48:26 +01:00
Bachir Soussi Chiadmi 7c51b255fb htaccess fixe 2015-11-24 12:47:26 +01:00
Bachir Soussi Chiadmi 5e099d48de htaccess fixe 2015-11-24 12:35:16 +01:00
Bachir Soussi Chiadmi bf84ac06cd htaccess fixe 2015-11-24 12:34:56 +01:00
Bachir Soussi Chiadmi 87beabee92 htaccess fixe 2015-11-24 12:29:51 +01:00
Bachir Soussi Chiadmi f6ee366a71 htaccess fixe 2015-11-24 12:29:32 +01:00
Bachir Soussi Chiadmi b216e30f28 htaccess fixe 2015-11-24 12:28:04 +01:00
Bachir Soussi Chiadmi 0a863fc1bb htaccess ssl 2015-11-24 12:26:56 +01:00
Bachir Soussi Chiadmi a5edc41323 htaccess fixe 2015-11-24 12:12:57 +01:00
Bachir Soussi Chiadmi 1cc2c9af75 updated term csv files 2015-11-24 11:17:12 +01:00
Bachir Soussi Chiadmi cc653b73be bug fixe 2015-11-24 11:02:29 +01:00
Bachir Soussi Chiadmi 73993a539b added fr2en batch function 2015-11-24 10:59:13 +01:00
Bachir Soussi Chiadmi d7e7410950 added term_merge module 2015-11-24 10:56:30 +01:00
Bachir Soussi Chiadmi 7298235d4f added taxonomy wrangler module 2015-11-23 14:47:44 +01:00
Bachir Soussi Chiadmi fc0a779214 fixed bookmarks for students 2015-10-21 20:22:58 +02:00
Bachir Soussi Chiadmi 248c5ed3e1 added search form back bouton 2015-10-21 19:43:37 +02:00
Bachir Soussi Chiadmi ace9de07e2 started base home back btn 2015-10-21 17:44:29 +02:00
Bachir Soussi Chiadmi 7ad1f5dc67 patched i18n modules for node don't record chosen language on create
see https://www.drupal.org/node/1852102#comment-9612303
2015-10-14 17:15:19 +02:00
Bachir Soussi Chiadmi d5628cc78e updated entity_translation to last dev version 2015-10-14 17:09:13 +02:00
Bachir Soussi Chiadmi 81371521c7 updated gitignore to ignore .directory files 2015-10-14 17:03:08 +02:00
Bachir Soussi Chiadmi eb699f528d updated date pathauto addressfield honeypot features modules 2015-10-12 12:03:12 +02:00
Bachir Soussi Chiadmi 0ba0c21bb9 upadated colorbox andd prod_check 2015-10-12 11:52:36 +02:00
Bachir Soussi Chiadmi 50c3e1c7d7 added list of deleted entities from flag list content 2015-09-27 19:50:44 +02:00
Bachir Soussi Chiadmi f0cb6175c1 patched flag list to trigger remove entity from lists when this entity is deleted 2015-09-27 18:55:26 +02:00
Bachir Soussi Chiadmi e004b7a238 patched node-export module
https://www.drupal.org/node/1869918
https://www.drupal.org/node/1911638
2015-09-18 14:54:38 +02:00
382 changed files with 38187 additions and 19085 deletions
+1
View File
@@ -21,3 +21,4 @@ node_modules
.sass-cache
typescript
.directory
+149 -106
View File
@@ -3,7 +3,7 @@
#
# Protect files and directories from prying eyes.
<FilesMatch "\.(engine|inc|info|install|make|module|profile|test|po|sh|.*sql|theme|tpl(\.php)?|xtmpl)$|^(\..*|Entries.*|Repository|Root|Tag|Template)$">
<FilesMatch "\.(engine|inc|info|install|make|module|profile|test|po|sh|.*sql|theme|tpl(\.php)?|xtmpl)(~|\.sw[op]|\.bak|\.orig|\.save)?$|^(\..*|Entries.*|Repository|Root|Tag|Template)$|^#.*#$|\.php(~|\.sw[op]|\.bak|\.orig\.save)$">
Order allow,deny
</FilesMatch>
@@ -20,7 +20,7 @@ ErrorDocument 404 /index.php
DirectoryIndex index.php index.html index.htm
# Override PHP settings that cannot be changed at runtime. See
# sites/default/default.settings.php and drupal_initialize_variables() in
# sites/default/default.settings.php and drupal_environment_initialize() in
# includes/bootstrap.inc for settings that can be changed at runtime.
# PHP 5, Apache 1 and 2.
@@ -56,6 +56,17 @@ DirectoryIndex index.php index.html index.htm
<IfModule mod_rewrite.c>
RewriteEngine on
# Set "protossl" to "s" if we were accessed via https://. This is used later
# if you enable "www." stripping or enforcement, in order to ensure that
# you don't bounce between http and https.
RewriteRule ^ - [E=protossl]
RewriteCond %{HTTPS} on
RewriteRule ^ - [E=protossl:s]
# Make sure Authorization HTTP header is available to PHP
# even when running as CGI or FastCGI.
RewriteRule ^ - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
# Block access to "hidden" directories whose names begin with a period. This
# includes directories used by version control systems such as Subversion or
# Git to store control files. Files whose names begin with a period, as well
@@ -71,18 +82,6 @@ DirectoryIndex index.php index.html index.htm
# downloaded.
RewriteRule "(^|/)\." - [F]
# custom
RewriteCond %{HTTP_HOST} ^basebeta\.materio\.com [OR]
RewriteCond %{HTTP_HOST} ^materio\.com [OR]
RewriteCond %{HTTP_HOST} ^www\.materio\.fr [OR]
RewriteCond %{HTTP_HOST} ^materio\.fr [OR]
RewriteCond %{HTTP_HOST} ^www\.materio\.net [OR]
RewriteCond %{HTTP_HOST} ^materio\.net [OR]
RewriteCond %{HTTP_HOST} ^www\.materio\.eu [OR]
RewriteCond %{HTTP_HOST} ^materio\.eu [NC]
RewriteRule ^ http://www.materio.com%{REQUEST_URI} [L,R=301]
# If your site can be accessed both with and without the 'www.' prefix, you
# can use one of the following settings to redirect users to your preferred
# URL, either WITH or WITHOUT the 'www.' prefix. Choose ONLY one option:
@@ -90,14 +89,32 @@ DirectoryIndex index.php index.html index.htm
# To redirect all users to access the site WITH the 'www.' prefix,
# (http://example.com/... will be redirected to http://www.example.com/...)
# uncomment the following:
# RewriteCond %{HTTP_HOST} .
# RewriteCond %{HTTP_HOST} !^www\. [NC]
# RewriteRule ^ http://www.%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
#
# RewriteRule ^ http%{ENV:protossl}://www.%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
# To redirect all users to access the site WITHOUT the 'www.' prefix,
# (http://www.example.com/... will be redirected to http://example.com/...)
# uncomment the following:
# RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
# RewriteRule ^ http://%1%{REQUEST_URI} [L,R=301]
# RewriteRule ^ http%{ENV:protossl}://%1%{REQUEST_URI} [L,R=301]
# redirect all materiau domains to materio.com
RewriteCond %{HTTP_HOST} ^basebeta\.materio\.com [OR]
RewriteCond %{HTTP_HOST} ^www\.materio\.fr [OR]
RewriteCond %{HTTP_HOST} ^materio\.fr [OR]
RewriteCond %{HTTP_HOST} ^www\.materio\.net [OR]
RewriteCond %{HTTP_HOST} ^materio\.net [OR]
RewriteCond %{HTTP_HOST} ^www\.materio\.eu [OR]
RewriteCond %{HTTP_HOST} ^materio\.eu [OR]
RewriteCond %{HTTP_HOST} ^www\.materio\.com
# RewriteRule ^ http%{ENV:protossl}://materio.com%{REQUEST_URI} [L,R=301]
RewriteRule ^ https://materio.com%{REQUEST_URI} [L,R=301]
# Redirect to HTTPS
# RewriteCond %{HTTPS} off
# RewriteCond %{HTTP:X-Forwarded-Proto} !https
# RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
# Modify the RewriteBase if you are using Drupal in a subdirectory or in a
# VirtualDocumentRoot and the rewrite rules are not working properly.
@@ -116,24 +133,6 @@ DirectoryIndex index.php index.html index.htm
RewriteCond %{REQUEST_URI} !=/favicon.ico
RewriteRule ^ index.php [L]
## local
# RewriteCond %{REMOTE_ADDR} !^127\.0\.0\.1
## maison quentin
# RewriteCond %{REMOTE_ADDR} !^86\.68\.188\.175
## materio
# RewriteCond %{REMOTE_ADDR} !^212\.198\.136\.215
## maison bach
# RewriteCond %{REMOTE_ADDR} !^82\.230\.62\.145
# RewriteRule !maintenance http://www.materio.com/maintenance [L,R=302,E=nocache:1]
## http://mark.koli.ch/2010/12/set-cache-control-and-expires-headers-on-a-redirect-with-mod-rewrite.html
## Set the response header if the "nocache" environment variable is set
## in the RewriteRule above.
# Header always set Cache-Control "no-store, no-cache, must-revalidate" env=nocache
## Set Expires too ...
# Header always set Expires "Thu, 01 Jan 1970 00:00:00 GMT" env=nocache
# Rules to correctly serve gzip compressed CSS and JS files.
# Requires both mod_rewrite and mod_headers to be enabled.
<IfModule mod_headers.c>
@@ -160,94 +159,138 @@ DirectoryIndex index.php index.html index.htm
</IfModule>
</IfModule>
# Add headers to all responses.
<IfModule mod_headers.c>
# Disable content sniffing, since it's an attack vector.
Header always set X-Content-Type-Options nosniff
</IfModule>
# from boiler plate V5.02
# FROM Html5 BoilerPlate 4.0
# ######################################################################
# # MEDIA TYPES AND CHARACTER ENCODINGS #
# ######################################################################
# ----------------------------------------------------------------------
# Proper MIME type for all files
# | Media types |
# ----------------------------------------------------------------------
# JavaScript
# Normalize to standard type (it's sniffed in IE anyways)
# tools.ietf.org/html/rfc4329#section-7.2
AddType application/javascript js jsonp
AddType application/json json
# Serve resources with the proper media types (f.k.a. MIME types).
#
# https://www.iana.org/assignments/media-types/media-types.xhtml
# https://httpd.apache.org/docs/current/mod/mod_mime.html#addtype
# Audio
AddType audio/ogg oga ogg
AddType audio/mp4 m4a f4a f4b
<IfModule mod_mime.c>
# Video
AddType video/ogg ogv
AddType video/mp4 mp4 m4v f4v f4p
AddType video/webm webm
AddType video/x-flv flv
# Data interchange
# SVG
# Required for svg webfonts on iPad
# twitter.com/FontSquirrel/status/14855840545
AddType image/svg+xml svg svgz
AddEncoding gzip svgz
# Webfonts
AddType application/vnd.ms-fontobject eot
AddType application/x-font-ttf ttf ttc
AddType font/opentype otf
AddType application/x-font-woff woff
# Assorted types
AddType image/x-icon ico
AddType image/webp webp
AddType text/cache-manifest appcache manifest
AddType text/x-component htc
AddType application/xml rss atom xml rdf
AddType application/x-chrome-extension crx
AddType application/x-opera-extension oex
AddType application/x-xpinstall xpi
AddType application/octet-stream safariextz
AddType application/x-web-app-manifest+json webapp
AddType text/x-vcard vcf
AddType application/x-shockwave-flash swf
AddType text/vtt vtt
AddType application/atom+xml atom
AddType application/json json map topojson
AddType application/ld+json jsonld
AddType application/rss+xml rss
AddType application/vnd.geo+json geojson
AddType application/xml rdf xml
# JavaScript
# Normalize to standard type.
# https://tools.ietf.org/html/rfc4329#section-7.2
AddType application/javascript js
# Manifest files
AddType application/manifest+json webmanifest
AddType application/x-web-app-manifest+json webapp
AddType text/cache-manifest appcache
# Media files
AddType audio/mp4 f4a f4b m4a
AddType audio/ogg oga ogg opus
AddType image/bmp bmp
AddType image/svg+xml svg svgz
AddType image/webp webp
AddType video/mp4 f4v f4p m4v mp4
AddType video/ogg ogv
AddType video/webm webm
AddType video/x-flv flv
# Serving `.ico` image files with a different media type
# prevents Internet Explorer from displaying then as images:
# https://github.com/h5bp/html5-boilerplate/commit/37b5fec090d00f38de64b591bcddcb205aadf8ee
AddType image/x-icon cur ico
# Web fonts
AddType application/font-woff woff
AddType application/font-woff2 woff2
AddType application/vnd.ms-fontobject eot
# Browsers usually ignore the font media types and simply sniff
# the bytes to figure out the font type.
# https://mimesniff.spec.whatwg.org/#matching-a-font-type-pattern
#
# However, Blink and WebKit based browsers will show a warning
# in the console if the following font types are served with any
# other media types.
AddType application/x-font-ttf ttc ttf
AddType font/opentype otf
# Other
AddType application/octet-stream safariextz
AddType application/x-bb-appworld bbaw
AddType application/x-chrome-extension crx
AddType application/x-opera-extension oex
AddType application/x-xpinstall xpi
AddType text/vcard vcard vcf
AddType text/vnd.rim.location.xloc xloc
AddType text/vtt vtt
AddType text/x-component htc
</IfModule>
# ----------------------------------------------------------------------
# UTF-8 encoding
# | Character encodings |
# ----------------------------------------------------------------------
# Use UTF-8 encoding for anything served text/plain or text/html
# Serve all resources labeled as `text/html` or `text/plain`
# with the media type `charset` parameter set to `UTF-8`.
#
# https://httpd.apache.org/docs/current/mod/core.html#adddefaultcharset
AddDefaultCharset utf-8
# Force UTF-8 for a number of file formats
AddCharset utf-8 .atom .css .js .json .rss .vtt .xml
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Serve the following file types with the media type `charset`
# parameter set to `UTF-8`.
#
# https://httpd.apache.org/docs/current/mod/mod_mime.html#addcharset
# ----------------------------------------------------------------------
# A little more security
# ----------------------------------------------------------------------
# Block access to "hidden" directories or files whose names begin with a
# period. This includes directories used by version control systems such as
# Subversion or Git.
<IfModule mod_rewrite.c>
RewriteCond %{SCRIPT_FILENAME} -d [OR]
RewriteCond %{SCRIPT_FILENAME} -f
RewriteRule "(^|/)\." - [F]
</IfModule>
# Block access to backup and source files. These files may be left by some
# text/html editors and pose a great security danger, when anyone can access
# them.
<FilesMatch "(\.(bak|config|sql|fla|psd|ini|log|sh|inc|swp|dist)|~)$">
Order allow,deny
Deny from all
Satisfy All
</FilesMatch>
# Increase cookie security
<IfModule php5_module>
php_value session.cookie_httponly true
<IfModule mod_mime.c>
AddCharset utf-8 .atom \
.bbaw \
.css \
.geojson \
.js \
.json \
.jsonld \
.manifest \
.rdf \
.rss \
.topojson \
.vtt \
.webapp \
.webmanifest \
.xloc \
.xml
</IfModule>
+5 -2
View File
@@ -17,10 +17,13 @@ feedback
filter_path_alias
flag_lists
//nodeformcols
search_api
//search_api
search_api_page
search_api_solr
//search_api_solr
termreferencetree
views
wysiwyg
views_rss_media
node_export :
- https://www.drupal.org/node/1869918
- https://www.drupal.org/node/1911638
-4
View File
@@ -1,4 +0,0 @@
[Dolphin]
Timestamp=2015,4,27,12,4,40
Version=3
ViewMode=2
@@ -0,0 +1 @@
.DS_Store
@@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
@@ -0,0 +1,539 @@
<?php
/**
* Menu Callback. Handles the export of the translatable entities.
*/
function entity_translation_export_import_admin_export_form($form, $form_state){
$form = array('#tree' => TRUE);
// Get translatable entity types.
$enabled = variable_get('entity_translation_entity_types', array());
$info = entity_get_info();
$entities_options = array();
$bundles_options = array();
$fields_options = array();
// Get the translatable fields foreach translatable entity_type.
foreach ($enabled as $entity_type) {
if (entity_translation_enabled($entity_type)) {
$bundles = !empty($info[$entity_type]['bundles']) ? array_keys($info[$entity_type]['bundles']) : array($entity_type);
foreach ($bundles as $bundle) {
$settings = entity_translation_settings($entity_type, $bundle);
if (entity_translation_enabled_bundle($entity_type, $bundle) && ($handler = entity_translation_get_handler($entity_type, $bundle))) {
$fields_info = field_info_instances($entity_type, $bundle);
foreach ($fields_info as $field_name => $value) {
$field = field_info_field($field_name);
if(field_is_translatable($entity_type, $field) and in_array($field['type'], array('text', 'text_long', 'text_with_summary')) and $field['cardinality'] == 1){
if(!array_key_exists($entity_type, $entities_options)){
$entities_options[$entity_type] = $info[$entity_type]['label'];
}
if(!isset($bundles_options[$entity_type][$bundle])){
$bundles_options[$entity_type][$bundle] = $info[$entity_type]['bundles'][$bundle]['label'];
}
$field_value = $entity_type . ':' . $bundle . ':' . $field_name;
if(!isset($fields_options[$entity_type][$bundle][$field_value])){
$fields_options[$entity_type][$bundle][$field_value] = $value['label'];
}
}
}
}
}
}
}
// If there is no translatable field yet.
if(empty($fields_options)){
$form['no_result'] = array('#markup' => '<p>' . t('You need to enable entity translation for at least one field.') . '</p>');
}
else{
drupal_set_title(t('Export entity translations'));
$form['export'] = array(
'#type' => 'fieldset',
'#title' => t('Export Settings'),
'#collapsible' => FALSE,
'#collapsed' => FALSE,
);
$form['export']['entity_type'] = array(
'#type' => 'checkboxes',
'#options' => $entities_options,
'#ajax' => array(
'callback' => 'entity_translation_export_import_admin_export_bundle_callback',
'wrapper' => 'entity-type-wrapper',
),
);
$form['export']['entity_type_wrapper'] = array(
'#type' => 'container',
'#id' => 'entity-type-wrapper',
);
if(isset($form_state['values']['export']['entity_type'])){
foreach($form_state['values']['export']['entity_type'] as $entity_type => $entity_type_value){
if($entity_type_value){
$form['export']['entity_type_wrapper'][$entity_type] = array(
'#type' => 'fieldset',
'#title' => $entities_options[$entity_type],
'#collapsible' => TRUE,
'#collapsed' => FALSE,
);
foreach($bundles_options[$entity_type] as $bundle_option){
$form['export']['entity_type_wrapper'][$entity_type]['bundle'] = array(
'#type' => 'checkboxes',
'#options' => $bundles_options[$entity_type],
'#ajax' => array(
'callback' => 'entity_translation_export_import_admin_export_fields_callback',
'wrapper' => drupal_html_class($entity_type . '-bundle-wrapper'),
),
);
$form['export']['entity_type_wrapper'][$entity_type]['bundle_wrapper'] = array(
'#type' => 'container',
'#id' => drupal_html_class($entity_type . '-bundle-wrapper'),
);
if(isset($form_state['values']['export']['entity_type_wrapper'][$entity_type]['bundle'])){
foreach($form_state['values']['export']['entity_type_wrapper'][$entity_type]['bundle'] as $bundle => $bundle_value){
if($bundle_value){
$form['export']['entity_type_wrapper'][$entity_type]['bundle_wrapper'][$bundle] = array(
'#type' => 'fieldset',
'#title' => $bundles_options[$entity_type][$bundle],
'#collapsible' => TRUE,
'#collapsed' => FALSE,
);
foreach($bundles_options[$entity_type] as $bundle_option){
$form['export']['entity_type_wrapper'][$entity_type]['bundle_wrapper'][$bundle]['field'] = array(
'#type' => 'checkboxes',
'#options' => $fields_options[$entity_type][$bundle],
);
}
}
}
}
}
}
}
}
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Export Translations'),
);
}
return $form;
}
/**
* Ajax callback.
*/
function entity_translation_export_import_admin_export_bundle_callback($form, $form_state){
$parents = array_slice($form_state['triggering_element']['#array_parents'], 0, -2);
$element = drupal_array_get_nested_value($form, $parents);
return $element['entity_type_wrapper'];
}
/**
* Ajax callback.
*/
function entity_translation_export_import_admin_export_fields_callback($form, $form_state){
$parents = array_slice($form_state['triggering_element']['#array_parents'], 0, -2);
$element = drupal_array_get_nested_value($form, $parents);
return $element['bundle_wrapper'];
}
/**
* Submit callback.
*/
function entity_translation_export_import_admin_export_form_validate($form, $form_state){
// Get all entities to export.
$at_least_one_field = FALSE;
if(isset($form_state['values']['export']['entity_type_wrapper'])){
foreach($form_state['values']['export']['entity_type_wrapper'] as $entity_type => $entity_type_value) {
if($entity_type_value and isset($entity_type_value['bundle_wrapper'])){
foreach($entity_type_value['bundle_wrapper'] as $bundle => $bundle_value) {
if($bundle_value){
foreach($bundle_value['field'] as $field_value){
if($field_value){
$at_least_one_field = TRUE;
}
}
}
}
}
}
}
if(!$at_least_one_field){
form_set_error('', t('You need to select at least one field.'));
}
}
/**
* Submit callback.
*/
function entity_translation_export_import_admin_export_form_submit($form, $form_state){
// Get all entities to export.
$entities = array();
foreach($form_state['values']['export']['entity_type_wrapper'] as $entity_type => $entity_type_value) {
if($entity_type_value){
foreach($entity_type_value['bundle_wrapper'] as $bundle => $bundle_value) {
if($bundle_value){
foreach($bundle_value['field'] as $field_value){
if($field_value){
$parts = explode(':', $field_value);
$entities[$parts[0]][$parts[1]][] = $parts[2];
}
}
}
}
}
}
$limit = 500;
$filename = 'public://entity_translation_export.csv';
variable_set('entity_translation_export_import_file', '');
$batch = array(
'operations' => array(
array('entity_translation_export_import_export_process', array($entities, $limit, $filename)),
),
'finished' => 'entity_translation_export_import_export_finished',
'title' => t('Export Translations'),
'init_message' => t('Retrieving Entities...'),
'progress_message' => t('Processed @current out of @total.'),
'error_message' => t('Exporting has encountered an error.'),
'file' => drupal_get_path('module', 'entity_translation_export_import') . '/entity_translation_export_import.admin.inc',
);
batch_set($batch);
batch_process('admin/config/regional/entity_translation/export');
}
/**
* Batch process callback.
*/
function entity_translation_export_import_export_process($entities, $limit, $filename, &$context){
// Set the default progress data.
if (!isset($context['sandbox']['progress'])) {
$context['results']['file'] = file_save_data('', $filename);
$context['sandbox']['progress'] = array();
$context['sandbox']['progress_total']['max'] = 0;
$context['sandbox']['progress_total']['current'] = 0;
foreach($entities as $entity_type => $bundles){
foreach($bundles as $bundle => $fields){
$query = new EntityFieldQuery();
$context['sandbox']['progress'][$entity_type][$bundle]['max'] = $query->entityCondition('entity_type', $entity_type)->entityCondition('bundle', $bundle)->count()->execute();
$context['sandbox']['progress'][$entity_type][$bundle]['current'] = 0;
$context['sandbox']['progress_total']['max'] += $context['sandbox']['progress'][$entity_type][$bundle]['max'];
}
}
}
if(!empty($context['sandbox']['progress'])){
$entity_type = key($context['sandbox']['progress']);
$bundle = key(current($context['sandbox']['progress']));
$progress = current(current($context['sandbox']['progress']));
// Receive items.
$items = _entity_translation_export_import_export_entity($entity_type, $bundle, $progress['current'], $limit, $entities[$entity_type][$bundle]);
$is_header = ($context['sandbox']['progress_total']['current'] === 0);
$data = _entity_translation_export_import_csv($items, $is_header);
file_put_contents($context['results']['file']->uri, $data, FILE_APPEND);
$progress['current'] += $limit;
$context['sandbox']['progress_total']['current'] += ($progress['current'] > $progress['max'] ? ($progress['max'] - ($progress['current']-$limit)) : $limit);
$context['sandbox']['progress'][$entity_type][$bundle]['current'] = $progress['current'];
if($progress['current'] >= $progress['max']){
unset($context['sandbox']['progress'][$entity_type][$bundle]);
if(empty($context['sandbox']['progress'][$entity_type])){
unset($context['sandbox']['progress'][$entity_type]);
}
}
if(!empty($context['sandbox']['progress'])){
reset($context['sandbox']['progress']);
$entity_type = key($context['sandbox']['progress']);
$bundle = key(current($context['sandbox']['progress']));
$progress = current(current($context['sandbox']['progress']));
$percentage = '';
if(isset($progress['current']) and $progress['current'] > 0){
$percentage = ' (' . $progress['current'] . '/' . $progress['max'] . ')';
}
$info = entity_get_info();
$context['message'] = t('Exporting ' . $info[$entity_type]['label'] . ' - ' . $info[$entity_type]['bundles'][$bundle]['label'] . $percentage);
}
}
$context['finished'] = ($context['sandbox']['progress_total']['current']/$context['sandbox']['progress_total']['max']);
}
/**
* batch process finish callback.
*/
function entity_translation_export_import_export_finished($success, $results){
$link = file_create_url($results['file']->uri);
drupal_set_message(t('Export has been created! Download the <a href="@link">file</a>.', array('@link' => $link)));
}
/**
* Get the field values of an entity.
*/
function _entity_translation_export_import_export_entity($entity_type, $bundle, $limitstart, $limit, $fields){
$query = new EntityFieldQuery();
$result = $query->entityCondition('entity_type', $entity_type)
->entityCondition('bundle', $bundle)
->entityOrderBy('entity_id')
->range($limitstart, $limit)
->execute();
// Force Default Language to be first.
$languages = language_list();
$language_default = language_default();
unset($languages[$language_default->language]);
$languages = array_merge(array($language_default->language => $language_default), $languages);
$export = array();
foreach($result as $entity_type => $items){
foreach($items as $entity_id => $item){
foreach($languages as $language){
$entity = current(entity_load($entity_type, array($entity_id)));
$export[$entity_id]['Entity Type (Not Editable)'] = $entity_type;
$export[$entity_id]['Bundle (Not Editable)'] = $bundle;
$export[$entity_id]['Entity ID (Not Editable)'] = $entity_id;
$handler = entity_translation_get_handler($entity_type, $entity, TRUE);
$translations = $handler->getTranslations();
foreach($fields as $field){
$value = '';
if(isset($translations->data[$language->language])){
$value = $entity->{$field}[$language->language][0]['value'];
}
$export[$entity_id][$field][$language->language] = $value;
}
}
}
}
return $export;
}
/**
* Format entity values as csv.
*/
function _entity_translation_export_import_csv($items, $is_header = FALSE){
$data = '';
// Render Header.
if($is_header){
$titles = array();
foreach(reset($items) as $field_name => $value){
if(is_array($value)){
foreach($value as $lang_code => $language_value){
$titles[] = $field_name . '_' . $lang_code;
}
}
else{
$titles[] = $field_name;
}
}
$data .= implode(",", $titles) . "\n";
}
foreach($items as $item){
$row = array();
foreach($item as $field_name => $value){
if(is_array($value)){
foreach($value as $language_value){
$row[] = '"' . str_replace('"', '""', $language_value) . '"';
}
}
else{
$row[] = '"' . str_replace('"', '""', $value) . '"';
}
}
$data .= implode(",", $row) . "\n";
}
return $data;
}
/**
* Import form callback.
*/
function entity_translation_export_import_admin_import_form($form, $form_state){
$form = array('#attributes' => array('enctype' => "multipart/form-data"));
$form['fid'] = array(
'#type' => 'managed_file',
'#title' => 'Translation File for Import',
'#upload_validators' => array('file_validate_extensions' => array('csv')),
'#description' => t('WARNING! Do not upload any file that is not formated as needed. Please try to export the translations first.'),
);
$fid = variable_get('entity_translation_export_import_file', '');
if(file_load($fid)){
$form['fid']['#default_value'] = $fid;
}
$form['submit'] = array(
'#type' => 'submit',
'#value' => 'Import Translations'
);
return $form;
}
/**
* Import form submit callback.
*/
function entity_translation_export_import_admin_import_form_submit($form, $form_state){
$file = file_load($form_state['values']['fid']);
variable_set('entity_translation_export_import_file', $file->fid);
$batch = array(
'operations' => array(
array('entity_translation_export_import_import_process', array($file)),
),
'finished' => 'entity_translation_export_import_import_finished',
'title' => t('Import Translations'),
'init_message' => t('Retrieving Entities...'),
'progress_message' => t('Processed @current out of @total.'),
'error_message' => t('Importing has encountered an error.'),
'file' => drupal_get_path('module', 'entity_translation_export_import') . '/entity_translation_export_import.admin.inc',
);
batch_set($batch);
batch_process('admin/config/regional/entity_translation/import');
}
/**
* Batch process callback.
*/
function entity_translation_export_import_import_process($file, &$context){
// Initialize Sandbox Data.
if (!isset($context['sandbox']['current'])) {
$context['sandbox']['total'] = filesize($file->uri);
$context['sandbox']['current'] = 0;
}
if($context['sandbox']['current'] < $context['sandbox']['total']){
$fp = @fopen($file->uri, "r");
// Set the current csv pointer.
@fseek($fp, $context['sandbox']['current']);
if($data = fgetcsv($fp)){
if(!isset($context['sandbox']['header'])){
$context['sandbox']['header'] = $data;
}
else{
entity_translation_export_import_import_translation(array_combine($context['sandbox']['header'], $data));
}
}
// Get the current csv pointer.
$context['sandbox']['current'] = @ftell($fp);
}
$context['finished'] = ($context['sandbox']['current']/$context['sandbox']['total']);
}
/**
* Batch process finish callback.
*/
function entity_translation_export_import_import_finished($success, $results){
drupal_set_message(t('Entity Translations have been imported successfully!'));
}
/**
* Imports entity translations.
*/
function entity_translation_export_import_import_translation($data){
$entity_type = $data['Entity Type (Not Editable)'];
$bundle = $data['Bundle (Not Editable)'];
$entity_id = $data['Entity ID (Not Editable)'];
$entities = entity_load($entity_type, array($entity_id));
// If cannot load entity.
if(empty($entities)){
drupal_set_message(t('Entity with id @id could not be found in database.', array('@id' => $entity_id)));
return FALSE;
}
// Get the entity.
$entity = current($entities);
// Get the default entity's language.
$source_language = entity_language($entity_type, $entity);
// Get the current entity's field info.
$fields_info = field_info_instances($entity_type, $bundle);
$values = array();
$languages = array();
foreach($data as $key => $value){
if(strstr($key, '_') and !empty($value)){
$parts = explode('_', $key);
$field_language = array_pop($parts);
$field_name = implode('_', $parts);
// If field is translatable.
if(array_key_exists($field_name, $fields_info)){
$field = field_info_field($field_name);
if(field_is_translatable($entity_type, $field) and in_array($field['type'], array('text', 'text_long', 'text_with_summary')) and $field['cardinality'] == 1){
if(!in_array($field_language, $languages)){
$languages[] = $field_language;
}
if(!isset($entity->{$field_name}[$field_language])){
$entity->{$field_name}[$field_language] = $entity->{$field_name}[$source_language];
}
$entity->{$field_name}[$field_language][0]['value'] = $value;
}
}
}
}
$handler = entity_translation_get_handler($entity_type, $entity, TRUE);
if($handler){
foreach($languages as $langcode){
$translation = array(
'translate' => 0,
'status' => 1,
'source' => ($source_language != $langcode) ? $source_language : '',
'language' => $langcode,
);
$handler->setTranslation($translation);
// If original entity and module title exists then do the replacements.
if($source_language == $langcode and module_exists('title')){
$fr_info = title_field_replacement_info($entity_type);
foreach($fr_info as $legacy_field => $info) {
if(isset($entity->{$info['field']['field_name']})){
drupal_static_reset('field_language');
title_field_sync_get($entity_type, $entity, $legacy_field, $info, $langcode);
}
}
}
}
}
entity_save($entity_type, $entity);
}
@@ -0,0 +1,13 @@
name = Entity Translation Export/Import
description = Exports/Imports entity translation fields. Very useful for fast updating of taxonomy terms, nodes etc.
core = 7.x
package = Multilingual - Entity Translation
dependencies[] = entity
dependencies[] = entity_translation
; Information added by drupal.org packaging script on 2013-09-30
version = "7.x-1.0-beta3+0-dev"
core = "7.x"
project = "entity_translation_export_import"
datestamp = "1380577025"
@@ -0,0 +1,37 @@
<?php
/**
* Implements hook_menu().
*/
function entity_translation_export_import_menu() {
$items['admin/config/regional/entity_translation/export'] = array(
'title' => 'Export',
'page callback' => 'drupal_get_form',
'page arguments' => array('entity_translation_export_import_admin_export_form'),
'access arguments' => array('administer entity translation'),
'type' => MENU_LOCAL_TASK,
'file' => 'entity_translation_export_import.admin.inc',
'file path' => drupal_get_path('module', 'entity_translation_export_import'),
);
$items['admin/config/regional/entity_translation/import'] = array(
'title' => 'Import',
'page callback' => 'drupal_get_form',
'page arguments' => array('entity_translation_export_import_admin_import_form'),
'access arguments' => array('administer entity translation'),
'type' => MENU_LOCAL_TASK,
'file' => 'entity_translation_export_import.admin.inc',
'file path' => drupal_get_path('module', 'entity_translation_export_import'),
);
return $items;
}
/**
* Implements hook_menu_alter().
*/
function entity_translation_export_import_menu_alter(&$items) {
$items['admin/config/regional/entity_translation/settings'] = $items['admin/config/regional/entity_translation'];
$items['admin/config/regional/entity_translation/settings']['type'] = MENU_DEFAULT_LOCAL_TASK;
}
@@ -203,7 +203,7 @@ function features_export_form($form, $form_state, $feature = NULL) {
$form['advanced']['generate'] = array(
'#type' => 'submit',
'#value' => t('Generate feature'),
'#submit' => array('features_export_build_form_submit'),
'#submit' => array('features_export_build_form_submit', 'features_form_rebuild'),
);
}
// build the Component Listing panel on the right
@@ -239,7 +239,7 @@ function features_export_form($form, $form_state, $feature = NULL) {
'#type' => 'submit',
'#value' => t('Download feature'),
'#weight' => 10,
'#submit' => array('features_export_build_form_submit'),
'#submit' => array('features_export_build_form_submit', 'features_form_rebuild'),
);
$form['#attached']['library'][] = array('system', 'ui.dialog');
@@ -597,6 +597,7 @@ function _features_export_build($feature, &$form_state) {
$component_export['selected'][$section] = array();
}
$options = features_invoke($component, 'features_export_options');
drupal_alter('features_export_options', $options, $component);
if (!empty($options)) {
$exported_components = !empty($exported_features_info[$component]) ? $exported_features_info[$component] : array();
$new_components = !empty($new_features_info[$component]) ? $new_features_info[$component] : array();
@@ -843,7 +844,7 @@ function _features_export_generate($export, $form_state, $feature = NULL) {
}
// If either update status-related keys are provided, add a project key
// corresponding to the module name.
if (!empty($form_state['values']['version']) || !empty($form_state['values']['project_status_url'])) {
if (!empty($form_state['values']['version']) && !empty($form_state['values']['project_status_url'])) {
$export['project'] = $form_state['values']['module_name'];
}
if (!empty($form_state['values']['version'])) {
@@ -900,6 +901,35 @@ function features_export_build_form_submit($form, &$form_state) {
$tar = array();
$filenames = array();
// Copy any files if _files key is there.
if (!empty($files['_files'])) {
foreach ($files['_files'] as $file_name => $file_info) {
if ($generate) {
// See if files are in a sub directory.
if (strpos($file_name, '/')) {
$file_directory = $directory . '/' . substr($file_name, 0, strrpos($file_name, '/'));
if (!is_dir($file_directory)) {
mkdir($file_directory);
}
}
if (!empty($file_info['file_path'])) {
file_unmanaged_copy($file_info['file_path'], "{$directory}/{$file_name}", FILE_EXISTS_REPLACE);
}
elseif ($file_info['file_content']) {
file_put_contents("{$directory}/{$file_name}", $file_info['file_content']);
}
}
else {
if (!empty($file_info['file_path'])) {
print features_tar_create("{$module_name}/{$file_name}", file_get_contents($file_info['file_path']));
}
elseif ($file_info['file_content']) {
features_tar_create("{$directory}/{$file_name}", $file_info['file_content']);
}
}
}
unset($files['_files']);
}
foreach ($files as $extension => $file_contents) {
if (!in_array($extension, array('module', 'info'))) {
$extension .= '.inc';
@@ -973,28 +1003,14 @@ function features_filter_hidden($module) {
* Form constructor for the features configuration form.
*/
function features_admin_form($form, $form_state) {
$features = _features_get_features_list();
$modules = array_filter(features_get_modules(), 'features_filter_hidden');
$conflicts = features_get_conflicts();
// Load export functions to use in comparison.
module_load_include('inc', 'features', 'features.export');
// Clear & rebuild key caches
features_get_info(NULL, NULL, TRUE);
features_rebuild();
$modules = array_filter(features_get_modules(), 'features_filter_hidden');
$features = array_filter(features_get_features(), 'features_filter_hidden');
$conflicts = features_get_conflicts();
foreach ($modules as $key => $module) {
if ($module->status && !empty($module->info['dependencies'])) {
foreach ($module->info['dependencies'] as $dependent) {
if (isset($features[$dependent])) {
$features[$dependent]->dependents[$key] = $module->info['name'];
}
}
}
}
if ( empty($features) ) {
if (empty($features) ) {
$form['no_features'] = array(
'#markup' => t('No Features were found. Please use the !create_link link to create
a new Feature module, or upload an existing Feature to your modules directory.',
@@ -1328,6 +1344,13 @@ function features_form_submit(&$form, &$form_state) {
}
}
/**
* Submit handler for the 'manage features' form rebuild button.
*/
function features_form_rebuild() {
cache_clear_all('features:features_list', 'cache');
}
/**
* Form for clearing cache after enabling a feature.
*/
@@ -1588,3 +1611,42 @@ function _features_get_used($module_name = NULL) {
$features_ignore_conflicts = $old_value;
return $conflicts;
}
/**
* Retrieves the array of features as expected on the Manage Features form.
* Uses caching for performance reasons if caching is enabled.
*
* @internal - This function might return cached result with outdated data,
* use with caution.
*/
function _features_get_features_list() {
$features = array();
$cache = cache_get('features:features_list');
if ($cache) {
$features = $cache->data;
}
if (empty($features)) {
// Clear & rebuild key caches
features_get_info(NULL, NULL, TRUE);
features_rebuild();
$modules = array_filter(features_get_modules(), 'features_filter_hidden');
$features = array_filter(features_get_features(), 'features_filter_hidden');
foreach ($modules as $key => $module) {
if ($module->status && !empty($module->info['dependencies'])) {
foreach ($module->info['dependencies'] as $dependent) {
if (isset($features[$dependent])) {
$features[$dependent]->dependents[$key] = $module->info['name'];
}
}
}
}
cache_set('features:features_list', $features);
}
return $features;
}
@@ -157,7 +157,8 @@ function hook_features_export_options() {
* of the module, e.g. the key for `hook_example` should simply be `example`
* The values in the array can also be in the form of an associative array
* with the required key of 'code' and optional key of 'args', if 'args' need
* to be added to the hook.
* to be added to the hook. Alternate it can be an associative array in the
* same style as hook_features_export_files() to add additional files.
*/
function hook_features_export_render($module_name, $data, $export = NULL) {
$code = array();
@@ -314,6 +315,26 @@ function hook_features_pipe_alter(&$pipe, $data, $export) {
}
}
/**
* Add extra files to the exported file.
*
* @return array
* An array of files, keyed by file name that will appear in feature and
* with either file_path key to indicate where to copy the file from or
* file_content key to indicate the contents of the file.
*/
function hook_features_export_files($module_name, $export) {
return array('css/main.css' => array('file_content' => 'body {background-color:blue;}'));
}
/**
* Alter the extra files added to the export.
*/
function hook_features_export_files_alter(&$files, $module_name, $export) {
$files['css/main.css']['file_content'] = 'body {background-color:black;}';
}
/**
* @defgroup features_component_alter_hooks Feature's component alter hooks
* @{
@@ -610,6 +610,28 @@ function _drush_features_export($info, $module_name = NULL, $directory = NULL) {
drupal_flush_all_caches();
$export = _drush_features_generate_export($info, $module_name);
$files = features_export_render($export, $module_name, TRUE);
// Copy any files if _files key is there.
if (!empty($files['_files'])) {
foreach ($files['_files'] as $file_name => $file_info) {
// See if files are in a sub directory.
if (strpos($file_name, '/')) {
$file_directory = $directory . '/' . substr($file_name, 0, strrpos($file_name, '/'));
if (!is_dir($file_directory)) {
drush_op('mkdir', $file_directory);
}
}
if (!empty($file_info['file_path'])) {
drush_op('file_unmanaged_copy', $file_info['file_path'], "{$directory}/{$file_name}", FILE_EXISTS_REPLACE);
}
elseif (!empty($file_info['file_content'])) {
drush_op('file_put_contents', "{$directory}/{$file_name}", $file_info['file_content']);
}
else {
drush_log(dt("Entry for @file_name.in !module is invalid. ", array('!module' => $module_name, '@file_name' => $file_name)), 'ok');
}
}
unset($files['_files']);
}
foreach ($files as $extension => $file_contents) {
if (!in_array($extension, array('module', 'info'))) {
$extension .= '.inc';
@@ -45,6 +45,9 @@ function features_populate($info, $module_name) {
* @return fully populated $export array.
*/
function _features_populate($pipe, &$export, $module_name = '', $reset = FALSE) {
// Ensure that the export will be created in the english language.
_features_set_export_language();
if ($reset) {
drupal_static_reset(__FUNCTION__);
}
@@ -295,6 +298,11 @@ function features_export_render($export, $module_name, $reset = FALSE) {
}
foreach ($hooks as $hook_name => $hook_info) {
// These are purely files that will be copied over.
if (is_array($hook_info) && (!empty($hook_info['file_path']) || !empty($hook_info['file_content']))) {
$code['_files'][$hook_name] = $hook_info;
continue;
}
$hook_code = is_array($hook_info) ? $hook_info['code'] : $hook_info;
$hook_args = is_array($hook_info) && !empty($hook_info['args']) ? $hook_info['args'] : '';
$hook_file = is_array($hook_info) && !empty($hook_info['file']) ? $hook_info['file'] : $file['name'];
@@ -305,7 +313,17 @@ function features_export_render($export, $module_name, $reset = FALSE) {
// Finalize strings to be written to files
$code = array_filter($code);
foreach ($code as $filename => $contents) {
$code[$filename] = "<?php\n/**\n * @file\n * {$module_name}.{$filename}.inc\n */\n\n". implode("\n\n", $contents) ."\n";
if ($filename != '_files') {
$code[$filename] = "<?php\n/**\n * @file\n * {$module_name}.{$filename}.inc\n */\n\n". implode("\n\n", $contents) ."\n";
}
}
// Allow extra files be added to feature.
if ($files = module_invoke_all('features_export_files', $module_name, $export)) {
$code['_files'] = !empty($code['_files']) ? $code['_files'] + $files : $files;
}
if (!empty($code['_files'])) {
drupal_alter('features_export_files', $code['_files'], $module_name, $export);
}
// Generate info file output
@@ -394,8 +412,8 @@ function features_detect_overrides($module) {
$overridden = array();
// Compare feature info
_features_sanitize($module->info);
_features_sanitize($export);
features_sanitize($module->info);
features_sanitize($export);
$compare = array('normal' => features_export_info($export), 'default' => features_export_info($module->info));
if ($compare['normal'] !== $compare['default']) {
@@ -408,8 +426,8 @@ function features_detect_overrides($module) {
if ($state != FEATURES_DEFAULT) {
$normal = features_get_normal($component, $module->name);
$default = features_get_default($component, $module->name);
_features_sanitize($normal);
_features_sanitize($default);
features_sanitize($normal, $component);
features_sanitize($default, $component);
$compare = array('normal' => features_var_export($normal), 'default' => features_var_export($default));
if (_features_linetrim($compare['normal']) !== _features_linetrim($compare['default'])) {
@@ -663,8 +681,7 @@ function features_get_signature($state = 'default', $module_name, $component, $r
break;
}
if (!empty($objects)) {
$objects = (array) $objects;
_features_sanitize($objects);
features_sanitize($objects, $component);
return md5(_features_linetrim(features_var_export($objects)));
}
return FALSE;
@@ -721,7 +738,7 @@ function features_get_normal($component, $module_name, $reset = FALSE) {
// Special handling for dependencies component.
if ($component === 'dependencies') {
$cache[$module_name][$component] = isset($module->info['dependencies']) ? array_filter($module->info['dependencies'], 'module_exists') : array();
$cache[$module_name][$component] = isset($module->info['dependencies']) ? array_filter($module->info['dependencies'], '_features_module_exists') : array();
}
// All other components.
else {
@@ -739,6 +756,17 @@ function features_get_normal($component, $module_name, $reset = FALSE) {
return isset($cache[$module_name][$component]) ? $cache[$module_name][$component] : FALSE;
}
/**
* Helper function to determine if a module is enabled
* @param $module
* This module name comes from the .info file and can have version info in it.
*/
function _features_module_exists($module) {
$parsed_dependency = drupal_parse_dependency($module);
$name = $parsed_dependency['name'];
return module_exists($name);
}
/**
* Get defaults for a given module/component pair.
*/
@@ -970,25 +998,52 @@ function _features_linetrim($code) {
return implode("\n", $code);
}
/**
* Helper function to "sanitize" an array or object.
* Converts everything to an array, sorts the keys, removes recursion.
* @param $array
* @param $component string name of component
* @param bool $remove_empty if set, remove null or empty values for assoc arrays.
*/
function features_sanitize(&$array, $component = NULL, $remove_empty = TRUE) {
// make a deep copy of data to prevent problems when removing recursion later.
$array = unserialize(serialize($array));
if (isset($component)) {
$ignore_keys = _features_get_ignore_keys($component);
// remove keys to be ignored
// doing this now allows us to better control which recursive parts are removed
if (count($ignore_keys)) {
_features_remove_ignores($array, $ignore_keys);
}
}
features_remove_recursion($array);
_features_sanitize($array, $remove_empty);
}
/**
* "Sanitizes" an array recursively, performing two key operations:
* - Sort an array by its keys (assoc) or values (non-assoc)
* - Remove any null or empty values for associative arrays (array_filter()).
* @param bool $remove_empty if set, remove null or empty values for assoc arrays.
*/
function _features_sanitize(&$array) {
function _features_sanitize(&$array, $remove_empty = TRUE) {
if (is_object($array)) {
$array = get_object_vars($array);
}
if (is_array($array)) {
$is_assoc = _features_is_assoc($array);
if ($is_assoc) {
ksort($array, SORT_STRING);
$array = array_filter($array);
if ($remove_empty) {
$array = array_filter($array);
}
}
else {
sort($array);
}
foreach ($array as $k => $v) {
if (is_array($v)) {
if (is_array($v) or is_object($v)) {
_features_sanitize($array[$k]);
if ($is_assoc && empty($array[$k])) {
if ($remove_empty && $is_assoc && empty($array[$k])) {
unset($array[$k]);
}
}
@@ -1010,3 +1065,127 @@ function _features_sanitize(&$array) {
function _features_is_assoc($array) {
return (is_array($array) && (0 !== count(array_diff_key($array, array_keys(array_keys($array)))) || count($array)==0));
}
/**
* Removes recursion from an object or array.
*
* @param $item
* An object or array passed by reference.
*/
function features_remove_recursion(&$item) {
$uniqid = __FUNCTION__ . mt_rand(); // use of uniqid() here impacts performance
$stack = array();
return _features_remove_recursion($item, $stack, $uniqid);
}
/**
* Helper to removes recursion from an object/array.
*
* @param $item
* An object or array passed by reference.
*/
function _features_remove_recursion(&$object, &$stack = array(), $uniqid) {
if ((is_object($object) || is_array($object)) && $object) {
$in_stack = FALSE;
foreach ($stack as &$item) {
if (_features_is_ref_to($object, $item, $uniqid)) {
$in_stack = TRUE;
break;
}
}
unset($item);
if (!$in_stack) {
$stack[] = $object;
foreach ($object as $key => &$subobject) {
if (_features_remove_recursion($subobject, $stack, $uniqid)) {
if (is_object($object)) {
unset($object->$key);
}
else {
unset($object[$key]);
}
}
}
unset($subobject);
}
else {
return TRUE;
}
}
return FALSE;
}
/**
* Helper function in determining equality of arrays. Credit to http://stackoverflow.com/a/4263181
*
* @see _features_remove_recursion()
*
* @param $a
* object a
* @param $b
* object b
* @return bool
*
*/
function _features_is_ref_to(&$a, &$b, $uniqid) {
if (is_object($a) && is_object($b)) {
return ($a === $b);
}
$temp_a = $a;
$temp_b = $b;
$b = $uniqid;
if ($a === $uniqid) $return = true;
else $return = false;
$a = $temp_a;
$b = $temp_b;
return $return;
}
/**
* Helper to removes a set of keys an object/array.
*
* @param $item
* An object or array passed by reference.
* @param $ignore_keys
* Array of keys to be ignored. Values are the level of the key.
* @param $level
* Level of key to remove. Up to 2 levels deep because $item can still be
* recursive
*/
function _features_remove_ignores(&$item, $ignore_keys, $level = -1) {
$is_object = is_object($item);
if (!is_array($item) && !is_object($item)) {
return;
}
foreach ($item as $key => $value) {
if (isset($ignore_keys[$key]) && ($ignore_keys[$key] == $level)) {
if ($is_object) {
unset($item->$key);
}
else {
unset($item[$key]);
}
}
elseif (($level < 2) && (is_array($value) || is_object($value))) {
_features_remove_ignores($value, $ignore_keys, $level+1);
}
}
}
/**
* Returns an array of keys to be ignored for various exportables
* @param $component
* The component to retrieve ignore_keys from.
*/
function _features_get_ignore_keys($component) {
static $cache;
if (!isset($cache[$component])) {
$cache[$component] = module_invoke_all('features_ignore', $component);
}
return $cache[$component];
}
@@ -6,9 +6,9 @@ files[] = tests/features.test
configure = admin/structure/features/settings
; Information added by Drupal.org packaging script on 2015-04-13
version = "7.x-2.5"
; Information added by Drupal.org packaging script on 2015-06-24
version = "7.x-2.6"
core = "7.x"
project = "features"
datestamp = "1428944073"
datestamp = "1435165997"
@@ -5,6 +5,15 @@
* Install, update and uninstall functions for the features module.
*/
/**
* Implements hook_schema().
*/
function features_schema() {
$schema['cache_features'] = drupal_get_schema_unprocessed('system', 'cache');
$schema['cache_features']['description'] = 'Cache table for features to store module info.';
return $schema;
}
/**
* Implements hook_install().
*/
@@ -33,7 +42,7 @@ function features_uninstall() {
->execute();
db_delete('variable')
->condition('name', 'features_component_locked_%', 'LIKE')
->execute();variable_del('features_component_locked_' . $component);
->execute();
if (db_table_exists('menu_custom')) {
db_delete('menu_custom')
@@ -130,3 +139,13 @@ function features_update_6101() {
}
return array();
}
/**
* Add {cache_features} table.
*/
function features_update_7200() {
if (!db_table_exists('cache_features')) {
$schema = drupal_get_schema_unprocessed('system', 'cache');
db_create_table('cache_features', $schema);
}
}
@@ -128,10 +128,12 @@ jQuery.fn.sortElements = (function(){
if (!$(this).hasClass('features-checkall')) {
var key = $(this).attr('name');
var matches = key.match(/^([^\[]+)(\[.+\])?\[(.+)\]\[(.+)\]$/);
var component = matches[1];
var item = matches[4];
if ((component in moduleConflicts) && (moduleConflicts[component].indexOf(item) != -1)) {
$(this).parent().addClass('features-conflict');
if (matches != null) {
var component = matches[1];
var item = matches[4];
if ((component in moduleConflicts) && (moduleConflicts[component].indexOf(item) != -1)) {
$(this).parent().addClass('features-conflict');
}
}
}
});
@@ -290,7 +292,7 @@ jQuery.fn.sortElements = (function(){
}
// Handle component selection UI
$('#features-export-wrapper input[type=checkbox]', context).click(function() {
$('#features-export-wrapper input[type=checkbox]:not(.processed)', context).addClass('processed').click(function() {
_resetTimeout();
if ($(this).hasClass('component-select')) {
moveCheckbox(this, 'added', true);
@@ -268,14 +268,17 @@ function features_theme() {
* Implements hook_flush_caches().
*/
function features_flush_caches() {
if (variable_get('features_rebuild_on_flush', TRUE)) {
if (($modules_changed = variable_get('features_modules_changed', FALSE)) || variable_get('features_rebuild_on_flush', TRUE)) {
if ($modules_changed) {
variable_set('features_modules_changed', FALSE);
}
features_rebuild();
// Don't flush the modules cache during installation, for performance reasons.
if (variable_get('install_task') == 'done') {
features_get_modules(NULL, TRUE);
}
}
return array();
return array('cache_features');
}
/**
@@ -349,6 +352,15 @@ function features_modules_disabled($modules) {
* Implements hook_modules_enabled().
*/
function features_modules_enabled($modules) {
// Allow distributions to disable this behavior and rebuild the features
// manually inside a batch.
if (!variable_get('features_rebuild_on_module_install', TRUE)) {
return;
}
// mark modules as being changed for test in features_flush_caches
variable_set('features_modules_changed', TRUE);
// Go through all modules and gather features that can be enabled.
$items = array();
foreach ($modules as $module) {
@@ -385,7 +397,7 @@ function features_include($reset = FALSE) {
// Features provides integration on behalf of these modules.
// The features include provides handling for the feature dependencies.
// Note that ctools is placed last because it implements hooks "dynamically" for other modules.
$modules = array('features', 'block', 'context', 'field', 'filter', 'image', 'locale', 'menu', 'node', 'taxonomy', 'user', 'views', 'ctools');
$modules = array('features', 'block', 'contact', 'context', 'field', 'filter', 'image', 'locale', 'menu', 'node', 'taxonomy', 'user', 'views', 'ctools');
foreach (array_filter($modules, 'module_exists') as $module) {
module_load_include('inc', 'features', "includes/features.$module");
@@ -535,13 +547,13 @@ function features_get_components($component = NULL, $key = NULL, $reset = FALSE)
if ($reset || !isset($components) || !isset($component_by_key)) {
$components = $component_by_key = array();
if (!$reset && ($cache = cache_get('features_api'))) {
if (!$reset && ($cache = cache_get('features_api', 'cache_features'))) {
$components = $cache->data;
}
else {
$components = module_invoke_all('features_api');
drupal_alter('features_api', $components);
cache_set('features_api', $components);
cache_set('features_api', $components, 'cache_features');
}
foreach ($components as $component_type => $component_information) {
@@ -607,6 +619,8 @@ function features_hook($component, $hook, $reset = FALSE) {
* Clear the module info cache.
*/
function features_install_modules($modules) {
variable_set('features_modules_changed', TRUE);
module_load_include('inc', 'features', 'features.export');
$files = system_rebuild_module_data();
@@ -650,7 +664,7 @@ function features_get_features($name = NULL, $reset = FALSE) {
function features_get_info($type = 'module', $name = NULL, $reset = FALSE) {
static $cache;
if (!isset($cache)) {
$cache = cache_get('features_module_info');
$cache = cache_get('features_module_info', 'cache_features');
}
if (empty($cache) || $reset) {
$data = array(
@@ -738,7 +752,7 @@ function features_get_info($type = 'module', $name = NULL, $reset = FALSE) {
$data['feature'] = $sorted;
variable_set('features_ignored_orphans', $ignored);
cache_set("features_module_info", $data);
cache_set('features_module_info', $data, 'cache_features');
$cache = new stdClass();
$cache->data = $data;
}
@@ -1067,6 +1081,7 @@ function features_hook_info() {
'features_api',
'features_pipe_alter',
'features_export_alter',
'features_export_options_alter',
);
return array_fill_keys($hooks, array('group' => 'features'));
}
@@ -1189,7 +1204,6 @@ function features_feature_lock($feature, $component = NULL) {
variable_set('features_feature_locked', $locked);
}
/**
* Unlocks a feature or it's component.
*/
@@ -1203,3 +1217,115 @@ function features_feature_unlock($feature, $component = NULL) {
}
variable_set('features_feature_locked', $locked);
}
/**
* Sets the current language to english to ensure a proper export.
*/
function _features_set_export_language() {
// Ensure this is only done if the language isn't already en.
// This can be called multiple times - ensure the handling is done just once.
if ($GLOBALS['language']->language != 'en' && !drupal_static(__FUNCTION__)) {
// Create the language object as language_default() does.
$GLOBALS['language'] = (object) array(
'language' => 'en',
'name' => 'English',
'native' => 'English',
'direction' => 0,
'enabled' => 1,
'plurals' => 0,
'formula' => '',
'domain' => '',
'prefix' => '',
'weight' => 0,
'javascript' => '',
);
// Ensure that static caches are cleared, as they might contain language
// specific information. But keep some important ones. The call below
// accesses a non existing key and requests to reset it. In such cases the
// whole caching data array is returned.
$static = drupal_static(uniqid('', TRUE), NULL, TRUE);
drupal_static_reset();
// Restore some of the language independent, runtime state information to
// keep everything working and avoid unnecessary double processing.
$static_caches_to_keep = array(
'conf_path',
'system_list',
'ip_address',
'drupal_page_is_cacheable',
'list_themes',
'drupal_page_header',
'drupal_send_headers',
'drupal_http_headers',
'language_list',
'module_implements',
'drupal_alter',
'path_is_admin',
'path_get_admin_paths',
'drupal_match_path',
'menu_get_custom_theme',
'menu_get_item',
'arg',
'drupal_system_listing',
'drupal_parse_info_file',
'libraries_get_path',
'module_hook_info',
'drupal_add_js',
'drupal_add_js:jquery_added',
'drupal_add_library',
'drupal_get_library',
'drupal_add_css',
'menu_set_active_trail',
'menu_link_get_preferred',
'menu_set_active_menu_names',
'theme_get_registry',
'features_get_components',
'features_get_components_by_key',
);
foreach ($static_caches_to_keep as $cid) {
if (isset($static[$cid])) {
$data = &drupal_static($cid);
$data = $static[$cid];
}
}
$called = &drupal_static(__FUNCTION__);
$called = TRUE;
}
}
/**
* Implements hook_features_ignore().
*/
function features_features_ignore($component) {
// Determine which keys need to be ignored for override diff for various components.
// Value is how many levels deep the key is.
$ignores = array();
switch ($component) {
case 'views_view':
$ignores['current_display'] = 0;
$ignores['display_handler'] = 0;
$ignores['handler'] = 2;
$ignores['query'] = 0;
$ignores['localization_plugin'] = 0;
// Views automatically adds these two on export to set values.
$ignores['api_version'] = 0;
$ignores['disabled'] = 0;
break;
case 'image':
$ignores['module'] = 0;
$ignores['name'] = 0;
$ignores['storage'] = 0;
// Various properties are loaded into the effect in image_styles.
$ignores['summary theme'] = 2;
$ignores['module'] = 2;
$ignores['label'] = 2;
$ignores['help'] = 2;
$ignores['form callback'] = 2;
$ignores['effect callback'] = 2;
$ignores['dimensions callback'] = 2;
break;
case 'field':
$ignores['locked'] = 1;
break;
}
return $ignores;
}
@@ -274,7 +274,8 @@ function field_base_features_rebuild($module) {
// Create or update field.
if (isset($existing_fields[$field['field_name']])) {
$existing_field = $existing_fields[$field['field_name']];
if ($field + $existing_field !== $existing_field) {
$array_diff_result = drupal_array_diff_assoc_recursive($field + $existing_field, $existing_field);
if (!empty($array_diff_result)) {
field_update_field($field);
}
}
@@ -483,7 +484,8 @@ function field_features_rebuild($module) {
$field_config = $field['field_config'];
if (isset($existing_fields[$field_config['field_name']])) {
$existing_field = $existing_fields[$field_config['field_name']];
if ($field_config + $existing_field !== $existing_field) {
$array_diff_result = drupal_array_diff_assoc_recursive($field_config + $existing_field, $existing_field);
if (!empty($array_diff_result)) {
try {
field_update_field($field_config);
}
@@ -253,11 +253,11 @@ function menu_links_features_export_render($module, $data, $export = NULL) {
$translatables[] = $link['link_title'];
}
}
$code[] = '';
if (!empty($translatables)) {
$code[] = features_translatables_export($translatables, ' ');
}
$code[] = '';
$code[] = ' return $menu_links;';
$code = implode("\n", $code);
return array('menu_default_menu_links' => $code);
@@ -287,3 +287,43 @@ class FeaturesCtoolsIntegrationTest extends DrupalWebTestCase {
}
}
}
/**
* Test detecting modules as features.
*/
class FeaturesDetectionTestCase extends DrupalWebTestCase {
protected $profile = 'testing';
/**
* Test info.
*/
public static function getInfo() {
return array(
'name' => t('Feature Detection tests'),
'description' => t('Run tests for detecting items as features.') ,
'group' => t('Features'),
);
}
/**
* Set up test.
*/
public function setUp() {
parent::setUp(array(
'features',
));
}
/**
* Run test.
*/
public function test() {
module_load_include('inc', 'features', 'features.export');
// First test that features_populate inserts the features api key.
$export = features_populate(array(), array(), 'features_test_empty_fake');
$this->assertTrue(!empty($export['features']['features_api']) && key($export['features']['features_api']) == 'api:' . FEATURES_API, 'Features API key added to new export.');
$this->assertTrue((bool)features_get_features('features_test'), 'Features test recognized as a feature.');
$this->assertFalse((bool)features_get_features('features'), 'Features module not recognized as a feature.');
}
}
@@ -21,9 +21,9 @@ features[user_permission][] = create features_test content
features[views_view][] = features_test
hidden = 1
; Information added by Drupal.org packaging script on 2015-04-13
version = "7.x-2.5"
; Information added by Drupal.org packaging script on 2015-06-24
version = "7.x-2.6"
core = "7.x"
project = "features"
datestamp = "1428944073"
datestamp = "1435165997"
@@ -0,0 +1,13 @@
Index: node_export.module
===================================================================
--- node_export.module (revision 172)
+++ node_export.module (working copy)
@@ -1146,7 +1146,7 @@
if (!empty($query)) {
watchdog('node_export', 'kept existing managed file at uri "%uri"', array('%uri' => $file->uri), WATCHDOG_NOTICE);
- $file = file_load(array_shift($query));
+ $file = (object) array_merge((array) $file, (array) file_load(array_shift($query)));
}
$file = file_save($file);
@@ -38,7 +38,7 @@ function node_export_menu() {
'description' => 'Configure the settings for Node export.',
'file' => 'node_export.pages.inc',
);
$selected_formats = variable_get('node_export_format', array('drupal'));
$selected_formats = variable_get('node_export_format', array('drupal' => 'drupal'));
if (count(array_filter($selected_formats)) > 1) {
$format_handlers = node_export_format_handlers();
foreach ($format_handlers as $format_handler => $format) {
@@ -218,7 +218,7 @@ function node_export_node_operations() {
$operations = array();
if (user_access('export nodes')) {
$selected_formats = variable_get('node_export_format', array('drupal'));
$selected_formats = variable_get('node_export_format', array('drupal' => 'drupal'));
if (count(array_filter($selected_formats)) > 1) {
$format_handlers = node_export_format_handlers();
foreach ($format_handlers as $format_handler => $format) {
@@ -258,7 +258,7 @@ function node_export_bulk_operation($nodes = NULL, $format = NULL, $delivery = N
function node_export_action_info() {
$actions = array();
if (user_access('export nodes')) {
$selected_formats = variable_get('node_export_format', array('drupal'));
$selected_formats = variable_get('node_export_format', array('drupal' => 'drupal'));
$format_handlers = node_export_format_handlers();
foreach ($format_handlers as $format_handler => $format) {
if (!empty($selected_formats[$format_handler])) {
@@ -296,10 +296,13 @@ function node_export_action_info() {
function node_export_action_form($context, &$form_state, $format = NULL) {
// Get the name of the vbo views field
$vbo = _views_bulk_operations_get_field($form_state['build_info']['args'][0]);
// Adjust the selection in case the user chose 'select all'
_views_bulk_operations_adjust_selection($form_state['selection'], $form_state['select_all_pages'], $vbo);
if (!empty($form_state['select_all_pages'])) {
views_bulk_operations_direct_adjust($form_state['selection'], $vbo);
}
$nodes = array_combine($form_state['selection'], $form_state['selection']);
return node_export_bulk_operation($nodes);
return node_export_bulk_operation($nodes, $format);
}
/**
@@ -347,7 +350,7 @@ function node_export($nids, $format = NULL, $msg_t = 't') {
// Get the node code from the format handler
$format_handlers = node_export_format_handlers();
$node_export_format = variable_get('node_export_format', array('drupal'));
$node_export_format = variable_get('node_export_format', array('drupal' => 'drupal'));
$format_handler = $format ? $format : reset($node_export_format);
if (!isset($format_handlers[$format_handler])) {
$format_handler = 'drupal';
@@ -1146,7 +1149,7 @@ function _node_export_file_field_import_file(&$file) {
if (!empty($query)) {
watchdog('node_export', 'kept existing managed file at uri "%uri"', array('%uri' => $file->uri), WATCHDOG_NOTICE);
$file = file_load(array_shift($query));
$file = (object) array_merge((array) $file, (array) file_load(array_shift($query)));
}
$file = file_save($file);
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,56 @@
diff --git a/node_export.module b/node_export.module
index 8bdadb9..052fa6c 100755
--- a/node_export.module
+++ b/node_export.module
@@ -38,7 +38,7 @@ function node_export_menu() {
'description' => 'Configure the settings for Node export.',
'file' => 'node_export.pages.inc',
);
- $selected_formats = variable_get('node_export_format', array('drupal'));
+ $selected_formats = variable_get('node_export_format', array('drupal' => 'drupal'));
if (count(array_filter($selected_formats)) > 1) {
$format_handlers = node_export_format_handlers();
foreach ($format_handlers as $format_handler => $format) {
@@ -218,7 +218,7 @@ function node_export_node_operations() {
$operations = array();
if (user_access('export nodes')) {
- $selected_formats = variable_get('node_export_format', array('drupal'));
+ $selected_formats = variable_get('node_export_format', array('drupal' => 'drupal'));
if (count(array_filter($selected_formats)) > 1) {
$format_handlers = node_export_format_handlers();
foreach ($format_handlers as $format_handler => $format) {
@@ -258,7 +258,7 @@ function node_export_bulk_operation($nodes = NULL, $format = NULL, $delivery = N
function node_export_action_info() {
$actions = array();
if (user_access('export nodes')) {
- $selected_formats = variable_get('node_export_format', array('drupal'));
+ $selected_formats = variable_get('node_export_format', array('drupal' => 'drupal'));
$format_handlers = node_export_format_handlers();
foreach ($format_handlers as $format_handler => $format) {
if (!empty($selected_formats[$format_handler])) {
@@ -296,10 +296,13 @@ function node_export_action_info() {
function node_export_action_form($context, &$form_state, $format = NULL) {
// Get the name of the vbo views field
$vbo = _views_bulk_operations_get_field($form_state['build_info']['args'][0]);
+
// Adjust the selection in case the user chose 'select all'
- _views_bulk_operations_adjust_selection($form_state['selection'], $form_state['select_all_pages'], $vbo);
+ if (!empty($form_state['select_all_pages'])) {
+ views_bulk_operations_direct_adjust($form_state['selection'], $vbo);
+ }
$nodes = array_combine($form_state['selection'], $form_state['selection']);
- return node_export_bulk_operation($nodes);
+ return node_export_bulk_operation($nodes, $format);
}
/**
@@ -347,7 +350,7 @@ function node_export($nids, $format = NULL, $msg_t = 't') {
// Get the node code from the format handler
$format_handlers = node_export_format_handlers();
- $node_export_format = variable_get('node_export_format', array('drupal'));
+ $node_export_format = variable_get('node_export_format', array('drupal' => 'drupal'));
$format_handler = $format ? $format : reset($node_export_format);
if (!isset($format_handlers[$format_handler])) {
$format_handler = 'drupal';
@@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
@@ -0,0 +1,95 @@
CONTENTS OF THIS FILE
---------------------
* Introduction
* Installing
* Uninstalling
* Frequently Asked Questions (FAQ)
* Known Issues
* More Information
* How Can You Contribute?
INTRODUCTION
------------
Current Maintainer: Sami Radi a.k.a VirtuoWorks <http://drupal.org/user/656630>
Remove Duplicates removes duplicate nodes
according to any selected content field.
This can be very useful with Drupal feeds module :
- http://drupal.org/project/feeds
This module was inspired by these threads :
- http://drupal.org/node/720190 : Remove Duplicate Nodes based on title
- http://drupal.org/node/1211922 : remove duplicated node base on title problem
THANKS
------
Thanks to dman <https://drupal.org/user/33240> for endorsing this project.
He wrote a sandbox module "Deduplicate Nodes" for D6 intended to deduplicate
nodes according to titles only : https://drupal.org/sandbox/dman/1422586
INSTALLING
----------
See http://drupal.org/getting-started/install-contrib
for instructions on how to install or update Drupal modules.
Once Remove Duplicates is installed and enabled,
you can use it at admin/config/content/remove_duplicates.
It is highly recommended that you backup your database before using this module.
UNINSTALLING
------------
Because Drupal does not uninstall modules in reverse
order of their dependencies, if you want to uninstall
all the Remove Duplicates modules, be sure to disable
and uninstall all the sub-modules before the base Remove
Duplicates module.
To help fix this bug in Drupal core, visit http://drupal.org/node/151452.
FREQUENTLY ASKED QUESTIONS (FAQ)
--------------------------------
- There are no frequently asked questions at this time.
KNOWN ISSUES
------------
- See http://drupal.org/project/issues/2005296
for a list of the current known issues.
MORE INFORMATION
----------------
- To issue any bug reports, feature or support requests
see the module issue queue at
http://drupal.org/project/issues/2005296
- For additional documentation, see the online module handbook at
https://drupal.org/project/remove_duplicates
HOW CAN YOU CONTRIBUTE?
-----------------------
- Report any bugs, feature requests, etc. in the issue tracker.
http://drupal.org/node/add/project-issue/2005296
- Help translate this module.
- Write a review for this module at drupalmodules.com.
http://drupalmodules.com/
- Help keep development active by donating to the developers.
http://www.virtuoworks.com/
@@ -0,0 +1,12 @@
name = Remove Duplicates
description = "Remove duplicate nodes according to node fields or CCK fields."
core = 7.x
package = Content management
configure = admin/config/content/remove_duplicates
; Information added by Drupal.org packaging script on 2015-04-13
version = "7.x-1.4"
core = "7.x"
project = "remove_duplicates"
datestamp = "1428944507"
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
@@ -0,0 +1,41 @@
Special Menu Items Module
------------------------
Written by Tamir Al Zoubi and Karim Djelid - Servit Open Source Solutions - www.servit.ch
Description
-----------
Special Menu Items is module that enables placeholder and separator menu items.Placeholder is a menu item which is
actually not a link. Something like this is useful with drop down menus where we want to have a parent link which
is actually not linking to a page but which is just acting as a parent grouping some children below it.
A separator menu item is something like "-------" which is also not linking anywhere but merely a mean to structure menus.
This module depends on the Menu module. It is recommended that the SimpleMenu module or another drop down menu module
is used, or you will not be able to acess children of nolink menu items.
Features
--------
- User can create a new menu item and place either "<nolink>" or "<separator>" in the Path field, without quotes.
- When the menu is rendered the "nolink" item will be rendered similar to a normal menu link item, but there will
be no link, just the title. Since version 1.3 you can change HTML tag used for menu item.
- When the menu is rendered the "separator" item will be rendered as an item which has no link,
and the default title will be "-------". Since version 1.3 it is possible to change both the HTML tag and title.
- Breadcrumb of "<nolink>" will be rendered same as "<nolink>" menu item.
- CSS class "nolink" is added to "<nolink>" menu item.
- CSS class "seperator" is added to "<seperator>" menu item.
- Compatible with the Sitemap module.
Installation
------------
1. Copy the special_menu_items folder to your sites/all/modules directory.
2. At Administer -> Site building -> Modules (admin/modules) enable the module.
3. Configure the module settings at Administer -> Site configuration -> Special Menu Items (admin/config/system/special_menu_items).
Upgrading
---------
Just overwrite (or replace) the older special_menu_items folder with the newer version.
Contact
-------
This module is developed by Servit Open Source Solutions - http://servit.ch
and maintained by Khaled Zaidan - zaidan@servit.ch
@@ -0,0 +1,13 @@
name = Special menu items
description = "Allow users to add placeholder and/or separator menu items."
core = 7.x
dependencies[] = menu
configure = admin/config/system/special_menu_items
; Information added by drupal.org packaging script on 2012-09-04
version = "7.x-2.0"
core = "7.x"
project = "special_menu_items"
datestamp = "1346788411"
@@ -0,0 +1,38 @@
<?php
/**
* Implementation of hook_uninstall().
*/
function special_menu_items_uninstall() {
variable_del('special_menu_items_nolink_tag');
variable_del('special_menu_items_seperator_tag');
variable_del('special_menu_items_seperator_value');
variable_del('special_menu_items_menu_item_link');
}
/**
* "nolink" to <nolink> and "separator" to <separator>
*/
function special_menu_items_update_7000(&$sandbox){
//change "nolink" to <nolink>
db_update('menu_links')
->fields(array(
'link_path' => '<nolink>',
'router_path' => '<nolink>',
))
->condition('router_path', 'nolink', '=')
->execute();
//change "separator" to <separator>
db_update('menu_links')
->fields(array(
'link_path' => '<separator>',
'router_path' => '<separator>',
))
->condition('router_path', 'separator', '=')
->execute();
//we don't need this variable anymore
variable_del('special_menu_items_menu_item_link');
}
@@ -0,0 +1,199 @@
<?php
/**
* @file
* Module to enable placeholder or separator menu items.Placeholder is a menu item which is
* actually not a link. Something like this is useful with drop down menus where we want to
* have a parent link which is actually not linking to a page but which is just acting as a
* parent and grouping some children below it.
* A separator menu item is something like "-------" which is also not linking anywhere but
* merely a mean to structure menus.
*
* Written by Tamir Al Zoubi and Karim Djelid - Servit Open Source Solutions - www.servit.ch
*/
/**
*Implementation of hook_menu()
*/
function special_menu_items_menu() {
$items['<nolink>'] = array(
'page callback' => 'drupal_not_found',
'access callback' => TRUE,
'type' => MENU_CALLBACK,
);
$items['<separator>'] = array(
'page callback' => 'drupal_not_found',
'access callback' => TRUE,
'type' => MENU_CALLBACK,
);
$items['admin/config/system/special_menu_items'] = array(
'title' => 'Special Menu Items',
'description' => 'Configure Special Menu Items.',
'page callback' => 'drupal_get_form',
'page arguments' => array('special_menu_items_admin_settings_form'),
'access arguments' => array('administer site configuration'),
'type' => MENU_NORMAL_ITEM,
);
return $items;
}
/**
* Override of theme_link()
* This function will render link if it is "nolink" or "separator". Otherwise it will call originally
* overwritten menu_item_link function.
*/
function special_menu_items_link(array $variables) {
if (in_array($variables['path'], array('<nolink>', '<separator>'))) {
switch ($variables['path']) {
case '<nolink>':
$tag = variable_get('special_menu_items_nolink_tag', '<span>');
$title = $variables['options']['html'] ? $variables['text'] : check_plain($variables['text']);
$variables['options']['attributes']['class'][] = 'nolink';
break;
case '<separator>':
$tag = variable_get('special_menu_items_separator_tag', '<span>');
$title = variable_get('special_menu_items_separator_value', '<hr>');
$variables['options']['attributes']['class'][] = 'separator';
break;
}
$attributes = drupal_attributes($variables['options']['attributes']);
if ($tag != '<a>') {
// <a> tags can have these but a <span> cannot, so we remove them.
foreach (array('accesskey', 'target', 'rel', 'name') as $attribute) {
$attributes = preg_replace("/ $attribute=\".*\"/i", "", $attributes);
}
}
return special_menu_items_render_menu_item($tag, $title, $attributes);
}
// Call the original theme function for normal menu link.
return theme('special_menu_items_link_default', $variables);
}
/**
* Returns menu item rendered.
*/
function special_menu_items_render_menu_item($tag, $value, $attrs = array()) {
// $attrs may be a string already or an array
if (is_array($attrs)) {
$attrs = drupal_attributes($attrs);
}
$length = strlen($tag);
if ($tag[0] == '<' && $tag[$length - 1] == '>') {
$tag = substr($tag, 1, $length-2);
}
$closingtag = explode(' ', $tag,2);
$closingtag = '</' . $closingtag[0] . '>';
$tag = '<' . $tag . $attrs . '>';
return $tag . $value . $closingtag;
}
/**
* Implementation of hook_theme_registry_alter()
* We replace theme_menu_item_link with our own function.
*/
function special_menu_items_theme_registry_alter(&$registry) {
// Save previous value from registry in case another theme overwrites menu_item_link
$registry['special_menu_items_link_default'] = $registry['link'];
$registry['link']['function'] = 'special_menu_items_link';
}
/**
* Implementation of hook_form_FROM_ID_alter()
* Description changed, added nolink and separator as path types.
*/
function special_menu_items_form_menu_edit_item_alter(&$form, &$form_state) {
// Some menu items have a pre-defined path which cannot be modified hence no default_value
if (isset($form['link_path']['#default_value'])) {
$default_value = $form['link_path']['#default_value'];
if (preg_match('/^<nolink>\/[0-9]+$/', $default_value)) {
$default_value = '<nolink>';
}
elseif (preg_match('/^<separator>\/[0-9]+$/', $default_value)) {
$default_value = '<separator>';
}
$form['link_path']['#default_value'] = $default_value;
$form['link_path']['#description'] .= ' ' . t('Enter "%nolink" to generate non-linkable item, enter "%separator" to generate separator item.', array('%nolink' => '<nolink>', '%separator' => '<separator>'));
}
}
/**
* Implementation of hook_init().
*/
function special_menu_items_init() {
// Make breadcrumb of nolink menu item nonlinkable.
$breadcrumb = drupal_get_breadcrumb();
foreach($breadcrumb as $key => $crumb){
if (strlen(strstr($crumb,'<nolink>')) > 0) {
$crumb = strip_tags($crumb);
$tag = variable_get('special_menu_items_nolink_tag', '<span>');
$breadcrumb[$key] = special_menu_items_render_menu_item($tag, $crumb);
}
}
drupal_set_breadcrumb($breadcrumb);
}
/**
* Special Menu Items admin settings form.
*
* @return
* The settings form used by Special Menu Items.
*/
function special_menu_items_admin_settings_form() {
$form['special_menu_items_nolink_tag'] = array(
'#type' => 'textfield',
'#title' => t('HTML tag for "nolink"'),
'#description' => t('By default, Special Menu Items will use a span tag for the nolink menu item. Here you can specify your own tag.'),
'#default_value' => variable_get('special_menu_items_nolink_tag', '<span>'),
);
$form['special_menu_items_separator_tag'] = array(
'#type' => 'textfield',
'#title' => t('HTML tag for "separator"'),
'#description' => t('By default, Special Menu Items will use a span tag for the separator menu item. Here you can specify your own tag.'),
'#default_value' => variable_get('special_menu_items_separator_tag', '<span>'),
);
$form['special_menu_items_separator_value'] = array(
'#type' => 'textfield',
'#title' => t('Value to be displayed for the "separator"'),
'#description' => t('By default, Special Menu Items will use a "&lt;hr&gt;" value for the separator. You can specify your own value for the separator.'),
'#default_value' => variable_get('special_menu_items_separator_value', '<hr>'),
);
return system_settings_form($form);
}
/**
* Implements hook_menu_link_update()
*
*/
/*
function special_menu_items_menu_link_update($link) {
//do all links in db
global $db_type;
if ($db_type == 'pgsql') {
db_query("UPDATE {menu_links} SET link_path=link_path||'/'||mlid WHERE (link_path='<nolink>' OR link_path='<separator>') AND hidden != -1");
}
else {
db_query("UPDATE {menu_links} SET link_path=CONCAT(CONCAT(link_path,'/'),mlid) WHERE (link_path='<nolink>' OR link_path='<separator>') AND hidden!=-1");
}
}
*
*/
@@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
@@ -0,0 +1,21 @@
/**
* @file
* Styles for taxonomy wrangler
*/
.taxonomy-wrangler-accordion-item {
float: left;
font-size: 1.35em;
min-width: 15px;
padding-right: 5px;
padding-left: 5px;
color: #656565;
}
.taxonomy-wrangler-accordion-item:hover {
text-decoration: none;
}
.taxonomy-wrangler-accordion-item--parent span:after {
content: "\25be"; /*right pointing small triangle*/
}
.taxonomy-wrangler-accordion-item--parent.is-collapsed span:after {
content: "\25b8"; /*down pointing small triangle*/
}
@@ -0,0 +1,591 @@
/**
* @file
* js for taxonomy ui tweaks
*/
(function ($, Drupal, window, document, undefined) {
function TaxonomyWranglerUID() {
var prefix = 'taxonomy-wrangler-';
var randomId = prefix + this.count;
while($('#' + randomId).length) {
this.count++;
randomId = prefix + this.count;
}
return randomId;
}
TaxonomyWranglerUID.count = 0;
function TaxonomyWranglerAccordion(table) {
this.table = table;
this.rows = $('tbody tr', table);
this.init();
}
TaxonomyWranglerAccordion.instances = {};
var ctrlDown = false;
$(window).keydown(function(e) {
if (e.keyCode == 17) {
ctrlDown = true;
}
}).keyup(function(e) {
if (e.keyCode == 17) {
ctrlDown = false;
}
});
TaxonomyWranglerAccordion.prototype.init = function() {
var accordion = this;
$('tbody tr').not(':eq(0)').each(function(){
var $row = $(this);
accordion.collapseTree($row);
accordion.attachAccordionEvents($row);
});
};
TaxonomyWranglerAccordion.prototype.attachAccordionEvents = function($row) {
var accordion = this;
$row.find('.taxonomy-wrangler-accordion-item').bind('click.tweaksAccordion', function(e){
e.preventDefault();
if (ctrlDown) {
accordion.toggleTree($row);
}
else {
accordion.toggleRow($row);
}
return false;
});
}
TaxonomyWranglerAccordion.prototype.refreshRows = function(){
var accordion = this;
this.rows.not(':eq(0)').each(function(){
var $this = $(this);
var hasChildren = accordion.rows.filter('[data-parent="'+ $this.data('tid') +'"]').length > 0 ? true : false;
var $collapseLink = $this.find('.taxonomy-wrangler-accordion-item--parent');
//create a new parent
if (hasChildren && !$collapseLink.length) {
$this.find('.tabledrag-handle').after('<a href="#" class="taxonomy-wrangler-accordion-item taxonomy-wrangler-accordion-item--parent"><span></span></a>');
accordion.attachAccordionEvents($this);
}
else if (!hasChildren && $collapseLink.length) {
$collapseLink.remove();
}
});
};
/**
* This is a bit different than a typical accordion in that the
* data structure is hierarchical, but its representation in the DOM is flat
*/
TaxonomyWranglerAccordion.prototype.showRow = function($row) {
$row.css('display', 'table-row').removeClass('tabledrag-hidden');
}
TaxonomyWranglerAccordion.prototype.hideRow = function($row) {
$row.css('display', 'none').addClass('tabledrag-hidden');
}
TaxonomyWranglerAccordion.prototype.setCollapsed = function($row, collapsed, recurse) {
var accordion = this;
var $children = this.rows.filter('[data-parent=' + $row.data('tid') + ']');
if (collapsed && $children.length) {
$row.data('is-collapsed', true);
$row.find('.taxonomy-wrangler-accordion-item').addClass('is-collapsed');
if ($children.length) {
$row.addClass('tabledrag-leaf');
}
}
else {
$row.data('is-collapsed', false).removeClass('tabledrag-leaf');
$row.find('.taxonomy-wrangler-accordion-item').removeClass('is-collapsed');
}
if (recurse) {
accordion.rows.filter('[data-parent=' + $row.data('tid') + ']').each(function(){
accordion.setCollapsed($(this), collapsed, true);
});
}
}
/*
* Hide all children
*/
TaxonomyWranglerAccordion.prototype.hideChildren = function($row) {
var $item = $row.find('.taxonomy-wrangler-accordion-item');
var accordion = this;
this.rows.filter('[data-parent=' + $row.data('tid') + ']').each(function(){
accordion.hideRow($(this));
accordion.hideChildren($(this));
});
};
/**
* Show children if they are not collapsed
*/
TaxonomyWranglerAccordion.prototype.showChildren = function($row) {
var accordion = this;
accordion.rows.filter('[data-parent=' + $row.data('tid') + ']').each(function(){
accordion.showRow($(this));
if (!$(this).data('is-collapsed')) {
accordion.showChildren($(this));
}
});
};
TaxonomyWranglerAccordion.prototype.toggleRow = function($row) {
if ($row.data('is-collapsed')) {
this.setCollapsed($row, false);
this.showChildren($row);
}
else {
this.setCollapsed($row, true);
this.hideChildren($row);
}
};
TaxonomyWranglerAccordion.prototype.expandTree = function($row) {
this.setCollapsed($row, false, true);
this.showChildren($row);
};
TaxonomyWranglerAccordion.prototype.collapseTree = function($row) {
this.setCollapsed($row, true, true);
this.hideChildren($row);
};
TaxonomyWranglerAccordion.prototype.toggleTree = function($row) {
if ($row.data('is-collapsed')) {
this.expandTree($row);
}
else {
this.collapseTree($row);
}
}
Drupal.behaviors.taxonomyWranglerAccordion = {
attach: function(context, settings) {
idCount = 0;
$('.taxonomy-wrangler-accordion', context)
.not('.taxonomy-wrangler-accordion-processed')
.addClass('taxonomy-wrangler-accordion-processed')
.each(function(){
var id = $(this).attr('id')
if (!!!id || id == '') {
$(this).attr('id', TaxonomyWranglerUID());
}
TaxonomyWranglerAccordion.instances[id] = new TaxonomyWranglerAccordion(this);
});
}
};
function TaxonomyWranglerCollection() {
this.updatedTerms = {};
};
TaxonomyWranglerCollection.prototype.updateTerm = function(row) {
var $row = $(row);
var tid = $row.data('tid');
var now = Date.now();
this.updatedTerms[tid] = {
"tid": $row.data('tid'),
"parent": $row.data('parent'),
"depth": $row.data('depth'),
"weight": $row.data('weight'),
"updated": now
}
//el.attr('data-{key}') & el.data('key') are not the same!
$row.attr('data-parent', $row.data('parent'))
.attr('data-depth', $row.data('depth'))
.attr('data-weight', $row.data('weight'))
.data('updated', now);
};
/**
* New TableDrag "class" that inherits Drupal.tableDrag
*/
function TaxonomyWranglerDrag(table, tableSettings) {
Drupal.tableDrag.call(this, table, tableSettings);
}
TaxonomyWranglerDrag.prototype = new Drupal.tableDrag();
TaxonomyWranglerDrag.instances = {};
/**
* Find the target used within a particular row and group.
* This isn't strictly necessary in this case because the data
* is stored on the row element itself.
* Copied and modified from misc/tabledrag.js
*/
TaxonomyWranglerDrag.prototype.rowSettings = function (group, row) {
for (var delta in this.tableSettings[group]) {
// Return a copy of the row settings.
var rowSettings = {};
for (var n in this.tableSettings[group][delta]) {
rowSettings[n] = this.tableSettings[group][delta][n];
}
return rowSettings;
}
};
/**
* After the row is dropped, update the table fields according to the settings
* set for this table.
*
* @param changedRow
* DOM object for the row that was just dropped.
*/
TaxonomyWranglerDrag.prototype.updateFields = function (changedRow) {
var $c = $(changedRow);
for (var group in this.tableSettings) {
// Each group may have a different setting for relationship, so we find
// the source rows for each separately.
this.updateField(changedRow, group);
}
};
/**
* After the row is dropped, update a single table field according to specific
* settings. This code is cribbed from tabledrag.js and modified to
* use the data properties on the row object rather than input fields in the row.
*
* @param changedRow
* DOM object for the row that was just dropped.
* @param group
* The settings group on which field updates will occur.
*/
TaxonomyWranglerDrag.prototype.updateField = function (changedRow, group) {
var rowSettings = this.rowSettings(group, changedRow);
// Set the row as its own target.
if (rowSettings.relationship == 'self' || rowSettings.relationship == 'group') {
var sourceRow = changedRow;
}
// Siblings are easy, check previous and next rows.
else if (rowSettings.relationship == 'sibling') {
var previousRow = $(changedRow).prev('tr').get(0);
var nextRow = $(changedRow).next('tr').get(0);
var sourceRow = changedRow;
if ($(previousRow).is('.draggable') && $('.' + group, previousRow).length) {
if (this.indentEnabled) {
if ($('.indentations', previousRow).length == $('.indentations', changedRow)) {
sourceRow = previousRow;
}
}
else {
sourceRow = previousRow;
}
}
else if ($(nextRow).is('.draggable') && $('.' + group, nextRow).length) {
if (this.indentEnabled) {
if ($('.indentations', nextRow).length == $('.indentations', changedRow)) {
sourceRow = nextRow;
}
}
else {
sourceRow = nextRow;
}
}
}
// Parents, look up the tree until we find a field not in this group.
// Go up as many parents as indentations in the changed row.
else if (rowSettings.relationship == 'parent') {
var previousRow = $(changedRow).prev('tr');
while (previousRow.length && $('.indentation', previousRow).length >= this.rowObject.indents) {
previousRow = previousRow.prev('tr');
}
// If we found a row.
if (previousRow.length) {
sourceRow = previousRow[0];
}
// Otherwise we went all the way to the left of the table without finding
// a parent, meaning this item has been placed at the root level.
else {
// Use the first row in the table as source, because it's guaranteed to
// be at the root level. Find the first item, then compare this row
// against it as a sibling.
sourceRow = $(this.table).find('tr.draggable:first').get(0);
if (sourceRow == this.rowObject.element) {
sourceRow = $(this.rowObject.group[this.rowObject.group.length - 1]).next('tr.draggable').get(0);
}
var useSibling = true;
}
}
// Because we may have moved the row from one category to another,
// take a look at our sibling and borrow its sources and targets.
this.copyDragClasses(sourceRow, changedRow, group);
rowSettings = this.rowSettings(group, changedRow);
// In the case that we're looking for a parent, but the row is at the top
// of the tree, copy our sibling's values.
if (useSibling) {
rowSettings.relationship = 'sibling';
rowSettings.source = rowSettings.target;
}
var $changedRow = $(changedRow);
// Check if data exists in this row.
if (typeof $(sourceRow).data(rowSettings.target) !== 'undefined') {
switch (rowSettings.action) {
case 'depth':
// Get the depth of the target row.
$changedRow.data(rowSettings.target, $('.indentation', $(sourceRow)).length);
break;
case 'match':
// Update the value.
$changedRow.data(rowSettings.target, $(sourceRow).data(rowSettings.source));
break;
case 'order':
var siblings = this.rowObject.findSiblings(rowSettings);
// We're not using fields for this so we just assume
// weight starting from 0
var weight = 0;
$(siblings).each(function () {
$(this).data('weight', weight);
weight++;
$(this).trigger('taxonomyWranglerTermDrag.rowDataChange', [this]);
});
break;
}
}
$changedRow.trigger('taxonomyWranglerTermDrag.rowDataChange', [changedRow]);
};
TaxonomyWranglerDrag.prototype.dropRow = function (event, self) {
// Drop row functionality shared between mouseup and blur events.
if (self.rowObject != null) {
var droppedRow = self.rowObject.element;
// The row is already in the right place so we just release it.
if (self.rowObject.changed == true) {
// Update the fields in the dropped row.
self.updateFields(droppedRow);
// If a setting exists for affecting the entire group, update all the
// fields in the entire dragged group.
for (var group in self.tableSettings) {
var rowSettings = self.rowSettings(group, droppedRow);
if (rowSettings.relationship == 'group') {
for (var n in self.rowObject.children) {
self.updateField(self.rowObject.children[n], group);
}
}
}
self.rowObject.markChanged();
if (self.changed == false) {
self.changed = true;
}
}
if (self.indentEnabled) {
self.rowObject.removeIndentClasses();
}
if (self.oldRowElement) {
$(self.oldRowElement).removeClass('drag-previous');
}
$(droppedRow).removeClass('drag').addClass('drag-previous');
self.oldRowElement = droppedRow;
self.onDrop();
self.rowObject = null;
}
// Functionality specific only to mouseup event.
if (self.dragObject != null) {
$('.tabledrag-handle', droppedRow).removeClass('tabledrag-handle-hover');
self.dragObject = null;
$('body').removeClass('drag');
clearInterval(self.scrollInterval);
// Hack for IE6 that flickers uncontrollably if select lists are moved.
if (navigator.userAgent.indexOf('MSIE 6.') != -1) {
$('select', this.table).css('display', 'block');
}
}
};
/**
* Indent a row within the legal bounds of the table.
*
* @param indentDiff
* The number of additional indentations proposed for the row (can be
* positive or negative). This number will be adjusted to nearest valid
* indentation level for the row.
*/
TaxonomyWranglerDrag.prototype.row.prototype.indent = function (indentDiff) {
// Determine the valid indentations interval if not available yet.
if (!this.interval) {
var prevRow = $(this.element).prevAll('tr:not(.tabledrag-hidden)').get(0);
var nextRow = $(this.group).filter(':last').nextAll('tr:not(.tabledrag-hidden)').get(0);
this.interval = this.validIndentInterval(prevRow, nextRow);
}
// Adjust to the nearest valid indentation.
var indent = this.indents + indentDiff;
indent = Math.max(indent, this.interval.min);
indent = Math.min(indent, this.interval.max);
indentDiff = indent - this.indents;
for (var n = 1; n <= Math.abs(indentDiff); n++) {
// Add or remove indentations.
if (indentDiff < 0) {
$('.indentation:first', this.group).remove();
this.indents--;
}
else {
$('td:first', this.group).prepend(Drupal.theme('tableDragIndentation'));
this.indents++;
}
}
if (indentDiff) {
// Update indentation for this row.
this.changed = true;
this.groupDepth += indentDiff;
this.onIndent();
}
return indentDiff;
};
/**
* Perform the swap between two rows. Take into account the collapsed
* accordion rows when swapping after.
*
* @param position
* Whether the swap will occur 'before' or 'after' the given row.
* @param row
* DOM element what will be swapped with the row group.
*/
TaxonomyWranglerDrag.prototype.row.prototype.swap = function (position, row) {
var swapRow = row;
if (position == 'after') {
var $row = $(row);
if ($row.data('is-collapsed')) {
var $target = $row.nextUntil(":visible");
if ($target.length) {
swapRow = $target.get($target.length - 1);
}
else {
// If there are no other rows visible, we must be at the bottom
// of the table, get the last row.
swapRow = $row.nextAll(":last").get(0);
}
}
}
Drupal.detachBehaviors(this.group, Drupal.settings, 'move');
$(swapRow)[position](this.group);
Drupal.attachBehaviors(this.group, Drupal.settings);
this.changed = true;
this.onSwap(row);
};
/**
* Ensure that two rows are allowed to be swapped.
*
* @param row
* DOM object for the row being considered for swapping.
*/
TaxonomyWranglerDrag.prototype.row.prototype.isValidSwap = function (row) {
if (this.indentEnabled) {
var prevRow, nextRow;
if (this.direction == 'down') {
prevRow = row;
nextRow = $(row).nextAll('tr:not(:hidden)').get(0);
}
else {
prevRow = $(row).prevAll('tr:not(:hidden)').get(0);
nextRow = row;
}
this.interval = this.validIndentInterval(prevRow, nextRow);
// We have an invalid swap if the valid indentations interval is empty.
if (this.interval.min > this.interval.max) {
return false;
}
}
// Do not let an un-draggable first row have anything put before it.
if (this.table.tBodies[0].rows[0] == row && $(row).is(':not(.draggable)')) {
return false;
}
return true;
};
/**
* Move a block in the blocks table from one region to another via select list.
*
* This behavior is dependent on the tableDrag behavior, since it uses the
* objects initialized in that behavior to update the row.
*/
Drupal.behaviors.taxonomyWranglerTermDrag = {
attach: function (context, settings) {
for (var base in settings.taxonomyWrangler.tables) {
if (typeof TaxonomyWranglerDrag.instances[base] == 'undefined') {
$('#' + base, context).once('tabledrag', function () {
// Create the new tableDrag instance. Save in the Drupal variable
// to allow other scripts access to the object.
TaxonomyWranglerDrag.instances[base] = new TaxonomyWranglerDrag(this, settings.taxonomyWrangler.tables[base]);
});
}
}
var table = $('#taxonomy-wrangler', context);
var tableDrag = TaxonomyWranglerDrag.instances['taxonomy-wrangler']; // Get the blocks tableDrag object.
var $messages = $('#taxonomy-wrangler-messages-js', context);
// Compare server timestamp with javascript changed stamp and remove changed classes.
if (settings.taxonomyWrangler && settings.taxonomyWrangler.updatedTimestamps) {
$.each(settings.taxonomyWrangler.updatedTimestamps, function (tid, stamp) {
var $row = table.find('tr[data-tid="'+ tid +'"]');
if ($row.data('updated') == stamp) {
$row.removeClass('drag-previous').find('span.tabledrag-changed').remove();
}
});
}
if (tableDrag.tm === null) {
$messages.removeClass('is-busy').empty();
}
if (!table.hasClass('taxonomy-wrangler-processed')) {
table.addClass('taxonomy-wrangler-processed');
var rows = $('tr', table).length;
var $termData = $('input[name="term_data"]', context);
var $submit = $('input[name="op"][value="Save"]', context)
var $rows = $('tr', table);
tableDrag.collection = new TaxonomyWranglerCollection();
var updatedTerms = {};
$submit.attr('disabled', 'disabled');
var accordion = !!TaxonomyWranglerAccordion.instances[table.attr('id')] ? TaxonomyWranglerAccordion.instances[table.attr('id')] : null;
$rows.bind('taxonomyWranglerTermDrag.rowDataChange', function(e, row){
tableDrag.collection.updateTerm(row);
var termsArray = [];
var tids = [];
accordion.refreshRows();
$.each(tableDrag.collection.updatedTerms, function(i, val) {
termsArray.push(val);
tids.push(val.tid + ':' + val.weight);
});
$termData.val(JSON.stringify({ "termData": termsArray }));
if (tableDrag.tm) {
clearTimeout(tableDrag.tm);
tableDrag.tm = null;
}
tableDrag.tm = setTimeout(function(){
$submit.removeAttr('disabled').trigger('mousedown').attr('disabled', 'disabled');
tableDrag.tm = null;
}, 1000);
if (!$messages.hasClass('is-busy')) {
$messages.addClass('is-busy').append('<div class="ajax-progress ajax-progress-throbber ajax-progress-throbber--taxonomy-wrangler"><div class="throbber">&nbsp;</div> Updating terms</div>');
}
});
}
}
};
})(jQuery, Drupal, this, this.document);
@@ -0,0 +1,291 @@
<?php
/**
* @file
* Administrative page callbacks for the taxonomy ui tweaks module.
*/
/**
* Form builder for the taxonomy tweaks terms overview.
*
* Display a tree of all the terms in a vocabulary, with options to edit
* each one. The form is made drag and drop by the theme function.
*
* @ingroup forms
* @see taxonomy_overview_terms_submit()
* @see theme_taxonomy_overview_terms()
*/
function taxonomy_wrangler_overview_terms($form, &$form_state, $vocabulary) {
global $pager_page_array, $pager_total, $pager_total_items;
// Check for confirmation forms.
if (isset($form_state['confirm_reset_alphabetical'])) {
module_load_include('inc', 'taxonomy', 'taxonomy.admin');
return taxonomy_vocabulary_confirm_reset_alphabetical($form, $form_state, $vocabulary->vid);
}
$form['#vocabulary'] = $vocabulary;
$form['#tree'] = TRUE;
$form['#parent_fields'] = FALSE;
$form['header']['info'] = array(
'#markup' => '<p>' . t('Drag and drop taxonomy terms below. Ctrl + click on an arrow will expand the term and all its children') . '</p>',
);
$form['header']['messages'] = array(
'#markup' => '<div id="taxonomy-wrangler-messages"><div id="taxonomy-wrangler-messages-server"></div><div id="taxonomy-wrangler-messages-js"></div></div>',
);
$root_entries = 0; // Elements at the root level on this page.
$delta = 0;
$term_deltas = array();
$tree = taxonomy_get_tree($vocabulary->vid);
$form_state['storage']['tree'] = $tree;
$term = current($tree);
$form['taxonomy_wrangler_messages'] = array(
'#type' => 'markup',
'#markup' => '',
);
// The original form handled setting the order after a validation error.
// Since we aren't supporting the non-ajax form, it has been removed.
// Build the actual form.
foreach ($tree as $orig_key => $term) {
$term_deltas[$term->tid] = isset($term_deltas[$term->tid]) ? $term_deltas[$term->tid] + 1 : 0;
$key = 'tid:' . $term->tid . ':' . $term_deltas[$term->tid];
// Save the term for the current page so we don't have to load it a second time.
$form[$key]['#term'] = (array) $term;
if (isset($term->parents)) {
$form[$key]['#term']['parent'] = $term->parent = $term->parents[0];
unset($form[$key]['#term']['parents'], $term->parents);
}
$form[$key]['view'] = array('#type' => 'link', '#title' => $term->name, '#href' => "taxonomy/term/$term->tid");
if ($vocabulary->hierarchy < 2 && count($tree) > 1) {
$form['#parent_fields'] = TRUE;
$form[$key]['view']['#options']['attributes']['data-tid'] = $term->tid;
$form[$key]['view']['#options']['attributes']['data-parent'] = $term->tid;
$form[$key]['view']['#options']['attributes']['data-depth'] = $term->depth;
$form[$key]['view']['#options']['attributes']['data-weight'] = $term->weight;
}
$form[$key]['edit'] = array('#type' => 'link', '#title' => t('edit'), '#href' => 'taxonomy/term/' . $term->tid . '/edit', '#options' => array('query' => drupal_get_destination()));
}
$form['#empty_text'] = t('No terms available. <a href="@link">Add term</a>.', array('@link' => url('admin/structure/taxonomy/' . $vocabulary->machine_name . '/add')));
// Hidden input field for ajax.
$form['term_data'] = array(
'#type' => 'hidden',
'#default_value' => '{}',
);
if ($vocabulary->hierarchy < 2 && count($tree) > 1) {
$form['actions'] = array('#type' => 'actions', '#tree' => FALSE);
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => t('Save'),
'#ajax' => array(
'callback' => 'taxonomy_wrangler_terms_overview_ajax_handler',
),
);
$form['actions']['reset_alphabetical'] = array(
'#type' => 'submit',
'#value' => t('Reset to alphabetical'),
);
}
$module_path = drupal_get_path('module', 'taxonomy_wrangler');
$form['#attached']['css'][] = $module_path . '/css/taxonomy_wrangler.css';
$form['#attached']['js'][] = $module_path . '/js/taxonomy_wrangler.js';
return $form;
}
/**
* Validate callback for taxonomy_wrangler_overview_terms.
*/
function taxonomy_wrangler_overview_terms_validate($form, &$form_state) {
if ($form_state['triggering_element']['#value'] != t('Reset to alphabetical')) {
if (isset($form_state['values']['term_data'])) {
$updated_terms_json = json_decode($form_state['values']['term_data']);
if ($updated_terms_json == NULL) {
drupal_set_message(t('an error occurred parsing submitted values.'), 'error');
}
else {
$form_state['storage']['updated_terms'] = array();
$values_to_update = array('parent', 'weight');
if (!empty($updated_terms_json->termData)) {
$form_state['storage']['raw_term_data'] = $updated_terms_json->termData;
foreach ($updated_terms_json->termData as $updated_term) {
$term = taxonomy_term_load($updated_term->tid);
foreach ($values_to_update as $prop) {
if (isset($updated_term->{$prop})) {
$term->{$prop} = (int) $updated_term->{$prop};
}
}
$form_state['storage']['updated_terms'][$term->tid] = $term;
}
}
}
}
}
}
/**
* Submit callback for taxonomy_wrangler_overview_terms.
*/
function taxonomy_wrangler_overview_terms_submit($form, &$form_state) {
if ($form_state['triggering_element']['#value'] == t('Reset to alphabetical')) {
module_load_include('inc', 'taxonomy', 'taxonomy.admin');
// Execute the reset action.
if ($form_state['values']['reset_alphabetical'] === TRUE) {
return taxonomy_vocabulary_confirm_reset_alphabetical_submit($form, $form_state);
}
// Rebuild the form to confirm the reset action.
$form_state['rebuild'] = TRUE;
$form_state['confirm_reset_alphabetical'] = TRUE;
return;
}
else {
if (!empty($form_state['storage']['updated_terms'])) {
foreach ($form_state['storage']['updated_terms'] as $term) {
taxonomy_term_save($term);
}
}
}
}
/**
* Ajax handler for taxonomy_wrangler_overview_terms.
*/
function taxonomy_wrangler_terms_overview_ajax_handler($form, &$form_state) {
$commands = array();
$messages = '<div id="taxonomy-wrangler-messages-server">';
$messages .= theme('status_messages');
$messages .= '</div>';
$commands[] = ajax_command_replace('#taxonomy-wrangler-messages-server', $messages);
// Send timestamps back to browser so it can update the row status.
$timestamps = array();
if (!empty($form_state['storage']['raw_term_data'])) {
foreach($form_state['storage']['raw_term_data'] as $term) {
if (isset($term->updated)) {
$timestamps[$term->tid] = (float) $term->updated;
}
}
}
$commands[] = ajax_command_settings(array(
'taxonomyWrangler' => array('updatedTimestamps' => $timestamps),
));
return array('#type' => 'ajax', '#commands' => $commands);
}
/**
* Returns HTML for a terms overview form as a sortable list of terms.
*
* @param $variables
* An associative array containing:
* - form: A render element representing the form.
*
* @see taxonomy_overview_terms()
* @ingroup themeable
*/
function theme_taxonomy_wrangler_overview_terms($variables) {
$form = $variables['form'];
// Add drag and drop if parent fields are present in the form.
if ($form['#parent_fields']) {
taxonomy_wrangler_add_tabledrag('taxonomy-wrangler', 'match', 'parent', 'parent', 'parent', 'tid', FALSE);
taxonomy_wrangler_add_tabledrag('taxonomy-wrangler', 'depth', 'group', 'depth', NULL, NULL, FALSE);
drupal_add_css(drupal_get_path('module', 'taxonomy') . '/taxonomy.css');
}
taxonomy_wrangler_add_tabledrag('taxonomy-wrangler', 'order', 'sibling', 'weight');
$errors = form_get_errors() != FALSE ? form_get_errors() : array();
$rows = array();
$element_children = element_children($form);
$is_root = TRUE;
foreach ($element_children as $i => $key) {
if (isset($form[$key]['#term'])) {
$term = &$form[$key];
$next_key = isset($element_children[$i + 1]) ? $element_children[$i + 1] : FALSE;
$is_parent = FALSE;
if (isset($form[$next_key]['#term']) && $form[$next_key]['#term']['depth'] > $term['#term']['depth'] && !$is_root) {
$is_parent = TRUE;
}
$row = array();
$row[] = (isset($term['#term']['depth']) && $term['#term']['depth'] > 0 ? theme('indentation', array('size' => $term['#term']['depth'])) : '');
$accordion_classes = array('taxonomy-wrangler-accordion-item');
if ($is_root) {
$accordion_classes[] = 'taxonomy-wrangler-accordion-item--root';
$is_root = FALSE;
}
elseif ($is_parent) {
$accordion_classes[] = 'taxonomy-wrangler-accordion-item--parent';
}
else {
$accordion_classes[] = 'taxonomy-wrangler-accordion-item--child';
}
$row[0] .= '<a href="#" class="' . implode(' ', $accordion_classes) . '"><span></span></a>';
$row[0] .= drupal_render($term['view']);
if ($form['#parent_fields']) {
$term['tid']['#attributes']['class'] = array('term-id');
$term['parent']['#attributes']['class'] = array('term-parent');
$term['depth']['#attributes']['class'] = array('term-depth');
$row[0] .= drupal_render($term['parent']) . drupal_render($term['tid']) . drupal_render($term['depth']);
}
$term['weight']['#attributes']['class'] = array('term-weight');
$row[] = drupal_render($term['weight']);
$row[] = drupal_render($term['edit']);
$row = array(
'data' => $row,
'data-tid' => $term['#term']['tid'],
'data-parent' => $term['#term']['parent'],
'data-depth' => $term['#term']['depth'],
'data-weight' => $term['#term']['weight'],
'data-haschildren' => $is_parent,
);
$rows[$key] = $row;
}
}
// Add necessary classes to rows.
$row_position = 0;
foreach ($rows as $key => $row) {
$rows[$key]['class'] = array();
if (isset($form['#parent_fields'])) {
$rows[$key]['class'][] = 'draggable';
}
// Add an error class if this row contains a form error.
foreach ($errors as $error_key => $error) {
if (strpos($error_key, $key) === 0) {
$rows[$key]['class'][] = 'error';
}
}
$rows[$key]['class'][] = 'taxonomy-wrangler-term-row';
$rows[$key]['class'][] = 'term-weight-row';
$row_position++;
}
if (empty($rows)) {
$rows[] = array(array('data' => $form['#empty_text'], 'colspan' => '3'));
}
$output = drupal_render($form['header']);
//unset($form['header']);
$header = array(t('Name'), t('Weight'), t('Operations'));
$output .= theme('table', array(
'header' => $header,
'rows' => $rows,
'attributes' => array(
'id' => 'taxonomy-wrangler',
'class' => array('taxonomy-wrangler', 'taxonomy-wrangler-accordion'),
),
));
$output .= drupal_render_children($form);
return $output;
}
@@ -0,0 +1,12 @@
name = Taxonomy Wrangler
description = Provides a better UI for the taxonomy overview page
core = 7.x
dependencies[] = taxonomy
package = Taxonomy
; Information added by Drupal.org packaging script on 2015-02-17
version = "7.x-1.x-dev"
core = "7.x"
project = "taxonomy_wrangler"
datestamp = "1424205783"
@@ -0,0 +1,59 @@
<?php
/**
* @file
* Taxonomy wrangler main module file
*/
/**
* Implements hook_menu_alter().
*
* Override the main taxonomy overview page.
*/
function taxonomy_wrangler_menu_alter(&$items) {
$items['admin/structure/taxonomy/%taxonomy_vocabulary_machine_name']['page arguments'] = array('taxonomy_wrangler_overview_terms', 3);
$items['admin/structure/taxonomy/%taxonomy_vocabulary_machine_name']['file'] = 'taxonomy_wrangler.admin.inc';
$items['admin/structure/taxonomy/%taxonomy_vocabulary_machine_name']['file path'] = drupal_get_path('module', 'taxonomy_wrangler');
}
/**
* Implements hook_theme().
*/
function taxonomy_wrangler_theme() {
return array(
'taxonomy_wrangler_overview_terms' => array(
'render element' => 'form',
),
);
}
/**
* Copy of drupal_add_tabledrag().
*
* This is a copy of drupal_add_tabledrag used just to instantiate
* our own tabledrag class vs Drupal's.
* @see drupal_add_tabledrag
*/
function taxonomy_wrangler_add_tabledrag($table_id, $action, $relationship, $group, $subgroup = NULL, $source = NULL, $hidden = TRUE, $limit = 0) {
$js_added = &drupal_static(__FUNCTION__, FALSE);
if (!$js_added) {
// Add the table drag JavaScript to the page before the module JavaScript
// to ensure that table drag behaviors are registered before any module
// uses it.
drupal_add_library('system', 'jquery.cookie');
drupal_add_js('misc/tabledrag.js', array('weight' => -1));
$js_added = TRUE;
}
// If a subgroup or source isn't set, assume it is the same as the group.
$target = isset($subgroup) ? $subgroup : $group;
$source = isset($source) ? $source : $target;
$settings['taxonomyWrangler']['tables'][$table_id][$group][] = array(
'target' => $target,
'source' => $source,
'relationship' => $relationship,
'action' => $action,
'hidden' => $hidden,
'limit' => $limit,
);
drupal_add_js($settings, 'setting');
}
@@ -1,149 +0,0 @@
This document explains how to provide "Pathauto integration" in a
module. You need this if you would like to provide additional tokens
or if your module has paths and you wish to have them automatically
aliased. The simplest integration is just to provide tokens so we
cover that first. More advanced integration requires an
implementation of hook_pathauto to provide a settings form.
It may be helpful to review some examples of integration from the
pathauto_node.inc, pathauto_taxonomy.inc, and pathauto_user.inc files.
==================
1 - Providing additional tokens
==================
If all you want is to enable tokens for your module you will simply
need to implement two functions:
hook_token_values
hook_token_list
See the token.module and it's API.txt for more information about this
process.
If the token is intended to generate a path expected to contain slashes,
the token name must end in 'path', 'path-raw' or 'alias'. This indicates to
Pathauto that the slashes should not be removed from the replacement value.
When an object is created (whether it is a node or a user or a
taxonomy term) the data that Pathauto hands to the token_values in the
$object is in a specific format. This is the format that most people
write code to handle. However, during edits and bulk updates the data
may be in a totally different format. So, if you are writing a
hook_token_values implementation to add special tokens, be sure to
test creation, edit, and bulk update cases to make sure your code will
handle it.
==================
2 - Settings hook - To create aliases for your module
==================
You must implement hook_pathauto($op), where $op is always (at this
time) 'settings'. Return an object (NOT an array) containing the
following members, which will be used by pathauto to build a group
of settings for your module and define the variables for saving your
settings:
module - The name of your module (e.g., 'node')
groupheader - The translated label for the settings group (e.g.,
t('Content path settings')
patterndescr - The translated label for the default pattern (e.g.,
t('Default path pattern (applies to all content types with blank patterns below)')
patterndefault - A translated default pattern (e.g., t('[cat]/[title].html'))
token_type - The token type (e.g. 'node', 'user') that can be used.
patternitems - For modules which need to express multiple patterns
(for example, the node module supports a separate pattern for each
content type), an array whose keys consist of identifiers for each
pattern (e.g., the content type name) and values consist of the
translated label for the pattern
bulkname - For modules which support a bulk update operation, the
translated label for the action (e.g., t('Bulk update content paths'))
bulkdescr - For modules which support a bulk update operation, a
translated, more thorough description of what the operation will do
(e.g., t('Generate aliases for all existing content items which do not already have aliases.'))
==================
2 - $alias = pathauto_create_alias($module, $op, $placeholders, $src, $type=NULL)
==================
At the appropriate time (usually when a new item is being created for
which a generated alias is desired), call pathauto_create_alias() to
generate and create the alias. See the user, taxonomy, and nodeapi hook
implementations in pathauto.module for examples.
$module - The name of your module (e.g., 'node')
$op - Operation being performed on the item ('insert', 'update', or
'bulkupdate')
$placeholders - An array whose keys consist of the translated placeholders
which appear in patterns and values are the "clean" values to be
substituted into the pattern. Call pathauto_cleanstring() on any
values which you do not know to be purely alphanumeric, to substitute
any non-alphanumerics with the user's designated separator. Note that
if the pattern has multiple slash-separated components (e.g., [term:path]),
pathauto_cleanstring() should be called for each component, not the
complete string.
Example: $placeholders[t('[title]')] = pathauto_cleanstring($node->title);
$src - The "real" URI of the content to be aliased (e.g., "node/$node->nid")
$type - For modules which provided patternitems in hook_autopath(),
the relevant identifier for the specific item to be aliased (e.g.,
$node->type)
pathauto_create_alias() returns the alias that was created.
==================
3 - Bulk update function
==================
If a module supports bulk updating of aliases, it must provide a
function of this form, to be called by pathauto when the corresponding
checkbox is selected and the settings page submitted:
function <module>_pathauto_bulkupdate()
The function should iterate over the content items controlled by the
module, calling pathauto_create_alias() for each one. It is
recommended that the function report on its success (e.g., with a
count of created aliases) via drupal_set_message().
==================
4 - Bulk delete hook_path_alias_types()
==================
For modules that create new types of pages that can be aliased with pathauto, a
hook implementation is needed to allow the user to delete them all at once.
function hook_path_alias_types()
This hook returns an array whose keys match the beginning of the source paths
(e.g.: "node/", "user/", etc.) and whose values describe the type of page (e.g.:
"content", "users"). Like all displayed strings, these descriptionsshould be
localized with t(). Use % to match interior pieces of a path; "user/%/track". This
is a database wildcard, so be careful.
==================
Modules that extend node and/or taxonomy
==================
NOTE: this is basically not true any more. If you feel you need this file an issue.
Many contributed Drupal modules extend the core node and taxonomy
modules. To extend pathauto patterns to support their extensions, they
may implement the pathauto_node and pathauto_taxonomy hooks.
To do so, implement the function <modulename>_pathauto_node (or _taxonomy),
accepting the arguments $op and $node (or $term). Two operations are
supported:
$op = 'placeholders' - return an array keyed on placeholder strings
(e.g., t('[eventyyyy]')) valued with descriptions (e.g. t('The year the
event starts.')).
$op = 'values' - return an array keyed on placeholder strings, valued
with the "clean" actual value for the passed node or category (e.g.,
pathauto_cleanstring(date('M', $eventstart)));
See contrib/pathauto_node_event.inc for an example of extending node
patterns.
@@ -1,48 +1,49 @@
Please read this file and also the INSTALL.txt.
Please read this file and also the INSTALL.txt.
They contain answers to many common questions.
If you are developing for this module, the API.txt may be interesting.
If you are upgrading, check the CHANGELOG.txt for major changes.
**Description:
The Pathauto module provides support functions for other modules to
automatically generate aliases based on appropriate criteria, with a
** Description:
The Pathauto module provides support functions for other modules to
automatically generate aliases based on appropriate criteria, with a
central settings path for site administrators.
Implementations are provided for core entity types: content, taxonomy terms,
and users (including blogs and tracker pages).
and users (including blogs and forum pages).
Pathauto also provides a way to delete large numbers of aliases. This feature
is available at Administer > Site building > URL aliases > Delete aliases
Pathauto also provides a way to delete large numbers of aliases. This feature
is available at Administer > Configuration > Search and metadata > URL aliases
> Delete aliases.
**Benefits:
** Benefits:
Besides making the page address more reflective of its content than
"node/138", it's important to know that modern search engines give
heavy weight to search terms which appear in a page's URL. By
automatically using keywords based directly on the page content in the URL,
"node/138", it's important to know that modern search engines give
heavy weight to search terms which appear in a page's URL. By
automatically using keywords based directly on the page content in the URL,
relevant search engine hits for your page can be significantly
enhanced.
**Installation AND Upgrades:
** Installation AND Upgrades:
See the INSTALL.txt file.
**Notices:
** Notices:
Pathauto just adds URL aliases to content, users, and taxonomy terms.
Because it's an alias, the standard Drupal URL (for example node/123 or
taxonomy/term/1) will still function as normal. If you have external links
to your site pointing to standard Drupal URLs, or hardcoded links in a module,
Because it's an alias, the standard Drupal URL (for example node/123 or
taxonomy/term/1) will still function as normal. If you have external links
to your site pointing to standard Drupal URLs, or hardcoded links in a module,
template, content or menu which point to standard Drupal URLs it will bypass
the alias set by Pathauto.
There are reasons you might not want two URLs for the same content on your
site. If this applies to you, please note that you will need to update any
hard coded links in your content or blocks.
There are reasons you might not want two URLs for the same content on your
site. If this applies to you, please note that you will need to update any
hard coded links in your content or blocks.
If you use the "system path" (i.e. node/10) for menu items and settings like
that, Drupal will replace it with the url_alias.
For external links, you might want to consider the Path Redirect or
Global Redirect modules, which allow you to set forwarding either per item or
across the site to your aliased URLs.
For external links, you might want to consider the Path Redirect or
Global Redirect modules, which allow you to set forwarding either per item or
across the site to your aliased URLs.
URLs (not) Getting Replaced With Aliases:
Please bear in mind that only URLs passed through Drupal's l() or url()
@@ -54,41 +55,27 @@ Drupal API instead:
* 'href="'. url("node/$node->nid") .'"' or
* l("Your link title", "node/$node->nid")
See http://api.drupal.org/api/HEAD/function/url and
See http://api.drupal.org/api/HEAD/function/url and
http://api.drupal.org/api/HEAD/function/l for more information.
** Disabling Pathauto for a specific content type (or taxonomy)
When the pattern for a content type is left blank, the default pattern will be
used. But if the default pattern is also blank, Pathauto will be disabled
When the pattern for a content type is left blank, the default pattern will be
used. But if the default pattern is also blank, Pathauto will be disabled
for that content type.
** Bulk Updates Must be Run Multiple Times:
As of 5.x-2.x Pathauto now performs bulk updates in a manner which is more
likely to succeed on large sites. The drawback is that it needs to be run
multiple times. If you want to reduce the number of times that you need to
run Pathauto you can increase the "Maximum number of objects to alias in a
bulk update:" setting under General Settings.
**WYSIWYG Conflicts - FCKEditor, TinyMCE, etc.
If you use a WYSIWYG editor, please disable it for the Pathauto admin page.
Failure to do so may cause errors about "preg_replace" problems due to the <p>
tag being added to the "strings to replace". See http://drupal.org/node/175772
**Credits:
** Credits:
The original module combined the functionality of Mike Ryan's autopath with
Tommy Sundstrom's path_automatic.
Significant enhancements were contributed by jdmquin @ www.bcdems.net.
Matt England added the tracker support.
Matt England added the tracker support (tracker support has been removed in
recent changes).
Other suggestions and patches contributed by the Drupal community.
Current maintainers:
Greg Knaddison - http://growingventuresolutions.com
Current maintainers:
Dave Reid - http://www.davereid.net
Greg Knaddison - http://www.knaddison.com
Mike Ryan - http://mikeryan.name
Frederik 'Freso' S. Olesen - http://freso.dk
**Changes:
See the CHANGELOG.txt file.
@@ -64,17 +64,11 @@ function pathauto_patterns_form($form, $form_state) {
}
}
// Display the user documentation of placeholders supported by
// this module, as a description on the last pattern
// Show the token help relevant to this pattern type.
$form[$module]['token_help'] = array(
'#title' => t('Replacement patterns'),
'#type' => 'fieldset',
'#collapsible' => TRUE,
'#collapsed' => TRUE,
);
$form[$module]['token_help']['help'] = array(
'#theme' => 'token_tree',
'#token_types' => array($settings->token_type),
'#dialog' => TRUE,
);
}
@@ -144,7 +138,7 @@ function pathauto_settings_form($form) {
$description = t('What should Pathauto do when updating an existing content item which already has an alias?');
if (module_exists('redirect')) {
$description .= ' ' . t('The <a href="!url">Redirect module settings</a> affect whether a redirect is created when an alias is deleted.', array('!url' => url('admin/config/search/redirect')));
$description .= ' ' . t('The <a href="!url">Redirect module settings</a> affect whether a redirect is created when an alias is deleted.', array('!url' => url('admin/config/search/redirect/settings')));
}
else {
$description .= ' ' . t('Considering installing the <a href="!url">Redirect module</a> to get redirects when your aliases change.', array('!url' => 'http://drupal.org/project/redirect'));
@@ -165,7 +159,7 @@ function pathauto_settings_form($form) {
'#type' => 'checkbox',
'#title' => t('Transliterate prior to creating alias'),
'#default_value' => variable_get('pathauto_transliterate', FALSE) && module_exists('transliteration'),
'#description' => t('When a pattern includes certain characters (such as those with accents) should Pathauto attempt to transliterate them into the ASCII-96 alphabet? Transliteration is handled by the Transliteration module.'),
'#description' => t('When a pattern includes certain characters (such as those with accents) should Pathauto attempt to transliterate them into the US-ASCII alphabet? Transliteration is handled by the Transliteration module.'),
'#access' => module_exists('transliteration'),
);
@@ -1,17 +1,171 @@
<?php
/**
* @file
* Documentation for pathauto API.
*
* @see hook_token_info
* @see hook_tokens
* It may be helpful to review some examples of integration from
* pathauto.pathauto.inc.
*
* Pathauto works by using tokens in path patterns. Thus the simplest
* integration is just to provide tokens. Token support is provided by Drupal
* core. To provide additional token from your module, implement the following
* hooks:
*
* hook_tokens() - http://api.drupal.org/api/function/hook_tokens
* hook_token_info() - http://api.drupal.org/api/function/hook_token_info
*
* If you wish to provide pathauto integration for custom paths provided by your
* module, there are a few steps involved.
*
* 1. hook_pathauto()
* Provide information required by pathauto for the settings form as well as
* bulk generation. See the documentation for hook_pathauto() for more
* details.
*
* 2. pathauto_create_alias()
* At the appropriate time (usually when a new item is being created for
* which a generated alias is desired), call pathauto_create_alias() with the
* appropriate parameters to generate and create the alias. See the user,
* taxonomy, and node hook implementations in pathauto.module for examples.
* Also see the documentation for pathauto_create_alias().
*
* 3. pathauto_path_delete_all()
* At the appropriate time (usually when an item is being deleted), call
* pathauto_path_delete_all() to remove any aliases that were created for the
* content being removed. See the documentation for
* pathauto_path_delete_all() for more details.
*
* 4. hook_path_alias_types()
* For modules that create new types of content that can be aliased with
* pathauto, a hook implementation is needed to allow the user to delete them
* all at once. See the documentation for hook_path_alias_types() below for
* more information.
*
* There are other integration points with pathauto, namely alter hooks that
* allow you to change the data used by pathauto at various points in the
* process. See the below hook documentation for details.
*/
/**
* Used primarily by the bulk delete form. This hooks provides pathauto the
* information needed to bulk delete aliases created by your module. The keys
* of the return array are used by pathauto as the system path prefix to delete
* from the url_aliases table. The corresponding value is simply used as the
* label for each type of path on the bulk delete form.
*
* @return
* An array whose keys match the beginning of the source paths
* (e.g.: "node/", "user/", etc.) and whose values describe the type of page
* (e.g.: "Content", "Users"). Like all displayed strings, these descriptions
* should be localized with t(). Use % to match interior pieces of a path,
* like "user/%/track". This is a database wildcard (meaning "user/%/track"
* matches "user/1/track" as well as "user/1/view/track").
*/
function hook_path_alias_types() {
$objects['user/'] = t('Users');
$objects['node/'] = t('Content');
return $objects;
}
/**
* Provide information about the way your module's aliases will be built.
*
* The information you provide here is used to build the form
* on search/path/patterns. File pathauto.pathauto.inc provides example
* implementations for system modules.
*
* @see node_pathauto
*
* @param $op
* At the moment this will always be 'settings'.
*
* @return object|null
* An object, or array of objects (if providing multiple groups of path
* patterns). Each object should have the following members:
* - 'module': The module or entity type.
* - 'token_type': Which token type should be allowed in the patterns form.
* - 'groupheader': Translated label for the settings group
* - 'patterndescr': The translated label for the default pattern (e.g.,
* t('Default path pattern (applies to all content types with blank
* patterns below)')
* - 'patterndefault': Default pattern (e.g. 'content/[node:title]'
* - 'batch_update_callback': The name of function that should be ran for
* bulk update. @see node_pathauto_bulk_update_batch_process for example
* - 'batch_file': The name of the file with the bulk update function.
* - 'patternitems': Optional. An array of descritpions keyed by bundles.
*/
function hook_pathauto($op) {
switch ($op) {
case 'settings':
$settings = array();
$settings['module'] = 'file';
$settings['token_type'] = 'file';
$settings['groupheader'] = t('File paths');
$settings['patterndescr'] = t('Default path pattern (applies to all file types with blank patterns below)');
$settings['patterndefault'] = 'files/[file:name]';
$settings['batch_update_callback'] = 'file_entity_pathauto_bulk_update_batch_process';
$settings['batch_file'] = drupal_get_path('module', 'file_entity') . '/file_entity.pathauto.inc';
foreach (file_type_get_enabled_types() as $file_type => $type) {
$settings['patternitems'][$file_type] = t('Pattern for all @file_type paths.', array('@file_type' => $type->label));
}
return (object) $settings;
default:
break;
}
}
/**
* Determine if a possible URL alias would conflict with any existing paths.
*
* Returning TRUE from this function will trigger pathauto_alias_uniquify() to
* generate a similar URL alias with a suffix to avoid conflicts.
*
* @param string $alias
* The potential URL alias.
* @param string $source
* The source path for the alias (e.g. 'node/1').
* @param string $langcode
* The language code for the alias (e.g. 'en').
*
* @return bool
* TRUE if $alias conflicts with an existing, reserved path, or FALSE/NULL if
* it does not match any reserved paths.
*
* @see pathauto_alias_uniquify()
*/
function hook_pathauto_is_alias_reserved($alias, $source, $langcode) {
// Check our module's list of paths and return TRUE if $alias matches any of
// them.
return (bool) db_query("SELECT 1 FROM {mytable} WHERE path = :path", array(':path' => $alias))->fetchField();
}
/**
* Alter the pattern to be used before an alias is generated by Pathauto.
*
* This hook will only be called if a default pattern is configured (on
* admin/config/search/path/patterns).
*
* @param string $pattern
* The alias pattern for Pathauto to pass to token_replace() to generate the
* URL alias.
* @param array $context
* An associative array of additional options, with the following elements:
* - 'module': The module or entity type being aliased.
* - 'op': A string with the operation being performed on the object being
* aliased. Can be either 'insert', 'update', 'return', or 'bulkupdate'.
* - 'source': A string of the source path for the alias (e.g. 'node/1').
* - 'data': An array of keyed objects to pass to token_replace().
* - 'type': The sub-type or bundle of the object being aliased.
* - 'language': A string of the language code for the alias (e.g. 'en').
* This can be altered by reference.
*/
function hook_pathauto_pattern_alter(&$pattern, array $context) {
// Switch out any [node:created:*] tokens with [node:updated:*] on update.
if ($context['module'] == 'node' && ($context['op'] == 'update')) {
$pattern = preg_replace('/\[node:created(\:[^]]*)?\]/', '[node:updated$1]', $pattern);
}
}
/**
@@ -59,30 +59,27 @@ define('PATHAUTO_PUNCTUATION_DO_NOTHING', 2);
* A string alias.
* @param $source
* A string that is the internal path.
* @param $language
* @param $langcode
* A string indicating the path's language.
* @return
*
* @return bool
* TRUE if an alias exists, FALSE if not.
*
* @deprecated Use path_pathauto_is_alias_reserved() instead.
*/
function _pathauto_alias_exists($alias, $source, $language = LANGUAGE_NONE) {
$pid = db_query_range("SELECT pid FROM {url_alias} WHERE source <> :source AND alias = :alias AND language IN (:language, :language_none) ORDER BY language DESC, pid DESC", 0, 1, array(
':source' => $source,
':alias' => $alias,
':language' => $language,
':language_none' => LANGUAGE_NONE,
))->fetchField();
return !empty($pid);
function _pathauto_alias_exists($alias, $source, $langcode = LANGUAGE_NONE) {
return path_pathauto_is_alias_reserved($alias, $source, $langcode);
}
/**
* Fetches an existing URL alias given a path and optional language.
*
* @param $source
* @param string $source
* An internal Drupal path.
* @param $language
* @param string $language
* An optional language code to look up the path in.
* @return
*
* @return bool|array
* FALSE if no alias was found or an associative array containing the
* following keys:
* - pid: Unique path alias identifier.
@@ -111,12 +108,17 @@ function _pathauto_existing_alias_data($source, $language = LANGUAGE_NONE) {
* This function should *not* be called on URL alias or path strings because it
* is assumed that they are already clean.
*
* @param $string
* @param string $string
* A string to clean.
* @return
* @param array $options
* (optional) A keyed array of settings and flags to control the Pathauto
* clean string replacement process. Supported options are:
* - langcode: A language code to be used when translating strings.
*
* @return string
* The cleaned string.
*/
function pathauto_cleanstring($string) {
function pathauto_cleanstring($string, array $options = array()) {
// Use the advanced drupal_static() pattern, since this is called very often.
static $drupal_static_fast;
if (!isset($drupal_static_fast)) {
@@ -161,6 +163,7 @@ function pathauto_cleanstring($string) {
if ($ignore_words_regex) {
$cache['ignore_words_regex'] = '\b' . $ignore_words_regex . '\b';
if (function_exists('mb_eregi_replace')) {
mb_regex_encoding('UTF-8');
$cache['ignore_words_callback'] = 'mb_eregi_replace';
}
else {
@@ -170,15 +173,23 @@ function pathauto_cleanstring($string) {
}
}
// Empty strings do not need any proccessing.
// Empty strings do not need any processing.
if ($string === '' || $string === NULL) {
return '';
}
$langcode = NULL;
if (!empty($options['language']->language)) {
$langcode = $options['language']->language;
}
elseif (!empty($options['langcode'])) {
$langcode = $options['langcode'];
}
// Check if the string has already been processed, and if so return the
// cached result.
if (isset($cache['strings'][$string])) {
return $cache['strings'][$string];
if (isset($cache['strings'][$langcode][$string])) {
return $cache['strings'][$langcode][$string];
}
// Remove all HTML tags from the string.
@@ -186,7 +197,10 @@ function pathauto_cleanstring($string) {
// Optionally transliterate (by running through the Transliteration module)
if ($cache['transliterate']) {
$output = transliteration_get($output);
// If the reduce strings to letters and numbers is enabled, don't bother
// replacing unknown characters with a question mark. Use an empty string
// instead.
$output = transliteration_get($output, $cache['reduce_ascii'] ? '' : '?', $langcode);
}
// Replace or drop punctuation based on user settings
@@ -220,7 +234,7 @@ function pathauto_cleanstring($string) {
$output = truncate_utf8($output, $cache['maxlength'], TRUE);
// Cache this result in the static array.
$cache['strings'][$string] = $output;
$cache['strings'][$langcode][$string] = $output;
return $output;
}
@@ -228,11 +242,12 @@ function pathauto_cleanstring($string) {
/**
* Trims duplicate, leading, and trailing separators from a string.
*
* @param $string
* @param string $string
* The string to clean path separators from.
* @param $separator
* @param string $separator
* The path separator to use when cleaning.
* @return
*
* @return string
* The cleaned version of the string.
*
* @see pathauto_cleanstring()
@@ -250,21 +265,20 @@ function _pathauto_clean_separators($string, $separator = NULL) {
$output = $string;
// Clean duplicate or trailing separators.
if (strlen($separator)) {
// Escape the separator.
$seppattern = preg_quote($separator, '/');
// Trim any leading or trailing separators.
$output = preg_replace("/^$seppattern+|$seppattern+$/", '', $output);
$output = trim($output, $separator);
// Replace trailing separators around slashes.
if ($separator !== '/') {
$output = preg_replace("/$seppattern+\/|\/$seppattern+/", "/", $output);
}
// Escape the separator for use in regular expressions.
$seppattern = preg_quote($separator, '/');
// Replace multiple separators with a single one.
$output = preg_replace("/$seppattern+/", $separator, $output);
// Replace trailing separators around slashes.
if ($separator !== '/') {
$output = preg_replace("/\/+$seppattern\/+|$seppattern\/+|\/+$seppattern/", "/", $output);
}
}
return $output;
@@ -278,9 +292,10 @@ function _pathauto_clean_separators($string, $separator = NULL) {
* - Trim duplicate, leading, and trailing separators.
* - Shorten to a desired length and logical position based on word boundaries.
*
* @param $alias
* @param string $alias
* A string with the URL alias to clean up.
* @return
*
* @return string
* The cleaned URL alias.
*/
function pathauto_clean_alias($alias) {
@@ -294,12 +309,15 @@ function pathauto_clean_alias($alias) {
$output = $alias;
// Trim duplicate, leading, and trailing back-slashes.
$output = _pathauto_clean_separators($output, '/');
// Trim duplicate, leading, and trailing separators.
// Trim duplicate, leading, and trailing separators. Do this before cleaning
// backslashes since a pattern like "[token1]/[token2]-[token3]/[token4]"
// could end up like "value1/-/value2" and if backslashes were cleaned first
// this would result in a duplicate blackslash.
$output = _pathauto_clean_separators($output);
// Trim duplicate, leading, and trailing backslashes.
$output = _pathauto_clean_separators($output, '/');
// Shorten to a logical place based on word boundaries.
$output = truncate_utf8($output, $cache['maxlength'], TRUE);
@@ -328,8 +346,10 @@ function pathauto_clean_alias($alias) {
* (e.g., $node->type).
* @param $language
* A string specify the path's language.
* @return
* The alias that was created.
*
* @return array|null|false
* The alias array that was created, NULL if an empty alias was generated, or
* FALSE if the alias generation was not possible.
*
* @see _pathauto_set_alias()
* @see token_replace()
@@ -337,20 +357,32 @@ function pathauto_clean_alias($alias) {
function pathauto_create_alias($module, $op, $source, $data, $type = NULL, $language = LANGUAGE_NONE) {
// Retrieve and apply the pattern for this content type.
$pattern = pathauto_pattern_load_by_entity($module, $type, $language);
// Allow other modules to alter the pattern.
$context = array(
'module' => $module,
'op' => $op,
'source' => $source,
'data' => $data,
'type' => $type,
'language' => &$language,
);
drupal_alter('pathauto_pattern', $pattern, $context);
if (empty($pattern)) {
// No pattern? Do nothing (otherwise we may blow away existing aliases...)
return '';
return FALSE;
}
// Special handling when updating an item which is already aliased.
$existing_alias = NULL;
if ($op == 'update' || $op == 'bulkupdate') {
if ($op != 'insert') {
if ($existing_alias = _pathauto_existing_alias_data($source, $language)) {
switch (variable_get('pathauto_update_action', PATHAUTO_UPDATE_ACTION_DELETE)) {
case PATHAUTO_UPDATE_ACTION_NO_NEW:
// If an alias already exists, and the update action is set to do nothing,
// then gosh-darn it, do nothing.
return '';
return FALSE;
}
}
}
@@ -369,26 +401,19 @@ function pathauto_create_alias($module, $op, $source, $data, $type = NULL, $lang
// @see token_scan()
$pattern_tokens_removed = preg_replace('/\[[^\s\]:]*:[^\s\]]*\]/', '', $pattern);
if ($alias === $pattern_tokens_removed) {
return '';
return;
}
$alias = pathauto_clean_alias($alias);
// Allow other modules to alter the alias.
$context = array(
'module' => $module,
'op' => $op,
'source' => &$source,
'data' => $data,
'type' => $type,
'language' => &$language,
'pattern' => $pattern,
);
$context['source'] = &$source;
$context['pattern'] = $pattern;
drupal_alter('pathauto_alias', $alias, $context);
// If we have arrived at an empty string, discontinue.
if (!drupal_strlen($alias)) {
return '';
return;
}
// If the alias already exists, generate a new, hopefully unique, variant.
@@ -432,7 +457,7 @@ function pathauto_create_alias($module, $op, $source, $data, $type = NULL, $lang
* A string with a language code.
*/
function pathauto_alias_uniquify(&$alias, $source, $langcode) {
if (!_pathauto_alias_exists($alias, $source, $langcode)) {
if (!pathauto_is_alias_reserved($alias, $source, $langcode)) {
return;
}
@@ -445,9 +470,9 @@ function pathauto_alias_uniquify(&$alias, $source, $langcode) {
do {
// Append an incrementing numeric suffix until we find a unique alias.
$unique_suffix = $separator . $i;
$alias = truncate_utf8($original_alias, $maxlength - drupal_strlen($unique_suffix, TRUE)) . $unique_suffix;
$alias = truncate_utf8($original_alias, $maxlength - drupal_strlen($unique_suffix), TRUE) . $unique_suffix;
$i++;
} while (_pathauto_alias_exists($alias, $source, $langcode));
} while (pathauto_is_alias_reserved($alias, $source, $langcode));
}
/**
@@ -463,7 +488,7 @@ function pathauto_alias_uniquify(&$alias, $source, $langcode) {
function _pathauto_path_is_callback($path) {
// We need to use a try/catch here because of a core bug which will throw an
// exception if $path is something like 'node/foo/bar'.
// @todo Remove when http://drupal.org/node/1302158 is fixed in core.
// @todo Remove when http://drupal.org/node/1003788 is fixed in core.
try {
$menu = menu_get_item($path);
}
@@ -498,26 +523,19 @@ function _pathauto_path_is_callback($path) {
* An optional string with the operation being performed.
*
* @return
* The saved path from path_save() or NULL if the path was not saved.
* The saved path from path_save() or FALSE if the path was not saved.
*
* @see path_save()
*/
function _pathauto_set_alias(array $path, $existing_alias = NULL, $op = NULL) {
$verbose = _pathauto_verbose(NULL, $op);
// Alert users that an existing callback cannot be overridden automatically
if (_pathauto_path_is_callback($path['alias'])) {
if ($verbose) {
_pathauto_verbose(t('Ignoring alias %alias due to existing path conflict.', array('%alias' => $path['alias'])));
}
return;
}
// Alert users if they are trying to create an alias that is the same as the internal path
if ($path['source'] == $path['alias']) {
if ($verbose) {
_pathauto_verbose(t('Ignoring alias %alias because it is the same as the internal path.', array('%alias' => $path['alias'])));
}
return;
return FALSE;
}
// Skip replacing the current alias with an identical alias
@@ -529,7 +547,7 @@ function _pathauto_set_alias(array $path, $existing_alias = NULL, $op = NULL) {
switch (variable_get('pathauto_update_action', PATHAUTO_UPDATE_ACTION_DELETE)) {
case PATHAUTO_UPDATE_ACTION_NO_NEW:
// Do not create the alias.
return;
return FALSE;
case PATHAUTO_UPDATE_ACTION_LEAVE:
// Create a new alias instead of overwriting the existing by leaving
// $path['pid'] empty.
@@ -605,7 +623,7 @@ function pathauto_clean_token_values(&$replacements, $data = array(), $options =
foreach ($replacements as $token => $value) {
// Only clean non-path tokens.
if (!preg_match('/(path|alias|url|url-brief)\]$/', $token)) {
$replacements[$token] = pathauto_cleanstring($value);
$replacements[$token] = pathauto_cleanstring($value, $options);
}
}
}
@@ -3,13 +3,14 @@ description = Provides a mechanism for modules to automatically generate aliases
dependencies[] = path
dependencies[] = token
core = 7.x
files[] = pathauto.migrate.inc
files[] = pathauto.test
configure = admin/config/search/path/patterns
recommends[] = redirect
; Information added by drupal.org packaging script on 2012-08-09
version = "7.x-1.2"
; Information added by Drupal.org packaging script on 2015-10-07
version = "7.x-1.3"
core = "7.x"
project = "pathauto"
datestamp = "1344525185"
datestamp = "1444232655"
@@ -7,19 +7,59 @@
* @ingroup pathauto
*/
/**
* Implements hook_schema().
*/
function pathauto_schema() {
$schema['pathauto_state'] = array(
'description' => 'The status of each entity alias (whether it was automatically generated or not).',
'fields' => array(
'entity_type' => array(
'type' => 'varchar',
'length' => 32,
'not null' => TRUE,
'description' => 'An entity type.',
),
'entity_id' => array(
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'description' => 'An entity ID.',
),
'pathauto' => array(
'type' => 'int',
'size' => 'tiny',
'not null' => TRUE,
'default' => 0,
'description' => 'The automatic alias status of the entity.',
),
),
'primary key' => array('entity_type', 'entity_id'),
);
return $schema;
}
/**
* Implements hook_install().
*/
function pathauto_install() {
// Set some default variables necessary for the module to perform.
variable_set('pathauto_node_pattern', 'content/[node:title]');
variable_set('pathauto_taxonomy_term_pattern', '[term:vocabulary]/[term:name]');
variable_set('pathauto_forum_pattern', '[term:vocabulary]/[term:name]');
variable_set('pathauto_user_pattern', 'users/[user:name]');
variable_set('pathauto_blog_pattern', 'blogs/[user:name]');
// Set the default separator character to replace instead of remove (default).
variable_set('pathauto_punctuation_hyphen', 1);
$defaults = array(
'pathauto_node_pattern' => 'content/[node:title]',
'pathauto_taxonomy_term_pattern' => '[term:vocabulary]/[term:name]',
'pathauto_forum_pattern' => '[term:vocabulary]/[term:name]',
'pathauto_user_pattern' => 'users/[user:name]',
'pathauto_blog_pattern' => 'blogs/[user:name]',
// Set hyphen character to replace instead of remove.
'pathauto_punctuation_hyphen' => 1,
);
foreach ($defaults as $variable => $default) {
if (variable_get($variable) === NULL) {
variable_set($variable, $default);
}
}
// Set the weight to 1
db_update('system')
@@ -38,6 +78,23 @@ function pathauto_uninstall() {
cache_clear_all('variables', 'cache');
}
/**
* Implements hook_requirements().
*/
function pathauto_requirements($phase) {
$requirements = array();
$t = get_t();
if ($phase == 'runtime' && module_exists('pathauto_persist')) {
$requirements['pathauto'] = array(
'title' => $t('Pathauto Persist'),
'value' => $t('Enabled'),
'description' => $t('Pathauto Persist is installed and enabled. As Pathauto Persist has been merged into Pathauto, the Pathauto Persist module can be safely disabled and removed. All Pathauto Persist settings have been migrated to the Pathauto implementation.'),
'severity' => REQUIREMENT_INFO,
);
}
return $requirements;
}
/**
* Remove the unsupported user/%/contact and user/%/tracker pattern variables.
*/
@@ -168,6 +225,55 @@ function pathauto_update_7005() {
return 'Your Pathauto taxonomy and forum patterns have been corrected. You may wish to regenerate your taxonomy and forum term URL aliases.';
}
/**
* Create pathauto_state table, using data from pathauto_persist if it exists.
*/
function pathauto_update_7006() {
if (!db_table_exists('pathauto_state')) {
$schema['pathauto_state'] = array(
'description' => 'The status of each entity alias (whether it was automatically generated or not).',
'fields' => array(
'entity_type' => array(
'type' => 'varchar',
'length' => 32,
'not null' => TRUE,
'description' => 'The entity type.',
),
'entity_id' => array(
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'description' => 'The entity ID.',
),
'pathauto' => array(
'type' => 'int',
'size' => 'tiny',
'not null' => TRUE,
'default' => 0,
'description' => 'The automatic alias status of the entity.',
),
),
'primary key' => array('entity_type', 'entity_id'),
);
if (db_table_exists('pathauto_persist')) {
// Rename pathauto_persist's table, then create a new empty one just so
// that we can cleanly disable that module.
db_rename_table('pathauto_persist', 'pathauto_state');
db_create_table('pathauto_persist', $schema['pathauto_state']);
// Disable the module and inform the user.
if (module_exists('pathauto_persist')) {
module_disable(array('pathauto_persist'));
}
return t('The Pathauto Persist module and all of its data has been merged into Pathauto. The Pathauto Persist module has been disabled and can be safely uninstalled.');
}
else {
db_create_table('pathauto_state', $schema['pathauto_state']);
}
}
}
/**
* Build a list of Drupal 6 tokens and their Drupal 7 token names.
*/
@@ -3,13 +3,13 @@
Drupal.behaviors.pathFieldsetSummaries = {
attach: function (context) {
$('fieldset.path-form', context).drupalSetSummary(function (context) {
var path = $('.form-item-path-alias input').val();
var automatic = $('.form-item-path-pathauto input').attr('checked');
var path = $('.form-item-path-alias input', context).val();
var automatic = $('.form-item-path-pathauto input', context).attr('checked');
if (automatic) {
return Drupal.t('Automatic alias');
}
if (path) {
else if (path) {
return Drupal.t('Alias: @alias', { '@alias': path });
}
else {
@@ -0,0 +1,56 @@
<?php
/**
* @file
* Support for the Pathauto module.
*/
/**
* Field handler.
*/
class PathautoMigrationHandler extends MigrateDestinationHandler {
public function __construct() {
$this->registerTypes(array('entity'));
}
/**
* Make the destination field visible.
*/
public function fields() {
return array(
'pathauto' => t('Pathauto: Perform aliasing (set to 0 to prevent alias generation during migration'),
);
}
public function prepare($entity, stdClass $row) {
if (isset($entity->pathauto)) {
if (!isset($entity->path)) {
$entity->path = array();
}
elseif (is_string($entity->path)) {
// If MigratePathEntityHandler->prepare() hasn't run yet, support
// the alias (set as $entity->path as a string) being formatted properly
// in the path alias array.
$path = $entity->path;
$entity->path = array();
$entity->path['alias'] = $path;
}
$entity->path['pathauto'] = $entity->pathauto;
if (!isset($entity->path['alias'])) {
$entity->path['alias'] = '';
}
unset($entity->pathauto);
}
}
}
/*
* Implementation of hook_migrate_api().
*/
function pathauto_migrate_api() {
$api = array(
'api' => 2,
'destination handlers' => array('PathautoMigrationHandler'),
);
return $api;
}
@@ -26,30 +26,14 @@ define('PATHAUTO_IGNORE_WORDS', 'a, an, as, at, before, but, by, for, from, is,
* Implements hook_hook_info().
*/
function pathauto_hook_info() {
$info['pathauto'] = array('group' => 'pathauto');
$info['path_alias_types'] = array('group' => 'pathauto');
return $info;
}
/**
* Implements hook_module_implements_alter().
*
* Adds pathauto support for core modules.
*/
function pathauto_module_implements_alter(&$implementations, $hook) {
$hooks = pathauto_hook_info();
if (isset($hooks[$hook])) {
$modules = array('node', 'taxonomy', 'user', 'forum', 'blog');
foreach ($modules as $module) {
if (module_exists($module)) {
$implementations[$module] = TRUE;
}
}
// Move pathauto.module to get included first since it is responsible for
// other modules.
unset($implementations['pathauto']);
$implementations = array_merge(array('pathauto' => 'pathauto'), $implementations);
}
$hooks = array(
'pathauto',
'path_alias_types',
'pathauto_pattern_alter',
'pathauto_alias_alter',
'pathauto_is_alias_reserved',
);
return array_fill_keys($hooks, array('group' => 'pathauto'));
}
/**
@@ -67,6 +51,9 @@ function pathauto_help($path, $arg) {
$output .= '<dd>' . t('The <strong>maximum alias length</strong> and <strong>maximum component length</strong> values default to 100 and have a limit of @max from Pathauto. This length is limited by the length of the "alias" column of the url_alias database table. The default database schema for this column is @max. If you set a length that is equal to that of the one set in the "alias" column it will cause problems in situations where the system needs to append additional words to the aliased URL. You should enter a value that is the length of the "alias" column minus the length of any strings that might get added to the end of the URL. The length of strings that might get added to the end of your URLs depends on which modules you have enabled and on your Pathauto settings. The recommended and default value is 100.', array('@max' => _pathauto_get_schema_alias_maxlength())) . '</dd>';
$output .= '</dl>';
return $output;
case 'admin/config/search/path/update_bulk':
$output = '<p>' . t('Bulk generation will only generate URL aliases for items that currently have no aliases. This is typically used when installing Pathauto on a site that has existing un-aliased content that needs to be aliased in bulk.') . '</p>';
return $output;
}
}
@@ -109,7 +96,7 @@ function pathauto_menu() {
'file' => 'pathauto.admin.inc',
);
$items['admin/config/search/path/update_bulk'] = array(
'title' => 'Bulk update',
'title' => 'Bulk generate',
'page callback' => 'drupal_get_form',
'page arguments' => array('pathauto_bulk_update_form'),
'access arguments' => array('administer url aliases'),
@@ -295,9 +282,19 @@ function pathauto_field_attach_form($entity_type, $entity, &$form, &$form_state,
if (!empty($id)) {
module_load_include('inc', 'pathauto');
$uri = entity_uri($entity_type, $entity);
$path = drupal_get_path_alias($uri['path'], $langcode);
$pathauto_alias = pathauto_create_alias($entity_type, 'return', $uri['path'], array($entity_type => $entity), $bundle, $langcode);
$entity->path['pathauto'] = ($path != $uri['path'] && $path == $pathauto_alias);
if ($pathauto_alias === FALSE) {
// If Pathauto is not going to be able to generate an alias, then we
// should not bother to show the checkbox since it wouldn't do anything.
// Note that if a pattern does apply, but all the tokens currently
// evaluate to empty strings, then $pathauto_alias would equal null and
// not false.
return;
}
else {
$path = drupal_get_path_alias($uri['path'], $langcode);
$entity->path['pathauto'] = ($path != $uri['path'] && $path == $pathauto_alias);
}
}
else {
$entity->path['pathauto'] = TRUE;
@@ -341,10 +338,54 @@ function pathauto_field_attach_form($entity_type, $entity, &$form, &$form_state,
}
}
/**
* Implements hook_entity_load().
*/
function pathauto_entity_load($entities, $entity_type) {
// Statically cache which entity types have data in the pathauto_state
// table to avoid unnecessary queries for entities that would not have any
// data anyway.
static $loadable_types;
if (!isset($loadable_types)) {
$loadable_types = &drupal_static(__FUNCTION__);
if (!isset($loadable_types)) {
// Prevent errors if pathauto_update_7006() has not yet been run.
if (!db_table_exists('pathauto_state')) {
$loadable_types = array();
}
else {
$loadable_types = db_query("SELECT DISTINCT entity_type FROM {pathauto_state}")->fetchCol();
}
}
}
// Check if this entity type has loadable records.
if (!in_array($entity_type, $loadable_types)) {
return;
}
$states = pathauto_entity_state_load_multiple($entity_type, array_keys($entities));
foreach ($states as $id => $state) {
if (!isset($entities[$id]->path)) {
$entities[$id]->path = array();
}
if (is_array($entities[$id]->path) && !isset($entities[$id]->path['pathauto'])) {
$entities[$id]->path['pathauto'] = $state;
}
}
}
/**
* Implements hook_entity_presave().
*/
function pathauto_entity_presave($entity, $type) {
function pathauto_entity_presave($entity, $entity_type) {
if (isset($entity->path['pathauto']) && is_array($entity->path)) {
// We must set an empty alias string for the path to prevent saving an
// alias.
$entity->path += array('alias' => '');
}
// About to be saved (before insert/update)
if (!empty($entity->path['pathauto']) && isset($entity->path['old_alias'])
&& $entity->path['alias'] == '' && $entity->path['old_alias'] != '') {
@@ -366,6 +407,109 @@ function pathauto_entity_presave($entity, $type) {
}
}
/**
* Implements hook_entity_insert().
*/
function pathauto_entity_insert($entity, $entity_type) {
if (isset($entity->path['pathauto'])) {
pathauto_entity_state_save($entity_type, $entity, $entity->path['pathauto']);
}
}
/**
* Implements hook_entity_update().
*/
function pathauto_entity_update($entity, $entity_type) {
if (isset($entity->path['pathauto'])) {
pathauto_entity_state_save($entity_type, $entity, $entity->path['pathauto']);
}
}
/**
* Implements hook_entity_delete().
*/
function pathauto_entity_delete($entity, $entity_type) {
if (isset($entity->path['pathauto'])) {
pathauto_entity_state_delete($entity_type, $entity);
}
}
/**
* Load a pathauto state for an entity.
*
* @param string $entity_type
* An entity type.
* @param int $entity_id
* An entity ID.
*
* @return bool
* A value that evaluates to TRUE if Pathauto should control this entity's
* path. A value that evaluates to FALSE if Pathauto should not manage the
* entity's path.
*/
function pathauto_entity_state_load($entity_type, $entity_id) {
$pathauto_state = pathauto_entity_state_load_multiple($entity_type, array($entity_id));
return !empty($pathauto_state) ? reset($pathauto_state) : FALSE;
}
/**
* Load a pathauto state for multiple entities.
*
* @param string $entity_type
* The entity type.
* @param int[] $entity_ids
* The array of entity IDs.
*
* @return bool[]
* An array of Pathauto states keyed by entity ID.
*/
function pathauto_entity_state_load_multiple($entity_type, $entity_ids) {
return db_query("SELECT entity_id, pathauto FROM {pathauto_state} WHERE entity_type = :entity_type AND entity_id IN (:entity_ids)", array(':entity_type' => $entity_type, ':entity_ids' => $entity_ids))->fetchAllKeyed();
}
/**
* Save the pathauto state for an entity.
*
* @param string $entity_type
* The entity type.
* @param object $entity
* The entity object.
* @param bool $pathauto_state
* A value that evaluates to TRUE means that Pathauto should keep controlling
* this entity's path in the future. A value that evaluates to FALSE means
* that Pathauto should not manage the entity's path.
*/
function pathauto_entity_state_save($entity_type, $entity, $pathauto_state) {
list($entity_id) = entity_extract_ids($entity_type, $entity);
db_merge('pathauto_state')
->key(array(
'entity_type' => $entity_type,
'entity_id' => $entity_id,
))
->fields(array(
'pathauto' => $pathauto_state ? 1 : 0,
))
->execute();
drupal_static_reset('pathauto_entity_load');
}
/**
* Delete the pathauto state for an entity.
*
* @param string $entity_type
* The entity type.
* @param object $entity
* The entity object.
*/
function pathauto_entity_state_delete($entity_type, $entity) {
list($entity_id) = entity_extract_ids($entity_type, $entity);
db_delete('pathauto_state')
->condition('entity_type', $entity_type)
->condition('entity_id', $entity_id)
->execute();
drupal_static_reset('pathauto_entity_load');
}
/**
* Implements hook_action_info().
*/
@@ -374,16 +518,19 @@ function pathauto_action_info() {
'type' => 'node',
'label' => t('Update node alias'),
'configurable' => FALSE,
'triggers' => array(),
);
$info['pathauto_taxonomy_term_update_action'] = array(
'type' => 'taxonomy_term',
'label' => t('Update taxonomy term alias'),
'configurable' => FALSE,
'triggers' => array(),
);
$info['pathauto_user_update_action'] = array(
'type' => 'user',
'label' => t('Update user alias'),
'configurable' => FALSE,
'triggers' => array(),
);
return $info;
@@ -393,7 +540,7 @@ function pathauto_action_info() {
* Returns the language code of the given entity.
*
* Backward compatibility layer to ensure that installations running an older
* version of core where entity_language() is not avilable do not break.
* version of core where entity_language() is not available do not break.
*
* @param string $entity_type
* An entity type.
@@ -417,6 +564,46 @@ function pathauto_entity_language($entity_type, $entity, $check_language_propert
return !empty($langcode) ? $langcode : LANGUAGE_NONE;
}
function pathauto_is_alias_reserved($alias, $source, $langcode = LANGUAGE_NONE) {
foreach (module_implements('pathauto_is_alias_reserved') as $module) {
$result = module_invoke($module, 'pathauto_is_alias_reserved', $alias, $source, $langcode);
if (!empty($result)) {
// As soon as the first module says that an alias is in fact reserved,
// then there is no point in checking the rest of the modules.
return TRUE;
}
}
return FALSE;
}
/**
* Implements hook_pathauto_is_alias_reserved() on behalf of path.module.
*/
function path_pathauto_is_alias_reserved($alias, $source, $langcode) {
// For language neutral content, we need to make sure the alias doesn't
// collide with any existing aliases. For localized content, just make sure
// it doesn't collide with same language or language neutral aliases.
$query = db_select('url_alias', 'ua')
->fields('ua', array('pid'))
->condition('source', $source, '<>')
->condition('alias', $alias);
if ($langcode != LANGUAGE_NONE) {
$query->condition('language', array($langcode, LANGUAGE_NONE), 'IN');
}
return $query->execute()->rowCount() > 0;
}
/**
* Implements hook_pathauto_is_alias_reserved().
*/
function pathauto_pathauto_is_alias_reserved($alias, $source, $langcode) {
module_load_include('inc', 'pathauto');
return _pathauto_path_is_callback($alias);
}
if (!function_exists('path_field_extra_fields')) {
/**
* Implements hook_field_extra_fields() on behalf of path.module.
@@ -459,6 +646,47 @@ function path_field_extra_fields() {
* @{
*/
/**
* Implements hook_path_alias_types() on behalf of node module.
*/
function node_path_alias_types() {
return array('node/' => t('Content'));
}
/**
* Implements hook_pathauto() on behalf of node module.
*/
function node_pathauto($op) {
if ($op == 'settings') {
$settings = array();
$settings['module'] = 'node';
$settings['token_type'] = 'node';
$settings['groupheader'] = t('Content paths');
$settings['patterndescr'] = t('Default path pattern (applies to all content types with blank patterns below)');
$settings['patterndefault'] = 'content/[node:title]';
$settings['batch_update_callback'] = 'node_pathauto_bulk_update_batch_process';
$settings['batch_file'] = drupal_get_path('module', 'pathauto') . '/pathauto.pathauto.inc';
$languages = array();
if (module_exists('locale')) {
$languages = array(LANGUAGE_NONE => t('language neutral')) + locale_language_list('name');
}
foreach (node_type_get_names() as $node_type => $node_name) {
if (count($languages) && variable_get('language_content_type_' . $node_type, 0)) {
$settings['patternitems'][$node_type] = t('Default path pattern for @node_type (applies to all @node_type content types with blank patterns below)', array('@node_type' => $node_name));
foreach ($languages as $lang_code => $lang_name) {
$settings['patternitems'][$node_type . '_' . $lang_code] = t('Pattern for all @language @node_type paths', array('@node_type' => $node_name, '@language' => $lang_name));
}
}
else {
$settings['patternitems'][$node_type] = t('Pattern for all @node_type paths', array('@node_type' => $node_name));
}
}
return (object) $settings;
}
}
/**
* Implements hook_node_insert().
*/
@@ -517,20 +745,20 @@ function pathauto_node_operations() {
*/
function pathauto_node_update_alias(stdClass $node, $op, array $options = array()) {
// Skip processing if the user has disabled pathauto for the node.
if (isset($node->path['pathauto']) && empty($node->path['pathauto'])) {
return;
if (isset($node->path['pathauto']) && empty($node->path['pathauto']) && empty($options['force'])) {
return FALSE;
}
$options += array('language' => pathauto_entity_language('node', $node));
// Skip processing if the node has no pattern.
if (!pathauto_pattern_load_by_entity('node', $node->type, $options['language'])) {
return;
return FALSE;
}
module_load_include('inc', 'pathauto');
$uri = entity_uri('node', $node);
pathauto_create_alias('node', $op, $uri['path'], array('node' => $node), $node->type, $options['language']);
return pathauto_create_alias('node', $op, $uri['path'], array('node' => $node), $node->type, $options['language']);
}
/**
@@ -573,6 +801,42 @@ function pathauto_node_update_action($node, $context = array()) {
* @{
*/
/**
* Implements hook_path_alias_types() on behalf of taxonomy module.
*/
function taxonomy_path_alias_types() {
return array('taxonomy/term/' => t('Taxonomy terms'));
}
/**
* Implements hook_pathauto() on behalf of taxonomy module.
*/
function taxonomy_pathauto($op) {
if ($op == 'settings') {
$settings = array();
$settings['module'] = 'taxonomy_term';
$settings['token_type'] = 'term';
$settings['groupheader'] = t('Taxonomy term paths');
$settings['patterndescr'] = t('Default path pattern (applies to all vocabularies with blank patterns below)');
$settings['patterndefault'] = '[term:vocabulary]/[term:name]';
$settings['batch_update_callback'] = 'taxonomy_pathauto_bulk_update_batch_process';
$settings['batch_file'] = drupal_get_path('module', 'pathauto') . '/pathauto.pathauto.inc';
$vocabularies = taxonomy_get_vocabularies();
if (count($vocabularies)) {
$settings['patternitems'] = array();
foreach ($vocabularies as $vid => $vocabulary) {
if ($vid == variable_get('forum_nav_vocabulary', '')) {
// Skip the forum vocabulary.
continue;
}
$settings['patternitems'][$vocabulary->machine_name] = t('Pattern for all %vocab-name paths', array('%vocab-name' => $vocabulary->name));
}
}
return (object) $settings;
}
}
/**
* Implements hook_taxonomy_term_insert().
*/
@@ -617,8 +881,8 @@ function pathauto_form_taxonomy_form_term_alter(&$form, $form_state) {
*/
function pathauto_taxonomy_term_update_alias(stdClass $term, $op, array $options = array()) {
// Skip processing if the user has disabled pathauto for the term.
if (isset($term->path['pathauto']) && empty($term->path['pathauto'])) {
return;
if (isset($term->path['pathauto']) && empty($term->path['pathauto']) && empty($options['force'])) {
return FALSE;
}
$module = 'taxonomy_term';
@@ -627,7 +891,7 @@ function pathauto_taxonomy_term_update_alias(stdClass $term, $op, array $options
$module = 'forum';
}
else {
return;
return FALSE;
}
}
@@ -644,21 +908,22 @@ function pathauto_taxonomy_term_update_alias(stdClass $term, $op, array $options
// Skip processing if the term has no pattern.
if (!pathauto_pattern_load_by_entity($module, $term->vocabulary_machine_name)) {
return;
return FALSE;
}
module_load_include('inc', 'pathauto');
$uri = entity_uri('taxonomy_term', $term);
pathauto_create_alias($module, $op, $uri['path'], array('term' => $term), $term->vocabulary_machine_name, $options['language']);
$result = pathauto_create_alias($module, $op, $uri['path'], array('term' => $term), $term->vocabulary_machine_name, $options['language']);
if (!empty($options['alias children'])) {
// For all children generate new aliases.
$options['alias children'] = FALSE;
unset($options['language']);
foreach (taxonomy_get_tree($term->vid, $term->tid) as $subterm) {
foreach (taxonomy_get_children($term->tid, $term->vid) as $subterm) {
pathauto_taxonomy_term_update_alias($subterm, $op, $options);
}
}
return $result;
}
/**
@@ -696,11 +961,68 @@ function pathauto_taxonomy_term_update_action($term, $context = array()) {
* @} End of "name pathauto_taxonomy".
*/
/**
* @name pathauto_forum Pathauto integration for the core forum module.
* @{
*/
/**
* Implements hook_path_alias_types() on behalf of forum module.
*/
function forum_path_alias_types() {
return array('forum/' => t('Forums'));
}
/**
* Implements hook_pathauto() for forum module.
*/
function forum_pathauto($op) {
if ($op == 'settings') {
$settings = array();
$settings['module'] = 'forum';
$settings['token_type'] = 'term';
$settings['groupheader'] = t('Forum paths');
$settings['patterndescr'] = t('Pattern for forums and forum containers');
$settings['patterndefault'] = '[term:vocabulary]/[term:name]';
$settings['batch_update_callback'] = 'forum_pathauto_bulk_update_batch_process';
$settings['batch_file'] = drupal_get_path('module', 'pathauto') . '/pathauto.pathauto.inc';
return (object) $settings;
}
}
/**
* @} End of "name pathauto_forum".
*/
/**
* @name pathauto_user Pathauto integration for the core user and blog modules.
* @{
*/
/**
* Implements hook_path_alias_types() on behalf of user module.
*/
function user_path_alias_types() {
return array('user/' => t('Users'));
}
/**
* Implements hook_pathauto() on behalf of user module.
*/
function user_pathauto($op) {
if ($op == 'settings') {
$settings = array();
$settings['module'] = 'user';
$settings['token_type'] = 'user';
$settings['groupheader'] = t('User paths');
$settings['patterndescr'] = t('Pattern for user account page paths');
$settings['patterndefault'] = 'users/[user:name]';
$settings['batch_update_callback'] = 'user_pathauto_bulk_update_batch_process';
$settings['batch_file'] = drupal_get_path('module', 'pathauto') . '/pathauto.pathauto.inc';
return (object) $settings;
}
}
/**
* Implements hook_user_insert().
*/
@@ -748,8 +1070,8 @@ function pathauto_user_operations() {
*/
function pathauto_user_update_alias(stdClass $account, $op, array $options = array()) {
// Skip processing if the user has disabled pathauto for the account.
if (isset($account->path['pathauto']) && empty($account->path['pathauto'])) {
return;
if (isset($account->path['pathauto']) && empty($account->path['pathauto']) && empty($options['force'])) {
return FALSE;
}
$options += array(
@@ -761,17 +1083,19 @@ function pathauto_user_update_alias(stdClass $account, $op, array $options = arr
// Skip processing if the account has no pattern.
if (!pathauto_pattern_load_by_entity('user', '', $options['language'])) {
return;
return FALSE;
}
module_load_include('inc', 'pathauto');
$uri = entity_uri('user', $account);
pathauto_create_alias('user', $op, $uri['path'], array('user' => $account), NULL, $options['language']);
$return = pathauto_create_alias('user', $op, $uri['path'], array('user' => $account), NULL, $options['language']);
// Because blogs are also associated with users, also generate the blog paths.
if (!empty($options['alias blog'])) {
pathauto_blog_update_alias($account, $op, $options);
}
return $return;
}
/**
@@ -805,6 +1129,39 @@ function pathauto_user_update_action($account, $context = array()) {
pathauto_user_update_alias($account, 'bulkupdate', array('message' => TRUE));
}
/**
* @} End of "name pathauto_user".
*/
/**
* @name pathauto_blog Pathauto integration for the core blog module.
* @{
*/
/**
* Implements hook_path_alias_types() on behalf of blog module.
*/
function blog_path_alias_types() {
return array('blog/' => t('User blogs'));
}
/**
* Implements hook_pathauto() on behalf of blog module.
*/
function blog_pathauto($op) {
if ($op == 'settings') {
$settings = array();
$settings['module'] = 'blog';
$settings['token_type'] = 'user';
$settings['groupheader'] = t('Blog paths');
$settings['patterndescr'] = t('Pattern for blog page paths');
$settings['patterndefault'] = 'blogs/[user:name]';
$settings['batch_update_callback'] = 'blog_pathauto_bulk_update_batch_process';
$settings['batch_file'] = drupal_get_path('module', 'pathauto') . '/pathauto.pathauto.inc';
return (object) $settings;
}
}
/**
* Update the blog URL aliases for an individual user account.
*
@@ -819,7 +1176,7 @@ function pathauto_user_update_action($account, $context = array()) {
function pathauto_blog_update_alias(stdClass $account, $op, array $options = array()) {
// Skip processing if the blog has no pattern.
if (!pathauto_pattern_load_by_entity('blog')) {
return;
return FALSE;
}
$options += array(
@@ -828,7 +1185,7 @@ function pathauto_blog_update_alias(stdClass $account, $op, array $options = arr
module_load_include('inc', 'pathauto');
if (node_access('create', 'blog', $account)) {
pathauto_create_alias('blog', $op, "blog/{$account->uid}", array('user' => $account), NULL, $options['language']);
return pathauto_create_alias('blog', $op, "blog/{$account->uid}", array('user' => $account), NULL, $options['language']);
}
else {
pathauto_path_delete_all("blog/{$account->uid}");
@@ -836,5 +1193,30 @@ function pathauto_blog_update_alias(stdClass $account, $op, array $options = arr
}
/**
* @} End of "name pathauto_user".
* @} End of "name pathauto_blog".
*/
/**
* Implements hook_features_pipe_COMPONENT_alter().
*/
function pathauto_features_pipe_node_alter(&$pipe, $data, $export) {
foreach ($data as $node_type) {
$pipe['variable'][] = "pathauto_node_{$node_type}_pattern";
if (module_exists('locale')) {
$langcodes = array_keys(locale_language_list('name'));
$langcodes[] = LANGUAGE_NONE;
foreach ($langcodes as $langcode) {
$pipe['variable'][] = "pathauto_node_{$node_type}_{$langcode}_pattern";
}
}
}
}
/**
* Implements hook_features_pipe_COMPONENT_alter().
*/
function pathauto_features_pipe_taxonomy_alter(&$pipe, $data, $export) {
foreach ($data as $vocabulary) {
$pipe['variable'][] = "pathauto_taxonomy_term_{$vocabulary}_pattern";
}
}
@@ -7,79 +7,6 @@
* @ingroup pathauto
*/
/**
* Implements hook_path_alias_types().
*
* Used primarily by the bulk delete form.
*/
function pathauto_path_alias_types() {
$objects['user/'] = t('Users');
$objects['node/'] = t('Content');
if (module_exists('blog')) {
$objects['blog/'] = t('User blogs');
}
if (module_exists('taxonomy')) {
$objects['taxonomy/term/'] = t('Taxonomy terms');
}
if (module_exists('forum')) {
$objects['forum/'] = t('Forums');
}
return $objects;
}
/**
* Implements hook_pathauto().
*
* This function is empty so that the other core module implementations can be
* defined in this file. This is because in pathauto_module_implements_alter()
* we add pathauto to be included first. The module system then peforms a
* check on any subsequent run if this function still exists. If this does not
* exist, than this file will not get included and the core implementations
* will never get run.
*
* @see pathauto_module_implements_alter().
*/
function pathauto_pathauto() {
// Empty hook; see the above comment.
}
/**
* Implements hook_pathauto().
*/
function node_pathauto($op) {
switch ($op) {
case 'settings':
$settings = array();
$settings['module'] = 'node';
$settings['token_type'] = 'node';
$settings['groupheader'] = t('Content paths');
$settings['patterndescr'] = t('Default path pattern (applies to all content types with blank patterns below)');
$settings['patterndefault'] = 'content/[node:title]';
$settings['batch_update_callback'] = 'node_pathauto_bulk_update_batch_process';
$settings['batch_file'] = drupal_get_path('module', 'pathauto') . '/pathauto.pathauto.inc';
$languages = array();
if (module_exists('locale')) {
$languages = array(LANGUAGE_NONE => t('language neutral')) + locale_language_list('name');
}
foreach (node_type_get_names() as $node_type => $node_name) {
if (count($languages) && variable_get('language_content_type_' . $node_type, 0)) {
$settings['patternitems'][$node_type] = t('Default path pattern for @node_type (applies to all @node_type content types with blank patterns below)', array('@node_type' => $node_name));
foreach ($languages as $lang_code => $lang_name) {
$settings['patternitems'][$node_type . '_' . $lang_code] = t('Pattern for all @language @node_type paths', array('@node_type' => $node_name, '@language' => $lang_name));
}
}
else {
$settings['patternitems'][$node_type] = t('Pattern for all @node_type paths', array('@node_type' => $node_name));
}
}
return (object) $settings;
default:
break;
}
}
/**
* Batch processing callback; Generate aliases for nodes.
*/
@@ -122,38 +49,6 @@ function node_pathauto_bulk_update_batch_process(&$context) {
}
}
/**
* Implements hook_pathauto().
*/
function taxonomy_pathauto($op) {
switch ($op) {
case 'settings':
$settings = array();
$settings['module'] = 'taxonomy_term';
$settings['token_type'] = 'term';
$settings['groupheader'] = t('Taxonomy term paths');
$settings['patterndescr'] = t('Default path pattern (applies to all vocabularies with blank patterns below)');
$settings['patterndefault'] = '[term:vocabulary]/[term:name]';
$settings['batch_update_callback'] = 'taxonomy_pathauto_bulk_update_batch_process';
$settings['batch_file'] = drupal_get_path('module', 'pathauto') . '/pathauto.pathauto.inc';
$vocabularies = taxonomy_get_vocabularies();
if (count($vocabularies)) {
$settings['patternitems'] = array();
foreach ($vocabularies as $vid => $vocabulary) {
if ($vid == variable_get('forum_nav_vocabulary', '')) {
// Skip the forum vocabulary.
continue;
}
$settings['patternitems'][$vocabulary->machine_name] = t('Pattern for all %vocab-name paths', array('%vocab-name' => $vocabulary->name));
}
}
return (object) $settings;
default:
break;
}
}
/**
* Batch processing callback; Generate aliases for taxonomy terms.
*/
@@ -200,26 +95,6 @@ function taxonomy_pathauto_bulk_update_batch_process(&$context) {
}
}
/**
* Implements hook_pathauto() for forum module.
*/
function forum_pathauto($op) {
switch ($op) {
case 'settings':
$settings = array();
$settings['module'] = 'forum';
$settings['token_type'] = 'term';
$settings['groupheader'] = t('Forum paths');
$settings['patterndescr'] = t('Pattern for forums and forum containers');
$settings['patterndefault'] = '[term:vocabulary]/[term:name]';
$settings['batch_update_callback'] = 'forum_pathauto_bulk_update_batch_process';
$settings['batch_file'] = drupal_get_path('module', 'pathauto') . '/pathauto.pathauto.inc';
return (object) $settings;
default:
break;
}
}
/**
* Batch processing callback; Generate aliases for forums.
*/
@@ -263,26 +138,6 @@ function forum_pathauto_bulk_update_batch_process(&$context) {
}
}
/**
* Implements hook_pathauto().
*/
function user_pathauto($op) {
switch ($op) {
case 'settings':
$settings = array();
$settings['module'] = 'user';
$settings['token_type'] = 'user';
$settings['groupheader'] = t('User paths');
$settings['patterndescr'] = t('Pattern for user account page paths');
$settings['patterndefault'] = 'users/[user:name]';
$settings['batch_update_callback'] = 'user_pathauto_bulk_update_batch_process';
$settings['batch_file'] = drupal_get_path('module', 'pathauto') . '/pathauto.pathauto.inc';
return (object) $settings;
default:
break;
}
}
/**
* Batch processing callback; Generate aliases for users.
*/
@@ -325,26 +180,6 @@ function user_pathauto_bulk_update_batch_process(&$context) {
}
}
/**
* Implements hook_pathauto().
*/
function blog_pathauto($op) {
switch ($op) {
case 'settings':
$settings = array();
$settings['module'] = 'blog';
$settings['token_type'] = 'user';
$settings['groupheader'] = t('Blog paths');
$settings['patterndescr'] = t('Pattern for blog page paths');
$settings['patterndefault'] = 'blogs/[user:name]';
$settings['batch_update_callback'] = 'blog_pathauto_bulk_update_batch_process';
$settings['batch_file'] = drupal_get_path('module', 'pathauto') . '/pathauto.pathauto.inc';
return (object) $settings;
default:
break;
}
}
/**
* Batch processing callback; Generate aliases for blogs.
*/
@@ -55,9 +55,13 @@ class PathautoTestHelper extends DrupalWebTestCase {
$this->assertEntityAlias($entity_type, $entity, $uri['path'], $language);
}
function assertNoEntityAliasExists($entity_type, $entity) {
function assertNoEntityAliasExists($entity_type, $entity, $alias = NULL) {
$uri = entity_uri($entity_type, $entity);
$this->assertNoAliasExists(array('source' => $uri['path']));
$path = array('source' => $uri['path']);
if (!empty($alias)) {
$path['alias'] = $alias;
}
$this->assertNoAliasExists($path);
}
function assertAlias($source, $expected_alias, $language = LANGUAGE_NONE) {
@@ -192,6 +196,23 @@ class PathautoUnitTestCase extends PathautoTestHelper {
}
}
/**
* Test pathauto_clean_alias().
*/
function testCleanAlias() {
$tests = array();
$tests['one/two/three'] = 'one/two/three';
$tests['/one/two/three/'] = 'one/two/three';
$tests['one//two///three'] = 'one/two/three';
$tests['one/two--three/-/--/-/--/four---five'] = 'one/two-three/four-five';
$tests['one/-//three--/four'] = 'one/three/four';
foreach ($tests as $input => $expected) {
$output = pathauto_clean_alias($input);
$this->assertEqual($output, $expected, t("pathauto_clean_alias('@input') expected '@expected', actual '@output'", array('@input' => $input, '@expected' => $expected, '@output' => $output)));
}
}
/**
* Test pathauto_path_delete_multiple().
*/
@@ -244,7 +265,7 @@ class PathautoUnitTestCase extends PathautoTestHelper {
$node->title = 'Fifth title';
pathauto_node_update($node);
$this->assertEntityAlias('node', $node, 'content/fourth-title');
$this->assertNoAliasExists(array('alias' => 'content/fith-title'));
$this->assertNoAliasExists(array('alias' => 'content/fifth-title'));
// Test PATHAUTO_UPDATE_ACTION_NO_NEW with unaliased node and 'update'.
$this->deleteAllAliases();
@@ -289,6 +310,40 @@ class PathautoUnitTestCase extends PathautoTestHelper {
$this->assertEntityAlias('taxonomy_term', $term2, 'My Crazy/Alias/child-term');
}
/**
* Test using fields for path structures.
*/
function testParentChildPathTokens() {
// First create a field which will be used to create the path. It must
// begin with a letter.
$fieldname = 'a' . drupal_strtolower($this->randomName());
field_create_field(array('field_name' => $fieldname, 'type' => 'text'));
field_create_instance(array('field_name' => $fieldname, 'entity_type' => 'taxonomy_term', 'bundle' => 'tags'));
// Make the path pattern of a field use the value of this field appended
// to the parent taxonomy term's pattern if there is one.
variable_set('pathauto_taxonomy_term_tags_pattern', '[term:parents:join-path]/[term:' . $fieldname . ']');
// Start by creating a parent term.
$parent = new stdClass();
$parent->$fieldname = array(LANGUAGE_NONE => array(array('value' => $parent->name = $this->randomName())));
$parent->vid = 1;
taxonomy_term_save($parent);
// Create the child term.
$child = new stdClass();
$child->name = $this->randomName();
$child->$fieldname = array(LANGUAGE_NONE => array(array('value' => $child->name = $this->randomName())));
$child->vid = 1;
$child->parent = $parent->tid;
taxonomy_term_save($child);
$this->assertEntityAlias('taxonomy_term', $child, drupal_strtolower($parent->name . '/' . $child->name));
// Re-saving the parent term should not modify the child term's alias.
taxonomy_term_save($parent);
$this->assertEntityAlias('taxonomy_term', $child, drupal_strtolower($parent->name . '/' . $child->name));
}
function testEntityBundleRenamingDeleting() {
// Create a vocabulary and test that it's pattern variable works.
$vocab = $this->addVocabulary(array('machine_name' => 'old_name'));
@@ -315,17 +370,17 @@ class PathautoUnitTestCase extends PathautoTestHelper {
// Check that Pathauto does not create an alias of '/admin'.
$node = $this->drupalCreateNode(array('title' => 'Admin', 'type' => 'page'));
$this->assertNoEntityAlias('node', $node);
$this->assertEntityAlias('node', $node, 'admin-0');
// Check that Pathauto does not create an alias of '/modules'.
$node->title = 'Modules';
node_save($node);
$this->assertNoEntityAlias('node', $node);
$this->assertEntityAlias('node', $node, 'modules-0');
// Check that Pathauto does not create an alias of '/index.php'.
$node->title = 'index.php';
node_save($node);
$this->assertNoEntityAlias('node', $node);
$this->assertEntityAlias('node', $node, 'index.php-0');
// Check that a safe value gets an automatic alias. This is also a control
// to ensure the above tests work properly.
@@ -333,6 +388,18 @@ class PathautoUnitTestCase extends PathautoTestHelper {
node_save($node);
$this->assertEntityAlias('node', $node, 'safe-value');
}
function testPathAliasUniquifyWordsafe() {
variable_set('pathauto_max_length', 25);
$node_1 = $this->drupalCreateNode(array('title' => 'thequick brownfox jumpedover thelazydog', 'type' => 'page'));
$node_2 = $this->drupalCreateNode(array('title' => 'thequick brownfox jumpedover thelazydog', 'type' => 'page'));
// Check that pathauto_alias_uniquify is calling truncate_utf8 with $wordsafe param set to TRUE.
// If it doesn't path alias result would be content/thequick-brownf-0
$this->assertEntityAlias('node', $node_1, 'content/thequick-brownfox');
$this->assertEntityAlias('node', $node_2, 'content/thequick-0');
}
}
/**
@@ -408,13 +475,23 @@ class PathautoFunctionalTestCase extends PathautoFunctionalTestHelper {
$this->drupalGet($automatic_alias);
$this->assertText($title, 'Node accessible through automatic alias.');
// Disable the update action. The checkbox should not be visible.
variable_set('pathauto_update_action', 0);
$this->drupalGet("node/{$node->nid}/edit");
$this->assertNoFieldById('edit-path-pathauto');
// Reset the update action back to default. The checkbox should be visible.
variable_del('pathauto_update_action');
$this->drupalGet("node/{$node->nid}/edit");
$this->assertFieldChecked('edit-path-pathauto');
// Manually set the node's alias.
$manual_alias = 'content/' . $node->nid;
$edit = array(
'path[pathauto]' => FALSE,
'path[alias]' => $manual_alias,
);
$this->drupalPost("node/{$node->nid}/edit", $edit, t('Save'));
$this->drupalPost(NULL, $edit, t('Save'));
$this->assertText("Basic page $title has been updated.");
// Check that the automatic alias checkbox is now unchecked by default.
@@ -449,6 +526,26 @@ class PathautoFunctionalTestCase extends PathautoFunctionalTestHelper {
$this->assertNoFieldById('edit-path-pathauto');
$this->assertFieldByName('path[alias]', '');
$this->assertNoEntityAlias('node', $node);
// Set the page pattern to use only tokens so we can test the checkbox
// behavior if none of the tokens have a value currently.
variable_set('pathauto_node_page_pattern', '[node:title]');
// Create a node with an empty title. The Pathauto checkbox should still be
// visible but unchecked.
$node = $this->drupalCreateNode(array('type' => 'page', 'title' => ''));
$this->drupalGet('node/' . $node->nid . '/edit');
$this->assertNoFieldChecked('edit-path-pathauto');
$this->assertFieldByName('path[alias]', '');
$this->assertNoEntityAlias('node', $node);
$edit = array();
$edit['title'] = 'Valid title';
$edit['path[pathauto]'] = TRUE;
$this->drupalPost(NULL, $edit, t('Save'));
$this->drupalGet('node/' . $node->nid . '/edit');
$this->assertFieldChecked('edit-path-pathauto');
$this->assertFieldByName('path[alias]', 'valid-title');
}
/**
@@ -472,6 +569,83 @@ class PathautoFunctionalTestCase extends PathautoFunctionalTestHelper {
$this->assertEntityAlias('node', $node2, 'node/' . $node2->nid);
}
/**
* @todo Merge this with existing node test methods?
*/
public function testNodeState() {
$nodeNoAliasUser = $this->drupalCreateUser(array('bypass node access'));
$nodeAliasUser = $this->drupalCreateUser(array('bypass node access', 'create url aliases'));
$node = $this->drupalCreateNode(array(
'title' => 'Node version one',
'type' => 'page',
'path' => array(
'pathauto' => FALSE,
),
));
$this->assertNoEntityAlias('node', $node);
// Set a manual path alias for the node.
$node->path['alias'] = 'test-alias';
node_save($node);
// Ensure that the pathauto field was saved to the database.
$node = node_load($node->nid, NULL, TRUE);
$this->assertFalse($node->path['pathauto']);
// Ensure that the manual path alias was saved and an automatic alias was not generated.
$this->assertEntityAlias('node', $node, 'test-alias');
$this->assertNoEntityAliasExists('node', $node, 'content/node-version-one');
// Save the node as a user who does not have access to path fieldset.
$this->drupalLogin($nodeNoAliasUser);
$this->drupalGet('node/' . $node->nid . '/edit');
$this->assertNoFieldByName('path[pathauto]');
$edit = array('title' => 'Node version two');
$this->drupalPost(NULL, $edit, 'Save');
$this->assertText('Basic page Node version two has been updated.');
$this->assertEntityAlias('node', $node, 'test-alias');
$this->assertNoEntityAliasExists('node', $node, 'content/node-version-one');
$this->assertNoEntityAliasExists('node', $node, 'content/node-version-two');
// Load the edit node page and check that the Pathauto checkbox is unchecked.
$this->drupalLogin($nodeAliasUser);
$this->drupalGet('node/' . $node->nid . '/edit');
$this->assertNoFieldChecked('edit-path-pathauto');
// Edit the manual alias and save the node.
$edit = array(
'title' => 'Node version three',
'path[alias]' => 'manually-edited-alias',
);
$this->drupalPost(NULL, $edit, 'Save');
$this->assertText('Basic page Node version three has been updated.');
$this->assertEntityAlias('node', $node, 'manually-edited-alias');
$this->assertNoEntityAliasExists('node', $node, 'test-alias');
$this->assertNoEntityAliasExists('node', $node, 'content/node-version-one');
$this->assertNoEntityAliasExists('node', $node, 'content/node-version-two');
$this->assertNoEntityAliasExists('node', $node, 'content/node-version-three');
// Programatically save the node with an automatic alias.
$node = node_load($node->nid, NULL, TRUE);
$node->path['pathauto'] = TRUE;
node_save($node);
// Ensure that the pathauto field was saved to the database.
$node = node_load($node->nid, NULL, TRUE);
$this->assertTrue($node->path['pathauto']);
$this->assertEntityAlias('node', $node, 'content/node-version-three');
$this->assertNoEntityAliasExists('node', $node, 'manually-edited-alias');
$this->assertNoEntityAliasExists('node', $node, 'test-alias');
$this->assertNoEntityAliasExists('node', $node, 'content/node-version-one');
$this->assertNoEntityAliasExists('node', $node, 'content/node-version-two');
}
/**
* Basic functional testing of Pathauto with taxonomy terms.
*/
@@ -672,6 +846,14 @@ class PathautoLocaleTestCase extends PathautoFunctionalTestHelper {
$this->assertEntityAlias('node', $node, 'content/english-node-0', 'en');
$this->assertEntityAlias('node', $node, 'french-node', 'fr');
$this->assertAliasExists(array('pid' => $english_alias['pid'], 'alias' => 'content/english-node-0'));
// Create a new node with the same title as before but without
// specifying a language.
$node = $this->drupalCreateNode(array('title' => 'English node'));
// Check that the new node had a unique alias generated with the '-1'
// suffix.
$this->assertEntityAlias('node', $node, 'content/english-node-1');
}
}
@@ -718,11 +900,11 @@ class PathautoBulkUpdateTestCase extends PathautoFunctionalTestHelper {
// Add a new node.
$new_node = $this->drupalCreateNode(array('path' => array('alias' => '', 'pathauto' => FALSE)));
// Run the update again which should only run against the new node.
// Run the update again which should not run against any nodes.
$this->drupalPost('admin/config/search/path/update_bulk', $edit, t('Update'));
$this->assertText('Generated 1 URL alias.'); // 1 node + 0 users
$this->assertText('No new URL aliases to generate.');
$this->assertEntityAliasExists('node', $new_node);
$this->assertNoEntityAliasExists('node', $new_node);
}
}
@@ -35,7 +35,7 @@ function pathauto_tokens($type, $tokens, array $data = array(), array $options =
$values = array();
foreach (element_children($array) as $key) {
$value = is_array($array[$key]) ? render($array[$key]) : (string) $array[$key];
$value = pathauto_cleanstring($value);
$value = pathauto_cleanstring($value, $options);
$values[] = $value;
}
$replacements[$original] = implode('/', $values);
@@ -113,6 +113,20 @@ Production monitor
6. If you wish to fetch the data immediately, check the appropriate box and save
the settings. Good to go!
Cron setup
----------
To automatically check the site status and/or module updates on cron, you will
need to install drush and configure the following tasks in the crontab:
# Check ALL sites for updates, once a day starting at 0100H at night.
0 1 * * * /path/to/drush -r /path/to/docroot prod-monitor-updates -y --quiet
# Fetch ALL site data every five minutes (or whatever you please obviously).
0/5 * * * * /path/to/drush -r /path/to/docroot prod-monitor-fetch -y --quiet
Obviously, the time and frequency of these cron jobs is at your discretion.
Do note that, depending on the number of sites you have configured, the crons
may be running for quite some time, especially the module update checking job!
Upgrading
---------
When upgrading Production monitor to a newer version, always run update.php to
@@ -123,9 +137,9 @@ Nagios
------
1. Download and install the Nagios module from http://drupal.org/project/nagios
as per its readme instructions
2. Enable Nagios support in the prod_check module on /admin/settings/prod-check
2. Enable Nagios support in the prod_check module on /admin/config/system/prod-check
by ticking the appropriate box.
3. Untick the checboxes for those items you do not whish to be monitored by
3. Untick the checkboxes for those items you do not whish to be monitored by
Nagios.
4. Save the settings and you're good to go!
@@ -172,7 +186,7 @@ For Production monitor, these commands are available:
$ drush prod-monitor-fetch [id]
$ drush prod-monitor-flush [id]
$ drush prod-monitor-delete [id]
$ drush prod-monitor-updates [id] (--check)
$ drush prod-monitor-updates [id] (--check, --security-only)
or their aliases:
@@ -180,7 +194,7 @@ or their aliases:
$ drush pmon-fe [id]
$ drush pmon-fl [id]
$ drush pmon-rm [id]
$ drush pmon-up [id] (--check)
$ drush pmon-up [id] (--check, --security-only)
The id parameter is optional for the prod-monitor command. The best usage is to
first get a list of sites:
@@ -200,16 +214,19 @@ You can pass multiple ID's by separating them with spaces:
The prod-monitor-updates command acts on one id only!
APC
---
APC/OPcache
-----------
Production Check complains about APC not being installed or misconfigured. What
is APC you wonder? Well, APC is an opcode caching mechanism that will pre-com-
pile PHP files and keep them stored in memory. The full manual can be found
here: http://php.net/manual/en/book.apc.php .
For Drupal sites, it is important to tune APC in order to achieve maximum per-
formance there. Drupal uses a massive amount of files and therefore you should
assign a proper amount of RAM to APC. For a dedicated setup 64Mb should be
sufficient, in shared setups, you should easily double that!
PHP version 5.5 comes bundled with an alternative to APC named OPcache. The full
manual can be found here: http://php.net/manual/en/book.opcache.php .
For Drupal sites, it is important to tune APC/OPcache in order to achieve
maximum performance there. Drupal uses a massive amount of files and therefore
you should assign a proper amount of RAM to APC/OPcache. For a dedicated setup
64Mb should be sufficient, in shared setups, you will need to multiply that!
To tune your setup, you can use the aforementioned hidden link provided by
Production check. You can see the memory usage there, verify your settings and
much more.
@@ -219,6 +236,7 @@ extension (drupal.org CVS did not seem to accept files with .ini extension?).
Note: This 'hidden link' makes use of the APC supplied PHP code and is subject
to the PHP license: http://www.php.net/license/3_01.txt .
The OPcache variant is taken from https://github.com/rlerdorf/opcache-status .
Updates
@@ -233,8 +251,8 @@ Cron is NOT used to do this, since we want to keep the transfer to a minimum.
Hidden link
===========
Production check adds some 'hidden links' to the site where you can check the
APC, Memcache and DB status of your site. These pages can be found on:
/admin/reports/status/apc
APC/OPcache, Memcache and DB status of your site. These pages can be found on:
/admin/reports/status/apc-opc
/admin/reports/status/memcache
/admin/reports/status/database
@@ -251,8 +269,8 @@ The detailed report page
The page is divided into 4 sections:
- Settings: checks various Drupal settings
- Server: checks that are 'outside of Drupal' such as APC and wether or not you
have removed the release note files from the root.
- Server: checks that are 'outside of Drupal' such as APC/OPcache and wether or
not you have removed the release note files from the root.
- Performance: checks relevant to the performance settings in Drupal such as
page / block caching.
- Modules: checks if certain modules are on / off
@@ -97,8 +97,8 @@ function prod_check_settings_form($form, &$form_state) {
$form['prod_check_apc'] = array(
'#type' => 'fieldset',
'#title' => t('Advanced APC settings'),
'#description' => t('These settings are used in the !link functionality.', prod_check_link_array('advanced APC', 'admin/reports/status/apc')),
'#title' => t('Advanced APC/OPcache settings'),
'#description' => t('These settings are used in the !link functionality.', prod_check_link_array('advanced APC', 'admin/reports/status/apc-opc')),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
);
@@ -106,7 +106,7 @@ function prod_check_settings_form($form, &$form_state) {
// Cache full count threshold
$form['prod_check_apc']['prod_check_apc_expunge'] = array(
'#type' => 'textfield',
'#title' => t('APC cache full count threshold'),
'#title' => t('APC/OPcache cache full count threshold'),
'#default_value' => variable_get('prod_check_apc_expunge', 0),
'#size' => 2,
'#description' => t('Issue a critical error when the cache full count is greater than the number entered here.'),
@@ -293,7 +293,7 @@ function prod_check_settings_form($form, &$form_state) {
'!settings' => l(t('Nagios page callback'), 'admin/config/system/nagios'),
'%callback' => 'prod_check_nagios_status_page',
)
) .'</p>',
) . '</p>',
);
$form['prod_check_nagios']['nagios']['settings']['prod_check_nagios_verbose'] = array(
@@ -428,7 +428,7 @@ function prod_check_settings_form_validate($form, &$form_state) {
}
if (!is_numeric($form_state['values']['prod_check_apc_expunge'])) {
form_set_error('prod_check_apc_expunge', t('APC Cache full count threshold should be numeric!'));
form_set_error('prod_check_apc_expunge', t('APC/OPcache Cache full count threshold should be numeric!'));
}
if (isset($form_state['values']['prod_check_enable_nagios']) && $form_state['values']['prod_check_enable_nagios']) {
@@ -461,9 +461,11 @@ function prod_check_settings_form_submit($form, &$form_state) {
case t('Save configuration'):
variable_set('prod_check_sitemail', $form_state['values']['prod_check_sitemail']);
// PHP errors.
variable_set('prod_check_dblog_php', $form_state['values']['prod_check_dblog_php']);
variable_set('prod_check_dblog_php_threshold', $form_state['values']['prod_check_dblog_php_threshold']);
// APC.
if (module_exists('dblog')) {
variable_set('prod_check_dblog_php', $form_state['values']['prod_check_dblog_php']);
variable_set('prod_check_dblog_php_threshold', $form_state['values']['prod_check_dblog_php_threshold']);
}
// APC/OPcache.
variable_set('prod_check_apc_expunge', $form_state['values']['prod_check_apc_expunge']);
variable_set('prod_check_apcuser', $form_state['values']['prod_check_apcuser']);
if (!empty($form_state['values']['prod_check_apcpass'])) {
@@ -1031,11 +1033,22 @@ function _prod_check_dbstatus_pgsql($db_name, $details) {
/**
* Integration of the APC status page.
*/
function prod_check_apc() {
define('ADMIN_USERNAME', variable_get('prod_check_apcuser', 'apc'));
define('ADMIN_PASSWORD', variable_get('prod_check_apcpass', 'password'));
include(drupal_get_path('module', 'prod_check') . '/includes/prod_check.apc.inc');
exit;
function prod_check_apc_opc() {
// APC.
if (function_exists('apc_cache_info')) {
define('ADMIN_USERNAME', variable_get('prod_check_apcuser', 'apc'));
define('ADMIN_PASSWORD', variable_get('prod_check_apcpass', 'password'));
include(drupal_get_path('module', 'prod_check') . '/includes/prod_check.apc.inc');
exit;
}
// OPcache.
elseif (function_exists('opcache_get_status')) {
include(drupal_get_path('module', 'prod_check') . '/includes/prod_check.opcache.inc');
exit;
}
else {
return t('APC nor OPcache is installed on this webserver!');
}
}
/**
@@ -0,0 +1,502 @@
<?php
function opcache_get_status() {
return array (
'opcache_enabled' => true,
'cache_full' => false,
'memory_usage' =>
array (
'used_memory' => 12028872,
'free_memory' => 4235696,
'wasted_memory' => 512648,
'current_wasted_percentage' => 3.0556201934814,
),
'opcache_statistics' =>
array (
'num_cached_scripts' => 59,
'num_cached_keys' => 78,
'max_cached_keys' => 223,
'hits' => 66817,
'last_restart_time' => 0,
'misses' => 126,
'blacklist_misses' => 0,
'blacklist_miss_ratio' => 0,
'opcache_hit_rate' => 99.81178017119,
'oom_restarts' => 0,
'manual_restarts' => 0,
'hash_restarts' => 0,
),
'scripts' =>
array (
'/var/www/phpweb/manual/en/toc/faq.inc' =>
array (
'full_path' => '/var/www/phpweb/manual/en/toc/faq.inc',
'hits' => 9,
'memory_consumption' => 5680,
'last_used' => 'Sat Mar 16 12:16:31 2013',
'last_used_timestamp' => 1363461391,
'timestamp' => 1363345905,
),
'/var/www/phpweb/downloads.php' =>
array (
'full_path' => '/var/www/phpweb/downloads.php',
'hits' => 86,
'memory_consumption' => 9488,
'last_used' => 'Sat Mar 16 12:03:47 2013',
'last_used_timestamp' => 1363460627,
'timestamp' => 1355102402,
),
'/var/www/phpweb/include/languages.inc' =>
array (
'full_path' => '/var/www/phpweb/include/languages.inc',
'hits' => 5605,
'memory_consumption' => 16984,
'last_used' => 'Sat Mar 16 12:16:31 2013',
'last_used_timestamp' => 1363461391,
'timestamp' => 1362994804,
),
'/var/www/phpweb/include/header.inc' =>
array (
'full_path' => '/var/www/phpweb/include/header.inc',
'hits' => 9,
'memory_consumption' => 20352,
'last_used' => 'Sat Mar 16 12:16:31 2013',
'last_used_timestamp' => 1363461391,
'timestamp' => 1357938001,
),
'/var/www/phpweb/manual/en/toc/getting-started.inc' =>
array (
'full_path' => '/var/www/phpweb/manual/en/toc/getting-started.inc',
'hits' => 9,
'memory_consumption' => 1936,
'last_used' => 'Sat Mar 16 12:16:31 2013',
'last_used_timestamp' => 1363461391,
'timestamp' => 1363345409,
),
'/var/www/phpweb/manual/en/faq.php' =>
array (
'full_path' => '/var/www/phpweb/manual/en/faq.php',
'hits' => 122,
'memory_consumption' => 5152,
'last_used' => 'Sat Mar 16 12:06:15 2013',
'last_used_timestamp' => 1363460775,
'timestamp' => 1363345906,
),
'/var/www/phpweb/include/footer.inc' =>
array (
'full_path' => '/var/www/phpweb/include/footer.inc',
'hits' => 9,
'memory_consumption' => 1976,
'last_used' => 'Sat Mar 16 12:16:31 2013',
'last_used_timestamp' => 1363461391,
'timestamp' => 1347648001,
),
'/var/www/phpweb/include/site.inc' =>
array (
'full_path' => '/var/www/phpweb/include/site.inc',
'hits' => 5605,
'memory_consumption' => 63488,
'last_used' => 'Sat Mar 16 12:16:31 2013',
'last_used_timestamp' => 1363461391,
'timestamp' => 1354724402,
),
'/var/www/phpweb/include/mirrors.inc' =>
array (
'full_path' => '/var/www/phpweb/include/mirrors.inc',
'hits' => 5605,
'memory_consumption' => 96160,
'last_used' => 'Sat Mar 16 12:16:31 2013',
'last_used_timestamp' => 1363461391,
'timestamp' => 1363419613,
),
'/var/www/phpweb/manual/en/toc/refs.basic.vartype.inc' =>
array (
'full_path' => '/var/www/phpweb/manual/en/toc/refs.basic.vartype.inc',
'hits' => 9,
'memory_consumption' => 5032,
'last_used' => 'Sat Mar 16 12:16:31 2013',
'last_used_timestamp' => 1363461391,
'timestamp' => 1363345844,
),
'/var/www/phpweb/include/layout.inc' =>
array (
'full_path' => '/var/www/phpweb/include/layout.inc',
'hits' => 5605,
'memory_consumption' => 145704,
'last_used' => 'Sat Mar 16 12:16:31 2013',
'last_used_timestamp' => 1363461391,
'timestamp' => 1356610801,
),
'/var/www/phpweb/sites.php' =>
array (
'full_path' => '/var/www/phpweb/sites.php',
'hits' => 73,
'memory_consumption' => 2024,
'last_used' => 'Sat Mar 16 12:10:10 2013',
'last_used_timestamp' => 1363461010,
'timestamp' => 1354724402,
),
'/var/www/phpweb/search.php' =>
array (
'full_path' => '/var/www/phpweb/search.php',
'hits' => 3083,
'memory_consumption' => 19728,
'last_used' => 'Sat Mar 16 12:16:31 2013',
'last_used_timestamp' => 1363461391,
'timestamp' => 1347648005,
),
'/var/www/phpweb/manual/en/toc/langref.inc' =>
array (
'full_path' => '/var/www/phpweb/manual/en/toc/langref.inc',
'hits' => 49,
'memory_consumption' => 7416,
'last_used' => 'Sat Mar 16 12:16:31 2013',
'last_used_timestamp' => 1363461391,
'timestamp' => 1363345411,
),
'/var/www/phpweb/manual/en/toc/index.inc' =>
array (
'full_path' => '/var/www/phpweb/manual/en/toc/index.inc',
'hits' => 132,
'memory_consumption' => 4680,
'last_used' => 'Sat Mar 16 12:16:31 2013',
'last_used_timestamp' => 1363461391,
'timestamp' => 1363345409,
),
'/var/www/phpweb/docs.php' =>
array (
'full_path' => '/var/www/phpweb/docs.php',
'hits' => 73,
'memory_consumption' => 4536,
'last_used' => 'Sat Mar 16 12:04:36 2013',
'last_used_timestamp' => 1363460676,
'timestamp' => 1347648000,
),
'/var/www/phpweb/include/shared-manual.inc' =>
array (
'full_path' => '/var/www/phpweb/include/shared-manual.inc',
'hits' => 122,
'memory_consumption' => 107688,
'last_used' => 'Sat Mar 16 12:06:15 2013',
'last_used_timestamp' => 1363460775,
'timestamp' => 1357938001,
),
'/var/www/phpweb/include/mozopensearch.inc' =>
array (
'full_path' => '/var/www/phpweb/include/mozopensearch.inc',
'hits' => 14,
'memory_consumption' => 1232,
'last_used' => 'Sat Mar 16 12:00:34 2013',
'last_used_timestamp' => 1363460434,
'timestamp' => 1347648001,
),
'/var/www/phpweb/include/ip-to-country.inc' =>
array (
'full_path' => '/var/www/phpweb/include/ip-to-country.inc',
'hits' => 5605,
'memory_consumption' => 21840,
'last_used' => 'Sat Mar 16 12:16:31 2013',
'last_used_timestamp' => 1363461391,
'timestamp' => 1347648001,
),
'/var/www/phpweb/include/version.inc' =>
array (
'full_path' => '/var/www/phpweb/include/version.inc',
'hits' => 775,
'memory_consumption' => 7880,
'last_used' => 'Sat Mar 16 12:16:31 2013',
'last_used_timestamp' => 1363461391,
'timestamp' => 1363321202,
),
'/var/www/phpweb/include/errors.inc' =>
array (
'full_path' => '/var/www/phpweb/include/errors.inc',
'hits' => 691,
'memory_consumption' => 59888,
'last_used' => 'Sat Mar 16 12:10:33 2013',
'last_used_timestamp' => 1363461033,
'timestamp' => 1347648001,
),
'/var/www/phpweb/include/countries.inc' =>
array (
'full_path' => '/var/www/phpweb/include/countries.inc',
'hits' => 5605,
'memory_consumption' => 31184,
'last_used' => 'Sat Mar 16 12:16:31 2013',
'last_used_timestamp' => 1363461391,
'timestamp' => 1363419614,
),
'/var/www/phpweb/error.php' =>
array (
'full_path' => '/var/www/phpweb/error.php',
'hits' => 691,
'memory_consumption' => 61952,
'last_used' => 'Sat Mar 16 12:10:33 2013',
'last_used_timestamp' => 1363461033,
'timestamp' => 1347648000,
),
'/var/www/phpweb/manual/en/toc/refs.basic.text.inc' =>
array (
'full_path' => '/var/www/phpweb/manual/en/toc/refs.basic.text.inc',
'hits' => 9,
'memory_consumption' => 3312,
'last_used' => 'Sat Mar 16 12:16:31 2013',
'last_used_timestamp' => 1363461391,
'timestamp' => 1363345831,
),
'/var/www/phpweb/include/loadavg.inc' =>
array (
'full_path' => '/var/www/phpweb/include/loadavg.inc',
'hits' => 3775,
'memory_consumption' => 12184,
'last_used' => 'Sat Mar 16 12:16:31 2013',
'last_used_timestamp' => 1363461391,
'timestamp' => 1354738801,
),
'/var/www/phpweb/include/prepend.inc' =>
array (
'full_path' => '/var/www/phpweb/include/prepend.inc',
'hits' => 5605,
'memory_consumption' => 23432,
'last_used' => 'Sat Mar 16 12:16:31 2013',
'last_used_timestamp' => 1363461391,
'timestamp' => 1347648001,
),
'/var/www/phpweb/o.php' =>
array (
'full_path' => '/var/www/phpweb/o.php',
'hits' => 1,
'memory_consumption' => 13424,
'last_used' => 'Sat Mar 16 18:11:21 2013',
'last_used_timestamp' => 1363482681,
'timestamp' => 1363482654,
),
'/var/www/phpweb/include/pregen-events.inc' =>
array (
'full_path' => '/var/www/phpweb/include/pregen-events.inc',
'hits' => 1141,
'memory_consumption' => 840,
'last_used' => 'Sat Mar 16 12:15:07 2013',
'last_used_timestamp' => 1363461307,
'timestamp' => 1363419615,
),
'/var/www/phpweb/opcache.php' =>
array (
'full_path' => '/var/www/phpweb/opcache.php',
'hits' => 0,
'memory_consumption' => 13944,
'last_used' => 'Sun Mar 17 01:22:31 2013',
'last_used_timestamp' => 1363508551,
'timestamp' => 1363508542,
),
'/var/www/phpweb/manual/en/toc/refs.calendar.inc' =>
array (
'full_path' => '/var/www/phpweb/manual/en/toc/refs.calendar.inc',
'hits' => 9,
'memory_consumption' => 2264,
'last_used' => 'Sat Mar 16 12:16:31 2013',
'last_used_timestamp' => 1363461391,
'timestamp' => 1363345437,
),
'/var/www/phpweb/manual/en/toc/security.inc' =>
array (
'full_path' => '/var/www/phpweb/manual/en/toc/security.inc',
'hits' => 19,
'memory_consumption' => 5352,
'last_used' => 'Sat Mar 16 12:16:31 2013',
'last_used_timestamp' => 1363461391,
'timestamp' => 1363345419,
),
'/var/www/phpweb/include/pregen-news.inc' =>
array (
'full_path' => '/var/www/phpweb/include/pregen-news.inc',
'hits' => 1161,
'memory_consumption' => 57896,
'last_used' => 'Sat Mar 16 12:16:31 2013',
'last_used_timestamp' => 1363461391,
'timestamp' => 1363419615,
),
'/var/www/phpweb/my.php' =>
array (
'full_path' => '/var/www/phpweb/my.php',
'hits' => 34,
'memory_consumption' => 22640,
'last_used' => 'Sat Mar 16 12:15:15 2013',
'last_used_timestamp' => 1363461315,
'timestamp' => 1354724402,
),
'/var/www/phpweb/include/pregen-confs.inc' =>
array (
'full_path' => '/var/www/phpweb/include/pregen-confs.inc',
'hits' => 678,
'memory_consumption' => 1400,
'last_used' => 'Sat Mar 16 12:02:49 2013',
'last_used_timestamp' => 1363460569,
'timestamp' => 1363419615,
),
'/var/www/index.php' =>
array (
'full_path' => '/var/www/index.php',
'hits' => 1,
'memory_consumption' => 744,
'last_used' => 'Sat Mar 16 12:01:20 2013',
'last_used_timestamp' => 1363460480,
'timestamp' => 1358644238,
),
'/var/www/phpweb/index-stable.php' =>
array (
'full_path' => '/var/www/phpweb/index-stable.php',
'hits' => 679,
'memory_consumption' => 22208,
'last_used' => 'Sat Mar 16 12:02:49 2013',
'last_used_timestamp' => 1363460569,
'timestamp' => 1347648003,
),
'/var/www/phpweb/conferences/index.php' =>
array (
'full_path' => '/var/www/phpweb/conferences/index.php',
'hits' => 462,
'memory_consumption' => 3872,
'last_used' => 'Sat Mar 16 12:15:07 2013',
'last_used_timestamp' => 1363461307,
'timestamp' => 1347648000,
),
'/var/www/phpweb/support.php' =>
array (
'full_path' => '/var/www/phpweb/support.php',
'hits' => 73,
'memory_consumption' => 1912,
'last_used' => 'Sat Mar 16 12:06:40 2013',
'last_used_timestamp' => 1363460800,
'timestamp' => 1347648005,
),
'/var/www/phpweb/op.php' =>
array (
'full_path' => '/var/www/phpweb/op.php',
'hits' => 0,
'memory_consumption' => 1448,
'last_used' => 'Sun Mar 17 01:34:33 2013',
'last_used_timestamp' => 1363509273,
'timestamp' => 1363509263,
),
'/var/www/phpweb/include/posttohost.inc' =>
array (
'full_path' => '/var/www/phpweb/include/posttohost.inc',
'hits' => 342,
'memory_consumption' => 3824,
'last_used' => 'Sat Mar 16 12:09:18 2013',
'last_used_timestamp' => 1363460958,
'timestamp' => 1347648001,
),
'/var/www/phpweb/include/langchooser.inc' =>
array (
'full_path' => '/var/www/phpweb/include/langchooser.inc',
'hits' => 5605,
'memory_consumption' => 23784,
'last_used' => 'Sat Mar 16 12:16:31 2013',
'last_used_timestamp' => 1363461391,
'timestamp' => 1347648001,
),
'/var/www/phpweb/include/manual-lookup.inc' =>
array (
'full_path' => '/var/www/phpweb/include/manual-lookup.inc',
'hits' => 624,
'memory_consumption' => 26648,
'last_used' => 'Sat Mar 16 12:10:33 2013',
'last_used_timestamp' => 1363461033,
'timestamp' => 1354750802,
),
'/var/www/phpweb/mailing-lists.php' =>
array (
'full_path' => '/var/www/phpweb/mailing-lists.php',
'hits' => 342,
'memory_consumption' => 31576,
'last_used' => 'Sat Mar 16 12:09:18 2013',
'last_used_timestamp' => 1363460958,
'timestamp' => 1361200802,
),
'/var/www/phpweb/include/last_updated.inc' =>
array (
'full_path' => '/var/www/phpweb/include/last_updated.inc',
'hits' => 5605,
'memory_consumption' => 840,
'last_used' => 'Sat Mar 16 12:16:31 2013',
'last_used_timestamp' => 1363461391,
'timestamp' => 1363419675,
),
'/var/www/phpweb/include/email-validation.inc' =>
array (
'full_path' => '/var/www/phpweb/include/email-validation.inc',
'hits' => 342,
'memory_consumption' => 9168,
'last_used' => 'Sat Mar 16 12:09:18 2013',
'last_used_timestamp' => 1363460958,
'timestamp' => 1362175205,
),
'/var/www/phpweb/index.php' =>
array (
'full_path' => '/var/www/phpweb/index.php',
'hits' => 679,
'memory_consumption' => 1840,
'last_used' => 'Sat Mar 16 12:02:49 2013',
'last_used_timestamp' => 1363460569,
'timestamp' => 1347648003,
),
),
);
}
function opcache_get_configuration() {
return array (
'directives' =>
array (
'opcache.enable' => true,
'opcache.enable_cli' => false,
'opcache.use_cwd' => true,
'opcache.validate_timestamps' => true,
'opcache.inherited_hack' => true,
'opcache.dups_fix' => false,
'opcache.revalidate_path' => false,
'opcache.log_verbosity_level' => 4,
'opcache.memory_consumption' => 16777216,
'opcache.interned_strings_buffer' => 8,
'opcache.max_accelerated_files' => 200,
'opcache.max_wasted_percentage' => 0.05,
'opcache.consistency_checks' => 0,
'opcache.force_restart_timeout' => 180,
'opcache.revalidate_freq' => 0,
'opcache.preferred_memory_model' => '',
'opcache.blacklist_filename' => '/etc/zo_blacklist.txt',
'opcache.max_file_size' => 0,
'opcache.error_log' => '',
'opcache.protect_memory' => false,
'opcache.save_comments' => true,
'opcache.load_comments' => true,
'opcache.fast_shutdown' => true,
'opcache.enable_file_override' => false,
'opcache.optimization_level' => 4294967295,
),
'version' =>
array (
'version' => '7.0.1-dev',
'opcache_product_name' => 'Zend Optimizer+',
),
'blacklist' =>
array (
0 => '/var/www/www.php.net/manual/es',
1 => '/var/www/www.php.net/manual/it',
2 => '/var/www/www.php.net/manual/bg',
3 => '/var/www/www.php.net/manual/fa',
4 => '/var/www/www.php.net/manual/fr',
5 => '/var/www/www.php.net/manual/kr',
6 => '/var/www/www.php.net/manual/pt_BR',
7 => '/var/www/www.php.net/manual/tr',
8 => '/var/www/www.php.net/manual/de',
9 => '/var/www/www.php.net/manual/ro',
10 => '/var/www/www.php.net/manual/ru',
11 => '/var/www/www.php.net/manual/zh',
12 => '/var/www/www.php.net/manual/ja',
13 => '/var/www/www.php.net/manual/pl',
14 => '/var/www/www.php.net/manual/sr',
),
);
}
@@ -0,0 +1,748 @@
<?php
/**
* Taken from https://github.com/rlerdorf/opcache-status
*/
define('THOUSAND_SEPARATOR',true);
if (!extension_loaded('Zend OPcache')) {
echo '<div style="background-color: #F2DEDE; color: #B94A48; padding: 1em;">You do not have the Zend OPcache extension loaded, sample data is being shown instead.</div>';
require 'prod_check.opcache.data_sample.inc';
}
class OpCacheDataModel
{
private $_configuration;
private $_status;
private $_d3Scripts = array();
public function __construct()
{
$this->_configuration = opcache_get_configuration();
$this->_status = opcache_get_status();
}
public function getPageTitle()
{
return 'PHP ' . phpversion() . " with OpCache {$this->_configuration['version']['version']}";
}
public function getStatusDataRows()
{
$rows = array();
foreach ($this->_status as $key => $value) {
if ($key === 'scripts') {
continue;
}
if (is_array($value)) {
foreach ($value as $k => $v) {
if ($v === false) {
$value = 'false';
}
if ($v === true) {
$value = 'true';
}
if ($k === 'used_memory' || $k === 'free_memory' || $k === 'wasted_memory') {
$v = $this->_size_for_humans(
$v
);
}
if ($k === 'current_wasted_percentage' || $k === 'opcache_hit_rate') {
$v = number_format(
$v,
2
) . '%';
}
if ($k === 'blacklist_miss_ratio') {
$v = number_format($v, 2) . '%';
}
if ($k === 'start_time' || $k === 'last_restart_time') {
$v = ($v ? date(DATE_RFC822, $v) : 'never');
}
if (THOUSAND_SEPARATOR === true && is_int($v)) {
$v = number_format($v);
}
$rows[] = "<tr><th>$k</th><td>$v</td></tr>\n";
}
continue;
}
if ($value === false) {
$value = 'false';
}
if ($value === true) {
$value = 'true';
}
$rows[] = "<tr><th>$key</th><td>$value</td></tr>\n";
}
return implode("\n", $rows);
}
public function getConfigDataRows()
{
$rows = array();
foreach ($this->_configuration['directives'] as $key => $value) {
if ($value === false) {
$value = 'false';
}
if ($value === true) {
$value = 'true';
}
if ($key == 'opcache.memory_consumption') {
$value = $this->_size_for_humans($value);
}
$rows[] = "<tr><th>$key</th><td>$value</td></tr>\n";
}
return implode("\n", $rows);
}
public function getScriptStatusRows()
{
foreach ($this->_status['scripts'] as $key => $data) {
$dirs[dirname($key)][basename($key)] = $data;
$this->_arrayPset($this->_d3Scripts, $key, array(
'name' => basename($key),
'size' => $data['memory_consumption'],
));
}
asort($dirs);
$basename = '';
while (true) {
if (count($this->_d3Scripts) !=1) break;
$basename .= DIRECTORY_SEPARATOR . key($this->_d3Scripts);
$this->_d3Scripts = reset($this->_d3Scripts);
}
$this->_d3Scripts = $this->_processPartition($this->_d3Scripts, $basename);
$id = 1;
$rows = array();
foreach ($dirs as $dir => $files) {
$count = count($files);
$file_plural = $count > 1 ? 's' : null;
$m = 0;
foreach ($files as $file => $data) {
$m += $data["memory_consumption"];
}
$m = $this->_size_for_humans($m);
if ($count > 1) {
$rows[] = '<tr>';
$rows[] = "<th class=\"clickable\" id=\"head-{$id}\" colspan=\"3\" onclick=\"toggleVisible('#head-{$id}', '#row-{$id}')\">{$dir} ({$count} file{$file_plural}, {$m})</th>";
$rows[] = '</tr>';
}
foreach ($files as $file => $data) {
$rows[] = "<tr id=\"row-{$id}\">";
$rows[] = "<td>" . $this->_format_value($data["hits"]) . "</td>";
$rows[] = "<td>" . $this->_size_for_humans($data["memory_consumption"]) . "</td>";
$rows[] = $count > 1 ? "<td>{$file}</td>" : "<td>{$dir}/{$file}</td>";
$rows[] = '</tr>';
}
++$id;
}
return implode("\n", $rows);
}
public function getScriptStatusCount()
{
return count($this->_status["scripts"]);
}
public function getGraphDataSetJson()
{
$dataset = array();
$dataset['memory'] = array(
$this->_status['memory_usage']['used_memory'],
$this->_status['memory_usage']['free_memory'],
$this->_status['memory_usage']['wasted_memory'],
);
$dataset['keys'] = array(
$this->_status['opcache_statistics']['num_cached_keys'],
$this->_status['opcache_statistics']['max_cached_keys'] - $this->_status['opcache_statistics']['num_cached_keys'],
0
);
$dataset['hits'] = array(
$this->_status['opcache_statistics']['misses'],
$this->_status['opcache_statistics']['hits'],
0,
);
$dataset['restarts'] = array(
$this->_status['opcache_statistics']['oom_restarts'],
$this->_status['opcache_statistics']['manual_restarts'],
$this->_status['opcache_statistics']['hash_restarts'],
);
if (THOUSAND_SEPARATOR === true) {
$dataset['TSEP'] = 1;
} else {
$dataset['TSEP'] = 0;
}
return json_encode($dataset);
}
public function getHumanUsedMemory()
{
return $this->_size_for_humans($this->getUsedMemory());
}
public function getHumanFreeMemory()
{
return $this->_size_for_humans($this->getFreeMemory());
}
public function getHumanWastedMemory()
{
return $this->_size_for_humans($this->getWastedMemory());
}
public function getUsedMemory()
{
return $this->_status['memory_usage']['used_memory'];
}
public function getFreeMemory()
{
return $this->_status['memory_usage']['free_memory'];
}
public function getWastedMemory()
{
return $this->_status['memory_usage']['wasted_memory'];
}
public function getWastedMemoryPercentage()
{
return number_format($this->_status['memory_usage']['current_wasted_percentage'], 2);
}
public function getD3Scripts()
{
return $this->_d3Scripts;
}
private function _processPartition($value, $name = null)
{
if (array_key_exists('size', $value)) {
return $value;
}
$array = array('name' => $name,'children' => array());
foreach ($value as $k => $v) {
$array['children'][] = $this->_processPartition($v, $k);
}
return $array;
}
private function _format_value($value)
{
if (THOUSAND_SEPARATOR === true) {
return number_format($value);
} else {
return $value;
}
}
private function _size_for_humans($bytes)
{
if ($bytes > 1048576) {
return sprintf('%.2f&nbsp;MB', $bytes / 1048576);
} else {
if ($bytes > 1024) {
return sprintf('%.2f&nbsp;kB', $bytes / 1024);
} else {
return sprintf('%d&nbsp;bytes', $bytes);
}
}
}
// Borrowed from Laravel
private function _arrayPset(&$array, $key, $value)
{
if (is_null($key)) return $array = $value;
$keys = explode(DIRECTORY_SEPARATOR, ltrim($key, DIRECTORY_SEPARATOR));
while (count($keys) > 1) {
$key = array_shift($keys);
if ( ! isset($array[$key]) || ! is_array($array[$key])) {
$array[$key] = array();
}
$array =& $array[$key];
}
$array[array_shift($keys)] = $value;
return $array;
}
}
$dataModel = new OpCacheDataModel();
?>
<!DOCTYPE html>
<meta charset="utf-8">
<html>
<head>
<style>
body {
font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
margin: 0;
padding: 0;
}
#container {
width: 1024px;
margin: auto;
position: relative;
}
h1 {
padding: 10px 0;
}
table {
border-collapse: collapse;
}
tbody tr:nth-child(even) {
background-color: #eee;
}
p.capitalize {
text-transform: capitalize;
}
.tabs {
position: relative;
float: left;
width: 60%;
}
.tab {
float: left;
}
.tab label {
background: #eee;
padding: 10px 12px;
border: 1px solid #ccc;
margin-left: -1px;
position: relative;
left: 1px;
}
.tab [type=radio] {
display: none;
}
.tab th, .tab td {
padding: 8px 12px;
}
.content {
position: absolute;
top: 28px;
left: 0;
background: white;
border: 1px solid #ccc;
height: 450px;
width: 100%;
overflow: auto;
}
.content table {
width: 100%;
}
.content th, .tab:nth-child(3) td {
text-align: left;
}
.content td {
text-align: right;
}
.clickable {
cursor: pointer;
}
[type=radio]:checked ~ label {
background: white;
border-bottom: 1px solid white;
z-index: 2;
}
[type=radio]:checked ~ label ~ .content {
z-index: 1;
}
#graph {
float: right;
width: 40%;
position: relative;
}
#graph > form {
position: absolute;
right: 60px;
top: -20px;
}
#graph > svg {
position: absolute;
top: 0;
right: 0;
}
#stats {
position: absolute;
right: 125px;
top: 145px;
}
#stats th, #stats td {
padding: 6px 10px;
font-size: 0.8em;
}
#partition {
position: absolute;
width: 100%;
height: 100%;
z-index: 10;
top: 0;
left: 0;
background: #ddd;
display: none;
}
#close-partition {
display: none;
position: absolute;
z-index: 20;
right: 15px;
top: 15px;
background: #f9373d;
color: #fff;
padding: 12px 15px;
}
#close-partition:hover {
background: #D32F33;
cursor: pointer;
}
#partition rect {
stroke: #fff;
fill: #aaa;
fill-opacity: 1;
}
#partition rect.parent {
cursor: pointer;
fill: steelblue;
}
#partition text {
pointer-events: none;
}
label {
cursor: pointer;
}
</style>
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.0.1/d3.v3.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<script>
var hidden = {};
function toggleVisible(head, row) {
if (!hidden[row]) {
d3.selectAll(row).transition().style('display', 'none');
hidden[row] = true;
d3.select(head).transition().style('color', '#ccc');
} else {
d3.selectAll(row).transition().style('display');
hidden[row] = false;
d3.select(head).transition().style('color', '#000');
}
}
</script>
<title><?php echo $dataModel->getPageTitle(); ?></title>
</head>
<body>
<div id="container">
<h1><?php echo $dataModel->getPageTitle(); ?></h1>
<div class="tabs">
<div class="tab">
<input type="radio" id="tab-status" name="tab-group-1" checked>
<label for="tab-status">Status</label>
<div class="content">
<table>
<?php echo $dataModel->getStatusDataRows(); ?>
</table>
</div>
</div>
<div class="tab">
<input type="radio" id="tab-config" name="tab-group-1">
<label for="tab-config">Configuration</label>
<div class="content">
<table>
<?php echo $dataModel->getConfigDataRows(); ?>
</table>
</div>
</div>
<div class="tab">
<input type="radio" id="tab-scripts" name="tab-group-1">
<label for="tab-scripts">Scripts (<?php echo $dataModel->getScriptStatusCount(); ?>)</label>
<div class="content">
<table style="font-size:0.8em;">
<tr>
<th width="10%">Hits</th>
<th width="20%">Memory</th>
<th width="70%">Path</th>
</tr>
<?php echo $dataModel->getScriptStatusRows(); ?>
</table>
</div>
</div>
<div class="tab">
<input type="radio" id="tab-visualise" name="tab-group-1">
<label for="tab-visualise">Visualise Partition</label>
<div class="content"></div>
</div>
</div>
<div id="graph">
<form>
<label><input type="radio" name="dataset" value="memory" checked> Memory</label>
<label><input type="radio" name="dataset" value="keys"> Keys</label>
<label><input type="radio" name="dataset" value="hits"> Hits</label>
<label><input type="radio" name="dataset" value="restarts"> Restarts</label>
</form>
<div id="stats"></div>
</div>
</div>
<div id="close-partition">&#10006; Close Visualisation</div>
<div id="partition"></div>
<script>
var dataset = <?php echo $dataModel->getGraphDataSetJson(); ?>;
var width = 400,
height = 400,
radius = Math.min(width, height) / 2,
colours = ['#B41F1F', '#1FB437', '#ff7f0e'];
d3.scale.customColours = function() {
return d3.scale.ordinal().range(colours);
};
var colour = d3.scale.customColours();
var pie = d3.layout.pie().sort(null);
var arc = d3.svg.arc().innerRadius(radius - 20).outerRadius(radius - 50);
var svg = d3.select("#graph").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var path = svg.selectAll("path")
.data(pie(dataset.memory))
.enter().append("path")
.attr("fill", function(d, i) { return colour(i); })
.attr("d", arc)
.each(function(d) { this._current = d; }); // store the initial values
d3.selectAll("input").on("change", change);
set_text("memory");
function set_text(t) {
if (t === "memory") {
d3.select("#stats").html(
"<table><tr><th style='background:#B41F1F;'>Used</th><td><?php echo $dataModel->getHumanUsedMemory()?></td></tr>"+
"<tr><th style='background:#1FB437;'>Free</th><td><?php echo $dataModel->getHumanFreeMemory()?></td></tr>"+
"<tr><th style='background:#ff7f0e;' rowspan=\"2\">Wasted</th><td><?php echo $dataModel->getHumanWastedMemory()?></td></tr>"+
"<tr><td><?php echo $dataModel->getWastedMemoryPercentage()?>%</td></tr></table>"
);
} else if (t === "keys") {
d3.select("#stats").html(
"<table><tr><th style='background:#B41F1F;'>Cached keys</th><td>"+format_value(dataset[t][0])+"</td></tr>"+
"<tr><th style='background:#1FB437;'>Free Keys</th><td>"+format_value(dataset[t][1])+"</td></tr></table>"
);
} else if (t === "hits") {
d3.select("#stats").html(
"<table><tr><th style='background:#B41F1F;'>Misses</th><td>"+format_value(dataset[t][0])+"</td></tr>"+
"<tr><th style='background:#1FB437;'>Cache Hits</th><td>"+format_value(dataset[t][1])+"</td></tr></table>"
);
} else if (t === "restarts") {
d3.select("#stats").html(
"<table><tr><th style='background:#B41F1F;'>Memory</th><td>"+dataset[t][0]+"</td></tr>"+
"<tr><th style='background:#1FB437;'>Manual</th><td>"+dataset[t][1]+"</td></tr>"+
"<tr><th style='background:#ff7f0e;'>Keys</th><td>"+dataset[t][2]+"</td></tr></table>"
);
}
}
function change() {
// Filter out any zero values to see if there is anything left
var remove_zero_values = dataset[this.value].filter(function(value) {
return value > 0;
});
// Skip if the value is undefined for some reason
if (typeof dataset[this.value] !== 'undefined' && remove_zero_values.length > 0) {
$('#graph').find('> svg').show();
path = path.data(pie(dataset[this.value])); // update the data
path.transition().duration(750).attrTween("d", arcTween); // redraw the arcs
// Hide the graph if we can't draw it correctly, not ideal but this works
} else {
$('#graph').find('> svg').hide();
}
set_text(this.value);
}
function arcTween(a) {
var i = d3.interpolate(this._current, a);
this._current = i(0);
return function(t) {
return arc(i(t));
};
}
function size_for_humans(bytes) {
if (bytes > 1048576) {
return (bytes/1048576).toFixed(2) + ' MB';
} else if (bytes > 1024) {
return (bytes/1024).toFixed(2) + ' KB';
} else return bytes + ' bytes';
}
function format_value(value) {
if (dataset["TSEP"] == 1) {
return value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
} else {
return value;
}
}
var w = window.innerWidth,
h = window.innerHeight,
x = d3.scale.linear().range([0, w]),
y = d3.scale.linear().range([0, h]);
var vis = d3.select("#partition")
.style("width", w + "px")
.style("height", h + "px")
.append("svg:svg")
.attr("width", w)
.attr("height", h);
var partition = d3.layout.partition()
.value(function(d) { return d.size; });
root = JSON.parse('<?php echo json_encode($dataModel->getD3Scripts()); ?>');
var g = vis.selectAll("g")
.data(partition.nodes(root))
.enter().append("svg:g")
.attr("transform", function(d) { return "translate(" + x(d.y) + "," + y(d.x) + ")"; })
.on("click", click);
var kx = w / root.dx,
ky = h / 1;
g.append("svg:rect")
.attr("width", root.dy * kx)
.attr("height", function(d) { return d.dx * ky; })
.attr("class", function(d) { return d.children ? "parent" : "child"; });
g.append("svg:text")
.attr("transform", transform)
.attr("dy", ".35em")
.style("opacity", function(d) { return d.dx * ky > 12 ? 1 : 0; })
.text(function(d) { return d.name; })
d3.select(window)
.on("click", function() { click(root); })
function click(d) {
if (!d.children) return;
kx = (d.y ? w - 40 : w) / (1 - d.y);
ky = h / d.dx;
x.domain([d.y, 1]).range([d.y ? 40 : 0, w]);
y.domain([d.x, d.x + d.dx]);
var t = g.transition()
.duration(d3.event.altKey ? 7500 : 750)
.attr("transform", function(d) { return "translate(" + x(d.y) + "," + y(d.x) + ")"; });
t.select("rect")
.attr("width", d.dy * kx)
.attr("height", function(d) { return d.dx * ky; });
t.select("text")
.attr("transform", transform)
.style("opacity", function(d) { return d.dx * ky > 12 ? 1 : 0; });
d3.event.stopPropagation();
}
function transform(d) {
return "translate(8," + d.dx * ky / 2 + ")";
}
$(document).ready(function() {
function handleVisualisationToggle(close) {
$('#partition, #close-partition').fadeToggle();
// Is the visualisation being closed? If so show the status tab again
if (close) {
$('#tab-visualise').removeAttr('checked');
$('#tab-status').trigger('click');
}
}
$('label[for="tab-visualise"], #close-partition').on('click', function() {
handleVisualisationToggle(($(this).attr('id') === 'close-partition'));
});
$(document).keyup(function(e) {
if (e.keyCode == 27) handleVisualisationToggle(true);
});
});
</script>
</body>
</html>
@@ -71,7 +71,7 @@ function my_module_additional_check($caller = 'internal') {
$title = 'My modules settings';
$setting1 = t('Enable debug info');
$setting2 = t('Disable debug info');
$path = 'admin/settings/my-module-settings-page';
$path = 'admin/config/system/my-module-settings-page';
if ($caller != 'internal') {
$path = PRODCHECK_BASEURL . $path;
}
@@ -85,7 +85,7 @@ function my_module_additional_check($caller = 'internal') {
'#description_ok' => prod_check_ok_title($title, $path),
'#description_nok' => t('Your !link settings are set to %setting1, they should be set to %setting2 on a producion environment!',
array(
'!link' => '<em>'.l(t($title), $path, array('attributes' => array('title' => t($title)))).'</em>',
'!link' => '<em>' . l(t($title), $path, array('attributes' => array('title' => t($title)))) . '</em>',
'%setting1' => $setting1,
'%setting2' => $setting2,
)
@@ -0,0 +1,91 @@
<?php
/**
* @file
* Simple database connection check that can be placed anywhere within a Drupal
* installation. Does NOT need to be in the root where index.php resides!
*/
/**
* Locate the actual Drupal root. Based on drush_locate_root().
*/
function locate_root() {
$drupal_root = FALSE;
$start_path = isset($_SERVER['PWD']) ? $_SERVER['PWD'] : '';
if (empty($start_path)) {
$start_path = getcwd();
}
foreach (array(TRUE, FALSE) as $follow_symlinks) {
$path = $start_path;
if ($follow_symlinks && is_link($path)) {
$path = realpath($path);
}
// Check the start path.
if (valid_root($path)) {
$drupal_root = $path;
break;
}
else {
// Move up dir by dir and check each.
while ($path = shift_path_up($path)) {
if ($follow_symlinks && is_link($path)) {
$path = realpath($path);
}
if (valid_root($path)) {
$drupal_root = $path;
break 2;
}
}
}
}
return $drupal_root;
}
/**
* Based on the DrupalBoot*::valid_root() from Drush.
*/
function valid_root($path) {
if (!empty($path) && is_dir($path) && file_exists($path . '/index.php')) {
$candidate = 'includes/common.inc';
if (file_exists($path . '/' . $candidate) && file_exists($path . '/misc/drupal.js')) {
return TRUE;
}
}
return FALSE;
}
/**
* Based on _drush_shift_path_up().
*/
function shift_path_up($path) {
if (empty($path)) {
return FALSE;
}
$path = explode('/', $path);
// Move one directory up.
array_pop($path);
return implode('/', $path);
}
/**
* Do the actual database connection check.
*/
define('DRUPAL_ROOT', locate_root());
require_once DRUPAL_ROOT . '/includes/bootstrap.inc';
drupal_bootstrap(DRUPAL_BOOTSTRAP_DATABASE);
$result = db_query('SELECT COUNT(filename) FROM {system}')->fetchField();
if ($result) {
$msg = 'OK';
http_response_code(200);
}
else {
http_response_code(500);
$msg = 'NOK';
}
exit($msg);
@@ -44,14 +44,14 @@ function drush_prod_check_status() {
foreach ($functions as $set => $data) {
$rows[] = array('');
$rows[] = array("\033[1m".dt($data['title'])."\033[0m");
$rows[] = array("\033[1m" . dt($data['title'])."\033[0m");
foreach ($data['functions'] as $function => $title) {
$result = call_user_func($function);
$func = ltrim($function, '_');
if (is_array($result) && !empty($result)) {
$rows[] = array(
$result[$func]['title'],
"\033[".$severity[$result[$func]['severity']].'m'.strip_tags($result[$func]['value'])."\033[0m",
"\033[" . $severity[$result[$func]['severity']] . 'm' . strip_tags($result[$func]['value']) . "\033[0m",
);
if ($error < $result[$func]['severity']) {
$error = $result[$func]['severity'];
@@ -59,12 +59,12 @@ function drush_prod_check_status() {
}
}
}
drush_print("\033[1m".dt('Production Check status')."\033[0m", 1);
drush_print("\033[1m" . dt('Production Check status')."\033[0m", 1);
drush_print_table($rows);
if ($error > 0) {
// Would be cool if we could prefix the admin path with http://<host>/ so it
// will become a clickable link in some terminals. Any ideas?
drush_print("\033[1m".dt('Some errors were reported!')."\033[0m ".dt('Check the full status page on')." \033[1m".'admin/reports/prod-check'."\033[0m ".dt('for details.'));
drush_print("\033[1m" . dt('Some errors were reported!') . "\033[0m " . dt('Check the full status page on') . " \033[1m" . 'admin/reports/prod-check' . "\033[0m " . dt('for details.'));
}
}
@@ -4,9 +4,9 @@ package = Monitoring
core = 7.x
configure = admin/config/system/prod-check
; Information added by packaging script on 2013-11-25
version = "7.x-1.8"
; Information added by Drupal.org packaging script on 2015-08-04
version = "7.x-1.9"
core = "7.x"
project = "prod_check"
datestamp = "1385405033"
datestamp = "1438700931"
@@ -31,15 +31,15 @@ function prod_check_requirements($phase) {
'title' => t('Production check'),
'value' => t('Site e-mail check not properly configured.'),
'severity' => REQUIREMENT_WARNING,
'description' => t('You have not changed the e-mail address on the prod-check !link. The Site e-mail check will not function properly. Please read the README.txt file.', prod_check_link_array('settings page', 'admin/settings/prod-check')),
'description' => t('You have not changed the e-mail address on the prod-check !link. The Site e-mail check will not function properly. Please read the README.txt file.', prod_check_link_array('settings page', 'admin/config/system/prod-check')),
);
}
if (function_exists('apc_cache_info') && variable_get('prod_check_apcpass', 'password') == 'password') {
$requirements['prod_check_apc'] = array(
$requirements['prod_check_apc_opc'] = array(
'title' => t('Production check'),
'value' => t('APC password not configured.'),
'severity' => REQUIREMENT_WARNING,
'description' => t('You have not !link for the APC status page. The page will function, but advanced options require that you set a password. Please read the README.txt file.', prod_check_link_array('changed the password', 'admin/settings/prod-check')),
'description' => t('You have not !link for the APC status page. The page will function, but advanced options require that you set a password. Please read the README.txt file.', prod_check_link_array('changed the password', 'admin/config/system/prod-check')),
);
}
$nagios = variable_get('prod_check_enable_nagios', 0);
@@ -28,7 +28,7 @@ $protocol = 'http://';
if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') {
$protocol = 'https://';
}
define('PRODCHECK_BASEURL', $protocol.$_SERVER['HTTP_HOST'].'/');
define('PRODCHECK_BASEURL', $protocol . $_SERVER['HTTP_HOST'] . '/');
/**
* Implementation of hook_help().
@@ -37,27 +37,27 @@ function prod_check_help($path, $arg) {
$output = '';
switch ($path) {
case 'admin/help#prod_check':
$output .= '<p>'.t('Production check is a module that will add a report detailing the status of several settings and modules. The report is tailored for a <strong>production environment</strong>. It will tell you which modules should (not) be running, what settings are OK or not and much more. It is an easy way to have an overview of the status of your site when bringing it live, so that you can quickly put all the configuration details in order to be ready for production use.').'</p>';
$output .= '<p>'.t('Using the settings page, you can enable <strong>XMLRPC support</strong> so that it can report back to the <strong>Production monitor</strong> module, available as an extra module in this package. If you install the <em>Production monitor</em> module on a central site, you can monitor several sites in a glance, ensuring that no one changes settings without you knowing about it. See the <em>Production monitor</em> built in help for more information.').'</p>';
$output .= '<p>'.t('If you prefer using <strong>!link</strong> for monitoring, you can simply enable support for that on the settings page by ticking the appropriate checkmark. An extra set of checkboxes will appear, allowing you to configure in detail what exactly you wish !link to monitor.', prod_check_link_array('Nagios', 'http://drupal.org/project/nagios')).'</p>';
$output .= '<p>' . t('Production check is a module that will add a report detailing the status of several settings and modules. The report is tailored for a <strong>production environment</strong>. It will tell you which modules should (not) be running, what settings are OK or not and much more. It is an easy way to have an overview of the status of your site when bringing it live, so that you can quickly put all the configuration details in order to be ready for production use.') . '</p>';
$output .= '<p>' . t('Using the settings page, you can enable <strong>XMLRPC support</strong> so that it can report back to the <strong>Production monitor</strong> module, available as an extra module in this package. If you install the <em>Production monitor</em> module on a central site, you can monitor several sites in a glance, ensuring that no one changes settings without you knowing about it. See the <em>Production monitor</em> built in help for more information.') . '</p>';
$output .= '<p>' . t('If you prefer using <strong>!link</strong> for monitoring, you can simply enable support for that on the settings page by ticking the appropriate checkmark. An extra set of checkboxes will appear, allowing you to configure in detail what exactly you wish !link to monitor.', prod_check_link_array('Nagios', 'http://drupal.org/project/nagios')) . '</p>';
break;
case 'admin/reports/prod-check':
case 'admin/reports/prod-check/status':
$output .= '<p>'.t('This is an overview of all checks performed by the <em>Production check</em> module and their status. You can click the links inside the report to jump to the module\'s settings page, or to go to the project page of a module, in case you need to download it for installation.').'</p>';
$output .= '<p>' . t("This is an overview of all checks performed by the <em>Production check</em> module and their status. You can click the links inside the report to jump to the module's settings page, or to go to the project page of a module, in case you need to download it for installation.") . '</p>';
break;
case 'admin/config/system/prod-check':
$output .= '<p><strong>'.t('Sitemail check').'</strong><br />';
$output .= t('The value entered here is used in a regular expression. Prod check will use it to see if the e-mail address you have entered in <em>Site information</em> is no longer a development e-mail address.').'</p>';
$output .= '<p><strong>'.t('Advanced APC settings').'</strong><br />';
$output .= t('Production check enables a <em>hidden</em> path where you can review your APC setup. This is absolutely unmissable if you want to properly setup APC and tune it specifically for your website.').'</p>';
$output .= '<p><strong>'.t('Enable XMLRPC API').'</strong><br />';
$output .= t('By ticking this box, you open up the module\'s XMLRPC functions so they can be called by the <strong>Production monitor</strong> module for remote monitoring of your site. When enabling XMLRPC, you <strong>must</strong> enter an <strong>API key</strong> to secure the transfer of data. It\'s limited to 128 characters. A mixture of alphanumeric and special characters will increase security.').'</p>';
$output .= '<p><strong>'.t('Report module list every <em>x</em> at time <em>y</em>').'</strong><br />';
$output .= t('Select on which day of the week and at what time <em>Production check</em> is allowed to pass the module list of the site it is on to <em>Production monitor</em>. Set this carefully, as the amount data being transfered is quite big!').'<br />';
$output .= t('Depending on when the cron is run on the <em>Production monitor</em> site, the module list will be reported on or maybe even several hours(!) after the time given here!').'</p>';
$output .= '<p><strong>'.t('Enable Nagios integration').'</strong><br />';
$output .= t('By ticking this box, you open up the module\'s Nagios hooks, so that it can interface with the !link module. You will obviously need to install this module next to <em>Production check</em> to enable this functionality.', prod_check_link_array('Nagios', 'http://drupal.org/project/nagios')).'<br />';
$output .= t('When the checkbox is enabled, a new array of checkboxes will appear, allowing you to specify in detail what will be reported to !link.', prod_check_link_array('Nagios', 'http://drupal.org/project/nagios')).'</p>';
$output .= '<p><strong>' . t('Sitemail check') . '</strong><br />';
$output .= t('The value entered here is used in a regular expression. Prod check will use it to see if the e-mail address you have entered in <em>Site information</em> is no longer a development e-mail address.') . '</p>';
$output .= '<p><strong>' . t('Advanced APC/OPcache settings') . '</strong><br />';
$output .= t('Production check enables a <em>hidden</em> path where you can review your APC setup. This is absolutely unmissable if you want to properly setup APC and tune it specifically for your website.') . '</p>';
$output .= '<p><strong>' . t('Enable XMLRPC API') . '</strong><br />';
$output .= t("By ticking this box, you open up the module's XMLRPC functions so they can be called by the <strong>Production monitor</strong> module for remote monitoring of your site. When enabling XMLRPC, you <strong>must</strong> enter an <strong>API key</strong> to secure the transfer of data. It's limited to 128 characters. A mixture of alphanumeric and special characters will increase security.") . '</p>';
$output .= '<p><strong>' . t('Report module list every <em>x</em> at time <em>y</em>') . '</strong><br />';
$output .= t('Select on which day of the week and at what time <em>Production check</em> is allowed to pass the module list of the site it is on to <em>Production monitor</em>. Set this carefully, as the amount data being transfered is quite big!') . '<br />';
$output .= t('Depending on when the cron is run on the <em>Production monitor</em> site, the module list will be reported on or maybe even several hours(!) after the time given here!') . '</p>';
$output .= '<p><strong>' . t('Enable Nagios integration') . '</strong><br />';
$output .= t("By ticking this box, you open up the module's Nagios hooks, so that it can interface with the !link module. You will obviously need to install this module next to <em>Production check</em> to enable this functionality.", prod_check_link_array('Nagios', 'http://drupal.org/project/nagios')) . '<br />';
$output .= t('When the checkbox is enabled, a new array of checkboxes will appear, allowing you to specify in detail what will be reported to !link.', prod_check_link_array('Nagios', 'http://drupal.org/project/nagios')) . '</p>';
break;
}
return $output;
@@ -140,9 +140,9 @@ function prod_check_menu() {
'page callback' => 'prod_check_dbstatus',
) + $admin_defaults;
$items['admin/reports/status/apc'] = array(
'title' => 'APC',
'page callback' => 'prod_check_apc',
$items['admin/reports/status/apc-opc'] = array(
'title' => 'APC/OPcache',
'page callback' => 'prod_check_apc_opc',
'access callback' => 'user_access',
) + $admin_defaults;
@@ -263,7 +263,7 @@ function prod_check_xmlrpc() {
'prod_check.get_settings',
'prod_check_get_settings',
array('struct', 'string'),
t('Returns a struct containing a form to be displayed on the prod_monitor module\'s settings page for site specific configuration.')
t("Returns a struct containing a form to be displayed on the prod_monitor module's settings page for site specific configuration.")
),
array(
'prod_check.get_data',
@@ -431,9 +431,9 @@ function prod_check_nagios() {
if (!$value['count']) {
continue;
}
$message .= '@'.strtolower($state).' '.$state;
$message .= '@' . strtolower($state) . ' ' . $state;
if(isset($nagios[$state]['items'])) {
$message .= ': '.implode('|', $nagios[$state]['items']);
$message .= ': ' . implode('|', $nagios[$state]['items']);
}
$message .= ', ';
}
@@ -521,7 +521,7 @@ function prod_check_execute_check($checks, $caller, $compatibility = 'all') {
}
}
// Special stuff here, only compatible with prod_monitor!
else if (is_array($checks) && $compatibility == 'prod_mon') {
elseif (is_array($checks) && $compatibility == 'prod_mon') {
$result = $checks;
}
@@ -532,7 +532,7 @@ function prod_check_execute_check($checks, $caller, $compatibility = 'all') {
* Helper function to generate generic 'settings OK' description.
*/
function prod_check_ok_title($title, $path, $text = 'Your !link settings are OK for production use.') {
return t($text, array('!link' => '<em>'.l(t($title), $path, array('attributes' => array('title' => t($title)), 'query' => drupal_get_destination())).'</em>'));
return t($text, array('!link' => '<em>' . l(t($title), $path, array('attributes' => array('title' => t($title)), 'query' => drupal_get_destination())) . '</em>'));
}
/**
@@ -550,7 +550,7 @@ function prod_check_link_array($title, $path, $fragment=NULL) {
if ($fragment) {
$options['fragment'] = $fragment;
}
return array('!link' => '<em>'.l(t($title), $path, $options).'</em>');
return array('!link' => '<em>' . l(t($title), $path, $options) . '</em>');
}
// --- All check functions follow here ---
@@ -582,7 +582,7 @@ function _prod_check_functions() {
'title' => 'Server',
'description' => 'Checks certain server side parameters such as APC.',
'functions' => array(
'_prod_check_apc' => 'APC',
'_prod_check_apc_opc' => 'APC/OPcache',
'_prod_check_dblog_php' => 'PHP errors',
'_prod_check_release_notes' => 'Release notes',
),
@@ -617,13 +617,14 @@ function _prod_check_functions() {
// Modules
$functions['modules'] = array(
'title' => 'Modules',
'description' => 'Checks if certain modules are on or off and if they\'re properly configured.',
'description' => "Checks if certain modules are on or off and if they're properly configured.",
'functions' => array(
'_prod_check_contact' => 'Contact',
'_prod_check_devel' => 'Devel',
'_prod_check_search_config' => 'Search config',
'_prod_check_update_status' => 'Update status',
'_prod_check_webform' => 'Webform',
'_prod_check_mimemail' => 'Mimemail',
'_prod_check_missing_module_files' => 'Active modules',
),
);
@@ -688,7 +689,7 @@ function _prod_check_error_reporting($caller = 'internal') {
'#description_ok' => prod_check_ok_title($title, $path),
'#description_nok' => t('Your !link settings are set to %setting1, they should be set to %setting2 on a producion environment!',
array(
'!link' => '<em>'.l(t($title), $path, array('attributes' => array('title' => t($title)), 'query' => drupal_get_destination())).'</em>',
'!link' => '<em>' . l(t($title), $path, array('attributes' => array('title' => t($title)), 'query' => drupal_get_destination())) . '</em>',
'%setting1' => $setting[$current],
'%setting2' => $setting[ERROR_REPORTING_HIDE],
)
@@ -723,7 +724,7 @@ function _prod_check_user_register($caller = 'internal') {
'#description_ok' => prod_check_ok_title($title, $path),
'#description_nok' => t('Your !link settings are set to %setting1. Are you sure this is what you want and did not mean to use %setting2? With improperly setup access rights, this can be dangerous...',
array(
'!link' => '<em>'.l(t($title), $path, array('attributes' => array('title' => t($title)), 'query' => drupal_get_destination())).'</em>',
'!link' => '<em>' . l(t($title), $path, array('attributes' => array('title' => t($title)), 'query' => drupal_get_destination())) . '</em>',
'%setting1' => $setting[$current],
'%setting2' => $setting[USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL],
)
@@ -811,30 +812,50 @@ function _prod_check_poormanscron($caller = 'internal') {
// --- SERVER ---
// APC check
function _prod_check_apc($caller = 'internal') {
function _prod_check_apc_opc($caller = 'internal') {
$check = array();
$desc_ok = $desc_nok = '';
$title = 'APC';
$path = 'admin/reports/status/apc';
$title = 'APC/OPcache';
$path = 'admin/reports/status/apc-opc';
if ($caller != 'internal') {
$path = PRODCHECK_BASEURL . $path;
}
if (!function_exists('apc_cache_info')) {
$cache = array();
if (!function_exists('apc_cache_info') && !function_exists('opcache_get_status')) {
$desc_nok = t('!link does not appear to be running.', prod_check_link_array($title, $path));
$val_nok = t('Disabled');
$error = TRUE;
}
else if ($cache = @apc_cache_info('opcode')) {
elseif (function_exists('apc_cache_info')) {
$cache = @apc_cache_info('opcode');
}
elseif (function_exists('opcache_get_status')) {
$opc_cache = @opcache_get_status();
if ($opc_cache && $opc_cache['opcache_enabled']) {
$cache['num_hits'] = $opc_cache['opcache_statistics']['hits'];
$cache['num_misses'] = $opc_cache['opcache_statistics']['misses'];
// TODO: Should we make the sum of these two or check separately?
$cache['expunges'] = $opc_cache['opcache_statistics']['oom_restarts'] + $opc_cache['opcache_statistics']['hash_restarts'];
}
else {
$desc_nok = t('!link does not appear to be running.', prod_check_link_array($title, $path));
$val_nok = t('Disabled');
$error = TRUE;
}
}
if (!empty($cache)) {
$apc_expunge = variable_get('prod_check_apc_expunge', 0);
$detailed_info = ': '.t('hits').': '.$cache['num_hits'].', '.t('misses').': '.$cache['num_misses'].', '.t('cache full count').': '.$cache['expunges'].'.';
$detailed_info = ': ' . t('hits') . ': ' . $cache['num_hits'] . ', ' . t('misses') . ': ' . $cache['num_misses'] . ', ' . t('cache full count') . ': ' . $cache['expunges'] . '.';
if ($cache['num_misses'] >= $cache['num_hits']) {
$desc_nok = t('!link not properly configured, too many misses', prod_check_link_array($title, $path)) . $detailed_info;
$val_nok = t('Not functioning properly.');
$error = TRUE;
}
else if ($cache['expunges'] > $apc_expunge) {
elseif ($cache['expunges'] > $apc_expunge) {
$desc_nok = t('!link not properly configured, cache size too small', prod_check_link_array($title, $path)) . $detailed_info;
$val_nok = t('Not functioning properly.');
$error = TRUE;
@@ -851,7 +872,7 @@ function _prod_check_apc($caller = 'internal') {
$error = TRUE;
}
$check['prod_check_apc'] = array(
$check['prod_check_apc_opc'] = array(
'#title' => t($title),
'#state' => !$error,
'#severity' => ($caller == 'nagios') ? NAGIOS_STATUS_CRITICAL : PROD_CHECK_REQUIREMENT_ERROR,
@@ -997,9 +1018,9 @@ function _prod_check_page_cache($caller = 'internal') {
'#description_ok' => prod_check_ok_title($title, $path),
'#description_nok' => t('Your !link settings are disabled. You should at least set page caching to "Cache pages for anonymous users" on a production site! You should also consider using the !boost module or a more powerful system like !varnish!',
array(
'!link' => '<em>'.l(t($title), $path, array('attributes' => array('title' => t($title)), 'query' => drupal_get_destination())).'</em>',
'!boost' => '<em>'.l(t('Boost'), 'http://drupal.org/project/boost', array('attributes' => array('title' => t('Boost')))).'</em>',
'!varnish' => '<em>'.l(t('Varnish'), 'http://drupal.org/project/steroids', array('attributes' => array('title' => t('Varnish')))).'</em>',
'!link' => '<em>' . l(t($title), $path, array('attributes' => array('title' => t($title)), 'query' => drupal_get_destination())) . '</em>',
'!boost' => '<em>' . l(t('Boost'), 'http://drupal.org/project/boost', array('attributes' => array('title' => t('Boost')))) . '</em>',
'!varnish' => '<em>' . l(t('Varnish'), 'http://drupal.org/project/steroids', array('attributes' => array('title' => t('Varnish')))) . '</em>',
)
),
'#nagios_key' => 'PCACHE',
@@ -1068,7 +1089,7 @@ function _prod_check_boost($caller = 'internal') {
$subtitle = 'text/html - Maximum Cache Lifetime';
$var = variable_get('boost_lifetime_max_text/html', 3600);
$check['prod_check_boost_cache_lifetime'] = array(
'#title' => t($title.$subtitle),
'#title' => t($title . $subtitle),
'#state' => $var <= 3600,
'#severity' => ($caller == 'nagios') ? NAGIOS_STATUS_WARNING : PROD_CHECK_REQUIREMENT_WARNING,
'#value_ok' => t('Set to !seconds seconds.', array('!seconds' => $var)),
@@ -1083,7 +1104,7 @@ function _prod_check_boost($caller = 'internal') {
$subtitle = 'Remove old cache files on cron';
$var = variable_get('boost_expire_cron', BOOST_EXPIRE_CRON);
$check['prod_check_boost_expire_cron'] = array(
'#title' => t($title.$subtitle),
'#title' => t($title . $subtitle),
'#state' => $var,
'#severity' => ($caller == 'nagios') ? NAGIOS_STATUS_WARNING : PROD_CHECK_REQUIREMENT_WARNING,
'#value_ok' => t('Enabled'),
@@ -1098,7 +1119,7 @@ function _prod_check_boost($caller = 'internal') {
$subtitle = 'Crawl on cron';
$var = module_exists('boost_crawler') && variable_get('boost_crawl_on_cron', FALSE);
$check['prod_check_boost_crawl_on_cron'] = array(
'#title' => t($title.$subtitle),
'#title' => t($title . $subtitle),
'#state' => $var,
'#severity' => ($caller == 'nagios') ? NAGIOS_STATUS_WARNING : PROD_CHECK_REQUIREMENT_WARNING,
'#value_ok' => t('Enabled'),
@@ -1127,13 +1148,13 @@ function _prod_check_boost($caller = 'internal') {
}
$check['prod_check_boost_apache_nagios_page'] = array(
'#title' => t($title.$subtitle),
'#title' => t($title . $subtitle),
'#state' => !$var,
'#severity' => ($caller == 'nagios') ? NAGIOS_STATUS_WARNING : PROD_CHECK_REQUIREMENT_WARNING,
'#value_ok' => t('Enabled'),
'#value_nok' => t('Not properly configured.'),
'#description_ok' => prod_check_ok_title($subtitle, $path),
'#description_nok' => t('The !link is being cached by Boost. '.$advise, prod_check_link_array($subtitle, $path)),
'#description_nok' => t('The !link is being cached by Boost. ' . $advise, prod_check_link_array($subtitle, $path)),
'#nagios_key' => 'BNAPA',
'#nagios_type' => 'state',
);
@@ -1143,7 +1164,7 @@ function _prod_check_boost($caller = 'internal') {
$subtitle = 'ETag';
$var = variable_get('boost_apache_etag', BOOST_APACHE_ETAG);
$check['prod_check_boost_apache_etag'] = array(
'#title' => t($title.$subtitle),
'#title' => t($title . $subtitle),
'#state' => $var >= 2,
'#severity' => ($caller == 'nagios') ? NAGIOS_STATUS_WARNING : PROD_CHECK_REQUIREMENT_WARNING,
'#value_ok' => t('Enabled'),
@@ -1151,9 +1172,9 @@ function _prod_check_boost($caller = 'internal') {
'#description_ok' => prod_check_ok_title($subtitle, $path_htaccess),
'#description_nok' => t('Your !link settings are not ok! You should enable entity tags (!etag) in Boost so that user side caching and bandwith usage will be optimal! You do need to enable !mod for this to work.',
array(
'!link' => '<em>'.l(t($subtitle), $path_htaccess, array('attributes' => array('title' => t($subtitle)), 'query' => drupal_get_destination())).'</em>',
'!etag' => '<em>'.l(t('ETags'), 'http://en.wikipedia.org/wiki/HTTP_ETag', array('attributes' => array('title' => t('Etags')))).'</em>',
'!mod' => '<em>'.l(t('mod_headers'), 'http://httpd.apache.org/docs/2.0/mod/mod_headers.html', array('attributes' => array('title' => t('mod_headers')))).'</em>',
'!link' => '<em>' . l(t($subtitle), $path_htaccess, array('attributes' => array('title' => t($subtitle)), 'query' => drupal_get_destination())) . '</em>',
'!etag' => '<em>' . l(t('ETags'), 'http://en.wikipedia.org/wiki/HTTP_ETag', array('attributes' => array('title' => t('Etags')))) . '</em>',
'!mod' => '<em>' . l(t('mod_headers'), 'http://httpd.apache.org/docs/2.0/mod/mod_headers.html', array('attributes' => array('title' => t('mod_headers')))) . '</em>',
)
),
'#nagios_key' => 'BETAG',
@@ -1320,7 +1341,7 @@ function _prod_check_user_pass($caller = 'internal') {
// Be sure to omit the anonymous user with id 0.
$result = db_query('SELECT uid, name FROM {users} WHERE uid <> 0 AND status = 1 AND MD5(name) = pass');
foreach ($result as $row) {
$list .= l($row['name'], $path.'user/'.$row['uid'].'/edit', array('attributes' => array('title' => t('Edit user').' '.$row['name']), 'query' => drupal_get_destination())).', ';
$list .= l($row['name'], $path . 'user/' . $row['uid'] . '/edit', array('attributes' => array('title' => t('Edit user') . ' ' . $row['name']), 'query' => drupal_get_destination())) . ', ';
}
if (!empty($list)) {
$secure = FALSE;
@@ -1334,7 +1355,7 @@ function _prod_check_user_pass($caller = 'internal') {
'#value_ok' => t('Secure'),
'#value_nok' => t('Security risk!'),
'#description_ok' => t('No security risk found.'),
'#description_nok' => t('Some users have a password that is identical to their username! You should check the following users:' .' '.$list.'.'),
'#description_nok' => t('Some users have a password that is identical to their username! You should check the following users:' . ' ' . $list . '.'),
'#nagios_key' => 'USRBD',
'#nagios_type' => 'state',
);
@@ -1511,7 +1532,7 @@ function _prod_check_devel($caller = 'internal') {
$path = PRODCHECK_BASEURL . $path;
}
$checks['prod_check_'.$data['name']] = array(
$checks['prod_check_' . $data['name']] = array(
'#title' => t($title),
'#state' => !$data['error'],
'#severity' => ($caller == 'nagios') ? NAGIOS_STATUS_CRITICAL : PROD_CHECK_REQUIREMENT_ERROR,
@@ -1646,6 +1667,37 @@ function _prod_check_webform($caller = 'internal') {
return prod_check_execute_check($check, $caller);
}
// Mimemail
function _prod_check_mimemail($caller = 'internal') {
if (!module_exists('mimemail')) {
return;
}
$check = array();
$title = 'Mimemail';
$path = 'admin/config/system/mimemail';
if ($caller != 'internal') {
$path = PRODCHECK_BASEURL . $path;
}
$mimemail_mail = variable_get('mimemail_mail', variable_get('site_mail', ini_get('sendmail_from')));
$arguments = array('!mimemail' => $title, '%mail' => $mimemail_mail);
$check['prod_check_mimemail'] = array(
'#title' => t($title),
'#state' => $mimemail_mail != '' && !preg_match('/' . preg_quote(variable_get('prod_check_sitemail', '')) . '/i', $mimemail_mail),
'#severity' => ($caller == 'nagios') ? NAGIOS_STATUS_CRITICAL : PROD_CHECK_REQUIREMENT_ERROR,
'#value_ok' => t('!mimemail default sender e-mail address OK: %mail', $arguments),
'#value_nok' => t('!mimemail default sender e-mail address set to %mail', $arguments),
'#description_ok' => prod_check_ok_title($title, $path),
'#description_nok' => t('The !link default from e-mail address should not be a development address on production sites!', prod_check_link_array($title, $path)),
'#nagios_key' => 'MIML',
'#nagios_type' => 'state',
);
return prod_check_execute_check($check, $caller);
}
// Active modules
function _prod_check_missing_module_files($caller = 'internal') {
$missing = $total = 0;
@@ -1707,7 +1759,7 @@ function _prod_check_googleanalytics($caller = 'internal') {
$value_nok = t('Disabled');
$msg_nok = t('You have not enabled the !link module. If you wish to track and optimise your site !link is absolutely necessary.', prod_check_link_array($title, 'http://drupal.org/project/google_analytics'));
}
else if (empty($ga_account) || $ga_account == 'UA-') {
elseif (empty($ga_account) || $ga_account == 'UA-') {
$error = TRUE;
$severity = ($caller == 'nagios') ? NAGIOS_STATUS_CRITICAL : PROD_CHECK_REQUIREMENT_ERROR;
$value_nok = t('Not properly configured.');
@@ -1757,6 +1809,10 @@ function _prod_check_metatag($caller = 'internal') {
}
function _prod_check_page_title($caller = 'internal') {
if (module_exists('metatag')) {
return;
}
$check = array();
$error = FALSE;
$pager = variable_get('page_title_pager_pattern', '');
@@ -1773,9 +1829,9 @@ function _prod_check_page_title($caller = 'internal') {
if (!module_exists('page_title')) {
$error = TRUE;
$value_nok = t('Disabled');
$msg_nok = t('You have not enabled the !link module. This module can help out with problems such as pages with paging being marked as duplicate content by search engines.', prod_check_link_array($title, 'http://drupal.org/project/page_title'));
$msg_nok = t('You have not enabled the !link (or equivalent) module. This module can help out with problems such as pages with paging being marked as duplicate content by search engines.', prod_check_link_array($title, 'http://drupal.org/project/page_title'));
}
else if (empty($pager)) {
elseif (empty($pager)) {
$error = TRUE;
$value_nok = t('Not properly configured.');
$msg_nok = t('You have not !link You should really do this if you want proper Google Indexing.', prod_check_link_array('set a pager suffix', $path));
@@ -4,8 +4,15 @@
* Build status page.
*/
function prod_monitor_status($id) {
$site = _prod_monitor_get_site($id, TRUE);
drupal_set_title(t('Production monitor status for') .' '. _prod_monitor_sanitize_url($site['url']));
$site = _prod_monitor_get_site($id, 'all');
if (!$site) {
// See https://api.drupal.org/api/drupal/includes!common.inc/function/drupal_not_found/7
return MENU_NOT_FOUND;
}
drupal_set_title(t('Production monitor status for') . ' ' . _prod_monitor_sanitize_url($site['url']));
$functions = $site['settings']['functions'];
$nodata = t('No data recieved yet.');
@@ -25,24 +32,25 @@ function prod_monitor_status($id) {
// Display results of all checks.
foreach ($functions as $set => $data) {
if (isset($site['data'][$set])) {
$output .= '<h2>'.t($data['title']).'</h2>'."\n";
$output .= '<div class="description"><p><em>'.t($data['description']).'</em></p></div>'."\n";
$output .= '<h2>' . t($data['title']) . '</h2>' . "\n";
$output .= '<div class="description"><p><em>' . t($data['description']) . '</em></p></div>'."\n";
if (!empty($site['data'][$set])) {
$output .= theme('prod_monitor_status_report', array('requirements' => $site['data'][$set]));
}
else {
$output .= '<p>'.$nodata.'</p><p>&nbsp;</p>';
$output .= '<p>' . $nodata . '</p><p>&nbsp;</p>';
}
}
}
if (empty($output)) {
$output = '<p>'.$nodata.'</p><p>&nbsp;</p>';
$output = '<p>' . $nodata . '</p><p>&nbsp;</p>';
}
// TODO: do not use drupal_render but change this so that hook_page_alter can
// be used as well.
$output .= drupal_render(drupal_get_form('_prod_monitor_update_data_form', $id, $site));
$form = drupal_get_form('_prod_monitor_update_data_form', $id, $site);
$output .= drupal_render($form);
return $output;
}
@@ -51,30 +59,54 @@ function prod_monitor_status($id) {
* Helper function to provide general status block on status overview page
*/
function _prod_monitor_status_general($prod_mon, $modules) {
// TODO: Should we hide the rows for which no data is being retrieved?
$cron = t('Unknown');
if (isset($prod_mon['prod_check_cron_last'])) {
$cron = format_date($prod_mon['prod_check_cron_last'], 'large');
}
$updates = _prod_monitor_generate_updates_link($modules['id'], $modules['updates']);
$output = '<h2>'.t('Overall status').'</h2>'."\n";
$output = '<h2>' . t('Overall status') . '</h2>' . "\n";
$rows = array(
array(
array('data' => t('Drupal core version'), 'header' => TRUE),
$modules['projects']['drupal']['info']['version'],
),
array(
array('data' => t('Last cron run'), 'header' => TRUE),
$cron,
),
array(
array('data' => t('Update status'), 'header' => TRUE),
$updates,
),
);
$output .= theme('table', array('header' => array(), 'rows' => $rows));
if (isset($prod_mon['prod_check_cron_last'])) {
$rows[] = array(
array('data' => t('Last cron run'), 'header' => TRUE),
format_date($prod_mon['prod_check_cron_last'], 'large'),
);
}
// Add dbconnect check info if configured.
if (isset($prod_mon['prod_check_dbconnect'])) {
$dbconnect = $prod_mon['prod_check_dbconnect'];
$class = array();
$title = t('DB connection status');
if (stripos($dbconnect, '200') === FALSE) {
$class = array('error');
$title = '<strong>' . $title . '</strong>';
$dbconnect = '<strong>' . $dbconnect . '</strong>';
}
$rows[] = array(
'data' => array(
array(
'data' => $title, 'header' => TRUE,
'class' => $class,
),
array(
'data' => $dbconnect,
'class' => $class,
),
),
'class' => $class,
);
}
$rows[] = array(
array('data' => t('Update status'), 'header' => TRUE),
$updates,
);
$output .= theme('table', array('prod_monitor_id' => 'status_general', 'header' => array(), 'rows' => $rows));
return $output;
}
@@ -99,7 +131,7 @@ function _prod_monitor_generate_updates_link($id, $update_status) {
$title = t('Security risk!');
break;
}
$updates = array('data' => '<strong>'.l($title, 'admin/reports/prod-monitor/site/'.$id.'/view/updates', array('attributes' => array('title' => $title, 'class' => $class))).'</strong>', 'class' => $class);
$updates = array('data' => '<strong>' . l($title, 'admin/reports/prod-monitor/site/' . $id . '/view/updates', array('attributes' => array('title' => $title, 'class' => $class))) . '</strong>', 'class' => $class);
}
return $updates;
@@ -109,10 +141,10 @@ function _prod_monitor_generate_updates_link($id, $update_status) {
* Callback for performance page.
*/
function prod_monitor_performance($data) {
drupal_set_title(t('Performance logs for') .' '. _prod_monitor_get_url($data['id']));
drupal_set_title(t('Performance logs for') . ' ' . _prod_monitor_get_url($data['id']));
// TODO: add 'get stats now' button.
//$site = _prod_monitor_get_site($id, TRUE);
//$site = _prod_monitor_get_site($id, 'all');
return array(
'performance_data' => array(
@@ -130,7 +162,7 @@ function prod_monitor_updates($modules) {
$id = $modules['id'];
drupal_set_title(t('Module update status for') .' '. _prod_monitor_get_url($id));
drupal_set_title(t('Module update status for') . ' ' . _prod_monitor_get_url($id));
// Only show a report if the available updates have been fetched!
if (!empty($modules) && !empty($modules['projects']) && !empty($modules['available'])) {
@@ -149,7 +181,7 @@ function prod_monitor_updates($modules) {
'No information is available about potential new releases for currently installed modules and themes. To check for updates, you may need to !cron or you can !check. Please note that checking for available updates can take a long time, so please be patient.',
array(
'!cron' => l(t('run cron'), 'admin/reports/status/run-cron', array('attributes' => array('title' => t('run cron')), 'query' => $destination)),
'!check' => l(t('check manually'), 'admin/reports/prod-monitor/site/'.$id.'/update-check', array('attributes' => array('title' => t('check manually')))),
'!check' => l(t('check manually'), 'admin/reports/prod-monitor/site/' . $id . '/update-check', array('attributes' => array('title' => t('check manually')))),
)
)
)
@@ -179,7 +211,7 @@ function prod_monitor_updates_check($id) {
else {
drupal_set_message(t('No module data available: cannot check for updates!'), 'error');
}
drupal_goto('admin/reports/prod-monitor/site/'.$id.'/view/updates');
drupal_goto('admin/reports/prod-monitor/site/' . $id . '/view/updates');
}
/**
@@ -188,18 +220,27 @@ function prod_monitor_updates_check($id) {
function prod_monitor_overview_form($form, &$form_state, $edit = FALSE) {
drupal_set_title(t('Production monitor settings'));
$base = drupal_get_path('module', 'prod_monitor');
drupal_add_css($base.'/css/prod-monitor.css', 'file');
drupal_add_js($base.'/js/jquery.equalheights.js', 'file');
drupal_add_js($base.'/js/prod-monitor.js', 'file');
drupal_add_css($base . '/css/prod-monitor.css', 'file');
drupal_add_js($base . '/js/jquery.equalheights.js', 'file');
drupal_add_js($base . '/js/prod-monitor.js', 'file');
$form = array();
$collapsed = FALSE;
if (!$edit) {
// Button to initiate our fetch all batcher.
$form['fetch_all_submit'] = array(
'#weight' => -100,
'#type' => 'submit',
'#value' => t('Fetch all'),
'#submit' => array('prod_monitor_fetch_all_submit'),
'#limit_validation_errors' => array(),
);
// Add new site situation.
$sites = _prod_monitor_get_sites();
$api_key = $url = '';
$dbconnect_path = $api_key = $url = '';
$options = array();
$button = t('Get settings');
if (!empty($sites)) {
@@ -211,6 +252,7 @@ function prod_monitor_overview_form($form, &$form_state, $edit = FALSE) {
if (!empty($form_state['storage']['get_settings'])) {
// Second step of add new site situation.
$api_key = $form_state['values']['api_key'];
$dbconnect_path = $form_state['values']['dbconnect_path'];
$url = $form_state['values']['url'];
$button = t('Add site');
$collapsed = FALSE;
@@ -227,6 +269,7 @@ function prod_monitor_overview_form($form, &$form_state, $edit = FALSE) {
}
drupal_set_title(t('Production monitor settings for !url', array('!url' => _prod_monitor_sanitize_url($url))));
$api_key = $site['settings']['api_key'];
$dbconnect_path = $site['settings']['dbconnect_path'];
$options = $site['settings']['checks'];
if (isset($site['settings']['checks']['perf_data'])) {
$perf_enabled = $site['settings']['checks']['perf_data'];
@@ -255,7 +298,7 @@ function prod_monitor_overview_form($form, &$form_state, $edit = FALSE) {
$form['sites']['api_key'] = array(
'#type' => 'textfield',
'#title' => t('The website\'s API key'),
'#title' => t("The website's API key"),
'#default_value' => $api_key,
'#description' => t('Enter the API key you have configured for this site using the <em>Production check</em> module.'),
'#size' => 60,
@@ -263,6 +306,15 @@ function prod_monitor_overview_form($form, &$form_state, $edit = FALSE) {
'#required' => TRUE,
);
$form['sites']['dbconnect_path'] = array(
'#type' => 'textfield',
'#title' => t('DB connect check script'),
'#default_value' => $dbconnect_path,
'#description' => t('Enter the relative path, starting from the Drupal root, to the <em>prod_check.dbconnect.php</em> on the remote site. This wil usually be something like sites/all/modules/contrib/prod_check/prod_check.dbconnect.php . <strong>Leave empty if you do not want do enable this check!</strong>'),
'#size' => 60,
'#maxlength' => 512,
);
// Only show on second step of add form or when editing.
if (!empty($form_state['storage']['get_settings']) || $edit) {
// Get the settings from the remote site. We always do this when the form is
@@ -303,7 +355,7 @@ function prod_monitor_overview_form($form, &$form_state, $edit = FALSE) {
'#description' => t($data['description']),
'#options' => $data['functions'],
'#default_value' => array_keys($data['functions']),
'#prefix' => '<div class="prod-check-settings '.(($rest) ? 'odd' : 'even').'">',
'#prefix' => '<div class="prod-check-settings ' . (($rest) ? 'odd' : 'even') . '">',
'#suffix' => '</div>',
);
$i++;
@@ -398,8 +450,8 @@ function _prod_monitor_overview_form_table($sites) {
$view = t('View');
$flush = t('Flush');
if ($site_info['data']) {
$view = l(t('View'), 'admin/reports/prod-monitor/site/'.$id, array('attributes' => array('title' => t('View'))));
$flush = l(t('Flush'), 'admin/reports/prod-monitor/site/'.$id.'/flush', array('attributes' => array('title' => t('Flush'))));
$view = l(t('View'), 'admin/reports/prod-monitor/site/' . $id, array('attributes' => array('title' => t('View'))));
$flush = l(t('Flush'), 'admin/reports/prod-monitor/site/' . $id . '/flush', array('attributes' => array('title' => t('Flush'))));
}
$update_status = _prod_monitor_get_update_status($id);
@@ -407,7 +459,7 @@ function _prod_monitor_overview_form_table($sites) {
if (!empty($site_info['status'])) {
$title = t(ucwords($site_info['status']));
$status = array('data' => '<strong>'.l($title, 'admin/reports/prod-monitor/site/'.$id, array('attributes' => array('title' => $title, 'class' => $site_info['status']))).'</strong>', 'class' => array($site_info['status']));
$status = array('data' => '<strong>' . l($title, 'admin/reports/prod-monitor/site/' . $id, array('attributes' => array('title' => $title, 'class' => $site_info['status']))) . '</strong>', 'class' => array($site_info['status']));
}
else {
$status = '';
@@ -424,17 +476,17 @@ function _prod_monitor_overview_form_table($sites) {
(!$site_info['lastupdate']) ? t('Not yet updated.') : $site_info['lastupdate'],
/* Compose links. */
$view,
l(t('Edit'), 'admin/reports/prod-monitor/site/'.$id.'/edit', array('query' => $home, 'attributes' => array('title' => t('Edit')))),
l(t('Fetch data'), 'admin/reports/prod-monitor/site/'.$id.'/fetch', array('attributes' => array('title' => t('Fetch & View')))),
l(t('Edit'), 'admin/reports/prod-monitor/site/' . $id . '/edit', array('query' => $home, 'attributes' => array('title' => t('Edit')))),
l(t('Fetch data'), 'admin/reports/prod-monitor/site/' . $id . '/fetch', array('attributes' => array('title' => t('Fetch & View')))),
$flush,
l(t('Delete'), 'admin/reports/prod-monitor/site/'.$id.'/delete', array('attributes' => array('title' => t('Delete')))),
l(t('Delete'), 'admin/reports/prod-monitor/site/' . $id . '/delete', array('attributes' => array('title' => t('Delete')))),
),
'class' => array($site_info['status']),
);
$rows[] = $row;
}
return theme('table', array('header' => $headers, 'rows' => $rows));
return theme('table', array('prod_monitor_id' => 'overview_form', 'header' => $headers, 'rows' => $rows));
}
/**
@@ -501,6 +553,8 @@ function prod_monitor_overview_form_submit($form, &$form_state) {
$site->settings = serialize(
array(
'api_key' => $form_state['values']['api_key'],
// Trim spaces and / from left and right side.
'dbconnect_path' => trim($form_state['values']['dbconnect_path'], ' /'),
'functions' => $form_state['storage']['functions'],
'checks' => $checks,
)
@@ -511,9 +565,12 @@ function prod_monitor_overview_form_submit($form, &$form_state) {
if ($result) {
drupal_set_message(t('Website %url correctly saved.', array('%url' => $site->url)));
if ($form_state['values']['fetch']) {
$site_info = _prod_monitor_get_site($site->id, TRUE);
$site_info = _prod_monitor_get_site($site->id, 'all');
// First: all checks.
_prod_monitor_retrieve_data($site->id, $site_info, TRUE);
$form_state['redirect'] = 'admin/reports/prod-monitor/site/'.$site->id;
// MUST be second because of the status update!
_prod_monitor_db_connect_check($site->id, $site_info);
$form_state['redirect'] = 'admin/reports/prod-monitor/site/' . $site->id;
}
}
else {
@@ -523,13 +580,81 @@ function prod_monitor_overview_form_submit($form, &$form_state) {
}
}
/**
* Submit handler for the fetch all button.
*/
function prod_monitor_fetch_all_submit($form, &$form_state) {
_prod_monitor_fetch_all_data_batcher_create();
}
/**
* Function to create the batch process. Also used in Drush!
*/
function _prod_monitor_fetch_all_data_batcher_create($fetch_only = FALSE, $update_only = FALSE, $msg = TRUE) {
$title = t('Fetching all site data and checking for updates...');
if ($fetch_only) {
$title = t('Fetching all site data...');
}
if ($update_only) {
$title = t('Checking for updates...');
}
$batch = array(
'operations' => array(
array('prod_monitor_fetch_all_data_batcher', array($fetch_only, $update_only, $msg)),
),
'title' => $title,
'file' => drupal_get_path('module', 'prod_monitor') . '/includes/prod_monitor.admin.inc',
);
batch_set($batch);
}
/**
* Batch fetching of all site info.
*/
function prod_monitor_fetch_all_data_batcher($fetch_only, $update_only, $msg, &$context) {
$sandbox = &$context['sandbox'];
if (empty($context['sandbox'])) {
$sandbox['sites'] = array_keys(_prod_monitor_get_sites());
$sandbox['count'] = count($sandbox['sites']);
}
// Get site info.
$id = array_shift($sandbox['sites']);
$site_info = _prod_monitor_get_site($id, 'all');
if (!$update_only) {
// First: all checks.
_prod_monitor_retrieve_data($id, $site_info, $msg);
// MUST be second because of the status update!
_prod_monitor_db_connect_check($id, $site_info);
}
if (!$fetch_only) {
// Module update status.
$modules = prod_monitor_load($id);
if (!empty($modules) && !empty($modules['projects'])) {
module_load_include('inc', 'prod_monitor', 'includes/prod_monitor.update');
_prod_monitor_update_refresh($id, $modules['projects'], $modules['sitekey']);
_prod_monitor_calculate_project_data($id, $modules['projects'], $modules['available']);
}
}
$context['message'] = t('Updating data for %url', array('%url' => $site_info['url']));
$context['finished'] = empty($sandbox['sites']) ? 1 : (1 - count($sandbox['sites']) / $sandbox['count']);
}
/**
* Callback to fetch site data
*/
function prod_monitor_fetch_data($id) {
$site_info = _prod_monitor_get_site($id, TRUE);
$site_info = _prod_monitor_get_site($id, 'all');
// First: all checks.
_prod_monitor_retrieve_data($id, $site_info, TRUE);
drupal_goto('admin/reports/prod-monitor/site/'.$id);
// MUST be second because of the status update!
_prod_monitor_db_connect_check($id, $site_info);
drupal_goto('admin/reports/prod-monitor/site/' . $id);
}
/**
@@ -550,7 +675,7 @@ function prod_monitor_flush_form($form, &$form_state, $id) {
'#value' => $url,
);
return confirm_form($form, t('Are you sure you wish to delete all fetched data for %url?', array('%url' => $url)), 'admin/reports/prod-monitor', t('Note that the module update status data will not be flushed!').'<br />'.t('This action cannot be undone.'));
return confirm_form($form, t('Are you sure you wish to delete all fetched data for %url?', array('%url' => $url)), 'admin/reports/prod-monitor', t('Note that the module update status data will not be flushed!') . '<br />' . t('This action cannot be undone.'));
}
/**
@@ -634,6 +759,95 @@ function _prod_monitor_update_data_form($form, $form_state, $id, $site_info) {
}
function _prod_monitor_update_data_form_submit($form, &$form_state) {
// First: all checks.
_prod_monitor_retrieve_data($form_state['values']['site_id'], $form_state['values']['site_info'], TRUE);
// MUST be second because of the status update!
_prod_monitor_db_connect_check($form_state['values']['site_id'], $form_state['values']['site_info']);
}
/**
* Callback for module lookup form.
*
*/
function prod_monitor_module_lookup_form($form, &$form_state) {
$form = array();
// Show results.
if (isset($form_state['projects'])) {
$module = $form_state['values']['module'];
$rows = array();
foreach ($form_state['projects'] as $id => $project) {
$application = db_select('prod_monitor_sites', 'pms')->fields('pms', array('url'))->condition('id', $id)->execute()->fetchField();
$version = $project['info']['version'];
$rows[] = array(
$application,
$version,
l(t('View'), 'admin/reports/prod-monitor/site/' . $id),
);
}
$form['title'] = array(
'#theme' => 'html_tag',
'#tag' => 'h2',
'#value' => t('Applications where %module is used', array('%module' => $module)),
);
$form['table'] = array(
'#theme' => 'table',
'#rows' => $rows,
'#header' => array(t('Application'), t('Version'), t('View')),
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Perform another lookup'),
);
}
// Show lookup form.
else {
$form['module'] = array(
'#type' => 'textfield',
'#title' => t('Module name'),
'#required' => TRUE,
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Fetch Applications'),
);
}
return $form;
}
/**
* Submission handler for module lookup form
*/
function prod_monitor_module_lookup_form_submit(&$form, &$form_state) {
if (isset($form_state['values']['module'])) {
$projects = db_select('prod_monitor_site_modules', 'psm')
->fields('psm', array('id', 'projects'))
->condition('projects', '%' . db_like($form_state['values']['module']) . '%', 'LIKE')
->execute()->fetchAllAssoc('id');
$form_state['rebuild'] = TRUE;
if ($projects && !empty($projects)) {
foreach($projects as $project) {
$modules = unserialize($project->projects);
if (isset($modules[$form_state['values']['module']])) {
$form_state['projects'][$project->id] = $modules[$form_state['values']['module']];
}
}
}
if (!isset($form_state['projects'])) {
drupal_set_message(t('No projects found for :module', array(':module' => $form_state['values']['module'])));
}
}
else {
unset($form_state['projects']);
}
}
@@ -1,5 +1,20 @@
<?php
/**
* Implements hook_preprocess_prod_monitor_update_report().
*/
function prod_monitor_preprocess_prod_monitor_update_report(&$vars) {
if (is_array($vars['data'])) {
foreach (array_keys($vars['data']) as $module_id) {
// When ignoring a module, we'll give a reason.
if (isset($vars['data'][$module_id]['ignored'])) {
$vars['data'][$module_id]['reason'] = t('Being ignored');
}
}
}
}
/**
* Returns HTML for the project status report.
*
@@ -15,12 +30,12 @@ function theme_prod_monitor_update_report($variables) {
$data = $variables['data'];
if (!is_array($data)) {
$output = '<p>'. $data .'</p>';
$output = '<p>' . $data . '</p>';
return $output;
}
$output = '<div class="update checked">'. ($last ? t('Last checked: @time ago', array('@time' => format_interval(time() - $last))) : t('Last checked: never'));
$output .= ' <span class="check-manually">('. l(t('Check manually'), 'admin/reports/prod-monitor/site/'.$id.'/update-check') .')</span>';
$output .= ' <span class="check-manually">('. l(t('Check manually'), 'admin/reports/prod-monitor/site/' . $id . '/update-check') . ')</span>';
$output .= "</div>\n";
$header = array();
@@ -226,7 +241,7 @@ function theme_prod_monitor_update_report($variables) {
if (!empty($rows[$type_name])) {
ksort($rows[$type_name]);
$output .= "\n<h3>" . $type_label . "</h3>\n";
$output .= theme('table', array('header' => $header, 'rows' => $rows[$type_name], 'attributes' => array('class' => array('update'))));
$output .= theme('table', array('prod_monitor_id' => 'update_report', 'header' => $header, 'rows' => $rows[$type_name], 'attributes' => array('class' => array('update'))));
}
}
drupal_add_css(drupal_get_path('module', 'update') . '/update.css');
@@ -29,7 +29,7 @@ function _prod_monitor_update_refresh($id, $projects, $site_key) {
// As replacement for DRUPAL_CORE_COMPATIBILITY since prod_check and
// prod_monitor should be site independant.
$core = explode('.', $projects['drupal']['info']['version']);
$core = $core[0].'.x';
$core = $core[0] . '.x';
$max_fetch_attempts = UPDATE_MAX_FETCH_ATTEMPTS;
@@ -65,10 +65,10 @@ function _prod_monitor_update_refresh($id, $projects, $site_key) {
}
}
$modules->available = serialize($available);
watchdog('prod_monitor', 'Attempted to fetch information about all available new releases and updates for %link.', array('%link' => _prod_monitor_get_url($id)), WATCHDOG_NOTICE, l(t('view'), 'admin/reports/prod-monitor/site/'.$id.'/view/updates'));
watchdog('prod_monitor', 'Fetched information about all available new releases and updates for %link.', array('%link' => _prod_monitor_get_url($id)), WATCHDOG_NOTICE, l(t('view'), 'admin/reports/prod-monitor/site/' . $id . '/view/updates'));
}
else {
watchdog('prod_monitor', 'Unable to fetch any information about available new releases and updates for %link.', array('%link' => _prod_monitor_get_url($id)), WATCHDOG_ERROR, l(t('view'), 'admin/reports/prod-monitor/site/'.$id.'/view/updates'));
watchdog('prod_monitor', 'Unable to fetch any information about available new releases and updates for %link.', array('%link' => _prod_monitor_get_url($id)), WATCHDOG_ERROR, l(t('view'), 'admin/reports/prod-monitor/site/' . $id . '/view/updates'));
}
// Whether this worked or not, we did just (try to) check for updates.
$modules->lastupdate = time();
@@ -99,7 +99,7 @@ function _prod_monitor_update_refresh($id, $projects, $site_key) {
function _prod_monitor_update_build_fetch_url($project, $site_key = '', $core) {
$name = $project['name'];
$url = _prod_monitor_update_get_fetch_url_base($project);
$url .= '/'. $name .'/'. $core;
$url .= '/' . $name . '/' . $core;
// Only append a site_key and the version information if we have a site_key
// in the first place, and if this is not a disabled module or theme. We do
// not want to record usage statistics for disabled code.
@@ -169,6 +169,19 @@ function _prod_monitor_calculate_project_data($id, $projects, $available) {
}
}
// Handle the ignored modules.
if (($ignored = _prod_monitor_get_site_ignored($id))
&& !empty($ignored['updates'])) {
foreach ($ignored['updates'] as $project_name) {
if (isset($projects[$project_name])) {
$projects[$project_name]['ignored'] = TRUE;
$projects[$project_name]['status'] = UPDATE_UNKNOWN;
}
}
}
drupal_alter('prod_monitor_project_data', $id, $projects, $available);
// Check if we need to flag a security update warning.
// Prepare object to store generated data to DB.
$modules = new stdClass();
@@ -0,0 +1,41 @@
<?php
/**
* @file
* Documentation on api functions for prod_monitor.
*
* @ingroup prod_monitor
* @{
*/
/**
* Implements hook_prod_monitor_ignore().
*
* Allows modules to specify certain ignore directives, currently
* a list of modules whose update status should be ignored.
*
* @see _prod_monitor_get_site_ignored().
* @see _prod_monitor_calculate_project_data().
*/
function hook_prod_monitor_ignore($site_id) {
$ignore = array('updates' => array());
// Ignore this module (suppress warnings) because we cannot do anything
// about it's update status as it has been abandoned and we are not going
// to stop using it.
if ($site_id == 12) {
$ignore['updates'][] = 'node_embed';
}
return $ignore;
}
/**
* Implements hook_prod_monitor_project_data_alter().
*
* Allows modules to alter the data being calculated for a project.
*
* @see _prod_monitor_calculate_project_data().
*/
function hook_prod_monitor_project_data_alter($site_id, &$data, $available) {
}
@@ -15,14 +15,15 @@ function prod_monitor_drush_command() {
),
);
$items['prod-monitor-updates'] = array(
'callback' => '_drush_prod_monitor_updates',
'callback' => 'drush_prod_monitor_updates',
'description' => 'Display the update module status page',
'aliases' => array('pmon-up'),
'arguments' => array(
'id' => 'ID of the site to view module updates for.',
),
'options' => array(
'--check' => 'Check for module updates.'
'check' => 'Check for module updates.',
'security-only' => 'Only show modules that have security updates available.',
),
);
$items['prod-monitor-fetch'] = array(
@@ -58,20 +59,38 @@ function prod_monitor_drush_command() {
*/
function drush_prod_monitor_fetch() {
$args = func_get_args();
foreach($args as $arg) {
$site = _prod_monitor_get_site($arg);
if (!empty($site['url'])) {
$result = _prod_monitor_retrieve_data($arg, $site);
$site['url'] = _prod_monitor_sanitize_url(rtrim($site['url'], '/'));
if ($result === FALSE) {
drush_print("\033[1;31m".dt('Error:')." \033[0m".dt('Unable to fetch data for').' '.$site['url'].'!');
}
else {
drush_print(dt('Sucessfully fetched data for').' '.$site['url'].'.');
}
// Fetch ALL.
if (empty($args)) {
if (!drush_confirm(dt('Do you really want to fetch the data for ALL remote sites?'))) {
drush_set_error('prod_monitor', dt('Aborting.'));
return;
}
else {
drush_print("\033[1;31m".dt('Error:')." \033[0m".dt('No site found with ID').' '.$arg.'!');
module_load_include('inc', 'prod_monitor', 'includes/prod_monitor.admin');
// Batch process data fetching.
_prod_monitor_fetch_all_data_batcher_create(TRUE, FALSE, FALSE);
drush_backend_batch_process();
}
}
// Fetch one or more sites as requested.
else {
foreach($args as $arg) {
$site = _prod_monitor_get_site($arg);
if (!empty($site['url'])) {
$result = _prod_monitor_retrieve_data($arg, $site);
$site['url'] = _prod_monitor_sanitize_url(rtrim($site['url'], '/'));
if ($result === FALSE) {
drush_print("\033[1;31m" . dt('Error:') . " \033[0m" . dt('Unable to fetch data for') . ' ' . $site['url'] . '!');
}
else {
_prod_monitor_db_connect_check($arg, $site);
drush_print(dt('Sucessfully fetched data for') . ' ' . $site['url'] . '.');
}
}
else {
drush_print("\033[1;31m" . dt('Error:') . " \033[0m" . dt('No site found with ID') . ' ' . $arg . '!');
}
}
}
}
@@ -84,19 +103,19 @@ function drush_prod_monitor_flush() {
foreach ($args as $arg) {
$url = _prod_monitor_get_url($arg);
if (!empty($url)) {
if (!drush_confirm(dt('Do you really want to flush all data for').' '.$url.'?')) {
if (!drush_confirm(dt('Do you really want to flush all data for') . ' ' . $url . '?')) {
drush_die('Aborting.');
}
$result = _prod_monitor_flush_data($arg);
if ($result === FALSE) {
drush_print("\033[1;31m".dt('Error:')." \033[0m".dt('Unable to flush data!'));
drush_print("\033[1;31m" . dt('Error:') . " \033[0m" . dt('Unable to flush data!'));
}
else {
drush_print(dt('Stored data successfully flushed.'));
}
}
else {
drush_print("\033[1;31m".dt('Error:')." \033[0m".dt('No site found with ID').' '.$arg.'!');
drush_print("\033[1;31m" . dt('Error:') . " \033[0m" . dt('No site found with ID') . ' ' . $arg . '!');
}
}
}
@@ -109,19 +128,19 @@ function drush_prod_monitor_delsite() {
foreach ($args as $arg) {
$url = _prod_monitor_get_url($arg);
if (!empty($url)) {
if (!drush_confirm(dt("Do you really want to delete").' '.$url.' '.dt('and all its data?'))) {
if (!drush_confirm(dt("Do you really want to delete") . ' ' . $url . ' ' . dt('and all its data?'))) {
drush_die('Aborting.');
}
$result = _prod_monitor_delete_site($arg);
if ($result === FALSE) {
drush_print("\033[1;31m".dt('Error:')." \033[0m".dt('Unable to delete') . $url .'!');
drush_print("\033[1;31m" . dt('Error:') . " \033[0m" . dt('Unable to delete') . $url . '!');
}
else {
drush_print(dt('Website successfully deleted.'));
}
}
else {
drush_print("\033[1;31m".dt('Error:')." \033[0m".dt('No site found with ID').' '.$arg.'!');
drush_print("\033[1;31m" . dt('Error:') . " \033[0m" . dt('No site found with ID') . ' ' . $arg . '!');
}
}
}
@@ -135,7 +154,7 @@ function drush_prod_monitor_statusdetail() {
if (empty($args)) {
_drush_prod_monitor_overview();
}
else if (is_numeric($args[0])) {
elseif (is_numeric($args[0])) {
_drush_prod_monitor_detail($args[0]);
}
}
@@ -158,7 +177,7 @@ function _drush_prod_monitor_overview() {
dt('Last update'),
dt('Status'),
));
// TODO: check why the colour coding messes up the tabs for the table
// Worked around this by placing the status column last
foreach ($sites as $id => $site_info) {
@@ -168,10 +187,10 @@ function _drush_prod_monitor_overview() {
(!$site_info['data']) ? dt('Empty') : t('Stored'),
$site_info['added'],
(!$site_info['lastupdate']) ? dt('Not yet updated') : $site_info['lastupdate'],
"\033[".$severity[$site_info['status']].'m'.ucwords($site_info['status'])."\033[0m",
"\033[" . $severity[$site_info['status']] . 'm' . ucwords($site_info['status']) . "\033[0m",
);
}
drush_print("\033[1m".dt('Production Monitor status')."\033[0m\n", 1);
drush_print("\033[1m" . dt('Production Monitor status') . "\033[0m\n", 1);
if (count($rows) > 1) {
drush_print_table($rows, TRUE);
drush_print(dt('Use drush prod-monitor [id] to view the details of a specific site.'));
@@ -182,9 +201,9 @@ function _drush_prod_monitor_overview() {
}
function _drush_prod_monitor_detail($id) {
$site = _prod_monitor_get_site($id, TRUE);
$site = _prod_monitor_get_site($id, 'all');
if (!isset($site['url'])) {
drush_print("\033[1;31m".dt('Error:')." \033[0m".dt('No site found with ID').' '.$id.'!');
drush_print("\033[1;31m" . dt('Error:') . " \033[0m" . dt('No site found with ID') . ' ' . $id . '!');
return;
}
@@ -219,7 +238,7 @@ function _drush_prod_monitor_detail($id) {
$updates = $color . $title . "\033[0m";
// Construct block
$block[] = array("\033[1m".dt('Overall status')."\033[0m");
$block[] = array("\033[1m" . dt('Overall status') . "\033[0m");
$block[] = array(
dt('Drupal core version'),
$modules['projects']['drupal']['info']['version'],
@@ -254,12 +273,12 @@ function _drush_prod_monitor_detail($id) {
foreach ($functions as $set => $data) {
if (isset($site['data'][$set])) {
$rows[] = array('');
$rows[] = array("\033[1m".dt($data['title'])."\033[0m");
$rows[] = array("\033[1m" . dt($data['title']) . "\033[0m");
if (!empty($site['data'][$set])) {
foreach ($site['data'][$set] as $check => $result) {
$rows[] = array(
$result['title'],
"\033[".$severity[$result['severity']].'m'.strip_tags($result['value'])."\033[0m",
"\033[" . $severity[$result['severity']] . 'm' . strip_tags($result['value']) . "\033[0m",
);
if ($error < $result['severity']) {
$error = $result['severity'];
@@ -273,7 +292,7 @@ function _drush_prod_monitor_detail($id) {
}
// Actual printing.
drush_print("\033[1m".dt('Production Monitor status for').' '._prod_monitor_sanitize_url($url)."\033[0m", 1);
drush_print("\033[1m" . dt('Production Monitor status for') . ' ' . _prod_monitor_sanitize_url($url) . "\033[0m", 1);
if (!empty($block)) {
drush_print_table($block);
}
@@ -286,76 +305,109 @@ function _drush_prod_monitor_detail($id) {
if ($error > 0) {
// Would be cool if we could prefix the admin path with http://<host>/ so it
// will become a clickable link in some terminals. Any ideas?
drush_print("\033[1m".dt('Some errors were reported!')."\033[0m ".dt('Check the full status page on')." \033[1m".'admin/reports/prod-monitor/'.$id.'/view'."\033[0m ".dt('for details.'));
drush_print("\033[1m" . dt('Some errors were reported!') . "\033[0m " . dt('Check the full status page on') . " \033[1m" . 'admin/reports/prod-monitor/' . $id . '/view' . "\033[0m " . dt('for details.'));
}
}
/**
* Update status page callback.
*/
function _drush_prod_monitor_updates() {
function drush_prod_monitor_updates() {
$id = func_get_args();
if (empty($id)) {
drush_set_error('prod_monitor', dt('You must provide a site ID!'));
return;
}
$id = $id['0'];
// Get module info.
$modules = _prod_monitor_get_site_modules($id);
$url = _prod_monitor_get_url($id);
if (empty($modules)) {
if (empty($url)) {
drush_set_error('prod_monitor', dt('No site found with ID').' '. $id .'!');
// Fetch ALL.
if (empty($id)) {
if (!drush_confirm(dt('Do you really want to check ALL sites for module updates?'))) {
drush_set_error('prod_monitor', dt('Aborting.'));
return;
}
else {
drush_set_error('prod_monitor', dt('No module data found for') .' '. $url.'!');
return;
module_load_include('inc', 'prod_monitor', 'includes/prod_monitor.admin');
// Batch process update checking.
_prod_monitor_fetch_all_data_batcher_create(FALSE, TRUE, FALSE);
drush_backend_batch_process();
}
}
else if (empty($modules['available'])) {
drush_set_error('prod_monitor', dt('No update data found for') .' '. $url.'!');
// No data, ask for refresh.
_drush_prod_monitor_update_refresh($id, $modules);
}
// Fetch the site as requested.
else {
$id = $id['0'];
// Refresh if user asked for it.
if (drush_get_option('check')) {
_drush_prod_monitor_update_refresh($id, $modules);
}
$last = $modules['lastupdate'];
module_load_include('inc', 'prod_monitor', 'includes/prod_monitor.update');
$projects = _prod_monitor_calculate_project_data($id, $modules['projects'], $modules['available']);
// Cleanup.
unset($modules);
// Table headers.
$rows[] = array(dt('Name'), dt('Installed version'), dt('Proposed version'), dt('Status'));
// Process releases, notifying user of status and building a list of proposed updates
drush_include_engine('update_info', 'drupal', NULL, DRUSH_BASE_PATH . '/commands/pm/update_info');
drush_include(DRUSH_BASE_PATH . '/commands/pm', 'updatecode.pm');
$updateable = pm_project_filter($projects, $rows);
// Pipe preparation
if (drush_get_context('DRUSH_PIPE')) {
$pipe = "";
foreach($projects as $project){
$pipe .= $project['name']. " ";
$pipe .= $project['existing_version']. " ";
$pipe .= $project['candidate_version']. " ";
$pipe .= str_replace(' ', '-', pm_update_filter($project)). "\n";
// Get module info.
$modules = _prod_monitor_get_site_modules($id);
$url = _prod_monitor_get_url($id);
if (empty($modules)) {
if (empty($url)) {
drush_set_error('prod_monitor', dt('No site found with ID') . ' ' . $id . '!');
return;
}
else {
drush_set_error('prod_monitor', dt('No module data found for') . ' ' . $url . '!');
return;
}
}
elseif (empty($modules['available'])) {
drush_set_error('prod_monitor', dt('No update data found for') . ' ' . $url . '!');
// No data, ask for refresh.
_drush_prod_monitor_update_refresh($id, $modules);
}
drush_print_pipe($pipe);
// Automatically curtail update process if in pipe mode
$updateable = FALSE;
}
drush_print("\033[1m".dt('Module update status for').' '.$url."\033[0m", 1);
drush_print(dt('Update information last refreshed:') .' '. ($last ? format_date($last) : dt('Never'))."\n", 1);
drush_print_table($rows, TRUE);
// Refresh if user asked for it.
if (drush_get_option('check')) {
_drush_prod_monitor_update_refresh($id, $modules);
}
$security_only = drush_get_option('security-only');
$last = $modules['lastupdate'];
module_load_include('inc', 'prod_monitor', 'includes/prod_monitor.update');
$projects = _prod_monitor_calculate_project_data($id, $modules['projects'], $modules['available']);
// Cleanup.
unset($modules);
// Table headers.
$rows[] = array(dt('Name'), dt('Installed version'), dt('Proposed version'), dt('Status'));
// Process releases, notifying user of status and building a list of proposed updates
drush_include_engine('update_info', 'drupal', NULL, DRUSH_BASE_PATH . '/commands/pm/update_info');
drush_include(DRUSH_BASE_PATH . '/commands/pm', 'updatecode.pm');
foreach ($projects as $project) {
// Only show security updates if the user requested it.
if ($security_only && $project['status'] !== UPDATE_NOT_SECURE) {
continue;
}
// Add color to the status.
$color = "\033[0m";
switch ($project['status']) {
case UPDATE_CURRENT:
$color = "\033[1;32m";
break;
case UPDATE_NOT_CURRENT:
$color = "\033[1;33m";
break;
case UPDATE_NOT_SECURE:
$color = "\033[1;31m";
break;
}
// Translate status to readable name and fill 'candidate_version'.
$status = pm_update_filter($project);
// Generate row.
$rows[] = array(
$project['name'],
$project['existing_version'],
$project['candidate_version'],
$color . $status . "\033[0m",
);
}
drush_print("\033[1m" . dt('Module update status for') . ' ' . $url . "\033[0m", 1);
drush_print(dt('Update information last refreshed:') . ' ' . ($last ? format_date($last) : dt('Never')) . "\n", 1);
if (count($rows) > 1) {
drush_print_table($rows, TRUE);
}
else {
drush_print("\033[1m" . dt('No updates to show!') . "\033[0m", 1);
}
}
}
/**
@@ -375,7 +427,7 @@ function _drush_prod_monitor_update_refresh($id, &$modules) {
$modules['lastupdate'] = time();
}
else {
drush_set_error('prod_monitor', dt('Failed to refres update status information for') .' '. $url.'!');
drush_set_error('prod_monitor', dt('Failed to refres update status information for') . ' ' . $url . '!');
drush_die('Aborting.');
}
}
@@ -4,9 +4,9 @@ package = Monitoring
core = 7.x
configure = admin/reports/prod-monitor
; Information added by packaging script on 2013-11-25
version = "7.x-1.8"
; Information added by Drupal.org packaging script on 2015-08-04
version = "7.x-1.9"
core = "7.x"
project = "prod_check"
datestamp = "1385405033"
datestamp = "1438700931"
@@ -149,29 +149,6 @@ function prod_monitor_schema() {
);
}
/**
* Implementation of hook_requirements().
*/
function prod_monitor_requirements($phase) {
$requirements = array();
switch ($phase) {
case 'install':
if (module_exists('update')) {
$requirements['prod_monitor_update'] = array(
'title' => t('Production monitor'),
'value' => t('Update manager enabled.'),
'severity' => REQUIREMENT_ERROR,
'description' => t('You have enabled <em>Update manager</em>. You have to disable this module before enabling Production monitor!'),
);
}
break;
}
return $requirements;
}
/**
* Implementation of hook_uninstall().
*/
@@ -294,3 +271,25 @@ function prod_check_update_7103() {
)
);
}
/**
* Add new dbconnect_path setting to database to prevent warnings.
*/
function prod_check_update_7104() {
$table = 'prod_monitor_sites';
// Fetch all settings.
$result = db_select($table, 'psm')
->fields('psm', array('id', 'settings'))
->execute();
// Update all sites to add new setting.
foreach ($result as $site) {
$settings = unserialize($site->settings);
$settings['dbconnect_path'] = '';
$site->settings = serialize($settings);
// Write to DB.
drupal_write_record($table, $site, array('id'));
}
}
@@ -13,21 +13,24 @@ define('PROD_MONITOR_REQUIREMENT_WARNING', 1);
define('PROD_MONITOR_REQUIREMENT_ERROR', 2);
/**
* We do the same here for the update module constants: redefine them so that we
* do not need to run the update module entirely!
* We don't want to load the Update module just to be able to run Production
* Monitor, so we copy the constants from Update. But we need to check them all
* so that we don't get notices if Update is already running.
*/
define('UPDATE_DEFAULT_URL', 'http://updates.drupal.org/release-history');
define('UPDATE_NOT_SECURE', 1);
define('UPDATE_REVOKED', 2);
define('UPDATE_NOT_SUPPORTED', 3);
define('UPDATE_NOT_CURRENT', 4);
define('UPDATE_CURRENT', 5);
define('UPDATE_NOT_CHECKED', -1);
define('UPDATE_UNKNOWN', -2);
define('UPDATE_NOT_FETCHED', -3);
define('UPDATE_FETCH_PENDING', -4);
define('UPDATE_MAX_FETCH_ATTEMPTS', 2);
define('UPDATE_MAX_FETCH_TIME', 5);
if (!module_exists('update')) {
define('UPDATE_DEFAULT_URL', 'http://updates.drupal.org/release-history');
define('UPDATE_NOT_SECURE', 1);
define('UPDATE_REVOKED', 2);
define('UPDATE_NOT_SUPPORTED', 3);
define('UPDATE_NOT_CURRENT', 4);
define('UPDATE_CURRENT', 5);
define('UPDATE_NOT_CHECKED', -1);
define('UPDATE_UNKNOWN', -2);
define('UPDATE_NOT_FETCHED', -3);
define('UPDATE_FETCH_PENDING', -4);
define('UPDATE_MAX_FETCH_ATTEMPTS', 2);
define('UPDATE_MAX_FETCH_TIME', 5);
}
/**
* Implementation of hook_help().
@@ -36,24 +39,29 @@ function prod_monitor_help($path, $arg) {
$output = '';
switch ($path) {
case 'admin/help#prod_monitor':
$output .= '<p>'.t('Production monitor is a module that can connect to the <strong>Production check</strong> module using <strong>XMLRPC</strong> and an <strong>API key</strong>. It will retrieve all specified data from the remote site to create a satus page and monitoring facility in a central place.').'<br />';
$output .= t('You can add multiple sites and configure per site what data you wish (not) to monitor, allowing you to setup a central Drupal site that will monitor all of your sites that have the <em>Production check</em> module with <em>XMLRPC</em> enabled.').'<br />';
$output .= t('The <strong>data retrieval</strong> mechanism can be called <strong>manually</strong> and is integrated with the <strong>cron</strong>, so you get a fresh update of data each cron run.').'</p>';
$output .= '<p>' . t('Production monitor is a module that can connect to the <strong>Production check</strong> module using <strong>XMLRPC</strong> and an <strong>API key</strong>. It will retrieve all specified data from the remote site to create a satus page and monitoring facility in a central place.') . '<br />';
$output .= t('You can add multiple sites and configure per site what data you wish (not) to monitor, allowing you to setup a central Drupal site that will monitor all of your sites that have the <em>Production check</em> module with <em>XMLRPC</em> enabled.') . '<br />';
$output .= t('The <strong>data retrieval</strong> mechanism can be called <strong>manually</strong> and is integrated with the <strong>cron</strong>, so you get a fresh update of data each cron run.') . '</p>';
break;
case 'admin/reports/prod-monitor':
$output .= '<p><strong>'.t('Site overview table').'</strong><br />';
$output .= t('The overview table gives you an overview of what sites you have added together with their status. The status will be the highest error detected in the retrieved data set.').'<br />';
$output .= t('The per site functions <strong>View</strong>, <strong>Edit</strong>, <strong>Fetch data</strong>, <strong>Flush</strong> and <strong>Delete</strong> should be self explanatory.').'</p>';
$output .= '<p><strong>' . t('Site overview table') . '</strong><br />';
$output .= t('The overview table gives you an overview of what sites you have added together with their status. The status will be the highest error detected in the retrieved data set.') . '<br />';
$output .= t('The per site functions <strong>View</strong>, <strong>Edit</strong>, <strong>Fetch data</strong>, <strong>Flush</strong> and <strong>Delete</strong> should be self explanatory.') . '</p>';
// No break!
case 'admin/reports/prod-monitor/site/%/edit':
$output .= '<p><strong>'.t('Website URL & API key').'</strong><br />';
$output .= t('To add a site, enter it\'s <strong>full url</strong>, including the protocol, but omitting the <em>xmlrpc.php</em> part and the <strong>API key</strong> that you have configured for it using the <strong>Production check</strong> module. Now click the <strong>Get settings</strong> button.').'<br />';
$output .= t('All of the checks that the <em>Production check</em> module can perform are fetched from the remote site and presented as an array of checkboxes. Finally you can configure what exactly you wish to monitor for this site, then hit the <strong>Add site</strong> button.').'<br />';
$output .= t('Each time you edit a site, the settings are fetched from the remote server so that any new checks that might have been added to the <em>Production check</em> module there are always up to date in the monitoring section.').'<br />'; $output .= t('<strong>Fetch data immediately</strong> does exactly what it says and fetches all the configured data from the remote site and will direct you to the report page.').'</p>';
$output .= '<p><strong>' . t('Website URL & API key') . '</strong><br />';
$output .= t("To add a site, enter it's <strong>full url</strong>, including the protocol, but omitting the <em>xmlrpc.php</em> part and the <strong>API key</strong> that you have configured for it using the <strong>Production check</strong> module. Now click the <strong>Get settings</strong> button.") . '<br />';
$output .= t('All of the checks that the <em>Production check</em> module can perform are fetched from the remote site and presented as an array of checkboxes. Finally you can configure what exactly you wish to monitor for this site, then hit the <strong>Add site</strong> button.') . '<br />';
$output .= t('Each time you edit a site, the settings are fetched from the remote server so that any new checks that might have been added to the <em>Production check</em> module there are always up to date in the monitoring section.') . '<br />';
$output .= t('<strong>Fetch data immediately</strong> does exactly what it says and fetches all the configured data from the remote site and will direct you to the report page.') . '</p>';
break;
case 'admin/reports/prod-monitor/module-lookup':
$output .= '<p><strong>' . t('Module name') . '</strong><br />';
$output .= t("Enter (part of) the module's machine name to see what sites are using the module.") . '<br />';
break;
case 'admin/reports/prod-monitor/site/%':
case 'admin/reports/prod-monitor/site/%/view':
$output .= '<p>'.t('This is an overview of all checks performed by the <em>Production check</em> module and their status <strong>on the remote site</strong>. You can click the links inside the report to jump to the module\'s settings page, or to go to the project page of a module, in case you need to download it for installation.').'</p>';
$output .= '<p>' . t("This is an overview of all checks performed by the <em>Production check</em> module and their status <strong>on the remote site</strong>. You can click the links inside the report to jump to the module's settings page, or to go to the project page of a module, in case you need to download it for installation.") . '</p>';
break;
}
return $output;
@@ -89,6 +97,25 @@ function prod_monitor_menu() {
'file' => 'includes/prod_monitor.admin.inc',
);
// Default primary tab (callback for this is it's parent path).
$items['admin/reports/prod-monitor/overview'] = array(
'title' => 'Overview',
'type' => MENU_DEFAULT_LOCAL_TASK,
'weight' => 0,
);
$items['admin/reports/prod-monitor/module-lookup'] = array(
'title' => 'Module lookup',
'description' => 'Searches monitored applications for module versions.',
'page callback' => 'drupal_get_form',
'page arguments' => array('prod_monitor_module_lookup_form'),
'access callback' => 'user_access',
'access arguments' => array('access production monitor'),
'type' => MENU_LOCAL_TASK,
'file' => 'includes/prod_monitor.admin.inc',
'weight' => 1,
);
// This hook_menu() thing, still can't fully see the logic in it. However,
// this here is what I want to achieve. It would be nice to see the /view/ bit
// in the path dissapear, that would finish it entirely. I'll settle for this
@@ -330,8 +357,10 @@ function prod_monitor_cron() {
foreach ($sites as $id => $site_info) {
$elapsed = time() - $cron_start;
if ($elapsed < $time_limit) {
//TODO: add module status update check here.
// First: all checks.
_prod_monitor_retrieve_data($id, $site_info);
// MUST be second because of the status update!
_prod_monitor_db_connect_check($id, $site_info);
$process++;
}
else {
@@ -367,7 +396,7 @@ function _prod_monitor_retrieve_functions($url, $api_key, $msg = TRUE) {
'error'
);
}
else if ($msg) {
elseif ($msg) {
drupal_set_message(t('Settings form updated, please adjust your settings.'));
}
@@ -469,7 +498,7 @@ function _prod_monitor_retrieve_data($id, $site_info, $msg = FALSE) {
);
}
}
else if ($msg) {
elseif ($msg) {
drupal_set_message(t('Module data for %link successfully updated.', array('%link' => $site_info['url'])));
}
}
@@ -495,7 +524,7 @@ function _prod_monitor_retrieve_data($id, $site_info, $msg = FALSE) {
);
}
}
else if ($msg) {
elseif ($msg) {
drupal_set_message(t('Performance data for %link successfully updated.', array('%link' => $site_info['url'])));
}
}
@@ -504,6 +533,42 @@ function _prod_monitor_retrieve_data($id, $site_info, $msg = FALSE) {
}
}
/**
* Perform separate dbconnect check. We cannot incorporate this at the side of
* prod_check as this would make no sense at all when the DB is down.
*/
function _prod_monitor_db_connect_check($id, $site_info) {
// Execute only if setup properly.
if (empty($site_info['settings']['dbconnect_path'])) {
return;
}
// Do the check.
$dbconnect_path = rtrim($site_info['url'], '/') . '/' . $site_info['settings']['dbconnect_path'];
$response = drupal_http_request($dbconnect_path);
if ($response->code !== '200' && $response->data !== 'OK') {
// Update status to notify the user of the problem!
$site_info['status'] = PROD_MONITOR_REQUIREMENT_ERROR;
$response->data = 'NOK';
}
// ALWAYS get stored site data as it should have been updated right before
// calling this function!
$site_data = _prod_monitor_get_site($id, 'data');
// Add data to site and save.
$site_data['data']['prod_mon']['prod_check_dbconnect'] = $response->code . ' ' . $response->data;
// Store site data
$site = new stdClass();
$site->id = $id;
if (isset($site_info['status'])) {
$site->status = $site_info['status'];
}
$site->data = serialize($site_data['data']);
$site->lastupdate = REQUEST_TIME;
$result = drupal_write_record('prod_monitor_sites', $site, array('id'));
}
/**
* Helper function to get all sites.
*/
@@ -526,7 +591,7 @@ function _prod_monitor_get_sites($start_id = FALSE) {
if (!empty($row->data)) {
foreach ($row->data as $set => $checks) {
foreach ($checks as $check => $results) {
$status = ($results['severity'] > $status) ? $results['severity'] : $status;
$status = (isset($results['severity']) && $results['severity'] > $status) ? $results['severity'] : $status;
}
}
$data_status = TRUE;
@@ -561,21 +626,33 @@ function _prod_monitor_get_sites($start_id = FALSE) {
*
* @param $id
* int site id.
* @param $all
* Boolean whether or not to return all fields or just the url and settings.
* @param $type
* String the amount of data to be returned.
*/
function _prod_monitor_get_site($id, $all = FALSE) {
if (!$all) {
$site = db_query("SELECT url, settings FROM {prod_monitor_sites} WHERE id = :id", array(':id' => $id))->fetchAssoc();
}
else {
$site = db_query("SELECT * FROM {prod_monitor_sites} WHERE id = :id", array(':id' => $id))->fetchAssoc();
function _prod_monitor_get_site($id, $type = 'settings') {
switch ($type) {
case 'settings':
$site = db_query("SELECT url, settings FROM {prod_monitor_sites} WHERE id = :id", array(':id' => $id))->fetchAssoc();
break;
case 'all':
$site = db_query("SELECT * FROM {prod_monitor_sites} WHERE id = :id", array(':id' => $id))->fetchAssoc();
break;
case 'data':
$site = db_query("SELECT data FROM {prod_monitor_sites} WHERE id = :id", array(':id' => $id))->fetchAssoc();
break;
}
if (!empty($site)) {
$site['settings'] = unserialize($site['settings']);
if ($all) {
$site['data'] = unserialize($site['data']);
switch ($type) {
case 'all':
$site['data'] = unserialize($site['data']);
// No break!
case 'settings':
$site['settings'] = unserialize($site['settings']);
break;
case 'data':
$site['data'] = unserialize($site['data']);
break;
}
}
@@ -607,6 +684,25 @@ function _prod_monitor_get_site_modules($id, $exists = FALSE) {
return $modules;
}
/**
* Returns an array of ignored directives by site id.
*
* @param int $id The site id.
*
* @return array
* - updates array List of project names, whose update status we'll ignore.
*/
function _prod_monitor_get_site_ignored($id) {
$ignored = &drupal_static(__FUNCTION__, array());
if (!array_key_exists($id, $ignored)) {
$ignored[$id] = module_invoke_all('prod_monitor_ignore', $id) + array(
'updates' => array(),
);
}
return $ignored[$id];
}
/**
* Helper function to get the module status of a site by ID.
*
@@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
@@ -0,0 +1,78 @@
<?php
function uc_ssl_admin()
{
$form = array();
$form['uc_ssl_ssl_domain'] = array(
'#type' => 'textfield',
'#title' => t('Secure Domain Name'),
'#default_value' => variable_get('uc_ssl_ssl_domain', ''),
//'#size' => 2,
//'#maxlength' => 2,
'#description' => t("This field accepts domain names in the following format: https://something.com OR https://something.com:5938. <br><br><b>IPMORTANT DRUPAL 7 NOTICE ABOUT DOMAIN NAMES:</b> This module DOES support using port numbers and different domains for SSL and normal HTTP like it's Drupal 6 predecessor HOWEVER, this is NOT meant for a production environment!!!! RFC restrictions PROHIBIT passing cookie data between sites with different port numbers (80 to 5933 for example. The ONLY exception is for the default ports of 80 and 443 which are never specified to begin with and thus pass RFC requirements for sharing data!!) and it treats them as separate sites. So if you use a port number drupal WILL require you to login BEFORE that url will work (drupal 6 has the same limitation btw, I just never documented it). In Drupal 7 this results in the 'Store' configuration panel NOT showing up. You must login to the store config panel by going to the https://yoursite.com:PORTNUMBER/admin/store url FIRST and login. Once you have done that, you can access store/admin. The administration panel overlay code for drupal 7 is NOT compatible with this module because it is NOT a secure method to access data!!!! ALWAYS go DIRECTLY to the /store/admin link DIRECTLY. The same goes for /cart. /cart WILL ask you to login. Once your site is ready for production you must absolutely use the SAME url for both secure AND non-secure pages. So your site should always be accessed from http://yoursite.com and https://yoursite.com OR http://www.yoursite.com and https://www.yoursite.com. It should NEVER be accessed like this: http://yoursite.com http://ssl.yoursite.com. Or any other sub domain for that matter. It will result in drupal asking you to login!"),
'#required' => TRUE,
);
$form['uc_ssl_non_ssl_domain'] = array(
'#type' => 'textfield',
'#title' => t('NON-Secure Domain Name'),
'#default_value' => variable_get('uc_ssl_non_ssl_domain', ''),
//'#size' => 2,
//'#maxlength' => 2,
'#description' => t("This field accepts domain names in the following format: http://something.com OR http://something.com:8080"),
'#required' => TRUE,
);
$form['uc_ssl_status'] = array(
'#type' => 'radios',
'#title' => t('Ubercart SSL Status'),
'#default_value' => variable_get('uc_ssl_status', ''),
'#options' => array( TRUE => t('Enabled'), FALSE => t('Disabled')),
'#required' => TRUE,
);
$form['uc_ssl_switch_to_non_ssl'] = array(
'#type' => 'radios',
'#title' => t('Ubercart Non-SSL Switch'),
'#default_value' => variable_get('uc_ssl_switch_to_non_ssl', ''),
'#options' => array( TRUE => t('Enabled'), FALSE => t('Disabled')),
'#required' => TRUE,
'#description' => t("Since this module switches to SSL mode during checkout, all links will link back to a secured version of the website. If you want to switch back to non-secured mode after a transaction is complete or they click on a keep shopping link, etc. This isnt really necessary but some people may want to save on the small resources savings you can get from doing this. So if you want to keep http for any non-cart related things, you should check this as enabled. If you dont care, then set it to disabled.<br><br>DRUPAL 7 NOTICE: Enabling this will DISABLE the Overlay module because it cannot work securely in a mixed SSL environment!! Setting this back to Disabled WILL NOT re-enable the Overlay module. You must re-enable the Overlay module yourself."),
);
$form['uc_ssl_case_insensitive'] = array(
'#type' => 'radios',
'#title' => t('Ubercart SSL Case Sensitivity'),
'#default_value' => variable_get('uc_ssl_case_insensitive', TRUE),
'#options' => array( TRUE => t('Enabled'), FALSE => t('Disabled')),
'#required' => TRUE,
'#description' => t('By default drupal allows paths to be accessed in a case insensitive manor. This is fine but not very secure. SSL Standards dictate that paths should always be case sensitive. So in order to be compatible with SSL standards, we allow the administrator to change SSL path recognition to case sensitive by simply setting this setting to disabled. In order to stick close to drupal standards, this module defaults this value to Enabled which means /Cart AND /cart would match and be secured where as in case sensitive mode /Cart would NOT be secured, alerting the user to an insecure page but /cart would be secured.'),
);
return system_settings_form($form);
}
function uc_ssl_admin_validate($form, &$form_state)
{
$ssl_domain = $form_state['values']['uc_ssl_ssl_domain'];
$non_ssl_domain = $form_state['values']['uc_ssl_non_ssl_domain'];
if (!valid_url($ssl_domain) || !stristr($ssl_domain, 'https://'))
{
form_set_error('uc_ssl_ssl_domain', t('You must enter a valid SSL Domain name.'));
}
if (!valid_url($non_ssl_domain) || !stristr($non_ssl_domain, 'http://'))
{
form_set_error('uc_ssl_non_ssl_domain', t('You must enter a valid Non-SSL Domain name.'));
}
if (!uc_ssl_check($ssl_domain.base_path()))
{
form_set_error('uc_ssl_ssl_domain', t('CRITICAL ERROR! The domain you entered for the secured domain came back as non-secure or it does not point back to this installation of drupal. Your secure domain name MUST be setup and MUST be pointing at this install of drupal.'));
variable_set('uc_ssl_status', '0');
$form_state['values']['uc_ssl_status'] = '0';
$form['values']['uc_ssl_status'] = '0';
}
}
@@ -0,0 +1,14 @@
name = Ubercart SSL
description = (D7) Secure Ubercart /cart links with an SSL Cert. Do NOT enable this unless your site works with SSL as it WILL break your cart. If it does, just disable this and get your site working with SSL first and then re-enable this module. This module does not require Ubercart so it can be used for anything else as well, its just that its most common use is to be used with Ubercart so that is why I named it so. Feel free to use it with anything you like.
package = Ubercart - extra
core = 7.x
project = "uc_ssl"
configure = admin/config/system/uc_ssl
; Information added by drupal.org packaging script on 2012-12-03
version = "7.x-1.11"
core = "7.x"
project = "uc_ssl"
datestamp = "1354572030"
@@ -0,0 +1,12 @@
<?php
/**
* Implementation of hook_uninstall().
*/
function uc_ssl_uninstall()
{
variable_del('uc_ssl_ssl_domain');
variable_del('uc_ssl_non_ssl_domain');
variable_del('uc_ssl_status');
variable_del('uc_ssl_switch_to_non_ssl');
}
@@ -0,0 +1,416 @@
<?php
//
//Note about the include/exclude hooks. As an example, this module includes /cart* which essentially secures ALL cart pages with SSL. But it also implements an exclude which is called /cart Which you would think would negate the include but it DOES NOT. Even though Excludes take presedence over includes, it still allows /cart/1 for example, to be secured. Why? Because the exclude is not a wildcard match! Thus the exclude would NOT match /cart/1 because it's defined as /cart. Thus while /cart IS excluded (not secured with SSL),/cart/1 is STILL secured.
//Implementation of hook_include_ssl_paths()
//NOTE: Exclude takes presedence over Includes!!! So if the same path is in both hooks, then the path will be EXCLUDED from secured pages.
//Since base_url has a design flaw in D4/D5/D6/D7 that seemingly will never be fixed, we fix it here.
function uc_ssl_boot()
{
global $conf;
//the $conf['https'] option MUST be enabled if mixed ssl mode is enabled. We make sure its enabled here. If it's not enabled then user
//sessions will not persist across the secure and non-secure domain names resulting in the user being prompted to log in whenever they are
//sent to a secure page.
if (uc_ssl_switch_to_non_ssl() && !isset($conf['https']) || (isset($conf['https']) && !$conf['https']))
{
$conf['https'] = TRUE;
}
if ($GLOBALS['base_url'])
{
//If base_url exists and is set to a non-secure protocol while the page being called is secure, allow base_url to be changed to
//the secure version of the website. Drupal for some reason doesnt have this logic anywhere for $base_url.
if (uc_ssl_page_is_in_https_mode() && stristr($GLOBALS['base_url'], 'http://'))
{
$GLOBALS['base_url'] = str_ireplace('http://', 'https://', $GLOBALS['base_url']);
}
}
}
function uc_ssl_permission()
{
$perms = array(
'administer uc_ssl' => array(
'title' => t('Administer Ubercart SSL settings'),
),
);
return $perms;
}
function uc_ssl_page_is_in_https_mode()
{
if (isset($_SERVER['HTTPS']) && !empty($_SERVER['HTTPS']))
{
return TRUE;
}
return FALSE;
}
function uc_ssl_disable_overlay_module()
{
$modules = module_list();
$base_path = base_path();
if (variable_get('uc_ssl_switch_to_non_ssl', '') && in_array('overlay', $modules))
{
//Overlay is on, lets disable it.
module_disable(array(0 => 'overlay'));
}
}
function uc_ssl_init()
{
//Drupals Overlay panel is not secure! Fix that here by disable it all together. We cannot allow this. Drupal 7 is insecure by default, such a sad thing :/
uc_ssl_disable_overlay_module();
//menu_rebuild();
//First and foremost, make sure Clean URLs are on.
if (uc_ssl_clean_urls_enabled() && !drupal_is_cli())
{
//Setup a hook for ssl checking
uc_ssl_check();
//Run ssl check on every page load.
uc_ssl_run();
}
}
function uc_ssl_include_ssl_paths()
{
global $user;
$paths = array(
'All Ubercart Cart pages' => '/cart*', //Secure ALL cart pages.
//NOTE: If you remove the /user definition here, you CAN NOT use Mixed SSL Mode with D7. It HAS to exist because D7 http sessions are missing
//data that https sessions require. But http sessions work with the https session variables. Thus, you MUST login with https or your sessions
//will not carry between the http and https versions of your wesite. This is a D7 bug only, I believe its more of a feature than a bug.
'All User pages' => '/user*', //Secure ALL user pages.
//Added support for Drupal commerce which is based upon Ubercart.
'All Drupal Commerce Checkout pages' => '/checkout*', //Secure ALL cart pages. NOTE: SEE EXCLUDES to see why the cart listing itself isnt secured!
//This supports both UC and Commerce admins
'All Ubercart Cart Admin pages' => '/admin/store*', //Secure ALL UC admin pages.
#'example 1' => '/cart', //Only secure the /cart and /cart/ page.
#'example 2' => 'cart', //Only secure the /cart and /cart/ page.
#'example 3' => 'cart/', //Only secure the /cart and /cart/ page.
#'example 4' => '/cart*/something', //matches /cartThis/something and /cart/something but NOT thiscart/something etc.
#'example 5' => '/cart/*/something', //Matches /cart/123/something and cart/this/something, but NOT /cart/something
#'example 6' => '/cart/something', //Only secure the /cart/something and /cart/something/ page.
);
return $paths;
}
//Implementation of hook_exclude_ssl_paths()
function uc_ssl_exclude_ssl_paths()
{
$paths = array(
//These excludes no longer seem to be required for the D7 version and thus they have been removed until issues come up again.
//'Exclude the cart page itself' => '/cart', //We dont need to secure the cart contents so we exclude that here
//'Exclude Autocomplete' => '*autocomplete*',
//'Exclude Ajax' => '*ajax*',
#'example 1' => '/cart', //Only exclude the /cart and /cart/ page.
#'example 2' => 'cart', //Only exclude the /cart and /cart/ page.
#'example 3' => 'cart/', //Only secure the /cart and /cart/ page.
#'example 4' => '/cart*/something', //matches /cartThis/something and /cart/something but NOT thiscart/something etc.
#'example 5' => '/cart/*/something', //Matches /cart/123/something and cart/this/something, but NOT /cart/something
#'example 6' => '/cart/something', //Only exclude the /cart/something and /cart/something/ page.
);
return $paths;
}
//Implementation of hook_exclude_ssl_paths()
//This function is so you can completely ignore ANY switching at all no matter what.
function uc_ssl_exclude_ssl_switch_paths()
{
$paths = array(
#'example 1' => '/*ajax*', //Dont touch ajax paths whatsoever, this is just an example.
);
return $paths;
}
function uc_ssl_menu()
{
$items = array();
$items['admin/config/system/uc_ssl'] = array(
'title' => 'Ubercart SSL',
// 'title' => 'Ubercart SSL module settings',
'description' => 'Ubercart SSL module settings',
'page callback' => 'drupal_get_form',
'page arguments' => array('uc_ssl_admin'),
'access arguments' => array('administer uc_ssl'),
'type' => MENU_NORMAL_ITEM,
'file' => 'uc_ssl.admin.inc',
);
return $items;
}
function uc_ssl_enabled()
{
return variable_get('uc_ssl_status', '');
}
function uc_ssl_is_configured()
{
$query = "SELECT count(name) AS count from {variable} WHERE name = 'uc_ssl_status'";
$results = db_query($query);
$rows = $results->fetchAll();
$rows = $rows[0];
//If uc_ssl is not configured, warn the user and show a link to config it.
if (!$rows->count)
{
$admin_link = 'http://'.$_SERVER['HTTP_HOST'].url().'admin/config/system/uc_ssl';
drupal_set_message("Please configure Ubercart SSL for SSL enabled cart checkouts to work: <a href = '$admin_link'>$admin_link</a>", 'warning');
}
return $rows->count;
}
function uc_ssl_switch_to_non_ssl()
{
return variable_get('uc_ssl_switch_to_non_ssl', '');
}
function uc_ssl_path_match($path, $pages)
{
$result = FALSE; //This is here just to get rid of retarded php 'notice' messages. I hate php notices.
if (!empty($pages))
{
//Trim off trailing / and then re-add it to avoid double /'s. This allows cart and cart/ to match as the same thing or cart/1 and cart/1/ to be the same.
$path = trim($path, '/').'/';
$regexp = '/^(';
foreach ($pages AS $key => $regexp_path)
{
if ($regexp_path)
{
$regexp_path = trim($regexp_path, '/').'/';
$regexp_path1 = str_replace('/', '\/', $regexp_path); //replace / with a \/
$regexp_path2 = str_replace('*', '.*', $regexp_path1);//replace * with a .*
$regexp .= $regexp_path2.'|'; //Append a | to signify end of this expression
}
}
$regexp = trim($regexp, '|');
$regexp .= ')$/';
if (variable_get('uc_ssl_case_insensitive', 1))
{
$regexp .= 'i';
}
$result = preg_match($regexp, $path);
}
return $result;
}
function is_clean_url($path)
{
//Skip ANY ?q= urls since these do NOT come from a web browser when clean URLs is on. Doing this allows us to skip
//ajax and autocomplete type URLs that come from within drupal itself. This is a major problem with Secure_Pages module
//and we dont want the same problems so we restrict people to using ONLY clean URLs.
if (!stristr($path, '?q='))
{
return TRUE;
}
return FALSE;
}
function uc_ssl_http_request($url)
{
//Handle JS type urls in the background as they are likely to be ajax type posts.
if (arg(0) == 'js')
{
$js_response = drupal_http_request($url);
return;
}
//Handle post type URLs. This should handle ajax POST urls such as the tax calculation/progress bars.
if ($_SERVER['REQUEST_METHOD'] == 'POST' && empty($_POST['uc_ssl_post']))
{
$_POST['uc_ssl_post'] = 1;
$options['headers'] = array('Content-Type' => 'application/x-www-form-urlencoded');
$options['method'] = 'POST';
$options['data'] = http_build_query($_POST, '', '&');
//Drupal 7 decided to completely recode drupal_http_request so I dont know if this will cause issues or not. Report any bugs please.
//$response = drupal_http_request($url, $headers, 'POST', http_build_query($_POST, '', '&'));
$response = drupal_http_request($url, $options);
return;
}
//All other types of urls are handled normally.
// Reset destination variables or we'll get sent there instead of to $url
unset($_REQUEST['destination'], $_GET['destination']);
drupal_goto($url);
return;
}
function uc_ssl_run()
{
//Set the current path without url() since it will mess up regex checks. Using url() also supports languages, base_path does not.
//$current_path = trim($_SERVER['REQUEST_URI'], url());
$root = url();
$current_path = substr(trim($_SERVER['REQUEST_URI']),strlen($root));
$ssl_domain = variable_get('uc_ssl_ssl_domain', '');
$nonssl_domain = variable_get('uc_ssl_non_ssl_domain', '');
//If uc_ssl is configured, run it.
if (uc_ssl_is_configured() && is_clean_url($current_path))
{
if (uc_ssl_enabled())
{
//Get a list of all the URL links that should be included and excluded from SSL pages from other modules.
$items = array();
$exclude_secured_urls = array();
$exclude_secured_urls = module_invoke('uc_ssl', 'exclude_ssl_paths', $exclude_secured_urls);
foreach (module_implements('exclude_ssl_paths') as $module)
{
$new = module_invoke($module, 'exclude_ssl_paths', $exclude_secured_urls);
if ($module != 'uc_ssl') //Skip uc_ssl since we already processed it above. Allow external modules to change uc_ssl defaults.
{
if (is_array($new))
{
$exclude_secured_urls = array_merge($exclude_secured_urls, $new);
}
}
}
$include_secured_urls = array();
$include_secured_urls = module_invoke('uc_ssl', 'include_ssl_paths', $include_secured_urls);
foreach (module_implements('include_ssl_paths') as $module)
{
$new = module_invoke($module, 'include_ssl_paths', $include_secured_urls);
if ($module != 'uc_ssl') //Skip uc_ssl since we already processed it above. Allow external modules to change uc_ssl defaults.
{
if (is_array($new))
{
$include_secured_urls = array_merge($include_secured_urls, $new);
}
}
}
// Get a list of all the URL links that should not switch between HTTP/HTTPS at all.
$exclude_switch_urls = array();
$exclude_switch_urls = module_invoke('uc_ssl', 'exclude_ssl_switch_paths', $exclude_switch_urls);
foreach (module_implements('exclude_ssl_switch_paths') as $module)
{
$new = module_invoke($module, 'exclude_ssl_switch_paths', $exclude_switch_urls);
if ($module != 'uc_ssl') //Skip uc_ssl since we already processed it above. Allow external modules to change uc_ssl defaults.
{
if (is_array($new))
{
$exclude_switch_urls = array_merge($exclude_switch_urls, $new);
}
}
}
// make sure we return an array
if (! is_array($include_secured_urls)) { $include_secured_urls = array(); }
if (! is_array($exclude_secured_urls)) { $exclude_secured_urls = array(); }
if (! is_array($exclude_switch_urls)) { $exclude_switch_urls = array(); }
//If the path is in the exclude switch hook, dont do ANYTHING at all.
if (uc_ssl_path_match($current_path, $exclude_switch_urls))
{
return;
}
//Switch to SSL no matter what the link is if Switch isnt on.
if (!uc_ssl_switch_to_non_ssl())
{
if (!uc_ssl_page_is_in_https_mode() && !uc_ssl_path_match($current_path, $exclude_secured_urls))
{
uc_ssl_http_request($ssl_domain . $_SERVER['REQUEST_URI']);
}
}
else
{
//If uc_ssl_switch_to_non_ssl() is enabled then we switch back to non-ssl link for non-cart links.
if (uc_ssl_page_is_in_https_mode())
{
//The only exceptions are the links listed in the exclude list by other modules.
if (uc_ssl_path_match($current_path, $exclude_secured_urls))
{
uc_ssl_http_request($nonssl_domain . $_SERVER['REQUEST_URI']);
}
if (!uc_ssl_path_match($current_path, $include_secured_urls) && uc_ssl_switch_to_non_ssl())
{
uc_ssl_http_request($nonssl_domain . $_SERVER['REQUEST_URI']);
}
}
}
//If on a cart url, use HTTPS and re-post/re-direct to the SSL version of the url.
if (!uc_ssl_page_is_in_https_mode() && uc_ssl_path_match($current_path, $include_secured_urls) && !uc_ssl_path_match($current_path, $exclude_secured_urls))
{
uc_ssl_http_request($ssl_domain . $_SERVER['REQUEST_URI']);
}
}
}
}
function uc_ssl_clean_urls_enabled()
{
$check = variable_get('clean_url', 0);
if ($check == 1)
{
return TRUE;
}
drupal_set_message('Ubercart SSL (uc_ssl): The uc_ssl module REQUIRES that Clean URLs be enabled in order to work properly. This allows the module to distinguish between an Ajax call and a Web browser call as the two are handled differently so we need a way to distinguish them and this is the quickest easiest way to do so. This will also make your website URLs search engine friendly as well which is a good thing. Please enable clean urls here: <a href="?q=admin/settings/clean-urls">Clean URLs</a>', 'warn');
return FALSE;
}
//This is a quickie hook to see if SSL works or not. It's not fool proof but it works for me.
function uc_ssl_check($site = '')
{
error_reporting(E_ALL);
if (!$site)
{
if (isset($_GET['uc_ssl_check']) && $_GET['uc_ssl_check'])
{
if (uc_ssl_page_is_in_https_mode())
{
echo TRUE;
}
else
{
echo FALSE;
}
exit;
}
}
else
{
$check = drupal_http_request($site."?uc_ssl_check=1");
if (isset($check->data) && $check->data == '1')
{
return TRUE;
}
//IF we are here, then the request failed for some reason, so we try to use a different method.
if (ini_get('allow_url_fopen'))
{
if (file_get_contents($site."?uc_ssl_check=1") == '1')
{
return TRUE;
}
}
//If we are here, something went wrong. Give the user some ideas on what they need to fix.
drupal_set_message("Ubercart SSL (uc_ssl): The uc_ssl_check() function is returning FALSE because it was unable to contact the SSL (https) version of your website that you defined in the settings. This can be caused by 3 things. 1. Your website is not setup properly for SSL, 2. The OpenSSL extension is not enabled on in your PHP installation, 3. allow_url_fopen is not enabled in php.ini. If #2 fails, uc_ssl will try to use file_get_contents() which requires allow_url_fopen to be set to TRUE in php.ini. Hopefully these hints will help you fix this issue so that you can use uc_ssl. You can try to debug this by going to $site?uc_ssl_check=1", "error");
}
return FALSE;
}
if (!function_exists('printr'))
{
function printr($arr)
{
echo "<pre>";
print_r($arr);
echo "</pre>";
}
}
@@ -1,4 +1,3 @@
This is a highly flexible and easy extendable filter module to embed any type
of video in your site using a simple tag. Other modules can add video
sites/formats (called codecs) using an easy plug-in architecture.
@@ -22,6 +21,9 @@ processed after that filter.
To enable WYSIWYG support, go to the WYSIWYG settings for each input format and
enable the Video Filter button.
To enable CKEditor (standalone) support, please see plugin instructions in:
editors/ckeditor/README.txt
========= Usage =========
Single video: [video:url]
@@ -1,35 +1,15 @@
##############################################
## ONLY if you use ckeditor WITHOUT wysiwyg ##
##############################################
############################################################
## ONLY if with the CKEditor module, *NOT* WYSIWYG module ##
############################################################
Installation:
Do the following steps to add video_filter button to the CKEditor toolbar:
1. Open ckeditor.config.js (in the ckeditor module root)
1. Go to Configuration -> CKEditor (admin/config/content/ckeditor)
Click "Edit" on the profile you what to use with the video filter.
2. Scroll down to the end of the file, right before "};" insert:
2. Expand "Editor appearance" and go to "Toolbar". Drag the new video_filter
button from the "All Buttons" toolbar to the "Used Buttons" toolbar.
// Video_filter plugin.
config.extraPlugins += (config.extraPlugins ? ',video_filter' : 'video_filter' );
CKEDITOR.plugins.addExternal('video_filter', Drupal.settings.basePath + Drupal.settings.video_filter.modulepath + '/editors/ckeditor/');
3. Add button to the toolbar.
3.1 Go to Configuration -> CKEditor (admin/config/content/ckeditor)
Click "Edit" on the profile you what to use with Linkit.
3.2 Expand "Editor appearance" and go to "Toolbar".
The button name is: video_filter
For example if you have a toolbar with an array of buttons defined as
follows:
['Bold','Italic']
simply add the button somewhere in the array:
['Bold','Italic','video_filter']
(remember the single quotes).
3. Go to "Plugins" on the same page. Enable the video filter plugin.
@@ -7,25 +7,126 @@
requires : [],
init: function(editor) {
// Add Button
editor.ui.addButton('video_filter', {
label: 'Video filter',
command: 'video_filter',
icon: this.path + 'video_filter.png'
});
// Add Command
editor.addCommand('video_filter', {
exec : function () {
var path = (Drupal.settings.video_filter.url.wysiwyg_ckeditor) ? Drupal.settings.video_filter.url.wysiwyg_ckeditor : Drupal.settings.video_filter.url.ckeditor
var media = window.showModalDialog(path, { 'opener' : window, 'editorname' : editor.name }, "dialogWidth:580px; dialogHeight:480px; center:yes; resizable:yes; help:no;");
}
});
// Register an extra fucntion, this will be used in the popup.
editor._.video_filterFnNum = CKEDITOR.tools.addFunction(insert, editor);
if(typeof window.showModalDialog !== 'undefined') {
editor.addCommand('video_filter', {
exec : function () {
var path = (Drupal.settings.video_filter.url.wysiwyg_ckeditor) ? Drupal.settings.video_filter.url.wysiwyg_ckeditor : Drupal.settings.video_filter.url.ckeditor
var media = window.showModalDialog(path, { 'opener' : window, 'editorname' : editor.name }, "dialogWidth:580px; dialogHeight:480px; center:yes; resizable:yes; help:no;");
}
});
// Register an extra function, this will be used in the popup.
editor._.video_filterFnNum = CKEDITOR.tools.addFunction(insert, editor);
}
else {
editor.addCommand('video_filter', new CKEDITOR.dialogCommand('video_filterDialog'));
}
}
});
CKEDITOR.dialog.add('video_filterDialog', function( editor ) {
var instructions_path = Drupal.settings.video_filter.instructions_url;
return {
title : 'Add Video',
minWidth : 600,
minHeight : 180,
contents : [{
id : 'general',
label : 'Settings',
elements : [
{
type : 'text',
id : 'file_url',
label : 'URL',
validate : CKEDITOR.dialog.validate.notEmpty( 'The link must have a URL.' ),
required : true,
commit : function( data )
{
data.file_url = this.getValue();
}
},
{
type : 'text',
id : 'width',
label : 'Width',
commit : function( data )
{
data.width = this.getValue();
}
},
{
type : 'text',
id : 'height',
label : 'Height',
commit : function( data )
{
data.height = this.getValue();
}
},
{
type : 'select',
id : 'align',
label : 'Align',
'default': 'none',
items: [
['None', ''],
['Left', 'left'],
['Right', 'right'],
['Center', 'center']
],
commit : function( data )
{
data.align = this.getValue();
}
},
{
type : 'checkbox',
id : 'autoplay',
label : 'Autoplay',
'default': '',
commit : function( data )
{
data.autoplay = this.getValue() ? 1 : 0;
}
},
{
type: 'html',
html: '<iframe src="' + instructions_path + '" style="width:100%; height: 200px;"></iframe>',
},
]
}],
onOk : function()
{
var dialog = this,
data = {},
link = editor.document.createElement( 'p' );
this.commitContent( data );
var str = '[video:' + data.file_url;
if (data.width) {
str += ' width:' + data.width;
}
if (data.height) {
str += ' height:' + data.height;
}
if (data.align) {
str += ' align:' + data.align;
}
if (data.autoplay) {
str += ' autoplay:' + data.autoplay;
}
str += ']';
link.setHtml( str );
editor.insertElement( link );
}
};
});
function insert(params, editor) {
@@ -49,6 +150,9 @@
if (params.autoplay) {
str += ' autoplay:' + params.autoplay;
}
else {
str += ' autoplay:' + '0';
}
str += ']';
for (var i = 0, len = ranges.length; i < len; i++) {
@@ -43,6 +43,9 @@ function Ok() {
if ($('#edit-autoplay').is(':checked')) {
str += ' autoplay:' + $('#edit-autoplay').val();
}
else {
str += ' autoplay:' + '0';
}
str += ']';
oEditor.FCKUndo.SaveUndoStep();
@@ -34,6 +34,9 @@ video_filter_dialog = {
if ($('#edit-autoplay').is(':checked')) {
str += ' autoplay:' + $('#edit-autoplay').val();
}
else {
str += ' autoplay:' + '0';
}
str += ']';
ed.execCommand('mceInsertContent', false, str);
@@ -0,0 +1,70 @@
<?php
/**
* @file
* Hooks provided by the Video Filter module.
*/
/**
* Defines video codecs and callbacks.
*
* @return array
* A video codec described as an associative array that may contain the
* following key-value pairs:
* - name: Required. A name for the codec.
* - sample_url: Required. An example of a URL the codec can handle.
* - callback: The function to call to generate embed code for the codec.
* Either this or html5_callback must be specified.
* - html5_callback: The function to call to generate device agnostic, HTML5
* embed code for the codec. Either this or callback must be specified.
* - instructions: Instructions for using the codec, to be displayed on the
* "Compse tips" page at filter/tips.
* - regexp: Required. A regular expression describing URLs the codec can
* handle. Multiple regular expressions may be supplied as an array.
* $video['codec']['delta'] will be set to the key of the match.
* - ratio: Required. A ratio for resizing the video within the dimensions
* optionally supplied in the token, expressed as height / width.
* - control_bar_height: The pixel height of the video player control bar, if
* applicable.
*/
function hook_codec_info() {
$codecs = array();
$codecs['minimal_example'] = array(
'name' => t('Minimal Example'),
'sample_url' => 'http://minimal.example.com/uN1qUeId',
'callback' => 'MODULE_minimal_example',
'regexp' => '/minimal\.example\.com\/([a-z0-9\-_]+)/i',
'ratio' => 4 / 3,
);
$codecs['complete_example'] = array(
'name' => t('Complete Example'),
'sample_url' => 'http://complete.example.com/username/uN1qUeId',
'callback' => 'MODULE_complete_example',
'html5_callback' => 'MODULE_complete_example_html5',
'instructions' => t('Your Complete Example username can be the first URL argument or a sub-subdomain.'),
'regexp' => array(
'/complete\.example\.com\/([a-z0-9\-_]+)\/([a-z0-9\-_]+)/i',
'/([a-z0-9\-_]+)\.complete\.example\.com\/([a-z0-9\-_]+)/i',
),
'ratio' => 4 / 3,
'control_bar_height' => 25,
);
return $codecs;
}
/**
* Alters the codecs available to Video Filter.
*
* @param array $codecs
*/
function hook_video_filter_codec_info_alter(&$codecs) {}
/**
* Alters a video's attributes previous to rendering.
*
* @param array $video
*/
function hook_video_filter_video_alter(&$video) {}
@@ -34,6 +34,15 @@ function video_filter_codec_info() {
'control_bar_height' => 30,
);
$codecs['candidcareer'] = array(
'name' => t('Candid Career'),
'sample_url' => 'https://www.candidcareer.com/embed.php?vkey=ed5fdd900a274930252f&shared=CandidCareer&uid=30',
'callback' => 'video_filter_candidcareer',
'regexp' => '/candidcareer\.com\/embed\.php\?vkey=([a-zA-Z0-9\-_&;]+)shared=([a-zA-Z0-9\-_&;]+)uid=([a-zA-Z0-9\-_&;]+)/',
'ratio' => 16 / 9,
'control_bar_height' => 20,
);
$codecs['capped'] = array(
'name' => t('Capped'),
'sample_url' => 'http://capped.tv/playeralt.php?vid=some-title',
@@ -196,7 +205,7 @@ function video_filter_codec_info() {
'sample_url' => 'http://wistia.com/medias/9pj9n6ftlk',
'callback' => 'video_filter_wistia_html5',
'html5_callback' => 'video_filter_wistia_html5',
'regexp' => '@https?://(.+\.)?(wistia\.com|wi\.st)/((m|medias|projects)|embed/(iframe|playlists))/([a-zA-Z0-9]+)@',
'regexp' => '@https?://(.+\.)?(wistia\.(com|net)|wi\.st)/((m|medias|projects)|embed/(iframe|playlists))/([a-zA-Z0-9]+)@',
);
$codecs['youtube'] = array(
@@ -205,9 +214,10 @@ function video_filter_codec_info() {
'callback' => 'video_filter_youtube',
'html5_callback' => 'video_filter_youtube_html5',
'regexp' => array(
'/youtube\.com\/watch\?v=([a-z0-9\-_]+)/i',
'/youtube\.com\/watch\?.*?v=([a-z0-9\-_]+)/i',
'/youtu.be\/([a-z0-9\-_]+)/i',
'/youtube\.com\/v\/([a-z0-9\-_]+)/i',
'/youtube\.com\/embed\/([a-z0-9\-_]+)/i',
),
'ratio' => 16 / 9,
'control_bar_height' => 25,
@@ -233,7 +243,7 @@ function video_filter_codec_info() {
* @see video_filter_codec_info()
*/
function video_filter_archive($video) {
$video['source'] = 'http://www.archive.org/embed/' . $video['codec']['matches'][1];
$video['source'] = '//www.archive.org/embed/' . $video['codec']['matches'][1];
return video_filter_iframe($video);
}
@@ -290,7 +300,7 @@ function video_filter_bliptv($video) {
}
}
$video['source'] = 'http://blip.tv/play/' . $id;
$video['source'] = '//blip.tv/play/' . $id;
$params = array(
'allowscriptaccess' => 'always',
);
@@ -298,13 +308,27 @@ function video_filter_bliptv($video) {
return video_filter_flash($video, $params);
}
/**
* Callback for Candid Career codec.
*
* @see video_filter_codec_info()
*/
function video_filter_candidcareer($video) {
// Their urls contain & symbols which Drupal is encoding, so decode those.
$decoded = decode_entities($video['codec']['matches'][0]);
$video['source'] = '//' . $decoded;
$video['attributes']['marginwidth'] = 0;
$video['attributes']['marginheight'] = 0;
return video_filter_iframe($video);
}
/**
* Callback for Capped codec.
*
* @see video_filter_codec_info()
*/
function video_filter_capped($video) {
$video['source'] = 'http://capped.micksam7.com/playeralt.swf?vid=' . $video['codec']['matches'][1];
$video['source'] = '//capped.micksam7.com/playeralt.swf?vid=' . $video['codec']['matches'][1];
return video_filter_flash($video);
}
@@ -315,7 +339,7 @@ function video_filter_capped($video) {
* @see video_filter_codec_info()
*/
function video_filter_collegehumor($video) {
$video['source'] = 'http://www.collegehumor.com/moogaloop/moogaloop.swf?clip_id=' . $video['codec']['matches'][1] . '&amp;fullscreen=1';
$video['source'] = '//www.collegehumor.com/moogaloop/moogaloop.swf?clip_id=' . $video['codec']['matches'][1] . '&amp;fullscreen=1';
return video_filter_flash($video);
}
@@ -326,7 +350,7 @@ function video_filter_collegehumor($video) {
* @see video_filter_codec_info()
*/
function video_filter_dailymotion($video) {
$video['source'] = 'http://www.dailymotion.com/swf/' . $video['codec']['matches'][1];
$video['source'] = '//www.dailymotion.com/swf/' . $video['codec']['matches'][1];
return video_filter_flash($video);
}
@@ -354,7 +378,7 @@ function video_filter_flickr_slideshows($video) {
* @see video_filter_codec_info()
*/
function video_filter_flickr_video($video) {
$video['source'] = 'http://www.flickr.com/apps/video/stewart.swf?v=1.161';
$video['source'] = '//www.flickr.com/apps/video/stewart.swf?v=1.161';
$params['flashvars'] = '&amp;photo_id=' . $video['codec']['matches'][2] . '&amp;flickr_show_info_box=true';
@@ -373,7 +397,7 @@ function video_filter_gametrailers($video) {
elseif (is_numeric($video['codec']['matches'][2])) {
$match = $video['codec']['matches'][2];
}
$video['source'] = 'http://media.mtvnservices.com/embed/mgid:moses:video:gametrailers.com:' . $match;
$video['source'] = '//media.mtvnservices.com/embed/mgid:moses:video:gametrailers.com:' . $match;
return video_filter_iframe($video);
}
@@ -384,7 +408,7 @@ function video_filter_gametrailers($video) {
* @see video_filter_codec_info()
*/
function video_filter_gamevideos($video) {
$video['source'] = 'http://gamevideos.1up.com/swf/gamevideos12.swf?embedded=1&amp;fullscreen=1&amp;autoplay=0&amp;src=http://gamevideos.1up.com/do/videoListXML%3Fid%3D' . $video['codec']['matches'][1];
$video['source'] = '//gamevideos.1up.com/swf/gamevideos12.swf?embedded=1&amp;fullscreen=1&amp;autoplay=0&amp;src=http://gamevideos.1up.com/do/videoListXML%3Fid%3D' . $video['codec']['matches'][1];
return video_filter_flash($video);
}
@@ -395,7 +419,7 @@ function video_filter_gamevideos($video) {
* @see video_filter_codec_info()
*/
function video_filter_godtube($video) {
$video['source'] = 'http://www.godtube.com/embed/watch/' . $video['codec']['matches'][1];
$video['source'] = '//www.godtube.com/embed/watch/' . $video['codec']['matches'][1];
return video_filter_iframe($video);
}
@@ -406,7 +430,7 @@ function video_filter_godtube($video) {
* @see video_filter_codec_info()
*/
function video_filter_google($video) {
$video['source'] = 'http://video.google.com/googleplayer.swf?docId=' . $video['codec']['matches'][1];
$video['source'] = '//video.google.com/googleplayer.swf?docId=' . $video['codec']['matches'][1];
return video_filter_flash($video);
}
@@ -417,7 +441,7 @@ function video_filter_google($video) {
* @see video_filter_codec_info()
*/
function video_filter_metacafe($video) {
$video['source'] = 'http://metacafe.com/fplayer/' . $video['codec']['matches'][1] . '/' . $video['codec']['matches'][2] . '.swf';
$video['source'] = '//metacafe.com/fplayer/' . $video['codec']['matches'][1] . '/' . $video['codec']['matches'][2] . '.swf';
return video_filter_flash($video);
}
@@ -430,7 +454,7 @@ function video_filter_metacafe($video) {
function video_filter_myspace($video) {
// The last match is the ID we need.
$last = count($video['codec']['matches']);
$video['source'] = 'http://mediaservices.myspace.com/services/media/embed.aspx/m=' . $video['codec']['matches'][$last - 1];
$video['source'] = '//mediaservices.myspace.com/services/media/embed.aspx/m=' . $video['codec']['matches'][$last - 1];
return video_filter_flash($video, $params);
}
@@ -441,7 +465,7 @@ function video_filter_myspace($video) {
* @see video_filter_codec_info()
*/
function video_filter_picasa_slideshows($video) {
$video['source'] = 'http://picasaweb.google.com/s/c/bin/slideshow.swf';
$video['source'] = '//picasaweb.google.com/s/c/bin/slideshow.swf';
$user_name = $video['codec']['matches'][1];
$set_id = $video['codec']['matches'][2];
@@ -457,7 +481,7 @@ function video_filter_picasa_slideshows($video) {
* @see video_filter_codec_info()
*/
function video_filter_slideshare($video) {
$video['source'] = 'http://www.slideshare.net/slideshow/embed_code/' . $video['codec']['matches'][1];
$video['source'] = '//www.slideshare.net/slideshow/embed_code/' . $video['codec']['matches'][1];
return video_filter_iframe($video);
}
@@ -468,7 +492,7 @@ function video_filter_slideshare($video) {
* @see video_filter_codec_info()
*/
function video_filter_streamhoster($video) {
$video['source'] = 'http://public.streamhoster.com/Resources/Flash/JWFLVMediaPlayer/mediaplayer.swf';
$video['source'] = '//public.streamhoster.com/Resources/Flash/JWFLVMediaPlayer/mediaplayer.swf';
$params = array('allowscriptaccess' => 'always');
$protocol = $video['codec']['matches'][1];
@@ -496,7 +520,7 @@ function video_filter_streamhoster($video) {
* @see video_filter_codec_info()
*/
function video_filter_teachertube($video) {
$video['source'] = 'http://www.teachertube.com/embed/player.swf';
$video['source'] = '//www.teachertube.com/embed/player.swf';
$params['flashvars'] = 'file=http://www.teachertube.com/embedFLV.php?pg=video_' . $video['codec']['matches'][1] . '&amp;menu=false&amp;frontcolor=ffffff&amp;lightcolor=FF0000&amp;logo=http://www.teachertube.com/www3/images/greylogo.swf&amp;skin=http://www.teachertube.com/embed/overlay.swf&amp;volume=80&amp;controlbar=over&amp;displayclick=link&amp;viral.link=http://www.teachertube.com/viewVideo.php?video_id=' . $video['codec']['matches'][1] . '&amp;stretching=exactfit&amp;plugins=viral-2&amp;viral.callout=none&amp;viral.onpause=false';
@@ -509,7 +533,7 @@ function video_filter_teachertube($video) {
* @see video_filter_codec_info()
*/
function video_filter_vimeo($video) {
$video['source'] = 'http://www.vimeo.com/moogaloop.swf?clip_id=' . $video['codec']['matches'][1] . '&amp;server=www.vimeo.com&amp;fullscreen=1&amp;show_title=1&amp;show_byline=1&amp;show_portrait=0&amp;color=&amp;autoplay=' . $video['autoplay'];
$video['source'] = '//www.vimeo.com/moogaloop.swf?clip_id=' . $video['codec']['matches'][1] . '&amp;server=www.vimeo.com&amp;fullscreen=1&amp;show_title=1&amp;show_byline=1&amp;show_portrait=0&amp;color=&amp;autoplay=' . $video['autoplay'];
return video_filter_flash($video);
}
@@ -520,7 +544,7 @@ function video_filter_vimeo($video) {
* @see video_filter_codec_info()
*/
function video_filter_vimeo_html5($video) {
$video['source'] = 'http://player.vimeo.com/video/' . $video['codec']['matches'][1] . ($video['autoplay'] ? '?autoplay=1' : '');
$video['source'] = '//player.vimeo.com/video/' . $video['codec']['matches'][1] . ($video['autoplay'] ? '?autoplay=1' : '');
return video_filter_iframe($video);
}
@@ -537,7 +561,7 @@ function video_filter_youtube($video) {
'fs' => 'fs=1',
);
$video['source'] = 'http://www.youtube.com/v/' . $video['codec']['matches'][1] . '?' . implode('&amp;', $attributes);
$video['source'] = '//www.youtube.com/v/' . $video['codec']['matches'][1] . '?' . implode('&amp;', $attributes);
$params['wmode'] = 'opaque';
@@ -551,11 +575,12 @@ function video_filter_youtube($video) {
*/
function video_filter_youtube_html5($video) {
$attributes = array(
'html5' => 'html5=1',
'rel' => $video['related'] ? 'rel=1' : 'rel=0',
'autoplay' => $video['autoplay'] ? 'autoplay=1' : 'autoplay=0',
'wmode' => 'wmode=opaque',
);
$video['source'] = 'http://www.youtube.com/embed/' . $video['codec']['matches'][1] . '?' . implode('&amp;', $attributes);
$video['source'] = '//www.youtube.com/embed/' . $video['codec']['matches'][1] . '?' . implode('&amp;', $attributes);
return video_filter_iframe($video);
}
@@ -571,7 +596,7 @@ function video_filter_youtube_playlist_html5($video) {
'autoplay' => $video['autoplay'] ? 'autoplay=1' : 'autoplay=0',
'wmode' => 'wmode=opaque',
);
$video['source'] = 'http://www.youtube.com/embed/videoseries?list=' . $video['codec']['matches'][1] . '&amp;' . implode('&amp;', $attributes);
$video['source'] = '//www.youtube.com/embed/videoseries?list=' . $video['codec']['matches'][1] . '&amp;' . implode('&amp;', $attributes);
return video_filter_iframe($video);
}
@@ -584,9 +609,9 @@ function video_filter_youtube_playlist_html5($video) {
* @see video_filter_codec_info()
*/
function video_filter_wistia_html5($video) {
$video_code = $video['codec']['matches'][6];
$video_code = $video['codec']['matches'][7];
$matches = $video['codec']['matches'];
$embed_type = ($matches[3] == 'projects' || $matches[5] == 'playlists') ? 'playlists' : 'iframe';
$embed_type = ($matches[4] == 'projects' || $matches[6] == 'playlists') ? 'playlists' : 'iframe';
// Get embed code via oEmbed.
$endpoint = 'http://fast.wistia.com/oembed';
@@ -599,12 +624,12 @@ function video_filter_wistia_html5($video) {
$html = $data['html'];
// See if the video source is already an iframe src.
$pattern = '@https?://fast.wistia.com/embed/(iframe|playlists)/[a-zA-Z0-9]+\?+.+@';
$pattern = '@https?://fast.wistia.(com|net)/embed/(iframe|playlists)/[a-zA-Z0-9]+\?+.+@';
$matches = array();
if (preg_match($pattern, $video['source'], $matches)) {
// Replace the oEmbed iframe src with that provided in the token, in order
// to support embed builder URLs.
$pattern = '@https?://fast.wistia.com/embed/(iframe|playlists)/[a-zA-Z0-9]+\?[^"]+@';
$pattern = '@https?://fast.wistia.(com|net)/embed/(iframe|playlists)/[a-zA-Z0-9]+\?[^"]+@';
$replacement = $matches[0];
$html = preg_replace($pattern, $replacement, $html);
}
@@ -5,9 +5,9 @@ package = Input filters
stylesheets[all][] = video_filter.css
; Information added by drupal.org packaging script on 2012-11-14
version = "7.x-3.1"
; Information added by Drupal.org packaging script on 2015-09-24
version = "7.x-3.1+17-dev"
core = "7.x"
project = "video_filter"
datestamp = "1352915891"
datestamp = "1443119944"
@@ -284,9 +284,14 @@ function theme_video_filter_flash($variables) {
$classes = video_filter_get_classes($video);
$output .= '<object class="' . implode(' ', $classes) . '" type="application/x-shockwave-flash" ';
$attributes = '';
if (!empty($video['attributes'])) {
$attributes = drupal_attributes($video['attributes']);
}
$output .= 'width="' . $video['width'] . '" height="' . $video['height'] . '" data="' . $video['source'] . '">' . "\n";
$output .= '<div class="video-filter"><object class="' . implode(' ', $classes) . '" type="application/x-shockwave-flash" ';
$output .= 'width="' . $video['width'] . '" height="' . $video['height'] . '" data="' . $video['source'] . '" ' . $attributes . '>' . "\n";
$defaults = array(
'movie' => $video['source'],
@@ -300,7 +305,7 @@ function theme_video_filter_flash($variables) {
$output .= ' <param name="' . $name . '" value="' . $value . '" />' . "\n";
}
$output .= '</object>' . "\n";
$output .= '</object></div>' . "\n";
return $output;
}
@@ -312,10 +317,13 @@ function theme_video_filter_flash($variables) {
*/
function theme_video_filter_iframe($variables) {
$video = $variables['video'];
$classes = video_filter_get_classes($video);
$attributes = '';
if (!empty($video['attributes'])) {
$attributes = drupal_attributes($video['attributes']);
}
$output = '<iframe src="' . $video['source'] . '" width="' . $video['width'] . '" height="' . $video['height'] . '" class="' . implode(' ', $classes) . '" frameborder="0"></iframe>';
$output = '<div class="video-filter"><iframe src="' . $video['source'] . '" width="' . $video['width'] . '" height="' . $video['height'] . '" class="' . implode(' ', $classes) . '" frameborder="0"' . $attributes . '></iframe></div>';
return $output;
}
@@ -378,6 +386,15 @@ function video_filter_menu() {
'theme callback' => '_video_filter_dashboard_theme',
);
$items['video_filter/instructions'] = array(
'title' => 'Videofilter instructions',
'description' => 'instructions',
'page callback' => 'video_filter_instructions_page',
'access arguments' => array('access content'),
'type' => MENU_CALLBACK,
'theme callback' => '_video_filter_dashboard_theme',
);
return $items;
}
@@ -432,7 +449,19 @@ function video_filter_dashboard_page($editor) {
break;
}
print theme('video_filter_dashboard', array('form' => render(drupal_get_form('_video_filter_form'))));
$form = drupal_get_form('_video_filter_form');
print theme('video_filter_dashboard', array('form' => render($form)));
exit();
}
/**
* Creates the instructions page.
*/
function video_filter_instructions_page() {
module_invoke('admin_menu', 'suppress');
$form = drupal_get_form('_video_filter_instructions_form');
print theme('video_filter_dashboard', array('form' => render($form)));
exit();
}
@@ -492,6 +521,25 @@ function _video_filter_form() {
'#weight' => 5,
);
$form += _video_filter_instructions_form();
$form['cancel'] = array(
'#type' => 'button',
'#value' => t('Cancel'),
'#weight' => 98,
);
$form['insert'] = array(
'#type' => 'button',
'#value' => t('Insert'),
'#weight' => 99,
);
return $form;
}
function _video_filter_instructions_form() {
$form['instructions'] = array(
'#type' => 'fieldset',
'#title' => t('Instructions'),
@@ -509,21 +557,28 @@ function _video_filter_form() {
'#markup' => $text,
);
$form['cancel'] = array(
'#type' => 'button',
'#value' => t('Cancel'),
'#weight' => 98,
);
$form['insert'] = array(
'#type' => 'button',
'#value' => t('Insert'),
'#weight' => 99,
);
return $form;
}
/**
* Implements hook_ckeditor_plugin().
*/
function video_filter_ckeditor_plugin() {
$plugins = array();
$plugins['video_filter'] = array(
'name' => 'video_filter',
'desc' => t('Plugin to directly embed videos with the video filter module.'),
'path' => drupal_get_path('module', 'video_filter') . '/editors/ckeditor/',
'buttons' => array(
'video_filter' => array(
'label' => t('Video filter'),
'icon' => 'video_filter.png',
),
),
);
return $plugins;
}
function video_filter_wysiwyg_plugin($editor, $version) {
_video_filter_add_settings('wysiwyg_' . $editor);
@@ -582,7 +637,10 @@ function _video_filter_add_settings($editor) {
// Add popup url.
$settings = array(
'video_filter' => array('url' => array($editor => url('video_filter/dashboard/' . $editor))),
'video_filter' => array(
'url' => array($editor => url('video_filter/dashboard/' . $editor)),
'instructions_url' => url('video_filter/instructions'),
),
);
drupal_add_js($settings, 'setting');
}
@@ -1,298 +1,298 @@
AT NULL Feldkirch NULL 6800 Pater Grimm Weg 20
AU NULL Melbourne NULL
AU NULL Sydney NULL
AU 04 NULL NORMANBY NULL 4059 30 Normanby Terrace
BD NULL Dhaka NULL 1205 23, Subal Das Road, Chowdhury Bazar, Lalbagh
BD NULL Dhaka NULL 1207 R-1,H-19,Kallaynpur,Mirpur,Dhaka
BD NULL Dhaka NULL 1207 World Bank Office Dhaka, Plot E 32, Agargaon, Sher-E-Bangla Nagar
BD NULL Dhaka NULL 1209 House# 66B, Flat# B2 Zigatola
BD NULL Dhaka NULL 1219 390 West Rampura Dhaka
BD NULL Dhaka NULL 1230 Uttara
BD 81 NULL Dhaka NULL 1000 Institute of Water and Flood Management
BD 81 NULL Dhaka NULL 1203 84/a maniknagar
BD 81 NULL Dhaka NULL 1205 Dhaka Bangladesh
BD 81 NULL Dhaka NULL 1207 BetterStories Limited 17 West Panthopath
BD 81 NULL Dhaka NULL 1216 Mirpur, Dhaka
BD 81 NULL Dhaka NULL 1230 830, Prembagan, Dhakshin Khan
BD 82 NULL khulna NULL 9203
BD NULL NULL Dhaka NULL 1000 Institute of Water and Flood Management
BD NULL NULL Dhaka NULL 1207 World Bank Office Dhaka, Plot E 32, Agargaon, Sher-E-Bangla Nagar
BE NULL Brussels NULL
BE NULL Watermael-Boitsfort NULL 1170 Avenue des Staphylins
BH NULL Manama NULL 00973 Manama Bahrain Manama Bahrain
BR NULL Porto Alegre NULL
BR NULL Recife NULL
BR RJ NULL Rio de Janeiro NULL
BW NULL Francistown NULL NULL
BW NULL NULL Francistown NULL NULL
CA NULL Montreal NULL
CA NULL Toronto NULL
CA BC NULL Vancouver NULL
CA ON NULL Kitchener NULL
CA ON NULL wterloo NULL n2l3g1 200 University Avenue West
CH NULL Geneva NULL 1202 15, chemin Louis-Dunant
CH 25 NULL Zurich NULL 8098 UBS Optimus Foundation Augustinerhof 1
DE NULL Berlin NULL
DE 05 NULL Frankfurt am Main NULL 60386 Johanna-Tesch-Platz 7
DK NULL Aarhus NULL
ES NULL Bilbao NULL
ET 44 NULL ADDIS ABABA NULL 11945 ADDIS ABABA,P.O.BOX 11945
FI NULL Espoo NULL 02130 Mahlarinne 3B
FI NULL Helsinki NULL 00580 Hermannin rantatie 2 A Hermannin rantatie 2 A
FI NULL Tampere NULL 33101 Tampere Univerity of Technology
FI 13 NULL Espoo NULL 02150 Aalto Venture Garage Betonimiehenkuja 3
GB NULL Exeter NULL
GB NULL London NULL
GB NULL London NULL N4 2DP 2 Myddleton Ave
GB NULL London NULL N7 0AH 104 St Georges Avenue
GB NULL London NULL SE16 3UL 25 Blue Anchor Lane
GB NULL London NULL SW18 5SP Flat 1 150 Merton road
GB NULL London NULL W1T 4BQ 13 Fitzroy Street
GB NULL Oxford NULL
GB NULL Southampton NULL
GB C3 NULL NULL cb244qg 32 market street swavesey
GB E7 NULL London NULL SE3 7TP
GB F3 NULL Wood Green NULL N22 5RU 6 Cedar House
GB H1 NULL London NULL SE11 5JD 47-49 Durham Street
GB H6 NULL London NULL SE8 4DD 8 Harton St Deptford
GB K2 NULL Oxford NULL OX2 6QY 3 The Villas, Rutherway
GH 05 NULL NSAWAM NULL NULL P.O.BOX 455
GH NULL NULL Accra NULL NULL
ID NULL Bandung NULL 40134 Jalan Sadang Hegar 1 No. 12 RT04 RW13 Sadang Serang
ID NULL Bekasi NULL 17411 Jl.Binadharma 1 No.62. Jatiwaringin
ID NULL Jakarta NULL
ID NULL Jakarta NULL 12440 Jl. H. Niin 7 Lebak Bulus, Cilandak
ID NULL Jakarta NULL 13330 Otista
ID NULL Jakarta selatan NULL 12000 jl. rawa jati timur 6 no. 10
ID NULL Jakarta Timur NULL Jl.Mulia No.15B Kel.Bidara Cina, Kec.Jatinegara, Jakarta Timur
ID NULL Pematang Siantar NULL 51511 Jl. Durian I 30
ID 04 NULL Bogor NULL 16165
ID 04 NULL jakarta NULL otista
ID 04 NULL Jakarta NULL 12520 Jl. Pertanian Raya III No.42 Jakarta Selatan Pasar Minggu
ID 04 NULL Jakarta NULL 13330 Jakarta
ID 04 NULL Jakarta NULL 13330 Jl Sensus IIC Bidaracina Jaktim
ID 04 NULL Jakarta NULL 13330 Jl. Bonasut 2 no.22
ID 04 NULL Jakarta NULL 13330 Otista 64c
ID 04 NULL jakarta NULL 13330 Otista jaktim
ID 04 NULL Jakarta Timur NULL 13330 Kebon Sayur I no. 1 RT 10/15
ID 04 NULL Jakarta Timur NULL 13460 Jl. Pondok Kopi Blok G4/5 RT. 005/08 Jakarta Timur
ID 04 NULL Jakarta Timur NULL 13810 Jl. Raya Pondok Gede Rt03 Rw08 no.35 , Lubang Buaya, Jakarta Timur Jl. Raya Pondok Gede Rt03 Rw08 no.35 , Lubang Buaya, Jakarta Timur
ID 07 NULL Brebes NULL 54321 Jl Kersem Blok D14 Perum Taman Indo Kaligangsa Wetan Brebes
ID 07 NULL Semarang NULL 50143 Puspowarno Tengah 2/2
ID 08 NULL Lumajang NULL 67373 Desa Tumpeng Kecamatan Candipuro Lumajang
ID 30 NULL Bandung NULL 55241 Jl Pelesiran No 55A/56
ID 30 NULL Bekasi NULL 17510 bekasi West Java Indonesia
ID 30 NULL Depok NULL 16245 Jalan juragan sinda 2 no 10
ID 30 NULL Depok NULL 16424 Jalan Margonda RayaJalan Kober Gang Mawar
ID 30 NULL Depok NULL 16424 Jl. Haji Yahya Nuih no.24, Pondok Cina
ID 30 NULL Depok NULL 16425 Kukusan Kelurahan
ID 30 NULL Depok NULL 16518 Jl. Salak No.1/C.88 Durenseribu Bojongsari
ID 30 NULL Depok NULL 16952 Jl. Merak No.34 -36 Rt.004/014 Jl. Merak No. 34 -36 Rt. 004/014
ID 36 NULL biak numfor NULL 98111 jl. s. mamberamo no 6782 biak numfor
IL NULL Tel Aviv NULL
IN NULL Bangalore NULL
IN NULL India NULL
IN NULL new delhi NULL 110003 55 lodi estate
IN 07 NULL NEW DELHI NULL 110018 15/11 A 1ST FLOOR TILAK NAGAR
IN 07 NULL New Delhu NULL 110075 B 54 Hilansh Apartments Plot No 1, Sector 10, Dwarka
IN 10 NULL Gurgaon NULL D- 201 Ivy Apartments Sushant Lok 1 Gurgaon Haryana
IN 13 NULL Trivandrum NULL 695010 TC 9/1615, SRMH Road, Sasthamangalam, Trivandrum
IN 16 NULL Mumbai NULL 400020 Bharat Mahal, Flat#55 Marine Drive
IN 16 NULL Mumbai NULL 400028 303,Shree Parvati Co-Op Housing Society, D.L.Vaidya Road,Dadar
IN 16 NULL Pune NULL
IN 16 NULL Pune NULL Infosys Campus Hinjewadi Phase 2
IN 16 NULL Pune NULL 400705 #22 Iris Garden Gokhale Road
IN 16 NULL PUNE NULL 411043
IN 16 NULL Pune NULL 411051
IN 16 NULL Pune NULL 411057 Infosys Ltd. Rajiv gandhi infostech park Hinjewadi phase 2
IN 16 NULL Pune NULL 412108 Pune Maharatshtra
IN 16 NULL Pune NULL 433011 502 utkarsh vihar golande state pune
IN 19 NULL Bangalore NULL 560080 Indian Institute for Human Settlements IIHS Bangalore City Campus, Sadashivanagar,
IN 19 NULL Bangalore NULL 560100 electronic city
IN 19 NULL Bhalki NULL 585411 bhalki,bidar ,karnataka karnataka
IN 24 NULL Jaipur NULL 302011 Institute of Health Management Research 1, Prabhu Dayal Marg
IR 26 NULL Tehran NULL 1118844454 Baharestan sq. mostafa khomeini str., javahery Ave., no. 11,
IT NULL Trento NULL
JM 08 NULL Kingston NULL Kgn 7 MOna Campus UWI
KE NULL Nairobi NULL
KE 05 NULL Nairobi NULL 30300 212,kapsabet
KH NULL NULL Phnom Penh NULL
LR NULL Monrovia NULL 00000
NG 11 NULL Abuja NULL 930001 17 Bechar street Wuse zone 2
PE 15 NULL Lima NULL 18 Lima Lima
PE 15 NULL Lima NULL Lima 18 123 Miraflores
PE NULL NULL Lima NULL 03 Calle Granada 104
PE NULL NULL Lima NULL 18 Lima Lima
PH NULL Manila NULL Globe Telepark 111 Valero Street
PH NULL Quezon Coty NULL 1109 86 Harvard Street, Cubao, Quezon City, Philippines 84 Harvard Street, Cubao, Quezon City,hilippines
PH 20 NULL Silang NULL 4118 370 Bayungan Kaong Silang Cavite
PH 57 NULL Kidapawan NULL 9400 Kidapawan City Kidapawan City
PH 66 NULL zamboanga NULL 7000 29-tripplet rd san jose 29-tripplet rd san jose
PH D9 NULL Pasig City NULL World Bank Office Manila, 20/F Taipan Place F. Ortigas Jr. Road, Ortigas Center
PK NULL Lahore NULL 54000 17-R Model Town Lahore
PK NULL Lahore NULL 54000 53- chamber lane road Lahore
PK NULL Lahore NULL 54000 85 E block Model Town
PK NULL Lahore NULL 54000 House no 227, street no 5, Imamia Colony Shahadra Lahore
PK NULL LAHORE NULL 54000 room no.6 khalid bim waleed hall, near New Anarkali, LAHORE room no.6 khalid bim waleed hall, near New Anarkali, LAHORE
PK NULL Lahore NULL pk097 LUMS, Lahore,
PK NULL Sheikhupura NULL 03935 D.H.Q.Hospital Sheikhupura House number 08. Room no 109 Khalid bin waleed haal, punjab University lahore old campus.
PK 02 NULL Quetta NULL 87000 Postal Address 87000, Kuchlak, Quetta, Balochistan. H#24 Peer Abul Khair road Quetta, Balochistan.
PK 02 NULL Quetta NULL 87300 block no-1 falt no. 7 New Crime Branch Abbas Ali Road Cantt
PK 02 NULL Quetta NULL 87300 Flat no. 3 Shafeen Centre Jinnah Town ,Near I.T university , Quetta
PK 02 NULL Quetta NULL 87300 H-no. C-220 Zarghoonabad Phase-2 , Nawa Killi ,Quetta
PK 04 NULL burewala NULL 60101 Fatima Fayyaz Hazrat Sakina hall girls hostel number 9 Punjab university Lahore Pakistan Sardar Wajid Azim Azeem abad Burewala dist Vehari Pakistan
PK 04 NULL Faisalabad NULL 38000 P 101/1, Green Town, Millat Road, Faisalabad
PK 04 NULL Islamabad NULL 44000 P.O Tarlai kalan chappar Islamabad
PK 04 NULL lahore NULL
PK 04 NULL Lahore NULL 54000
PK 04 NULL lahore NULL 54000 Street No.63 House 36/A Al-madad Pak Colony Ravi Road, Lahore. Street No.63 House 36/A Al-madad Pak Colony Ravi Road, Lahore.
PK 04 NULL Lahore NULL 54000 1149-1-D2 Green Town Lahore
PK 04 NULL Lahore NULL 54000 124, street# 2, karim block Allama Iqbal Town lahore. 124, street# 2, karim block Allama Iqbal Town lahore.
PK 04 NULL Lahore NULL 54000 150 A Qila Lachman Singh Ravi Road lahore
PK 04 NULL Lahore NULL 54000 166/1L DHA Lahore
PK 04 NULL Lahore NULL 54000 172 A2 Township Lahore
PK 04 NULL Lahore NULL 54000 183,S/Block, Model Town, Lhr
PK 04 NULL lahore NULL 54000 19- A block ,Eden Lane Villas Raiwind Road ,Lahore
PK 04 NULL lahore NULL 54000 3-c kaliyar road opposite kids lyceum, rustam park near mor samnabad
PK 04 NULL Lahore NULL 54000 31 Saeed Block, Canal Bank Scheme
PK 04 NULL Lahore NULL 54000 31c DHA Lahore
PK 04 NULL Lahore NULL 54000 387 E1 wapda town, Lahore
PK 04 NULL Lahore NULL 54000 45-D dha eme sector multan road,lahore
PK 04 NULL Lahore NULL 54000 5 Zafar Ali Road
PK 04 NULL Lahore NULL 54000 54-R PGECHS
PK 04 NULL lahore NULL 54000 566 E-1 johar town lahore 566 E-1 johar town lahore
PK 04 NULL Lahore NULL 54000 82/1 Z Block, Phase 3 DHA
PK 04 NULL Lahore NULL 54000 A-1 VRI Zarrar shaheed road lahore cantt A-1 VRI Zarrar shaheed road lahore cantt
PK 04 NULL lahore NULL 54000 e5/39D street 6 zaman colony cavalry ground ext
PK 04 NULL Lahore NULL 54000 Ho # 61, Block G3, Johar Town Lahore
PK 04 NULL LAhore NULL 54000 House #19-A street #5 Usman nagr Ghaziabad Lahore
PK 04 NULL lahore NULL 54000 House no 692 street no 67 sadar bazar
PK 04 NULL Lahore NULL 54000 Khosa Law Chamber 1 Turner Road
PK 04 NULL Lahore NULL 54000 Lahore,Pakistan Lahore,Pakistan
PK 04 NULL Lahore NULL 54000 room no 69, khalid bin waleed hall, anarkali
PK 04 NULL Lahore NULL 54000 Suite # 8, Al-Hafeez Suites, Gulberg II
PK 04 NULL Lahore NULL 54085 199 Shadman 2
PK 04 NULL Lahore NULL 54300 Mughalpura Lahore Pakistan
PK 04 NULL Lahore NULL 54660 SD 69 falcon complex gulberg III lahore
PK 04 NULL lahore NULL 54800 764-G4 johar town ,lahore
PK 04 NULL Rawalpindi NULL 44000 House 522, F-Block Sattellite Town, Rawalpindi
PK 04 NULL Rawalpindi NULL 46000 1950/c, Indusroad 2, Tariqabad, Rawalpindi Cantt
PK 04 NULL Rawalpindi NULL 46000 House 54-E Lane 9 Sector 4, AECHS Chaklala Rawalpindi
PK 04 NULL Rawalpindi NULL 46000 House B-1343, Sattellite town Rawalpindi
PK 04 NULL Rawalpindi NULL 46000 House CB-299F, Street 1, Lane 4 Peshawar Road Rawalpindi
PK 04 NULL Rawalpindi NULL 46300 House No 1518 Umer Block phase 8 BehriaTown
PK 04 NULL sialkot NULL 51310 The National Model School, Ismaiealabad, Pacca Garah Sialkot
PK 08 NULL Islamabad NULL CIomsats Institute of Information Technology Islamabad
PK 08 NULL Islamabad NULL 38700 COMSATS tarlai boys hostel Islamabad. COMSATS tarlai boys hostel Islamabad (Room 30)
PK 08 NULL Islamabad NULL 44000
PK 08 NULL Islamabad NULL 44000 House # 256, Street # 9, Shahzad Town, Islamabad.
PK 08 NULL Islamabad NULL 44000 Islamabad , Comsats University Islamabd ,Pakistan
PK 08 NULL Islamabad NULL 44000 World Bank Building Sector G 5
PK 08 NULL lahore NULL 54000 3c zafar ali road gulburg 5 3c zafar ali road gulburg 5
PK 08 NULL lahore NULL 54000 49-a bilal park, chaburgy 49-a bilal park, chaburgy
PK NULL NULL Lahore NULL 54000
PK NULL NULL Lahore NULL 54000 85 E block Model Town
SN 01 NULL NULL ouakam cité comico en face 217
SN 01 NULL Dakar NULL
SN 01 NULL Dakar NULL IDEV-ic Patte d'oie Builder's Villa B11
SN 01 NULL Dakar NULL liberte 6/ dakar
SN 01 NULL Dakar NULL ngor
SN 01 NULL Dakar NULL 4027 ZAC Mbao Cité Fadia
SN NULL NULL Dakar NULL IDEV-ic Patte d'oie Builder's Villa B11
TZ NULL Dar es Salaam NULL NULL
TZ NULL Dar es salaam NULL NULL 76021 Dar es salaam 1507 Morogoro
TZ NULL Dar es salaam NULL NULL dar es salaam nassoro.ahmedy@yahoo.com
TZ NULL DAR ES SALAAM NULL NULL dar es salaam UDSM
TZ NULL Dar es salaam NULL NULL NA
TZ NULL DAR ES SALAAM NULL NULL P O BOX 23409
TZ NULL dar es salaam NULL NULL p. o. box 104994
TZ NULL Dar es Salaam NULL NULL P.o. BOX 71415 Dar es Salaam
TZ NULL Dar es Salaam NULL NULL P.O.BOx 66675 DSM
TZ NULL Dar es salaam NULL NULL Tz Tz
TZ NULL dsm NULL NULL
TZ 02 NULL Bagamoyo NULL NULL PO.Box 393
TZ 02 NULL Dar es salaam NULL NULL 22548
TZ 03 NULL Dar-es-salaam NULL NULL Dodoma Municipal Kimara, Dar-es-salaa,
TZ 23 NULL Dar es Salaam NULL NULL
TZ 23 NULL dar es salaam NULL NULL 35074
TZ 23 NULL dar es salaam NULL NULL 67389
TZ 23 NULL Dar es Salaam NULL NULL COSTECH, Dar es Salaam, Tanzania
TZ 23 NULL Dar es salaam NULL NULL na
TZ 23 NULL dar es salaam NULL NULL p o box 60164
TZ 23 NULL dar es salaam NULL NULL P. O. Box 77588
TZ 23 NULL dar es salaam NULL NULL P.O BOX 78144
TZ 23 NULL Dar es Salaam NULL NULL P.O.BOX 78373
TZ 23 NULL Dar es salaam NULL NULL UDSM Dar es Salaam
TZ 23 NULL Dar es salaam NULL NULL udsm udsm
TZ 23 NULL Temeke NULL NULL P.O. Box 50127
TZ NULL NULL Dar es Salaam NULL NULL
TZ NULL NULL Dar es Salaam NULL NULL Kigoma
TZ NULL NULL Dar es Salaam NULL NULL Mwanza
UG NULL Kampala NULL NULL
UG NULL Kampala NULL NULL Kampala Uganda East Africa
US NULL London NULL SE1 8RT Capital Tower 91 Waterloo Road
US CA NULL Los Angeles NULL
US CA NULL Pleasanton NULL 94588 3412 Pickens Lane
US CA NULL Sacramento NULL
US CA NULL San Francisco NULL
US CA NULL seattle NULL 98113 1234 1st st
US CO NULL Denver NULL 80235 6666 West Quincy Ave
US CT NULL Greenwich NULL 06830 140 Milbank
US CT NULL Hartford NULL 06106 Center for Urban and Global Studies at Trinity College, 70 Vernon Street
US DC NULL Washington NULL
US DC NULL Washington NULL 20007 World Bank Headquarters 1818 H Street NW
US DC NULL Washington NULL 20010
US DC NULL Washington NULL 20036
US DC NULL Washington NULL 20405 1889 F St NW
US DC NULL Washington NULL 20433
US DC NULL Washington NULL 20433 1818 H Street NW
US DC NULL Washington NULL 20433 1818H St
US DC NULL Washington NULL 20433 1818 H Street NW
US DC NULL Washington DC NULL 20005 1424, K Street, NW Suite 600
US DC NULL Washington DC NULL 20010 1818 H Street, NW
US DC NULL Washington, DC NULL 20003 1818 H Street NW
US DE NULL Virgin Islands|Charlotte Amalie,Cruz Bay,Christiansted NULL Morocco|Tafraout,Rabat,Tangier,Tetouan,Casablanca,Marrakesh,Fez,Oujda,Meknes,Agadir United Arab Emirates|Garhoud,Dubai,Bur Dubai,Ras al Khaymah,Abu Dhabi,Ajman,Al Fujayrah,Sharjah
US FL NULL Falmouth NULL Falmouth Falmouth
US FL NULL Lilongwe NULL Lilongwe Lilongwe
US GA NULL Atlanta NULL
US GU NULL Herndon NULL 15642 Ht USA
US GU NULL Miami NULL Miami Miami
US MD NULL Gaithersburg NULL 20877 554 N Frederick Avenue Suite 216
US MD NULL Potomac NULL 20854 14 Sandalfoot Court
US MD NULL Silver Spring NULL 20901 9202 Whitney St.
US MI NULL Traverse City NULL 49685 PO Box 792
US ND NULL Pirassununga NULL Pirassununga Pirassununga
US NJ NULL Princeton NULL
US NY NULL Brooklyn NULL 11206-1980 25 Montrose Ave. Apt 304
US NY NULL Brooklyn NULL 11225 975 washington ave 2d
US NY NULL Brooklyn NULL 11217 150 4TH AVE APT 9E
US NY NULL New York NULL
US NY NULL New York NULL 10013 148 Lafayette St. PH
US NY NULL New York NULL 10017 UNICEF 3 UN Plaza
US NY NULL New York NULL 10019 25 Columbus Circle Suite 52E
US NY NULL New York NULL 10024 65 West 85th Street 3A
US NY NULL New York NULL 10027 606 W. 116th Street #22
US NY NULL New York NULL 10037
US NY NULL Rochester NULL
US NY NULL Scarsdale NULL 10583-1423 54 Walworth Avenue
US OR NULL Portland NULL
US PA NULL Philadelphia NULL
US PA NULL Philadelphia NULL
US PR NULL Colonel Hill NULL Colonel Hill Colonel Hill
US SD NULL Banjul NULL Banjul Banjul
US SD NULL London NULL London London
US TX NULL Aledo NULL 76008 1588 Hunterglenn Dr
US TX NULL Keller NULL 76248 810 Placid View Ct.
US WA NULL Seattle NULL
ZA NULL Cape Town NULL
ZA NULL Cape Town NULL 7945 Alexander Road Muizenberg
ZA NULL Pretoria NULL
ZA 11 NULL Cape Town NULL
ZA 11 NULL Cape Town NULL 7435 PostNet Suite #57, Private Bag X18 Milnerton
ZA 11 NULL Cape town NULL 7508 24 Solyet Court, Lansdowne Road Claremont
ZA 11 NULL Cape Town NULL 7701
ZA 11 NULL Cape Town NULL 7785 10 Nyamakazi Road Luzuko Park Phillipi East
ZA 11 NULL Cape Town NULL 7915 66 Albert Rd
ZA 11 NULL Cape Town NULL 8001 210 Long Street
ZM NULL Lusaka NULL
ZM 09 NULL LUSAKA NULL 10101 P.O. BOX FW 174
AT NULL Feldkirch NULL 6800 Pater Grimm Weg 20 NULL
AU NULL Melbourne NULL NULL
AU NULL Sydney NULL NULL
AU 4 NULL NORMANBY NULL 4059 30 Normanby Terrace NULL
BD NULL Dhaka NULL 1205 23, Subal Das Road, Chowdhury Bazar, Lalbagh NULL
BD NULL Dhaka NULL 1207 R-1,H-19,Kallaynpur,Mirpur,Dhaka NULL
BD NULL Dhaka NULL 1207 World Bank Office Dhaka, Plot E 32, Agargaon, Sher-E-Bangla Nagar NULL
BD NULL Dhaka NULL 1209 House# 66B, Flat# B2 Zigatola NULL
BD NULL Dhaka NULL 1219 390 West Rampura Dhaka NULL
BD NULL Dhaka NULL 1230 Uttara NULL
BD 81 NULL Dhaka NULL 1000 Institute of Water and Flood Management NULL
BD 81 NULL Dhaka NULL 1203 84/a maniknagar NULL
BD 81 NULL Dhaka NULL 1205 Dhaka Bangladesh NULL
BD 81 NULL Dhaka NULL 1207 BetterStories Limited 17 West Panthopath NULL
BD 81 NULL Dhaka NULL 1216 Mirpur, Dhaka NULL
BD 81 NULL Dhaka NULL 1230 830, Prembagan, Dhakshin Khan NULL
BD 82 NULL khulna NULL 9203 NULL
BD NULL NULL Dhaka NULL 1000 Institute of Water and Flood Management NULL
BD NULL NULL Dhaka NULL 1207 World Bank Office Dhaka, Plot E 32, Agargaon, Sher-E-Bangla Nagar NULL
BE NULL Brussels NULL NULL
BE NULL Watermael-Boitsfort NULL 1170 Avenue des Staphylins NULL
BH NULL Manama NULL 973 Manama Bahrain Manama Bahrain NULL
BR NULL Porto Alegre NULL NULL
BR NULL Recife NULL NULL
BR RJ NULL Rio de Janeiro NULL NULL
BW NULL Francistown NULL NULL NULL
BW NULL NULL Francistown NULL NULL NULL
CA NULL Montreal NULL NULL
CA NULL Toronto NULL NULL
CA BC NULL Vancouver NULL NULL
CA ON NULL Kitchener NULL NULL
CA ON NULL wterloo NULL n2l3g1 200 University Avenue West NULL
CH NULL Geneva NULL 1202 15, chemin Louis-Dunant NULL
CH 25 NULL Zurich NULL 8098 UBS Optimus Foundation Augustinerhof 1 NULL
DE NULL Berlin NULL NULL
DE 5 NULL Frankfurt am Main NULL 60386 Johanna-Tesch-Platz 7 NULL
DK NULL Aarhus NULL NULL
ES NULL Bilbao NULL NULL
ET 44 NULL ADDIS ABABA NULL 11945 ADDIS ABABA,P.O.BOX 11945 NULL
FI NULL Espoo NULL 2130 Mahlarinne 3B NULL
FI NULL Helsinki NULL 580 Hermannin rantatie 2 A Hermannin rantatie 2 A NULL
FI NULL Tampere NULL 33101 Tampere University of Technology NULL
FI 13 NULL Espoo NULL 2150 Aalto Venture Garage Betonimiehenkuja 3 NULL
GB NULL Exeter NULL NULL
GB NULL London NULL NULL
GB NULL London NULL N4 2DP 2 Myddleton Ave NULL
GB NULL London NULL N7 0AH 104 St Georges Avenue NULL
GB NULL London NULL SE16 3UL 25 Blue Anchor Lane NULL
GB NULL London NULL SW18 5SP Flat 1 150 Merton road NULL
GB NULL London NULL W1T 4BQ 13 Fitzroy Street NULL
GB NULL Oxford NULL NULL
GB NULL Southampton NULL NULL
GB C3 NULL NULL cb244qg 32 market street swavesey NULL
GB E7 NULL London NULL SE3 7TP NULL
GB F3 NULL Wood Green NULL N22 5RU 6 Cedar House NULL
GB H1 NULL London NULL SE11 5JD 47-49 Durham Street NULL
GB H6 NULL London NULL SE8 4DD 8 Harton St Deptford NULL
GB K2 NULL Oxford NULL OX2 6QY 3 The Villas, Rutherway NULL
GH 5 NULL NSAWAM NULL NULL P.O.BOX 455 NULL
GH NULL NULL Accra NULL NULL NULL
ID NULL Bandung NULL 40134 Jalan Sadang Hegar 1 No. 12 RT04 RW13 Sadang Serang NULL
ID NULL Bekasi NULL 17411 Jl.Binadharma 1 No.62. Jatiwaringin NULL
ID NULL Jakarta NULL NULL
ID NULL Jakarta NULL 12440 Jl. H. Niin 7 Lebak Bulus, Cilandak NULL
ID NULL Jakarta NULL 13330 Otista NULL
ID NULL Jakarta selatan NULL 12000 jl. rawa jati timur 6 no. 10 NULL
ID NULL Jakarta Timur NULL Jl.Mulia No.15B Kel.Bidara Cina, Kec.Jatinegara, Jakarta Timur NULL
ID NULL Pematang Siantar NULL 51511 Jl. Durian I 30 NULL
ID 4 NULL Bogor NULL 16165 NULL
ID 4 NULL jakarta NULL otista NULL
ID 4 NULL Jakarta NULL 12520 Jl. Pertanian Raya III No.42 Jakarta Selatan Pasar Minggu NULL
ID 4 NULL Jakarta NULL 13330 Jakarta NULL
ID 4 NULL Jakarta NULL 13330 Jl Sensus IIC Bidaracina Jaktim NULL
ID 4 NULL Jakarta NULL 13330 Jl. Bonasut 2 no.22 NULL
ID 4 NULL Jakarta NULL 13330 Otista 64c NULL
ID 4 NULL jakarta NULL 13330 Otista jaktim NULL
ID 4 NULL Jakarta Timur NULL 13330 Kebon Sayur I no. 1 RT 10/15 NULL
ID 4 NULL Jakarta Timur NULL 13460 Jl. Pondok Kopi Blok G4/5 RT. 005/08 Jakarta Timur NULL
ID 4 NULL Jakarta Timur NULL 13810 Jl. Raya Pondok Gede Rt03 Rw08 no.35 , Lubang Buaya, Jakarta Timur Jl. Raya Pondok Gede Rt03 Rw08 no.35 , Lubang Buaya, Jakarta Timur NULL
ID 7 NULL Brebes NULL 54321 Jl Kersem Blok D14 Perum Taman Indo Kaligangsa Wetan Brebes NULL
ID 7 NULL Semarang NULL 50143 Puspowarno Tengah 2/2 NULL
ID 8 NULL Lumajang NULL 67373 Desa Tumpeng Kecamatan Candipuro Lumajang NULL
ID 30 NULL Bandung NULL 55241 Jl Pelesiran No 55A/56 NULL
ID 30 NULL Bekasi NULL 17510 bekasi West Java Indonesia NULL
ID 30 NULL Depok NULL 16245 Jalan juragan sinda 2 no 10 NULL
ID 30 NULL Depok NULL 16424 Jalan Margonda RayaJalan Kober Gang Mawar NULL
ID 30 NULL Depok NULL 16424 Jl. Haji Yahya Nuih no.24, Pondok Cina NULL
ID 30 NULL Depok NULL 16425 Kukusan Kelurahan NULL
ID 30 NULL Depok NULL 16518 Jl. Salak No.1/C.88 Durenseribu Bojongsari NULL
ID 30 NULL Depok NULL 16952 Jl. Merak No.34 -36 Rt.004/014 Jl. Merak No. 34 -36 Rt. 004/014 NULL
ID 36 NULL biak numfor NULL 98111 jl. s. mamberamo no 6782 biak numfor NULL
IL NULL Tel Aviv NULL NULL
IN NULL Bangalore NULL NULL
IN NULL India NULL NULL
IN NULL new delhi NULL 110003 55 lodi estate NULL
IN 7 NULL NEW DELHI NULL 110018 15/11 A 1ST FLOOR TILAK NAGAR NULL
IN 7 NULL New Delhu NULL 110075 B 54 Hilansh Apartments Plot No 1, Sector 10, Dwarka NULL
IN 10 NULL Gurgaon NULL D- 201 Ivy Apartments Sushant Lok 1 Gurgaon Haryana NULL
IN 13 NULL Trivandrum NULL 695010 TC 9/1615, SRMH Road, Sasthamangalam, Trivandrum NULL
IN 16 NULL Mumbai NULL 400020 Bharat Mahal, Flat#55 Marine Drive NULL
IN 16 NULL Mumbai NULL 400028 303,Shree Parvati Co-Op Housing Society, D.L.Vaidya Road,Dadar NULL
IN 16 NULL Pune NULL NULL
IN 16 NULL Pune NULL Infosys Campus Hinjewadi Phase 2 NULL
IN 16 NULL Pune NULL 400705 #22 Iris Garden Gokhale Road NULL
IN 16 NULL PUNE NULL 411043 NULL
IN 16 NULL Pune NULL 411051 NULL
IN 16 NULL Pune NULL 411057 Infosys Ltd. Rajiv gandhi infostech park Hinjewadi phase 2 NULL
IN 16 NULL Pune NULL 412108 Pune Maharatshtra NULL
IN 16 NULL Pune NULL 433011 502 utkarsh vihar golande state pune NULL
IN 19 NULL Bangalore NULL 560080 Indian Institute for Human Settlements IIHS Bangalore City Campus, Sadashivanagar, NULL
IN 19 NULL Bangalore NULL 560100 electronic city NULL
IN 19 NULL Bhalki NULL 585411 bhalki,bidar ,karnataka karnataka NULL
IN 24 NULL Jaipur NULL 302011 Institute of Health Management Research 1, Prabhu Dayal Marg NULL
IR 26 NULL Tehran NULL 1118844454 Baharestan sq. mostafa khomeini str., javahery Ave., no. 11, NULL
IT NULL Trento NULL NULL
JM 8 NULL Kingston NULL Kgn 7 MOna Campus UWI NULL
KE NULL Nairobi NULL NULL
KE 5 NULL Nairobi NULL 30300 212,kapsabet NULL
KH NULL NULL Phnom Penh NULL NULL
LR NULL Monrovia NULL 0 NULL
NG 11 NULL Abuja NULL 930001 17 Bechar street Wuse zone 2 NULL
PE 15 NULL Lima NULL 18 Lima Lima NULL
PE 15 NULL Lima NULL Lima 18 123 Miraflores NULL
PE NULL NULL Lima NULL 3 Calle Granada 104 NULL
PE NULL NULL Lima NULL 18 Lima Lima NULL
PH NULL Manila NULL Globe Telepark 111 Valero Street NULL
PH NULL Quezon Coty NULL 1109 86 Harvard Street, Cubao, Quezon City, Philippines 84 Harvard Street, Cubao, Quezon City,hilippines NULL
PH 20 NULL Silang NULL 4118 370 Bayungan Kaong Silang Cavite NULL
PH 57 NULL Kidapawan NULL 9400 Kidapawan City Kidapawan City NULL
PH 66 NULL zamboanga NULL 7000 29-tripplet rd san jose 29-tripplet rd san jose NULL
PH D9 NULL Pasig City NULL World Bank Office Manila, 20/F Taipan Place F. Ortigas Jr. Road, Ortigas Center NULL
PK NULL Lahore NULL 54000 17-R Model Town Lahore NULL
PK NULL Lahore NULL 54000 53- chamber lane road Lahore NULL
PK NULL Lahore NULL 54000 85 E block Model Town NULL
PK NULL Lahore NULL 54000 House no 227, street no 5, Imamia Colony Shahadra Lahore NULL
PK NULL LAHORE NULL 54000 room no.6 khalid bim waleed hall, near New Anarkali, LAHORE room no.6 khalid bim waleed hall, near New Anarkali, LAHORE NULL
PK NULL Lahore NULL pk097 LUMS, Lahore, NULL
PK NULL Sheikhupura NULL 3935 D.H.Q.Hospital Sheikhupura House number 08. Room no 109 Khalid bin waleed haal, punjab University lahore old campus. NULL
PK 2 NULL Quetta NULL 87000 Postal Address 87000, Kuchlak, Quetta, Balochistan. H#24 Peer Abul Khair road Quetta, Balochistan. NULL
PK 2 NULL Quetta NULL 87300 block no-1 falt no. 7 New Crime Branch Abbas Ali Road Cantt NULL
PK 2 NULL Quetta NULL 87300 Flat no. 3 Shafeen Centre Jinnah Town ,Near I.T university , Quetta NULL
PK 2 NULL Quetta NULL 87300 H-no. C-220 Zarghoonabad Phase-2 , Nawa Killi ,Quetta NULL
PK 4 NULL burewala NULL 60101 Fatima Fayyaz Hazrat Sakina hall girls hostel number 9 Punjab university Lahore Pakistan Sardar Wajid Azim Azeem abad Burewala dist Vehari Pakistan NULL
PK 4 NULL Faisalabad NULL 38000 P 101/1, Green Town, Millat Road, Faisalabad NULL
PK 4 NULL Islamabad NULL 44000 P.O Tarlai kalan chappar Islamabad NULL
PK 4 NULL lahore NULL NULL
PK 4 NULL Lahore NULL 54000 NULL
PK 4 NULL lahore NULL 54000 Street No.63 House 36/A Al-madad Pak Colony Ravi Road, Lahore. Street No.63 House 36/A Al-madad Pak Colony Ravi Road, Lahore. NULL
PK 4 NULL Lahore NULL 54000 1149-1-D2 Green Town Lahore NULL
PK 4 NULL Lahore NULL 54000 124, street# 2, karim block Allama Iqbal Town lahore. 124, street# 2, karim block Allama Iqbal Town lahore. NULL
PK 4 NULL Lahore NULL 54000 150 A Qila Lachman Singh Ravi Road lahore NULL
PK 4 NULL Lahore NULL 54000 166/1L DHA Lahore NULL
PK 4 NULL Lahore NULL 54000 172 A2 Township Lahore NULL
PK 4 NULL Lahore NULL 54000 183,S/Block, Model Town, Lhr NULL
PK 4 NULL lahore NULL 54000 19- A block ,Eden Lane Villas Raiwind Road ,Lahore NULL
PK 4 NULL lahore NULL 54000 3-c kaliyar road opposite kids lyceum, rustam park near mor samnabad NULL
PK 4 NULL Lahore NULL 54000 31 Saeed Block, Canal Bank Scheme NULL
PK 4 NULL Lahore NULL 54000 31c DHA Lahore NULL
PK 4 NULL Lahore NULL 54000 387 E1 wapda town, Lahore NULL
PK 4 NULL Lahore NULL 54000 45-D dha eme sector multan road,lahore NULL
PK 4 NULL Lahore NULL 54000 5 Zafar Ali Road NULL
PK 4 NULL Lahore NULL 54000 54-R PGECHS NULL
PK 4 NULL lahore NULL 54000 566 E-1 johar town lahore 566 E-1 johar town lahore NULL
PK 4 NULL Lahore NULL 54000 82/1 Z Block, Phase 3 DHA NULL
PK 4 NULL Lahore NULL 54000 A-1 VRI Zarrar shaheed road lahore cantt A-1 VRI Zarrar shaheed road lahore cantt NULL
PK 4 NULL lahore NULL 54000 e5/39D street 6 zaman colony cavalry ground ext NULL
PK 4 NULL Lahore NULL 54000 Ho # 61, Block G3, Johar Town Lahore NULL
PK 4 NULL LAhore NULL 54000 House #19-A street #5 Usman nagr Ghaziabad Lahore NULL
PK 4 NULL lahore NULL 54000 House no 692 street no 67 sadar bazar NULL
PK 4 NULL Lahore NULL 54000 Khosa Law Chamber 1 Turner Road NULL
PK 4 NULL Lahore NULL 54000 Lahore,Pakistan Lahore,Pakistan NULL
PK 4 NULL Lahore NULL 54000 room no 69, khalid bin waleed hall, anarkali NULL
PK 4 NULL Lahore NULL 54000 Suite # 8, Al-Hafeez Suites, Gulberg II NULL
PK 4 NULL Lahore NULL 54085 199 Shadman 2 NULL
PK 4 NULL Lahore NULL 54300 Mughalpura Lahore Pakistan NULL
PK 4 NULL Lahore NULL 54660 SD 69 falcon complex gulberg III lahore NULL
PK 4 NULL lahore NULL 54800 764-G4 johar town ,lahore NULL
PK 4 NULL Rawalpindi NULL 44000 House 522, F-Block Sattellite Town, Rawalpindi NULL
PK 4 NULL Rawalpindi NULL 46000 1950/c, Indusroad 2, Tariqabad, Rawalpindi Cantt NULL
PK 4 NULL Rawalpindi NULL 46000 House 54-E Lane 9 Sector 4, AECHS Chaklala Rawalpindi NULL
PK 4 NULL Rawalpindi NULL 46000 House B-1343, Sattellite town Rawalpindi NULL
PK 4 NULL Rawalpindi NULL 46000 House CB-299F, Street 1, Lane 4 Peshawar Road Rawalpindi NULL
PK 4 NULL Rawalpindi NULL 46300 House No 1518 Umer Block phase 8 BehriaTown NULL
PK 4 NULL sialkot NULL 51310 The National Model School, Ismaiealabad, Pacca Garah Sialkot NULL
PK 8 NULL Islamabad NULL CIomsats Institute of Information Technology Islamabad NULL
PK 8 NULL Islamabad NULL 38700 COMSATS tarlai boys hostel Islamabad. COMSATS tarlai boys hostel Islamabad (Room 30) NULL
PK 8 NULL Islamabad NULL 44000 NULL
PK 8 NULL Islamabad NULL 44000 House # 256, Street # 9, Shahzad Town, Islamabad. NULL
PK 8 NULL Islamabad NULL 44000 Islamabad , Comsats University Islamabd ,Pakistan NULL
PK 8 NULL Islamabad NULL 44000 World Bank Building Sector G 5 NULL
PK 8 NULL lahore NULL 54000 3c zafar ali road gulburg 5 3c zafar ali road gulburg 5 NULL
PK 8 NULL lahore NULL 54000 49-a bilal park, chaburgy 49-a bilal park, chaburgy NULL
PK NULL NULL Lahore NULL 54000 NULL
PK NULL NULL Lahore NULL 54000 85 E block Model Town NULL
SN 1 NULL NULL ouakam cité comico en face 217 NULL
SN 1 NULL Dakar NULL NULL
SN 1 NULL Dakar NULL IDEV-ic Patte d'oie Builder's Villa B11 NULL
SN 1 NULL Dakar NULL liberte 6/ dakar NULL
SN 1 NULL Dakar NULL ngor NULL
SN 1 NULL Dakar NULL 4027 ZAC Mbao Cité Fadia NULL
SN NULL NULL Dakar NULL IDEV-ic Patte d'oie Builder's Villa B11 NULL
TZ NULL Dar es Salaam NULL NULL NULL
TZ NULL Dar es salaam NULL NULL 76021 Dar es salaam 1507 Morogoro NULL
TZ NULL Dar es salaam NULL NULL dar es salaam nassoro.ahmedy@yahoo.com NULL
TZ NULL DAR ES SALAAM NULL NULL dar es salaam UDSM NULL
TZ NULL Dar es salaam NULL NULL NA NULL
TZ NULL DAR ES SALAAM NULL NULL P O BOX 23409 NULL
TZ NULL dar es salaam NULL NULL p. o. box 104994 NULL
TZ NULL Dar es Salaam NULL NULL P.o. BOX 71415 Dar es Salaam NULL
TZ NULL Dar es Salaam NULL NULL P.O.BOx 66675 DSM NULL
TZ NULL Dar es salaam NULL NULL Tz Tz NULL
TZ NULL dsm NULL NULL NULL
TZ 2 NULL Bagamoyo NULL NULL PO.Box 393 NULL
TZ 2 NULL Dar es salaam NULL NULL 22548 NULL
TZ 3 NULL Dar-es-salaam NULL NULL Dodoma Municipal Kimara, Dar-es-salaa, NULL
TZ 23 NULL Dar es Salaam NULL NULL NULL
TZ 23 NULL dar es salaam NULL NULL 35074 NULL
TZ 23 NULL dar es salaam NULL NULL 67389 NULL
TZ 23 NULL Dar es Salaam NULL NULL COSTECH, Dar es Salaam, Tanzania NULL
TZ 23 NULL Dar es salaam NULL NULL na NULL
TZ 23 NULL dar es salaam NULL NULL p o box 60164 NULL
TZ 23 NULL dar es salaam NULL NULL P. O. Box 77588 NULL
TZ 23 NULL dar es salaam NULL NULL P.O BOX 78144 NULL
TZ 23 NULL Dar es Salaam NULL NULL P.O.BOX 78373 NULL
TZ 23 NULL Dar es salaam NULL NULL UDSM Dar es Salaam NULL
TZ 23 NULL Dar es salaam NULL NULL udsm udsm NULL
TZ 23 NULL Temeke NULL NULL P.O. Box 50127 NULL
TZ NULL NULL Dar es Salaam NULL NULL NULL
TZ NULL NULL Dar es Salaam NULL NULL Kigoma NULL
TZ NULL NULL Dar es Salaam NULL NULL Mwanza NULL
UG NULL Kampala NULL NULL NULL
UG NULL Kampala NULL NULL Kampala Uganda East Africa NULL
US NULL London NULL SE1 8RT Capital Tower 91 Waterloo Road NULL
US CA NULL Los Angeles NULL NULL
US CA NULL Pleasanton NULL 94588 3412 Pickens Lane NULL
US CA NULL Sacramento NULL NULL
US CA NULL San Francisco NULL NULL
US CA NULL seattle NULL 98113 1234 1st st NULL
US CO NULL Denver NULL 80235 6666 West Quincy Ave NULL
US CT NULL Greenwich NULL 6830 140 Milbank NULL
US CT NULL Hartford NULL 6106 Center for Urban and Global Studies at Trinity College, 70 Vernon Street NULL
US DC NULL Washington NULL NULL
US DC NULL Washington NULL 20007 World Bank Headquarters 1818 H Street NW NULL
US DC NULL Washington NULL 20010 NULL
US DC NULL Washington NULL 20036 NULL
US DC NULL Washington NULL 20405 1889 F St NW NULL
US DC NULL Washington NULL 20433 NULL
US DC NULL Washington NULL 20433 1818 H Street NW NULL
US DC NULL Washington NULL 20433 1818H St NULL
US DC NULL Washington NULL 20433 1818 H Street NW NULL
US DC NULL Washington DC NULL 20005 1424, K Street, NW Suite 600 NULL
US DC NULL Washington DC NULL 20010 1818 H Street, NW NULL
US DC NULL Washington, DC NULL 20003 1818 H Street NW NULL
US DE NULL Virgin Islands|Charlotte Amalie,Cruz Bay,Christiansted NULL Morocco|Tafraout,Rabat,Tangier,Tetouan,Casablanca,Marrakesh,Fez,Oujda,Meknes,Agadir United Arab Emirates|Garhoud,Dubai,Bur Dubai,Ras al Khaymah,Abu Dhabi,Ajman,Al Fujayrah,Sharjah NULL
US FL NULL Falmouth NULL Falmouth Falmouth NULL
US FL NULL Lilongwe NULL Lilongwe Lilongwe NULL
US GA NULL Atlanta NULL NULL
US GU NULL Herndon NULL 15642 Ht USA NULL
US GU NULL Miami NULL Miami Miami NULL
US MD NULL Gaithersburg NULL 20877 554 N Frederick Avenue Suite 216 NULL
US MD NULL Potomac NULL 20854 14 Sandalfoot Court NULL
US MD NULL Silver Spring NULL 20901 9202 Whitney St. NULL
US MI NULL Traverse City NULL 49685 PO Box 792 NULL
US ND NULL Pirassununga NULL Pirassununga Pirassununga NULL
US NJ NULL Princeton NULL NULL
US NY NULL Brooklyn NULL 11206-1980 25 Montrose Ave. Apt 304 NULL
US NY NULL Brooklyn NULL 11225 975 washington ave 2d NULL
US NY NULL Brooklyn NULL 11217 150 4TH AVE APT 9E NULL
US NY NULL New York NULL NULL
US NY NULL New York NULL 10013 148 Lafayette St. PH NULL
US NY NULL New York NULL 10017 UNICEF 3 UN Plaza NULL
US NY NULL New York NULL 10019 25 Columbus Circle Suite 52E NULL
US NY NULL New York NULL 10024 65 West 85th Street 3A NULL
US NY NULL New York NULL 10027 606 W. 116th Street #22 NULL
US NY NULL New York NULL 10037 NULL
US NY NULL Rochester NULL NULL
US NY NULL Scarsdale NULL 10583-1423 54 Walworth Avenue NULL
US OR NULL Portland NULL NULL
US PA NULL Philadelphia NULL NULL
US PA NULL Philadelphia NULL NULL
US PR NULL Colonel Hill NULL Colonel Hill Colonel Hill NULL
US SD NULL Banjul NULL Banjul Banjul NULL
US SD NULL London NULL London London NULL
US TX NULL Aledo NULL 76008 1588 Hunterglenn Dr NULL
US TX NULL Keller NULL 76248 810 Placid View Ct. NULL
US WA NULL Seattle NULL NULL
ZA NULL Cape Town NULL NULL
ZA NULL Cape Town NULL 7945 Alexander Road Muizenberg NULL
ZA NULL Pretoria NULL NULL
ZA 11 NULL Cape Town NULL NULL
ZA 11 NULL Cape Town NULL 7435 PostNet Suite #57, Private Bag X18 Milnerton NULL
ZA 11 NULL Cape town NULL 7508 24 Solyet Court, Lansdowne Road Claremont NULL
ZA 11 NULL Cape Town NULL 7701 NULL
ZA 11 NULL Cape Town NULL 7785 10 Nyamakazi Road Luzuko Park Phillipi East NULL
ZA 11 NULL Cape Town NULL 7915 66 Albert Rd NULL
ZA 11 NULL Cape Town NULL 8001 210 Long Street NULL
ZM NULL Lusaka NULL NULL
ZM 9 NULL LUSAKA NULL 10101 P.O. BOX FW 174 NULL
@@ -41,11 +41,11 @@ function addressfield_get_address_format($country_code) {
// postal code in 'used_fields'.
$countries_with_optional_postal_code = array(
'AC', 'AD', 'AL', 'AZ', 'BA', 'BB', 'BD', 'BG', 'BH', 'BM', 'BN', 'BT',
'CR', 'CY', 'CZ', 'DO', 'DZ', 'EC', 'EH', 'ET', 'FO', 'GE', 'GN', 'GT',
'CR', 'CY', 'DO', 'DZ', 'EC', 'EH', 'ET', 'FO', 'GE', 'GN', 'GT',
'GW', 'HR', 'HT', 'IL', 'IS', 'JO', 'KE', 'KG', 'KH', 'KW', 'LA',
'LA', 'LB', 'LK', 'LR', 'LS', 'MA', 'MC', 'MD', 'ME', 'MG', 'MK', 'MM',
'MT', 'MU', 'MV', 'NE', 'NP', 'OM', 'PK', 'PY', 'RO', 'RS', 'SA', 'SI',
'SK', 'SN', 'SZ', 'TA', 'TJ', 'TM', 'TN', 'VA', 'VC', 'VG', 'XK', 'ZM',
'SN', 'SZ', 'TA', 'TJ', 'TM', 'TN', 'VA', 'VC', 'VG', 'XK', 'ZM',
);
foreach ($countries_with_optional_postal_code as $code) {
$address_formats[$code] = array(
@@ -56,9 +56,9 @@ function addressfield_get_address_format($country_code) {
// These formats differ from the default only by the presence of the
// postal code in 'used_fields' and 'required_fields'.
$countries_with_required_postal_code = array(
'AT', 'AX', 'BE', 'BL', 'CH', 'DE', 'DK', 'FI', 'FK', 'FR', 'GF', 'GG',
'AT', 'AX', 'BE', 'BL', 'CH', 'CZ', 'DE', 'DK', 'FI', 'FK', 'FR', 'GF', 'GG',
'GL', 'GP', 'GR', 'GS', 'HU', 'IM', 'IO', 'JE', 'LI', 'LU', 'MF', 'MQ', 'NC',
'NL', 'NO', 'PL', 'PM', 'PN', 'PT', 'RE', 'SE', 'SH', 'SJ', 'TC', 'WF',
'NL', 'NO', 'PL', 'PM', 'PN', 'PT', 'RE', 'SE', 'SH', 'SJ', 'SK', 'TC', 'WF',
'YT',
);
foreach ($countries_with_required_postal_code as $code) {
@@ -114,7 +114,7 @@ function addressfield_get_address_format($country_code) {
'used_fields' => array('locality', 'administrative_area', 'postal_code'),
);
$address_formats['CL'] = array(
'used_fields' => array('locality', 'administrative_area', 'postal_code'),
'used_fields' => array('dependent_locality', 'locality', 'administrative_area', 'postal_code'),
'administrative_area_label' => t('State', array(), array('context' => 'Territory of a country')),
'render_administrative_area_value' => TRUE,
);
@@ -124,7 +124,7 @@ function addressfield_get_address_format($country_code) {
'dependent_locality_label' => t('District'),
);
$address_formats['CO'] = array(
'used_fields' => array('locality', 'administrative_area'),
'used_fields' => array('locality', 'administrative_area', 'postal_code'),
'administrative_area_label' => t('Department', array(), array('context' => 'Territory of a country')),
);
$address_formats['CV'] = array(
@@ -235,6 +235,7 @@ function addressfield_get_address_format($country_code) {
'used_fields' => array('dependent_locality', 'locality', 'administrative_area', 'postal_code'),
'required_fields' => array('locality', 'administrative_area', 'postal_code'),
'dependent_locality_label' => t('District'),
'render_administrative_area_value' => TRUE,
);
$address_formats['KY'] = array(
'used_fields' => array('administrative_area', 'postal_code'),
@@ -11,6 +11,29 @@
* NULL if not found.
*/
function addressfield_get_administrative_areas($country_code) {
// Maintain a static cache to avoid passing the administrative areas through
// t() more than once per request.
$administrative_areas = &drupal_static(__FUNCTION__, array());
if (empty($administrative_areas)) {
// Get the default administrative areas.
$administrative_areas = _addressfield_get_administrative_areas_defaults();
// Allow other modules to alter the administrative areas.
drupal_alter('addressfield_administrative_areas', $administrative_areas);
}
return isset($administrative_areas[$country_code]) ? $administrative_areas[$country_code] : NULL;
}
/**
* Provides the default administrative areas.
*/
function _addressfield_get_administrative_areas_defaults() {
// To avoid needless pollution of the strings list we only pass to t()
// those administrative areas that are in English (or a latin transcription),
// and belong to a country that either has multiple official languages (CA)
// or uses a non-latin script (AE, CN, JP, KR, UA, RU, etc).
// No translation is expected in other cases.
$administrative_areas = array();
$administrative_areas['AE'] = array(
'AZ' => t('Abu Dhabi'),
@@ -22,69 +45,69 @@ function addressfield_get_administrative_areas($country_code) {
'AJ' => t('Ajmān'),
);
$administrative_areas['AR'] = array(
'B' => t('Buenos Aires'),
'K' => t('Catamarca'),
'H' => t('Chaco'),
'U' => t('Chubut'),
'C' => t('Ciudad de Buenos Aires'),
'X' => t('Córdoba'),
'W' => t('Corrientes'),
'E' => t('Entre Ríos'),
'P' => t('Formosa'),
'Y' => t('Jujuy'),
'L' => t('La Pampa'),
'F' => t('La Rioja'),
'M' => t('Mendoza'),
'N' => t('Misiones'),
'Q' => t('Neuquén'),
'R' => t('Río Negro'),
'A' => t('Salta'),
'J' => t('San Juan'),
'D' => t('San Luis'),
'Z' => t('Santa Cruz'),
'S' => t('Santa Fe'),
'G' => t('Santiago del Estero'),
'V' => t('Tierra del Fuego'),
'T' => t('Tucumán'),
'B' => 'Buenos Aires',
'K' => 'Catamarca',
'H' => 'Chaco',
'U' => 'Chubut',
'C' => 'Ciudad de Buenos Aires',
'X' => 'Córdoba',
'W' => 'Corrientes',
'E' => 'Entre Ríos',
'P' => 'Formosa',
'Y' => 'Jujuy',
'L' => 'La Pampa',
'F' => 'La Rioja',
'M' => 'Mendoza',
'N' => 'Misiones',
'Q' => 'Neuquén',
'R' => 'Río Negro',
'A' => 'Salta',
'J' => 'San Juan',
'D' => 'San Luis',
'Z' => 'Santa Cruz',
'S' => 'Santa Fe',
'G' => 'Santiago del Estero',
'V' => 'Tierra del Fuego',
'T' => 'Tucumán',
);
$administrative_areas['AU'] = array(
'ACT' => t('Australian Capital Territory'),
'NSW' => t('New South Wales'),
'NT' => t('Northern Territory'),
'QLD' => t('Queensland'),
'SA' => t('South Australia'),
'TAS' => t('Tasmania'),
'VIC' => t('Victoria'),
'WA' => t('Western Australia'),
'ACT' => 'Australian Capital Territory',
'NSW' => 'New South Wales',
'NT' => 'Northern Territory',
'QLD' => 'Queensland',
'SA' => 'South Australia',
'TAS' => 'Tasmania',
'VIC' => 'Victoria',
'WA' => 'Western Australia',
);
$administrative_areas['BR'] = array(
'AC' => t('Acre'),
'AL' => t('Alagoas'),
'AM' => t('Amazonas'),
'AP' => t('Amapá'),
'BA' => t('Bahia'),
'CE' => t('Ceará'),
'DF' => t('Distrito Federal'),
'ES' => t('Espírito Santo'),
'GO' => t('Goiás'),
'MA' => t('Maranhão'),
'MG' => t('Minas Gerais'),
'MS' => t('Mato Grosso do Sul'),
'MT' => t('Mato Grosso'),
'PA' => t('Pará'),
'PB' => t('Paraíba'),
'PE' => t('Pernambuco'),
'PI' => t('Piauí'),
'PR' => t('Paraná'),
'RJ' => t('Rio de Janeiro'),
'RN' => t('Rio Grande do Norte'),
'RO' => t('Rondônia'),
'RR' => t('Roraima'),
'RS' => t('Rio Grande do Sul'),
'SC' => t('Santa Catarina'),
'SE' => t('Sergipe'),
'SP' => t('São Paulo'),
'TO' => t('Tocantins'),
'AC' => 'Acre',
'AL' => 'Alagoas',
'AM' => 'Amazonas',
'AP' => 'Amapá',
'BA' => 'Bahia',
'CE' => 'Ceará',
'DF' => 'Distrito Federal',
'ES' => 'Espírito Santo',
'GO' => 'Goiás',
'MA' => 'Maranhão',
'MG' => 'Minas Gerais',
'MS' => 'Mato Grosso do Sul',
'MT' => 'Mato Grosso',
'PA' => 'Pará',
'PB' => 'Paraíba',
'PE' => 'Pernambuco',
'PI' => 'Piauí',
'PR' => 'Paraná',
'RJ' => 'Rio de Janeiro',
'RN' => 'Rio Grande do Norte',
'RO' => 'Rondônia',
'RR' => 'Roraima',
'RS' => 'Rio Grande do Sul',
'SC' => 'Santa Catarina',
'SE' => 'Sergipe',
'SP' => 'São Paulo',
'TO' => 'Tocantins',
);
$administrative_areas['CA'] = array(
'AB' => t('Alberta'),
@@ -102,21 +125,21 @@ function addressfield_get_administrative_areas($country_code) {
'YT' => t('Yukon Territory'),
);
$administrative_areas['CL'] = array(
'AI' => t('Aysén del General Carlos Ibáñez del Campo'),
'AN' => t('Antofagasta'),
'AR' => t('Araucanía'),
'AP' => t('Arica y Parinacota'),
'AT' => t('Atacama'),
'BI' => t('Biobío'),
'CO' => t('Coquimbo'),
'LI' => t('Libertador General Bernardo O\'Higgins'),
'LL' => t('Los Lagos'),
'LR' => t('Los Ríos'),
'MA' => t('Magallanes y de la Antártica Chilena'),
'ML' => t('Maule'),
'RM' => t('Metropolitana de Santiago'),
'TA' => t('Tarapacá'),
'VS' => t('Valparaíso'),
'AI' => 'Aysén del General Carlos Ibáñez del Campo',
'AN' => 'Antofagasta',
'AR' => 'Araucanía',
'AP' => 'Arica y Parinacota',
'AT' => 'Atacama',
'BI' => 'Biobío',
'CO' => 'Coquimbo',
'LI' => 'Libertador General Bernardo O\'Higgins',
'LL' => 'Los Lagos',
'LR' => 'Los Ríos',
'MA' => 'Magallanes y de la Antártica Chilena',
'ML' => 'Maule',
'RM' => 'Metropolitana de Santiago',
'TA' => 'Tarapacá',
'VS' => 'Valparaíso',
);
$administrative_areas['CN'] = array(
'34' => t('Anhui Sheng'),
@@ -155,55 +178,55 @@ function addressfield_get_administrative_areas($country_code) {
'33' => t('Zhejiang Sheng'),
);
$administrative_areas['CO'] = array(
'AMA' => t('Amazonas'),
'ANT' => t('Antioquia'),
'ARA' => t('Arauca'),
'ATL' => t('Atlántico'),
'BOL' => t('Bolívar'),
'BOY' => t('Boyacá'),
'CAL' => t('Caldas'),
'CAQ' => t('Caquetá'),
'CAS' => t('Casanare'),
'CAU' => t('Cauca'),
'CES' => t('Cesar'),
'COR' => t('Córdoba'),
'CUN' => t('Cundinamarca'),
'CHO' => t('Chocó'),
'GUA' => t('Guainía'),
'GUV' => t('Guaviare'),
'HUI' => t('Huila'),
'LAG' => t('La Guajira'),
'MAG' => t('Magdalena'),
'MET' => t('Meta'),
'NAR' => t('Nariño'),
'NSA' => t('Norte de Santander'),
'PUT' => t('Putumayo'),
'QUI' => t('Quindío'),
'RIS' => t('Risaralda'),
'SAP' => t('San Andrés, Providencia y Santa Catalina'),
'SAN' => t('Santander'),
'SUC' => t('Sucre'),
'TOL' => t('Tolima'),
'VAC' => t('Valle del Cauca'),
'VAU' => t('Vaupés'),
'VID' => t('Vichada'),
'AMA' => 'Amazonas',
'ANT' => 'Antioquia',
'ARA' => 'Arauca',
'ATL' => 'Atlántico',
'BOL' => 'Bolívar',
'BOY' => 'Boyacá',
'CAL' => 'Caldas',
'CAQ' => 'Caquetá',
'CAS' => 'Casanare',
'CAU' => 'Cauca',
'CES' => 'Cesar',
'COR' => 'Córdoba',
'CUN' => 'Cundinamarca',
'CHO' => 'Chocó',
'GUA' => 'Guainía',
'GUV' => 'Guaviare',
'HUI' => 'Huila',
'LAG' => 'La Guajira',
'MAG' => 'Magdalena',
'MET' => 'Meta',
'NAR' => 'Nariño',
'NSA' => 'Norte de Santander',
'PUT' => 'Putumayo',
'QUI' => 'Quindío',
'RIS' => 'Risaralda',
'SAP' => 'San Andrés, Providencia y Santa Catalina',
'SAN' => 'Santander',
'SUC' => 'Sucre',
'TOL' => 'Tolima',
'VAC' => 'Valle del Cauca',
'VAU' => 'Vaupés',
'VID' => 'Vichada',
);
$administrative_areas['EE'] = array(
'37' => t('Harjumaa'),
'39' => t('Hiiumaa'),
'44' => t('Ida-Virumaa'),
'49' => t('Jõgevamaa'),
'51' => t('Järvamaa'),
'57' => t('Läänemaa'),
'59' => t('Lääne-Virumaa'),
'65' => t('Põlvamaa'),
'67' => t('Pärnumaa'),
'70' => t('Raplamaa'),
'74' => t('Saaremaa'),
'78' => t('Tartumaa'),
'82' => t('Valgamaa'),
'84' => t('Viljandimaa'),
'86' => t('Võrumaa'),
'37' => 'Harjumaa',
'39' => 'Hiiumaa',
'44' => 'Ida-Virumaa',
'49' => 'Jõgevamaa',
'51' => 'Järvamaa',
'57' => 'Läänemaa',
'59' => 'Lääne-Virumaa',
'65' => 'Põlvamaa',
'67' => 'Pärnumaa',
'70' => 'Raplamaa',
'74' => 'Saaremaa',
'78' => 'Tartumaa',
'82' => 'Valgamaa',
'84' => 'Viljandimaa',
'86' => 'Võrumaa',
);
$administrative_areas['EG'] = array(
'ALX' => t('Alexandria'),
@@ -235,58 +258,58 @@ function addressfield_get_administrative_areas($country_code) {
'LX' => t('Luxor'),
);
$administrative_areas['ES'] = array(
'C' => t("A Coruña"),
'VI' => t('Alava'),
'AB' => t('Albacete'),
'A' => t('Alicante'),
'AL' => t("Almería"),
'O' => t('Asturias'),
'AV' => t("Ávila"),
'BA' => t('Badajoz'),
'PM' => t('Baleares'),
'B' => t('Barcelona'),
'BU' => t('Burgos'),
'CC' => t("Cáceres"),
'CA' => t("Cádiz"),
'S' => t('Cantabria'),
'CS' => t("Castellón"),
'CE' => t('Ceuta'),
'CR' => t('Ciudad Real'),
'CO' => t("Córdoba"),
'CU' => t('Cuenca'),
'GI' => t('Gerona'),
'GR' => t('Granada'),
'GU' => t('Guadalajara'),
'SS' => t("Guipúzcoa"),
'H' => t('Huelva'),
'HU' => t('Huesca'),
'J' => t("Jaén"),
'LO' => t('La Rioja'),
'GC' => t('Las Palmas'),
'LE' => t("León"),
'L' => t("Lérida"),
'LU' => t('Lugo'),
'M' => t('Madrid'),
'MA' => t("Málaga"),
'ML' => t('Melilla'),
'MU' => t('Murcia'),
'NA' => t('Navarra'),
'OR' => t('Ourense'),
'P' => t('Palencia'),
'PO' => t('Pontevedra'),
'SA' => t('Salamanca'),
'TF' => t('Santa Cruz de Tenerife'),
'SG' => t('Segovia'),
'SE' => t('Sevilla'),
'SO' => t('Soria'),
'T' => t('Tarragona'),
'TE' => t('Teruel'),
'TO' => t('Toledo'),
'V' => t('Valencia'),
'VA' => t('Valladolid'),
'BI' => t('Vizcaya'),
'ZA' => t('Zamora'),
'Z' => t('Zaragoza'),
'C' => "A Coruña",
'VI' => 'Alava',
'AB' => 'Albacete',
'A' => 'Alicante',
'AL' => "Almería",
'O' => 'Asturias',
'AV' => "Ávila",
'BA' => 'Badajoz',
'PM' => 'Baleares',
'B' => 'Barcelona',
'BU' => 'Burgos',
'CC' => "Cáceres",
'CA' => "Cádiz",
'S' => 'Cantabria',
'CS' => "Castellón",
'CE' => 'Ceuta',
'CR' => 'Ciudad Real',
'CO' => "Córdoba",
'CU' => 'Cuenca',
'GI' => 'Girona',
'GR' => 'Granada',
'GU' => 'Guadalajara',
'SS' => "Guipúzcoa",
'H' => 'Huelva',
'HU' => 'Huesca',
'J' => "Jaén",
'LO' => 'La Rioja',
'GC' => 'Las Palmas',
'LE' => "León",
'L' => "Lleida",
'LU' => 'Lugo',
'M' => 'Madrid',
'MA' => "Málaga",
'ML' => 'Melilla',
'MU' => 'Murcia',
'NA' => 'Navarra',
'OR' => 'Ourense',
'P' => 'Palencia',
'PO' => 'Pontevedra',
'SA' => 'Salamanca',
'TF' => 'Santa Cruz de Tenerife',
'SG' => 'Segovia',
'SE' => 'Sevilla',
'SO' => 'Soria',
'T' => 'Tarragona',
'TE' => 'Teruel',
'TO' => 'Toledo',
'V' => 'Valencia',
'VA' => 'Valladolid',
'BI' => 'Vizcaya',
'ZA' => 'Zamora',
'Z' => 'Zaragoza',
);
$administrative_areas['HK'] = array(
// HK subdivisions have no ISO codes assigned.
@@ -330,57 +353,57 @@ function addressfield_get_administrative_areas($country_code) {
'SU' => t('Sumatera Utara'),
);
$administrative_areas['IE'] = array(
'CW' => t('Co Carlow'),
'CN' => t('Co Cavan'),
'CE' => t('Co Clare'),
'CO' => t('Co Cork'),
'DL' => t('Co Donegal'),
'D' => t('Co Dublin'),
'D1' => t('Dublin 1'),
'D2' => t('Dublin 2'),
'D3' => t('Dublin 3'),
'D4' => t('Dublin 4'),
'D5' => t('Dublin 5'),
'D6' => t('Dublin 6'),
'D6W' => t('Dublin 6w'),
'D7' => t('Dublin 7'),
'D8' => t('Dublin 8'),
'D9' => t('Dublin 9'),
'D10' => t('Dublin 10'),
'D11' => t('Dublin 11'),
'D12' => t('Dublin 12'),
'D13' => t('Dublin 13'),
'D14' => t('Dublin 14'),
'D15' => t('Dublin 15'),
'D16' => t('Dublin 16'),
'D17' => t('Dublin 17'),
'D18' => t('Dublin 18'),
'D19' => t('Dublin 19'),
'D20' => t('Dublin 20'),
'D21' => t('Dublin 21'),
'D22' => t('Dublin 22'),
'D23' => t('Dublin 23'),
'D24' => t('Dublin 24'),
'G' => t('Co Galway'),
'KY' => t('Co Kerry'),
'KE' => t('Co Kildare'),
'KK' => t('Co Kilkenny'),
'LS' => t('Co Laois'),
'LM' => t('Co Leitrim'),
'LK' => t('Co Limerick'),
'LD' => t('Co Longford'),
'LH' => t('Co Louth'),
'MO' => t('Co Mayo'),
'MH' => t('Co Meath'),
'MN' => t('Co Monaghan'),
'OY' => t('Co Offaly'),
'RN' => t('Co Roscommon'),
'SO' => t('Co Sligo'),
'TA' => t('Co Tipperary'),
'WD' => t('Co Waterford'),
'WH' => t('Co Westmeath'),
'WX' => t('Co Wexford'),
'WW' => t('Co Wicklow'),
'CW' => 'Co Carlow',
'CN' => 'Co Cavan',
'CE' => 'Co Clare',
'CO' => 'Co Cork',
'DL' => 'Co Donegal',
'D' => 'Co Dublin',
'D1' => 'Dublin 1',
'D2' => 'Dublin 2',
'D3' => 'Dublin 3',
'D4' => 'Dublin 4',
'D5' => 'Dublin 5',
'D6' => 'Dublin 6',
'D6W' => 'Dublin 6w',
'D7' => 'Dublin 7',
'D8' => 'Dublin 8',
'D9' => 'Dublin 9',
'D10' => 'Dublin 10',
'D11' => 'Dublin 11',
'D12' => 'Dublin 12',
'D13' => 'Dublin 13',
'D14' => 'Dublin 14',
'D15' => 'Dublin 15',
'D16' => 'Dublin 16',
'D17' => 'Dublin 17',
'D18' => 'Dublin 18',
'D19' => 'Dublin 19',
'D20' => 'Dublin 20',
'D21' => 'Dublin 21',
'D22' => 'Dublin 22',
'D23' => 'Dublin 23',
'D24' => 'Dublin 24',
'G' => 'Co Galway',
'KY' => 'Co Kerry',
'KE' => 'Co Kildare',
'KK' => 'Co Kilkenny',
'LS' => 'Co Laois',
'LM' => 'Co Leitrim',
'LK' => 'Co Limerick',
'LD' => 'Co Longford',
'LH' => 'Co Louth',
'MO' => 'Co Mayo',
'MH' => 'Co Meath',
'MN' => 'Co Monaghan',
'OY' => 'Co Offaly',
'RN' => 'Co Roscommon',
'SO' => 'Co Sligo',
'TA' => 'Co Tipperary',
'WD' => 'Co Waterford',
'WH' => 'Co Westmeath',
'WX' => 'Co Wexford',
'WW' => 'Co Wicklow',
);
$administrative_areas['IN'] = array(
'AP' => t('Andhra Pradesh'),
@@ -388,8 +411,6 @@ function addressfield_get_administrative_areas($country_code) {
'AS' => t('Assam'),
'BR' => t('Bihar'),
'CT' => t('Chhattisgarh'),
'DD' => t('Daman & Diu'),
'DN' => t('Dadra & Nagar Haveli'),
'GA' => t('Goa'),
'GJ' => t('Gujarat'),
'HP' => t('Himachal Pradesh'),
@@ -424,116 +445,116 @@ function addressfield_get_administrative_areas($country_code) {
'PY' => t('Puducherry'),
);
$administrative_areas['IT'] = array(
'AG' => t('Agrigento'),
'AL' => t('Alessandria'),
'AN' => t('Ancona'),
'AO' => t('Aosta'),
'AP' => t('Ascoli Piceno'),
'AQ' => t("L'Aquila"),
'AR' => t('Arezzo'),
'AT' => t('Asti'),
'AV' => t('Avellino'),
'BA' => t('Bari'),
'BG' => t('Bergamo'),
'BI' => t('Biella'),
'BL' => t('Belluno'),
'BN' => t('Benevento'),
'BO' => t('Bologna'),
'BR' => t('Brindisi'),
'BS' => t('Brescia'),
'BT' => t('Barletta-Andria-Trani'),
'BZ' => t('Bolzano/Bozen'),
'CA' => t('Cagliari'),
'CB' => t('Campobasso'),
'CE' => t('Caserta'),
'CH' => t('Chieti'),
'CI' => t('Carbonia-Iglesias'),
'CL' => t('Caltanissetta'),
'CN' => t('Cuneo'),
'CO' => t('Como'),
'CR' => t('Cremona'),
'CS' => t('Cosenza'),
'CT' => t('Catania'),
'CZ' => t('Catanzaro'),
'EN' => t('Enna'),
'FC' => t('Forlì-Cesena'),
'FE' => t('Ferrara'),
'FG' => t('Foggia'),
'FI' => t('Firenze'),
'FM' => t('Fermo'),
'FR' => t('Frosinone'),
'GE' => t('Genova'),
'GO' => t('Gorizia'),
'GR' => t('Grosseto'),
'IM' => t('Imperia'),
'IS' => t('Isernia'),
'KR' => t('Crotone'),
'LC' => t('Lecco'),
'LE' => t('Lecce'),
'LI' => t('Livorno'),
'LO' => t('Lodi'),
'LT' => t('Latina'),
'LU' => t('Lucca'),
'MB' => t('Monza e Brianza'),
'MC' => t('Macerata'),
'ME' => t('Messina'),
'MI' => t('Milano'),
'MN' => t('Mantova'),
'MO' => t('Modena'),
'MS' => t('Massa-Carrara'),
'MT' => t('Matera'),
'NA' => t('Napoli'),
'NO' => t('Novara'),
'NU' => t('Nuoro'),
'OG' => t('Ogliastra'),
'OR' => t('Oristano'),
'OT' => t('Olbia-Tempio'),
'PA' => t('Palermo'),
'PC' => t('Piacenza'),
'PD' => t('Padova'),
'PE' => t('Pescara'),
'PG' => t('Perugia'),
'PI' => t('Pisa'),
'PN' => t('Pordenone'),
'PO' => t('Prato'),
'PR' => t('Parma'),
'PT' => t('Pistoia'),
'PU' => t('Pesaro e Urbino'),
'PV' => t('Pavia'),
'PZ' => t('Potenza'),
'RA' => t('Ravenna'),
'RC' => t('Reggio Calabria'),
'RE' => t('Reggio Emilia'),
'RG' => t('Ragusa'),
'RI' => t('Rieti'),
'RM' => t('Roma'),
'RN' => t('Rimini'),
'RO' => t('Rovigo'),
'SA' => t('Salerno'),
'SI' => t('Siena'),
'SO' => t('Sondrio'),
'SP' => t('La Spezia'),
'SR' => t('Siracusa'),
'SS' => t('Sassari'),
'SV' => t('Savona'),
'TA' => t('Taranto'),
'TE' => t('Teramo'),
'TN' => t('Trento'),
'TO' => t('Torino'),
'TP' => t('Trapani'),
'TR' => t('Terni'),
'TS' => t('Trieste'),
'TV' => t('Treviso'),
'UD' => t('Udine'),
'VA' => t('Varese'),
'VB' => t('Verbano-Cusio-Ossola'),
'VC' => t('Vercelli'),
'VE' => t('Venezia'),
'VI' => t('Vicenza'),
'VR' => t('Verona'),
'VS' => t('Medio Campidano'),
'VT' => t('Viterbo'),
'VV' => t('Vibo Valentia'),
'AG' => 'Agrigento',
'AL' => 'Alessandria',
'AN' => 'Ancona',
'AO' => 'Aosta',
'AP' => 'Ascoli Piceno',
'AQ' => "L'Aquila",
'AR' => 'Arezzo',
'AT' => 'Asti',
'AV' => 'Avellino',
'BA' => 'Bari',
'BG' => 'Bergamo',
'BI' => 'Biella',
'BL' => 'Belluno',
'BN' => 'Benevento',
'BO' => 'Bologna',
'BR' => 'Brindisi',
'BS' => 'Brescia',
'BT' => 'Barletta-Andria-Trani',
'BZ' => 'Bolzano/Bozen',
'CA' => 'Cagliari',
'CB' => 'Campobasso',
'CE' => 'Caserta',
'CH' => 'Chieti',
'CI' => 'Carbonia-Iglesias',
'CL' => 'Caltanissetta',
'CN' => 'Cuneo',
'CO' => 'Como',
'CR' => 'Cremona',
'CS' => 'Cosenza',
'CT' => 'Catania',
'CZ' => 'Catanzaro',
'EN' => 'Enna',
'FC' => 'Forlì-Cesena',
'FE' => 'Ferrara',
'FG' => 'Foggia',
'FI' => 'Firenze',
'FM' => 'Fermo',
'FR' => 'Frosinone',
'GE' => 'Genova',
'GO' => 'Gorizia',
'GR' => 'Grosseto',
'IM' => 'Imperia',
'IS' => 'Isernia',
'KR' => 'Crotone',
'LC' => 'Lecco',
'LE' => 'Lecce',
'LI' => 'Livorno',
'LO' => 'Lodi',
'LT' => 'Latina',
'LU' => 'Lucca',
'MB' => 'Monza e Brianza',
'MC' => 'Macerata',
'ME' => 'Messina',
'MI' => 'Milano',
'MN' => 'Mantova',
'MO' => 'Modena',
'MS' => 'Massa-Carrara',
'MT' => 'Matera',
'NA' => 'Napoli',
'NO' => 'Novara',
'NU' => 'Nuoro',
'OG' => 'Ogliastra',
'OR' => 'Oristano',
'OT' => 'Olbia-Tempio',
'PA' => 'Palermo',
'PC' => 'Piacenza',
'PD' => 'Padova',
'PE' => 'Pescara',
'PG' => 'Perugia',
'PI' => 'Pisa',
'PN' => 'Pordenone',
'PO' => 'Prato',
'PR' => 'Parma',
'PT' => 'Pistoia',
'PU' => 'Pesaro e Urbino',
'PV' => 'Pavia',
'PZ' => 'Potenza',
'RA' => 'Ravenna',
'RC' => 'Reggio Calabria',
'RE' => 'Reggio Emilia',
'RG' => 'Ragusa',
'RI' => 'Rieti',
'RM' => 'Roma',
'RN' => 'Rimini',
'RO' => 'Rovigo',
'SA' => 'Salerno',
'SI' => 'Siena',
'SO' => 'Sondrio',
'SP' => 'La Spezia',
'SR' => 'Siracusa',
'SS' => 'Sassari',
'SV' => 'Savona',
'TA' => 'Taranto',
'TE' => 'Teramo',
'TN' => 'Trento',
'TO' => 'Torino',
'TP' => 'Trapani',
'TR' => 'Terni',
'TS' => 'Trieste',
'TV' => 'Treviso',
'UD' => 'Udine',
'VA' => 'Varese',
'VB' => 'Verbano-Cusio-Ossola',
'VC' => 'Vercelli',
'VE' => 'Venezia',
'VI' => 'Vicenza',
'VR' => 'Verona',
'VS' => 'Medio Campidano',
'VT' => 'Viterbo',
'VV' => 'Vibo Valentia',
);
$administrative_areas['JM'] = array(
'13' => 'Clarendon',
@@ -638,38 +659,38 @@ function addressfield_get_administrative_areas($country_code) {
'ZHA' => t('Zhambyl region'),
);
$administrative_areas['MX'] = array(
'AGU' => t('Aguascalientes'),
'BCN' => t('Baja California'),
'BCS' => t('Baja California Sur'),
'CAM' => t('Campeche'),
'COA' => t('Coahuila'),
'COL' => t('Colima'),
'CHP' => t('Chiapas'),
'CHH' => t('Chihuahua'),
'DIF' => t('Distrito Federal'),
'DUG' => t('Durango'),
'MEX' => t('Estado de México'),
'GUA' => t('Guanajuato'),
'GRO' => t('Guerrero'),
'HID' => t('Hidalgo'),
'JAL' => t('Jalisco'),
'MIC' => t('Michoacán'),
'MOR' => t('Morelos'),
'NAY' => t('Nayarit'),
'NLE' => t('Nuevo León'),
'OAX' => t('Oaxaca'),
'PUE' => t('Puebla'),
'QUE' => t('Queretaro'),
'ROO' => t('Quintana Roo'),
'SLP' => t('San Luis Potosí'),
'SIN' => t('Sinaloa'),
'SON' => t('Sonora'),
'TAB' => t('Tabasco'),
'TAM' => t('Tamaulipas'),
'TLA' => t('Tlaxcala'),
'VER' => t('Veracruz'),
'YUC' => t('Yucatán'),
'ZAC' => t('Zacatecas'),
'AGU' => 'Aguascalientes',
'BCN' => 'Baja California',
'BCS' => 'Baja California Sur',
'CAM' => 'Campeche',
'COA' => 'Coahuila',
'COL' => 'Colima',
'CHP' => 'Chiapas',
'CHH' => 'Chihuahua',
'DIF' => 'Distrito Federal',
'DUG' => 'Durango',
'MEX' => 'Estado de México',
'GUA' => 'Guanajuato',
'GRO' => 'Guerrero',
'HID' => 'Hidalgo',
'JAL' => 'Jalisco',
'MIC' => 'Michoacán',
'MOR' => 'Morelos',
'NAY' => 'Nayarit',
'NLE' => 'Nuevo León',
'OAX' => 'Oaxaca',
'PUE' => 'Puebla',
'QUE' => 'Queretaro',
'ROO' => 'Quintana Roo',
'SLP' => 'San Luis Potosí',
'SIN' => 'Sinaloa',
'SON' => 'Sonora',
'TAB' => 'Tabasco',
'TAM' => 'Tamaulipas',
'TLA' => 'Tlaxcala',
'VER' => 'Veracruz',
'YUC' => 'Yucatán',
'ZAC' => 'Zacatecas',
);
$administrative_areas['MY'] = array(
'01' => t('Johor'),
@@ -690,31 +711,31 @@ function addressfield_get_administrative_areas($country_code) {
'11' => t('Terengganu'),
);
$administrative_areas['PE'] = array(
'AMA' => t('Amazonas'),
'ANC' => t('Ancash'),
'APU' => t('Apurimac'),
'ARE' => t('Arequipa'),
'AYA' => t('Ayacucho'),
'CAJ' => t('Cajamarca'),
'CAL' => t('Callao'),
'CUS' => t('Cusco'),
'HUV' => t('Huancavelica'),
'HUC' => t('Huanuco'),
'ICA' => t('Ica'),
'JUN' => t('Junin'),
'LAL' => t('La Libertad'),
'LAM' => t('Lambayeque'),
'LIM' => t('Lima'),
'LOR' => t('Loreto'),
'MDD' => t('Madre de Dios'),
'MOQ' => t('Moquegua'),
'PAS' => t('Pasco'),
'PIU' => t('Piura'),
'PUN' => t('Puno'),
'SAM' => t('San Martin'),
'TAC' => t('Tacna'),
'TUM' => t('Tumbes'),
'UCA' => t('Ucayali'),
'AMA' => 'Amazonas',
'ANC' => 'Ancash',
'APU' => 'Apurimac',
'ARE' => 'Arequipa',
'AYA' => 'Ayacucho',
'CAJ' => 'Cajamarca',
'CAL' => 'Callao',
'CUS' => 'Cusco',
'HUV' => 'Huancavelica',
'HUC' => 'Huanuco',
'ICA' => 'Ica',
'JUN' => 'Junin',
'LAL' => 'La Libertad',
'LAM' => 'Lambayeque',
'LIM' => 'Lima',
'LOR' => 'Loreto',
'MDD' => 'Madre de Dios',
'MOQ' => 'Moquegua',
'PAS' => 'Pasco',
'PIU' => 'Piura',
'PUN' => 'Puno',
'SAM' => 'San Martin',
'TAC' => 'Tacna',
'TUM' => 'Tumbes',
'UCA' => 'Ucayali',
);
$administrative_areas['RU'] = array(
'MOW' => t('Moskva'),
@@ -918,7 +939,6 @@ function addressfield_get_administrative_areas($country_code) {
'21' => t('Zakarpats\'ka oblast'),
'23' => t('Zaporiz\'ka oblast'),
'26' => t('Ivano-Frankivs\'ka oblast'),
'30' => t('Kyiv city'),
'30' => t('Kiev Oblast'),
'35' => t('Kirovohrads\'ka oblast'),
'09' => t('Luhans\'ka oblast'),
@@ -1003,35 +1023,32 @@ function addressfield_get_administrative_areas($country_code) {
'VI' => t('Virgin Islands'),
);
$administrative_areas['VE'] = array(
'Z' => t('Amazonas'),
'B' => t('Anzoátegui'),
'C' => t('Apure'),
'D' => t('Aragua'),
'E' => t('Barinas'),
'F' => t('Bolívar'),
'G' => t('Carabobo'),
'H' => t('Cojedes'),
'Y' => t('Delta Amacuro'),
'W' => t('Dependencias Federales'),
'A' => t('Distrito Federal'),
'I' => t('Falcón'),
'J' => t('Guárico'),
'K' => t('Lara'),
'L' => t('Mérida'),
'M' => t('Miranda'),
'N' => t('Monagas'),
'O' => t('Nueva Esparta'),
'P' => t('Portuguesa'),
'R' => t('Sucre'),
'S' => t('Táchira'),
'T' => t('Trujillo'),
'X' => t('Vargas'),
'U' => t('Yaracuy'),
'V' => t('Zulia'),
'Z' => 'Amazonas',
'B' => 'Anzoátegui',
'C' => 'Apure',
'D' => 'Aragua',
'E' => 'Barinas',
'F' => 'Bolívar',
'G' => 'Carabobo',
'H' => 'Cojedes',
'Y' => 'Delta Amacuro',
'W' => 'Dependencias Federales',
'A' => 'Distrito Federal',
'I' => 'Falcón',
'J' => 'Guárico',
'K' => 'Lara',
'L' => 'Mérida',
'M' => 'Miranda',
'N' => 'Monagas',
'O' => 'Nueva Esparta',
'P' => 'Portuguesa',
'R' => 'Sucre',
'S' => 'Táchira',
'T' => 'Trujillo',
'X' => 'Vargas',
'U' => 'Yaracuy',
'V' => 'Zulia',
);
// Allow other modules to alter the administrative areas.
drupal_alter('addressfield_administrative_areas', $administrative_areas);
return isset($administrative_areas[$country_code]) ? $administrative_areas[$country_code] : null;
return $administrative_areas;
}
@@ -43,7 +43,6 @@ function _addressfield_sample_addresses() {
$fields = array();
if ($handle = @fopen("$filepath/addresses.txt",'r')) {
if (is_resource($handle)) {
$addresses = array();
while (($buffer = fgets($handle)) !== false) {
list($country, $administrative_area, $sub_administrative_area, $locality, $dependent_locality, $postal_code, $thoroughfare, $premise, $sub_premise) = explode("\t", $buffer);
$fields[] = array(
@@ -6,10 +6,10 @@
*/
/**
* Implements hook_feeds_node_processor_targets_alter().
* Implements hook_feeds_processor_targets_alter().
*/
function addressfield_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_name) {
foreach (field_info_instances($entity_type, $bundle_name) as $name => $instance) {
function addressfield_feeds_processor_targets_alter(&$targets, $entity_type, $bundle) {
foreach (field_info_instances($entity_type, $bundle) as $name => $instance) {
$info = field_info_field($name);
if ($info['type'] == 'addressfield') {
foreach ($info['columns'] as $sub_field => $schema_info) {
@@ -34,7 +34,7 @@ function addressfield_feeds_processor_targets_alter(&$targets, $entity_type, $bu
* An entity object, for instance a node object.
* @param $target
* A string identifying the target on the node.
* @param $value
* @param $values
* The value to populate the target with.
*/
function addressfield_set_target($source, $entity, $target, $values) {
@@ -6,12 +6,13 @@ package = Fields
dependencies[] = ctools
files[] = addressfield.migrate.inc
files[] = views/addressfield_views_handler_field_administrative_area.inc
files[] = views/addressfield_views_handler_field_country.inc
files[] = views/addressfield_views_handler_filter_country.inc
; Information added by Drupal.org packaging script on 2015-04-23
version = "7.x-1.1"
; Information added by Drupal.org packaging script on 2015-10-07
version = "7.x-1.2"
core = "7.x"
project = "addressfield"
datestamp = "1429819382"
datestamp = "1444254070"
@@ -152,7 +152,7 @@ class MigrateAddressFieldHandler extends MigrateFieldHandler {
if ($value) {
if (isset($field_info['columns'][$column_key])) {
// Store the data in a seperate column.
// Store the data in a separate column.
$result[$column_key] = $value;
}
else {
@@ -534,7 +534,9 @@ function addressfield_field_widget_form(&$form, &$form_state, $field, $instance,
// $form_state['values'] is empty because of #limit_validation_errors, so
// $form_state['input'] needs to be used instead.
$parents = array_merge($element['#field_parents'], array($element['#field_name'], $langcode, $delta));
$input_address = drupal_array_get_nested_value($form_state['input'], $parents);
if (!empty($form_state['input'])) {
$input_address = drupal_array_get_nested_value($form_state['input'], $parents);
}
if (!empty($input_address)) {
$address = $input_address;
}
@@ -728,8 +730,6 @@ function addressfield_field_formatter_settings_summary($field, $instance, $view_
$display = $instance['display'][$view_mode];
$settings = $display['settings'];
$summary = '';
if ($settings['use_widget_handlers']) {
return t('Use widget configuration');
}
@@ -7,9 +7,9 @@ hidden = TRUE
dependencies[] = ctools
dependencies[] = addressfield
; Information added by Drupal.org packaging script on 2015-04-23
version = "7.x-1.1"
; Information added by Drupal.org packaging script on 2015-10-07
version = "7.x-1.2"
core = "7.x"
project = "addressfield"
datestamp = "1429819382"
datestamp = "1444254070"
@@ -3552,8 +3552,6 @@ function addressfield_form_ch_postal_code_validation($element, &$form_state, &$f
if (!empty($element['#value']) && (isset($data[$element['#value']]))) {
// Get the base #parents for this address form.
$base_parents = array_slice($element['#parents'], 0, -1);
$base_array_parents = array_slice($element['#array_parents'], 0, -2);
$city = $data[$element['#value']];
// Set the new values in the form.
@@ -7,7 +7,7 @@
*/
$plugin = array(
'title' => t('Make all fields optional (Not recommended)'),
'title' => t('Make all fields optional (No validation - unsuitable for postal purposes)'),
'format callback' => 'addressfield_format_address_optional',
'type' => 'address',
'weight' => 100,
@@ -83,7 +83,7 @@ function addressfield_format_address_generate(&$format, $address, $context = arr
'#tag' => 'div',
'#attributes' => array(
'class' => array('dependent-locality'),
'autocomplete' => '"address-level3',
'autocomplete' => 'address-level3',
),
// Most formats place this field in its own row.
'#suffix' => $clearfix,
@@ -97,7 +97,7 @@ function addressfield_format_address_generate(&$format, $address, $context = arr
'#prefix' => ' ',
'#attributes' => array(
'class' => array('locality'),
'autocomplete' => '"address-level2',
'autocomplete' => 'address-level2',
),
);
$format['locality_block']['administrative_area'] = array(
@@ -20,7 +20,7 @@ $plugin = array(
function addressfield_format_organisation_generate(&$format, $address) {
$format['organisation_block'] = array(
'#type' => 'addressfield_container',
'#attributes' => array('class' => array('addressfield-container-inline', 'name-block')),
'#attributes' => array('class' => array('addressfield-container-inline', 'organisation-block')),
'#weight' => -50,
// The addressfield is considered empty without a country, hide all fields
// until one is selected.
@@ -26,7 +26,7 @@ function addressfield_field_views_data($field) {
// Only expose these components as Views field handlers.
$implemented = array(
'country' => 'addressfield_views_handler_field_country',
'administrative_area' => 'views_handler_field',
'administrative_area' => 'addressfield_views_handler_field_administrative_area',
'sub_administrative_area' => 'views_handler_field',
'dependent_locality' => 'views_handler_field',
'locality' => 'views_handler_field',
@@ -0,0 +1,48 @@
<?php
/**
* Defines a field handler that can display the administrative area name
* instead of the code.
*/
class addressfield_views_handler_field_administrative_area extends views_handler_field {
function query() {
parent::query();
$this->country_alias = $this->query->add_field($this->table_alias, $this->definition['field_name'] . '_country');
}
function option_definition() {
$options = parent::option_definition();
$options['display_name'] = array('default' => TRUE);
return $options;
}
function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
$form['display_name'] = array(
'#type' => 'checkbox',
'#title' => t('Display the name of administrative area instead of the code.'),
'#default_value' => $this->options['display_name'],
);
}
function get_value($values, $field = NULL) {
$value = parent::get_value($values, $field);
// If we have a value for the field, look for the administrative area name in the
// Address Field options list array if specified.
if (!empty($value) && !empty($this->options['display_name'])) {
module_load_include('inc', 'addressfield', 'addressfield.administrative_areas');
$country = $values->{$this->country_alias};
$areas = addressfield_get_administrative_areas($country);
if (!empty($areas[$value])) {
$value = $areas[$value];
}
}
return $value;
}
}

Some files were not shown because too many files have changed in this diff Show More