diff --git a/sites/all/modules/themekey/.gitignore b/sites/all/modules/themekey/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..cf36c9f623f6d137b5f97096cebde316346e0f64 --- /dev/null +++ b/sites/all/modules/themekey/.gitignore @@ -0,0 +1,3 @@ +.project +.buildpath +.settings diff --git a/sites/all/modules/themekey/CHANGELOG.txt b/sites/all/modules/themekey/CHANGELOG.txt new file mode 100644 index 0000000000000000000000000000000000000000..0474ec2a69206444ead1aca8b54ff074896886f8 --- /dev/null +++ b/sites/all/modules/themekey/CHANGELOG.txt @@ -0,0 +1,432 @@ +ThemeKey 7.x-1.4, 2011-08-17 +---------------------------- +[ ] mkalkbrenner: added new properties sytem:month and system_day_of_month +[ ] mkalkbrenner: hide debug information about current theme if themekey_debug_trace_rule_switching not set + + +ThemeKey 7.x-1.3, 2011-08-12 +---------------------------- +[#1246604] mkalkbrenner: Use of undefined constants when running update.php +[ ] mkalkbrenner: added new permission to show debug messages to roles and support for administration role + + +ThemeKey 7.x-1.2, 2011-08-11 +---------------------------- +[#1215656] mkalkbrenner: Avoid errors if a third party module gets deactivated +[ ] mkalkbrenner: disable theme switching during drupal installation or update +[ ] mkalkbrenner: Removed obsolete option 'Force custom theme overriding' + which has never been finally ported from ThemeKey 6 to 7. + Improved feature is provided bey themekey_compat module now. +[#1236886] mkalkbrenner: show current theme using ThemeKey Debug + + +ThemeKey 7.x-1.1, 2011-06-27 +---------------------------- +[ ] mkalkbrenner: fixed debug messages in themekey_compat +[ ] mkalkbrenner: fixed javascript tooltip exchange on property change +[ ] mkalkbrenner: Call to undefined function themekey_update_static_rule() +[#1199750] mkalkbrenner: rename css files + + +ThemeKey 7.x-1.0, 2011-05-23 +---------------------------- +[ ] mkalkbrenner: fixed some debug informations +[ ] mkalkbrenner: fixed php notice when saving rule chain with disabled elements +[ ] mkalkbrenner: enabled theme switching on block configuration and disabled it on block demos +[ ] mkalkbrenner: introduced additional arguments for mapping callback functions +[ ] mkalkbrenner: introduced experimental module ThemeKey Compatibility +[ ] mkalkbrenner: integrated Print module's paths to detect node ids +[#1142402] mkalkbrenner: new property for POST variables: system:post +[#1165324] TommyChris: use constant for watchdog error level +[#1159326] mkalkbrenner: drupal:path does not respect operator +[ ] mkalkbrenner: use full module names in ThemeKey Compatibility admin section + + +ThemeKey 7.x-1.0-rc2, 2011-04-15 +-------------------------------- +[#1062966] mkalkbrenner: PHP 5.3 strict warning in themekey_help() +[ ] mkalkbrenner: fixed fatal error in ThemekeyMultipleNodePropertiesTestCase +[ ] mkalkbrenner: moved ThemeKey Example module to Development package +[#1065878] mkalkbrenner: Error messages when activating all Themekey modules on a Standard English Drupal-7.0 installation +[#1125920] mkalkbrenner: don't set $custom_theme to 'default' +[#1096356] mkalkbrenner: taxonomy:tid and taxonomy:tid_and_childs change themes for term nodes but not for nodes referencing terms +[#1074066] mkalkbrenner: Theme Key works for a nodes comments/reply, but not for comments/n/edit or comments/n/delete + + +ThemeKey 7.x-1.0-rc1, 2011-02-11 +-------------------------------- +[ ] mkalkbrenner: fixed admin theme and node admin theme in overlay again in a better way +[#1046214] mkalkbrenner: fixed fatal error during installation + + +ThemeKey 7.x-1.0-beta3, 2011-02-04 +---------------------------------- +[ ] mkalkbrenner: fixed admin theme and node admin theme in overlay +[ ] mkalkbrenner: removed information about lazy session intitialization in Drupal 6 +[ ] mkalkbrenner: removed hook_themekey_load_validators and introduced 'file' and 'path' as arguments in hook_themekey_properties() instead +[ ] mkalkbrenner: added module "ThemeKey Example" for developers +[ ] mkalkbrenner: documented ThemeKey API aka hooks + + +ThemeKey 7.x-1.0-beta2, 2011-02-01 +---------------------------------- +[ ] mkalkbrenner: fixed a warning and cleaned up some code +[ ] mkalkbrenner: ThemeKey UI: Support multiple theme select form elements in one form +[#1047316] mkalkbrenner: Selectable themes in theme form element provided by ThemeKey UI should be configurable +[ ] mkalkbrenner: fixed fatal error when disabling module ThemeKey UI +[ ] mkalkbrenner: fixed coding style +[#1046214] mkalkbrenner: Selection of theme for user - Replacement for D6 Core feature that has been removed in D7 Core + + +ThemeKey 7.x-1.0-beta1, 2011-01-31 +---------------------------------- +[ ] mkalkbrenner: fixed debug message constraints on settings page +[#958174] LoMo: Edited English strings +[#958824] LoMo: Extend themekey_browser_detection to include recognition of Windows 7 as useragent. +[#997330] technicolorenvy: fixed formatting of error messages +[#997330] technicolorenvy: fixed undefined function in themekey.install +[#1022166] mkalkbrenner: Call to undefined function themekey_invoke_modules() during database update when updating from an old version +[#1030268] mkalkbrenner: Toggle "Property drupal:path is case sensitive" does not work; drupal:path is always case sensitive +[#1036138] mkalkbrenner: book_nodeapi() is not used on D7 : fatal error +[ ] mkalkbrenner: created basic test framework for further simple tests +[#731728] carstenmueller: added new property views:vid +[#1037902] mkalkbrenner: D7: Errors in help section after installation +[ ] carstenmueller: fixed wrong uri in themekey debug properties to set new rule +[#1045094] webflo, LoMo: more SimpleTests +[ ] carstenmueller, mkalkbrenner: set undefined language and LANGUAGE_NONE ('und') to 'neutral' +[ ] pixeltank: ported some DB statements to the new API + + +ThemeKey 7.x-1.0-alpha2, 2010-10-18 +----------------------------------- +[ ] mkalkbrenner: rewritten rule chain form validation +[ ] mkalkbrenner: moved dedicated debug code from themekey.module to themekey_debug.module +[ ] mkalkbrenner: extended the debug module to detect if something else set a custom theme if no ThemeKey rule matched + + +ThemeKey 7.x-1.0-alpha1, 2010-10-17 +----------------------------------- +[ ] mkalkbrenner: used new hook_custom_theme instead of hook_init and replaced global variable $custom_theme by drupal_static +[ ] mkalkbrenner: used coder module to convert ThemeKey to Drupal 7 +[ ] carstenmueller: upgraded themekey.install to Drupal 7 +[ ] carstenmueller: upgraded themekey_ui.install to Drupal 7 +[ ] carstenmueller: added files[] in themekey.info for upgreading to Drupal 7 +[ ] carstenmueller: improved themekey_ui_update_6200() +[ ] carstenmueller: fixed db_fetch_array() calls in updates in themekey.install +[ ] mkalkbrenner: adjusted database code turned condition into object in themekey_match_rule_childs and themekey_match_condition +[ ] carstenmueller: fixed db_fetch_array() calls in updates in themekey_ui_helper.ui +[ ] cspitzlay: moved examples and validators over from the themekey_properties module (which will be merged into themekey) +[ ] carstenmueller: fixed db_fetch_array() calls +[ ] mkalkbrenner: adjusted database code in themekey_load_rules and turned parameter into object in themekey_complete_path +[ ] carstenmueller: fixed db_fetch_object() calls +[ ] carstenmueller: replaced db_lock_table() by transactions (see http://drupal.org/node/355875) +[ ] cspitzlay: moved property definitions, mapping definitions and mapping implementations over from the themekey_properties module; renamed browser detection class to contain "themekey" +[ ] mkalkbrenner: fixed themekey_theme +[ ] mkalkbrenner: fixed css classes in rule chain form +[ ] mkalkbrenner: got themekey_debug to work +[ ] carstenmueller: ported themekey_ui.module to Drupal 7 +[ ] mkalkbrenner: themekey_debug prevented theme switching +[ ] mkalkbrenner: render forms in help section +[ ] mkalkbrenner: don't scan for new properties provided by other modules on form submission +[ ] cspitzlay: fixed signature of help form functions so default value for "collapsed" works +[ ] cspitzlay: renamed property functions moved over from themekey_properties module +[ ] mkalkbrenner: changed detection if theme switching fails due to another module +[ ] cspitzlay: fixed the help forms: HTML content is displayed now. +[ ] mkalkbrenner: fixed rule deletion and menu entries +[ ] mkalkbrenner: set bartik as default theme +[ ] mkalkbrenner: adjusted README +[ ] mkalkbrenner: addded Configure links to module page +[ ] carstenmueller: modified function themekey_taxonomy_vid2tid() to fit new taxonomy with field api +[ ] carstenmueller: modified function themekey_taxonomy_vid2vid() to fit new taxonomy with field api +[ ] EugenMayer, mkalkbrenner: ported themekey java script to D7 +[ ] mkalkbrenner: sanitized a lot of strings according to coder module +[ ] carstenmueller: modified function themekey_ui_pathalias() to fit Drupal 7 + + +ThemeKey 6.x-3.0, 2010-10-14 +---------------------------- +[ ] mkalkbrenner: Show tooltips about possible property values at rule chain edit form +[#908640] mkalkbrenner: explain operators +[ ] mkalkbrenner: avoid duplicate notes after saving theme switching rule chain +[ ] mkalkbrenner: added warnings about properties and page cache + + +ThemeKey 6.x-3.0-rc2, 2010-09-02 +-------------------------------- +[ ] mkalkbrenner: fixed Undefined variable: _SESSION in themekey_init() when using Pressflow or Cocomore Drupal Core +[ ] mkalkbrenner: fixed links in descriptions and help texts +[ ] mkalkbrenner: fixed typing error + + +ThemeKey 6.x-3.0-rc1, 2010-08-20 +-------------------------------- +[#884862] flamingvan, mkalkbrenner: taxonomy properties and node revisions +[ ] mkalkbrenner: improved comments +[ ] mkalkbrenner: don't execute rule chain when cron has been started by drush + + +ThemeKey 6.x-3.0-beta1, 2010-08-16 +---------------------------------- +[ ] mkalkbrenner: fixed warning: Undefined variable: object +[ ] mkalkbrenner: fixed warnings in rule chain form +[ ] mkalkbrenner: changed code according to drupal coding standards +[#847204] mkalkbrenner: ThemeKey UI: Unable to change themes for nodes already created + + +ThemeKey 6.x-3.0-alpha3, 2010-07-30 +----------------------------------- +[ ] mkalkbrenner: added icons to Theme Switching Rule Chain that show the page cache compatibility of this rule +[ ] mkalkbrenner: check names of new properties added via hook_themekey_properties() +[ ] mkalkbrenner: added explainations of page cache compatibility +[#863252] mkalkbrenner: Administration theme overrides the default theme if Rule Chain is empty + + +ThemeKey 6.x-3.0-alpha2, 2010-07-27 +----------------------------------- +[ ] mkalkbrenner: ThemeKey Debug: fixed fatal error: Call to undefined function themekey_rule_get() +[ ] mkalkbrenner: fixed critical error: property setting from path did not work correctly + + +ThemeKey 6.x-3.0-alpha1, 2010-07-22 +----------------------------------- +[ ] mkalkbrenner: improved some descriptions +[#445538] Sansui, mkalkbrenner: added new tutorial to help section: "Allowing users to select a theme for all content they create" +[#812114] mkalkbrenner: fast deletion of page cache after modifications to Theme Switching Rule Chain +[ ] mkalkbrenner: removed table themekey_paths and introduced the preprocessed variable themekey_paths instead which should be faster +[ ] mkalkbrenner: removed some obsolete internal functions +[#812114] mkalkbrenner: fast deletion of page cache after modifications to user profile theme setting +[#812114] mkalkbrenner: wildcard deletion of page cache after modifications to path alias theme setting +[#812114] mkalkbrenner: introduced classification of page cache support for themekey properties +[ ] mkalkbrenner: allow '0' as value when creating a new rule +[#812114] mkalkbrenner: introduced themekey cron to clean up page cache depending on rules containing time based properties +[ ] mkalkbrenner: aded some documentation + + +ThemeKey 6.x-2.2, 2010-07-19 +---------------------------- +[#445538] mkalkbrenner: allow the author himself to select a theme for all his nodes +[#754978] mkalkbrenner: Completed Debug for non root users/visitors +[#855026] mkalkbrenner: Add "administration theme", additionally to "System default" in theme select box +[ ] mkalkbrenner: fixed debug message on block configuration page +[#567222] JMTorres, mkalkbrenner: ajax views do not work with the themekey module + + +ThemeKey 6.x-2.1, 2010-06-15 +---------------------------- +[#808858] mkalkbrenner: Scanning modules may not work +[#807624] mkalkbrenner: Use of "t()" function can produce wrong theme when using theme_debug +[#825868] mkalkbrenner: "Retain the theme until a new theme is set for anonymous users" option is not working +[#754978] mkalkbrenner: Debug for non root users/visitors + + +ThemeKey 6.x-2.0, 2010-05-14 +---------------------------- +[#797332] mkalkbrenner: undefined function - same in themekey_ui_admin.inc + + +ThemeKey 6.x-2.0-rc5, 2010-05-13 +-------------------------------- +[#796306] mkalkbrenner: fixed warning +[#797318] mkalkbrenner: Can't configure Themekey - configuration page is blank +[#797332] mkalkbrenner: undefined function + + +ThemeKey 6.x-2.0-rc4, 2010-05-10 +-------------------------------- +[#707208] kfritsche: Out of Memory - removed obsolete constant PATH_MAX_PARTS +[#777312] AlexisWilke, mkalkbrenner: Theme silently not changed when there is a conflict... +[ ] mkalkbrenner: lazy loading of themekey_base.inc +[#778556] AlexisWilke, mkalkbrenner: Fixed E_NOTICE about $wildcard +[#756556] mkalkbrenner: Non matching child rules might terminate rule chain evaluation + + +ThemeKey 6.x-2.0-rc3, 2010-04-20 +-------------------------------- +[#771686] AlexisWilke: Errors with E_NOTICE turned on +[#707208] mkalkbrenner: Out of Memory - more effective algorithm to check property drupal:path +[ ] mkalkbrenner: documented property drupal:path + + +ThemeKey 6.x-2.0-rc2, 2010-03-26 +-------------------------------- +[#745932] mkalkbrenner: property node:type should be set at node/add/XYZ +[ ] mkalkbrenner: introduced new property drupal:get_q to solve #745932 +[#707208] mkalkbrenner: Out of Memory - turn off ThemeKey when cron runs +[#754210] mkalkbrenner: Fatal error when turning off module themekey_debug with debug options enabled + + +ThemeKey 6.x-2.0-rc1, 2010-03-16 +-------------------------------- +[#682626] mkalkbrenner: added setting to treat paths case sensitive or case insensitive +[ ] mkalkbrenner: added possible value 'neutral' to property node:language +[#684868] mkalkbrenner: Imagecache breaks, avoid node_load() in hook_init() +[#740412] AlexisWilke: DELETE variables must use %% instead of % +[#741268] mkalkbrenner: added switch for custom theme overriding +[#730254] mkalkbrenner: made theme_themekey_ui_theme_select_form more robust +[ ] mkalkbrenner: fixed default value of checkbox "Retain the theme until a new theme is set for anonymous users" +[ ] mkalkbrenner: fixed layout of theme selection form element of ThemeKey UI +[#701642] will_in_wi, emueller, mkalkbrenner: Editing a node removes theme defined +[#685108] butler360, mkalkbrenner: ThemeKey won't work correctly until you click 'Save configuration' at ThemeKey settings + + +ThemeKey 6.x-2.0-beta7, 2009-01-12 +---------------------------------- +[ ] mkalkbrenner: improved ThemeKey's help +[ ] mkalkbrenner: added a tutorial about Rule Chaining +[ ] mkalkbrenner: integrated ThemeKey Properties Debug into ThemeKey +[#681358] mkalkbrenner: debug messages might stop ThemeKey to switch themes +[ ] mkalkbrenner: added rule matching debugging + + +ThemeKey 6.x-2.0-beta6, 2009-01-07 +---------------------------------- +[ ] mkalkbrenner: improved validator for drupal:path +[ ] mkalkbrenner: hide delete link if active rule has child +[ ] mkalkbrenner: repeat ThemeKey rule on delete confirm page +[ ] mkalkbrenner: don't offer delete link for static rules +[#676322] mkalkbrenner: wildcard handling for path aliases is broken + + +ThemeKey 6.x-2.0-beta5, 2009-01-06 +---------------------------------- +[ ] mkalkbrenner: Introduced new right 'assign path alias themes' for ThemeKey UI +[#669376] mkalkbrenner: ThemeKey UI is incompatible to Organic Groups (Illegal choice when I select a theme using themekey) +[ ] mkalkbrenner: ThemeKey UI uses beautified theme selector +[#675952] mkalkbrenner: "Tables not locked" error + + +ThemeKey 6.x-2.0-beta4, 2009-01-04 +----------------------------------- +[#665710] mkalkbrenner: wrong strategy for checking multi valued properties for some operators +[ ] mkalkbrenner: added themekey_validator_time() +[ ] mkalkbrenner: improved validators and documented validators in source code +[ ] mkalkbrenner: applied "coder" and fixed warnings +[ ] mkalkbrenner: commented more source code +[ ] mkalkbrenner: cleaned up code and API +[#669994] mkalkbrenner: wrong detection of "identical theme switching rules in the chain" +[ ] mkalkbrenner: themekey_ui_get_path_theme() should only return themes assigened to rules without children +[ ] mkalkbrenner: lock table to prevent race condition in function themekey_properties_del() +[#670086] mkalkbrenner: ThemeKey UI: Unable to set different themes for different path aliases +[#301904] Carsten Müller: Meaning of propertys: added help texts for properties +[ ] mkalkbrenner: always add new rules at the end of the chain +[ ] mkalkbrenner: introduced hook_themekey_load_validators() +[ ] mkalkbrenner: added help section + + +ThemeKey 6.x-2.0-beta3, 2009-12-18 +----------------------------------- +[ ] Carsten Müller, mkalkbrenner: added validation function themekey_validator_string_boolean() and improved themekey_validator_nummeric_boolean() +[#662786] mkalkbrenner: Error Message after upgrading to latest beta + + +ThemeKey 6.x-2.0-beta2, 2009-12-17 +----------------------------------- +[ ] Carsten Müller: improved validator themekey_validator_nummeric_boolean() +[ ] mkalkbrenner: moved modules into package ThemeKey +[ ] mkalkbrenner: fixed theme for path detection in ThemeKey UI in combination with different operators +[ ] mkalkbrenner: improved help section a little +[ ] mkalkbrenner: API: introduced static properties as generic concept for ThemeKey UI and Taxonomy Theme + + +ThemeKey 6.x-2.0-beta1, 2009-12-15 +---------------------------------- +[ ] mkalkbrenner: API: replaced concept of conditions by chaining simple property based rules +[ ] mkalkbrenner: API: don't allow altering of themekey_attributes, themekey_properties and themekey_maps via hook +[ ] mkalkbrenner: API: introduced validators for rules depending on selected property +[ ] mkalkbrenner: new properties node:created_date_time, node:created_date, node:changed_date_time, node:changed_date +[ ] mkalkbrenner: optimized database performance +[ ] mkalkbrenner: improved user interface +[ ] mkalkbrenner: adjusted more default values +[ ] mkalkbrenner: allow <= and >= as operator for conditions +[ ] mkalkbrenner: removed pager from "Theme Switching Rule Chain" +[ ] mkalkbrenner: refactored upgrade path from ThemeKey 6.x-1.1 to 6.x-2.0beta1 +[ ] mkalkbrenner: refactored upgrade path from ThemeKey 6.x-1.2beta7 to 6.x-2.0beta1 +[#442192] mkalkbrenner: Main condition not = +[#654368] metaphysis, mkalkbrenner: Update from 6.x-1.2-beta1 to beta7 fails with postgresql + + +ThemeKey 6.x-2.0-alpha3, 2009-11-25 +----------------------------------- +[ ] mkalkbrenner: fixed error during fresh installation: Key column 'custom' doesn't exist in table +[ ] mkalkbrenner: simplified installation process and changed default values + + +ThemeKey 6.x-2.0-alpha2, 2009-11-25 +----------------------------------- +[ ] mkalkbrenner: API: hook_themekey_properties(): removed attribute 'path' +[#629676#comment-2304878] mkalkbrenner: API: object based node property mapping is completely removed and replaced by "normal" map functions +[ ] mkalkbrenner: fixed warning when using drupal:path in combination with wildcards on a non aliased path +[#619940#comment-2303706] mkalkbrenner: no other node properties than node:nid are working +[#619940#comment-2303706] mkalkbrenner: algorithm to build path ancestors returned incomplete result + + +ThemeKey 6.x-2.0-alpha1, 2009-11-24 +----------------------------------- +[ ] mkalkbrenner: API: hook_themekey_properties(): removed attributes 'multiple' and 'weight' +[ ] mkalkbrenner: API: removed themekey custom path concept. Themekey custom paths became properties using drupal:path +[ ] mkalkbrenner: API: removed requirement to turn properties on or off +[#625176] mkalkbrenner: Let admin adjust path and property weight +[#591200] mkalkbrenner: Performance: Some property callbacks get called multiple times +[#639808] mkalkbrenner: Conditions always return TRUE if property is NULL +[#582738] mkalkbrenner: Themekey 'Retain the theme until a new theme is set' setting not working with cached pages for anonymous users + + +ThemeKey 6.x-1.2-beta7, 2009-11-24 +---------------------------------- +[#634028] mkalkbrenner: Callbacks for properties seems to be senseless +[#638426] mkalkbrenner: Two queries failed when upgrading from beta4 to beta6 + + +ThemeKey 6.x-1.2-beta6, 2009-11-13 +---------------------------------- +[#631946] donquixote, mkalkbrenner: Incorrect table definition (themekey_ui_). +[#631900] mkalkbrenner: Warning when entering first path at /admin/settings/themekey + + +ThemeKey 6.x-1.2-beta5, 2009-11-12 +---------------------------------- +[#319581] mkalkbrenner: GET parameters break theme switching based on url aliases +[#627974] mkalkbrenner: Rewrite ThemeKey UI +[#629672] mkalkbrenner: taxonomy:tid_and_childs still not working in all cases => rewrite of property value detection required +[#629676] mkalkbrenner: turn object property mapping into node property mapping +[#626254] mkalkbrenner: problem after changes in the general tab => "Discover all node properties for selection" is incompatible to some modules + + +ThemeKey 6.x-1.2-beta4, 2009-11-05 +---------------------------------- +[ ] mkalkbrenner: subfolder modules was missing in 6.x-1.2-beta3 + + +ThemeKey 6.x-1.2-beta3, 2009-11-05 +---------------------------------- +[#619940] mkalkbrenner: Allow themekey properties as conditions for themekey paths +[#591200] mkalkbrenner: Performance: Some property callbacks get called multiple times +[#624374] mkalkbrenner: Validate property conditions before saving +[#624124] mkalkbrenner: Impossible to set two identical paths with different conditions + + +ThemeKey 6.x-1.2-beta2, 2009-10-30 +---------------------------------- +[#616946] mkalkbrenner: Temporally deactivated Taxonomy Menu custom path support because code is outdated since a long time +[#617990] FVANtom, mkalkbrenner: Themekey disables themes after configuration +[#442188] kratib, mkalkbrenner: Supporting regex +[#615720] mkalkbrenner: Condition operators don't work on properties based on array values +[#607394] dankh, mkalkbrenner: Property "taxonomy:tid_and_childs" incompatible with module "Taxonomy Menu" + + +ThemeKey 6.x-1.2-beta1, 2009-10-01 +---------------------------------- +[#482766] mkalkbrenner: Themekey overriding administration theme on node edit +[#558044] mkalkbrenner: ThemeKey does not respect theme enabled/disabled +[#587872] mkalkbrenner: removed menu properties +[#587868] mkalkbrenner: finish book properties + + +ThemeKey 6.x-1.2-alpha1, 2009-09-25 +----------------------------------- +[#584982] sinasalek, mkalkbrenner: Does not respect hierarchy of terms - added new property taxonomy:tid_and_childs +[#434242] mkalkbrenner, cspitzlay: Non-array properties do not work correctly +[#570642] mkalkbrenner: ThemeKey breaks core block configuration +[ ] mkalkbrenner: mention ThemeKey Properties at README.txt +[ ] mkalkbrenner: added CHANGELOG.txt + diff --git a/sites/all/modules/themekey/LICENSE.txt b/sites/all/modules/themekey/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..2c095c8d3f42488e8168f9710a4ffbfc4125a159 --- /dev/null +++ b/sites/all/modules/themekey/LICENSE.txt @@ -0,0 +1,274 @@ +GNU GENERAL PUBLIC LICENSE + + Version 2, June 1991 + +Copyright (C) 1989, 1991 Free Software Foundation, Inc. 675 Mass Ave, +Cambridge, MA 02139, 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 Library 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 diff --git a/sites/all/modules/themekey/README.txt b/sites/all/modules/themekey/README.txt new file mode 100644 index 0000000000000000000000000000000000000000..e67d0cee9316a89928f2a81830fc8edc7e6c9a1f --- /dev/null +++ b/sites/all/modules/themekey/README.txt @@ -0,0 +1,96 @@ + +ThemeKey +======== + +Name: themekey +Authors: Markus Kalkbrenner | Cocomore AG + Carsten Müller | Cocomore AG + Christian Spitzlay | Cocomore AG + Thilo Wawrzik <drupal at profix898 dot de> +Drupal: 7.x +Sponsor: Cocomore AG - http://www.cocomore.com + - http://drupal.cocomore.com + +Description +=========== + +ThemeKey is meant to be a generic theme switching module. It +allows you to switch the theme for different paths and based +on object properties (e.g. node field values). It can also be +easily extended to support additional paths or properties as +exposed by other modules. + +Documentation for users and developers is very sparse at the +moment. I hope to complete the docs in the next few weeks. +Thanks for your patience :) + + +Installation +============ + +1. Place whole themekey folder into your Drupal modules/ or better + sites/x/modules/ directory. + +2. Enable the ThemeKey module by navigating to + Configuration > Modules + +3. Bring up themekey configuration screens by navigating to + Configuration > User Interface / ThemeKey + + +ThemeKey UI +=========== + +1. Enable the ThemeKey UI module by navigating to + Configuration > Modules + +2. Bring up ThemeKey configuration screens by navigating to + Configuration > User Interface / ThemeKey > Settings > User Interface + + +For Developers +============== + +HOOK_themekey_properties() + Attributes + Key: namespace:property + Value: array() + - description => Readable name of property (required) + - validator => Callback function to validate a rule starting with that property (optional) + TODO: describe validator arguments and return value + - file => File that provides the validator function (optional) + - path => Alternative path relative to dupal's doc root to load the file (optional) + - static => true/false, static properties don't occur in properties drop down + and have fixed operator and value (optional) + - page cache => Level of page caching support: + - THEMEKEY_PAGECACHE_SUPPORTED + - THEMEKEY_PAGECACHE_UNSUPPORTED + - THEMEKEY_PAGECACHE_TIMEBASED + Default is THEMEKEY_PAGECACHE_UNSUPPORTED (optional) + + Maps + Key: none (indexed) + Value: array() + - src => Source property path (required) + - dst => Destination property path (required) + - callback => Mapping callback function (required) + - file => File that provides the callback function (optional) + - path => Alternative path relative to dupal's doc root to load the file (optional) + - args => array of additional arguments to be passed to the callback function (optional) + +HOOK_themekey_global() + Global properties + Key: namespace:property + Value: property value (scalar value or array of scalar values) + +HOOK_themekey_paths() + Paths + Key: none (indexed) + Value: array() + - path => Router path to register (required) + - callbacks => Load (and/or match) callback (optional) + (the callback function can set the 'theme' element in $params array directly, which will be applied) + Callback arguments: + - $item: array of elements associated with the path/callback + - $params: array of parameters available for load callback + diff --git a/sites/all/modules/themekey/docs/themekey.api.php b/sites/all/modules/themekey/docs/themekey.api.php new file mode 100644 index 0000000000000000000000000000000000000000..5f94beab69410860258b522451ec87b9003e0e27 --- /dev/null +++ b/sites/all/modules/themekey/docs/themekey.api.php @@ -0,0 +1,121 @@ +<?php + + +/** + * @file + * ThemeKey API documentation + */ + +/** + * By Implementing hook_themekey_properties() it's + * possible to add new properties to ThemeKey. + * + * Two assign a value to a property during a page request + * you have three possibilities: + * 1. Provide a mapping function from one property + * to another and tell ThemeKey about it using this hook + * 2. Implement hook_themekey_global() + * 3. Implement hook_themekey_paths() + * + * There's an example implementation of this hook, + * @see themekey_example_themeley_properties() + * + * @return + * An array of ThemeKey properties and mapping functions: + * array of ThemeKey property attributes: + * key: namespace:property + * value: array( + * description => Readable name of property (required) + * validator => Callback function to validate a rule starting with that property (optional) + * TODO: describe validator arguments and return value + * file => File that provides the validator function (optional) + * path => Alternative path relative to dupal's doc root to load the file (optional) + * static => true/false, static properties don't occur in properties drop down + * and have fixed operator and value (optional) + * page cache => Level of page caching support: + * - THEMEKEY_PAGECACHE_SUPPORTED + * - THEMEKEY_PAGECACHE_UNSUPPORTED + * - THEMEKEY_PAGECACHE_TIMEBASED + * Default is THEMEKEY_PAGECACHE_UNSUPPORTED (optional) + * ) + * array of mapping functions + * key: none (indexed) + * value: array( + * src => Source property path (required) + * dst => Destination property path (required) + * callback => Mapping callback (required) + * file => File that provides the callback function (optional) + * path => Alternative path relative to dupal's doc root to load the file (optional) + * ) + */ +function hook_themekey_properties() { + // Attributes of properties + $attributes = array(); + + $attributes['example:number_one'] = array( + 'description' => t('Example: always returns "1"'), + 'validator' => 'themekey_example_validator_number_one', + 'file' => 'themekey_example_validators.inc', + 'page cache' => THEMEKEY_PAGECACHE_SUPPORTED, + ); + + $attributes['example:global_one'] = array( + 'description' => t('Example: always returns "1"'), + 'validator' => 'themekey_example_validator_number_one', + 'file' => 'themekey_example_validators.inc', + 'page cache' => THEMEKEY_PAGECACHE_SUPPORTED, + ); + + $attributes['example:path_number'] = array( + 'description' => t('Example: always returns "1"'), + 'validator' => 'themekey_validator_ctype_digit', + 'page cache' => THEMEKEY_PAGECACHE_SUPPORTED, + ); + + // Mapping functions + $maps = array(); + + $maps[] = array( + 'src' => 'system:dummy', + 'dst' => 'example:number_one', + 'callback' => 'themekey_example_dummy2number_one', + 'file' => 'themekey_example_mappers.inc', + ); + + return array('attributes' => $attributes, 'maps' => $maps); +} + + +/** + * Functions implementing hook_themekey_global() + * set some properties on every page request. + * + * So only easy stuff with low time and memory + * consumtion should be done by + * implementing hook_themekey_global(). + */ +function hook_themekey_global() { + + $parameters = array(); + + $parameters['example:global_one'] = "1"; + + return $parameters; +} + + +/** + * Functions implementing hook_themekey_paths() + * set some properties on every page request. + * + * Using this function you directly map parts of + * the path to property values. + */ +function hook_themekey_paths() { + $paths = array(); + + // a path like 'example/27/foo will set property example:path_number to '27' + $paths[] = array('path' => 'example/#example:path_number'); + + return $paths; +} diff --git a/sites/all/modules/themekey/img/page_cache_0.png b/sites/all/modules/themekey/img/page_cache_0.png new file mode 100644 index 0000000000000000000000000000000000000000..0daee0be82373e4b52097aa53dee650ca100e00f Binary files /dev/null and b/sites/all/modules/themekey/img/page_cache_0.png differ diff --git a/sites/all/modules/themekey/img/page_cache_1.png b/sites/all/modules/themekey/img/page_cache_1.png new file mode 100644 index 0000000000000000000000000000000000000000..8431237bd374e68931a13df798bc0996452f705f Binary files /dev/null and b/sites/all/modules/themekey/img/page_cache_1.png differ diff --git a/sites/all/modules/themekey/img/page_cache_2.png b/sites/all/modules/themekey/img/page_cache_2.png new file mode 100644 index 0000000000000000000000000000000000000000..681c8dab69865ae9a8c9fc7d38aab32600cf31de Binary files /dev/null and b/sites/all/modules/themekey/img/page_cache_2.png differ diff --git a/sites/all/modules/themekey/img/tutorials/create_content.png b/sites/all/modules/themekey/img/tutorials/create_content.png new file mode 100644 index 0000000000000000000000000000000000000000..180796d3caaa77b26f440d920e64fdfe724da2f4 Binary files /dev/null and b/sites/all/modules/themekey/img/tutorials/create_content.png differ diff --git a/sites/all/modules/themekey/img/tutorials/create_content_premium_user.png b/sites/all/modules/themekey/img/tutorials/create_content_premium_user.png new file mode 100644 index 0000000000000000000000000000000000000000..f701235c8f089b7a625c6f450d3660620035e02e Binary files /dev/null and b/sites/all/modules/themekey/img/tutorials/create_content_premium_user.png differ diff --git a/sites/all/modules/themekey/img/tutorials/create_content_premium_user_any_role.png b/sites/all/modules/themekey/img/tutorials/create_content_premium_user_any_role.png new file mode 100644 index 0000000000000000000000000000000000000000..18706a162a4fb040af7df1a4153191278ab97c40 Binary files /dev/null and b/sites/all/modules/themekey/img/tutorials/create_content_premium_user_any_role.png differ diff --git a/sites/all/modules/themekey/img/tutorials/create_content_premium_user_standard_user.png b/sites/all/modules/themekey/img/tutorials/create_content_premium_user_standard_user.png new file mode 100644 index 0000000000000000000000000000000000000000..92526d2af67e69a070e0cb6ce809c0affcb5e3f9 Binary files /dev/null and b/sites/all/modules/themekey/img/tutorials/create_content_premium_user_standard_user.png differ diff --git a/sites/all/modules/themekey/img/tutorials/iphone_create_content_pages.png b/sites/all/modules/themekey/img/tutorials/iphone_create_content_pages.png new file mode 100644 index 0000000000000000000000000000000000000000..4897449fe3510e92980fb993cf1d61082dcec73e Binary files /dev/null and b/sites/all/modules/themekey/img/tutorials/iphone_create_content_pages.png differ diff --git a/sites/all/modules/themekey/img/tutorials/iphone_create_content_premium_user_any_role.png b/sites/all/modules/themekey/img/tutorials/iphone_create_content_premium_user_any_role.png new file mode 100644 index 0000000000000000000000000000000000000000..ef640a687440f1d44fbcbbe1b874f53681acedad Binary files /dev/null and b/sites/all/modules/themekey/img/tutorials/iphone_create_content_premium_user_any_role.png differ diff --git a/sites/all/modules/themekey/img/tutorials/premium_user.png b/sites/all/modules/themekey/img/tutorials/premium_user.png new file mode 100644 index 0000000000000000000000000000000000000000..92311eb0e585da58ef0b251b4c7f0b4750c4f893 Binary files /dev/null and b/sites/all/modules/themekey/img/tutorials/premium_user.png differ diff --git a/sites/all/modules/themekey/img/tutorials/themekey_user_profiles_2.png b/sites/all/modules/themekey/img/tutorials/themekey_user_profiles_2.png new file mode 100644 index 0000000000000000000000000000000000000000..a25b65fb206bb95fbc45d24b425ca51271cd261e Binary files /dev/null and b/sites/all/modules/themekey/img/tutorials/themekey_user_profiles_2.png differ diff --git a/sites/all/modules/themekey/img/tutorials/themekey_user_profiles_3.png b/sites/all/modules/themekey/img/tutorials/themekey_user_profiles_3.png new file mode 100644 index 0000000000000000000000000000000000000000..d6c60db004332a5d5c780a9b7d9cc88feec37930 Binary files /dev/null and b/sites/all/modules/themekey/img/tutorials/themekey_user_profiles_3.png differ diff --git a/sites/all/modules/themekey/img/tutorials/themekey_user_profiles_4.png b/sites/all/modules/themekey/img/tutorials/themekey_user_profiles_4.png new file mode 100644 index 0000000000000000000000000000000000000000..6ab65fa97ebe46f9cf74ca229a37f5d0ac21c6b7 Binary files /dev/null and b/sites/all/modules/themekey/img/tutorials/themekey_user_profiles_4.png differ diff --git a/sites/all/modules/themekey/modules/themekey.book.inc b/sites/all/modules/themekey/modules/themekey.book.inc new file mode 100644 index 0000000000000000000000000000000000000000..9739d12942b7b41d50be63668e4a5860f0bca1d7 --- /dev/null +++ b/sites/all/modules/themekey/modules/themekey.book.inc @@ -0,0 +1,132 @@ +<?php + +/** + * @file + * Provides some comment attributes as ThemeKey properties. + * + * @author Markus Kalkbrenner | Cocomore AG + * @see http://drupal.org/user/124705 + * + * @author profix898 + * @see http://drupal.org/user/35192 + */ + + +/** + * Implements hook_themekey_properties(). + * + * Provides additional properties for the Themekey module: + * - book:bid + * - book:has_children + * + * @return + * array of themekey properties and mapping functions + */ +function themekey_book_themekey_properties() { + // Attributes for properties + $attributes = array(); + $attributes['book:bid'] = array( + 'description' => t('Book: ID - The id of the book (bid). This is the node id (nid) of the top book page. See !link for your books', array('!link' => l(t('!path', array('!path' => 'admin/content/book')), 'admin/content/book'))), + 'validator' => 'themekey_validator_ctype_digit', + 'page cache' => THEMEKEY_PAGECACHE_SUPPORTED, + ); + $attributes['book:has_children'] = array( + 'description' => t('Book: Has Children - Whether the book has child pages or not. Possible values are "0" for false and "1" for true.'), + 'validator' => 'themekey_validator_nummeric_boolean', + 'page cache' => THEMEKEY_PAGECACHE_SUPPORTED, + ); + + $maps = array(); + $maps[] = array( + 'src' => 'node:nid', + 'dst' => 'book:bid', + 'callback' => 'themekey_book_nid2bid', + ); + $maps[] = array( + 'src' => 'node:nid', + 'dst' => 'book:has_children', + 'callback' => 'themekey_book_nid2has_children', + ); + + return array('attributes' => $attributes, 'maps' => $maps); +} + + +/** + * ThemeKey mapping function to set a + * ThemeKey property's value (destination) + * with the aid of another ThemeKey property (source). + * + * src: node_nid + * dst: book:bid + * + * @param $nid + * a node id + * + * @return + * a book id + * or NULL if no value could be mapped + */ +function themekey_book_nid2bid($nid) { + return themekey_book_get_simple_book_property($nid, 'bid'); +} + + +/** + * ThemeKey mapping function to set a + * ThemeKey property's value (destination) + * with the aid of another ThemeKey property (source). + * + * src: node_nid + * dst: book:has_children + * + * @param $nid + * a node id + * + * @return + * boolean + * or NULL if no value could be mapped + */ +function themekey_book_nid2has_children($nid) { + return themekey_book_get_simple_book_property($nid, 'has_children'); +} + + +/** + * Helper function that loads a book and returns the + * value of a book's property. + * + * @param $nid + * a node id + * + * @param $property + * name of a nodes attribute as string + * + * @return + * the value of the property or NULL + */ +function themekey_book_get_simple_book_property($nid, $property) { + static $books = array(); + + if (!isset($books[$nid])) { + $nodes = array($nid => new stdClass()); + + // node_load() must not be called from hook_init(). + // Therefore we have to execute SQL here using book's hook_node_load(). + book_node_load($nodes, NULL); + + if (isset($nodes[$nid]->book)) { + $books[$nid] = $nodes[$nid]->book; + } + else { + $books[$nid] = array(); + } + } + + if (isset($books[$nid][$property])) { + return $books[$nid][$property]; + } + else { + return NULL; + } +} diff --git a/sites/all/modules/themekey/modules/themekey.comment.inc b/sites/all/modules/themekey/modules/themekey.comment.inc new file mode 100644 index 0000000000000000000000000000000000000000..08c491e8e85c2d503eb68b21a02fd0f8b4283901 --- /dev/null +++ b/sites/all/modules/themekey/modules/themekey.comment.inc @@ -0,0 +1,82 @@ +<?php + +/** + * @file + * Provides some comment attributes as ThemeKey properties. + * + * @author Markus Kalkbrenner | Cocomore AG + * @see http://drupal.org/user/124705 + * + * @author profix898 + * @see http://drupal.org/user/35192 + */ + + +/** + * Implements hook_themekey_properties(). + * + * Provides additional properties for the ThemeKey module: + * - comment:cid + * + * @return + * array of themekey properties and mapping functions + */ +function themekey_comment_themekey_properties() { + // Attributes for properties + $attributes = array(); + $attributes['comment:cid'] = array( + 'description' => t('Comment: ID - The id of a comment (cid). See !link for your published comments or !link_unpublished for comments awaiting approval', + array('!link' => l(t('!path', array('!path' => 'admin/content/comment')), 'admin/content/comment'), '!link_unpublished' => l(t('!path', array('!path' => 'admin/content/comment/approval')), 'admin/content/comment/approval'))), + 'validator' => 'themekey_validator_ctype_digit', + 'page cache' => THEMEKEY_PAGECACHE_SUPPORTED, + ); + + // Mapping functions + $maps = array(); + $maps[] = array( + 'src' => 'comment:cid', + 'dst' => 'node:nid', + 'callback' => 'themekey_comment_cid2nid', + ); + + return array('attributes' => $attributes, 'maps' => $maps); +} + + +/** + * Implements hook_themekey_paths(). + */ +function themekey_comment_themekey_paths() { + $paths = array(); + $paths[] = array('path' => 'comment/reply/#node:nid'); + $paths[] = array('path' => 'comment/reply/#node:nid/#comment:cid'); + $paths[] = array('path' => 'comment/#comment:cid/edit'); + $paths[] = array('path' => 'comment/#comment:cid/delete'); + + return $paths; +} + + +/** + * ThemeKey mapping function to set a + * ThemeKey property's value (destination) + * with the aid of another ThemeKey property (source). + * + * src: comment:cid + * dst: node:nid + * + * @param $nid + * a comment id + * + * @return + * a node id + * or NULL if no value could be mapped + */ +function themekey_comment_cid2nid($cid) { + $nid = db_select('comment', 'c') + ->fields('c', array('nid')) + ->condition('cid', $cid) + ->execute() + ->fetchField(); + return $nid ? $nid : NULL; +} diff --git a/sites/all/modules/themekey/modules/themekey.locale.inc b/sites/all/modules/themekey/modules/themekey.locale.inc new file mode 100644 index 0000000000000000000000000000000000000000..4deeba9b1fe2974ae222e96cfb33cf5fdee27488 --- /dev/null +++ b/sites/all/modules/themekey/modules/themekey.locale.inc @@ -0,0 +1,53 @@ +<?php + +/** + * @file + * Provides some node attributes as ThemeKey properties. + * + * @author Markus Kalkbrenner | Cocomore AG + * @see http://drupal.org/user/124705 + * + * @author profix898 + * @see http://drupal.org/user/35192 + */ + + +/** + * Implements hook_themekey_properties(). + * + * Provides additional properties for the ThemeKey module: + * - locale:language + * + * @return + * array of themekey properties and mapping functions + */ +function themekey_locale_themekey_properties() { + // Attributes for properties + $attributes = array(); + $attributes['locale:language'] = array( + 'description' => t('Locale: Language - The code of the current site language, formatted like "en" or "de" or "neutral". See !link for the codes of your enabled languages', + array('!link' => l(t('!path', array('!path' => 'admin/config/language')), 'admin/config/language'))), + 'validator' => 'themekey_validator_language', + 'page cache' => THEMEKEY_PAGECACHE_SUPPORTED, + ); + + return array('attributes' => $attributes); +} + + +/** + * Implements hook_themekey_paths(). + */ +function themekey_locale_themekey_global() { + global $language; + + $parameters = array(); + if (empty($language->language) || LANGUAGE_NONE == $language->language) { + $parameters['locale:language'] = 'neutral'; + } + else { + $parameters['locale:language'] = $language->language; + } + + return $parameters; +} diff --git a/sites/all/modules/themekey/modules/themekey.node.inc b/sites/all/modules/themekey/modules/themekey.node.inc new file mode 100644 index 0000000000000000000000000000000000000000..f1c4d84964d53b5e998eca8404174f7d5c746a5b --- /dev/null +++ b/sites/all/modules/themekey/modules/themekey.node.inc @@ -0,0 +1,500 @@ +<?php + +/** + * @file + * Provides some node attributes as ThemeKey properties. + * + * @author Markus Kalkbrenner | Cocomore AG + * @see http://drupal.org/user/124705 + * + * @author profix898 + * @see http://drupal.org/user/35192 + */ + + +/** + * Implements hook_themekey_properties(). + * + * Provides additional properties for the ThemeKey module: + * - node:changed + * - node:created + * - node:changed_date_time + * - node:created_date_time + * - node:changed_date + * - node:created_date + * - node:language + * - node:nid + * - node:promote + * - node:sticky + * - node:type + * - node:uid + * - node:title + * + * @return + * array of themekey properties and mapping functions + */ +function themekey_node_themekey_properties() { + // Attributes of properties + $attributes = array(); + $attributes['node:changed'] = array( + 'description' => t('Node: Changed date - The date when the node was last edited/updated, formatted as unix timestamp like "1248873565".'), + 'validator' => 'themekey_validator_ctype_digit', + 'page cache' => THEMEKEY_PAGECACHE_SUPPORTED, + ); + $attributes['node:created'] = array( + 'description' => t('Node: Created date - The date when the node was created, formatted as unix timestamp like "1248873565".'), + 'validator' => 'themekey_validator_ctype_digit', + 'page cache' => THEMEKEY_PAGECACHE_SUPPORTED, + ); + $attributes['node:changed_date_time'] = array( + 'description' => t('Node: Changed date - The date including the time when the node was last edited/updated, formatted like "2009-12-24 18:13:24"'), + 'validator' => 'themekey_validator_date_time', + 'page cache' => THEMEKEY_PAGECACHE_SUPPORTED, + ); + $attributes['node:created_date_time'] = array( + 'description' => t('Node: Created date - The date including the time when the node was created, formatted like "2009-12-24 18:13:24"'), + 'validator' => 'themekey_validator_date_time', + 'page cache' => THEMEKEY_PAGECACHE_SUPPORTED, + ); + $attributes['node:changed_date'] = array( + 'description' => t('Node: Changed date - The date without the time when the node was last edited/updated, formatted like "2009-12-24"'), + 'validator' => 'themekey_validator_date', + 'page cache' => THEMEKEY_PAGECACHE_SUPPORTED, + ); + $attributes['node:created_date'] = array( + 'description' => t('Node: Created date - The date without the time when the node was created, formatted like "2009-12-24"'), + 'validator' => 'themekey_validator_date', + 'page cache' => THEMEKEY_PAGECACHE_SUPPORTED, + ); + $attributes['node:language'] = array( + 'description' => t('Node: Language - The code of the selected language of a node (formatted like "en" or "de") or "neutral". See !link for the codes of your enabled languages', + array('!link' => l(t('!path', array('!path' => 'admin/config/language')), 'admin/config/language'))), + 'validator' => 'themekey_validator_language', + 'page cache' => THEMEKEY_PAGECACHE_SUPPORTED, + ); + $attributes['node:nid'] = array( + 'description' => t('Node: ID - The id of a node (nid), can be found in the URL of the node, "node/23" or "node/23/edit" (23 = nid)'), + 'validator' => 'themekey_validator_ctype_digit', + 'page cache' => THEMEKEY_PAGECACHE_SUPPORTED, + ); + $attributes['node:vid'] = array( + 'description' => t('Node: The version id of a node (vid), mostly for internal use within ThemeKey. In most cases it\'s better to use node:nid within your rule chains.'), + 'validator' => 'themekey_validator_ctype_digit', + 'page cache' => THEMEKEY_PAGECACHE_SUPPORTED, + ); + $attributes['node:promote'] = array( + 'description' => t('Node: Promoted - If the node is promoted to the front page. Possible values are "0" for true and "1" for false.'), + 'validator' => 'themekey_validator_nummeric_boolean', + 'page cache' => THEMEKEY_PAGECACHE_SUPPORTED, + ); + $attributes['node:sticky'] = array( + 'description' => t('Node: Sticky - If the node is set "Sticky at top of lists". Possible values are "0" for true and "1" for false.'), + 'validator' => 'themekey_validator_nummeric_boolean', + 'page cache' => THEMEKEY_PAGECACHE_SUPPORTED, + ); + $attributes['node:type'] = array( + 'description' => t('Node: Type - The machine-readable content type of the node. See !link for your content types (use column "Type"). Drupal default types are "page" and "story".', + array('!link' => l(t('!path', array('!path' => 'admin/structure/types')), 'admin/structure/types'))), + 'validator' => 'themekey_validator_node_type', + 'page cache' => THEMEKEY_PAGECACHE_SUPPORTED, + ); + $attributes['node:uid'] = array( + 'description' => t('Node: User ID - The node author\'s user id (uid). The user id can be found in the URL of the user\'s profile page, "user/23" or "user/23/edit" (23 = uid). See !link for your users.', + array('!link' => l(t('!path', array('!path' => 'admin/user/user/list')), 'admin/user/user/list'))), + 'validator' => 'themekey_validator_ctype_digit', + 'page cache' => THEMEKEY_PAGECACHE_SUPPORTED, + ); + $attributes['node:title'] = array( + 'description' => t('Node: Title - The title of the node.'), + 'validator' => '', + 'page cache' => THEMEKEY_PAGECACHE_SUPPORTED, + ); + + // Mapping functions + $maps = array(); + $maps[] = array( + 'src' => 'node:nid', + 'dst' => 'node:changed', + 'callback' => 'themekey_node_nid2changed', + ); + $maps[] = array( + 'src' => 'node:nid', + 'dst' => 'node:created', + 'callback' => 'themekey_node_nid2created', + ); + $maps[] = array( + 'src' => 'node:changed', + 'dst' => 'node:changed_date_time', + 'callback' => 'themekey_node_timestamp2datetime', + ); + $maps[] = array( + 'src' => 'node:created', + 'dst' => 'node:created_date_time', + 'callback' => 'themekey_node_timestamp2datetime', + ); + $maps[] = array( + 'src' => 'node:changed', + 'dst' => 'node:changed_date', + 'callback' => 'themekey_node_timestamp2date', + ); + $maps[] = array( + 'src' => 'node:created', + 'dst' => 'node:created_date', + 'callback' => 'themekey_node_timestamp2date', + ); + $maps[] = array( + 'src' => 'node:nid', + 'dst' => 'node:language', + 'callback' => 'themekey_node_nid2language', + ); + $maps[] = array( + 'src' => 'node:nid', + 'dst' => 'node:vid', + 'callback' => 'themekey_node_nid2vid', + ); + $maps[] = array( + 'src' => 'node:nid', + 'dst' => 'node:promote', + 'callback' => 'themekey_node_nid2promote', + ); + $maps[] = array( + 'src' => 'node:nid', + 'dst' => 'node:sticky', + 'callback' => 'themekey_node_nid2sticky', + ); + $maps[] = array( + 'src' => 'node:nid', + 'dst' => 'node:type', + 'callback' => 'themekey_node_nid2type', + ); + $maps[] = array( + 'src' => 'node:nid', + 'dst' => 'node:uid', + 'callback' => 'themekey_node_nid2uid', + ); + $maps[] = array( + 'src' => 'node:nid', + 'dst' => 'node:title', + 'callback' => 'themekey_node_nid2title', + ); + $maps[] = array( + 'src' => 'drupal:get_q', + 'dst' => 'node:type', + 'callback' => 'themekey_node_get_q2type', + ); + + return array('attributes' => $attributes, 'maps' => $maps); +} + + +/** + * Implements hook_themekey_paths(). + */ +function themekey_node_themekey_paths() { + $paths = array(); + $paths[] = array('path' => 'node/#node:nid'); + $paths[] = array('path' => 'node/#node:nid/revisions/#node:vid/view'); + return $paths; +} + + +/** + * Helper function that loads a node and returns the + * value of a node's property. + * + * @param $nid + * a node id + * + * @param $property + * name of a node's attribute as string + * + * @return + * the value of the property or NULL + */ +function themekey_node_get_simple_node_property($nid, $property) { + static $nodes = array(); + + if (!isset($nodes[$nid])) { + //node_load() must not be called from hook_init(). Therefore, we have to execute SQL here + $nodes[$nid] = db_select('node', 'n') + ->fields('n') + ->condition('n.nid', $nid) + ->execute() + ->fetchObject(); + } + + if (isset($nodes[$nid]->$property)) { + return $nodes[$nid]->$property; + } + return NULL; + +} + + +/** + * ThemeKey mapping function to set a + * ThemeKey property's value (destination) + * with the aid of another ThemeKey property (source). + * + * src: node:nid + * dst: node:changed + * + * @param $nid + * a node id + * + * @return + * string + * or NULL if no value could be mapped + */ +function themekey_node_nid2changed($nid) { + return themekey_node_get_simple_node_property($nid, 'changed'); +} + + +/** + * ThemeKey mapping function to set a + * ThemeKey property's value (destination) + * with the aid of another ThemeKey property (source). + * + * src: node:nid + * dst: node:created + * + * @param $nid + * a node id + * + * @return + * string + * or NULL if no value could be mapped + */ +function themekey_node_nid2created($nid) { + return themekey_node_get_simple_node_property($nid, 'created'); +} + + +/** + * ThemeKey mapping function to set a + * ThemeKey property's value (destination) + * with the aid of another ThemeKey property (source). + * + * src: node:nid + * dst: node:language + * + * @param $nid + * a node id + * + * @return + * string + * or NULL if no value could be mapped + */ +function themekey_node_nid2language($nid) { + $language = themekey_node_get_simple_node_property($nid, 'language'); + if (empty($language) || LANGUAGE_NONE == $language) { + $language = 'neutral'; + } + return $language; +} + + +/** + * ThemeKey mapping function to set a + * ThemeKey property's value (destination) + * with the aid of another ThemeKey property (source). + * + * src: node:nid + * dst: node:vid + * + * @param $nid + * a node id + * + * @return + * string + * or NULL if no value could be mapped + */ +function themekey_node_nid2vid($nid) { + return themekey_node_get_simple_node_property($nid, 'vid'); +} + + + + +/** + * ThemeKey mapping function to set a + * ThemeKey property's value (destination) + * with the aid of another ThemeKey property (source). + * + * src: node:nid + * dst: node:promote + * + * @param $nid + * a node id + * + * @return + * string + * or NULL if no value could be mapped + */ +function themekey_node_nid2promote($nid) { + return themekey_node_get_simple_node_property($nid, 'promote'); +} +/** + * ThemeKey mapping function to set a + * ThemeKey property's value (destination) + * with the aid of another ThemeKey property (source). + * + * src: node:nid + * dst: node:sticky + * + * @param $nid + * a node id + * + * @return + * string + * or NULL if no value could be mapped + */ +function themekey_node_nid2sticky($nid) { + return themekey_node_get_simple_node_property($nid, 'sticky'); +} + + +/** + * ThemeKey mapping function to set a + * ThemeKey property's value (destination) + * with the aid of another ThemeKey property (source). + * + * src: node:nid + * dst: node:type + * + * @param $nid + * a node id + * + * @return + * string + * or NULL if no value could be mapped + */ +function themekey_node_nid2type($nid) { + return themekey_node_get_simple_node_property($nid, 'type'); +} + + +/** + * ThemeKey mapping function to set a + * ThemeKey property's value (destination) + * with the aid of another ThemeKey property (source). + * + * src: node:nid + * dst: node:uid + * + * @param $nid + * a node id + * + * @return + * string + * or NULL if no value could be mapped + */ +function themekey_node_nid2uid($nid) { + return themekey_node_get_simple_node_property($nid, 'uid'); +} + + +/** + * ThemeKey mapping function to set a + * ThemeKey property's value (destination) + * with the aid of another ThemeKey property (source). + * + * src: node:nid + * dst: node:title + * + * @param $nid + * a node id + * + * @return + * string + * or NULL if no value could be mapped + */ +function themekey_node_nid2title($nid) { + return themekey_node_get_simple_node_property($nid, 'title'); +} + + +/** + * ThemeKey mapping function to set a + * ThemeKey property's value (destination) + * with the aid of another ThemeKey property (source). + * + * src: any timestamp + * dst: time formatted + * + * @param $timestamp + * a unix timestamp + * + * @return + * string + * or NULL if no value could be mapped + */ +function themekey_node_timestamp2datetime($timestamp) { + return date('Y-m-d H:i:s', $timestamp); +} + + +/** + * ThemeKey mapping function to set a + * ThemeKey property's value (destination) + * with the aid of another ThemeKey property (source). + * + * src: any timestamp + * dst: time formatted + * + * @param $timestamp + * a unix timestamp + * + * @return + * string + * or NULL if no value could be mapped + */ +function themekey_node_timestamp2date($timestamp) { + return date('Y-m-d', $timestamp); +} + + +/** + * ThemeKey mapping function to set a + * ThemeKey property's value (destination) + * with the aid of another ThemeKey property (source). + * + * src: Drupal path + * dst: node type + * + * @param $patch + * a Drupal path + * + * @return + * string + * or NULL if no value could be mapped + */ +function themekey_node_get_q2type($q) { + if (strpos($q, 'node/add/') === 0) { + return str_replace('-', '_', str_replace('node/add/', '', $q)); + } + return NULL; +} + + +/** + * This function isn't needed anymore and has been removed with ThemeKey 6.x-2.0-beta2. + * But if it's missing a fatal error occurs if you upgrade from any version before + * 6.x-2.0-beta2 until you call update.php. That should be avoided. + * So this function is available again and does nothing else than reminding the + * administrator to call update.php + * + * see http://drupal.org/node/662786 + * + * @param unknown_type $item + * @param unknown_type $parameters + * @return unknown_type + */ +function _themekey_node_callback(&$item, &$parameters) { + global $user; + + if (1 == $user->uid) { + drupal_set_message(filter_xss(t('You uploaded a new version of ThemeKey. Please !link your site!', array('!link' => l(t('update'), '/update.php'))))); + } +} diff --git a/sites/all/modules/themekey/modules/themekey.print.inc b/sites/all/modules/themekey/modules/themekey.print.inc new file mode 100644 index 0000000000000000000000000000000000000000..d282ac417cc6e0edf4f028782cc49bfa08c0f322 --- /dev/null +++ b/sites/all/modules/themekey/modules/themekey.print.inc @@ -0,0 +1,23 @@ +<?php + +/** + * @file + * Integrates Print module's paths into ThemeKey. + * + * @author Markus Kalkbrenner | Cocomore AG + * @see http://drupal.org/user/124705 + */ + + +/** + * Implements hook_themekey_paths(). + */ +function themekey_print_themekey_paths() { + $paths = array(); + + $paths[] = array('path' => 'print/#node:nid'); + $paths[] = array('path' => 'printpdf/#node:nid'); + $paths[] = array('path' => 'printmail/#node:nid'); + + return $paths; +} diff --git a/sites/all/modules/themekey/modules/themekey.system.inc b/sites/all/modules/themekey/modules/themekey.system.inc new file mode 100644 index 0000000000000000000000000000000000000000..7019fd3f95b1004812566aa6a4fdca68c839eda6 --- /dev/null +++ b/sites/all/modules/themekey/modules/themekey.system.inc @@ -0,0 +1,668 @@ +<?php + +/** + * @file + * Provides some ThemeKey properties. + * + * @author Markus Kalkbrenner | Cocomore AG + * @see http://drupal.org/user/124705 + * + * @author profix898 + * @see http://drupal.org/user/35192 + */ + + +/** + * Implements hook_themekey_properties(). + * + * Provides additional properties for module ThemeKey: + * - system:host + * - system:query_param + * - system:query_string + * - system:cookie + * - system:server_ip + * - system:server_port + * - system:server_name + * - system:https + * - system:remote_ip + * - system:referer + * - system:user_agent + * - system:user_browser + * - system:user_browser_simplified + * - system:user_os + * - system:user_os_simplified + * - system:date_time + * - system:date + * - system:time + * - system:day_of_week + * - system:dummy + * - system:session + * - system:random + * - drupal:path + * - drupal:path:wildcard + * - drupal:get_q + * - drupal:base_path + * - drupal:is_front_page + * + * @return + * array of themekey properties and mapping functions + */ +function themekey_system_themekey_properties() { + // Attributes for properties + $attributes = array(); + + $attributes['system:host'] = array( + 'description' => t('System: HTTP_HOST - The hostname/domain of the site without http:// or https://, like "www.drupal.org" or "drupal.cocomore.com"'), + 'validator' => 'themekey_validator_http_host', + 'page cache' => THEMEKEY_PAGECACHE_SUPPORTED, + ); + + $attributes['system:query_param'] = array( + 'description' => t("System: Query Parameter - Every single query parameter other than 'q' and its value, if present. Note that values are url decoded. Example: '?q=node&foo=bar&dummy&filter=tid%3A27' will cause three entries 'foo=bar', 'dummy' and 'filter=tid:27'. For 'q', see property drupal:get_q."), + 'page cache' => THEMEKEY_PAGECACHE_SUPPORTED, + ); + + $attributes['system:query_string'] = array( + 'description' => t("System: Query String - Complete query string except parameter 'q'. Example: '?q=node&foo=bar&dummy&filter=tid%3A27' will result in 'foo=bar&dummy&filter=tid%3A27'. For 'q' see property drupal:get_q."), + 'page cache' => THEMEKEY_PAGECACHE_SUPPORTED, + ); + + $attributes['system:cookie'] = array( + 'description' => t("System: Cookie - Every single cookie and its value if present will be formatted like 'COOKIE_NAME=COOKIE_VALUE'"), + 'page cache' => THEMEKEY_PAGECACHE_UNSUPPORTED, + ); + + $attributes['system:post'] = array( + 'description' => t("System: POST - Every single POST value formatted like 'POST_VARIABLE_NAME=POST_VARIABLE_VALUE'. Example: form_id=node_form"), + 'page cache' => THEMEKEY_PAGECACHE_UNSUPPORTED, + ); + + $attributes['system:server_ip'] = array( + 'description' => t("System: 'SERVER_ADDR' - The IP address of the server under which the current script is executing."), + 'validator' => 'themekey_validator_ip_address', + 'page cache' => THEMEKEY_PAGECACHE_UNSUPPORTED, + ); + + $attributes['system:server_port'] = array( + 'description' => t("System: 'SERVER_PORT' - The port on the server machine being used by the web server for communication. For default setups, this will be '80'; using SSL, for instance, will change this to whatever your defined secure HTTP port is."), + 'validator' => 'themekey_validator_ctype_digit', + 'page cache' => THEMEKEY_PAGECACHE_UNSUPPORTED, + ); + + $attributes['system:server_name'] = array( + 'description' => t("System: 'SERVER_NAME' - The name of the server host under which the current script is executing. If the script is running on a virtual host, this will be the value defined for that virtual host."), + 'page cache' => THEMEKEY_PAGECACHE_UNSUPPORTED, + ); + + $attributes['system:https'] = array( + 'description' => t("System: 'HTTPS' - Set to 'true' value if the script was queried through the HTTPS protocol, otherwise 'false'."), + 'validator' => 'themekey_validator_string_boolean', + 'page cache' => THEMEKEY_PAGECACHE_SUPPORTED, + ); + + $attributes['system:remote_ip'] = array( + 'description' => t("System: 'REMOTE_ADDR' - The IP address from which the user is viewing the current page."), + 'validator' => 'themekey_validator_ip_address', + 'page cache' => THEMEKEY_PAGECACHE_UNSUPPORTED, + ); + + $attributes['system:referer'] = array( + 'description' => t("System: 'HTTP_REFERER' - The address of the page (if any) which referred the user agent to the current page. This is set by the user agent. Not all user agents will set this, and some provide the ability to modify HTTP_REFERER as a feature. In short, it cannot really be trusted."), + 'page cache' => THEMEKEY_PAGECACHE_UNSUPPORTED, + ); + + $attributes['system:user_agent'] = array( + 'description' => t("System: 'HTTP_USER_AGENT' - Contents of the User-Agent: header from the current request, if there is one. This is a string denoting the user agent which is accessing the page. A typical example is: Mozilla/4.5 [en] (X11; U; Linux 2.2.9 i586)."), + 'page cache' => THEMEKEY_PAGECACHE_UNSUPPORTED, + ); + + $attributes['system:user_browser'] = array( + 'description' => t("System: Browser - Information about user's browser. Possible values: 'Blazer [VERSION]', 'Internet Explorer [VERSION]', 'Mozilla Firefox [VERSION]', 'Netscape [VERSION]', 'Google Chrome [VERSION]', 'Safari [VERSION]', 'Galeon [VERSION]', 'Konqueror [VERSION]', 'Gecko based', 'Opera [VERSION]', 'Lynx [VERSION]', 'Fennec [VERSION]', 'Maemo [VERSION]', 'unknown'"), + 'page cache' => THEMEKEY_PAGECACHE_UNSUPPORTED, + ); + + $attributes['system:user_browser_simplified'] = array( + 'description' => t("System: Browser - Simplified information about user's browser. Possible values: 'Blazer', 'Internet Explorer', 'Mozilla Firefox', 'Netscape', 'Google Chrome', 'Safari', 'Galeon', 'Konqueror', 'Gecko based', 'Opera', 'Lynx', 'Fennec', 'Maemo', 'unknown'"), + 'page cache' => THEMEKEY_PAGECACHE_UNSUPPORTED, + ); + + $attributes['system:user_os'] = array( + 'description' => t("System: Operating System - Information about user's browser. Possible values: 'Windows XP', 'Windows Vista', 'Windows 98', 'Windows 2000', 'Windows 2003 server', 'Windows NT', 'Windows ME', 'Windows CE', 'Windows ME', 'iPhone', 'iPad', 'Mac OS X', 'Macintosh', 'Linux', 'Free BSD', 'Symbian', 'webOS', 'Android', 'Blackberry', 'unknown'"), + 'page cache' => THEMEKEY_PAGECACHE_UNSUPPORTED, + ); + + $attributes['system:user_os_simplified'] = array( + 'description' => t("System: Operating System - Simplified information about user's browser. Possible values: 'Windows', 'iPhone', 'iPad', 'Mac OS X', 'Macintosh', 'Linux', 'Free BSD', 'Symbian', 'webOS', 'Android', 'Blackberry', 'unknown'"), + 'page cache' => THEMEKEY_PAGECACHE_UNSUPPORTED, + ); + + $attributes['system:date_time'] = array( + 'description' => t("System: Date Time - Current time formatted as Y-m-d H:i:s (example: 2009-12-24 18:30:10)"), + 'validator' => 'themekey_validator_date_time', + 'page cache' => THEMEKEY_PAGECACHE_TIMEBASED, + ); + + $attributes['system:date'] = array( + 'description' => t("System: Date - Current time formatted as Y-m-d (example: 2009-12-24)"), + 'validator' => 'themekey_validator_date', + 'page cache' => THEMEKEY_PAGECACHE_TIMEBASED, + ); + + $attributes['system:month'] = array( + 'description' => t("System: Month - Current month formatted as 'Jan' through 'Dec'"), + 'validator' => 'themekey_validator_month', + 'page cache' => THEMEKEY_PAGECACHE_TIMEBASED, + ); + + $attributes['system:time'] = array( + 'description' => t("System: Time - Current time formatted as H:i:s (example: 18:30:10)"), + 'validator' => 'themekey_validator_time', + 'page cache' => THEMEKEY_PAGECACHE_TIMEBASED, + ); + + $attributes['system:day_of_week'] = array( + 'description' => t("System: Day of Week - Current day of the week formatted as three letters 'Mon' through 'Sun'"), + 'validator' => 'themekey_validator_day_of_week', + 'page cache' => THEMEKEY_PAGECACHE_TIMEBASED, + ); + + $attributes['system:day_of_month'] = array( + 'description' => t("System: Day of Month - Current day of the month formatted as 1 - 31"), + 'validator' => 'themekey_validator_day_of_month', + 'page cache' => THEMEKEY_PAGECACHE_TIMEBASED, + ); + + $attributes['system:dummy'] = array( + 'description' => t("System: Dummy - Dummy property. Value is always 'dummy'"), + 'page cache' => THEMEKEY_PAGECACHE_SUPPORTED, + ); + + $attributes['system:session'] = array( + 'description' => t("System: Session - Every single session parameter and its value, if a value is present and a simple type (string, integer, boolean). Booleans are represented as integers; '1' as TRUE and '0' as FALSE."), + 'page cache' => THEMEKEY_PAGECACHE_SUPPORTED, + ); + + $attributes['system:random'] = array( + 'description' => t("System: Random - A random value that could be '0' or 1'."), + 'validator' => 'themekey_validator_nummeric_boolean', + 'page cache' => THEMEKEY_PAGECACHE_UNSUPPORTED, + ); + + $attributes['drupal:path'] = array( + 'description' => t('Drupal: Drupal path like "node/add/story" or path alias with support for wildcards.<br /> +Query parameters are stripped off before the path gets examined. P.e. "node/add/story?destination=node" becomes "node/add/story" first. If you want to access query parameters, have a look at the system:query_param property provided by !link.<br /> +Wildcard characters are "#" for numeric parts and "%" for all characters. To match conditions against a certain part, use an identifier with the wildcard. For example "comment/reply/#xyz" matches all paths with "comment/reply" and a numeric third argument. You can then specify conditions for every wildcard argument using the property "drupal:path:wildcard" and the identifier you choose ("xyz" in this example).<br /> +These are the possible wildcard replacements for foo/bar/42/test.html:', array('!link' => l(t('!path', array('!path' => 'ThemeKey Properties')), 'http://drupal.org/project/themekey_properties'))) . +'<pre> +foo/bar/42/test.html +foo/bar/42/% +foo/bar/42 +foo/bar/%/test.html +foo/bar/%/% +foo/bar/% +foo/bar/#/test.html +foo/bar/#/% +foo/bar/# +foo/bar +foo/%/42/test.html +foo/%/42/% +foo/%/42 +foo/%/%/test.html +foo/%/%/% +foo/%/% +foo/%/#/test.html +foo/%/#/% +foo/%/# +foo/% +foo +%/bar/42/test.html +%/bar/42/% +%/bar/42 +%/bar/%/test.html +%/bar/%/% +%/bar/% +%/bar/#/test.html +%/bar/#/% +%/bar/# +%/bar +%/%/42/test.html +%/%/42/% +%/%/42 +%/%/%/test.html +%/%/%/% +%/%/% +%/%/#/test.html +%/%/#/% +%/%/# +%/% +% +</pre>', + 'validator' => 'themekey_validator_drupal_path', + 'page cache' => THEMEKEY_PAGECACHE_SUPPORTED, + ); + $attributes['drupal:path:wildcard'] = array( + 'description' => t('Wildcard of "drupal:path". See explanation of "drupal:path" for details.'), + 'validator' => 'themekey_validator_wildcard', + 'page cache' => THEMEKEY_PAGECACHE_SUPPORTED, + ); + $attributes['drupal:get_q'] = array( + 'description' => t('Drupal: $_GET[\'q\'] - Current value of Drupal\'s query parameter "q"'), + 'validator' => '', + 'page cache' => THEMEKEY_PAGECACHE_SUPPORTED, + ); + + $attributes['drupal:base_path'] = array( + 'description' => t("Drupal: Base path - If Drupal is installed in a subdirectory, the base path will be '/[subdirectory]/', otherwise just '/'."), + 'page cache' => THEMEKEY_PAGECACHE_SUPPORTED, + ); + + $attributes['drupal:is_front_page'] = array( + 'description' => t("Drupal: Is front page - 'true' if current page is front page, otherwise just 'false'."), + 'validator' => 'themekey_validator_string_boolean', + 'page cache' => THEMEKEY_PAGECACHE_SUPPORTED, + ); + + $maps = array(); + $maps[] = array('src' => 'system:dummy', + 'dst' => 'system:query_param', + 'callback' => 'themekey_dummy2query_param'); + $maps[] = array('src' => 'system:dummy', + 'dst' => 'system:cookie', + 'callback' => 'themekey_dummy2cookie'); + $maps[] = array('src' => 'system:dummy', + 'dst' => 'system:post', + 'callback' => 'themekey_dummy2post'); + $maps[] = array('src' => 'system:dummy', + 'dst' => 'system:query_string', + 'callback' => 'themekey_dummy2query_string'); + $maps[] = array('src' => 'system:dummy', + 'dst' => 'system:date_time', + 'callback' => 'themekey_dummy2date_time'); + $maps[] = array('src' => 'system:dummy', + 'dst' => 'system:date', + 'callback' => 'themekey_dummy2date'); + $maps[] = array('src' => 'system:dummy', + 'dst' => 'system:month', + 'callback' => 'themekey_dummy2month'); + $maps[] = array('src' => 'system:dummy', + 'dst' => 'system:time', + 'callback' => 'themekey_dummy2time'); + $maps[] = array('src' => 'system:dummy', + 'dst' => 'system:day_of_week', + 'callback' => 'themekey_dummy2day_of_week'); + $maps[] = array('src' => 'system:dummy', + 'dst' => 'system:day_of_month', + 'callback' => 'themekey_dummy2day_of_month'); + $maps[] = array('src' => 'system:dummy', + 'dst' => 'system:user_browser', + 'callback' => 'themekey_dummy2user_browser'); + $maps[] = array('src' => 'system:dummy', + 'dst' => 'system:user_os', + 'callback' => 'themekey_dummy2user_os'); + $maps[] = array('src' => 'system:user_browser', + 'dst' => 'system:user_browser_simplified', + 'callback' => 'themekey_user_browser2user_browser_simplified'); + $maps[] = array('src' => 'system:user_os', + 'dst' => 'system:user_os_simplified', + 'callback' => 'themekey_user_os2user_os_simplified'); + $maps[] = array('src' => 'system:dummy', + 'dst' => 'system:session', + 'callback' => 'themekey_dummy2session'); + + + return array('attributes' => $attributes, 'maps' => $maps); +} + + +/** + * Implements hook_themekey_global(). + */ +function themekey_system_themekey_global() { + global $user; + + $parameters = array(); + $parameters['system:host'] = $_SERVER['HTTP_HOST']; + $parameters['drupal:get_q'] = themekey_get_q(); + $parameters['system:server_ip'] = !empty($_SERVER['SERVER_ADDR']) ? $_SERVER['SERVER_ADDR']: NULL; + $parameters['system:server_port'] = !empty($_SERVER['SERVER_PORT']) ? $_SERVER['SERVER_PORT']: NULL; + $parameters['system:server_name'] = !empty($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME']: NULL; + // Note that when using ISAPI with IIS, the value will be 'off' if the request was not made through the HTTPS protocol. + $parameters['system:https'] = (!empty($_SERVER['HTTPS']) && 'off' != $_SERVER['HTTPS']) ? 'true' : 'false'; + $parameters['system:remote_ip'] = !empty($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR']: NULL; + $parameters['system:referer'] = !empty($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER']: NULL; + $parameters['system:user_agent'] = !empty($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT']: NULL; + $parameters['system:dummy'] = 'dummy'; + $parameters['system:random'] = rand(0, 1); + + $parameters['drupal:base_path'] = base_path(); + $parameters['drupal:is_front_page'] = drupal_is_front_page() ? 'true' : 'false'; + + return $parameters; +} + + +/** + * ThemeKey mapping function to set a + * ThemeKey property's value (destination) + * with the aid of another ThemeKey property (source). + * + * src: system:dummy + * dst: system:query_param + * + * @param $dummy + * string containing current value of ThemeKey property system:dummy + * + * @return + * array of system:query_param values + * or NULL if no value could be mapped + */ +function themekey_dummy2query_param($dummy) { + $filtered_params = array(); + $query_params = $_GET; + unset($query_params['q']); + foreach ($query_params as $key => $value) { + $filtered_params[] = $key . (!empty($value) ? '=' . $value : ''); + } + return count($filtered_params) ? array_unique($filtered_params) : NULL; +} + + +/** + * ThemeKey mapping function to set a + * ThemeKey property's value (destination) + * with the aid of another ThemeKey property (source). + * + * src: system:dummy + * dst: system:cookie + * + * @param $dummy + * string containing current value of ThemeKey property system:dummy + * + * @return + * array of system:cookie values + * or NULL if no value could be mapped + */ +function themekey_dummy2cookie($dummy) { + $filtered_cookies = array(); + foreach ($_COOKIE as $key => $value) { + $filtered_cookies[] = $key . (!empty($value) ? '=' . $value : ''); + } + return count($filtered_cookies) ? array_unique($filtered_cookies) : NULL; +} + + +/** + * ThemeKey mapping function to set a + * ThemeKey property's value (destination) + * with the aid of another ThemeKey property (source). + * + * src: system:dummy + * dst: system:post + * + * @param $dummy + * string containing current value of ThemeKey property system:dummy + * + * @return + * array of system:post values + * or NULL if no value could be mapped + */ +function themekey_dummy2post($dummy) { + $filtered_post = array(); + foreach ($_POST as $key => $value) { + $filtered_post[] = $key . (!empty($value) ? '=' . $value : ''); + } + return count($filtered_post) ? array_unique($filtered_post) : NULL; +} + + +/** + * ThemeKey mapping function to set a + * ThemeKey property's value (destination) + * with the aid of another ThemeKey property (source). + * + * src: system:dummy + * dst: system:query_string + * + * @param $dummy + * string containing current value of ThemeKey property system:dummy + * + * @return + * system:query_string as string + * or NULL if no value could be mapped + */ +function themekey_dummy2query_string($dummy) { + if (!empty($_SERVER['QUERY_STRING'])) { + $query_string = trim(preg_replace("/q=[^&]*/", '', $_SERVER['QUERY_STRING']), '&'); + if (!empty($query_string)) { + return $query_string; + } + } + + return NULL; +} + + +/** + * ThemeKey mapping function to set a + * ThemeKey property's value (destination) + * with the aid of another ThemeKey property (source). + * + * src: system:dummy + * dst: system:date_time + * + * @param $dummy + * string containing current value of ThemeKey property system:dummy + * + * @return + * current time as string formatted like "2009-12-24 23:56:15" + */ +function themekey_dummy2date_time($dummy) { + return format_date(time(), 'custom', 'Y-m-d H:i:s', variable_get('date_default_timezone', 0)); +} + + +/** + * ThemeKey mapping function to set a + * ThemeKey property's value (destination) + * with the aid of another ThemeKey property (source). + * + * src: system:dummy + * dst: system:date + * + * @param $dummy + * string containing current value of ThemeKey property system:dummy + * + * @return + * current date as string formatted like "2009-12-24" + */ +function themekey_dummy2date($dummy) { + return format_date(time(), 'custom', 'Y-m-d', variable_get('date_default_timezone', 0)); +} + + +/** + * ThemeKey mapping function to set a + * ThemeKey property's value (destination) + * with the aid of another ThemeKey property (source). + * + * src: system:dummy + * dst: system:month + * + * @param $dummy + * string containing current value of ThemeKey property system:dummy + * + * @return + * current date as string formatted like "2009-12-24" + */ +function themekey_dummy2month($dummy) { + return format_date(time(), 'custom', 'M', variable_get('date_default_timezone', 0)); +} + + +/** + * ThemeKey mapping function to set a + * ThemeKey property's value (destination) + * with the aid of another ThemeKey property (source). + * + * src: system:dummy + * dst: system:time + * + * @param $dummy + * string containing current value of ThemeKey property system:dummy + * + * @return + * current time as string formatted like "23:56:15" + */ +function themekey_dummy2time($dummy) { + return format_date(time(), 'custom', 'H:i:s', variable_get('date_default_timezone', 0)); +} + + +/** + * ThemeKey mapping function to set a + * ThemeKey property's value (destination) + * with the aid of another ThemeKey property (source). + * + * src: system:dummy + * dst: system:user_browser + * + * @param $dummy + * string containing current value of the ThemeKey property, system:dummy + * + * @return + * current user's browser as string + */ +function themekey_dummy2user_browser($dummy) { + return ThemekeyBrowserDetection::getBrowser($_SERVER['HTTP_USER_AGENT']); +} + + +/** + * ThemeKey mapping function to set a + * ThemeKey property's value (destination) + * with the aid of another ThemeKey property (source). + * + * src: system:dummy + * dst: system:user_os + * + * @param $dummy + * string containing current value of ThemeKey property system:dummy + * + * @return + * current user's operating system as string + */ +function themekey_dummy2user_os($dummy) { + return ThemekeyBrowserDetection::getOs($_SERVER['HTTP_USER_AGENT']); +} + + +/** + * ThemeKey mapping function to set a + * ThemeKey property's value (destination) + * with the aid of another ThemeKey property (source). + * + * src: system:user_browser + * dst: system:user_browser_simplified + * + * @param $dummy + * string containing current value of ThemeKey property system:dummy + * + * @return + * current user's browser as simplified string + */ +function themekey_user_browser2user_browser_simplified($user_browser) { + return ThemekeyBrowserDetection::getBrowserSimplified($user_browser); +} + + +/** + * ThemeKey mapping function to set a + * ThemeKey property's value (destination) + * with the aid of another ThemeKey property (source). + * + * src: system:user_os + * dst: system:user_os_simplified + * + * @param $dummy + * string containing current value of ThemeKey property system:user_os + * + * @return + * current user's operating system as simplified string + */ +function themekey_user_os2user_os_simplified($user_os) { + return ThemekeyBrowserDetection::getOsSimplified($user_os); +} + + +/** + * ThemeKey mapping function to set a + * ThemeKey property's value (destination) + * with the aid of another ThemeKey property (source). + * + * src: system:dummy + * dst: system:session + * + * @param $dummy + * string containing current value of ThemeKey property system:dummy + * + * @return + * array of system:session values + * or NULL if no value could be mapped + */ +function themekey_dummy2session($dummy) { + $filtered_params = array(); + if (!empty($_SESSION)) { + foreach ($_SESSION as $key => $value) { + if (is_bool($value)) { + $filtered_params[] = $key . '=' . ($value) ? '1' : '0'; + } + elseif (!empty($value) && (is_numeric($value) || is_string($value))) { + $filtered_params[] = $key . '=' . $value; + } + else { + $filtered_params[] = $key; + } + } + } + return count($filtered_params) ? array_unique($filtered_params) : NULL; +} + + +/** + * ThemeKey mapping function to set a + * ThemeKey property's value (destination) + * with the aid of another ThemeKey property (source). + * + * src: system:dummy + * dst: system:day_of_week + * + * @param $dummy + * string containing current value of ThemeKey property system:dummy + * + * @return + * current day of the week as three letters from "Mon" through "Sun" + */ +function themekey_dummy2day_of_week($dummy) { + return format_date(time(), 'custom', 'D', variable_get('date_default_timezone', 0)); +} + + +/** +* ThemeKey mapping function to set a +* ThemeKey property's value (destination) +* with the aid of another ThemeKey property (source). +* +* src: system:dummy +* dst: system:day_of_month +* +* @param $dummy +* string containing current value of ThemeKey property system:dummy +* +* @return +* current day of the week as three letters from "Mon" through "Sun" +*/ +function themekey_dummy2day_of_month($dummy) { + return format_date(time(), 'custom', 'j', variable_get('date_default_timezone', 0)); +} + + diff --git a/sites/all/modules/themekey/modules/themekey.taxonomy.inc b/sites/all/modules/themekey/modules/themekey.taxonomy.inc new file mode 100644 index 0000000000000000000000000000000000000000..3ea19c25e7b5be86d7780237d3ed946644cc6783 --- /dev/null +++ b/sites/all/modules/themekey/modules/themekey.taxonomy.inc @@ -0,0 +1,177 @@ +<?php + +/** + * @file + * Provides some taxonomy stuff as ThemeKey properties. + * + * @author Markus Kalkbrenner | Cocomore AG + * @see http://drupal.org/user/124705 + * + * @author profix898 + * @see http://drupal.org/user/35192 + */ + + +/** + * Implements hook_themekey_properties(). + * + * Provides additional properties for the Themekey module: + * - taxonomy:vid + * - taxonomy:tid + * - taxonomy:tid_and_childs + * + * @return + * array of themekey properties and mapping functions + */ +function themekey_taxonomy_themekey_properties() { + // Attributes for properties ;) + $attributes = array(); + $attributes['taxonomy:vid'] = array( + 'description' => t('Taxonomy: Vocabulary - The vocabulary id (vid) of a taxonomy vocabulary. See !link for your vocabularies.', + array('!link' => l(t('!path', array('!path' => 'admin/structure/taxonomy')), 'admin/structure/taxonomy'))), + 'validator' => 'themekey_validator_ctype_digit', + 'page cache' => THEMEKEY_PAGECACHE_SUPPORTED, + ); + $attributes['taxonomy:tid'] = array( + 'description' => t('Taxonomy: Term - The term id (tid) of a taxonomy term.'), + 'validator' => 'themekey_validator_ctype_digit', + 'page cache' => THEMEKEY_PAGECACHE_SUPPORTED, + ); + $attributes['taxonomy:tid_and_childs'] = array( + 'description' => t('Taxonomy: Term and its childs - The term id (tid) of a taxonomy term. If set, all child terms of this term will be used too.'), + 'validator' => 'themekey_validator_ctype_digit', + 'page cache' => THEMEKEY_PAGECACHE_UNSUPPORTED, + ); + + // Mapping functions + $maps = array(); + $maps[] = array( + 'src' => 'taxonomy:tid', + 'dst' => 'taxonomy:vid', + 'callback' => 'themekey_taxonomy_tid2vid', + ); + $maps[] = array( + 'src' => 'taxonomy:tid', + 'dst' => 'taxonomy:tid_and_childs', + 'callback' => 'themekey_taxonomy_tid2tid_and_parents', + ); + $maps[] = array( + 'src' => 'node:nid', + 'dst' => 'taxonomy:tid', + 'callback' => 'themekey_taxonomy_nid2tid', + ); + + return array('attributes' => $attributes, 'maps' => $maps); +} + + +/** + * Implements hook_themekey_paths(). + */ +function themekey_taxonomy_themekey_paths() { + $paths = array(); + $paths[] = array('path' => 'taxonomy/term/#taxonomy:tid'); + + // Add support for 'forum' paths + if (module_exists('forum') && variable_get('themekey_module_forum_triggers_taxonomy_vid', 0)) { + $paths[] = array('path' => 'forum/#taxonomy:vid'); + } + + return $paths; +} + + +/** + * ThemeKey mapping function to set a + * ThemeKey property's value (destination) + * with the aid of another ThemeKey property (source). + * + * src: taxonomy:tid + * dst: taxonomy:vid + * + * @param $tids + * array of taxonomy term ids or a single term id + * + * @return + * array of taxonomy vocabulary ids + * or NULL if no value could be mapped + */ +function themekey_taxonomy_tid2vid($tids) { + $vid = array(); + // Use SQL instead taxonomy API because this code runs during hook_init() stage. + // Using taxonomy API will require to load the node using node_load() which is not allowed in this stage. + $tids = is_array($tids) ? $tids : array($tids); + foreach ($tids as $tid) { + $vid[] = db_select('taxonomy_term_data', 't') + ->fields('t', array('vid')) + ->condition('tid', $tid) + ->execute() + ->fetchField(); + } + + return count($vid) ? $vid : NULL; +} + + +/** + * ThemeKey mapping function to set a + * ThemeKey property's value (destination) + * with the aid of another ThemeKey property (source). + * + * src: node:nid + * dst: taxonomy:tid + * + * @param $nid + * node id + * + * @return + * array of taxonomy term ids + * or NULL if no value could be mapped + */ +function themekey_taxonomy_nid2tid($nid) { + $tids = array(); + + $query = db_select('taxonomy_index', 't'); + $query->addField('t', 'tid'); + $query->condition('t.nid', $nid); + $result = $query->execute(); + + if ($result->rowCount()) { + foreach ($result as $item) { + $tids[] = $item->tid; + } + } + + return count($tids) ? $tids : NULL; +} + + +/** + * ThemeKey mapping function to set a + * ThemeKey property's value (destination) + * with the aid of another ThemeKey property (source). + * + * src: taxonomy:tid + * dst: taxonomy:tid_and_parents + * + * @param $tids + * array of taxonomy term ids or a single term id + * + * @return + * array of taxonomy term ids + * or NULL if no value could be mapped + */ +function themekey_taxonomy_tid2tid_and_parents($tids) { + $tids = is_array($tids) ? $tids : array($tids); + foreach ($tids as $tid) { + // note that taxonomy_get_parents_all() returns the term itself + $parent_terms = taxonomy_get_parents_all($tid); + $parents = array(); + foreach ($parent_terms as $parent_term) { + $parents[] = $parent_term->tid; + } + } + + return count($parents) ? array_unique($parents) : NULL; +} + diff --git a/sites/all/modules/themekey/modules/themekey.user.inc b/sites/all/modules/themekey/modules/themekey.user.inc new file mode 100644 index 0000000000000000000000000000000000000000..164bd48347f4c889ea594c07ca772d5be4fe4e06 --- /dev/null +++ b/sites/all/modules/themekey/modules/themekey.user.inc @@ -0,0 +1,77 @@ +<?php + +/** + * @file + * Provides some user attributes as ThemeKey properties. + * + * @author Markus Kalkbrenner | Cocomore AG + * @see http://drupal.org/user/124705 + * + * @author profix898 + * @see http://drupal.org/user/35192 + */ + + +/** + * Implements hook_themekey_properties(). + * + * Provides additional properties for module ThemeKey: + * - user:hostname + * - user:language + * - user:name + * - user:uid + * + * @return + * array of themekey properties and mapping functions + */ +function themekey_user_themekey_properties() { + // Attributes for properties + $attributes = array(); + $attributes['user:hostname'] = array( + 'description' => t("User: Hostname - The user hostname property which is the IP address of client machine, adjusted for reverse proxy. Text from Drupal bootstrap.inc: + If Drupal is behind a reverse proxy, we use the X-Forwarded-For header instead of \$_SERVER['REMOTE_ADDR'], which would be the IP address of the proxy server, and not the client's."), + 'validator' => 'themekey_validator_http_host', + 'page cache' => THEMEKEY_PAGECACHE_UNSUPPORTED, + ); + $attributes['user:language'] = array( + 'description' => t('User: Language - The language the user has set in the settings of his profile page. + Format is the language code (for example "en" for English or "de" for German) that can be found on !link.', + array('!link' => l(t('!path', array('!path' => 'admin/config/regional/language/overview')), 'admin/config/regional/language/overview'))), + 'validator' => 'themekey_validator_language', + 'page cache' => THEMEKEY_PAGECACHE_SUPPORTED, + ); + $attributes['user:name'] = array( + 'description' => t('User: Name - The username of the user. See !link for your users.', array('!link' => l(t('!path', array('!path' => 'admin/people/people')), 'admin/people/people'))), + 'page cache' => THEMEKEY_PAGECACHE_SUPPORTED, + ); + $attributes['user:uid'] = array( + 'description' => t('User: ID - The id of the user (uid). The user id can be found in the URL of the users profile page, "user/23" or "user/23/edit" (23 = uid). See !link for your users.', + array('!link' => l(t('!path', array('!path' => 'admin/people/people')), 'admin/people/people'))), + 'validator' => 'themekey_validator_ctype_digit', + 'page cache' => THEMEKEY_PAGECACHE_SUPPORTED, + ); + $attributes['user:role'] = array( + 'description' => t("User: Role - Defined user roles (examples: 'anonymous user', 'authenticated user')"), + 'validator' => 'themekey_validator_role', + 'page cache' => THEMEKEY_PAGECACHE_SUPPORTED, + ); + + return array('attributes' => $attributes); +} + + +/** + * Implements hook_themekey_global(). + */ +function themekey_user_themekey_global() { + global $user; + + $parameters = array(); + $parameters['user:hostname'] = !empty($user->hostname) ? $user->hostname : NULL; + $parameters['user:language'] = !empty($user->language) ? $user->language : NULL; + $parameters['user:name'] = !empty($user->name) ? $user->name : NULL; + $parameters['user:uid'] = $user->uid; + $parameters['user:role'] = $user->roles; + + return $parameters; +} diff --git a/sites/all/modules/themekey/modules/themekey.views.inc b/sites/all/modules/themekey/modules/themekey.views.inc new file mode 100644 index 0000000000000000000000000000000000000000..bb6a00cc3319a14e988e92aeb7fd9ce813a82c99 --- /dev/null +++ b/sites/all/modules/themekey/modules/themekey.views.inc @@ -0,0 +1,60 @@ +<?php + +/** + * @file themekey.views.inc + * Provides some views properties + */ + +function themekey_views_themekey_properties() { + // Attributes of properties + $attributes = array(); + + if (module_exists('views')) { + + $attributes['views:vid'] = array( + 'description' => t('Views: VID - The vid of a view (vid)'), + 'validator' => 'themekey_validator_ctype_digit', + 'page cache' => THEMEKEY_PAGECACHE_SUPPORTED, + ); + + // Mapping functions + $maps = array(); + $maps[] = array( + 'src' => 'drupal:get_q', + 'dst' => 'views:vid', + 'callback' => 'themekey_views_vid2getq', + ); + + $return = array('attributes' => $attributes, 'maps' => $maps); + + } + + return $return; +} + + +/** + * get the views id (vid) by given path + * @param unknown_type $get_q + * @param unknown_type $parameters + */ +function themekey_views_vid2getq($get_q, $parameters) { + + $vid = NULL; + + $all_views = views_get_all_views(); + + if (!empty($all_views)) { + foreach ($all_views as $views_name => $view) { + + // IF VIEW HAS A PATH WHICH IS EQUAL + if ($view->get_url() == $get_q) { + $vid = $view->vid; + break; + } + + } + } + + return $vid; +} \ No newline at end of file diff --git a/sites/all/modules/themekey/modules/themekey_browser_detection.php b/sites/all/modules/themekey/modules/themekey_browser_detection.php new file mode 100644 index 0000000000000000000000000000000000000000..1ecfd1b22982f43eaf7a741f207bd762d5967a8e --- /dev/null +++ b/sites/all/modules/themekey/modules/themekey_browser_detection.php @@ -0,0 +1,245 @@ +<?php + +/** + * @file + * Derived from Browser Detection Class by Dragan Dinic <dragan@dinke.net> + * and modified to fit the needs of ThemeKey Properties. + * @see http://www.dinke.net/blog/2005/10/30/browser-detection/en/ + * @see themekey_properties.module + * + * Here's a snippet of Dragans mail response as we asked him to reuse his code: + * + * > Hello Markus, + * > + * > Thank you very much for contacting me. You can use Browser detection + * > class in any project, no matter of its purpose (commercial or non + * > commerical). + * > + * > I wasnt' sure which license allows that freedom of use (perhaps BSD + * > license), if you know more about that please let me know. + * > + * > Best Regards, + * > Dragan + * + * @author Markus Kalkbrenner | Cocomore AG + * @see http://drupal.org/user/124705 + */ + +/** + * Browser Detection class + * contains common static method for + * getting browser version and OS + * + * It supports most popular browsers (IE, FF, Safari, Opera, Chrome ...), + * as well as some not-so-popular (lynx etc.) + * It doesn't recognize bots (like google, yahoo etc) + * + * usage + * <code> + * $browser = ThemekeyBrowserDetection::getBrowser($_SERVER['HTTP_USER_AGENT']); + * $os = Browser_Detection::getOs($_SERVER['HTTP_USER_AGENT']); + * </code> + * @access public + */ +class ThemekeyBrowserDetection { + + /** + * Get browser name and version + * @param string user agent + * @return string browser name and version or 'unknown' if unrecognized + * @static + * @access public + */ + function getBrowser($useragent) { + // check for most popular browsers first + // unfortunately, that's IE. We also ignore Opera and Netscape 8 + // because they sometimes send msie agent + if (strpos($useragent, 'MSIE') !== FALSE && strpos($useragent, 'Opera') === FALSE && strpos($useragent, 'Netscape') === FALSE) { + //deal with Blazer + if (preg_match("/Blazer\/([0-9]{1}\.[0-9]{1}(\.[0-9])?)/", $useragent, $matches)) { + return 'Blazer ' . $matches[1]; + } + //deal with IE + if (preg_match("/MSIE ([0-9]{1}\.[0-9]{1,2})/", $useragent, $matches)) { + return 'Internet Explorer ' . $matches[1]; + } + } + elseif (strpos($useragent, 'Gecko')) { + //deal with Gecko based + + //if firefox + if (preg_match("/Firefox\/([0-9]{1,2}\.[0-9]{1,2}(\.[0-9]{1,2})?)/", $useragent, $matches)) { + return 'Mozilla Firefox ' . $matches[1]; + } + + //if Netscape (based on gecko) + if (preg_match("/Netscape\/([0-9]{1}\.[0-9]{1}(\.[0-9])?)/", $useragent, $matches)) { + return 'Netscape ' . $matches[1]; + } + + //check chrome before safari because chrome agent contains both + if (preg_match("/Chrome\/([^\s]+)/", $useragent, $matches)) { + return 'Google Chrome ' . $matches[1]; + } + + //if Safari (based on gecko) + if (preg_match("/Safari\/([0-9]{2,3}(\.[0-9])?)/", $useragent, $matches)) { + return 'Safari ' . $matches[1]; + } + + //if Galeon (based on gecko) + if (preg_match("/Galeon\/([0-9]{1}\.[0-9]{1}(\.[0-9])?)/", $useragent, $matches)) { + return 'Galeon ' . $matches[1]; + } + + //if Konqueror (based on gecko) + if (preg_match("/Konqueror\/([0-9]{1}\.[0-9]{1}(\.[0-9])?)/", $useragent, $matches)) { + return 'Konqueror ' . $matches[1]; + } + + // if Fennec (based on gecko) + if (preg_match("/Fennec\/([0-9]{1}\.[0-9]{1}(\.[0-9])?)/", $useragent, $matches)) { + return 'Fennec' . $matches[1]; + } + + // if Maemo (based on gecko) + if (preg_match("/Maemo\/([0-9]{1}\.[0-9]{1}(\.[0-9])?)/", $useragent, $matches)) { + return 'Maemo' . $matches[1]; + } + + //no specific Gecko found + //return generic Gecko + return 'Gecko based'; + } + elseif (strpos($useragent, 'Opera') !== FALSE) { + //deal with Opera + if (preg_match("/Opera[\/ ]([0-9]{1}\.[0-9]{1}([0-9])?)/", $useragent, $matches)) { + return 'Opera ' . $matches[1]; + } + } + elseif (strpos($useragent, 'Lynx') !== FALSE) { + //deal with Lynx + if (preg_match("/Lynx\/([0-9]{1}\.[0-9]{1}(\.[0-9])?)/", $useragent, $matches)) { + return 'Lynx ' . $matches[1]; + } + } + elseif (strpos($useragent, 'Netscape') !== FALSE) { + //NN8 with IE string + if (preg_match("/Netscape\/([0-9]{1}\.[0-9]{1}(\.[0-9])?)/", $useragent, $matches)) { + return 'Netscape ' . $matches[1]; + } + } + else { + //unrecognized, this should be less than 1% of browsers (not counting bots like google etc)! + return 'unknown'; + } + } + + /** + * Get browsername simplified + * @param string browser + * @return string browser name or 'unknown' if unrecognized + * @static + * @access public + */ + function getBrowserSimplified($browser) { + return trim(preg_replace('/[0-9.]/', '', $browser)); + } + + /** + * Get operating system + * @param string user agent + * @return string os name and version or 'unknown' in unrecognized os + * @static + * @access public + */ + function getOs($useragent) { + $useragent = drupal_strtolower($useragent); + + //check for (aaargh) most popular first + //winxp + if (strpos($useragent, 'windows nt 5.1') !== FALSE) { + return 'Windows XP'; + } + elseif (strpos($useragent, 'windows nt 6.1') !== FALSE) { + return 'Windows 7'; + } + elseif (strpos($useragent, 'windows nt 6.0') !== FALSE) { + return 'Windows Vista'; + } + elseif (strpos($useragent, 'windows 98') !== FALSE) { + return 'Windows 98'; + } + elseif (strpos($useragent, 'windows nt 5.0') !== FALSE) { + return 'Windows 2000'; + } + elseif (strpos($useragent, 'windows nt 5.2') !== FALSE) { + return 'Windows 2003 server'; + } + elseif (strpos($useragent, 'windows nt') !== FALSE) { + return 'Windows NT'; + } + elseif (strpos($useragent, 'win 9x 4.90') !== FALSE && strpos($useragent, 'win me')) { + return 'Windows ME'; + } + elseif (strpos($useragent, 'win ce') !== FALSE) { + return 'Windows CE'; + } + elseif (strpos($useragent, 'win 9x 4.90') !== FALSE) { + return 'Windows ME'; + } + elseif (strpos($useragent, 'iphone') !== FALSE) { + return 'iPhone'; + } + // experimental + elseif (strpos($useragent, 'ipad') !== FALSE) { + return 'iPad'; + } + elseif (strpos($useragent, 'webos') !== FALSE) { + return 'webOS'; + } + elseif (strpos($useragent, 'symbian') !== FALSE) { + return 'Symbian'; + } + elseif (strpos($useragent, 'android') !== FALSE) { + return 'Android'; + } + elseif (strpos($useragent, 'blackberry') !== FALSE) { + return 'Blackberry'; + } + elseif (strpos($useragent, 'mac os x') !== FALSE) { + return 'Mac OS X'; + } + elseif (strpos($useragent, 'macintosh') !== FALSE) { + return 'Macintosh'; + } + elseif (strpos($useragent, 'linux') !== FALSE) { + return 'Linux'; + } + elseif (strpos($useragent, 'freebsd') !== FALSE) { + return 'Free BSD'; + } + elseif (strpos($useragent, 'symbian') !== FALSE) { + return 'Symbian'; + } + else { + return 'unknown'; + } + } + + /** + * Get operating system simplified + * @param string os + * @return string os name or 'unknown' in unrecognized os + * @static + * @access public + */ + function getOsSimplified($os) { + if (strpos($os, 'Windows') !== FALSE) { + return 'Windows'; + } + else { + return $os; + } + } +} diff --git a/sites/all/modules/themekey/tests/ThemekeyDrupalPropertiesTestCase.test b/sites/all/modules/themekey/tests/ThemekeyDrupalPropertiesTestCase.test new file mode 100644 index 0000000000000000000000000000000000000000..9bcc9569391b2910b10e608639964b93bf5779d8 --- /dev/null +++ b/sites/all/modules/themekey/tests/ThemekeyDrupalPropertiesTestCase.test @@ -0,0 +1,21 @@ +<?php + + +/** + * @file + * Implements tests for the theme switching rules. + */ + +class ThemekeyDrupalPropertiesTestCase extends ThemekeyWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Themekey Drupal Properties', + 'description' => 'Test the themekey node properties (drupal:).', + 'group' => 'Themekey', + ); + } + + public function testPropertyDrupalIsFrontPage() { + $this->simplePropertyTest('drupal:is_front_page', '=', 'true'); + } +} diff --git a/sites/all/modules/themekey/tests/ThemekeyMultipleNodePropertiesTestCase.test b/sites/all/modules/themekey/tests/ThemekeyMultipleNodePropertiesTestCase.test new file mode 100644 index 0000000000000000000000000000000000000000..0b9b770c9c4e419117cb1b0823da4a263f2972e6 --- /dev/null +++ b/sites/all/modules/themekey/tests/ThemekeyMultipleNodePropertiesTestCase.test @@ -0,0 +1,108 @@ +<?php + + +/** + * @file + * Implements tests for the theme switching rules. + */ + +class ThemekeyMultipleNodePropertiesTestCase extends ThemekeyWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Themekey Multiple Node Properties', + 'description' => 'Test multiple themekey node properties (node:).', + 'group' => 'Themekey', + ); + } + + + public function testMultipleProperties() { + $node = $this->drupalCreateNode(array('title' => 'Node title test', 'uid' => 1)); + + $propertiesArray = array(); + + // NODE:TITLE + $propertiesArray[] = array( + 'property' => 'node:title', + 'operator' => '=', + 'value' => 'Node title test', + 'theme' => 'garland', + 'url' => array('path' => 'node/' . $node->nid), + ); + + // node:uid + $propertiesArray[] = array( + 'property' => 'node:uid', + 'operator' => '=', + 'value' => '1', + 'theme' => 'garland', + 'url' => array('path' => 'node/' . $node->nid), + ); + + // CHECK DEFAULT THEME = BARTIK + // load page + $this->drupalGet('node/' . $node->nid, array()); + // theme is bartik + $this->assertTheme('bartik'); + + // CREATE RULES + if (!empty($propertiesArray)) { + foreach ($propertiesArray as $key => $propertyArray) { + // create ThemeKey Rule + $this->addThemeKeyRule($propertyArray['property'], $propertyArray['operator'], $propertyArray['value'], $propertyArray['theme']); + } + } + + module_load_include('inc', 'themekey', 'themekey_build'); + $rules = themekey_load_rules(); + + if (!empty($rules)) { + $last_rule = array(); + $cntr = 0; + foreach ($rules as $key => $rule) { + if ($key > 1) { + $cntr++; + $rule['parent'] = $last_rule['id']; + $rule['depth'] = $cntr; + debug($rule); + themekey_rule_set($rule); + debug($rule); + } + + $last_rule = $rule; + } + } + + + // TODO RULES ARE NOT SAVED CORRECTLY - DEPTH AND PARENT ARE NOT SET FOR SECOND RULE !! + + cache_clear_all('*', 'cache_page', TRUE); + themekey_rebuild(); + + $rules = themekey_load_rules(); + + debug($rules, 'Testing Rules'); + + + // CHECK RULES +// if (!empty($propertiesArray)) { +// +// foreach ($propertiesArray as $key => $propertyArray) { +// if (empty($propertyArray['url']['path'])) { +// $propertyArray['path'] = '<front>'; +// } +// if (empty($propertyArray['url']['options'])) { +// $propertyArray['url']['options'] = array(); +// } +// +// // load page +// $this->drupalGet($propertyArray['url']['path'], $propertyArray['url']['options']); +// // theme is garland +// $this->assertTheme('garland'); +// } +// +// } + + + } +} diff --git a/sites/all/modules/themekey/tests/ThemekeyNodePropertiesTestCase.test b/sites/all/modules/themekey/tests/ThemekeyNodePropertiesTestCase.test new file mode 100644 index 0000000000000000000000000000000000000000..9528c770abf100312abc1268a5a7f2beddab0528 --- /dev/null +++ b/sites/all/modules/themekey/tests/ThemekeyNodePropertiesTestCase.test @@ -0,0 +1,75 @@ +<?php + + +/** + * @file + * Implements tests for the theme switching rules. + */ + +class ThemekeyNodePropertiesTestCase extends ThemekeyWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Themekey Node Properties', + 'description' => 'Test the themekey node properties (node:).', + 'group' => 'Themekey', + ); + } + + public function testPropertyNodeChanged() { + $node = $this->drupalCreateNode(); + $this->simplePropertyTest('node:changed', '=', $node->changed, array('path' => 'node/' . $node->nid)); + } + + public function testPropertyNodeCreated() { + $node = $this->drupalCreateNode(); + $this->simplePropertyTest('node:created', '=', $node->changed, array('path' => 'node/' . $node->nid)); + } + + public function testPropertyNodeLanguage() { + $node = $this->drupalCreateNode(array('language' => LANGUAGE_NONE)); + $this->simplePropertyTest('node:language', '=', 'neutral', array('path' => 'node/' . $node->nid)); + } + +// public function testPropertyNodeLanguageEnglish() { +// $node = $this->drupalCreateNode(array('language' => 'en')); +// $this->simplePropertyTest('node:language', '=', 'en', array('path' => 'node/' . $node->nid)); +// } + + public function testPropertyNodeNid() { + $node = $this->drupalCreateNode(); + $this->simplePropertyTest('node:nid', '=', $node->nid, array('path' => 'node/' . $node->nid)); + } + + public function testPropertyNodeVid() { + $node = $this->drupalCreateNode(); + $node->revision = TRUE; + node_save($node); + $this->simplePropertyTest('node:vid', '=', $node->vid, array('path' => 'node/' . $node->nid . '/revision/' . $node->vid . '/view')); + } + + public function testPropertyNodePromote() { + $node = $this->drupalCreateNode(array('promote' => TRUE)); + $this->simplePropertyTest('node:promote', '=', 1, array('path' => 'node/' . $node->nid)); + } + + public function testPropertyNodeSticky() { + $node = $this->drupalCreateNode(array('sticky' => TRUE)); + $this->simplePropertyTest('node:sticky', '=', 1, array('path' => 'node/' . $node->nid)); + } + + public function testPropertyNodeType() { + $node = $this->drupalCreateNode(array('type' => 'page')); + $this->simplePropertyTest('node:type', '=', 'page', array('path' => 'node/' . $node->nid)); + } + + public function testPropertyNodeUid() { + $node = $this->drupalCreateNode(array('uid' => '1')); + $this->simplePropertyTest('node:uid', '=', '1', array('path' => 'node/' . $node->nid)); + } + + public function testPropertyNodeTitle() { + $node = $this->drupalCreateNode(array('title' => 'Node title test')); + $this->simplePropertyTest('node:title', '=', 'Node title test', array('path' => 'node/' . $node->nid)); + } + +} diff --git a/sites/all/modules/themekey/tests/ThemekeySystemPropertiesTestCase.test b/sites/all/modules/themekey/tests/ThemekeySystemPropertiesTestCase.test new file mode 100644 index 0000000000000000000000000000000000000000..6ba5e8b473f41cbaedc9c1b66f329e7a53a542db --- /dev/null +++ b/sites/all/modules/themekey/tests/ThemekeySystemPropertiesTestCase.test @@ -0,0 +1,45 @@ +<?php + + +/** + * @file + * Implements tests for the theme switching rules. + */ + +class ThemekeySystemPropertiesTestCase extends ThemekeyWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Themekey System Properties', + 'description' => 'Test the themekey system properties (system:).', + 'group' => 'Themekey', + ); + } + + public function testPropertySystemDummy() { + $this->simplePropertyTest('system:dummy', '=', 'dummy'); + } + + public function testPropertySystemQuery_Param() { + $url = array( + 'options' => array( + 'query' => array( + 'test' => TRUE, + ), + ), + ); + + $this->simplePropertyTest('system:query_param', '=', 'test=1', $url); + } + + public function testPropertySystemQuery_String() { + $url = array( + 'options' => array( + 'query' => array( + 'test' => TRUE, + ), + ), + ); + + $this->simplePropertyTest('system:query_string', '=', 'test=1', $url); + } +} diff --git a/sites/all/modules/themekey/tests/themekey.test b/sites/all/modules/themekey/tests/themekey.test new file mode 100644 index 0000000000000000000000000000000000000000..247ae266fbca2b79822b9ed06afcbad21ede19bc --- /dev/null +++ b/sites/all/modules/themekey/tests/themekey.test @@ -0,0 +1,103 @@ +<?php + + +/** + * @file + * Implements tests for the theme switching rules. + */ + + +class ThemekeyWebTestCase extends DrupalWebTestCase { + protected $privileged_user; + + public static function getInfo() { + return array( + 'name' => 'Themekey test', + 'description' => 'Test the themekey module.', + 'group' => 'Themekey', + ); + } + + public function setUp() { + parent::setUp('themekey'); + + $this->privileged_user = $this->drupalCreateUser(array('administer theme assignments', 'administer themekey settings')); + $this->drupalLogin($this->privileged_user); + theme_enable(array('garland')); + } + + public function simplePropertyTest($property, $operator, $value, $url = array()) { + debug($property . $operator . $value, 'Testing Rule'); + + $url += array( + 'path' => '<front>', + 'options' => array(), + ); + + // load page + $this->drupalGet($url['path'], $url['options']); + // theme is bartik + $this->assertTheme('bartik'); + // create ThemeKey Rule + $this->addThemeKeyRule($property, $operator, $value, 'garland'); + // load page + $this->drupalGet($url['path'], $url['options']); + // theme is garland + $this->assertTheme('garland'); + } + + + /** + * add multiple properties + * @param array $properties + * - property: the property + * - operator: the operator for the rule + * - value: the value + * - url: the url to call (array) + */ + public function multiplePropertyTest($properties_array) { + + if (!empty($properties_array)) { + foreach ($properties_array as $key => $property) { + + if (empty($property['url']['path'])) { + $property['path'] = '<front>'; + } + if (empty($property['url']['options'])) { + $property['url']['options'] = array(); + } + + debug($property['property'] . $property['operator'], $property['value']); + + // load page + $this->drupalGet($property['url']['path'], $property['url']['options']); + // theme is bartik + $this->assertTheme('bartik'); + // create ThemeKey Rule + $this->addThemeKeyRule($property['property'], $property['operator'], $property['value'], 'garland'); + // load page + $this->drupalGet($property['url']['path'], $property['url']['options']); + // theme is garland + $this->assertTheme('garland'); + + } + } + + } + + public function assertTheme($theme) { + $this->assertRaw('themes/' . $theme, 'current theme is ' . $theme); + } + + public function addThemeKeyRule($property, $operator, $value, $theme, $enabled = '1', $wildcard = '') { + $edit = array( + 'new_item[property]' => $property, + 'new_item[wildcard]' => $wildcard, + 'new_item[operator]' => $operator, + 'new_item[value]' => $value, + 'new_item[theme]' => $theme, + 'new_item[enabled]' => $enabled, + ); + $this->drupalPost('admin/config/user-interface/themekey/properties', $edit, t('Save configuration')); + } +} \ No newline at end of file diff --git a/sites/all/modules/themekey/themekey-debug-messages.tpl.php b/sites/all/modules/themekey/themekey-debug-messages.tpl.php new file mode 100644 index 0000000000000000000000000000000000000000..6cfa8929f85319caf70fcb2a3b8b44e2f956f1ab --- /dev/null +++ b/sites/all/modules/themekey/themekey-debug-messages.tpl.php @@ -0,0 +1,13 @@ +<?php + +/** + * @file + * template to format ThemeKey Debug Messages + */ +?> +<table border="1" style="color:black;" bgcolor="white"> + <tr><th><?php print t('ThemeKey Debug Messages'); ?></th></tr> + <?php foreach ($messages as $message) {?> + <tr><td><?php print $message; ?></td></tr> + <?php } ?> +</table> diff --git a/sites/all/modules/themekey/themekey.info b/sites/all/modules/themekey/themekey.info new file mode 100644 index 0000000000000000000000000000000000000000..40c5c55edd5887d921b053e7a51f23d7c50b55b4 --- /dev/null +++ b/sites/all/modules/themekey/themekey.info @@ -0,0 +1,27 @@ + +name = "ThemeKey" +description = "Map themes to Drupal paths or object properties." +core = 7.x +package = ThemeKey +configure = admin/config/user-interface/themekey/settings + +files[] = themekey.install +files[] = themekey-debug-messages.tpl.php +files[] = themekey_validators.inc +files[] = themekey_admin.inc +files[] = themekey_base.inc +files[] = themekey_build.inc +files[] = themekey_cron.inc +files[] = modules/themekey_browser_detection.php +files[] = tests/themekey.test +files[] = tests/ThemekeyDrupalPropertiesTestCase.test +files[] = tests/ThemekeyNodePropertiesTestCase.test +files[] = tests/ThemekeyMultipleNodePropertiesTestCase.test +files[] = tests/ThemekeySystemPropertiesTestCase.test + +; Information added by drupal.org packaging script on 2011-08-17 +version = "7.x-1.4" +core = "7.x" +project = "themekey" +datestamp = "1313612519" + diff --git a/sites/all/modules/themekey/themekey.install b/sites/all/modules/themekey/themekey.install new file mode 100644 index 0000000000000000000000000000000000000000..314422ebd0508746c886546301062d13109af9af --- /dev/null +++ b/sites/all/modules/themekey/themekey.install @@ -0,0 +1,540 @@ +<?php + +/** + * @file + * Database schema of + * @see themekey.module + * + * @author Markus Kalkbrenner | Cocomore AG + * @see http://drupal.org/user/124705 + * + * @author profix898 + * @see http://drupal.org/user/35192 + */ + + +/** + * Implements hook_schema(). + */ +function themekey_schema() { + $schema = array(); + $schema['themekey_properties'] = array( + 'fields' => array( + 'id' => array( + 'type' => 'serial', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'property' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'operator' => array( + 'type' => 'varchar', + 'length' => 2, + 'not null' => TRUE, + 'default' => '=', + ), + 'value' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'weight' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'theme' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'enabled' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'wildcards' => array( + 'type' => 'text', + 'not null' => TRUE, + 'size' => 'big', + 'serialize' => TRUE, + ), + 'parent' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + ), + 'primary key' => array('id'), + 'indexes' => array( + 'enabled_parent_weight' => array('enabled', 'parent', 'weight'), + 'parent_weight' => array('parent', 'weight'), + ), + ); + + return $schema; +} + + +/** + * Implements hook_install(). + */ +function themekey_install() { + // Define the page caching constants for installation. + themekey_define_constants(); +} + + +/** + * Implements hook_uninstall(). + */ +function themekey_uninstall() { + // Remove variables + db_delete('variable') + ->condition('name', 'themekey_%%', 'LIKE') + ->execute(); + cache_clear_all('variables', 'cache'); +} + + +/** + * Implements hook_update_N(). + * + * Update property 'nid' to 'node:nid' + */ +function themekey_update_6001() { + + $result = db_query('SELECT * FROM {themekey_properties} WHERE property = :property', array(':property' => 'nid')); + + foreach ($result as $item) { + if (db_query('SELECT COUNT(id) FROM {themekey_properties} WHERE property = :property AND value = :value', array(':property' => 'node:nid', ':value' => $item->value))->fetchField() > 0) { + db_delete('themekey_properties') + ->condition('id', $item->id, '=') + ->execute(); + } + else { + $num_updated = db_update('themekey_properties') + ->fields(array( + 'property' => 'node:nid', + )) + ->condition('id', $item->id, '=') + ->execute(); + } + } + + return t('Updated property "nid" to "node:nid"'); +} + + +/** + * Implements hook_update_N(). + */ +function themekey_update_6100() { + + $properties = variable_get('themekey_properties', array()); + + foreach ($properties as $key => &$property) { + if (array_key_exists('path', $property) && $key === $property['path']) { + $property['path'] = FALSE; + } + } + + variable_set('themekey_properties', $properties); + + return t('Updated variable themekey_properties:path.'); +} + + +/** + * Implements hook_update_N(). + */ +function themekey_update_6101() { + $num_updated = db_update('system') + ->fields(array( + 'weight' => 0, + )) + ->condition('name', 'themekey', '=') + ->execute(); + + return t('Updated system weight to 0 for themekey.'); +} + + +/** + * Implements hook_update_N(). + */ +function themekey_update_6102() { + if (module_exists('forum')) { + variable_set('themekey_module_forum_triggers_taxonomy_vid', 1); + } + + if (module_exists('taxonomy_menu')) { + variable_set('themekey_module_taxonomy_menu_triggers_taxonomy_tid', 1); + } + + return t('Set variables "themekey_module_forum_triggers_taxonomy_vid" and "themekey_module_taxonomy_menu_triggers_taxonomy_tid" with value 1'); +} + + +/** + * Implements hook_update_N(). + */ +function themekey_update_6103() { + variable_del('themekey_nodediscover'); + return t('Deleted variable themekey_nodediscover.'); +} + + +/** + * Implements hook_update_N(). + */ +function themekey_update_6104() { + db_drop_field('themekey_properties', 'callbacks'); + return t('Dropped field callbacks.'); +} + + +/** + * Implements hook_update_N(). + */ +function themekey_update_6105() { + global $db_type; + + $return = array(); + + // we need to handle upgrade of module ThemeKey UI here because + // it will fail when triggered at themekey_ui.install after + // ThemeKey upgrade from 6.x-1.1 to 6.x.2.0 + if (module_exists('themekey_ui')) { + $schema_version = drupal_get_installed_schema_version('themekey_ui'); + if (6100 > $schema_version) { + $return = drupal_install_schema('themekey_ui'); + + if (!variable_get('themekey_nodeaspath', 0)) { + $sql = ''; + if (0 === strpos($db_type, 'mysql')) { + $sql = "SELECT id, value, theme, nid, vid FROM {themekey_properties} JOIN {node_revisions} ON (value = nid) WHERE property = :property AND conditions = :conditions"; + } + elseif (0 === strpos($db_type, 'pqsql')) { + $sql = "SELECT id, value, theme, nid, vid FROM {themekey_properties} JOIN {node_revisions} ON (value = nid::character varying) WHERE property = :property AND conditions = :conditions"; + } + if ($result = db_query($sql, array(':property' => 'node:nid', ':conditions' => 'a:0:{}'))) { + + + foreach ($result as $row) { + + $return['INSERT']['success'] = db_insert('themekey_ui_node_theme') // Table name no longer needs {} + ->fields(array( + 'nid' => $row->nid, + 'vid' => $row->vid, + 'theme' => $row->theme, + )) + ->execute(); + + if ($return['INSERT']['success']) { + $return['DELETE']['success'] = db_delete('themekey_properties') + ->condition('id', $row->id) + ->execute(); + if (!($return['DELETE']['success'])) { + break; + } + } + else { + $return['INSERT']['success'] = FALSE; + break; + } + } + + $return = t('Themekey UI was successfully updated'); + + } + else { + $return = t('Update of Themekey UI failed.'); + } + } + + variable_del('themekey_nodeaspath'); + } + } + + return $return; +} + + +/** + * Function _themekey_properties_explode_conditions() + * converts conditions formatted as string into an array. + * It was part of themekey_build.inc up to version 6.x-1.2. + * Now it's only required one more time to perform + * themekey_update_6200() + * + * @param $conditions + * ThemeKey conditions as string + * + * @return + * ThemeKey conditions as array + */ +function _themekey_properties_explode_conditions($conditions) { + if (!is_array($conditions)) { + $parts = array_filter(explode(';', $conditions)); + $conditions = array(); + foreach ($parts as $part) { + $part = trim($part); + if (preg_match('/(.*?)([<>=!~])(.*)/', $part, $matches)) { + $conditions[] = array( + 'property' => trim($matches[1]), + 'operator' => trim($matches[2]), + 'value' => trim($matches[3]), + ); + } + } + } + + return $conditions; +} + + +/** + * Implements hook_update_N(). + */ +function themekey_update_6200() { + $field = array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => '0', + 'initial' => '1', + ); + db_add_field('themekey_properties', 'enabled', $field); + + db_drop_index('themekey_properties', 'property'); + db_add_index('themekey_properties', 'enabled', array('enabled')); + + $field = array( + 'type' => 'text', + 'not null' => TRUE, + ); + db_add_field('themekey_properties', 'wildcards', $field); + + $weight = ((int) db_query("SELECT MIN(weight) FROM {themekey_properties}")->fetchField()) - 1; + + $result = db_query("SELECT * FROM {themekey_paths} WHERE custom = :custom", array(':custom' => 1)); + + foreach ($result as $item) { + + $conditions = unserialize($item->conditions); + if (is_array($conditions) && !empty($conditions)) { + if (!is_array($conditions[0])) { + // ThemeKey 6.x-1.1 stored conditions for paths as simple string within an array + $conditions = _themekey_properties_explode_conditions($conditions[0]); + } + } + + $insert_success = db_insert('themekey_properties') // Table name no longer needs {} + ->fields(array( + 'property' => 'drupal:path', + 'value' => $item->path, + 'weight' => $weight, + 'conditions' => serialize($conditions), + 'theme' => $item->theme, + 'enabled' => 1, + 'wildcards' => $item->wildcards, + )) + ->execute(); + + $num_deleted = db_delete('themekey_paths') + ->condition('id', $item->id) + ->execute(); + + } + + db_drop_field('themekey_paths', 'conditions'); + db_drop_field('themekey_paths', 'custom'); + db_drop_field('themekey_paths', 'theme'); + + return t('Updated Themekey properties.'); +} + + +/** + * Implements hook_update_N(). + */ +function themekey_update_6201() { + // Don't rebuild anymore when themekey_update_6202() will run + //themekey_rebuild(); + return ''; +} + + +/** + * Implements hook_update_N(). + */ +function themekey_update_6202() { + $ret = array(); + + $field_operator = array( + 'type' => 'varchar', + 'length' => 2, + 'not null' => TRUE, + 'default' => '=', + 'initial' => '=', + ); + db_add_field('themekey_properties', 'operator', $field_operator); + + $field_parent = array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + 'initial' => 0, + ); + db_add_field('themekey_properties', 'parent', $field_parent); + + if ($result = db_query("SELECT * FROM {themekey_properties} WHERE conditions <> :conditions", array(':conditions' => serialize(array())))) { + + foreach ($result as $row) { + + $conditions = unserialize($row->conditions); + if (is_array($conditions)) { + $parent = $row->id; + foreach ($conditions as $condition) { + $insert_result = db_insert('themekey_properties') // Table name no longer needs {} + ->fields(array( + 'property' => $condition['property'], + 'operator' => $condition['operator'], + 'value' => $condition['value'], + 'weight' => 0, + 'theme' => $row->theme, + 'enabled' => $row->enabled, + 'wildcards' => serialize(array()), + 'parent' => $parent, + )) + ->execute(); + + if ($insert_result) { + $parent = db_last_insert_id('themekey_properties', 'id'); // TODO Wofür ist das gut? + } + + } + } + + } + + } + + db_drop_field('themekey_properties', 'conditions'); + db_drop_index('themekey_properties', 'enabled'); + db_drop_index('themekey_properties', 'weight'); + db_add_index('themekey_properties', 'enabled_parent_weight', array('enabled', 'parent', 'weight')); + db_add_index('themekey_properties', 'parent_weight', array('parent', 'weight')); + + db_drop_index('themekey_paths', 'path'); + db_drop_index('themekey_paths', 'fit'); + db_drop_index('themekey_paths', 'weight'); + db_add_index('themekey_paths', 'path_fit_weight', array('path', 'fit', 'weight')); + + return t('Updated themekey properties.'); +} + + +/** + * Implements hook_update_N(). + */ +function themekey_update_6203() { + // moved themekey_rebuild() to themekey_update_6300() + return ''; +} + + +/** + * Implements hook_update_N(). + */ +function themekey_update_6300() { + // moved themekey_rebuild() to themekey_update_6301() + + db_drop_table('themekey_paths'); + return t('Update of Themekey ran successfully'); +} + + +/** + * Implements hook_update_N(). + */ +function themekey_update_6301() { + // moved themekey_rebuild() to themekey_update_7101() + return ''; +} + +/** + * Implements hook_update_N(). + */ +function themekey_update_7100() { + // cleanup for users of older versions of the obsolete module themekey_properties + $attributes = array( + 'system:query_param', + 'system:query_string', + 'system:cookie', + 'system:server_ip', + 'system:server_port', + 'system:server_name', + 'system:https', + 'system:remote_ip', + 'system:referer', + 'system:user_agent', + 'system:user_browser', + 'system:user_browser_simplified', + 'system:user_os', + 'system:user_os_simplified', + 'system:date_time', + 'system:date', + 'system:time', + 'system:dummy', + 'drupal:base_path', + 'drupal:is_front_page', + 'user:role', + ); + $properties = variable_get('themekey_properties', array()); + foreach ($attributes as $attribute) { + if (array_key_exists($attribute, $properties)) { + $properties[$attribute]['path'] = FALSE; + } + } + variable_set('themekey_properties', $properties); + + // cleanup for users of older versions of the obsolete module themekey_properties + variable_del('themekey_properties_debug_show_values'); + + return t('Update of Themekey ran successfully'); +} + +/** + * Implements hook_update_N(). + */ +function themekey_update_7101() { + // Define the page caching constants for the update. + themekey_define_constants(); + + module_load_include('inc', 'themekey', 'themekey_base'); + module_load_include('inc', 'themekey', 'themekey_build'); + themekey_rebuild(); + + return t('Update of Themekey ran successfully'); +} + +/** + * Implements hook_update_N(). + */ +function themekey_update_7102() { + variable_del('themekey_override_custom_theme'); + + return t('Update of Themekey ran successfully'); +} + diff --git a/sites/all/modules/themekey/themekey.module b/sites/all/modules/themekey/themekey.module new file mode 100644 index 0000000000000000000000000000000000000000..d96323d840edb23a0ceb2cc6939a0f78cf414d4f --- /dev/null +++ b/sites/all/modules/themekey/themekey.module @@ -0,0 +1,339 @@ +<?php + +/** + * @file + * ThemeKey is designed as a generic theme-switching module. + * + * ThemeKey allows you to define simple or sophisticated Theme Switching Rules. + * Using these rules you are able to use a different theme depending on current + * path, taxonomy terms, language, node type and many, many more properties. + * It can also be easily extended to support additional properties as exposed by + * other modules. + * + * @author Markus Kalkbrenner | Cocomore AG + * @see http://drupal.org/user/124705 + * + * @author profix898 + * @see http://drupal.org/user/35192 + */ + + +/** + * Implements hook_theme(). + */ +function themekey_theme() { + $items = array( + 'themekey_rule_chain_form' => array( + 'file' => 'themekey_admin.inc', + 'render element' => 'form', + ), + 'themekey_page_cache_icon' => array( + 'file' => 'themekey_admin.inc', + 'variables' => array('page_cache_support' => 0), + ), + ); + return $items; +} + + +/** + * Implements hook_permission(). + */ +function themekey_permission() { + return array( + 'administer theme assignments' => array( + 'title' => t('administer theme assignments'), + 'description' => t('TODO Add a description for \'administer theme assignments\''), + ), + 'administer themekey settings' => array( + 'title' => t('administer themekey settings'), + 'description' => t('TODO Add a description for \'administer themekey settings\''), + ), + ); +} + + +/** + * Implements hook_menu(). + */ +function themekey_menu() { + $items = array(); + $items['admin/config/user-interface/themekey'] = array( + 'title' => 'ThemeKey', + 'description' => 'Set up rules to switch the site\'s appearance (theme) dynamically, depending on Drupal paths or different properties.', + 'access callback' => 'user_access', + 'access arguments' => array('administer theme assignments'), + 'file' => 'themekey_admin.inc', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('themekey_rule_chain_form'), + ); + $items['admin/config/user-interface/themekey/properties'] = array( + 'title' => 'Theme Switching Rule Chain', + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'weight' => 0, + ); + $items['admin/config/user-interface/themekey/properties/delete'] = array( + 'title' => 'Delete ThemeKey Property', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('themekey_admin_delete_rule_confirm', 1), + 'access callback' => 'user_access', + 'access arguments' => array('administer theme assignments'), + 'file' => 'themekey_admin.inc', + 'type' => MENU_CALLBACK, + ); + $items['admin/config/user-interface/themekey/settings'] = array( + 'title' => 'Settings', + 'access callback' => 'user_access', + 'access arguments' => array('administer themekey settings'), + 'file' => 'themekey_admin.inc', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('themekey_settings_form'), + 'type' => MENU_LOCAL_TASK, + 'weight' => 5, + ); + $items['admin/config/user-interface/themekey/settings/general'] = array( + 'title' => 'General', + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'weight' => 0, + ); + + return $items; +} + + +/** + * Implements hook_custom_theme(). + * + * This is where all of ThemeKey's magic happens. + * ThemeKey detects if any Theme Switching Rule matches + * the current request and returns a custom theme. + */ +function themekey_custom_theme() { + themekey_define_constants(); + + $custom_theme = &drupal_static('themekey_custom_theme', ''); + + // don't change theme when ... + if ((in_array('system', variable_get('themekey_compat_modules_enabled', array())) || !(variable_get('admin_theme', '0') && path_is_admin($_GET['q']))) // ... admin area and admin theme set + && strpos($_GET['q'], 'admin/structure/block/demo') !== 0 // ... blocks demo + && strpos($_SERVER['SCRIPT_FILENAME'], 'cron.php') === FALSE // ... during cron run executed by cron.php + && strpos($_SERVER['SCRIPT_FILENAME'], 'drush.php') === FALSE // ... during cron run executed by drush + && (!defined('MAINTENANCE_MODE') || (MAINTENANCE_MODE != 'install' && MAINTENANCE_MODE != 'update')) // ... during drupal installation or update + ) { + + $custom_theme_called = &drupal_static('themekey_custom_theme_called', FALSE); + $custom_theme_called = TRUE; + + if (!$custom_theme) { + require_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'themekey') . '/themekey_base.inc'; + + $theme_candidate = themekey_match_rules(); + + // If no theme has been triggered but a theme + // is in the user's session, use that theme. + if (!$theme_candidate && !empty($_SESSION['themekey_theme']) + && (!$custom_theme || $custom_theme == variable_get('theme_default', 'bartik'))) { + $theme_candidate = $_SESSION['themekey_theme']; + if (variable_get('themekey_debug_trace_rule_switching', FALSE)) { + themekey_set_debug_message('ThemeKey Debug: No rule triggered a different theme. Reusing last theme from user\'s session: %custom_theme', array('%custom_theme' => $theme_candidate)); + } + } + + // We have a theme, apply it + if (!empty($theme_candidate) && $theme_candidate != 'default') { + if ((user_is_logged_in() && variable_get('themekey_theme_maintain', 0)) || + (!user_is_logged_in() && variable_get('themekey_theme_maintain_anonymous', 0))) { + $_SESSION['themekey_theme'] = $theme_candidate; + } + elseif (!empty($_SESSION['themekey_theme'])) { + unset($_SESSION['themekey_theme']); + } + + $custom_theme = $theme_candidate; + if (variable_get('themekey_debug_trace_rule_switching', FALSE)) { + themekey_set_debug_message('Switching theme to %custom_theme.', array('%custom_theme' => $custom_theme)); + } + } + elseif (variable_get('themekey_debug_trace_rule_switching', FALSE)) { + if ($custom_theme) { + // REVIEW + // static rules set $theme_candidate to 'default and $custom_theme directly + themekey_set_debug_message('$custom_theme has been set to %custom_theme during rule matching.', array('%custom_theme' => $custom_theme)); + } + else { + themekey_set_debug_message('Using default theme.'); + } + } + } + } + elseif (variable_get('themekey_debug_trace_rule_switching', FALSE)) { + if (strpos($_GET['q'], 'admin/structure/block/demo') === 0) { + themekey_set_debug_message('Rule checking disabled on block demo.'); + } + } + + if (variable_get('themekey_debug_show_property_values', FALSE) && module_exists('themekey_debug')) { + themekey_debug_properties(); + } + + return $custom_theme; +} + + +/** + * Implements hook_help(). + */ +function themekey_help($path, $arg) { + switch ($path) { + case 'admin/help#themekey': + module_load_include('inc', 'themekey', 'themekey_help'); + $tutorials_form = drupal_get_form('themekey_help_tutorials_form', FALSE); + case 'admin/config/user-interface/themekey': + if (!function_exists('themekey_help_properties_form')) { + module_load_include('inc', 'themekey', 'themekey_help'); + } + $examples_form = drupal_get_form('themekey_help_examples_form', TRUE); + $properties_form = drupal_get_form('themekey_help_properties_form', TRUE); + $operators_form = drupal_get_form('themekey_help_operators_form', TRUE); + $text_1 = t('For every page request, Drupal steps through this Theme Switching Rule Chain until an activated rule matches or it reaches the end. If a rule matches, the theme associated with this rule will be applied to render the requested page.'); + + switch ($path) { + case 'admin/help#themekey': + return '<p>' . t('ThemeKey allows you to define simple or sophisticated Theme Switching Rules. Using these rules, you can use a different theme depending on the current path, taxonomy terms, language, node type, and many, many other properties. It can also be easily extended to support additional properties, as exposed by other modules.') . '</p>' . + '<p>' . $text_1 . '</p>' . + drupal_render($tutorials_form) . + drupal_render($examples_form) . + drupal_render($properties_form) . + drupal_render($operators_form); + + case 'admin/config/user-interface/themekey': + return '<p>' . $text_1 . '<br />' . t('To get an idea how to get started, you might have a look at the !tutorials_link.', array('!tutorials_link' => l(t('tutorials'), 'admin/help/themekey'))) . '</p> ' . + drupal_render($examples_form) . + drupal_render($properties_form) . + drupal_render($operators_form); + } + } +} + + +/** + * Replacement for drupal_set_message() during ThemeKey's initialization. + * drupal_set_message() might inititialize the theme engine too early, + * which causes ThemeKey to not switch the theme. + * + * themekey_set_debug_message() put the untranslated messages on a stack and + * hands them over to drupal_set_message() on demand. + * + * This function simply wraps themekey_debug_set_debug_message() to avoid the need + * for module_exists('themekey_debug') calls all over the code. + * + * @param $msg + * the message as string. If the message is 'flush' + * all messages stored on the stack will be printed using + * drupal_set_message() + * + * @param $placeholder + * associative array of string replacments for $msg + * @see t() + * + * @param $translate + * boolean, if set to TRUE $msg will be handled by t() + * when handed over to drupal_set_message() + */ +function themekey_set_debug_message($msg, $placeholder = array(), $translate = TRUE, $unshift = FALSE) { + if (module_exists('themekey_debug')) { + return themekey_debug_set_debug_message($msg, $placeholder, $translate, $unshift); + } +} + + +/** + * Returns the content of $_GET['q'] as expected. + * Therefore, $_GET['q'] gets transformed if necessary. + * E.g., Ajax Views rewrites the q parameter. + * + * @return string + */ +function themekey_get_q() { + static $get_q = ''; + + if (empty($get_q)) { + if ('views/ajax' == $_GET['q'] && !empty($_GET['view_path'])) { + // required for Ajax Views. see http://drupal.org/node/567222 + $get_q = $_GET['view_path']; + } + else { + $get_q = $_GET['q']; + } + } + + return $get_q; +} + + +/** + * Implements hook_cron(). + */ +function themekey_cron() { + if (variable_get('themekey_cron_page_cache', 1)) { + module_load_include('inc', 'themekey', 'themekey_cron'); + themekey_cron_clear_page_cache(); + } +} + + +/** + * Implements hook_modules_disabled(). + */ +function themekey_modules_disabled($modules) { + require_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'themekey') . '/themekey_build.inc'; + + // Don't call themekey_rebuild() because the callbacks of disabled modules are still available at this point. + // Simply turn off the properties provided for these modules at this point. + themekey_scan_modules($modules); +} + + +/** +* Implements hook_modules_enabled(). +*/ +function themekey_modules_enabled($modules) { + require_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'themekey') . '/themekey_build.inc'; + + // Search for new properties provided by a new module + themekey_rebuild(); +} + + +/** + * Implements hook_flush_caches(). + * + * ThemeKey hijacks this hook to act if a module get updated. + */ +function themekey_flush_caches() { + require_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'themekey') . '/themekey_build.inc'; + + // update.php doesn't call hook_boot(), hook_init(), hook_custom_theme() before hook_flush_caches() + // => avoid usage of undefined constants + themekey_define_constants(); + + // Search for new properties provided by a new module + themekey_rebuild(); + + // Don't add a cache table name. + return array(); +} + + +/** + * Helper function that defines constants needed by themekey and + * add-ons. + * Could be called safely multiple times. + */ +function themekey_define_constants() { + if (!defined('THEMEKEY_PAGECACHE_UNSUPPORTED')) { + define('THEMEKEY_PAGECACHE_UNSUPPORTED', 0); + define('THEMEKEY_PAGECACHE_SUPPORTED', 1); + define('THEMEKEY_PAGECACHE_TIMEBASED', 2); + } +} diff --git a/sites/all/modules/themekey/themekey_admin.inc b/sites/all/modules/themekey/themekey_admin.inc new file mode 100644 index 0000000000000000000000000000000000000000..b5094c245fa42d534e64e76d5f7bea178728b075 --- /dev/null +++ b/sites/all/modules/themekey/themekey_admin.inc @@ -0,0 +1,810 @@ +<?php + +/** + * @file + * Contains all form manipulations to required by ThemeKey. + * + * @author Markus Kalkbrenner | Cocomore AG + * @see http://drupal.org/user/124705 + * + * @author profix898 + * @see http://drupal.org/user/35192 + */ + +require_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'themekey') . '/themekey_base.inc'; +require_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'themekey') . '/themekey_build.inc'; + + +/** + * Form builder for the rule chain. + * + * The form will not be validated. All changes will be saved immediately. + * Validation will happen when the form displays the stored configuration. + * Otherwise all the drag'n'drop stuff will not work. + * + * @see themekey_form_alter() + * @see themekey_rule_chain_form_submit() + * @see themekey_rule_chain_form_set_error() + * + * @ingroup forms + */ +function themekey_rule_chain_form($form, &$form_state) { + $properties = variable_get('themekey_properties', array()); + + if (empty($properties)) { + // first call of this form ever + themekey_rebuild(); + $properties = variable_get('themekey_properties', array()); + } + + $themes = themekey_theme_options(TRUE, TRUE); + $attributes = variable_get('themekey_attributes', array()); + $operators = array( + '=' => '=', + '!' => '!', + '<' => '<', + '<=' => '<=', + '>' => '>', + '>=' => '>=', + '~' => '~', + ); + + $form = array('#tree' => TRUE); + + $items = array(); + $fix_depth = FALSE; + if (empty($form_state['input']['old_items'])) { + $items = themekey_load_rules(); + } + else { + $items = $form_state['input']['old_items']; + $fix_depth = TRUE; + } + + $parent_options = array_merge(array(0), array_keys($items)); + $parent_options = array_combine($parent_options, $parent_options); + foreach ($items as $item) { + if ($fix_depth) { + if (!empty($item['parent'])) { + $item['depth'] = $items[$item['parent']]['depth'] + 1; + } + else { + $item['depth'] = 0; + } + } + + $form['old_items'][$item['id']]['depth'] = array( + '#type' => 'hidden', + '#value' => $item['depth'], + ); + + $form['old_items'][$item['id']]['id'] = array( + '#type' => 'hidden', + '#value' => $item['id'], + ); + + $property = $item['property']; + $wildcard = ''; + $static = FALSE; + + if (!in_array($property, $properties)) { + if (!empty($attributes[$property]['static'])) { + $static = TRUE; + + $form['old_items'][$item['id']]['property'] = array( + '#type' => 'hidden', + '#default_value' => $property, + '#value' => $property, + '#prefix' => '<span class="themekey-fadeable">' . $property . '</span>', + ); + + $form['old_items'][$item['id']]['operator'] = array( + '#type' => 'hidden', + '#default_value' => '=', + '#value' => '=', + ); + + $form['old_items'][$item['id']]['value'] = array( + '#type' => 'hidden', + '#default_value' => 'static', + '#value' => 'static', + ); + + $form['old_items'][$item['id']]['theme'] = array( + '#type' => 'select', + '#default_value' => 'default', + '#options' => array('default' => t('Triggered')), + ); + } + else { + $property = 'drupal:path:wildcard'; + $wildcard = $item['property']; + } + } + + if (!isset($form['old_items'][$item['id']]['property'])) { + $form['old_items'][$item['id']]['property'] = array( + '#type' => 'select', + '#default_value' => $property, + '#options' => $properties, + ); + } + + $form['old_items'][$item['id']]['wildcard'] = array( + '#type' => 'textfield', + '#default_value' => $wildcard, + '#size' => 10, + '#maxlength' => 255, + ); + + if (!isset($form['old_items'][$item['id']]['operator'])) { + $form['old_items'][$item['id']]['operator'] = array( + '#type' => 'select', + '#default_value' => $item['operator'], + '#options' => $operators, + ); + } + + if (!isset($form['old_items'][$item['id']]['value'])) { + $form['old_items'][$item['id']]['value'] = array( + '#type' => 'textfield', + '#default_value' => $item['value'], + '#size' => 20, + '#maxlength' => 255, + ); + } + + $form['old_items'][$item['id']]['parent'] = array( + '#type' => 'select', + '#default_value' => $item['parent'], + '#options' => $parent_options, + ); + + if (!isset($form['old_items'][$item['id']]['theme'])) { + $form['old_items'][$item['id']]['theme'] = array( + '#type' => 'select', + '#default_value' => $item['theme'], + '#options' => $themes, + ); + } + + $form['old_items'][$item['id']]['enabled'] = array( + '#type' => 'checkbox', + '#default_value' => isset($item['enabled']) ? $item['enabled'] : FALSE, + ); + + $form['old_items'][$item['id']]['weight'] = array( + '#type' => 'weight', + '#delta' => 50, + '#default_value' => $item['weight'], + ); + + $form['old_items'][$item['id']]['delete'] = array( + '#markup' => $static ? '' : l(t('delete'), 'admin/config/user-interface/themekey/properties/delete/' . $item['id']), + ); + } + + $form['new_item']['property'] = array( + '#type' => 'select', + '#default_value' => !empty($_GET['property']) ? check_plain($_GET['property']) : '', + '#options' => $properties, + ); + $form['new_item']['wildcard'] = array( + '#type' => 'textfield', + '#default_value' => '', + '#size' => 10, + '#maxlength' => 255, + ); + $form['new_item']['operator'] = array( + '#type' => 'select', + '#default_value' => '=', + '#options' => $operators, + ); + $form['new_item']['value'] = array( + '#type' => 'textfield', + '#default_value' => !empty($_GET['value']) ? check_plain($_GET['value']) : '', + '#size' => 25, + '#maxlength' => 255, + ); + $form['new_item']['theme'] = array( + '#type' => 'select', + '#default_value' => 'default', + '#options' => $themes, + ); + $form['new_item']['enabled'] = array( + '#type' => 'checkbox', + '#default_value' => TRUE, + ); + + $form['buttons']['submit'] = array( + '#type' => 'submit', + '#value' => t('Save configuration'), + ); + + return $form; +} + + +/** + * Validation of + * @see themekey_rule_chain_form() + */ +function themekey_rule_chain_form_validate(&$form, $form_state) { + module_load_include('inc', 'themekey', 'themekey_validators'); + + $attributes = variable_get('themekey_attributes', array()); + + $values = $form_state['values']; + + if (!empty($values['old_items'])) { + foreach ($values['old_items'] as $key_1 => $value_1) { + if ($value_1['enabled'] && !themekey_check_theme_enabled($value_1['theme'])) { + form_set_error('old_items][' . $key_1 . '][theme', check_plain(t('Theme is not activated'))); + } + + if (empty($value_1['property'])) { + form_set_error('old_items][' . $key_1 . '][property', check_plain(t('Property missing'))); + } + else { + if (0 == drupal_strlen($value_1['value'])) { + form_set_error('old_items][' . $key_1 . '][value', check_plain(t('You must enter a value'))); + } + elseif (!empty($attributes[$value_1['property']]['validator'])) { + //validate rule with custom validator + $validator = $attributes[$value_1['property']]['validator']; + + if (!function_exists($validator) && isset($attributes[$value_1['property']]['file'])) { + themekey_load_function( + $validator, + $attributes[$value_1['property']]['file'], + isset($attributes[$value_1['property']]['path']) ? $attributes[$value_1['property']]['path'] : ''); + } + + if (function_exists($validator)) { + $rule = array( + 'property' => $value_1['property'], + 'wildcard' => $value_1['wildcard'], + 'operator' => $value_1['operator'], + 'value' => $value_1['value'], + ); + $errors = $validator($rule); + foreach ($errors as $element => $msg) { + form_set_error('old_items][' . $key_1 . '][' . $element, filter_xss($msg, array('em'))); + } + } + else { + form_set_error('old_items][' . $key_1 . '][property', filter_xss(t('ThemeKey requested an unknown validator called %validator to validate property %property', array('%validator' => $validator, '%property' => $value_1['property'])), array('em'))); + } + } + } + + foreach ($values['old_items'] as $key_2 => $value_2) { + if ($key_2 == $key_1) { + continue; + } + + if ($value_2['enabled'] && + !empty($value_1['value']) && + $value_2['property'] == $value_1['property'] && + $value_2['operator'] == $value_1['operator'] && + $value_2['value'] == $value_1['value'] && + ($value_2['parent'] == $value_1['parent'] || + $value_2['parent'] == $value_1['id'])) { + + if ('drupal:path:wildcard' != $value_2['property'] || + ('drupal:path:wildcard' == $value_2['property'] && $value_2['wildcard'] == $value_1['wildcard'])) { + // We have two identical rules with same 'indention' in a chain. + // This is allowed only if first one has childs and second one has none and one isn't the parent of the other + if (!$value_2['parent'] == $value_1['id']) { + $has_childs_1 = FALSE; + $has_childs_2 = FALSE; + + foreach ($values['old_items'] as $key_3 => $value_3) { + if ($value_3['parent'] == $value_1['id']) { + $has_childs_1 = TRUE; + } + + if ($value_3['parent'] == $value_2['id']) { + $has_childs_2 = TRUE; + } + + if ($has_childs_1 && $has_childs_2) { + break; + } + } + + if ((($value_1['weight'] < $value_2['weight']) && $has_childs_1 && !$has_childs_2) || + (($value_1['weight'] > $value_2['weight']) && !$has_childs_1 && $has_childs_2)) { + // no error + continue; + } + elseif (($value_1['weight'] > $value_2['weight']) && $has_childs_1 && !$has_childs_2) { + form_set_error('old_items][' . $key_1 . '][property', check_plain(t('Theme switching rule could never be reached'))); + continue; + } + elseif (($value_1['weight'] < $value_2['weight']) && !$has_childs_1 && $has_childs_2) { + form_set_error('old_items][' . $key_1 . '][property', check_plain(t('Theme switching rule hides a later one'))); + continue; + } + elseif (($value_1['weight'] < $value_2['weight']) && $has_childs_1 && $has_childs_2) { + form_set_error('old_items][' . $key_1 . '][property', check_plain(t('Theme switching rule should be combined with an identical one below'))); + continue; + } + elseif (($value_1['weight'] > $value_2['weight']) && $has_childs_1 && $has_childs_2) { + form_set_error('old_items][' . $key_1 . '][property', check_plain(t('Theme switching rule should be combined with an identical one above'))); + continue; + } + } + + form_set_error('old_items][' . $key_1 . '][property', check_plain(t('You entered two identical theme switching rules in the chain'))); + form_set_error('old_items][' . $key_2 . '][property', check_plain(t('You entered two identical theme switching rules in the chain'))); + } + } + } + } + } + + if (!empty($values['new_item']['value'])) { + if ($values['new_item']['enabled'] && !themekey_check_theme_enabled($values['new_item']['theme'])) { + form_set_error('new_item][theme', check_plain(t('Theme is not activated'))); + } + + if (empty($values['new_item']['property'])) { + form_set_error('new_item][property', check_plain(t('Property missing'))); + } + else { + if (!empty($attributes[$values['new_item']['property']]['validator'])) { + //validate rule with custom validator + $validator = $attributes[$values['new_item']['property']]['validator']; + + if (!function_exists($validator) && isset($attributes[$values['new_item']['property']]['file'])) { + themekey_load_function( + $validator, + $attributes[$values['new_item']['property']]['file'], + isset($attributes[$values['new_item']['property']]['path']) ? $attributes[$values['new_item']['property']]['path'] : ''); + } + + if (function_exists($validator)) { + $rule = array( + 'property' => $values['new_item']['property'], + 'wildcard' => $values['new_item']['wildcard'], + 'operator' => $values['new_item']['operator'], + 'value' => $values['new_item']['value'], + ); + $errors = $validator($rule); + foreach ($errors as $element => $msg) { + form_set_error('new_item][' . $element, filter_xss($msg)); + } + } + else { + form_set_error('new_item][property', filter_xss(t('ThemeKey requested an unknown validator called %validator to validate the property, %property', array('%validator' => $validator, '%property' => $value_1['property']))), array('em')); + } + } + } + } +} + + +/** + * Form submission handler for themekey_rule_chain_form(). + * + * @see themekey_rule_chain_form() + */ +function themekey_rule_chain_form_submit($form, &$form_state) { + $max_weight = 0; + if (!empty($form_state['values']['old_items'])) { + foreach ($form_state['values']['old_items'] as $id => $item) { + if ($item['weight'] > $max_weight) { + $max_weight = $item['weight']; + } + + $item['id'] = $id; + + if ('drupal:path:wildcard' == $item['property']) { + $item['property'] = $item['wildcard']; + } + + unset($item['wildcard']); + + themekey_rule_set($item); + } + } + + if (!empty($form_state['values']['new_item']['value']) || '0' === $form_state['values']['new_item']['value']) { + $item = $form_state['values']['new_item']; + + $item['parent'] = 0; + $item['weight'] = $max_weight + 1; + + if ('drupal:path:wildcard' == $item['property']) { + $item['property'] = $item['wildcard']; + } + + unset($item['wildcard']); + + themekey_rule_set($item); + } + + drupal_set_message(check_plain(t('The configuration options have been saved. Trying to clear page cache ...'))); + + // fast deletion of page cache (truncate) + cache_clear_all('*', 'cache_page', TRUE); +} + + +/** + * Form builder for the ThemeKey settings form. + * + * @ingroup forms + */ +function themekey_settings_form($form, &$form_state) { + $form['settings'] = array( + '#type' => 'fieldset', + '#title' => t('General Settings'), + '#collapsible' => FALSE, + '#collapsed' => FALSE, + ); + + $form['settings']['themekey_path_case_sensitive'] = array( + '#type' => 'checkbox', + '#title' => t('Property drupal:path is case sensitive'), + '#default_value' => variable_get('themekey_path_case_sensitive', 0), + '#description' => t('Drupal paths are case insensitive by default. Modules like Global Redirect might change this behavior.'), + ); + + $form['settings']['themekey_allthemes'] = array( + '#type' => 'checkbox', + '#title' => t('Provide all themes for selection'), + '#default_value' => variable_get('themekey_allthemes', 0), + '#description' => t('Make all installed themes available for selection, not enabled ones only.'), + ); + + $form['settings']['themekey_theme_maintain'] = array( + '#type' => 'checkbox', + '#title' => t('Retain the theme until a new theme is set'), + '#default_value' => variable_get('themekey_theme_maintain', 0), + '#description' => t('Select this option to have logged-in users stay in the same theme until they browse to a new page with an explicit theme set.'), + ); + + $form['settings']['themekey_theme_maintain_anonymous'] = array( + '#type' => 'checkbox', + '#title' => t('Retain the theme until a new theme is set for anonymous users'), + '#default_value' => variable_get('themekey_theme_maintain_anonymous', 0), + '#description' => t('Select this option to have anonymous users stay in the same theme until they browse to a new page with an explicit theme set.'), + ); + + if (module_exists('forum')) { + $form['settings']['themekey_module_forum_triggers_taxonomy_vid'] = array( + '#type' => 'checkbox', + '#title' => t('Forum pages trigger property taxonomy:vid'), + '#default_value' => variable_get('themekey_module_forum_triggers_taxonomy_vid', 0), + '#description' => t('Property taxonomy:vid is set when a single node is shown (e.g. /node/17). If this option is selected, forum pages like /forum/28 will set taxonomy:vid as well.'), + ); + } + + $form['settings']['themekey_cron_page_cache'] = array( + '#type' => 'checkbox', + '#title' => t('Cron cleans up page cache'), + '#default_value' => variable_get('themekey_cron_page_cache', 1), + '#description' => t('Select this option if ThemeKey should check rules containing time-based properties when cron runs. ThemeKey will carefully clean up the page cache if necessary to provide the right theme to anonymous users automatically, e.g. a Christmas theme.'), + ); + + return system_settings_form($form); +} + + +/** + * Themes themekey_rule_chain_form() and adds drag'n'drop features. + * + * @ingroup themeable + */ +function theme_themekey_rule_chain_form($variables) { + $form = $variables['form']; + themekey_admin_theme_warning(); + + $output = ''; + + $rows = array(); + + if (!empty($form['old_items'])) { + $num_childs = array(); + $parents_disabled = array(); + $attributes = variable_get('themekey_attributes', array()); + + foreach ($form['old_items'] as $key => $item) { + if (is_numeric($key) && !empty($item['property'])) { + $parents_disabled[$key] = FALSE; + + if (!empty($parents_disabled[$item['parent']['#value']])) { + $form['old_items'][$key]['enabled']['#value'] = 0; + } + + if (!$form['old_items'][$key]['enabled']['#value']) { + $parents_disabled[$key] = TRUE; + } + elseif (!empty($item['parent']['#value'])) { + $num_childs[$item['parent']['#value']] = empty($num_childs[$item['parent']['#value']]) ? 1 : $num_childs[$item['parent']['#value']] + 1; + } + } + } + + foreach ($form['old_items'] as $key => $item) { + if (is_numeric($key) && !empty($item['property'])) { + $row = (isset($form['old_items'][$key]['#attributes']) && is_array($form['old_items'][$key]['#attributes'])) ? $form['old_items'][$key]['#attributes'] : array(); + + // Add special classes to be used for tabledrag.js. + $form['old_items'][$key]['id']['#attributes']['class'] = array('themekey-property-id'); + $form['old_items'][$key]['parent']['#attributes']['class'] = array('themekey-property-parent'); + $form['old_items'][$key]['weight']['#attributes']['class'] = array('themekey-property-weight'); + // Add special classes to be used for themekey-properties.js. + $form['old_items'][$key]['property']['#attributes']['class'] = array('themekey-property-property themekey-fadeable'); + $form['old_items'][$key]['wildcard']['#attributes']['class'] = array('themekey-property-wildcard themekey-fadeable'); + $form['old_items'][$key]['operator']['#attributes']['class'] = array('themekey-fadeable'); + $form['old_items'][$key]['value']['#attributes']['class'] = array('themekey-fadeable'); + $form['old_items'][$key]['enabled']['#attributes']['class'] = array('themekey-property-enabled'); + $form['old_items'][$key]['theme']['#attributes']['class'] = array('themekey-property-theme themekey-fadeable'); + + // form items of type markup don't have attributes + $form['old_items'][$key]['delete']['#markup'] = str_replace('<a', '<a class="themekey-rule-delete-link"', $form['old_items'][$key]['delete']['#markup']); + + if ('drupal:path:wildcard' != $item['property']['#value']) { + $form['old_items'][$key]['wildcard']['#attributes']['style'] = 'display: none'; + } + + if (!empty($num_childs[$key])) { + $form['old_items'][$key]['theme']['#attributes']['style'] = 'display: none'; + // form items of type markup don't have attributes + $form['old_items'][$key]['delete']['#markup'] = str_replace('<a', '<a style="display: none"', $form['old_items'][$key]['delete']['#markup']); + } + + if (!empty($parents_disabled[$item['parent']['#value']])) { + $form['old_items'][$key]['enabled']['#attributes']['style'] = 'display: none'; + $form['old_items'][$key]['enabled']['#default_value'] = 0; + } + + $elements = array(); + + $description = htmlentities(strip_tags($attributes[$form['old_items'][$key]['property']['#default_value']]['description'])); + $description = str_replace('\'', '', $description); + + $elements[] = array( + 'data' => theme('indentation', array('size' => $form['old_items'][$key]['depth']['#value'])) . + drupal_render($form['old_items'][$key]['id']) . + drupal_render($form['old_items'][$key]['property']) . + drupal_render($form['old_items'][$key]['wildcard']) . + drupal_render($form['old_items'][$key]['operator']) . + drupal_render($form['old_items'][$key]['value']) . + '<a name="anchor-' . $key . '" href="#anchor-' . $key . '" id="' . drupal_clean_css_identifier('edit-old-items-' . $key . '-value-help') . '" class="themekey-fadeable" title="' . $description . '" onClick="alert(\'' . $description . '\')">?</a>', + 'class' => array('themekey-properties-row'), + ); + + $elements[] = array( + 'data' => drupal_render($form['old_items'][$key]['theme']), + ); + + $elements[] = array( + 'data' => drupal_render($form['old_items'][$key]['enabled']), + ); + + $elements[] = array( + 'data' => drupal_render($form['old_items'][$key]['delete']), + ); + + $elements[] = array( + 'data' => drupal_render($form['old_items'][$key]['parent']), + ); + + $elements[] = array( + 'data' => drupal_render($form['old_items'][$key]['weight']), + ); + + $page_cache = $attributes[$form['old_items'][$key]['property']['#default_value']]['page cache']; + if ($form['old_items'][$key]['enabled']['#value']) { + themekey_page_cache_warning($page_cache); + } + + $elements[] = array( + 'data' => '<div id="' . drupal_clean_css_identifier('edit-old-items-' . $key . '-page-cache-icon') . '">' . + theme('themekey_page_cache_icon', array('page_cache_support' => $page_cache)) . + '</div>', + ); + + $row['class'][] = 'draggable'; + if (!$form['old_items'][$key]['enabled']['#value']) { + $row['class'][] = 'themekey-fade-out'; + } + if (!$form['old_items'][$key]['parent']['#value']) { + $row['class'][] = 'themekey-top-level'; + } + $row['id'] = 'themekey-properties-row-' . $key; + $row['data'] = $elements; + $rows[] = $row; + } + } + } + + if (!empty($rows)) { + if (empty($form['pager']['#value'])) { + drupal_add_tabledrag('themekey-properties', 'match', 'parent', 'themekey-property-parent', 'themekey-property-parent', 'themekey-property-id', TRUE); + drupal_add_tabledrag('themekey-properties', 'order', 'sibling', 'themekey-property-weight'); + } + + $header = array( + t('Theme Switching Rule Chain'), + t('Theme'), + t('Enabled'), + t('Operation'), + t('Parent'), + t('Weight'), + t('Page<br />Cache'), + ); + + $output .= theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('id' => 'themekey-properties'))); + + foreach ($num_childs as $parent => $num) { + $output .= '<input id="themekey-num-childs-' . $parent . '" type="hidden" value="' . $num . '"></input>'; + } + } + + $rows = array(); + + if (!empty($form['new_item'])) { + if (isset($form['new_item']['property'])) { + $row = (isset($form['new_item']['#attributes']) && is_array($form['new_item']['#attributes'])) ? $form['new_item']['#attributes'] : array(); + + // Add special classes to be used for themekey-properties.js. + $form['new_item']['property']['#attributes']['class'] = array('themekey-property-property'); + $form['new_item']['wildcard']['#attributes']['class'] = array('themekey-property-wildcard'); + if ('drupal:path:wildcard' != $form['new_item']['property']['#value']) { + $form['new_item']['wildcard']['#attributes']['style'] = 'display: none'; + } + + $elements = array(); + $elements[] = t('New Rule:'); + $elements[] = array( + 'data' => drupal_render($form['new_item']['property']) . + drupal_render($form['new_item']['wildcard']) . + drupal_render($form['new_item']['operator']) . + drupal_render($form['new_item']['value']), + 'class' => array('themekey-properties-row'), + ); + $elements[] = array('data' => drupal_render($form['new_item']['theme'])); + $elements[] = array('data' => drupal_render($form['new_item']['enabled'])); + + $row['data'] = $elements; + $rows[] = $row; + } + } + + if (!empty($rows)) { + $header = array( + '', + t('Theme Switching Rule'), + t('Theme'), + t('Enabled'), + ); + + $output .= '<a name="themekey_new_rule"></a>'; + $output .= theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('id' => 'themekey-new-item'))); + } + + $output .= drupal_render_children($form); + + drupal_add_css(drupal_get_path('module', 'themekey') . '/themekey_rule_chain.admin.css'); + drupal_add_js(drupal_get_path('module', 'themekey') . '/themekey_rule_chain.admin.js'); + + return $output; +} + + +/** + * Detects if there's an active administration theme and displays a warning + */ +function themekey_admin_theme_warning() { + if (!in_array('system', variable_get('themekey_compat_modules_enabled', array())) && variable_get('admin_theme', '0')) { + drupal_set_message(filter_xss(t('%admin_theme is configured as administration theme at !link. This setting is more powerful than a corresponding ThemeKey rule.', + array('%admin_theme' => variable_get('admin_theme', '0'), '!link' => l(t('!path', array('!path' => 'admin/appearance')), 'admin/appearance')))), 'warning'); + if (variable_get('node_admin_theme', '0')) { + drupal_set_message(filter_xss(t('As configured at !link, adding or editing a node will use the administration theme, %admin_theme.', + array('%admin_theme' => variable_get('admin_theme', '0'), '!link' => l(t('!path', array('!path' => 'admin/appearance')), 'admin/appearance')))), 'warning'); + } + } +} + + +/** + * Detects if page cache is active and incompatible properties are used + * in theme switching rule chain + */ +function themekey_page_cache_warning($page_cache_support) { + static $warnings = array(); + + if (variable_get('cache', 0)) { + switch ($page_cache_support) { + case THEMEKEY_PAGECACHE_UNSUPPORTED: + if (!isset($warnings[THEMEKEY_PAGECACHE_UNSUPPORTED])) { + drupal_set_message(filter_xss(t('!page_cache_icon Your site uses !page_caching, but the Theme Switching Rule Chain contains properties that do not support page caching. You have to ensure that any rule using those properties are wrapped by a different rule that excludes anonymous users like "user:uid > 0".', + array('!page_caching' => l(t('page caching'), 'admin/config/development/performance'), '!page_cache_icon' => theme('themekey_page_cache_icon', array('page_cache_support' => $page_cache_support)))), array('a', 'img')), 'warning'); + $warnings[THEMEKEY_PAGECACHE_UNSUPPORTED] = TRUE; + } + break; + + case THEMEKEY_PAGECACHE_TIMEBASED: + if (!isset($warnings[THEMEKEY_PAGECACHE_TIMEBASED])) { + if (variable_get('themekey_cron_page_cache', 1)) { + drupal_set_message(filter_xss(t('!page_cache_icon Your site uses !page_caching and the Theme Switching Rule Chain contains properties that require your cron to run frequently enough. During the cron run ThemeKey deletes the page cache if necessary depending on your Theme Switching Rules.', + array('!page_caching' => l(t('page caching'), 'admin/config/development/performance'), '!page_cache_icon' => theme('themekey_page_cache_icon', array('page_cache_support' => $page_cache_support)))), array('a', 'img')), 'warning'); + } + else { + drupal_set_message(filter_xss(t('!page_cache_icon Your site uses !page_caching and the Theme Switching Rule Chain contains properties that require "!cron_page_cache" to be enabled, but it is disabled.', + array('!page_caching' => l(t('page caching'), 'admin/config/development/performance'), '!cron_page_cache' => l(t('Cron cleans up page cache'), 'admin/config/user-interface/themekey/settings'), '!page_cache_icon' => theme('themekey_page_cache_icon', array('page_cache_support' => $page_cache_support)))), array('a', 'img')), 'error'); + } + $warnings[THEMEKEY_PAGECACHE_TIMEBASED] = TRUE; + } + break; + } + } +} + + +/** + * Menu callback -- ask for confirmation of ThemeKey rule deletion + */ +function themekey_admin_delete_rule_confirm($form, &$form_state, $arg, $themekey_property_id) { + $form['themekey_property_id'] = array( + '#type' => 'value', + '#value' => $themekey_property_id, + ); + + $title = themekey_format_rule_as_string($themekey_property_id); + + return confirm_form($form, + t('Are you sure you want to delete the ThemeKey rule, %title?', array('%title' => $title)), + 'admin/config/user-interface/themekey/properties', + t('This action cannot be undone.'), + t('Delete'), + t('Cancel') + ); +} + + +/** + * Execute ThemeKey rule deletion + */ +function themekey_admin_delete_rule_confirm_submit($form, &$form_state) { + if ($form_state['values']['confirm']) { + themekey_rule_del($form_state['values']['themekey_property_id']); + } + + $form_state['redirect'] = 'admin/config/user-interface/themekey/properties'; +} + + +/** + * @todo Please document this function. + * @see http://drupal.org/node/1354 + */ +function theme_themekey_page_cache_icon($variables) { + $page_cache_support = $variables['page_cache_support']; + static $module_path = '', $page_cache_support_desriptions = array(); + + if (empty($module_path)) { + $module_path = drupal_get_path('module', 'themekey'); + $page_cache_support_desriptions = themekey_get_page_cache_support_desriptions(); + } + + return '<img src="/' . $module_path . '/img/page_cache_' . $page_cache_support . '.png" width="16" height="16" title="' . htmlentities(strip_tags($page_cache_support_desriptions[$page_cache_support]), ENT_COMPAT, 'UTF-8') . '"></img>'; +} + + +/** + * @todo Please document this function. + * @see http://drupal.org/node/1354 + */ +function themekey_get_page_cache_support_desriptions() { + return array( + THEMEKEY_PAGECACHE_UNSUPPORTED => t('Unsupported! If you enable page caching on your system, you have to ensure that any rule using this property is wrapped by another rule that excludes anonymous users, such as "user:uid > 0".'), + THEMEKEY_PAGECACHE_SUPPORTED => t('Supported'), + THEMEKEY_PAGECACHE_TIMEBASED => t('Supported if you enable "Cron cleans up page cache" at !link and run your cron frequently enough. During the cron run ThemeKey deletes the page cache if necessary depending on your Theme Switching Rules.', array('!link' => l(t('!path', array('!path' => 'admin/config/user-interface/themekey/settings')), 'admin/config/user-interface/themekey/settings'))), + ); +} diff --git a/sites/all/modules/themekey/themekey_base.inc b/sites/all/modules/themekey/themekey_base.inc new file mode 100644 index 0000000000000000000000000000000000000000..40e1635c26414d63b606d8c442659d35dbb582f4 --- /dev/null +++ b/sites/all/modules/themekey/themekey_base.inc @@ -0,0 +1,614 @@ +<?php + +/** + * @file + * The functions in this file are the back end of ThemeKey. + * + * @author Markus Kalkbrenner | Cocomore AG + * @see http://drupal.org/user/124705 + * + * @author profix898 + * @see http://drupal.org/user/35192 + */ + + +/** + * Invokes a hook on all modules stored in the global + * variable 'themekey_modules' + * + * @param $hook + * name of the hook as string + * + * @return + * mixed output of all hooks + */ +function themekey_invoke_modules($hook) { + $return = array(); + foreach (variable_get('themekey_modules', array('node')) as $module) { + if (module_exists($module) && is_readable(drupal_get_path('module', 'themekey') .'/modules/themekey.' . $module .'.inc')) { + require_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'themekey') . '/modules/themekey.' . $module . '.inc'; + + $function = 'themekey_' . $module . '_' . $hook; + if (function_exists($function)) { + $return = array_merge_recursive($return, $function()); + } + } + else { + // seems like a module has been deactivated => update variable 'themekey_modules' + module_load_include('inc', 'themekey', 'themekey_build'); + themekey_scan_modules(); + } + } + + return $return; +} + + +/** + * Detects if a ThemeKey rule for property drupal:path + * matches to the current page request. + * + * @param $condition + * ThemeKey rule as object: + * - property (must be "drupal:path") + * - operator + * - value + * + * @param $parameters + * reference to an array containing all + * ThemeKey properties and their values + * + * @return + * boolean + */ +function themekey_match_path($condition, &$parameters) { + static $alias_uri = ''; + + if ('drupal:path' != $condition->property) { + return FALSE; + } + + $get_q = $get_q_to_split = themekey_get_q(); + + if (!variable_get('themekey_path_case_sensitive', 0)) { + $condition->value = drupal_strtolower($condition->value); + $get_q_to_split = drupal_strtolower($get_q_to_split); + } + + $condition_parts = explode('/', $condition->value); + $get_q_parts = explode('/', $get_q_to_split); + + $wildcards = themekey_match_path_parts($get_q_parts, $condition_parts); + if (FALSE === $wildcards && module_exists('path')) { + if (empty($alias_uri)) { + // Derive path from request_uri + $offset = (variable_get('clean_url', 0) ? 0 : 3) + drupal_strlen(base_path()); + $alias_uri = drupal_substr(request_uri(), $offset); + if (strpos($alias_uri, '?') !== FALSE) { + // Remove query string from request uri + $alias_uri_parts = explode('?', $alias_uri); + $alias_uri = $alias_uri_parts[0]; + } + // For $alias_uri != $_GET['q'] the page was requested using an + // aliased path, otherwise get the path alias internally + if ($alias_uri == $get_q) { + $alias_uri = drupal_get_path_alias($get_q); + } + } + + if ($alias_uri != $get_q) { + $alias_parts = explode('/', $alias_uri); + + $wildcards = themekey_match_path_parts($alias_parts, $condition_parts); + } + } + + if (is_array($wildcards)) { + $parameters['internal:temp_wildcards'] = $wildcards; + return TRUE; + } + + $parameters['internal:temp_wildcards'] = array(); + return FALSE; +} + + +/** + * Compares if two paths are identical accepting + * wildcards "%" and "#". + * + * @param $path_parts + * array containing single parts of a path + * + * @param $condition_parts + * array containing single parts of a path + * whereas a part could be a wildcard + * + * @return + * FALSE if paths doesn't match or array containing + * the wildcards if paths matched + */ +function themekey_match_path_parts($path_parts, $condition_parts) { + if (count($path_parts) < count($condition_parts)) { + return FALSE; + } + + $wildcards = array(); + + foreach ($condition_parts as $key => $part) { + if ('#' == $part) { + if (ctype_digit($path_parts[$key])) { + $wildcards[$key] = $path_parts[$key]; + continue; + } + return FALSE; + } + + if ('%' == $part) { + if (isset($path_parts[$key])) { + $wildcards[$key] = $path_parts[$key]; + continue; + } + return FALSE; + } + + if ($part == $path_parts[$key]) { + continue; + } + + return FALSE; + } + + return $wildcards; +} + + + +/** + * Assigns global parameters' values to ThemeKey properties. + * Therefore it calls hook_themekey_global() + * + * @return + * associative array containing some + * ThemeKey properties and their values + */ +function themekey_get_global_parameters() { + static $global_parameters = NULL; + + if (is_null($global_parameters)) { + $global_parameters = array_merge_recursive(themekey_invoke_modules('themekey_global'), module_invoke_all('themekey_global')); + + $get_q_parts = explode('/', themekey_get_q()); + $paths = variable_get('themekey_paths', array()); + foreach ($paths as $item) { + $item_parts = explode('/', $item['path']); + $wildcards = themekey_match_path_parts($get_q_parts, $item_parts); + if (!empty($wildcards)) { + foreach ($item['wildcards'] as $index => $item_wildcard) { + $global_parameters[$item_wildcard] = $wildcards[$index]; + } + + if (count($item['callbacks'])) { + foreach ($item['callbacks'] as $callback) { + $callback($item, $global_parameters); + } + } + } + } + } + + return $global_parameters; +} + +/** + * This function steps through + * the rule chain and returns a theme. + * + * @return + * a theme as string or NULL + */ +function themekey_match_rules() { + $parameters = themekey_get_global_parameters(); + + $theme = themekey_match_rule_childs($parameters); + if (FALSE === $theme || TRUE === $theme) { + $theme = NULL; + } + elseif ('ThemeKeyAdminTheme' === $theme) { + $theme = variable_get('admin_theme', '0'); + } + + return $theme; +} + +/** + * Helper function of + * @see themekey_match_rules() + * + * @param $parameters + * reference to an array containing all + * ThemeKey properties and their values + * + * @param $parent + * id of parent rule + * + * @return + * NULL in case of an error + * string name of the theme if child rule matched + * FALSE if no child rule matches + * TRUE if no child properties in the chain + */ +function themekey_match_rule_childs(&$parameters, $parent = 0) { + static $child_lookups = array(); + + if (isset($child_lookups[$parent])) { + // prevent endless recursion in case of malformatted data in database + return $child_lookups[$parent]; + } + + if ($result = db_select('themekey_properties', 'tp') + ->fields('tp') + ->condition('enabled', 1) + ->condition('parent', $parent) + ->condition('value', '', '<>') + ->orderBy('weight', 'asc') + ->execute() + ) { + + $num_childs = 0; + + foreach ($result as $item) { + $num_childs++; + if (themekey_match_condition($item, $parameters)) { + if ('drupal:path' == $item->property && !empty($parameters['internal:temp_wildcards'])) { + $wildcards = unserialize($item->wildcards); + if (!empty($wildcards)) { + foreach ($wildcards as $index => $wildcard) { + $parameters[$wildcard] = $parameters['internal:temp_wildcards'][$index]; + } + } + } + + if (variable_get('themekey_debug_trace_rule_switching', FALSE)) { + themekey_set_debug_message('Match: %rule', array('%rule' => themekey_format_rule_as_string($item->id))); + } + + $child_lookups[$parent] = themekey_match_rule_childs($parameters, $item->id); + + if (FALSE === $child_lookups[$parent]) { + continue; + } + elseif (TRUE === $child_lookups[$parent]) { + if (!themekey_check_theme_enabled($item->theme)) { + if (variable_get('themekey_debug_trace_rule_switching', FALSE)) { + themekey_set_debug_message('Theme disabled: %theme', array('%theme' => $item->theme)); + } + continue; + } + + $child_lookups[$parent] = $item->theme; + } + + // return own theme or theme from child property or NULL in case of a database error + return $child_lookups[$parent]; + } + elseif (variable_get('themekey_debug_trace_rule_switching', FALSE)) { + themekey_set_debug_message('No match: %rule', array('%rule' => themekey_format_rule_as_string($item->id))); + } + } + + $child_lookups[$parent] = (!$num_childs); + return $child_lookups[$parent]; + } + + $child_lookups[$parent] = NULL; + return $child_lookups[$parent]; +} + + +/** + * Detects if a ThemeKey rule matches to the current + * page request. + * + * @param $condition + * ThemeKey rule as object: + * - property + * - operator + * - value + * + * @param $parameters + * reference to an array containing all + * ThemeKey properties an their values + * + * @return + * boolean + */ +function themekey_match_condition($condition, &$parameters) { + $custom_theme = &drupal_static('themekey_custom_theme', ''); + + if (is_object($condition)) { + // Default operator is 'equal' + if (empty($condition->operator)) { + $condition->operator = '='; + } + + if ('drupal:path' == $condition->property) { + $match_path = themekey_match_path($condition, $parameters); + if ($condition->operator == '=') { + return $match_path; + } + // only '=' and '!' are allowed + // @see themekey_validator_drupal_path() + return !$match_path; + } + + $value = themekey_property_value($parameters, $condition->property); + + if ('static' === $value && $custom_theme) { + if (variable_get('themekey_debug_trace_rule_switching', FALSE)) { + themekey_set_debug_message('A static rule set custom theme %custom_theme', array('%custom_theme' => $custom_theme)); + } + return TRUE; + } + + if (!is_array($value)) { + $value = array($value); + } + + if (!empty($value)) { + foreach ($value as $single_value) { + if (!is_null($single_value)) { + // Supported operators for condition check: + // smaller ('<'), greater ('>'), equal ('='), not equal ('!'), regex match ('~') + if ($condition->operator == '<' && $single_value >= $condition->value) { + return FALSE; + } + elseif ($condition->operator == '>' && $single_value <= $condition->value) { + return FALSE; + } + elseif ($condition->operator == '<=' && $single_value > $condition->value) { + return FALSE; + } + elseif ($condition->operator == '>=' && $single_value < $condition->value) { + return FALSE; + } + elseif ($condition->operator == '=' && $single_value == $condition->value) { + return TRUE; + } + elseif ($condition->operator == '!' && $single_value == $condition->value) { + return FALSE; + } + elseif ($condition->operator == '~' && preg_match($condition->value, $single_value)) { + return TRUE; + } + } + else { + // value is NULL + return FALSE; + } + } + + if ($condition->operator == '=' || $condition->operator == '~') { + // no value matched + return FALSE; + } + else { + // condition matched for all values + return TRUE; + } + } + else { + // value array is empty => value is NULL + return FALSE; + } + } + else { + trigger_error(t('Function themekey_match_condition() called with illegal parameters'), E_USER_ERROR); + } +} + + +/** + * Detects if a ThemeKey property's value for the current + * page request. + * + * @param $parameters + * reference to an array containing all + * ThemeKey properties and their values + * + * @param $property + * the name of the property as string + * + * @return + * The value of the property: + * - string if it's a single value + * - array of strings if there're multiple values + * - NULL if no value + */ +function themekey_property_value(&$parameters, $property) { + + // TODO Warning if property is not part of variable_get('themekey_attributes') + + // Property value is available directly + if (isset($parameters[$property])) { + return $parameters[$property]; + } + + $parameters[$property] = NULL; + + $src_candidates = array(); + $maps = variable_get('themekey_maps', array()); + + foreach ($maps as $pos => $map) { + if ($map['dst'] == $property) { + if (!empty($parameters[$map['src']])) { + $map_func = $map['callback']; + + if (!function_exists($map_func) && isset($map['file'])) { + themekey_load_function( + $map_func, + $map['file'], + isset($map['path']) ? $map['path'] : ''); + } + + $arguments = isset($map['args']) ? $map['args'] : array(); + + if (function_exists($map_func)) { + $parameters[$property] = $map_func($parameters[$map['src']], $arguments); + } + else { + themekey_set_debug_message('Map function %map_function does not exists', array('%map_function' => $map_func)); + watchdog('php', 'ThemeKey map function %map_function does not exists', array('%map_function' => $map_func), WATCHDOG_ERROR); + } + break; + } + $src_candidates[$pos] = $map['src']; + } + } + + if (is_null($parameters[$property]) && !empty($src_candidates)) { + foreach ($src_candidates as $pos => $src) { + $return = themekey_property_value($parameters, $src); + if ($return) { + $map_func = $maps[$pos]['callback']; + $parameters[$property] = $map_func($return, $parameters); + break; + } + } + } + + return $parameters[$property]; +} + + +/** + * Checks if a theme is enabled and fires warning messages + * to the site's administrator + * + * @param $theme + * name of the theme as string + * + * @param $settings_page + * boolean that indicates if the function + * is called from ThemeKey's administration + * backend which causes a different message + * + * @return + * TRUE if the theme is enabled, otherwise FALSE + */ +function themekey_check_theme_enabled($theme, $settings_page = FALSE) { + static $themes_enabled = array(); + static $warned = FALSE; + static $displayed_error = FALSE; + + if (!$theme || 'default' == $theme) { + return TRUE; + } + + if (empty($themes_enabled)) { + + if ($result = db_select('system', 's') + ->fields('s', array('name')) + ->condition('type', 'theme') + ->condition('status', '1') + ->execute() + ) { + foreach ($result as $row) { + $themes_enabled[] = $row->name; + } + + } + } + + if (in_array($theme, $themes_enabled)) { + return TRUE; + } + elseif ('ThemeKeyAdminTheme' == $theme && variable_get('admin_theme', '0') && in_array(variable_get('admin_theme', '0'), $themes_enabled)) { + return TRUE; + } + + if ($settings_page) { + if (!$displayed_error) { + drupal_set_message(filter_xss(t("Your current configuration of theme rules uses at least one theme that is not enabled. Nevertheless, this configuration is stored, but affected rules won't be applied until the targeted theme is enabled at !build_themes.", + array('!build_themes' => l(t('!path', array('!path' => 'admin/appearance')), 'admin/appearance')))), 'error'); + $displayed_error = TRUE; + } + } + else { + if (!$warned && variable_get('themekey_debug_trace_rule_switching', FALSE)) { + // Don't use the l() function at this early stage of bootstrapping because it will initialize the theme engine. Use url() instead. + themekey_set_debug_message('A matching Theme Switching Rule to select theme %theme was not applied because this theme is disabled. You can enable this theme at !build_themes, remove this Theme Switching Rule at !themekey_properties, or edit the current node if the theme was selected using ThemeKey UI.', + array('%theme' => $theme, '!build_themes' => '<a href="' . url('admin/appearance') . '">admin/appearance</a>', '!themekey_properties' => '<a href="' . url('admin/config/user-interface/themekey/properties') . '">admin/config/user-interface/themekey/properties</a>')); + $warned = TRUE; + } + } + + return FALSE; +} + +/** + * @todo Please document this function. + * @see http://drupal.org/node/1354 + */ +function themekey_format_rule_as_string($themekey_property_id) { + module_load_include('inc', 'themekey', 'themekey_build'); + + // fallback title + $title = $themekey_property_id; + + $item = themekey_rule_get($themekey_property_id); + if (!empty($item)) { + $properties = variable_get('themekey_properties', array()); + if (!in_array($item->property, $properties)) { + $item->wildcard = $item->property; + $item->property = 'drupal:path:wildcard'; + } + + $title = '"' . $item->property . ' '; + if (!empty($item->wildcard)) { + $title .= $item->wildcard . ' '; + } + $title .= $item->operator . ' ' . $item->value . ' >>> ' . $item->theme . '"'; + } + + return $title; +} + +/** + * Magic loading of validation and callback functions. + * @see _theme_process_registry() + * + * @return + * TRUE if the function has been loaded, otherwise FALSE + */ +function themekey_load_function($function, $file, $path = '') { + if (!empty($file)) { + if (empty($path)) { + $filename = './' . $file; + if (file_exists($filename)) { + require_once $filename; + if (function_exists($function)) { + return TRUE; + } + } + foreach (module_implements('themekey_properties') as $module) { + if (themekey_load_function($function, $file, drupal_get_path('module', $module))) { + if (function_exists($function)) { + return TRUE; + } + } + } + } + else { + $filename = './' . $path . '/' . $file; + if (file_exists($filename)) { + require_once $filename; + if (function_exists($function)) { + return TRUE; + } + } + } + } + + return FALSE; +} diff --git a/sites/all/modules/themekey/themekey_build.inc b/sites/all/modules/themekey/themekey_build.inc new file mode 100644 index 0000000000000000000000000000000000000000..a4624bbc5341fd745fb96eba3af020c5caf90a20 --- /dev/null +++ b/sites/all/modules/themekey/themekey_build.inc @@ -0,0 +1,477 @@ +<?php + +/** + * @file + * The functions in this file are the back end of ThemeKey which should be + * used only if you configure something, but not when ThemeKey switches themes. + * + * @author Markus Kalkbrenner | Cocomore AG + * @see http://drupal.org/user/124705 + * + * @author profix898 + * @see http://drupal.org/user/35192 + */ + + +/** + * Creates options array for a theme select box. + * + * Example: + * $form['theme'] = array( + * '#type' => 'select', + * '#title' => t('Theme'), + * '#options' => themekey_theme_options(), + * ); + * + * @param $default + * Boolean to indicate if options array should contain + * 'System default' theme. Default is TRUE. + * @param $admin + * Boolean to indicate if options array should contain + * 'Administration theme'. Default is FALSE. + * + * @return + * options array for a theme select box + */ +function themekey_theme_options($default = TRUE, $admin = FALSE) { + $themes = list_themes(); + ksort($themes); + + $options_themes = array(); + if ($default) { + $options_themes['default'] = '=> ' . t('System default'); + } + foreach ($themes as $theme) { + if ($theme->status || variable_get('themekey_allthemes', 0)) { + $options_themes[$theme->name] = $theme->info['name']; + } + } + if ($admin) { + $options_themes['ThemeKeyAdminTheme'] = '=> ' . t('Administration theme'); + } + + return $options_themes; +} + + +/** + * Rebuilds all ThemeKey-related Drupal variables + * by calling ThemeKey's hooks: + * - hook_themekey_properties() + * - hook_themekey_paths() + */ +function themekey_rebuild() { + // includes all modules in the themekey/modules subfolder (internal modules) + themekey_scan_modules(); + + module_load_include('inc', 'themekey', 'themekey_base'); + + // Get property definitions (from internal and other modules) + $properties = array_merge_recursive(themekey_invoke_modules('themekey_properties'), module_invoke_all('themekey_properties')); + + // Attributes + $attributes = isset($properties['attributes']) ? $properties['attributes'] : array(); + ksort($attributes); + + $property_names = array(); + foreach ($attributes as $property_name => $attribute) { + if (empty($attribute['static'])) { + if (preg_match("/^[\w-]+:[:\w-]+$/", $property_name)) { + $property_names[$property_name] = $property_name; + } + else { + drupal_set_message(t('%property is not a valid ThemeKey property name.', array('%property' => $property_name)), 'error'); + } + } + + if (!isset($attribute['page cache'])) { + $attributes[$property_name]['page cache'] = THEMEKEY_PAGECACHE_UNSUPPORTED; + } + } + variable_set('themekey_attributes', $attributes); + variable_set('themekey_properties', $property_names); + + // Property maps + $maps = isset($properties['maps']) ? $properties['maps'] : array(); + variable_set('themekey_maps', $maps); + + // Get (and register) paths from themekey modules + $paths = array_merge_recursive(themekey_invoke_modules('themekey_paths'), module_invoke_all('themekey_paths')); + + // assign fit factor and weight to this item + array_walk($paths, 'themekey_path_set'); + + $paths_sort = array(); + foreach ($paths as $item) { + $paths_sort[$item['fit']][$item['weight']][] = $item; + } + ksort($paths_sort, SORT_NUMERIC); + + $paths = array(); + foreach (array_reverse($paths_sort) as $same_fit) { + ksort($same_fit, SORT_NUMERIC); + foreach (array_reverse($same_fit) as $same_weight) { + foreach ($same_weight as $item) { + $paths[] = $item; + } + } + } + + variable_set('themekey_paths', $paths); +} + + +/** + * Scans directory themekey/modules for suitable files + * which provide ThemeKey properties mapping function and so on + * and stores the file names, for later use, in a Drupal variable + * called 'themekey_modules'. + * + * @see themekey_rebuild() + * @see themekey_invoke_modules() + * + * @param $blacklist + * array of module names that should not be included + */ +function themekey_scan_modules($blacklist = array()) { + $modules = array(); + $files = file_scan_directory(dirname(__FILE__) . '/modules', '/^themekey\.[a-z]+\.inc$/'); + foreach ($files as $file) { + list( , $module) = explode('.', $file->name); + if (!in_array($module, $blacklist) && module_exists($module)) { + $modules[] = $module; + } + } + + variable_set('themekey_modules', $modules); +} + + +/** + * Named wildcards in ThemeKey rules based on property + * drupal:path are stored as serialized array in the database. + * + * This function deserializes those wildcards and injects them back + * into the value of the rule. This format is needed by ThemeKey's + * administration interface. + * + * It's the counterpart of these functions: + * @see themekey_prepare_path() + * @see themekey_prepare_custom_path() + * + * @see themekey_load_rules() + * + * @param $item + * reference to an inject + * containing a ThemeKey rule as returned + * directly from database + */ +function themekey_complete_path($item) { + $item->wildcards = unserialize($item->wildcards); + if (count($item->wildcards)) { + $parts = explode('/', $item->value, MENU_MAX_PARTS); + foreach ($item->wildcards as $index => $wildcard) { + $parts[$index] .= $wildcard; + } + $item->value = implode('/', $parts); + } +} + + +/** + * Examines ThemeKey paths created by modules + * via hook_themekey_paths() in database and + * assigns a fit factor and a weight. + * + * @see themekey_rebuild() + * + * @param $item + * reference to an associative array + * containing a ThemeKey path structure + */ +function themekey_path_set(&$item) { + $item['callbacks'] = (isset($item['callbacks']) && !empty($item['callbacks'])) ? $item['callbacks'] : array(); + + list($item['fit'], $item['weight'], $item['wildcards']) = themekey_prepare_path($item['path']); +} + + +/** + * Extracts named wildcards from ThemeKey paths returned + * by modules via hook_themekey_paths() and associates a + * weight and a fit factor to this path. + * + * @param $item + * reference to path as string + * + * @return + * array containing three elements: + * - fit as integer + * - weight as integer + * - named wildcards as array + */ +function themekey_prepare_path(&$path) { + $fit = 0; + $weight = 0; + $wildcards = array(); + + $parts = explode('/', $path, MENU_MAX_PARTS); + $slashes = count($parts) - 1; + foreach ($parts as $index => $part) { + if (preg_match('/^(\%|\#)([a-z0-9_:]*)$/', $part, $matches)) { + $parts[$index] = $matches[1]; + if (!empty($matches[2])) { + $wildcards[$index] = $matches[2]; + } + if ($matches[1] == '#') { + $weight |= 1 << ($slashes - $index); + } + } + else { + $fit |= 1 << ($slashes - $index); + } + } + $path = implode('/', $parts); + + return array($fit, $weight, $wildcards); +} + + +/** + * Extracts named wildcards from paths entered as value + * in a ThemeKey rule with property drupal:path. + * + * @param $path + * path as string + * + * @return + * array containing two elements: + * - path with unnamed wildcards + * - named wildcards as array + */ +function themekey_prepare_custom_path($path) { + $wildcards = array(); + + $parts = explode('/', $path, MENU_MAX_PARTS); + foreach ($parts as $index => $part) { + if (preg_match('/^(\%|\#)([a-z0-9_:]*)$/', $part, $matches)) { + $parts[$index] = $matches[1]; + if (!empty($matches[2])) { + $wildcards[$index] = $matches[2]; + } + } + } + $path = implode('/', $parts); + + return array($path, $wildcards); +} + + +/** + * Loads all ThemeKey Rules from the database. + * Therefore, it uses recursion to build the rule chains. + * + * @param $parent + * id of the parent rule. Default is '0'. + * During the recursion this parameter changes. + * + * @param $depth + * Integer that represents the 'indentation' + * in current rule chain. Default is '0'. + * During the recursion this parameter changes. + * + * @return + * sorted array containing all ThemeKey rules + */ +function themekey_load_rules($parent = 0, $depth = 0) { + static $properties = array(); + static $parent_lookups = array(); + + if (isset($parent_lookups[$parent])) { + // prevent endless recursion in case of malformated data in database + return $properties; + } + + $result = db_select('themekey_properties', 'tp') + ->fields('tp') + ->condition('parent', $parent) + ->orderBy('weight', 'asc') + ->execute(); + + foreach ($result as $item) { + if ('drupal:path' == $item->property) { + themekey_complete_path($item); + } + $item->depth = $depth; + $properties[$item->id] = get_object_vars($item); + themekey_load_rules($item->id, $depth + 1); + $parent_lookups[$item->id] = TRUE; + } + return $properties; +} + + +/** + * Stores ThemeKey rules in database. + * It creates a new dataset or updates an existing one. + * + * @param $item + * reference to an associative array + * containing a ThemeKey rule structure: + * - id + * - property + * - operator + * - value + * - weight + * - theme + * - enabled + * - wildcards + * - parent + * + */ +function themekey_rule_set(&$item) { + if ('drupal:path' == $item['property']) { + list($item['value'], $item['wildcards']) = themekey_prepare_custom_path($item['value']); + } + else { + $item['wildcards'] = array(); + } + + if (empty($item['id'])) { + + // TRANSACTIONS - SEE http://drupal.org/node/355875 + // The transaction opens here. + $txn = db_transaction(); + + // new entry should be added at the end of the chain + $result = db_select('themekey_properties', 'tp'); + $result->addExpression('MAX(weight)', 'weight'); + $weight = $result->execute()->fetchField(); + + // if query fails $weight will be FALSE which will cause $item['weight'] to be set to '1' + $item['weight'] = 1 + $weight; + drupal_write_record('themekey_properties', $item, array()); + + } + else { + drupal_write_record('themekey_properties', $item, 'id'); + } + + // $txn goes out of scope here, and the entire transaction commits. +} + + +/** + * Deletes a ThemeKey rule from database. + * + * @param $id + * id of the rule to be deleted from database + */ +function themekey_rule_del($id) { + + // TRANSACTIONS - SEE http://drupal.org/node/355875 + // The transaction opens here. + $txn = db_transaction(); + + $result = db_select('themekey_properties', 'tp'); + $result->condition('parent', $id); + $result->addExpression('COUNT(*)', 'num_childs'); + $num_childs = $result->execute()->fetchField(); + + if (FALSE !== $num_childs) { + if ($num_childs > 0) { + drupal_set_message(t('ThemeKey rule could not be deleted because it has children in the chain'), 'error'); + } + else { + $result = db_delete('themekey_properties') + ->condition('id', $id) + ->execute(); + if (!$result) { + drupal_set_message(t('Error while deleting ThemeKey rule'), 'error'); + } + } + } + else { + drupal_set_message(t('Error while deleting ThemeKey rule'), 'error'); + } + + // $txn goes out of scope here, and the entire transaction commits. +} + + +/** + * Loads ThemeKey rule from database. + * + * @param $id + * id of the rule to be loaded from database + * + * @return + * the rule as associative array or NULL + */ +function themekey_rule_get($id) { + + if ($result = db_select('themekey_properties', 'tp') + ->fields('tp') + ->condition('id', $id) + ->execute() + ) { + + foreach ($result as $item) { + if ('drupal:path' == $item->property) { + themekey_complete_path($item); + } + return $item; + } + + } + + return NULL; +} + + +/** + * Adds or modifies a so-called static rule in the + * database. Static rules can be moved around in the chain + * and enabled or disabled by the site administrator, but the values + * are immutable. There's one static rule per static property. + * + * @param $property + * name of the static property as string + * + * @param $state + * boolean: + * - TRUE the rule should be created or updated + * - FALSE the rule should be deleted + */ +function themekey_update_static_rule($property, $state) { + + $id = db_select('themekey_properties', 'tp') + ->fields('tp', array('id')) + ->condition('property', $property) + ->execute() + ->fetchField(); + + + if ($state) { + $item = array( + 'property' => $property, + 'operator' => '=', + 'value' => 'static', + 'theme' => 'default', + ); + + if ($id) { + $item['id'] = $id; + // leave 'enabled' as it is in database + } + else { + // enable new rule + $item['enabled'] = 1; + } + + themekey_rule_set($item); + } + elseif ($id) { + themekey_rule_del($id); + } +} diff --git a/sites/all/modules/themekey/themekey_compat.info b/sites/all/modules/themekey/themekey_compat.info new file mode 100644 index 0000000000000000000000000000000000000000..ef46e1057f93fe42c82d4d51f79985d4afb7b948 --- /dev/null +++ b/sites/all/modules/themekey/themekey_compat.info @@ -0,0 +1,13 @@ +name = "ThemeKey Compatibility" +description = "Integration of different theme switching modules into ThemeKey and it's theme switching rule chain." +core = 7.x +dependencies[] = themekey +package = ThemeKey +configure = admin/config/user-interface/themekey/settings/compat + +; Information added by drupal.org packaging script on 2011-08-17 +version = "7.x-1.4" +core = "7.x" +project = "themekey" +datestamp = "1313612519" + diff --git a/sites/all/modules/themekey/themekey_compat.install b/sites/all/modules/themekey/themekey_compat.install new file mode 100644 index 0000000000000000000000000000000000000000..e8ad0196724399f52a6c537b07e5edbf3e0b5797 --- /dev/null +++ b/sites/all/modules/themekey/themekey_compat.install @@ -0,0 +1,20 @@ +<?php + +/** + * @file + * Cleans up variables when uninstalling + * @see themekey_compyt.module + * + * @author Markus Kalkbrenner | Cocomore AG + * @see http://drupal.org/user/124705 + */ + + +/** + * Implements hook_uninstall(). + */ +function themekey_compat_uninstall() { + // Remove variables + variable_del('themekey_compat_modules_enabled'); + variable_del('themekey_compat_modules_no_default_theme'); +} diff --git a/sites/all/modules/themekey/themekey_compat.module b/sites/all/modules/themekey/themekey_compat.module new file mode 100644 index 0000000000000000000000000000000000000000..1fb45d46d46b17854a5ae94a3d73d7a1fb3f6f83 --- /dev/null +++ b/sites/all/modules/themekey/themekey_compat.module @@ -0,0 +1,123 @@ +<?php + +/** + * @file + * Integration of different theme switching modules into ThemeKey and it's theme switching rule chain. + * @see themekey.module + * + * @author Markus Kalkbrenner | Cocomore AG + * @see http://drupal.org/user/124705 + */ + + +/** + * Implements hook_menu(). + */ +function themekey_compat_menu() { + $items = array(); + $items['admin/config/user-interface/themekey/settings/compat'] = array( + 'title' => 'Compatibility', + 'access callback' => 'user_access', + 'access arguments' => array('administer themekey settings'), + 'file' => 'themekey_compat_admin.inc', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('themekey_compat_settings_form'), + 'type' => MENU_LOCAL_TASK, + 'weight' => 11, + ); + + return $items; +} + + +/** + * Implements hook_themekey_properties(). + * + * Provides additional properties for the ThemeKey module: + * themekey_compat:module_MODULE_triggers_theme + * + * @return + * array of themekey properties + */ +function themekey_compat_themekey_properties() { + + // Attributes for properties + $attributes = array(); + // Mapping functions + $maps = array(); + + foreach (variable_get('themekey_compat_modules_enabled', array()) as $module) { + $attributes['themekey_compat:module_' . $module . '_triggers_theme'] = array( + 'description' => t("The property, themekey_compat:module_' . $module . '_triggers_theme, could not be selected from the property drop-down. You get this static property by activating !link. Afterwards, you can move the property to any position in the rule chain. When done, it triggers the switch to the theme assigned to the current node using ThemeKey UI.", + array('!link' => l(t('Show theme option in create/edit node forms'), 'admin/config/user-interface/themekey/settings/ui'))), + 'page cache' => THEMEKEY_PAGECACHE_SUPPORTED, + 'static' => TRUE, + ); + + $maps[] = array( + 'src' => 'system:dummy', + 'dst' => 'themekey_compat:module_' . $module . '_triggers_theme', + 'callback' => 'themekey_compat_dummy2theme', + 'args' => array('module' => $module), + ); + } + + return array('attributes' => $attributes, 'maps' => $maps); +} + + +/** + * This function implements the interface of a ThemeKey + * mapping function but doesn't set a ThemeKey property's + * value. It sets the Drupal static themekey_custom_theme + * which will cause ThemeKey to use this theme. + * + * @param $dummy + * dummy + * + * @return + * string "static" if global custom theme has been set + * or NULL if no theme has been assigned to the node + */ +function themekey_compat_dummy2theme($dummy, $args) { + $custom_theme = &drupal_static('themekey_custom_theme', ''); + + $function = $args['module'] . '_custom_theme'; + if (function_exists($function)) { + $custom_theme = $function(); + + if ($custom_theme && + (!in_array($args['module'], variable_get('themekey_compat_modules_enabled', array())) || variable_get('theme_default', 'bartik') != $custom_theme)) { + if (variable_get('themekey_debug_trace_rule_switching', FALSE)) { + themekey_set_debug_message('Theme set to %theme by module %module.', array('%theme' => $custom_theme, '%module' => $args['module'])); + } + return 'static'; + } + } + + return NULL; +} + + +/** + * Implements hook_modules_disabled(). + */ +function themekey_compat_modules_disabled($modules) { + require_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'themekey') . '/themekey_build.inc'; + + variable_set('themekey_compat_modules_enabled', + array_diff(variable_get('themekey_compat_modules_enabled', array()), $modules)); + + foreach ($modules as $module) { + themekey_update_static_rule('themekey_compat:module_' . $module . '_triggers_theme', FALSE); + } +} + + +function themekey_compat_module_implements_alter(&$implementations, $hook) { + if ('custom_theme' == $hook) { + foreach (variable_get('themekey_compat_modules_enabled', array()) as $module) { + unset($implementations['$module']); + } + } +} \ No newline at end of file diff --git a/sites/all/modules/themekey/themekey_compat_admin.inc b/sites/all/modules/themekey/themekey_compat_admin.inc new file mode 100644 index 0000000000000000000000000000000000000000..483a4d092bf9f1ee5a6f9a0e9dd6c0c32210710b --- /dev/null +++ b/sites/all/modules/themekey/themekey_compat_admin.inc @@ -0,0 +1,121 @@ +<?php + +/** + * @file + * + * @author Markus Kalkbrenner | Cocomore AG + * @see http://drupal.org/user/124705 + */ + + +/** + * Form builder for the form to enable modules for ThemeKey compatibility mode. + * + * @ingroup forms + */ +function themekey_compat_settings_form() { + $form = array(); + + $modules = module_implements('custom_theme'); + $modules = array_diff($modules, array('themekey')); + + if (!empty($modules)) { + $modules_list = system_list('module_enabled'); + $options = array(); + foreach ($modules as $module) { + $name = $module; + if (isset($modules_list[$module]->info) && !empty($modules_list[$module]->info['name'])) { + $name = $modules_list[$module]->info['name']; + } + if ('system' == $module) { + $name .= ' (' . t('Administration theme') . ')'; + } + $options[$module] = $name; + } + + $form['themekey_compat']['modules_enabled'] = array( + '#type' => 'fieldset', + '#title' => t('Modules'), + '#collapsible' => TRUE, + '#collapsed' => FALSE, + ); + + $form['themekey_compat']['modules_enabled']['themekey_compat_modules_enabled'] = array( + '#type' => 'checkboxes', + '#title' => t('Integrate Modules in Theme Switching Rule Chain'), + '#default_value' => variable_get('themekey_compat_modules_enabled', array()), + '#options' => $options, + ); + + $form['themekey_compat']['modules_no_default_theme'] = array( + '#type' => 'fieldset', + '#title' => t('No Default Theme'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + ); + + $form['themekey_compat']['modules_no_default_theme']['themekey_compat_modules_no_default_theme'] = array( + '#type' => 'checkboxes', + '#title' => t('Ignore the theme decision of these modules if they select the default theme'), + '#default_value' => variable_get('themekey_compat_modules_no_default_theme', array()), + '#options' => $options, + ); + + $form['buttons']['submit'] = array( + '#type' => 'submit', + '#value' => t('Save configuration'), + ); + } + else { + $form['themekey_compat']['no_module'] = array( + '#type' => 'markup', + '#value' => t('No different theme switching module installed.'), + ); + } + + return $form; +} + +/** + * Form submission handler for themekey_compat_settings_form(). + * + * @see themekey_compat_settings_form() + */ +function themekey_compat_settings_form_submit($form, &$form_state) { + $modules_enabled = array(); + $modules_no_default_theme = array(); + + foreach ($form_state['values']['themekey_compat_modules_enabled'] as $module => $enabled) { + if ($enabled) { + $modules_enabled[] = $module; + } + } + + variable_set('themekey_compat_modules_enabled', $modules_enabled); + + if (!empty($modules_enabled)) { + drupal_set_message(t('You integrated some modules in Theme Switching Rule Chain. !link', + array('!link' => l(t('Review the Theme Switching Rule Chain.'), 'admin/config/user-interface/themekey/properties'))), + 'warning'); + } + + foreach ($form_state['values']['themekey_compat_modules_no_default_theme'] as $module => $enabled) { + if ($enabled) { + $modules_no_default_theme[] = $module; + } + } + + variable_set('themekey_compat_modules_no_default_theme', $modules_no_default_theme); + + module_load_include('inc', 'themekey', 'themekey_build'); + + themekey_rebuild(); + + foreach ($form_state['values']['themekey_compat_modules_enabled'] as $module => $enabled) { + themekey_update_static_rule('themekey_compat:module_' . $module . '_triggers_theme', $enabled); + } + + cache_clear_all('module_implements', 'cache_bootstrap'); + + drupal_set_message(t('The configuration options have been saved.')); +} diff --git a/sites/all/modules/themekey/themekey_cron.inc b/sites/all/modules/themekey/themekey_cron.inc new file mode 100644 index 0000000000000000000000000000000000000000..a028ea6bb71da061ed078cec59a2d084d0200d79 --- /dev/null +++ b/sites/all/modules/themekey/themekey_cron.inc @@ -0,0 +1,60 @@ +<?php + +/** + * @file + * Provides everything that ThemeKey has to do when cron runs + * + * @author Markus Kalkbrenner | Cocomore AG + * @see http://drupal.org/user/124705 + */ + +/** + * Checks rules containing time-based properties when cron runs. + * ThemeKey will carefully clean up the page cache, if necessary, + * to provide the right theme to anonymous users automatically, + * e.g. a Christmas theme. + */ +function themekey_cron_clear_page_cache() { + $clear_page_cache = FALSE; + $rules_processed_new = array(); + + if ($result = db_select('themekey_properties', 'tp') + ->fields('tp') + ->condition('enabled', 1) + ->execute() + ) { + module_load_include('inc', 'themekey', 'themekey_base'); + + $rules_processed = variable_get('themekey_cron_rules_processed', array()); + $attributes = variable_get('themekey_attributes', array()); + $parameters = themekey_get_global_parameters(); + + foreach ($result as $item) { + + if (THEMEKEY_PAGECACHE_TIMEBASED == $attributes[$item->property]['page cache']) { + $md5 = md5(serialize($item)); // use md5 instead of item id, because we want a track if an item's weight changes + $match = themekey_match_condition($item, $parameters); + $processed = in_array($md5, $rules_processed); + if ($match && !$processed) { + $clear_page_cache = TRUE; + $rules_processed_new[] = $md5; + } + elseif (!$match && $processed) { + $clear_page_cache = TRUE; + } + elseif ($match && $processed) { + $rules_processed_new[] = $md5; + } + } + + } + + } + + if ($clear_page_cache) { + // fast deletion of page cache (truncate) + cache_clear_all('*', 'cache_page', TRUE); + } + + variable_set('themekey_cron_rules_processed', $rules_processed_new); +} diff --git a/sites/all/modules/themekey/themekey_debug.info b/sites/all/modules/themekey/themekey_debug.info new file mode 100644 index 0000000000000000000000000000000000000000..bff7e3be342a65dd1169cc03c3b132f9b6fb949e --- /dev/null +++ b/sites/all/modules/themekey/themekey_debug.info @@ -0,0 +1,14 @@ + +name = "ThemeKey Debug" +description = "Debug ThemeKey" +core = 7.x +dependencies[] = themekey +package = ThemeKey +configure = admin/config/user-interface/themekey/settings/debug + +; Information added by drupal.org packaging script on 2011-08-17 +version = "7.x-1.4" +core = "7.x" +project = "themekey" +datestamp = "1313612519" + diff --git a/sites/all/modules/themekey/themekey_debug.install b/sites/all/modules/themekey/themekey_debug.install new file mode 100644 index 0000000000000000000000000000000000000000..5597480976d5b451262f68082b80c21b5a512587 --- /dev/null +++ b/sites/all/modules/themekey/themekey_debug.install @@ -0,0 +1,24 @@ +<?php + +/** + * @file + * Cleans up variables when uninstalling + * @see themekey_debug.module + * + * @author Markus Kalkbrenner | Cocomore AG + * @see http://drupal.org/user/124705 + */ + + +/** + * Implements hook_uninstall(). + */ +function themekey_debug_uninstall() { + // Remove variables + // TODO Please review the conversion of this statement to the D7 database API syntax. + /* db_query("DELETE FROM {variable} WHERE name LIKE 'themekey_debug_%%'") */ + db_delete('variable') + ->condition('name', 'themekey_debug_%%', 'LIKE') + ->execute(); + cache_clear_all('variables', 'cache'); +} diff --git a/sites/all/modules/themekey/themekey_debug.module b/sites/all/modules/themekey/themekey_debug.module new file mode 100644 index 0000000000000000000000000000000000000000..7de9f58ee411300e93f9c6a71c31640cb2621af3 --- /dev/null +++ b/sites/all/modules/themekey/themekey_debug.module @@ -0,0 +1,234 @@ +<?php + +/** + * @file + * Provides a debug mode for module ThemeKey. + * @see themekey.module + * + * @author Markus Kalkbrenner | Cocomore AG + * @see http://drupal.org/user/124705 + */ + + +/** + * Implements hook_permission(). + */ +function themekey_debug_permission() { + return array( + 'themekey_debug_see_messages' => array( + 'title' => t('See ThemeKey debug messages'), + 'description' => t('Using this permission debug messages will be displayed to users in that role.'), + ), + ); +} + + +/** + * Iterates over all ThemeKey Properties and prints + * out their current values. + */ +function themekey_debug_properties() { + global $user; + + if (1 == $user->uid || user_access('themekey_debug_see_messages', $user) || variable_get('themekey_debug_non_admin_users', FALSE)) { + module_load_include('inc', 'themekey', 'themekey_base'); + + $properties = variable_get('themekey_properties', array()); + $message = t('These are the current values of all available ThemeKey Properties. By clicking the value you can start creating a corresponding Theme Switching Rule.') . '<ul>'; + $parameters = themekey_get_global_parameters(); + + foreach ($properties as $property) { + if (!isset($parameters[$property])) { + themekey_property_value($parameters, $property); + } + + $value = ''; + if (is_null($parameters[$property])) { + if ('drupal:path' == $property) { + $value = '<em>no debug information</em>'; + } + else { + $value = '<em>empty</em>'; + } + } + else { + $values = is_array($parameters[$property]) ? $parameters[$property] : array($parameters[$property]); + $links = array(); + foreach ($values as $single_value) { + // Don't use the l() function at this early stage of bootstrapping because it will initialize the theme engine. Use url() instead. + $links[] = '<a href="' . url('admin/config/user-interface/themekey', array('fragment' => 'themekey_new_rule', 'query' => array('property' => $property, 'value' => $single_value))) . '">' . $single_value . '</a>'; + } + + $value = implode('<br />', $links); + } + $message .= '<li>' . $property . '<br />' . $value . '</li><br />'; + } + $message .= '</ul>'; + themekey_set_debug_message($message, array(), FALSE); + } +} + + +/** + * Implements hook_menu(). + */ +function themekey_debug_menu() { + $items = array(); + $items['admin/config/user-interface/themekey/settings/debug'] = array( + 'title' => 'Debug', + 'access callback' => 'user_access', + 'access arguments' => array('administer themekey settings'), + 'file' => 'themekey_debug_admin.inc', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('themekey_debug_settings_form'), + 'type' => MENU_LOCAL_TASK, + 'weight' => 10, + ); + + return $items; +} + + +/** + * Implements hook_theme(). + */ +function themekey_debug_theme() { + $items = array( + 'themekey_debug_messages' => array( + 'template' => 'themekey-debug-messages', + 'variables' => array('messages' => array()), + ), + ); + return $items; +} + + +/** + * Implements hook_init(). + * + * Detects if hook_custom_theme() has been skipped because + * a different module already initialized the theme engine. + */ +function themekey_debug_init() { + if (variable_get('themekey_debug_trace_rule_switching', FALSE)) { + if (!in_array('system', variable_get('themekey_compat_modules_enabled', array())) && path_is_admin($_GET['q']) && variable_get('admin_theme', '0')) { + themekey_set_debug_message('"%admin_theme" is configured as administration theme at !link. This setting is more powerful than a corresponding ThemeKey rule.', + array('%admin_theme' => variable_get('admin_theme', '0'), '!link' => l(t('!path', array('!path' => 'admin/appearance')), 'admin/appearance')), TRUE, TRUE); + } + + $custom_theme_called = &drupal_static('themekey_custom_theme_called', FALSE); + + if (!$custom_theme_called) { + themekey_set_debug_message("Consider to activate the module ThemeKey Compatibility to integrate a different theme switching module into ThemeKey's theme switching rule chain", + array(), TRUE, TRUE); + themekey_set_debug_message('Skipped rule checking because another module already initialized the theme engine.', + array(), TRUE, TRUE); + } + else { + global $theme; + $themekey_custom_theme = &drupal_static('themekey_custom_theme', ''); + if (!empty($themekey_custom_theme) && 'default' != $themekey_custom_theme) { + if ($theme != $themekey_custom_theme) { + themekey_set_debug_message('Theme switching to custom theme "%custom_theme" did not work because theme has been set to "%theme" by another module.', array('%custom_theme' => $themekey_custom_theme, '%theme' => $theme), TRUE, TRUE); + } + } + else { + if (variable_get('theme_default', 'bartik') != $theme) { + themekey_set_debug_message('ThemeKey did not switch the theme because no rule matched. But something else set the theme to "%theme".', array('%theme' => $theme), TRUE, TRUE); + } + } + } + } +} + + +/** + * Implements hook_form_node_form_alter(). + * + * Detects if custom theme has been skipped because + * the adminstration theme is used for node editing. + */ +function themekey_debug_form_node_form_alter() { + if (variable_get('themekey_debug_trace_rule_switching', FALSE)) { + if (variable_get('node_admin_theme', '0')) { + themekey_set_debug_message('As configured at !link adding or editing a node will use the administration theme %admin_theme.', + array('%admin_theme' => variable_get('admin_theme', '0'), '!link' => l(t('!path', array('!path' => 'admin/appearance')), 'admin/appearance')), TRUE, TRUE); + } + } +} + + +/** + * Implements hook_page_alter(). + * + * Prints out debug messages at the end of the page. + * + * @return string + */ +function themekey_debug_page_alter(&$page) { + if ($messages = themekey_set_debug_message('flush')) { + $page['page_bottom']['themekey']= array( + '#type' => 'markup', + '#markup' => $messages, + ); + } +} + + +/** + * Replacement for drupal_set_message() during ThemeKey's initialization. + * drupal_set_message() might inititialize the theme engine too early, + * which causes ThemeKey to not switch the theme. + * + * themekey_debug_set_debug_message() put the untranslated messages on a + * stack and hand them over to drupal_set_message() on demand. + * + * @param $msg + * the message as string. If the message is 'flush' + * all messages stored on the stack will be printed using + * drupal_set_message() + * + * @param $placeholder + * associative array of string replacments for $msg + * @see t() + * + * @param $translate + * boolean, if set to TRUE $msg will be handled by t() + * when handed over to drupal_set_message() + */ +function themekey_debug_set_debug_message($msg, $placeholder = array(), $translate = TRUE, $unshift = FALSE) { + static $msg_stack = array(); + global $user, $theme; + + if (1 == $user->uid || user_access('themekey_debug_see_messages', $user) || variable_get('themekey_debug_non_admin_users', FALSE)) { + if ('flush' == $msg) { + $messages = array(); + if (variable_get('themekey_debug_trace_rule_switching', FALSE)) { + $messages[] = t('Current theme: %theme', array('%theme' => $theme)); + } + foreach ($msg_stack as $key => $msg) { + $messages[] = filter_xss($msg['translate'] ? t($msg['msg'], $msg['placeholder']) : $msg['msg'], array('a', 'b', 'br', 'li', 'ul')); + unset($msg_stack[$key]); + } + + if (!empty($messages)) { + return theme('themekey_debug_messages', array('messages' => $messages)); + } + } + else { + $tmp = array( + 'msg' => $msg, + 'placeholder' => $placeholder, + 'translate' => $translate, + ); + + if ($unshift) { + $tmp['msg'] = '<b>' . $tmp['msg'] . '</b>'; + array_unshift($msg_stack, $tmp); + } + else { + $msg_stack[] = $tmp; + } + } + } +} diff --git a/sites/all/modules/themekey/themekey_debug_admin.inc b/sites/all/modules/themekey/themekey_debug_admin.inc new file mode 100644 index 0000000000000000000000000000000000000000..0db88e1529dbb2f2c73b40302d38e483251bcfce --- /dev/null +++ b/sites/all/modules/themekey/themekey_debug_admin.inc @@ -0,0 +1,51 @@ +<?php + +/** + * @file + * provides a debug mode for module ThemeKey. + * @see themekey.module + * + * @author Markus Kalkbrenner | Cocomore AG + * @see http://drupal.org/user/124705 + */ + + +/** + * Form builder for the form to enable ThemeKey debug mode. + * + * @ingroup forms + */ +function themekey_debug_settings_form() { + $form = array(); + $form['themekey_debug'] = array( + '#type' => 'fieldset', + '#title' => t('Debug Settings'), + '#collapsible' => FALSE, + '#collapsed' => FALSE, + '#description' => t("Debug information will only be visible to the site's administrator."), + ); + + $form['themekey_debug']['themekey_debug_show_property_values'] = array( + '#type' => 'checkbox', + '#title' => t('Show values of ThemeKey properties'), + '#default_value' => variable_get('themekey_debug_show_property_values', FALSE), + '#description' => t('Shows current values of ThemeKey properties at the bottom of every page. Additionally you can start creating a corresponding Theme Switching Rule by clicking on a value.'), + ); + + $form['themekey_debug']['themekey_debug_trace_rule_switching'] = array( + '#type' => 'checkbox', + '#title' => t('Trace ThemeKey rule switching'), + '#default_value' => variable_get('themekey_debug_trace_rule_switching', FALSE), + '#description' => t('Prints out detailed information about matching or non-matching rules.'), + ); + + $form['themekey_debug']['themekey_debug_non_admin_users'] = array( + '#type' => 'checkbox', + '#title' => t('Show debug information to all users'), + '#default_value' => variable_get('themekey_debug_non_admin_users', FALSE), + '#description' => t('All debug output will be shown to all users, including anonymous users. Be careful in production environments and turn off page caching first!'), + ); + + + return system_settings_form($form); +} diff --git a/sites/all/modules/themekey/themekey_example/themekey_example.info b/sites/all/modules/themekey/themekey_example/themekey_example.info new file mode 100644 index 0000000000000000000000000000000000000000..8f1d265023b26e980e55de5c1689954dfa2db4d4 --- /dev/null +++ b/sites/all/modules/themekey/themekey_example/themekey_example.info @@ -0,0 +1,17 @@ + +name = "ThemeKey Example" +description = "Implements parts of the ThemeKey API as an example for Developers." +core = 7.x +dependencies[]= themekey +package = Development + +files[] = themekey_example.module +files[] = themekey_example_validators.inc +files[] = themekey_example_mappers.inc + +; Information added by drupal.org packaging script on 2011-08-17 +version = "7.x-1.4" +core = "7.x" +project = "themekey" +datestamp = "1313612519" + diff --git a/sites/all/modules/themekey/themekey_example/themekey_example.module b/sites/all/modules/themekey/themekey_example/themekey_example.module new file mode 100644 index 0000000000000000000000000000000000000000..6387f386be4c69dbbabcfec09cf5e511591d8705 --- /dev/null +++ b/sites/all/modules/themekey/themekey_example/themekey_example.module @@ -0,0 +1,96 @@ +<?php + + +/** + * @file + * ThemeKey Example demonstrates the usage + * of ThemeKeys API to add more properties + * to ThemeKey. + */ + + +/** + * Implements hook_themekey_properties(). + * + * A mapping function declared here only runs + * if really required during a page request. + * + * So complicated, time and memory consuming + * properties should be added to ThemeKey + * by implementing hook_themekey_properties(). + */ +function themekey_example_themekey_properties() { + // Attributes of properties + $attributes = array(); + + $attributes['example:number_one'] = array( + 'description' => t('Example: always returns "1"'), + 'validator' => 'themekey_example_validator_number_one', + 'file' => 'themekey_example_validators.inc', + 'page cache' => THEMEKEY_PAGECACHE_SUPPORTED, + ); + + $attributes['example:global_one'] = array( + 'description' => t('Example: always returns "1"'), + 'validator' => 'themekey_example_validator_number_one', + 'file' => 'themekey_example_validators.inc', + 'page cache' => THEMEKEY_PAGECACHE_SUPPORTED, + ); + + $attributes['example:path_number'] = array( + 'description' => t('Example: always returns "1"'), + 'validator' => 'themekey_validator_ctype_digit', + 'page cache' => THEMEKEY_PAGECACHE_SUPPORTED, + ); + + // Mapping functions + $maps = array(); + + $maps[] = array( + 'src' => 'system:dummy', + 'dst' => 'example:number_one', + 'callback' => 'themekey_example_dummy2number_one', + 'file' => 'themekey_example_mappers.inc', + ); + + return array('attributes' => $attributes, 'maps' => $maps); +} + + +/** + * Implements hook_themekey_global(). + * + * This function sets some properties + * on every page request. + * + * So only easy stuff with low time and memory + * consumtion should be done by + * implementing hook_themekey_global(). + */ +function themekey_example_themekey_global() { + + $parameters = array(); + + $parameters['example:global_one'] = "1"; + + return $parameters; +} + + +/** + * Implements hook_themekey_paths(). + * + * This function sets some properties + * on every page request. + * + * Using this function you directly map parts of + * the path to property values. + */ +function themekey_example_themekey_paths() { + $paths = array(); + + // a path like 'example/27/foo will set property example:path_number to '27' + $paths[] = array('path' => 'example/#example:path_number'); + + return $paths; +} diff --git a/sites/all/modules/themekey/themekey_example/themekey_example_mappers.inc b/sites/all/modules/themekey/themekey_example/themekey_example_mappers.inc new file mode 100644 index 0000000000000000000000000000000000000000..41aae3ee6a46adddc181a2d06bf954f06e64c8f2 --- /dev/null +++ b/sites/all/modules/themekey/themekey_example/themekey_example_mappers.inc @@ -0,0 +1,26 @@ +<?php + + +/** + * @file + * Mapping functions of ThemeKey Example. + */ + + +/** + * ThemeKey mapping function to set a + * ThemeKey property's value (destination) + * with the aid of another ThemeKey property (source). + * + * src: system:dummy + * dst: example:number_one + * + * @param $dummy + * string containing current value of ThemeKey property system:dummy + * + * @return + * string "1" + */ +function themekey_example_dummy2number_one($dummy) { + return "1"; +} diff --git a/sites/all/modules/themekey/themekey_example/themekey_example_validators.inc b/sites/all/modules/themekey/themekey_example/themekey_example_validators.inc new file mode 100644 index 0000000000000000000000000000000000000000..c5adcece65e2bb182182abd21c8353174d9e8944 --- /dev/null +++ b/sites/all/modules/themekey/themekey_example/themekey_example_validators.inc @@ -0,0 +1,57 @@ +<?php + + +/** + * @file + * Validating functions of ThemeKey Example. + */ + + +/** + * Validates a Theme Switching Rule. + * Allowed Operators: "=", "!", "<", "<=", ">", ">=" + * Allowed values: string of digits (numbers) + * + * + * @param $rule + * A Theme Switching Rule as associative array: + * - property: ThemeKey property as string (e.g., "drupal:path") + * - wildcard: optional string, only used if property is "drupal:path:wildcard" + * - operator: ThemeKey operator as string ("=", "!", "<". "<=", ">", ">=", "~") + * - value: ThemeKey property value as string + * + * @return + * An associative array of errors: + * - property: translated error message as string + * describing a problem with the property + * - wildcard: translated error message as string + * describing a problem with the wildcard + * - operator: translated error message as string + * describing a problem with the operator + * - value: translated error message as string + * describing a problem with the value + * If no errors detected the array is empty. + */ +function themekey_example_validator_number_one($rule) { + $errors = array(); + + switch ($rule['operator']) { + case '~': + $errors['operator'] = t('Possible operators are "=", "!", "<", "<=", ">" and ">="'); + break; + + case '=': + if ('1' !== $rule['value']) { + $errors['value'] = t('No other value than "1" makes sense for this operator'); + } + break; + + default: + if (!ctype_digit($rule['value'])) { + $errors['value'] = t('Value must be a number'); + } + break; + } + + return $errors; +} diff --git a/sites/all/modules/themekey/themekey_features.info b/sites/all/modules/themekey/themekey_features.info new file mode 100644 index 0000000000000000000000000000000000000000..6e8813ae009ea26f9114a23c70b714ae62a8783e --- /dev/null +++ b/sites/all/modules/themekey/themekey_features.info @@ -0,0 +1,16 @@ + +name = "ThemeKey Features (Experimental!)" +description = "Integrates ThemeKey with Features. Warning! Don't use in production! Highly Experimental!" +core = 7.x +dependencies[]= themekey +dependencies[]= features +package = ThemeKey + + + +; Information added by drupal.org packaging script on 2011-08-17 +version = "7.x-1.4" +core = "7.x" +project = "themekey" +datestamp = "1313612519" + diff --git a/sites/all/modules/themekey/themekey_features.module b/sites/all/modules/themekey/themekey_features.module new file mode 100644 index 0000000000000000000000000000000000000000..d20a2d3bc536deeb84e14d5c5a906462dab7a256 --- /dev/null +++ b/sites/all/modules/themekey/themekey_features.module @@ -0,0 +1,170 @@ +<?php + +/** + * Implements hook_features_api(). + */ +function themekey_features_features_api() { + return array( + 'themekey_features_rule_chain' => array( + 'name' => t('ThemeKey Rule Chain'), + 'default_hook' => 'themekey_features_rule_chain_import', + ), + ); +} + + +/** + * Implements hook_features_export_options(). + */ +function themekey_features_rule_chain_features_export_options() { + $options = array(); + $rules = themekey_features_load_rule_childs(); + + if (!empty($rules)) { + foreach ($rules as $rule) { + $options[md5(serialize($rule))] = $rule['string']; + } + } + + return $options; +} + + +/** + * Implements hook_features_export(). + */ +function themekey_features_rule_chain_features_export($data, &$export, $module_name = '') { + $export['dependencies']['features'] = 'features'; + $export['dependencies']['themekey_features'] = 'themekey_features'; + // TODO set dependencies to providers of each single themekey property + + foreach ($data as $rule_md5) { + $export['features']['themekey_features_rule_chain'][$rule_md5] = $rule_md5; + } + + return array(); +} + + +/** + * Implements hook_features_export_render(). + */ +function themekey_features_rule_chain_features_export_render($module_name, $data, $export = NULL) { + $rules = themekey_features_load_rule_childs(); + $keep_rules = array(); + + foreach ($rules as $rule) { + if (in_array(md5(serialize($rule)), $data)) { + $keep_rules[] = $rule; + } + } + + $code = array(); + $code[] = "if (!defined('THEMEKEY_PAGECACHE_UNSUPPORTED')) { + define('THEMEKEY_PAGECACHE_UNSUPPORTED', 0); + define('THEMEKEY_PAGECACHE_SUPPORTED', 1); + define('THEMEKEY_PAGECACHE_TIMEBASED', 2); + }"; + $code[] = '$rules = ' . features_var_export($keep_rules) . ';'; + $code[] = ''; + $code[] = 'return $rules;'; + + + + return array('themekey_features_rule_chain_import' => implode("\n", $code)); +} + + +/** + * Implements hook_features_rebuild(). + */ +function themekey_features_rule_chain_features_rebuild($module) { + module_load_include('inc', 'themekey', 'themekey_build'); + + db_truncate('themekey_properties'); + themekey_rebuild(); + + $rules = module_invoke($module, 'themekey_features_rule_chain_import'); + + themekey_features_save_rule_childs($rules); + + // fast deletion of page cache (truncate) + cache_clear_all('*', 'cache_page', TRUE); + + return TRUE; +} + + +/** + * Implements hook_features_revert(). + */ +function themekey_features_rule_chain_features_revert($module) { + return themekey_features_rule_chain_features_rebuild($module); +} + +/** + * Loads current ThemeKey Rule Chain as array. + * + * @param $parent + * internal use in recursion + * + * @return + * serialized ThemeKey Rule Chain as array + */ +function themekey_features_load_rule_childs($parent = 0) { + module_load_include('inc', 'themekey', 'themekey_base'); + module_load_include('inc', 'themekey', 'themekey_build'); + + $rules = array(); + + if ($result = db_select('themekey_properties', 'tp') + ->fields('tp', array('id')) + ->condition('parent', $parent) + ->orderBy('weight', 'ASC') + ->execute() + ) { + + foreach ($result as $record) { + + // we have to load the rule again using themekey_rule_get() which applies some transformations + $rule = themekey_rule_get($record->id); + if ('drupal:path' != $rule->property) { + themekey_complete_path($rule); + } + unset($rule->id); + unset($rule->parent); + unset($rule->weight); + + $rules[] = array( + 'rule' => $rule, + 'string' => themekey_format_rule_as_string($record->id), + 'childs' => themekey_features_load_rule_childs($record->id), + ); + + } + + } + + return $rules; +} + + +/** + * Takes a serialized ThemeKey Rule Chain as created by + * themekey_features_load_rule_childs() and replaces the current + * one in the database eith it. + * + * @param $childs + * serialized ThemeKey Rule Chain as array + * @param $parent + * internal use in recursion + */ +function themekey_features_save_rule_childs($childs, $parent = 0) { + module_load_include('inc', 'themekey', 'themekey_build'); + + foreach ($childs as $child) { + $child['rule']['parent'] = $parent; + themekey_rule_set($child['rule']); + themekey_features_save_rule_childs($child['childs'], $child['rule']['id']); + } +} diff --git a/sites/all/modules/themekey/themekey_help.inc b/sites/all/modules/themekey/themekey_help.inc new file mode 100644 index 0000000000000000000000000000000000000000..0eddf40a91da5e6ffcd56c1171f0e908683a439e --- /dev/null +++ b/sites/all/modules/themekey/themekey_help.inc @@ -0,0 +1,329 @@ +<?php + +/** + * @file + * Provides content for help pages. + * + * @author Markus Kalkbrenner | Cocomore AG + * @see http://drupal.org/user/124705 + */ + + +/** + * Uses D rupal's form builder to format ThemeKey's help tutorials + * + * @see themekey_help() + */ +function themekey_help_tutorials_form($form, &$form_state, $collapsed) { + $form['themekey_help_tutorials'] = array( + '#type' => 'fieldset', + '#title' => t('Tutorials'), + '#collapsible' => TRUE, + '#collapsed' => $collapsed, + ); + + $form['themekey_help_tutorials']['Rule Chaining: Using a special theme for content creation depending on user roles'] = array( + '#type' => 'fieldset', + '#title' => t('Rule Chaining: Using a special theme for content creation depending on user roles'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + ); + + $form['themekey_help_tutorials']['Rule Chaining: Using a special theme for content creation depending on user roles']['author'] = array( + '#type' => 'item', + '#title' => t('Author'), + '#markup' => l(t('!path', array('!path' => 'mkalkbrenner')), 'http://drupal.org/user/124705'), + ); + + $img_path = base_path() . drupal_get_path('module', 'themekey') . '/img/tutorials/'; + + $form['themekey_help_tutorials']['Rule Chaining: Using a special theme for content creation depending on user roles']['item'] = array( + '#type' => 'item', + '#markup' => '<p>' . t('Note: This tutorial uses some ThemeKey properties from an additional module called !themekey_properties_link.', array('!themekey_properties_link' => l(t('!path', array('!path' => 'ThemeKey Properties')), 'http://drupal.org/project/themekey_properties'))) . '</p>' . +'<p>' . t("In this tutorial you'll learn how to define ThemeKey rules and how to cascade them to achieve sophisticated rule chains.") . '</p>' . +'<p>' . t("The use case is to use a different theme during content creation for premium users, e.g., you don't want to show advertisements or you want to show some advanced help blocks ...") . '</p>' . +'<p>' . t('Therefore, you created two user roles called "premium user" and "standard user". Using ThemeKey it\'s easy to create a rule that switches the theme if a user\'s role is "premium user":') . '<br />' . +'<img src="' . $img_path . 'premium_user.png" alt="' . t('ThemeKey rule that switches the theme if the user\'s role is "premium user"') . '" /></p>' . +'<p>' . t('Similarly, we can create a rule that switches the theme if a content creation form is requested. To do this for any kind of content we use ThemeKey\'s drupal:path property and its wildcard feature:') . '<br />' . +'<img src="' . $img_path . 'create_content.png" alt="' . t('ThemeKey rule that switches the theme on content creation forms') . '" /></p>' . +'<p>' . t('But having these two separate rules is not what our use case specified. To implement our use case, we have to cascade or "chain" both rules. By clicking and dragging the cross icon in front of a rule, you can move any rule up and down, or indent it. The chain we need should like this:') . '<br />' . +'<img src="' . $img_path . 'create_content_premium_user.png" alt="' . t('ThemeKey rule chain that switches the theme on content creation forms for premium users') . '" /></p>' . +'<p>' . t('Now using this chain, ThemeKey only switches the theme on content creation forms if the current user\'s role is "premium user". Non-premium users will use the current system standard theme.') . '</p>' . +'<p>' . t('You might have noticed that the theme select box for the first rule disappeared as you indented the second one. That\'s because both separate rules became one.') . '</p>' . +'<p>' . t('Now it\'s possible to extend our chain to use another dedicated theme on content creation forms for "standard users".') . '<br />' . +'<img src="' . $img_path . 'create_content_premium_user_standard_user.png" alt="' . t('ThemeKey rule chain that switches to different themes on content creation forms for different user roles') . '" /></p>' . +'<p>' . t('If you have more than these two roles in your system and you simply want to use one theme for premium users and another for non-premium users on content creation pages, you don\'t need to add one rule per role, as decribed above. In this case, it\'s easier to change the latest rule we added, and say any other rule than "premium user", using ThemeKey\'s not operator "!".') . '<br />' . +'<img src="' . $img_path . 'create_content_premium_user_any_role.png" alt="' . t('Another ThemeKey rule chain that switches to different themes on content creation forms for different user roles') . '" /></p>' . +'<p>' . t('Now we have implemented our use case using chained ThemeKey rules, and you can add more rules to ThemeKey\'s Rule Chain to implement different use cases. The only decision you have to make is the order the rules are checked by ThemeKey on every page request. To demonstrate this, let\'s add another rule that switches the theme if the user uses an iPhone to access your page.') . '<br />' . +'<img src="' . $img_path . 'iphone_create_content_premium_user_any_role.png" alt="' . t('ThemeKey rule chain selecting special theme for iPhones') . '" /></p>' . +'<p>' . t('What happens now is that ThemeKey switches to a special theme for iPhones whenever the user accesses your page using such a device, because ThemeKey stops processing the rules if a rule, or one set of chained rules, matches. This means that content creation forms are shown using the iPhone theme, regardless of the role assigned to the current user.') . '</p>' . +'<p>' . t('I think that\'s a good choice. If you move the iPhone rule below the chain for the content cration form these forms will use the configured themes even on the iPhone, which might not be suitable for displaying them.') . '</p>' . +'<p>' . t('If you\'d like to treat node creation forms differently, even on the iPhone, you should "chain" a dedicated set of rules for that.') . '<br />' . +'<img src="' . $img_path . 'iphone_create_content_pages.png" alt="' . t('Sophisticated ThemeKey rule chain') . '" /></p>' . +'<p>' . t('Have Fun!') . '</p>', + ); + + $form['themekey_help_tutorials']['Rule Chaining: Using a special theme for content creation depending on user roles']['versions'] = array( + '#type' => 'fieldset', + '#title' => t('Versions used to create this tutorial'), + '#collapsible' => FALSE, + ); + + $form['themekey_help_tutorials']['Rule Chaining: Using a special theme for content creation depending on user roles']['versions']['themekey'] = array( + '#type' => 'item', + '#title' => t('ThemeKey'), + '#markup' => '6.x-2.0', + ); + + $form['themekey_help_tutorials']['Rule Chaining: Using a special theme for content creation depending on user roles']['versions']['themekey_ui'] = array( + '#type' => 'item', + '#title' => t('ThemeKey Properties'), + '#markup' => '6.x-2.0', + ); + + return $form; +} + + +/** + * Uses Drupal's form builder to format ThemeKey's help examples + * + * @see themekey_help() + */ +function themekey_help_examples_form($form, &$form_state, $collapsed) { + + $form['themekey_help_examples'] = array( + '#type' => 'fieldset', + '#title' => t('Examples'), + '#collapsible' => TRUE, + '#collapsed' => $collapsed, + ); + + $form['themekey_help_examples']['Set a special theme for site administrator'] = array( + '#type' => 'fieldset', + '#title' => t('Set a special theme for site administrator'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + ); + + $form['themekey_help_examples']['Set a special theme for site administrator']['item'] = array( + '#type' => 'item', + '#markup' => t('Create a Theme Switching Rules:') . '<pre> +Property: user:uid +Operator: = +Value: 1 +</pre>', + ); + + // TODO: add some examples using drupal:path and drupal:path:wildcard + $form['themekey_help_examples']['TODO: add some examples using drupal:path and drupal:path:wildcard'] = array( + '#type' => 'fieldset', + '#title' => 'TODO: add some examples using drupal:path and drupal:path:wildcard', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + ); + + $form['themekey_help_examples']['TODO: add some examples using drupal:path and drupal:path:wildcard']['item'] = array( + '#type' => 'item', + '#markup' => 'TODO' . '<pre> +</pre>', + ); + + $form['themekey_help_examples']['Select a theme for Firefox 3.0.x, but not Firefox 3.5.x'] = array( + '#type' => 'fieldset', + '#title' => t('Select a theme for Firefox 3.0.x, but not Firefox 3.5.x'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + ); + + $form['themekey_help_examples']['Select a theme for Firefox 3.0.x, but not Firefox 3.5.x']['item'] = array( + '#type' => 'item', + '#markup' => t('Cascade following Theme Switching Rules:') . '<pre> +Property: system:user_browser_simplified +Operator: = +Value: Mozilla Firefox + + Property: system:user_browser + Operator: > + Value: Mozilla Firefox 3.0 + + Property: system:user_browser + Operator: < + Value: Mozilla Firefox 3.5 +</pre>', + ); + + $form['themekey_help_examples']['Select a theme for IE 6'] = array( + '#type' => 'fieldset', + '#title' => t('Select a theme for IE 6'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + ); + + $form['themekey_help_examples']['Select a theme for IE 6']['item'] = array( + '#type' => 'item', + '#markup' => t('Cascade following Theme Switching Rules:') . '<pre> +Property: system:user_browser_simplified +Operator: = +Value: Internet Explorer + + Property: system:user_browser + Operator: > + Value: Internet Explorer 6 + + Property: system:user_browser + Operator: < + Value: Internet Explorer 7 +</pre>', + ); + + $form['themekey_help_examples']['Select a theme for Christmas 2009'] = array( + '#type' => 'fieldset', + '#title' => t('Select a theme for Christmas 2009'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + ); + + $form['themekey_help_examples']['Select a theme for Christmas 2009']['item'] = array( + '#type' => 'item', + '#markup' => t('Cascade following Theme Switching Rules:') . '<pre> +Property: system:date +Operator: > +Value: 2009-12-24 + + Property: system:date + Operator: < + Value: 2009-12-27 +</pre>', + ); + + $form['themekey_help_examples']['Select a theme for New Year 2010'] = array( + '#type' => 'fieldset', + '#title' => t('Select a theme for New Year 2010'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + ); + + $form['themekey_help_examples']['Select a theme for New Year 2010']['item'] = array( + '#type' => 'item', + '#markup' => t('Create a Theme Switching Rule:') . '<pre> +Property: system:date +Operator: = +Value: 2010-01-01 +</pre>', + ); + + $form['themekey_help_examples']['Select a theme dedicated for your start page (front page, index page, ...)'] = array( + '#type' => 'fieldset', + '#title' => t('Select a theme dedicated for use on your start page (front page, index page, ...)'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + ); + + $form['themekey_help_examples']['Select a theme dedicated for your start page (front page, index page, ...)']['item'] = array( + '#type' => 'item', + '#markup' => t('Create a Theme Switching Rule:') . '<pre> +Property: drupal:is_front_page +Operator: = +Value: true +</pre>', + ); + + return $form; +} + + +/** + * Uses Drupal's form builder to format ThemeKey's help properties + * + * @see themekey_help() + */ +function themekey_help_properties_form($form, &$form_state, $collapsed) { + module_load_include('inc', 'themekey', 'themekey_admin'); + + $page_cache_support_desriptions = themekey_get_page_cache_support_desriptions(); + + $attributes = variable_get('themekey_attributes', array()); + + $form['themekey_help_properties'] = array( + '#type' => 'fieldset', + '#title' => t('Properties explained'), + '#collapsible' => TRUE, + '#collapsed' => $collapsed, + ); + + foreach ($attributes as $property => $attribute) { + $form['themekey_help_properties'][$property] = array( + '#type' => 'fieldset', + '#title' => check_plain($property), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + ); + + $form['themekey_help_properties'][$property]['item'] = array( + '#type' => 'item', + '#title' => t('Description'), + '#markup' => '<div id="themekey-value-help-' . str_replace(array(':', '_'), array('-', '-'), $property) . '" style="display: none">' . htmlentities(strip_tags($attribute['description'])) . '</div>' . $attribute['description'], + ); + + $form['themekey_help_properties'][$property]['page_cache'] = array( + '#type' => 'item', + '#title' => t('Page Cache'), + '#markup' => '<div id="themekey-page-cache-' . str_replace(array(':', '_'), array('-', '-'), $property) . '" style="display: none">' . theme('themekey_page_cache_icon', array('page_cache_support' => $attribute['page cache'])) . '</div>' . $page_cache_support_desriptions[$attribute['page cache']], + ); + } + + return $form; +} + + +/** + * Uses Drupal's form builder to format ThemeKey's help operators + * + * @see themekey_help() + */ +function themekey_help_operators_form($form, &$form_state, $collapsed) { + $form['themekey_help_operators'] = array( + '#type' => 'fieldset', + '#title' => t('Operators explained'), + '#collapsible' => TRUE, + '#collapsed' => $collapsed, + ); + + $form['themekey_help_operators']['='] = array( + '#type' => 'item', + '#markup' => t('<strong>=</strong><br />equals (exact value of a property, consider ThemeKey Debug to get an impression of the possible values)'), + ); + + $form['themekey_help_operators']['!'] = array( + '#type' => 'item', + '#markup' => t('<strong>!</strong><br />not equals'), + ); + + $form['themekey_help_operators']['<'] = array( + '#type' => 'item', + '#markup' => t('<strong><</strong><br />less than (alphanumeric values are treated in alphabetical order: "A" is less than "B" but "B" is less than "a")'), + ); + + $form['themekey_help_operators']['<='] = array( + '#type' => 'item', + '#markup' => t('<strong><=</strong><br />less than or equal to (alphanumeric values are treated in alphabetical order: "a" is less than "b" but "B" is less than "a" and "A" is less than "a")'), + ); + + $form['themekey_help_operators']['>'] = array( + '#type' => 'item', + '#markup' => t('<strong>></strong><br />greater (alphanumeric values are treated in alphabetical order: "b" is greater than "a" but "a" is greater than "B")'), + ); + + $form['themekey_help_operators']['=>'] = array( + '#type' => 'item', + '#markup' => t('<strong>=></strong><br />greater than or equal (alphanumeric values are treated in alphabetical order: "b" is greater than "a" but "a" is greater than "B" and "a" is greater than "A")'), + ); + + $form['themekey_help_operators']['~'] = array( + '#type' => 'item', + '#markup' => t('<strong>~</strong><br />regular expression, including delimiters and modifiers (see !link)', array('!link' => l(t('PHP Manual'), 'http://php.net/manual/en/pcre.pattern.php'))), + ); + + return $form; +} diff --git a/sites/all/modules/themekey/themekey_rule_chain.admin.css b/sites/all/modules/themekey/themekey_rule_chain.admin.css new file mode 100644 index 0000000000000000000000000000000000000000..4d65e450951b21723ee0fa89c05c34487e3381c5 --- /dev/null +++ b/sites/all/modules/themekey/themekey_rule_chain.admin.css @@ -0,0 +1,25 @@ +@CHARSET "UTF-8"; + +td.themekey-properties-row div { + float: left; + margin: 0 10px 0 0; +} + +tr.themekey-fade-out .themekey-fadeable{ + opacity: 0.4; +} + +#themekey-properties tr.themekey-top-level { + border-style: solid; + border-top-width: 6px; +} + +#themekey-properties tr { + border-style: solid; + border-left-width: 3px; + border-right-width: 3px; +} + +tr.themekey-top-level td { + padding-top: 2.5em; +} diff --git a/sites/all/modules/themekey/themekey_rule_chain.admin.js b/sites/all/modules/themekey/themekey_rule_chain.admin.js new file mode 100644 index 0000000000000000000000000000000000000000..f2683289f245ad3fa9e6b5932d7c7983f9169d1f --- /dev/null +++ b/sites/all/modules/themekey/themekey_rule_chain.admin.js @@ -0,0 +1,146 @@ +(function($) { + var ThemeKey = ThemeKey || {}; + + ThemeKey.oldParentId; + + ThemeKey.adjustChildCounter = function (parentId, adjust) { + var childCounter = $('#themekey-num-childs-' + parentId); + childCounter.val(parseInt(childCounter.val()) + adjust); + var propertiesRow = $('#themekey-properties-row-' + parentId); + if (1 == adjust) { + $('.themekey-property-theme', propertiesRow).css('display', 'none'); + $('.themekey-rule-delete-link', propertiesRow).css('display', 'none'); + } + else if (childCounter.val() < 1) { + $('.themekey-property-theme', propertiesRow).css('display', 'block'); + $('.themekey-rule-delete-link', propertiesRow).css('display', 'block'); + } + }; + + ThemeKey.disableChilds = function (parentId) { + var childRows = $("option[selected][value='" + parentId + "']", $('.themekey-property-parent')).parents("tr[id^='themekey-properties-row-']"); + childRows.each(function () { + var childRow = $(this); + var enabledElement = $('.themekey-property-enabled', childRow); + enabledElement.css('display', 'none'); + if (enabledElement.attr('checked')) { + enabledElement.removeAttr('checked'); + childRow.addClass('themekey-fade-out'); + ThemeKey.adjustChildCounter(parentId, -1); + ThemeKey.disableChilds($('.themekey-property-id', childRow).val()); + } + }); + }; + + ThemeKey.allowEnablingDirectChilds = function (parentId) { + var childRows = $("option[selected][value='" + parentId + "']", $('.themekey-property-parent')).parents("tr[id^='themekey-properties-row-']"); + childRows.each(function () { + $('.themekey-property-enabled', $(this)).css('display', 'block'); + }); + }; + + Drupal.tableDrag.prototype.onDrag = function() { + ThemeKey.oldParentId = $('.themekey-property-parent', $(this.rowObject.element)).val(); + return null; + }; + + Drupal.tableDrag.prototype.onDrop = function() { + if (this.changed) { + var rowElement = $(this.rowObject.element); + var newParentId = $('.themekey-property-parent', rowElement).val(); + var enabledElement = $('.themekey-property-enabled', rowElement); + + if (enabledElement.attr('checked')) { + if (0 < ThemeKey.oldParentId) { + ThemeKey.adjustChildCounter(ThemeKey.oldParentId, -1); + } + + if (0 < newParentId) { + var parentEnabledElement = $('.themekey-property-enabled', $('#themekey-properties-row-' + newParentId)); + if (parentEnabledElement.attr('checked')) { + ThemeKey.adjustChildCounter(newParentId, 1); + } + else { + enabledElement.removeAttr('checked'); + enabledElement.css('display', 'none'); + rowElement.addClass('themekey-fade-out'); + // hide and disable children + var id = enabledElement.attr('name').replace('old_items[', '').replace('][enabled]', ''); + ThemeKey.disableChilds(id); + } + } + } + else { + if (0 < newParentId) { + var parentEnabledElement = $('.themekey-property-enabled', $('#themekey-properties-row-' + newParentId)); + if (parentEnabledElement.attr('checked')) { + enabledElement.css('display', 'block'); + } + else { + enabledElement.css('display', 'none'); + } + } + } + + if (0 < newParentId) { + if (0 >= ThemeKey.oldParentId) { + rowElement.removeClass('themekey-top-level'); + } + } + else { + enabledElement.css('display', 'block'); + rowElement.addClass('themekey-top-level'); + } + } + return null; + }; + + Drupal.behaviors.ThemeKey = { + attach: function(context) { + $('.themekey-property-property').change( + function() { + var wildcardElement = $('#' + $(this).attr('id').replace('property', 'wildcard')); + if ('drupal:path:wildcard' == $(this).val()) { + wildcardElement.css('display', 'block'); + } + else { + wildcardElement.css('display', 'none'); + } + + var propertyName = $(this).val().replace(':', '-').replace(':', '-').replace('_', '-').replace('_', '-'); + + var pageCacheIconElement = $('#' + $(this).attr('id').replace('property', 'page-cache-icon')); + pageCacheIconElement.empty(); + pageCacheIconElement.append($('#themekey-page-cache-' + propertyName).html()); + + var valueHelpElement = $('#' + $(this).attr('id').replace('property', 'value-help')); + var helpText = $('#themekey-value-help-' + propertyName).html(); + valueHelpElement.attr('title', helpText); + while (helpText.match("'")) { + helpText = helpText.replace("'", '"'); + } + valueHelpElement.attr('onClick', "alert('" + helpText + "')"); + } + ); + + $('.themekey-property-enabled').change( + function() { + var id = $(this).attr('name').replace('old_items[', '').replace('][enabled]', ''); + var rowElement = $('#themekey-properties-row-' + id); + var parentId = $('.themekey-property-parent', rowElement).val(); + if ($(this).attr('checked')) { + rowElement.removeClass('themekey-fade-out'); + ThemeKey.adjustChildCounter(parentId, 1); + ThemeKey.allowEnablingDirectChilds(id); + } + else { + rowElement.addClass('themekey-fade-out'); + ThemeKey.adjustChildCounter(parentId, -1); + // hide and disable children + ThemeKey.disableChilds(id); + } + } + ); + } + }; +})(jQuery); \ No newline at end of file diff --git a/sites/all/modules/themekey/themekey_ui.info b/sites/all/modules/themekey/themekey_ui.info new file mode 100644 index 0000000000000000000000000000000000000000..a471d1fb52e2fe3f138c095fe29dbfc474065bd9 --- /dev/null +++ b/sites/all/modules/themekey/themekey_ui.info @@ -0,0 +1,20 @@ + +name = "ThemeKey UI" +description = "Integrates ThemeKey with Drupal administration forms." +core = 7.x +dependencies[]= themekey +package = ThemeKey +configure = admin/config/user-interface/themekey/settings/ui + +files[] = themekey_ui.install +files[] = themekey_ui.module +files[] = themekey_ui_admin.inc +files[] = themekey_ui_help.inc +files[] = themekey_ui_helper.inc + +; Information added by drupal.org packaging script on 2011-08-17 +version = "7.x-1.4" +core = "7.x" +project = "themekey" +datestamp = "1313612519" + diff --git a/sites/all/modules/themekey/themekey_ui.install b/sites/all/modules/themekey/themekey_ui.install new file mode 100644 index 0000000000000000000000000000000000000000..0dfa7b2c28ca243909c10e64ee4a4e2669642cd6 --- /dev/null +++ b/sites/all/modules/themekey/themekey_ui.install @@ -0,0 +1,135 @@ +<?php + +/** + * @file + * Database schema of + * @see themekey_ui.module + * + * @author Markus Kalkbrenner | Cocomore AG + * @see http://drupal.org/user/124705 + */ + + +/** + * Implements hook_schema(). + */ +function themekey_ui_schema() { + $schema = array(); + + $schema['themekey_ui_node_theme'] = array( + 'fields' => array( + 'nid' => array( + 'description' => 'The {node} this version belongs to.', + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'vid' => array( + 'description' => 'The primary identifier for this version.', + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'theme' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + ), + 'primary key' => array('nid', 'vid'), + ); + + $schema['themekey_ui_author_theme'] = array( + 'fields' => array( + 'uid' => array( + 'description' => 'The user id.', + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'theme' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + ), + 'primary key' => array('uid'), + ); + + return $schema; +} + + +/** + * Implements hook_disable(). + */ +function themekey_ui_disable() { + module_load_include('inc', 'themekey', 'themekey_build'); + themekey_update_static_rule('themekey_ui:node_triggers_theme', FALSE); + themekey_update_static_rule('themekey_ui:node_author_triggers_theme', FALSE); +} + +/** + * Implements hook_uninstall(). + */ +function themekey_ui_uninstall() { + // Remove variables + db_delete('variable') + ->condition('name', 'themekey_ui%%', 'LIKE') + ->execute(); + + cache_clear_all('variables', 'cache'); +} + + +/** + * Implements hook_update_N(). + */ +function themekey_ui_update_6100() { + + // moved to themekey_update_6105() to not break upgrades from ThemeKey 6.x-1.1 to 6.x-2.0 + + // hook_update_N() no longer returns a $ret array. Instead, return + // nothing or a translated string indicating the update ran successfully. + // See http://drupal.org/node/224333#update_sql. + return '' /* array() */; +} + + +/** + * Implements hook_update_N(). + */ +function themekey_ui_update_6200() { + + if (!db_table_exists('themekey_ui_author_theme')) { + $schema['themekey_ui_author_theme'] = array( + 'fields' => array( + 'uid' => array( + 'description' => 'The user id.', + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'theme' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + ), + 'primary key' => array('uid'), + ); + + $ret = array(); + db_create_table('themekey_ui_author_theme', $schema['themekey_ui_author_theme']); + // hook_update_N() no longer returns a $ret array. Instead, return + // nothing or a translated string indicating the update ran successfully. + // See http://drupal.org/node/224333#update_sql. + return t('Created table themekey_ui_author_theme') /* $ret */; + } + else { + return ''; + } +} diff --git a/sites/all/modules/themekey/themekey_ui.module b/sites/all/modules/themekey/themekey_ui.module new file mode 100644 index 0000000000000000000000000000000000000000..41af67b5ecfde6ee7248445219526de6d9d6cc41 --- /dev/null +++ b/sites/all/modules/themekey/themekey_ui.module @@ -0,0 +1,423 @@ +<?php + +/** + * @file + * ThemeKey UI is an extension for ThemeKey + * + * ThemeKey UI adds a form element to node create and edit forms + * to assign a theme to a node. + * + * ThemeKey UI adds a theme option to the 'URL aliases' administration + * if module "Path" is enabled. + * + * ThemeKey UI adds a form element to user profile for theming all + * nodes created by this user. + * + * @see themekey.module + * + * @author Markus Kalkbrenner | Cocomore AG + * @see http://drupal.org/user/124705 + * + * @author profix898 + * @see http://drupal.org/user/35192 + */ + + +/** + * Implements hook_themekey_properties(). + * + * Provides additional properties for the ThemeKey module: + * themekey_ui:node_triggers_theme + * themekey_ui:node_author_triggers_theme + * + * @return + * array of themekey properties + */ +function themekey_ui_themekey_properties() { + + // Attributes for properties + $attributes = array(); + + $attributes['themekey_ui:node_triggers_theme'] = array( + 'description' => t("The property, themekey_ui:node_triggers_theme, could not be selected from the property drop-down. You get this static property by activating !link. Afterwards, you can move the property to any position in the rule chain. When done, it triggers the switch to the theme assigned to the current node using ThemeKey UI.", + array('!link' => l(t('Show theme option in create/edit node forms'), 'admin/config/user-interface/themekey/settings/ui'))), + 'page cache' => THEMEKEY_PAGECACHE_SUPPORTED, + 'static' => TRUE, + ); + + $attributes['themekey_ui:node_author_triggers_theme'] = array( + 'description' => t("The property, themekey_ui:node_author_triggers_theme, could not be selected from the property drop down. You get this static property by activating !link. Afterwards, you can move the property to any position in the rule chain. When done, it triggers the switch to the theme the author selected for her nodes in her user profile.", + array('!link' => l(t('Let the user select a theme for her nodes in her user profile'), 'admin/config/user-interface/themekey/settings/ui'))), + 'page cache' => THEMEKEY_PAGECACHE_SUPPORTED, + 'static' => TRUE, + ); + + // Mapping functions + $maps = array(); + + $maps[] = array( + 'src' => 'node:nid', + 'dst' => 'themekey_ui:node_triggers_theme', + 'callback' => 'themekey_ui_nid2theme', + ); + + $maps[] = array( + 'src' => 'node:nid', + 'dst' => 'themekey_ui:node_author_triggers_theme', + 'callback' => 'themekey_ui_author2theme', + ); + + return array('attributes' => $attributes, 'maps' => $maps); +} + + +/** + * This function implements the interface of a ThemeKey + * mapping function, but doesn't set a ThemeKey property's + * value. It sets the Drupal static themekey_custom_theme + * which will cause ThemeKey to use this theme. + * + * @param $nid + * a node id, current value of ThemeKey property node:nid + * + * @return + * string "static" if global custom theme has been set + * or NULL if no theme has been assigned to the node + */ +function themekey_ui_nid2theme($nid) { + $custom_theme = &drupal_static('themekey_custom_theme', ''); + + // node_load() must not be called from hook_init(). + // Therefore, we have to execute SQL here using hook_nodeapi(). + $node = new stdClass(); + $node->nid = $nid; + $node->vid = themekey_node_get_simple_node_property($nid, 'vid'); + $node->type = themekey_node_get_simple_node_property($nid, 'type'); + + themekey_ui_node_load(array($node->nid => $node), array($node->type)); + + if (!empty($node->themekey_ui_theme) && 'default' != $node->themekey_ui_theme && themekey_check_theme_enabled($node->themekey_ui_theme)) { + $custom_theme = $node->themekey_ui_theme; + return 'static'; + } + + return NULL; +} + + +/** + * This function implements the interface of a ThemeKey + * mapping function but doesn't set a ThemeKey property's + * value. It sets the Drupal static themekey_custom_theme + * which will cause ThemeKey to use this theme. + * + * @param $nid + * a node id, current value of ThemeKey property node:nid + * + * @return + * string "static" if global custom theme has been set + * or NULL if no theme has been assigned to the node + */ +function themekey_ui_author2theme($nid) { + $custom_theme = &drupal_static('themekey_custom_theme', ''); + + // node_load() must not be called from hook_init(). + // Therefore, we have to execute SQL here. + $query = db_select('node', 'n') + ->fields('tuat', array('theme')) + ->condition('n.nid', $nid); + $query->join('themekey_ui_author_theme', 'tuat', 'n.uid = tuat.uid'); + + if ($theme = $query->execute()->fetchField()) { + if ('default' != $theme && themekey_check_theme_enabled($theme)) { + $custom_theme = $theme; + return 'static'; + } + } + + return NULL; +} + + +/** + * Implements hook_theme(). + */ +function themekey_ui_theme() { + return array( + 'themekey_ui_table' => array( + 'file' => 'themekey_ui_admin.inc', + 'render element' => 'form', + ), + 'themekey_ui_theme_select_form' => array( + 'file' => 'themekey_ui_admin.inc', + 'render element' => 'form', + ), + ); +} + + +/** + * Implements hook_perm(). + */ +function themekey_ui_permission() { + return array( + 'assign node themes' => array( + 'title' => t('assign node themes'), + 'description' => '', + ), + 'assign path alias themes' => array( + 'title' => t('assign path alias themes'), + 'description' => '', + ), + 'assign theme to own nodes' => array( + 'title' => t('assign theme to own nodes'), + 'description' => '', + ), + ); +} + + +/** + * Implements hook_help(). + */ +function themekey_ui_help($path, $arg) { + switch ($path) { + case 'admin/help#themekey_user_profile': + return themekey_help('admin/help#themekey', $arg); + } +} + + +/** + * Implements hook_menu(). + */ +function themekey_ui_menu() { + $items = array(); + $items['admin/config/user-interface/themekey/settings/ui'] = array( + 'title' => 'User Interface', + 'access callback' => 'user_access', + 'access arguments' => array('administer themekey settings'), + 'file' => 'themekey_ui_admin.inc', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('themekey_ui_settings_form'), + 'type' => MENU_LOCAL_TASK, + 'weight' => 1, + ); + + return $items; +} + + +/** + * Implements hook_form_alter(). + */ +function themekey_ui_form_alter(&$form, $form_state, $form_id) { + if ('path_admin_form' == $form_id) { + // path aliases form + if (user_access('assign path alias themes') && variable_get('themekey_ui_pathalias', 0)) { + module_load_include('inc', 'themekey_ui', 'themekey_ui_admin'); + themekey_ui_pathalias($form); + } + } + elseif ('user_profile_form' == $form_id) { + if (user_access('assign theme to own nodes') && variable_get('themekey_ui_author', 0)) { + module_load_include('inc', 'themekey_ui', 'themekey_ui_admin'); + + // to avoid a sql query to load his/her nodes' themes, every time a user is loaded, we do this query here + $theme = FALSE; + if (!empty($form['#user']->uid)) { + $theme = db_select('themekey_ui_author_theme', 'tuat') + ->fields('tuat', array('theme')) + ->condition('uid', $form['#user']->uid) + ->execute() + ->fetchField(); + } + + themekey_ui_theme_select_form($form, t('Theme configuration for my nodes'), t('Every node I create will be shown to other users using this theme.'), $theme ? $theme : 'default'); + } + } + elseif ('themekey_help_tutorials_form' == $form_id) { + module_load_include('inc', 'themekey_ui', 'themekey_ui_help'); + themekey_ui_help_tutorials($form); + } + else { + // node form? + if (variable_get('themekey_ui_nodeform', 0) && user_access('assign node themes')) { + $type = isset($form['type']['#value']) ? $form['type']['#value'] : FALSE; + if ($form_id == $type . '_node_form' && variable_get('themekey_ui_nodeform|' . $type, 1)) { + module_load_include('inc', 'themekey_ui', 'themekey_ui_admin'); + $description = t('Theme configuration for this node'); + if (module_exists('og') && !og_is_omitted_type($type)) { + $description .= '<p><b>' . t('Note:') . '</b> ' . t('This content type is used in Organic Groups. A theme you select here will only be used to display this node if you set the theme for the Organic Group to %theme', array('%theme' => variable_get('theme_default', 'bartik') . ' ' . t('(site default theme)'))) . '</p>'; + } + themekey_ui_theme_select_form($form, t('Theme configuration for this node'), $description, !empty($form['#node']->themekey_ui_theme) ? $form['#node']->themekey_ui_theme : 'default'); + } + } + } +} + + + +/** Implementation of hook_node_load + * + * @param $nodes + * @param $types + */ +function themekey_ui_node_load($nodes, $types) { + + if (!empty($nodes)) { + foreach ($nodes as $nid => $node) { + + if (variable_get('themekey_ui_nodeform', 0) && variable_get('themekey_ui_nodeform|' . $node->type, 1)) { + if (!empty($node->vid)) { + + $theme = db_select('themekey_ui_node_theme', 't') + ->fields('t', array('theme')) + ->condition('nid', $node->nid) + ->condition('vid', $node->vid) + ->execute() + ->fetchField(); + + if ($theme) { + $node->themekey_ui_theme = $theme; + } + else { + $node->themekey_ui_theme = 'default'; + } + } + } + + } + } + +} + + +/** + * Implementation of hook_node_insert + * @param unknown_type $node + */ +function themekey_ui_node_insert($node) { + if (!empty($node->themekey_ui_theme)) { + db_insert('themekey_ui_node_theme') + ->fields(array( + 'nid' => $node->nid, + 'vid' => $node->vid, + 'theme' => $node->themekey_ui_theme, + )) + ->execute(); + } +} + + +/** + * Implementation of hook_node_update + * @param unknown_type $node + */ +function themekey_ui_node_update($node) { + + if (!empty($node->themekey_ui_theme)) { + + if (!$node->revision && 0 < db_select('themekey_ui_node_theme', 't') + ->fields('t') + ->condition('nid', $node->nid) + ->condition('vid', $node->vid) + ->execute() + ->rowCount() + ) { + + // UPDATE + $query = db_update('themekey_ui_node_theme') + ->fields(array( + 'nid' => $node->nid, + 'vid' => $node->vid, + 'theme' => $node->themekey_ui_theme, + )); + $query->condition('nid', $node->nid, '='); + $query->condition('vid', $node->vid, '='); + $query->execute(); + } + else { + // INSERT + themekey_ui_node_insert($node); + } + + } + +} + + +/** + * Implementation of hook_node_delete + * @param unknown_type $node + */ +function themekey_ui_node_delete($node) { + db_delete('themekey_ui_node_theme') + ->condition('nid', $node->nid) + ->execute(); +} + + +/** + * Implementation of hook_user_insert() + * @param unknown_type $edit + * @param unknown_type $account + * @param unknown_type $category + */ +function themekey_ui_user_insert(&$edit, $account, $category) { + if (user_access('assign theme to own nodes') && variable_get('themekey_ui_author', 0) && !empty($edit['themekey_ui_theme'])) { + + if (db_select('themekey_ui_author_theme', 't') + ->fields('t') + ->condition('uid', $account->uid) + ->execute() + ->rowCount() + ) { + + db_update('themekey_ui_author_theme') + ->fields(array( + 'uid' => $account->uid, + 'theme' => $edit['themekey_ui_theme'], + )) + ->condition('uid', $account->uid) + ->execute(); + } + else { + db_insert('themekey_ui_author_theme') + ->fields(array( + 'uid' => $account->uid, + 'theme' => $edit['themekey_ui_theme'], + )) + ->execute(); + } + + if (isset($account->themekey_ui_theme) && $edit['themekey_ui_theme'] != $account->themekey_ui_theme) { + // theme settings changed + // fast deletion of page cache (truncate) + cache_clear_all('*', 'cache_page', TRUE); + } + + + $edit['themekey_ui_theme'] = NULL; + } + +} + + +/** + * Implementation of hook_node_update() + */ +function themekey_ui_user_update(&$edit, $account, $category) { + themekey_ui_user_insert($edit, $account, $category); +} + + +/** + * Implementation of hook_user_delete() + * @param $account + */ +function themekey_ui_user_delete($account) { + db_delete('themekey_ui_author_theme') + ->condition('uid', $account->uid) + ->execute(); +} diff --git a/sites/all/modules/themekey/themekey_ui_admin.inc b/sites/all/modules/themekey/themekey_ui_admin.inc new file mode 100644 index 0000000000000000000000000000000000000000..84ea0cb9e0428954092eddc38a25a13eb8cd0d53 --- /dev/null +++ b/sites/all/modules/themekey/themekey_ui_admin.inc @@ -0,0 +1,372 @@ +<?php + +/** + * @file + * Adds options to ThemeKey's administration back end and alters Drupal forms + * to add theme select boxes. + * + * @see themekey_ui.module + * + * @author Markus Kalkbrenner | Cocomore AG + * @see http://drupal.org/user/124705 + * + * @author profix898 + * @see http://drupal.org/user/35192 + */ + +require_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'themekey') . '/themekey_base.inc'; +require_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'themekey_ui') . '/themekey_ui_helper.inc'; + +/** + * ThemeKey UI settings form + * + * @see themekey_ui_settings_form_submit() + * + * @ingroup forms + */ +function themekey_ui_settings_form() { + themekey_ui_theme_build_select_form( + $form, + t('Selectable Themes'), + t('Select all the the themes that should be provided in theme selectors.'), + variable_get('themekey_ui_selectable_themes', array('default')), + NULL, + TRUE, + 'themekey_ui_selectable_themes', + list_themes(), + FALSE); + + $form['themekey_ui_themes']['themekey_ui_selectable_themes']['#type'] = 'checkboxes'; + + $form['themekey_ui'] = array( + '#type' => 'fieldset', + '#title' => t('UI Settings'), + '#collapsible' => FALSE, + '#collapsed' => FALSE, + ); + + if (module_exists('path')) { + $form['themekey_ui']['themekey_ui_pathalias'] = array( + '#type' => 'checkbox', + '#title' => t('Show theme option in the \'URL aliases\' administration'), + '#default_value' => variable_get('themekey_ui_pathalias', 0), + '#description' => t('Assign themes to paths/path aliases from the \'URL aliases\' administration pages.'), + ); + } + // + $nodeform = variable_get('themekey_ui_nodeform', 0); + $form['themekey_ui']['themekey_ui_nodeform'] = array( + '#type' => 'checkbox', + '#title' => t('Show theme option in create/edit node forms'), + '#default_value' => $nodeform, + '#description' => t('Assign themes from create/edit node forms. This will show a \'Theme\' section on create/edit node pages.'), + ); + if ($nodeform) { + $form['themekey_ui']['content_type'] = array( + '#type' => 'fieldset', + '#title' => t('Show \'Theme\' option for nodes of type'), + '#collapsible' => TRUE, + '#collapsed' => FALSE, + ); + $form['themekey_ui']['content_type']['table'] = array( + '#theme' => 'themekey_ui_table', + '#header' => array(t('Content Type'), t('Enabled')), + ); + foreach (node_type_get_names() as $type => $title) { + $form['themekey_ui']['content_type']['table'][$type]['title'] = array('#markup' => $title); + $form['themekey_ui']['content_type']['table'][$type]['themekey_ui_nodeform|' . $type] = array( + '#type' => 'checkbox', + '#default_value' => variable_get('themekey_ui_nodeform|' . $type, 1), + ); + } + } + + $form['themekey_ui']['themekey_ui_author'] = array( + '#type' => 'checkbox', + '#title' => t('Let the user select a theme for her nodes in her user profile'), + '#default_value' => variable_get('themekey_ui_author', 0), + '#description' => t('Assign themes from user profile. All nodes created by a user will be shown to all visitors using the theme she selected in her profile.'), + ); + + $form['buttons']['submit'] = array( + '#type' => 'submit', + '#value' => t('Save configuration'), + ); + + return $form; +} + + +/** + * Form submission handler for themekey_ui_settings_form(). + * + * @see themekey_ui_settings_form() + */ +function themekey_ui_settings_form_submit($form, &$form_state) { + // + foreach ($form_state['values'] as $key => $value) { + if (0 === strpos($key, 'themekey_ui_')) { + variable_set($key, $value); + } + } + + themekey_update_static_rule('themekey_ui:node_triggers_theme', $form_state['values']['themekey_ui_nodeform']); + themekey_update_static_rule('themekey_ui:node_author_triggers_theme', $form_state['values']['themekey_ui_author']); + + drupal_set_message(t('The configuration options have been saved.')); +} + + +/** + * Adds theme selector to a form. + * + * @see themekey_ui_theme_build_select_form() + * + * @param $form + * form array + * + * @param $title + * title as string + * + * @param $description + * description as string + * + * @param $weight + * integer + * + * @param $collapsed + * boolean + * + * @param $element_key + * the name of form element + */ +function themekey_ui_theme_select_form(&$form, $title, $description, $default = 'default', $weight = NULL, $collapsed = TRUE, $element_key = 'themekey_ui_theme') { + $themes = list_themes(); + $selectable_themes = variable_get('themekey_ui_selectable_themes', array('default' => t('System default'))); + foreach (array_keys($themes) as $name) { + if (!array_key_exists($name, $selectable_themes) || empty($selectable_themes[$name])) { + unset($themes[$name]); + } + } + themekey_ui_theme_build_select_form($form, $title, $description, $default, $weight, $collapsed, $element_key, $themes, /*add_default*/ TRUE); +} + + +/** + * Internal function to add a theme selector to a form. + * + * The API for contrib modules is provided by + * @see themekey_ui_theme_select_form() + * + * @param $form + * form array + * + * @param $title + * title as string + * + * @param $description + * description as string + * + * @param $weight + * integer + * + * @param $collapsed + * boolean + * + * @param $element_key + * the name of form element + * + * @param $themes + * array of theme names to be provided as + * options in the select form + * + * @param $add_default + * boolean, TRUE if 'System default' should be + * be provided as options in the select form + */ +function themekey_ui_theme_build_select_form(&$form, $title, $description, $default, $weight, $collapsed, $element_key, $themes, $add_default) { + $theme_options = themekey_theme_options(); + + $form['themekey_ui_themes'] = array( + '#type' => 'fieldset', + '#title' => check_plain($title), + '#description' => filter_xss($description), + '#collapsible' => TRUE, + '#collapsed' => $collapsed, + '#theme' => 'themekey_ui_theme_select_form', + '#themekey_ui_theme_select_form_element_key' => $element_key, + ); + + if ($add_default) { + $form['themekey_ui_themes']['default']['screenshot'] = array(); + $form['themekey_ui_themes']['default']['description'] = array( + '#type' => 'item', + '#markup' => t("don't switch the theme"), + ); + + $options = array('default' => ''); + } + + foreach ($themes as $info) { + if (!array_key_exists($info->name, $theme_options)) { + continue; + } + + $options[$info->name] = ''; + + $screenshot = NULL; + $theme_key = $info->name; + while ($theme_key) { + if (file_exists($themes[$theme_key]->info['screenshot'])) { + $screenshot = $themes[$theme_key]->info['screenshot']; + break; + } + $theme_key = isset($themes[$theme_key]->info['base theme']) ? $themes[$theme_key]->info['base theme'] : NULL; + } + + $screenshot = $screenshot ? theme('image', array('path' => $screenshot, 'width' => t('Screenshot for %theme theme', array('%theme' => $info->name)), 'height' => '', 'alt' => array('class' => 'screenshot'), 'title' => FALSE)) : t('no screenshot'); + + $form['themekey_ui_themes'][$info->name]['screenshot'] = array('#markup' => $screenshot); + $form['themekey_ui_themes'][$info->name]['description'] = array( + '#type' => 'item', + '#title' => check_plain($info->info['name']), + '#markup' => dirname($info->filename), + ); + } + + $form['themekey_ui_themes'][$element_key] = array( + '#type' => 'radios', + '#options' => $options, + '#default_value' => $default, + ); + + if (!is_null($weight)) { + $form['themekey_ui_themes']['#weight'] = $weight; + } + + // REVIEW should we hardcode this here? + // SET GROUP FOR VERTICAL TABS + $form['themekey_ui_themes']['#group'] = 'additional_settings'; +} + + +/** + * Adds theme select box to url alias form + * + * @see path_admin_form() + * @see themekey_ui_pathalias_submit() + * + * @ingroup forms + */ +function themekey_ui_pathalias(&$form) { + if (!isset($form['alias'])) { + return; + } + + list($id, $theme) = themekey_ui_get_path_theme($form['alias']['#default_value']); + + themekey_ui_theme_select_form($form, t('Theme configuration'), t('Select a theme that will be used whenever content is requested using this path alias.'), $theme, -1); + + $form['themekey_ui_themes']['themekey_rule_id'] = array( + '#type' => 'value', + '#value' => $id, + ); + + $form['#submit'][] = 'themekey_ui_pathalias_submit'; + +} + + +/** + * Form submission handler for themekey_ui_pathalias(). + * + * @see themekey_ui_pathalias() + */ +function themekey_ui_pathalias_submit($form, &$form_state) { + + if ((empty($form_state['values']['themekey_ui_theme']) || 'default' == $form_state['values']['themekey_ui_theme']) && $form_state['values']['themekey_rule_id']) { + themekey_ui_del_path_theme($form_state['values']['themekey_rule_id']); + } + elseif (!empty($form_state['values']['themekey_ui_theme']) && 'default' != $form_state['values']['themekey_ui_theme']) { + themekey_ui_set_path_theme($form_state['values']['alias'], $form_state['values']['themekey_ui_theme'], $form_state['values']['themekey_rule_id']); + } + + if ($form['source']['#default_value'] == $form_state['values']['source'] && + $form['alias']['#default_value'] == $form_state['values']['alias'] && + $form['themekey_ui_themes']['themekey_ui_theme']['#default_value'] != $form_state['values']['themekey_ui_theme']) { + // only theme changed => clear page cache + // REVIEW this might be a performance issue on large sites + cache_clear_all('%' . $form_state['values']['alias'], 'cache_page', TRUE); + } +} + + +/** + * Formats a table with checkboxes used by ThemeKey UI settings form. + * + * @param $form + * array() containing form elements to be + * formatted as table + * + * @ingroup themeable + */ +function theme_themekey_ui_table($form) { + + $form = $form['form']; + + // TODO: Should this theme themekey_ui_table be declared in hook_theme()? + $header = isset($form['#header']) ? $form['#header'] : array(); + $attributes = isset($form['#attributes']) ? $form['#attributes'] : array(); + + $rows = array(); + foreach (element_children($form) as $key) { + $row = array(); + foreach (element_children($form[$key]) as $item) { + $row[] = drupal_render($form[$key][$item]); + } + $rows[] = $row; + } + + if (empty($rows)) { + $message = check_plain(isset($form['#empty']) ? $form['#empty'] : t('There are no items in the table.')); + $rows[] = array(array( + 'data' => $message, + 'colspan' => count($header), + 'align' => 'center', + 'class' => 'message', + )); + } + + return count($rows) ? theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => $attributes)) : ''; +} + + +/** + * Theme the theme select form. + * + * @see theme_system_theme_select_form() + * + * @param $form + * An associative array containing the structure of the form. + * + * @ingroup themeable + */ +function theme_themekey_ui_theme_select_form($form) { + $rows = array(); + + foreach (element_children($form['form']) as $key) { + $row = array(); + if (isset($form['form'][$key]['description']) && is_array($form['form'][$key]['description'])) { + $row[] = drupal_render($form['form'][$key]['screenshot']); + $row[] = drupal_render($form['form'][$key]['description']); + $row[] = drupal_render($form['form'][$form['form']['#themekey_ui_theme_select_form_element_key']][$key]); + } + $rows[] = $row; + } + + if (!empty($rows)) { + $header = array(t('Screenshot'), t('Name'), t('Selected')); + $output = theme('table', array('header' => $header, 'rows' => $rows)); + return $output; + } +} diff --git a/sites/all/modules/themekey/themekey_ui_help.inc b/sites/all/modules/themekey/themekey_ui_help.inc new file mode 100644 index 0000000000000000000000000000000000000000..25ce8279e83b570bef8be063eba3522677ae9191 --- /dev/null +++ b/sites/all/modules/themekey/themekey_ui_help.inc @@ -0,0 +1,70 @@ +<?php + +/** + * @file + * Provides content for help pages. + * + * @author Markus Kalkbrenner | Cocomore AG + * @see http://drupal.org/user/124705 + */ + + +/** + * Adds tutorials to themekey_help_form(). + * + * @see themekey_help() + * @see themekey_help_form() + * @see themekey_ui_form_alter() + * + * @param $form + * reference to a Drupal form + */ +function themekey_ui_help_tutorials(&$form) { + $form['themekey_help_tutorials']['Allowing users to select a theme for all content they create'] = array( + '#type' => 'fieldset', + '#title' => t('Allowing users to select a theme for all content they create'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + ); + + $form['themekey_help_tutorials']['Allowing users to select a theme for all content they create']['author'] = array( + '#type' => 'item', + '#title' => t('Author'), + '#markup' => l(t('!path', array('!path' => 'Sansui')), 'http://drupal.org/user/297165'), + ); + + $img_path = base_path() . drupal_get_path('module', 'themekey_ui') . '/img/tutorials/'; + + $form['themekey_help_tutorials']['Allowing users to select a theme for all content they create']['item'] = array( + '#type' => 'item', + '#markup' => '<p>' . t('In this tutorial, you will learn how to enable users to select a theme for content they create. For example, if you would like each of your bloggers to display their own theme on each of their blog entries, this tutorial will allow you to grant this option.</p>') . +'<p>' . t('First, you must enable any themes you would like available from under !link (Administer -> Appearance)</p>', array('!link' => l(t('!path', array('!path' => 'admin/appearance')), 'admin/appearance'))) . +'<p>' . t('Once you have enabled your themes, navigate to !link (Administer -> Configuration -> Themekey -> Settings -> User Interface). You will see a checkbox for "Show theme option in user profile". Click on the checkbox and save your settings.</p>', array('!link' => l(t('!path', array('!path' => 'admin/config/user-interface/themekey/settings/ui')), 'admin/config/user-interface/themekey/settings/ui'))) . +'<p>' . '<img src="' . $img_path . 'themekey_user_profiles_2.png" alt="' . t('Enabling theme selection in user profiles') . '" style="border:solid #d4e7f3 2px;" /></p>' . +'<p>' . t('Next, you must grant permission to the roles that you would like to be able to change themes for their own content. Navigate to !link (Administer -> People -> Permissions) and check the boxes for "Assign theme to own nodes" for all roles you want to have this option.</p>', array('!link' => l(t('!path', array('!path' => 'admin/people/permissions')), 'admin/people/permissions'))) . '<br />' . +'<p>' . '<img src="' . $img_path . 'themekey_user_profiles_3.png" alt="' . t('Enable correct permissions to use themekey for your users') . '" style="border:solid #d4e7f3 2px;" /></p>' . +'<p>' . t('Once you have done this, your users can log in, navigate to My Account -> Edit and a new option will appear under their account settings. A full select list of your enabled themes will appear for them to select from. Once they have chosen a new theme, any node that is authored by this user will display the theme they have selected.</p>') . +'<p>' . '<img src="' . $img_path . 'themekey_user_profiles_4.png" alt="' . t('ThemeKey rule chain that switches to different themes on content creation forms for different user roles') . '" style="border:solid #d4e7f3 2px;" /></p>' . +'<p>' . t('Have Fun!') . '</p>', + ); + + $form['themekey_help_tutorials']['Allowing users to select a theme for all content they create']['versions'] = array( + '#type' => 'fieldset', + '#title' => t('Versions used to create this tutorial'), + '#collapsible' => FALSE, + ); + + $form['themekey_help_tutorials']['Allowing users to select a theme for all content they create']['versions']['themekey'] = array( + '#type' => 'item', + '#title' => t('ThemeKey'), + '#markup' => '6.x-2.2', + ); + + $form['themekey_help_tutorials']['Allowing users to select a theme for all content they create']['versions']['themekey_ui'] = array( + '#type' => 'item', + '#title' => t('ThemeKey UI'), + '#markup' => '6.x-2.2', + ); + + return $form; +} diff --git a/sites/all/modules/themekey/themekey_ui_helper.inc b/sites/all/modules/themekey/themekey_ui_helper.inc new file mode 100644 index 0000000000000000000000000000000000000000..a44e71ac584171bb7c02d94f23536b0ff5f97b5b --- /dev/null +++ b/sites/all/modules/themekey/themekey_ui_helper.inc @@ -0,0 +1,102 @@ +<?php + +/** + * @file + * helper functions for ThemeKey UI feature to add a theme option to the + * 'URL aliases' administration if the "Path" module is enabled. + * + * @see themekey_ui.module + * @see themekey_ui_admin.inc + * + * @author Markus Kalkbrenner | Cocomore AG + * @see http://drupal.org/user/124705 + * + * @author profix898 + * @see http://drupal.org/user/35192 + */ + +require_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'themekey') . '/themekey_build.inc'; + +/** + * Returns a theme assigned to a Drupal path alias using + * a ThemeKey Theme Switching Rule which is not part of a chain + * + * @param $path + * Drupal path as string + * + * @return + * array with two elements: + * - id of the database entry in table themekey_properties + * or '0' if no entry exists + * - the theme + */ +function themekey_ui_get_path_theme($path) { + $serialized_empty_array = serialize(array()); + if ($result = db_select('themekey_properties', 'tp') + ->fields('tp', array('id', 'theme')) + ->condition('property', 'drupal:path') + ->condition('operator', '=') + ->condition('value', $path) + ->condition('wildcards', $serialized_empty_array) + ->condition('parent', 0) + ->execute() + ) { + + foreach ($result as $row) { + $query = db_select('themekey_properties', 'tp') + ->condition('parent', $row->id); + $query->addExpression('COUNT(*)'); + if (!$query->execute()->fetchField()) { + return array($row->id, $row->theme); + } + } + + } + + return array(0, 'default'); +} + + +/** + * Saves a theme assigned to a path alias as + * ThemeKey rule + * + * @see themekey_rule_set() + * + * @param $path + * Drupal path alias as string + * + * @param $theme + * assigned Drupal theme as string + * + * @param $id + * the id of an existing rule if this + * one should be modified + */ +function themekey_ui_set_path_theme($path, $theme = 'default', $id = 0) { + $item = array( + 'property' => 'drupal:path', + 'value' => $path, + 'theme' => $theme, + 'enabled' => 1, + ); + + if ($id > 0) { + $item['id'] = $id; + } + + themekey_rule_set($item); +} + + +/** + * Deletes a ThemeKey rule. + * + * @see themekey_rule_del() + * + * @param $id + * the id of an existing rule + */ +function themekey_ui_del_path_theme($id) { + return themekey_rule_del($id); +} diff --git a/sites/all/modules/themekey/themekey_user_profile.info b/sites/all/modules/themekey/themekey_user_profile.info new file mode 100644 index 0000000000000000000000000000000000000000..d516d28639be1add1c87bbe1ef00532424791593 --- /dev/null +++ b/sites/all/modules/themekey/themekey_user_profile.info @@ -0,0 +1,19 @@ + +name = "ThemeKey User Profile" +description = "Allows users to select their own theme for this site. ThemeKey User Profile replaces the corresponding feature that existed in Drupal 6 Core but has been removed in Drupal 7 Core." +core = 7.x +dependencies[]= themekey +dependencies[]= themekey_ui +package = ThemeKey +configure = admin/config/user-interface/themekey/settings/ui + +files[] = themekey_user_profile.install +files[] = themekey_user_profile.module +files[] = themekey_user_profile_help.inc + +; Information added by drupal.org packaging script on 2011-08-17 +version = "7.x-1.4" +core = "7.x" +project = "themekey" +datestamp = "1313612519" + diff --git a/sites/all/modules/themekey/themekey_user_profile.install b/sites/all/modules/themekey/themekey_user_profile.install new file mode 100644 index 0000000000000000000000000000000000000000..08a70834355f2bcee7007fa351f13d016a7e7251 --- /dev/null +++ b/sites/all/modules/themekey/themekey_user_profile.install @@ -0,0 +1,32 @@ +<?php + +/** + * @file + * Database schema of + * @see themekey_user_profile.module + * + * @author Markus Kalkbrenner | Cocomore AG + * @see http://drupal.org/user/124705 + */ + + +/** + * Implements hook_disable(). + */ +function themekey_user_profile_disable() { + module_load_include('inc', 'themekey', 'themekey_build'); + themekey_update_static_rule('user:profile_triggers_theme', FALSE); +} + + +/** + * Implements hook_uninstall(). + */ +function themekey_user_profile_uninstall() { + // Remove variables + db_delete('variable') + ->condition('name', 'themekey_ui_user_profile') + ->execute(); + + cache_clear_all('variables', 'cache'); +} diff --git a/sites/all/modules/themekey/themekey_user_profile.module b/sites/all/modules/themekey/themekey_user_profile.module new file mode 100644 index 0000000000000000000000000000000000000000..a22c016258696a97bcfb775745b1aab2c00cef5c --- /dev/null +++ b/sites/all/modules/themekey/themekey_user_profile.module @@ -0,0 +1,143 @@ +<?php + +/** + * @file + * ThemeKey User Profile allows the user to select a personal theme in + * her user profile. This theme will be used to render the pages instead + * of the theme the administrator configured as soon as the user logs in. + * + * ThemeKey User Profile replaces the corresponding feature that existed + * in Drupal 6 Core but has been removed in Drupal 7 Core. + * + * @see http://drupal.org/node/292253 + * @see http://drupal.org/node/559306 + * @see http://drupal.org/node/1046214 + * + * @author Markus Kalkbrenner | Cocomore AG + * @see http://drupal.org/user/124705 + */ + + +/** + * Implements hook_permission(). + * + * Backward compatible to Drupal 6 core. + * @see http://drupal.org/node/559306 + */ +function themekey_user_profile_permission() { + return array( + 'select different theme' => array( + 'title' => t('Select different theme'), + 'description' => t('A User is able to select a personal theme other than the default theme set by the site administrator.'), + ), + ); +} + + +/** + * Implements hook_help(). + */ +function themekey_user_profile_help($path, $arg) { + switch ($path) { + case 'admin/help#themekey_user_profile': + return themekey_help('admin/help#themekey', $arg); + } +} + + +/** + * Implements hook_form_alter(). + */ +function themekey_user_profile_form_alter(&$form, $form_state, $form_id) { + switch ($form_id) { + case 'user_profile_form': + if (user_access('select different theme') && variable_get('themekey_ui_user_profile', 0)) { + module_load_include('inc', 'themekey_ui', 'themekey_ui_admin'); + $theme = !empty($form_state['input']['theme']) ? $form_state['input']['theme'] : !empty($form_state['user']->theme) ? $form_state['user']->theme : 'default'; + $tmp_form = array(); + themekey_ui_theme_select_form($tmp_form, t('Theme configuration'), t('Selecting a different theme will change the look and feel of the site.'), $theme ? $theme : 'default', NULL, TRUE, 'theme'); + $form['theme_select'] = $tmp_form['themekey_ui_themes']; + } + break; + + case 'themekey_ui_settings_form': + $form['themekey_ui']['themekey_ui_user_profile'] = array( + '#type' => 'checkbox', + '#title' => t('Add theme option to user profile'), + '#default_value' => variable_get('themekey_ui_user_profile', 0), + '#description' => t('A User is able to select a personal theme other than the default theme set by the site administrator.'), + ); + + $form['#submit'][] = 'themekey_user_profile_form_alter_submit'; + break; + + case 'themekey_help_tutorials_form': + module_load_include('inc', 'themekey_user_profile', 'themekey_user_profile_help'); + themekey_user_profile_help_tutorials($form); + break; + } +} + + +/** + * Function taxonomy_theme_form_alter_submit(). + */ +function themekey_user_profile_form_alter_submit($form, &$form_state) { + themekey_update_static_rule('user:profile_triggers_theme', $form_state['values']['themekey_ui_user_profile']); +} + + +/** + * Implements hook_themekey_properties(). + * + * Provides additional properties for module ThemeKey: + * user:profile_theme + * + * @return + * array of themekey properties + */ +function themekey_user_profile_themekey_properties() { + + // Attributes for properties + $attributes = array(); + + $attributes['user:profile_triggers_theme'] = array( + 'description' => t("Property user:profile_triggers_theme could not be selected from the property drop down. You get this static property by activating !link. Afterwards you can move the property to any position in the rule chain. When done it triggers the switch to the theme assigned to a user profile using ThemeKey User Profile if the current user has selected a theme in her profile.", + array('!link' => l(t('Add theme option to user profile'), 'admin/config/user-interface/themekey/settings/ui'))), + 'page cache' => THEMEKEY_PAGECACHE_SUPPORTED, + 'static' => TRUE, + ); + + // Mapping functions + $maps = array(); + + $maps[] = array( + 'src' => 'user:uid', + 'dst' => 'user:profile_triggers_theme', + 'callback' => 'themekey_user_profile_uid2profile_theme', + ); + + return array('attributes' => $attributes, 'maps' => $maps); +} + + +/** + * Set custom theme from given user id (uid) + * + * @param $uid user id + * @return null|string + */ +function themekey_user_profile_uid2profile_theme($uid) { + $custom_theme = &drupal_static('themekey_custom_theme', ''); + + $theme = db_select('users', 'u')->fields('u', array('theme'))->condition('uid', $uid)->execute()->fetchField(); + + if ('default' != $theme) { + if (themekey_check_theme_enabled($theme)) { + $custom_theme = $theme; + return 'static'; + } + } + + return NULL; +} diff --git a/sites/all/modules/themekey/themekey_user_profile_help.inc b/sites/all/modules/themekey/themekey_user_profile_help.inc new file mode 100644 index 0000000000000000000000000000000000000000..ff6fa365075d96082153f8f0c0aed3cacab76a70 --- /dev/null +++ b/sites/all/modules/themekey/themekey_user_profile_help.inc @@ -0,0 +1,80 @@ +<?php + +/** + * @file + * Provides content for help pages. + * + * @author Markus Kalkbrenner | Cocomore AG + * @see http://drupal.org/user/124705 + */ + + +/** + * Adds tutorials to themekey_help_form(). + * + * @see themekey_help() + * @see themekey_help_form() + * @see themekey_ui_form_alter() + * + * @param $form + * reference to a Drupal form + */ +function themekey_user_profile_help_tutorials(&$form) { + $form['themekey_help_tutorials']['Allowing users to select a personal theme for this site'] = array( + '#type' => 'fieldset', + '#title' => t('Allowing users to select a personal theme for this site (Drupal 7 replacement of a Drupal 6 Core feature)'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + ); + + $form['themekey_help_tutorials']['Allowing users to select a personal theme for this site']['author'] = array( + '#type' => 'item', + '#title' => t('Author'), + '#markup' => l(t('!path', array('!path' => 'mkalkbrenner')), 'http://drupal.org/user/124705'), + ); + + $img_path = base_path() . drupal_get_path('module', 'themekey_user_profile') . '/img/tutorials/'; + + $form['themekey_help_tutorials']['Allowing users to select a personal theme for this site']['item'] = array( + '#type' => 'item', + '#markup' => '<p>' . t('In this tutorial, you will learn how to enable users to select a personal theme for your site. This feature was part of Drupal 6 Core but has been removed in Drupal 7 Core. The ThemeKey User Profile module replaces this functionality for Drupal 7 now.') . '</p>' . +'<p>' . t('Quick and dirty (screenshots will follow):<ol> +<li>Install ThemeKey User Profile which is part of the ThemeKey module package</li> +<li>Assign the permission "Select different theme" to the roles at !link1</li> +<li>Activate "Add theme option to user profile" at !link2</li> +<li>Configure "Selectable Thems" at !link3</li> +<li>Optional: Prioritize the static ThemeKey property "user:profile_triggers_theme" at !link4</li> +</ol>', array( + '!link1' => l(t('!path', array('!path' => '/admin/people/permissions')), 'admin/people/permissions'), + '!link2' => l(t('!path', array('!path' => '/admin/config/user-interface/themekey/settings/ui')), 'admin/config/user-interface/themekey/settings/ui'), + '!link3' => l(t('!path', array('!path' => '/admin/config/user-interface/themekey/settings/ui')), 'admin/config/user-interface/themekey/settings/ui'), + '!link4' => l(t('!path', array('!path' => '/admin/config/user-interface/themekey')), 'admin/config/user-interface/themekey'), + )) . '</p>', + ); + + $form['themekey_help_tutorials']['Allowing users to select a personal theme for this site']['versions'] = array( + '#type' => 'fieldset', + '#title' => t('Versions used to create this tutorial'), + '#collapsible' => FALSE, + ); + + $form['themekey_help_tutorials']['Allowing users to select a personal theme for this site']['versions']['themekey'] = array( + '#type' => 'item', + '#title' => t('ThemeKey'), + '#markup' => '7.x-1.0-beta2', + ); + + $form['themekey_help_tutorials']['Allowing users to select a personal theme for this site']['versions']['themekey_ui'] = array( + '#type' => 'item', + '#title' => t('ThemeKey UI'), + '#markup' => '7.x-1.0-beta2', + ); + + $form['themekey_help_tutorials']['Allowing users to select a personal theme for this site']['versions']['themekey_user_profile'] = array( + '#type' => 'item', + '#title' => t('ThemeKey User Profile'), + '#markup' => '7.x-1.0-beta2', + ); + + return $form; +} diff --git a/sites/all/modules/themekey/themekey_validators.inc b/sites/all/modules/themekey/themekey_validators.inc new file mode 100644 index 0000000000000000000000000000000000000000..48abf873de0460f04a86acfd797ecc320955e621 --- /dev/null +++ b/sites/all/modules/themekey/themekey_validators.inc @@ -0,0 +1,898 @@ +<?php + +/** + * @file + * Provides set of validators which can be used to validate + * ThemeKey Theme Switching Rules. + * @see themekey_admin.inc + * + * @author Markus Kalkbrenner | Cocomore AG + * @see http://drupal.org/user/124705 + * + * @author Carsten M�ller | Cocomore AG + * @see http://drupal.org/user/124707 + */ + + +/** + * Validates a Theme Switching Rule. + * Allowed Operators: "=", "!" + * Allowed values: "true", "false" as string + * + * + * @param $rule + * A Theme Switching Rule as associative array: + * - property: ThemeKey property as string (e.g., "drupal:path") + * - wildcard: optional string, only used if property is "drupal:path:wildcard" + * - operator: ThemeKey operator as string ("=", "!", "<", "<=", ">", ">=", "~") + * - value: ThemeKey property value as string + * + * @return + * An associative array of errors: + * - property: translated error message as string + * describing a problem with the property + * - wildcard: translated error message as string + * describing a problem with the wildcard + * - operator: translated error message as string + * describing a problem with the operator + * - value: translated error message as string + * describing a problem with the value + * If no errors detected the array is empty. + */ +function themekey_validator_string_boolean($rule) { + $errors = array(); + + switch ($rule['operator']) { + case '=': + case '!': + if ('true' !== $rule['value'] && 'false' !== $rule['value']) { + $errors['value'] = t('Possible values are "true" and "false"'); + } + break; + + default: + $errors['operator'] = t('Possible operators are "=" and "!"'); + } + + return $errors; +} + + +/** + * Validates a Theme Switching Rule. + * Allowed Operators: "=", "!" + * Allowed values: "1", "0" + * + * + * @param $rule + * A Theme Switching Rule as associative array: + * - property: ThemeKey property as string (e.g., "drupal:path") + * - wildcard: optional string, only used if property is "drupal:path:wildcard" + * - operator: ThemeKey operator as string ("=", "!", "<", "<=", ">", ">=", "~") + * - value: ThemeKey property value as string + * + * @return + * An associative array of errors: + * - property: translated error message as string + * describing a problem with the property + * - wildcard: translated error message as string + * describing a problem with the wildcard + * - operator: translated error message as string + * describing a problem with the operator + * - value: translated error message as string + * describing a problem with the value + * If no errors detected the array is empty. + */ +function themekey_validator_nummeric_boolean($rule) { + $errors = array(); + switch ($rule['operator']) { + case '=': + case '!': + if (!ctype_digit($rule['value']) || 1 < $rule['value']) { + $errors['value'] = t('Possible values are "0" and "1"'); + } + break; + + default: + $errors['operator'] = t('Possible operators are "=" and "!"'); + } + + return $errors; +} + + +/** + * Validates a Theme Switching Rule. + * Allowed Operators: "=", "!", "<", "<=", ">", ">=" + * Allowed values: string of digits (numbers) + * + * + * @param $rule + * A Theme Switching Rule as associative array: + * - property: ThemeKey property as string (e.g., "drupal:path") + * - wildcard: optional string, only used if property is "drupal:path:wildcard" + * - operator: ThemeKey operator as string ("=", "!", "<", "<=", ">", ">=", "~") + * - value: ThemeKey property value as string + * + * @return + * An associative array of errors: + * - property: translated error message as string + * describing a problem with the property + * - wildcard: translated error message as string + * describing a problem with the wildcard + * - operator: translated error message as string + * describing a problem with the operator + * - value: translated error message as string + * describing a problem with the value + * If no errors detected the array is empty. + */ +function themekey_validator_ctype_digit($rule) { + $errors = array(); + switch ($rule['operator']) { + case '~': + $errors['operator'] = t('Possible operators are "=", "!", "<", "<=", ">" and ">="'); + break; + } + + if (!ctype_digit($rule['value'])) { + $errors['value'] = t('Value must be a number'); + } + + return $errors; +} + + +/** + * Validates a Theme Switching Rule. + * Allowed Operators: any + * Allowed values: + * - configured Drupal language as string or 'und' if operator is "=" + * - valid regular expression if operator is "~" + * - any string for different operators + * + * + * @param $rule + * A Theme Switching Rule as associative array: + * - property: ThemeKey property as string (e.g., "drupal:path") + * - wildcard: optional string, only used if property is "drupal:path:wildcard" + * - operator: ThemeKey operator as string ("=", "!", "<", "<=", ">", ">=", "~") + * - value: ThemeKey property value as string + * + * @return + * An associative array of errors: + * - property: translated error message as string + * describing a problem with the property + * - wildcard: translated error message as string + * describing a problem with the wildcard + * - operator: translated error message as string + * describing a problem with the operator + * - value: translated error message as string + * describing a problem with the value + * If no errors detected the array is empty. + */ +function themekey_validator_language($rule) { + $errors = array(); + switch ($rule['operator']) { + case '=': + $languages = language_list(); + // add 'neutral' to the list of languages + $languages['neutral'] = TRUE; + if (!array_key_exists($rule['value'], $languages)) { + $errors['value'] = t('Possible values are %languages', array('%languages' => '"' . implode('", "', array_keys($languages)) . '"')); + } + break; + + case '~': + $errors = themekey_validator_regex($rule); + break; + } + + return $errors; +} + + +/** + * Validates a Theme Switching Rule. + * Allowed Operators: "<", "<=", ">", ">=" and "~" + * Allowed values: + * - valid regular expression if operator is "~" + * - string formatted like "2009-12-24 23:56:17" for different operators + * + * + * @param $rule + * A Theme Switching Rule as associative array: + * - property: ThemeKey property as string (e.g. "drupal:path") + * - wildcard: optional string, only used if property is "drupal:path:wildcard" + * - operator: ThemeKey operator as string ("=", "!", "<", "<=", ">", ">=", "~") + * - value: ThemeKey property value as string + * + * @return + * An associative array of errors: + * - property: translated error message as string + * describing a problem with the property + * - wildcard: translated error message as string + * describing a problem with the wildcard + * - operator: translated error message as string + * describing a problem with the operator + * - value: translated error message as string + * describing a problem with the value + * If no errors detected the array is empty. + */ +function themekey_validator_date_time($rule) { + $errors = array(); + switch ($rule['operator']) { + case '=': + case '!': + // It seems senseless to switch a theme for one second + $errors['operator'] = t('Possible operators are "<", "<=", ">", ">=" and "~"'); + break; + + case '~': + $errors = themekey_validator_regex($rule); + break; + + default: + if (!preg_match("/^[0-9]{4}[0-9\- :]*$/", $rule['value'])) { + $errors['value'] = t("Value isn't suitable for checks against dates formatted like \"2009-12-24 23:56:17\""); + } + } + + return $errors; +} + + +/** + * Validates a Theme Switching Rule. + * Allowed Operators: any + * Allowed values: + * - valid regular expression if operator is "~" + * - a valid date like "2009-12-24" if operator is "=" or "!" + * - fragment of a date which contains at least the year as four digits + * for different operators + * + * + * @param $rule + * A Theme Switching Rule as associative array: + * - property: ThemeKey property as string (e.g., "drupal:path") + * - wildcard: optional string, only used if property is "drupal:path:wildcard" + * - operator: ThemeKey operator as string ("=", "!", "<", "<=", ">", ">=", "~") + * - value: ThemeKey property value as string + * + * @return + * An associative array of errors: + * - property: translated error message as string + * describing a problem with the property + * - wildcard: translated error message as string + * describing a problem with the wildcard + * - operator: translated error message as string + * describing a problem with the operator + * - value: translated error message as string + * describing a problem with the value + * If no errors detected the array is empty. + */ +function themekey_validator_date($rule) { + $errors = array(); + switch ($rule['operator']) { + case '=': + case '!': + if (!preg_match("/^[0-9]{4}-[0-1]{1}[0-9]{1}-[0-3]{1}[0-9]{1}$/", $rule['value'])) { + $errors['value'] = t('Not a valid date string. Format should look like "2009-12-24"'); + } + break; + + case '~': + $errors = themekey_validator_regex($rule); + break; + + default: + if (!preg_match("/^[0-9]{4}[0-9\-]*$/", $rule['value'])) { + $errors['value'] = t("Value isn't suitable for checks against dates formatted like \"2009-12-24\""); + } + } + + return $errors; +} + + +/** + * Validates a Theme Switching Rule. + * Allowed Operators: any + * Allowed values: + * - valid regular expression if operator is "~" + * - a valid time like "23:16:56" if operator is "=" or "!" + * - fragment of a time which contains at least the hour as two digits + * for different operators + * + * + * @param $rule + * A Theme Switching Rule as associative array: + * - property: ThemeKey property as string (e.g., "drupal:path") + * - wildcard: optional string, only used if property is "drupal:path:wildcard" + * - operator: ThemeKey operator as string ("=", "!", "<", "<=", ">", ">=", "~") + * - value: ThemeKey property value as string + * + * @return + * An associative array of errors: + * - property: translated error message as string + * describing a problem with the property + * - wildcard: translated error message as string + * describing a problem with the wildcard + * - operator: translated error message as string + * describing a problem with the operator + * - value: translated error message as string + * describing a problem with the value + * If no errors detected the array is empty. + */ +function themekey_validator_time($rule) { + $errors = array(); + switch ($rule['operator']) { + case '=': + case '!': + if (!preg_match("/^[0-2][0-9]:[0-5][0-9]:[0-5][0-9]$/", $rule['value'])) { + $errors['value'] = t('Not a valid date string. Format should look like "23:16:56"'); + } + break; + + case '~': + $errors = themekey_validator_regex($rule); + break; + + default: + if (!preg_match("/^[0-2][0-9][0-9:]*$/", $rule['value'])) { + $errors['value'] = t("Value isn't suitable for checks against dates formatted like \"23:16:56\""); + } + } + + return $errors; +} + + +/** + * Validates a Theme Switching Rule. + * Allowed Operators: any + * Allowed values: + * - valid regular expression if operator is "~" + * - an existing content type if operator is "=" or "!" + * - must contain only lowercase letters, numbers, and underscores + * for different operators + * + * + * @param $rule + * A Theme Switching Rule as associative array: + * - property: ThemeKey property as string (e.g., "drupal:path") + * - wildcard: optional string, only used if property is "drupal:path:wildcard" + * - operator: ThemeKey operator as string ("=", "!", "<", "<=", ">", ">=", "~") + * - value: ThemeKey property value as string + * + * @return + * An associative array of errors: + * - property: translated error message as string + * describing a problem with the property + * - wildcard: translated error message as string + * describing a problem with the wildcard + * - operator: translated error message as string + * describing a problem with the operator + * - value: translated error message as string + * describing a problem with the value + * If no errors detected the array is empty. + */ +function themekey_validator_node_type($rule) { + $errors = array(); + switch ($rule['operator']) { + case '=': + case '!': + $node_types = node_type_get_types(); + if (!array_key_exists($rule['value'], $node_types)) { + $errors['value'] = t('Possible values are %node_types', array('%node_types' => '"' . implode('", "', array_keys($node_types)) . '"')); + } + break; + + case '~': + $errors = themekey_validator_regex($rule); + break; + + default: + if (!preg_match("/^[0-9a-z_]+$/", $rule['value'])) { + $errors['value'] = t("Value isn't suitable. It can only contain lowercase letters, numbers, and underscores"); + } + } + + return $errors; +} + + +/** + * Validates a Theme Switching Rule. + * Allowed Operators: any + * Allowed values: + * - valid regular expression if operator is "~" + * - must contain only lowercase letters, numbers, and hyphens + * for different operators + * + * + * @param $rule + * A Theme Switching Rule as associative array: + * - property: ThemeKey property as string (e.g., "drupal:path") + * - wildcard: optional string, only used if property is "drupal:path:wildcard" + * - operator: ThemeKey operator as string ("=", "!", "<", "<=", ">", ">=", "~") + * - value: ThemeKey property value as string + * + * @return + * An associative array of errors: + * - property: translated error message as string + * describing a problem with the property + * - wildcard: translated error message as string + * describing a problem with the wildcard + * - operator: translated error message as string + * describing a problem with the operator + * - value: translated error message as string + * describing a problem with the value + * If no errors detected the array is empty. + */ +function themekey_validator_http_host($rule) { + $errors = array(); + switch ($rule['operator']) { + case '~': + $errors = themekey_validator_regex($rule); + break; + + default: + if (!preg_match("/^[0-9a-z\-.]+$/", $rule['value'])) { + $errors['value'] = t("Value isn't suitable. It can only contain lowercase letters, numbers, and hyphens"); + } + } + + return $errors; +} + + +/** + * Validates a Theme Switching Rule. + * Allowed Operators: all + * Allowed values: any string without whitespace + * + * + * @param $rule + * A Theme Switching Rule as associative array: + * - property: ThemeKey property as string (e.g., "drupal:path") + * - wildcard: optional string, only used if property is "drupal:path:wildcard" + * - operator: ThemeKey operator as string ("=", "!", "<", "<=", ">", ">=", "~") + * - value: ThemeKey property value as string + * + * @return + * An associative array of errors: + * - property: translated error message as string + * describing a problem with the property + * - wildcard: translated error message as string + * describing a problem with the wildcard + * - operator: translated error message as string + * describing a problem with the operator + * - value: translated error message as string + * describing a problem with the value + * If no errors detected the array is empty. + */ +function themekey_validator_no_whitespace($rule) { + $errors = array(); + + if (preg_match("/\s/", $rule['value'])) { + $errors['value'] = t('Value must not contain whitespace characters'); + } + + return $errors; +} + + +/** + * Validates a Theme Switching Rule. + * Allowed Operators: any + * Allowed wildcards: any string without whitespace and not starting with "#" or "%" + * + * + * @param $rule + * A Theme Switching Rule as associative array: + * - property: ThemeKey property as string (e.g., "drupal:path") + * - wildcard: optional string, only used if property is "drupal:path:wildcard" + * - operator: ThemeKey operator as string ("=", "!", "<", "<=", ">", ">=", "~") + * - value: ThemeKey property value as string + * + * @return + * An associative array of errors: + * - property: translated error message as string + * describing a problem with the property + * - wildcard: translated error message as string + * describing a problem with the wildcard + * - operator: translated error message as string + * describing a problem with the operator + * - value: translated error message as string + * describing a problem with the value + * If no errors detected the array is empty. + */ +function themekey_validator_wildcard($rule) { + $errors = themekey_validator_no_whitespace($rule); + + if (preg_match("/^[#%]/", $rule['wildcard'])) { + $errors['wildcard'] = t('Wildcard must not start with type identifier "#" or "%" at this point'); + } + elseif (preg_match("/\s/", $rule['wildcard'])) { + $errors['wildcard'] = t('Wildcard must not contain whitespace characters'); + } + + return $errors; +} + + +/** + * Validates a Theme Switching Rule. + * Allowed Operators: "~" + * Allowed values: valid regular expression + * + * @see http://php.net/manual/en/pcre.pattern.php + * + * + * @param $rule + * A Theme Switching Rule as associative array: + * - property: ThemeKey property as string (e.g., "drupal:path") + * - wildcard: optional string, only used if property is "drupal:path:wildcard" + * - operator: ThemeKey operator as string ("=", "!", "<", "<=", ">", ">=", "~") + * - value: ThemeKey property value as string + * + * @return + * An associative array of errors: + * - property: translated error message as string + * describing a problem with the property + * - wildcard: translated error message as string + * describing a problem with the wildcard + * - operator: translated error message as string + * describing a problem with the operator + * - value: translated error message as string + * describing a problem with the value + * If no errors detected the array is empty. + */ +function themekey_validator_regex($rule) { + $errors = array(); + switch ($rule['operator']) { + case '~': + if (FALSE === @preg_match($rule['value'], 'dummy')) { + $errors['value'] = t('Regular expression seems to be malformed. See !link for details', array('!link' => l(t('PHP Manual'), 'http://php.net/manual/en/pcre.pattern.php'))); + } + break; + + default: + $errors['operator'] = t('The only possible operator is "~"'); + break; + } + + return $errors; +} +/** + * @file + * Provides set of validators which could be used to validate + * ThemeKey Theme Switching Rules. + * @see themekey_admin.inc + * + * @author Markus Kalkbrenner | Cocomore AG + * @see http://drupal.org/user/124705 + * + * @author Carsten Müller | Cocomore AG + * @see http://drupal.org/user/124707 + */ + + +/** + * Validates a Theme Switching Rule. + * Allowed Operators: "=", "!" + * Allowed values: paths without whitespace + * + * + * @param $rule + * A Theme Switching Rule as associative array: + * - property: ThemeKey property as string (e.g., "drupal:path") + * - wildcard: optional string, only used if property is "drupal:path:wildcard" + * - operator: ThemeKey operator as string ("=", "!", "<", "<=", ">", ">=", "~") + * - value: ThemeKey property value as string + * + * @return + * An associative array of errors: + * - property: translated error message as string + * describing a problem with the property + * - wildcard: translated error message as string + * describing a problem with the wildcard + * - operator: translated error message as string + * describing a problem with the operator + * - value: translated error message as string + * describing a problem with the value + * If no errors detected the array is empty. + */ +function themekey_validator_drupal_path($rule) { + $errors = themekey_validator_no_whitespace($rule); + + switch ($rule['operator']) { + case '=': + case '!': + if (strpos($rule['value'], '/') === 0) { + $errors['value'] = t('A Drupal path must not start with "/"'); + } + if (preg_match("@[^/][%#]@", $rule['value'])) { + $errors['value'] = t('A wildcard can only be used to replace part(s) of the path, but not for parts of a word'); + } + if (strpos($rule['value'], '?') !== FALSE) { + $errors['value'] = t('Query strings will be stripped before a Drupal path is processed. Maybe you want to chain drupal:path and system:query_string or system:query_param (both provided by additional module ThemeKey Properties).'); + } + break; + + default: + $errors['operator'] = t('Possible operators are "=" and "!"'); + } + + return $errors; +} + + +/** + * Validates a Theme Switching Rule. + * Allowed Operators: any + * Allowed values: + * - valid regular expression if operator is "~" + * - a valid IPv4 address like "123.123.123.123" if operator is "=" or "!" + * - fragment of an IPv4 address which contains at least one digit + * for different operators + * + * + * @param $rule + * A Theme Switching Rule as associative array: + * - property: ThemeKey property as string (e.g., "drupal:path") + * - wildcard: optional string, only used if property is "drupal:path:wildcard" + * - operator: ThemeKey operator as string ("=", "!", "<", "<=", ">", ">=", "~") + * - value: ThemeKey property value as string + * + * @return + * An associative array of errors: + * - property: translated error message as string + * describing a problem with the property + * - wildcard: translated error message as string + * describing a problem with the wildcard + * - operator: translated error message as string + * describing a problem with the operator + * - value: translated error message as string + * describing a problem with the value + * If no errors detected the array is empty. + */ +function themekey_validator_ip_address($rule) { + $errors = array(); + + switch ($rule['operator']) { + case '=': + case '!': + // TODO add for support IPv6 + // TODO improve regex for IPv4 + if (!preg_match("/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/", $rule['value'])) { + $errors['value'] = t('Not a valid IPv4 address. Format should look like "123.123.123.123"'); + } + break; + + case '~': + // CHECK THE REGULAR EXPRESSION + $errors = themekey_validator_regex($rule); + break; + + default: + // TODO add for support IPv6 + // TODO improve regex for IPv4 + if (!preg_match("/^[1-2][0-9\.]*$/", $rule['value'])) { + $errors['value'] = t("Value isn't suitable for checks against dates formatted like \"123.123.123.123\""); + } + break; + } + + return $errors; +} + + +/** + * Validates a Theme Switching Rule. + * Allowed Operators: "=", "!", "~" + * Allowed values: + * - valid regular expression if operator is "~" + * - a valid Drupal role if operator is "=" or "!" + * + * + * @param $rule + * A Theme Switching Rule as associative array: + * - property: ThemeKey property as string (e.g., "drupal:path") + * - wildcard: optional string, only used if property is "drupal:path:wildcard" + * - operator: ThemeKey operator as string ("=", "!", "<", "<=", ">", ">=", "~") + * - value: ThemeKey property value as string + * + * @return + * An associative array of errors: + * - property: translated error message as string + * describing a problem with the property + * - wildcard: translated error message as string + * describing a problem with the wildcard + * - operator: translated error message as string + * describing a problem with the operator + * - value: translated error message as string + * describing a problem with the value + * If no errors detected the array is empty. + */ +function themekey_validator_role($rule) { + $errors = array(); + + switch ($rule['operator']) { + case '=': + case '!': + $roles = array(); + $result = db_select('role', 'r') + ->fields('r') + ->orderBy('name', 'ASC') + ->execute(); + foreach ($result as $role) { + $roles[$role->rid] = $role->name; + } + + if (!in_array($rule['value'], $roles)) { + $errors['value'] = t('The entered user role %value is not valid. Possible roles are "%roles".', array('%value' => $rule['value'], '%roles' => implode('", "', $roles))); + } + break; + + case '~': + $errors = themekey_validator_regex($rule); + break; + + default: + $errors['operator'] = t('Possible operators are "=", "!", "~"'); + break; + + } + + return $errors; +} + + +/** + * Validates a Theme Switching Rule. + * Allowed Operators: "=", "!" + * Allowed values: + * - a three letter day String if operator is "=" or "!" + * + * + * @param $rule + * A Theme Switching Rule as associative array: + * - property: ThemeKey property as string (e.g., "drupal:path") + * - wildcard: optional string, only used if property is "drupal:path:wildcard" + * - operator: ThemeKey operator as string ("=", "!", "<", "<=", ">", ">=", "~") + * - value: ThemeKey property value as string + * + * @return + * An associative array of errors: + * - property: translated error message as string + * describing a problem with the property + * - wildcard: translated error message as string + * describing a problem with the wildcard + * - operator: translated error message as string + * describing a problem with the operator + * - value: translated error message as string + * describing a problem with the value + * If no errors detected the array is empty. + */ +function themekey_validator_day_of_week($rule) { + static $days_of_week = array('Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'); + + $errors = array(); + + switch ($rule['operator']) { + case '=': + case '!': + if (!in_array($rule['value'], $days_of_week)) { + $errors['value'] = t('The day %value is not valid. Possible days are "%days".', array('%value' => $rule['value'], '%days' => implode('", "', $days_of_week))); + } + break; + + default: + $errors['operator'] = t('Possible operators are "=" and "!"'); + break; + + } + + return $errors; +} + + +/** +* Validates a Theme Switching Rule. +* Allowed Operators: "=", "!" +* Allowed values: +* - a three letter day string if operator is "=" or "!" +* +* +* @param $rule +* A Theme Switching Rule as associative array: +* - property: ThemeKey property as string (e.g., "drupal:path") +* - wildcard: optional string, only used if property is "drupal:path:wildcard" +* - operator: ThemeKey operator as string ("=", "!", "<", "<=", ">", ">=", "~") +* - value: ThemeKey property value as string +* +* @return +* An associative array of errors: +* - property: translated error message as string +* describing a problem with the property +* - wildcard: translated error message as string +* describing a problem with the wildcard +* - operator: translated error message as string +* describing a problem with the operator +* - value: translated error message as string +* describing a problem with the value +* If no errors detected the array is empty. +*/ +function themekey_validator_month($rule) { + static $months = array('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'); + + $errors = array(); + + switch ($rule['operator']) { + case '=': + case '!': + if (!in_array($rule['value'], $months)) { + $errors['value'] = t('The month %value is not valid. Possible months are "%months".', array('%value' => $rule['value'], '%months' => implode('", "', $months))); + } + break; + + default: + $errors['operator'] = t('Possible operators are "=" and "!"'); + break; + + } + + return $errors; +} + + +/** +* Validates a Theme Switching Rule. +* Allowed Operators: "=", "!", "<", "<=", ">", ">=" +* Allowed values: +* - a three letter month string if operator is "=" or "!" +* +* +* @param $rule +* A Theme Switching Rule as associative array: +* - property: ThemeKey property as string (e.g., "drupal:path") +* - wildcard: optional string, only used if property is "drupal:path:wildcard" +* - operator: ThemeKey operator as string ("=", "!", "<", "<=", ">", ">=", "~") +* - value: ThemeKey property value as string +* +* @return +* An associative array of errors: +* - property: translated error message as string +* describing a problem with the property +* - wildcard: translated error message as string +* describing a problem with the wildcard +* - operator: translated error message as string +* describing a problem with the operator +* - value: translated error message as string +* describing a problem with the value +* If no errors detected the array is empty. +*/ +function themekey_validator_day_of_month($rule) { + $errors = array(); + + switch ($rule['operator']) { + case '=': + case '!': + case '<': + case '<=': + case '>': + case '>=': + if (!ctype_digit($rule['value']) || ((string)((int) $rule['value'])) !== ((string) $rule['value'])) { + $errors['value'] = t('Value must be a number between 1 and 31'); + } + break; + + default: + $errors['operator'] = t('Possible operators are "=", "!", "<", "<=", ">", ">="'); + break; + + } + + return $errors; +} diff --git a/sites/all/themes/unl_digitalsignage/README.txt b/sites/all/themes/unl_digitalsignage/README.txt new file mode 100644 index 0000000000000000000000000000000000000000..449273ff5ea9587b33914ce49d2381cd7f72af25 --- /dev/null +++ b/sites/all/themes/unl_digitalsignage/README.txt @@ -0,0 +1,18 @@ +Developed for Google Chrome on Mac OS X +Most recent version checked: + 13.0.782.112 + + +I haven't done a good enough job making the name references in the theme generic so you must use these: +Content type: digital_sign +Fields: Label/Name/Field/Widget + - Title + - Videos/field_videosources/Text/Text field (Enter the ID of your mediahub channel(s). Enter <em>172</em> for http://mediahub.unl.edu/channels/172) + - News/field_newssources/Text/Text field (Enter RSS feeds of your news sources. Could be a feed created by a View from your site, a feed of stories from newsroom.unl.edu that are properly tagged, etc.) + - Twitter/field_twitter/Text/Text field (Enter a public twitter username <em>UNLNews</em> or a public list <em>UNLNews/unl</em>. Do not enter the @ symbol or twitter.com.) + - Photos/field_beautyshots/Image/Image Min 710x1080 (Use only high quality professional photos, no "snap shots".) + +Things to make dev easier + - comment out overflow: hidden from html in style.css + - change data_url in unl_digitalsignage_unlalert.js to a local copy for testing + - add the controls attr and remove the autoplay attr from the video tag in unl_digitalsignage.js so the video doesn't autoplay and drive you crazy \ No newline at end of file diff --git a/sites/all/themes/unl_digitalsignage/css/style.css b/sites/all/themes/unl_digitalsignage/css/style.css new file mode 100644 index 0000000000000000000000000000000000000000..0662cf99cb904f41d1c0621d92ecd38b6475314b --- /dev/null +++ b/sites/all/themes/unl_digitalsignage/css/style.css @@ -0,0 +1,425 @@ +/* http://meyerweb.com/eric/tools/css/reset/ + v2.0 | 20110126 + License: none (public domain) +*/ + +html, body, div, span, applet, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +a, abbr, acronym, address, big, cite, code, +del, dfn, em, img, ins, kbd, q, s, samp, +small, strike, strong, sub, sup, tt, var, +b, u, i, center, +dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td, +article, aside, canvas, details, embed, +figure, figcaption, footer, header, hgroup, +menu, nav, output, ruby, section, summary, +time, mark, audio, video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; +} +/* HTML5 display-role reset for older browsers */ +article, aside, details, figcaption, figure, +footer, header, hgroup, menu, nav, section { + display: block; +} +body { + line-height: 1; +} +ol, ul { + list-style: none; +} +blockquote, q { + quotes: none; +} +blockquote:before, blockquote:after, +q:before, q:after { + content: ''; + content: none; +} +table { + border-collapse: collapse; + border-spacing: 0; +} + +/* + * MyFonts Webfont Build ID 790115, 2011-04-20T13:56:40-0400 + * + * The fonts listed in this notice are subject to the End User License + * Agreement(s) entered into by the website owner. All other parties are + * explicitly restricted from using the Licensed Webfonts(s). + * + * You may obtain a valid license at the URLs below. + * + * Webfont: URW Grotesk Cond Light + * URL: http://new.myfonts.com/fonts/urw/grotesk/urw-grotesk-t-light-condensed/ + * Foundry: URW++ + * Copyright: Copyright 2010 URW++ Design & Development Hamburg + * License: http://www.myfonts.com/viewlicense?1056 + * Licensed pageviews: unlimited/month + * CSS font-family: URWGroteskCon-Lig + * CSS font-weight: normal + * + * (c) 2011 Bitstream, Inc +*/ +@font-face { + font-family: 'URWGroteskCon-Lig'; + src: url('/wdn2011/templates_3.0/css/fonts/URWGrotesk/style_5721.eot'); /* IE9 Compat Modes */ + src: url('/wdn2011/templates_3.0/css/fonts/URWGrotesk/style_5721.eot?iefix') format('eot'), /* IE6-IE8 */ + url('/wdn2011/templates_3.0/css/fonts/URWGrotesk/style_5721.woff') format('woff'), /* Modern Browsers */ + url('data:font/opentype;base64,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AHAAOgAvAC8AbgBlAHcALgBtAHkAZgBvAG4AdABzAC4AYwBvAG0ALwBsAGkAYwBlAG4AcwBlAC8AMgA0ACwAMQAxADQANiYeAFUAUgBXAEcAcgBvAHQAZQBzAGsAQwBvAG4ALQBMAGkAZwB3AHcAdwAuAHUAcgB3AHAAcAAuAGQAZVVSV0dyb3Rlc2tDb24tTGlnAAACAAAAAAAA/74AKwAAAAAAAAAAAAAAAAAAAAAAAAAAAdYAAAABAAIAAwAEAAUABgAHAAgACQAKAAsADAANAA4ADwAQABEAEgATABQAFQAWABcAGAAZABoAGwAcAB0AHgAfACAAIQAiACMAJAAlACYAJwAoACkAKgArACwALQAuAC8AMAAxADIAMwA0ADUANgA3ADgAOQA6ADsAPAA9AD4APwBAAEEAQgBDAEQARQBGAEcASABJAEoASwBMAE0ATgBPAFAAUQBSAFMAVABVAFYAVwBYAFkAWgBbAFwAXQBeAF8AYABhAQIAowCEAIUAvQCWAOgAhgCOAIsAnQCpAKQBAwCKANoAgwCTAPIA8wCNAJcAiADDAN4A8QCeAKoA9QD0APYAogCtAMkAxwCuAGIAYwCQAGQAywBlAMgAygDPAMwAzQDOAOkAZgDTANAA0QCvAGcA8ACRANYA1ADVAGgA6wDtAIkAagBpAGsAbQBsAG4AoABvAHEAcAByAHMAdQB0AHYAdwDqAHgAegB5AHsAfQB8ALgAoQB/AH4AgACBAOwA7gC6AQQBBQEGAQcBCAEJAP0A/gEKAQsBDAD/AQABDQEOAQ8BAQEQAREBEgETARQBFQEWARcBGAEZARoBGwD4APkBHAEdAR4BHwEgASEBIgEjASQBJQEmAScA+gDXASgBKQEqASsBLAEtAS4BLwEwATEBMgEzATQA4gDjATUBNgE3ATgBOQE6ATsBPAE9AT4BPwFAAUEAsACxAUIBQwFEAUUBRgFHAUgBSQFKAUsA+wD8AOQA5QFMAU0BTgFPAVABUQFSAVMBVAFVAVYBVwFYAVkBWgFbAVwBXQFeALsBXwFgAWEBYgDmAOcApgFjAWQBZQFmAWcBaAFpAWoBawFsAW0BbgFvANgA4QFwANsA3ADdAOAA2QDfAXEBcgFzAXQBdQF2ALIAswF3ALYAtwDEALQAtQDFAIIAwgCHAKsAxgC+AL8BeAF5ALwBegF7AXwBfQF+AX8BgAGBAYIBgwGEAYUBhgGHAYgBiQGKAPcBiwGMAY0BjgGPAZAAjAGRAZIBkwGUAZUBlgGXAJgBmACoAJoAmQDvAKUAkgCcAZkApwCPAJQAlQGaAZsBnAC5AZ0BngGfAaABoQGiAaMBpAGlAaYBpwGoAMAAwQGpAaoBqwGsAa0BrgGvAbABsQGyAbMBtAG1AbYBtwG4AbkBugG7AbwBvQG+Ab8BwAHBAcIBwwHEAcUBxgHHAcgByQHKAcsBzAHNAc4BzwHQAdEB0gHTAdQB1QHWAdcB2AHZB3VuaTAwQTAHdW5pMDBBRAdBbWFjcm9uB2FtYWNyb24GQWJyZXZlBmFicmV2ZQdBb2dvbmVrB2FvZ29uZWsLQ2NpcmN1bWZsZXgKQ2RvdGFjY2VudApjZG90YWNjZW50BkRjYXJvbgZkY2Fyb24GRGNyb2F0B0VtYWNyb24HZW1hY3JvbgZFYnJldmUGZWJyZXZlCkVkb3RhY2NlbnQKZWRvdGFjY2VudAdFb2dvbmVrB2VvZ29uZWsGRWNhcm9uBmVjYXJvbgtHY2lyY3VtZmxleAtnY2lyY3VtZmxleApHZG90YWNjZW50Cmdkb3RhY2NlbnQMR2NvbW1hYWNjZW50DGdjb21tYWFjY2VudAtIY2lyY3VtZmxleARIYmFyBGhiYXIHSW1hY3JvbgdpbWFjcm9uBmlicmV2ZQdJb2dvbmVrB2lvZ29uZWsCSUoCaWoMS2NvbW1hYWNjZW50DGtjb21tYWFjY2VudAxrZ3JlZW5sYW5kaWMGTGFjdXRlBmxhY3V0ZQxMY29tbWFhY2NlbnQMbGNvbW1hYWNjZW50BkxjYXJvbgZsY2Fyb24ETGRvdARsZG90Bk5hY3V0ZQZuYWN1dGUMTmNvbW1hYWNjZW50DG5jb21tYWFjY2VudAZOY2Fyb24GbmNhcm9uC25hcG9zdHJvcGhlA0VuZwNlbmcHT21hY3JvbgdvbWFjcm9uDU9odW5nYXJ1bWxhdXQNb2h1bmdhcnVtbGF1dAZSYWN1dGUGcmFjdXRlDFJjb21tYWFjY2VudAxyY29tbWFhY2NlbnQGUmNhcm9uBnJjYXJvbgZTYWN1dGUGc2FjdXRlC1NjaXJjdW1mbGV4C3NjaXJjdW1mbGV4DFRjb21tYWFjY2VudAx0Y29tbWFhY2NlbnQGVGNhcm9uBnRjYXJvbgRUYmFyBHRiYXIGdXRpbGRlB1VtYWNyb24HdW1hY3JvbgZ1YnJldmUFVXJpbmcFdXJpbmcNVWh1bmdhcnVtbGF1dA11aHVuZ2FydW1sYXV0B1VvZ29uZWsHdW9nb25lawtXY2lyY3VtZmxleAtZY2lyY3VtZmxleAt5Y2lyY3VtZmxleAZaYWN1dGUGemFjdXRlClpkb3RhY2NlbnQKemRvdGFjY2VudAd1bmkwMUNEB3VuaTAxQ0UHdW5pMDFEMQd1bmkwMUQzB3VuaTAxRDQGR2Nhcm9uBmdjYXJvbgd1bmkwMUY0DFNjb21tYWFjY2VudAxzY29tbWFhY2NlbnQHdW5pMDIxQQd1bmkwMjFCCWFmaWk1NzkyOQd1bmkwMkM5B3VuaTAzODcGV2FjdXRlCVdkaWVyZXNpcwd1bmkyMDA3B3VuaTIwMTEKZmlndXJlZGFzaAlhZmlpMDAyMDgJZXhjbGFtZGJsB3VuaTIwM0UMemVyb3N1cGVyaW9yDGZvdXJzdXBlcmlvcgxmaXZlc3VwZXJpb3ILc2l4c3VwZXJpb3INc2V2ZW5zdXBlcmlvcg1laWdodHN1cGVyaW9yDG5pbmVzdXBlcmlvcgx6ZXJvaW5mZXJpb3ILb25laW5mZXJpb3ILdHdvaW5mZXJpb3INdGhyZWVpbmZlcmlvcgxmb3VyaW5mZXJpb3IMZml2ZWluZmVyaW9yC3NpeGluZmVyaW9yDXNldmVuaW5mZXJpb3INZWlnaHRpbmZlcmlvcgxuaW5laW5mZXJpb3IEbGlyYQZwZXNldGEERXVybwd1bmkyMTAxCWFmaWk2MTI0OAd1bmkyMTE3B3VuaTIxMjYIb25ldGhpcmQJdHdvdGhpcmRzCW9uZWVpZ2h0aAx0aHJlZWVpZ2h0aHMLZml2ZWVpZ2h0aHMMc2V2ZW5laWdodGhzCGVtcHR5c2V0B3VuaTIyMzYHZG90bWF0aAd0cmlhZ3J0B3RyaWFnbGYObXVzaWNhbG5vdGVkYmwIZG90bGVzc2oLY29tbWFhY2NlbnQFQWN1dGUFQ2Fyb24IRGllcmVzaXMFR3JhdmUMSHVuZ2FydW1sYXV0Bk1hY3Jvbg1jb21tYXN1cGVyaW9yDnBlcmlvZHN1cGVyaW9yAmZmA2ZmaQNmZmwGRGJyZXZlCkFkb3RhY2NlbnQKRmRvdGFjY2VudA1haHVuZ2FydW1sYXV0CmFkb3RhY2NlbnQNb25lLm51bWVyYXRvcg10d28ubnVtZXJhdG9yD3RocmVlLm51bWVyYXRvcg5mb3VyLm51bWVyYXRvcg5maXZlLm51bWVyYXRvcg1zaXgubnVtZXJhdG9yD3NldmVuLm51bWVyYXRvcg9laWdodC5udW1lcmF0b3IObmluZS5udW1lcmF0b3IOemVyby5udW1lcmF0b3IPb25lLmRlbm9taW5hdG9yD3R3by5kZW5vbWluYXRvchF0aHJlZS5kZW5vbWluYXRvchBmb3VyLmRlbm9taW5hdG9yEGZpdmUuZGVub21pbmF0b3IPc2l4LmRlbm9taW5hdG9yEXNldmVuLmRlbm9taW5hdG9yEWVpZ2h0LmRlbm9taW5hdG9yEG5pbmUuZGVub21pbmF0b3IQemVyby5kZW5vbWluYXRvcgl6ZXJvc2xhc2gGb25lb25lCG9uZWZpZnRoCHR3b2ZpZnRoCnRocmVlZmlmdGgJZm91cmZpZnRoCG9uZXNpeHRoCWZpdmVzaXh0aAlEb3RhY2NlbnQEUmluZwpDaXJjdW1mbGV4BUJyZXZlBVRpbGRlB0NlZGlsbGEGT2dvbmVrDmNvbW1hYWNjZW50LnNjCkNvbW1hLmhpZ2gMRGllcmVzaXMuYWx0CmNvbW1hLmhpZ2gMZGllcmVzaXMuYWx0DWNvbW1hcmV2ZXJzZWQOemVyb3plcm8uYWZyYWMAAAC4Af+FsAGNAEuwCFBYsQEBjlmxRgYrWCGwEFlLsBRSWCGwgFkdsAYrXFhZsBQrAAA=') format('opentype'), + url('/wdn2011/templates_3.0/css/fonts/URWGrotesk/style_5721.svg#style_5721') format('svg'); /* Legacy iOS */ + } + +/* General page +*************/ +html { + width: 1920px; + height: 1080px; + overflow: hidden; +} +body.node-type-digital-sign.front { + background: #FFF !important; +} +body.node-type-digital-sign { + background-color: gray; + background-image: url(../images/background-crosshatch.png); + color: #444; +} +body.node-type-digital-sign #breadcrumb { + display: none; +} +body.node-type-digital-sign #page-title { + display: inline-block; + margin-top: 24px; + margin-bottom: 20px; + padding: 14px 20px 0 122px; + font-family: 'URWGroteskCon-Lig'; + font-size: 65px; + color: #FFF; + text-shadow: 2px 2px 4px #000; + border-radius: 0 10px 10px 0; +} +body.node-type-digital-sign .field { + z-index: 10; + position: relative; +} +body.node-type-digital-sign .field .field-label { + font-family: 'URWGroteskCon-Lig'; + font-size: 45px; + letter-spacing: .04em; +} +/* Background image +****************/ +#unl_digitalsignage_background { + z-index: -1; + position: absolute; + top: 0; + left: 0; + width: 1920px; + height: 1080px; + background-image: url('../images/background-connected.png'); +} + + +/* Beauty Shots +*****************/ +body.node-type-digital-sign .field-name-field-beautyshots { + z-index: -2; + position: absolute; + top: 0; + right: 0; +} +body.node-type-digital-sign .field-name-field-beautyshots .field-item { + position: absolute; + top: 0; + right: 0; +} +body.node-type-digital-sign .field-name-field-beautyshots .field-item img { + max-height: 1080px; +} + +/* Twitter +*****************/ +body.node-type-digital-sign .field-name-field-twitter { + display: inline-block; + float: right; + width: 350px; + min-height: 175px; + padding: 20px 0 20px 20px; + margin: 20px 0 0 50px; + border-radius: 10px 0 0 90px / 10px 0 0 163px; + font-family: Helvetica Neue, Helvetica, Arial, sans-serif; + background-color: rgba(255, 255, 255, 0.86); + background-image: -webkit-gradient( + linear, + right bottom, + left top, + color-stop(0.19, rgb(240,240,240)), + color-stop(0.6, rgb(222,222,222)), + color-stop(0.8, rgb(252,252,252)) + ); +} +body.node-type-digital-sign .field-name-field-twitter img { + float: left; + margin: 0 10px 15px 0; + height: 48px; + width: 48px; +} +body.node-type-digital-sign .field-name-field-twitter .tweet-user { + float: left; +} +body.node-type-digital-sign .field-name-field-twitter .tweet-user-name { + float: left; + margin-bottom: 4px; + font-size: 25px; + font-weight: bold; +} +body.node-type-digital-sign .field-name-field-twitter .tweet-full-name { + float: left; + clear: left; + font-weight: normal; + font-size: 16px; + color: #999; +} +body.node-type-digital-sign .field-name-field-twitter .tweet-text { + clear: left; + text-align: right; + font-size: 20px; + line-height: 1.2em; + padding-right: 20px; + word-wrap: break-word; +} +body.node-type-digital-sign .field-name-field-twitter .retweet { + float: right; + padding-right: 20px; + font-style: italic; + padding-top: 10px; +} + +/* News +*****************/ + +body.node-type-digital-sign .field-name-field-newssources { + clear: both; + margin: 0 0 0 23px; +} +body.node-type-digital-sign .field-name-field-newssources .field-items { + float: left; + width: 510px; + background: rgba(255, 255, 255, 0.86); + background-image: -webkit-gradient( + linear, + right bottom, + left top, + color-stop(0.19, rgb(240,240,240)), + color-stop(0.6, rgb(222,222,222)), + color-stop(0.8, rgb(252,252,252)) + ); +} +body.node-type-digital-sign .field-name-field-newssources .field-item { + clear: left; + font-family: Helvetica Neue, Helvetica, Arial, sans-serif; + border-bottom: 1px solid black; + padding: 0; + min-height: 42px; +} +body.node-type-digital-sign .field-name-field-newssources .field-item:last-child { + border: none; +} + +body.node-type-digital-sign .field-name-field-newssources .field-item.show { + border-top: 1px solid #c9c9c9; + border-radius: 5px 0 0 5px; + width: 530px; + position: relative; + top: -1px; + left: -10px; + z-index: 11; + color: #222; + text-shadow: 0px 1px 0px white; + -webkit-box-shadow: 0px 1px 2px rgba(0,0,0, 0.5), inset 0 1px 2px #eee; + background-color: gray; + background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0.25, #C9C9C9), color-stop(1, #AFAFAF)); +} +body.node-type-digital-sign .field-name-field-newssources .field-item.show:after { + content: ' '; + height: 0; + position: absolute; + right: -52px; + top: 0px; + width: 0; + border: 26px solid transparent; + border-left-color: #333; + /*border-left-image: url(border.png) 27 27 27 27 round round;*/ +} +body.node-type-digital-sign .field-name-field-newssources .field-item.show h3 span { + padding: 0 0 0 52px; + font-size: 21px; +} + +body.node-type-digital-sign .field-name-field-newssources .field-item h3 { + font-size: 20px; + background-repeat: no-repeat; + padding: 5px 0 5px 5px; + background-origin: content-box; + height: 42px; +} +body.node-type-digital-sign .field-name-field-newssources .field-item h3 span { + padding: 0 0 0 45px; + display: block; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + position: relative; + top: 10px; + min-height: 23px; +} +body.node-type-digital-sign .field-name-field-newssources .field-newssources-desc, +body.node-type-digital-sign .field-name-field-newssources .field-newssources-link, +body.node-type-digital-sign .field-name-field-newssources .field-newssources-qrcode { + display: none; +} + +/* The actual div that shows the story +*********************/ + +.field-name-field-newssources .field-display { + float: left; + position: relative; + width: 742px; + height: 243px; + background-color: rgba(255, 255, 255, 0.86); + background-image: -webkit-gradient( + linear, + right bottom, + left top, + color-stop(0.19, rgb(240,240,240)), + color-stop(0.6, rgb(222,222,222)), + color-stop(0.8, rgb(252,252,252)) + ); + padding: 10px 10px 10px 20px; + margin-left: 20px; + font-size: 24px; + font-family: Helvetica Neue, Helvetica, Arial, sans-serif; + border-radius: 0 10px 10px 0; +} +.field-name-field-newssources .field-display .desc { + line-height: 1.2em; + overflow: hidden; + max-height: 221px; +} +.field-name-field-newssources .field-display .desc * { + margin: 0; + padding: 0; + background: transparent !important; +} +.field-name-field-newssources .field-display .link { + position: absolute; + bottom: 5px; + right: 5px; + font-size: 20px; +} +.field-name-field-newssources .field-display .link:before { + content: '...continued at'; + font-style: italic; + font-size: 16px; + padding-right: 12px; +} +.field-name-field-newssources .field-display .qrcode { + float: right; + margin-left: 10px; +} + +/* Videos +*****************/ + +body.node-type-digital-sign .field-name-field-videosources { + margin: 0 0 0 23px; +} +body.node-type-digital-sign .field-name-field-videosources video { + height: 540px; + float: left; + margin-bottom: 45px; +} +body.node-type-digital-sign .field-name-field-videosources .field-videosources-desc { + background: rgba(255, 255, 255, 0.86); + background-image: -webkit-gradient( + linear, + right bottom, + left top, + color-stop(0.19, rgb(240,240,240)), + color-stop(0.6, rgb(222,222,222)), + color-stop(0.8, rgb(252,252,252)) + ); + padding: 10px 10px 10px 10px; + position: relative; + top: 20px; + max-height: 468px; + width: 330px; + overflow: hidden; + font-family: Helvetica Neue, Helvetica, Arial, sans-serif; + font-size: 20px; + font-style: italic; + border-radius: 0 10px 10px 0; + opacity: 0.95; + line-height: 1.2em; +} +body.node-type-digital-sign .field-name-field-videosources .field-videosources-desc h3 { + font-size: 25px; + font-style: normal; + margin-bottom: 20px; +} +body.node-type-digital-sign .field-name-field-videosources .field-videosources-desc p { + display: inline; +} + +/* UNL Alert Box +*****************/ +#unlalert-wrapper { + z-index: 9999999; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + text-align: center; + font-family: Helvetica Neue, Helvetica, Arial, sans-serif; + font-size: 70px; + color: #111; + background: transparent; +} +#unlalert-wrapper div { + margin: 40px 0; +} +#unlalert-desc { + padding: 0 100px; + font-size: 110px; +} +#unlalert-bg-2 { + z-index: -1; + display: none; + position: fixed; + left: 0; + top: 0; + margin: 0 !important; + width: 100%; + height: 100%; + background-color:#d10000; + background-image: radial-gradient(55% 19%, ellipse closest-side, #d10000 9%,#750808 108%); +} +#unlalert-bg-1 { + z-index: -2; + position: fixed; + left: 0; + top: 0; + margin: 0 !important; + width: 100%; + height: 100%; + background-color: #fff; +} diff --git a/sites/all/themes/unl_digitalsignage/designfiles/designassets/3249853189_bccb75b997.jpg b/sites/all/themes/unl_digitalsignage/designfiles/designassets/3249853189_bccb75b997.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1ecdadf92face9a3599db04dafb5148ebae13f99 Binary files /dev/null and b/sites/all/themes/unl_digitalsignage/designfiles/designassets/3249853189_bccb75b997.jpg differ diff --git a/sites/all/themes/unl_digitalsignage/designfiles/designassets/3d_N.png b/sites/all/themes/unl_digitalsignage/designfiles/designassets/3d_N.png new file mode 100644 index 0000000000000000000000000000000000000000..d115092737e9624850c80f7c8a03559f961e332d Binary files /dev/null and b/sites/all/themes/unl_digitalsignage/designfiles/designassets/3d_N.png differ diff --git a/sites/all/themes/unl_digitalsignage/designfiles/designassets/Giovanni-Italian-Restaurant-Floor-Plan.jpg b/sites/all/themes/unl_digitalsignage/designfiles/designassets/Giovanni-Italian-Restaurant-Floor-Plan.jpg new file mode 100644 index 0000000000000000000000000000000000000000..37e4cbf228eca600f7802da748e92be3acdd7577 Binary files /dev/null and b/sites/all/themes/unl_digitalsignage/designfiles/designassets/Giovanni-Italian-Restaurant-Floor-Plan.jpg differ diff --git a/sites/all/themes/unl_digitalsignage/designfiles/designassets/Gnome-Apple Bar Graphs/Bar Graph_Josh McCulley.psd b/sites/all/themes/unl_digitalsignage/designfiles/designassets/Gnome-Apple Bar Graphs/Bar Graph_Josh McCulley.psd new file mode 100644 index 0000000000000000000000000000000000000000..b52e01f8f62fd23ab6ed833c1c738dfbb4773b09 Binary files /dev/null and b/sites/all/themes/unl_digitalsignage/designfiles/designassets/Gnome-Apple Bar Graphs/Bar Graph_Josh McCulley.psd differ diff --git a/sites/all/themes/unl_digitalsignage/designfiles/designassets/Gnome-Apple Bar Graphs/Copyright.txt b/sites/all/themes/unl_digitalsignage/designfiles/designassets/Gnome-Apple Bar Graphs/Copyright.txt new file mode 100644 index 0000000000000000000000000000000000000000..e1ac77c57293807e117c0c6b65559ea3051efd5c --- /dev/null +++ b/sites/all/themes/unl_digitalsignage/designfiles/designassets/Gnome-Apple Bar Graphs/Copyright.txt @@ -0,0 +1,5 @@ +Copyright 2010 Josh McCulley + +These are free for personal use, if you would like to use for commercial use please e-mail me @ wjlp45@gmail.com , and ask permission (inlcude website address, name, and a valid e-mail address.) + +Thanks, comments and request can be sent to the e-mail listed above as well! \ No newline at end of file diff --git a/sites/all/themes/unl_digitalsignage/designfiles/designassets/Gnome-Apple Bar Graphs/Preview.jpg b/sites/all/themes/unl_digitalsignage/designfiles/designassets/Gnome-Apple Bar Graphs/Preview.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f9b3f1116e4edf51bca90b140976eb8182f68281 Binary files /dev/null and b/sites/all/themes/unl_digitalsignage/designfiles/designassets/Gnome-Apple Bar Graphs/Preview.jpg differ diff --git a/sites/all/themes/unl_digitalsignage/designfiles/designassets/Twitter_Bird_Logo_by_iPotion.png b/sites/all/themes/unl_digitalsignage/designfiles/designassets/Twitter_Bird_Logo_by_iPotion.png new file mode 100644 index 0000000000000000000000000000000000000000..a3aa3b1beb3f15f56e86f4154c9710331fe5c060 Binary files /dev/null and b/sites/all/themes/unl_digitalsignage/designfiles/designassets/Twitter_Bird_Logo_by_iPotion.png differ diff --git a/sites/all/themes/unl_digitalsignage/designfiles/designassets/backgrounds/Motion-Red-1-QOFD4H7K5S-1920x1200.jpg b/sites/all/themes/unl_digitalsignage/designfiles/designassets/backgrounds/Motion-Red-1-QOFD4H7K5S-1920x1200.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d97e457639a7109ec63e89ed2a2ef2211a1ca5f4 Binary files /dev/null and b/sites/all/themes/unl_digitalsignage/designfiles/designassets/backgrounds/Motion-Red-1-QOFD4H7K5S-1920x1200.jpg differ diff --git a/sites/all/themes/unl_digitalsignage/designfiles/designassets/backgrounds/Red-Boxes.jpg b/sites/all/themes/unl_digitalsignage/designfiles/designassets/backgrounds/Red-Boxes.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b42d0515527a12da25f14966f34bd2eb72a9e7be Binary files /dev/null and b/sites/all/themes/unl_digitalsignage/designfiles/designassets/backgrounds/Red-Boxes.jpg differ diff --git a/sites/all/themes/unl_digitalsignage/designfiles/designassets/backgrounds/red_in_abstract-wide.jpg b/sites/all/themes/unl_digitalsignage/designfiles/designassets/backgrounds/red_in_abstract-wide.jpg new file mode 100644 index 0000000000000000000000000000000000000000..fcb0dc3de5b0e7eb4e9b40b65f48e0268b881617 Binary files /dev/null and b/sites/all/themes/unl_digitalsignage/designfiles/designassets/backgrounds/red_in_abstract-wide.jpg differ diff --git a/sites/all/themes/unl_digitalsignage/designfiles/designassets/backgrounds/water_drops_red_0360.jpg b/sites/all/themes/unl_digitalsignage/designfiles/designassets/backgrounds/water_drops_red_0360.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c4cd57040dbffd3defb6c66d06171a25b9c314c5 Binary files /dev/null and b/sites/all/themes/unl_digitalsignage/designfiles/designassets/backgrounds/water_drops_red_0360.jpg differ diff --git a/sites/all/themes/unl_digitalsignage/designfiles/designassets/blank pin.png b/sites/all/themes/unl_digitalsignage/designfiles/designassets/blank pin.png new file mode 100644 index 0000000000000000000000000000000000000000..11bbb85843832e85de64d219a7d074b1551a75fb Binary files /dev/null and b/sites/all/themes/unl_digitalsignage/designfiles/designassets/blank pin.png differ diff --git a/sites/all/themes/unl_digitalsignage/designfiles/designassets/briancoburn400x644.jpg b/sites/all/themes/unl_digitalsignage/designfiles/designassets/briancoburn400x644.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7d58b2807e35e0a274b3fb81d12bf01c9cd14d50 Binary files /dev/null and b/sites/all/themes/unl_digitalsignage/designfiles/designassets/briancoburn400x644.jpg differ diff --git a/sites/all/themes/unl_digitalsignage/designfiles/designassets/dondeplowman_02_left.jpg b/sites/all/themes/unl_digitalsignage/designfiles/designassets/dondeplowman_02_left.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1e960bfb2a11dca6c5df784ee1ed873d9187322f Binary files /dev/null and b/sites/all/themes/unl_digitalsignage/designfiles/designassets/dondeplowman_02_left.jpg differ diff --git a/sites/all/themes/unl_digitalsignage/designfiles/designassets/manderscheid.jpg b/sites/all/themes/unl_digitalsignage/designfiles/designassets/manderscheid.jpg new file mode 100644 index 0000000000000000000000000000000000000000..bf6046fca9d18e8256964f14940bc1af9b24b9c5 Binary files /dev/null and b/sites/all/themes/unl_digitalsignage/designfiles/designassets/manderscheid.jpg differ diff --git a/sites/all/themes/unl_digitalsignage/designfiles/designassets/psd-red-speedometer-icon.psd b/sites/all/themes/unl_digitalsignage/designfiles/designassets/psd-red-speedometer-icon.psd new file mode 100644 index 0000000000000000000000000000000000000000..94c3325f0f27a5276572b7243ca906762a3342f6 Binary files /dev/null and b/sites/all/themes/unl_digitalsignage/designfiles/designassets/psd-red-speedometer-icon.psd differ diff --git a/sites/all/themes/unl_digitalsignage/designfiles/designassets/scrollbars.png b/sites/all/themes/unl_digitalsignage/designfiles/designassets/scrollbars.png new file mode 100644 index 0000000000000000000000000000000000000000..18c04c8d870a3d96365dddf7d91b0a1d30bfdb60 Binary files /dev/null and b/sites/all/themes/unl_digitalsignage/designfiles/designassets/scrollbars.png differ diff --git a/sites/all/themes/unl_digitalsignage/designfiles/designassets/videoscreen-notitle.png b/sites/all/themes/unl_digitalsignage/designfiles/designassets/videoscreen-notitle.png new file mode 100644 index 0000000000000000000000000000000000000000..8c9cb68f98445365aceb7e1a72d10cb653cf7855 Binary files /dev/null and b/sites/all/themes/unl_digitalsignage/designfiles/designassets/videoscreen-notitle.png differ diff --git a/sites/all/themes/unl_digitalsignage/designfiles/designassets/videoscreen.png b/sites/all/themes/unl_digitalsignage/designfiles/designassets/videoscreen.png new file mode 100644 index 0000000000000000000000000000000000000000..4ed29d437a302edf91b7846ea9be0208e58d5d96 Binary files /dev/null and b/sites/all/themes/unl_digitalsignage/designfiles/designassets/videoscreen.png differ diff --git a/sites/all/themes/unl_digitalsignage/designfiles/designassets/vsip.png b/sites/all/themes/unl_digitalsignage/designfiles/designassets/vsip.png new file mode 100644 index 0000000000000000000000000000000000000000..82693d13c7d5cb16b8f8db46b1beaf788b03083f Binary files /dev/null and b/sites/all/themes/unl_digitalsignage/designfiles/designassets/vsip.png differ diff --git a/sites/all/themes/unl_digitalsignage/designfiles/display.png b/sites/all/themes/unl_digitalsignage/designfiles/display.png new file mode 100644 index 0000000000000000000000000000000000000000..24a8151479340ef27c0f5028cea6209649dd334a Binary files /dev/null and b/sites/all/themes/unl_digitalsignage/designfiles/display.png differ diff --git a/sites/all/themes/unl_digitalsignage/designfiles/display1920x1080.jpg b/sites/all/themes/unl_digitalsignage/designfiles/display1920x1080.jpg new file mode 100644 index 0000000000000000000000000000000000000000..641c4b0afee4b7963cca5bea3a9a93bcf89fbefd Binary files /dev/null and b/sites/all/themes/unl_digitalsignage/designfiles/display1920x1080.jpg differ diff --git a/sites/all/themes/unl_digitalsignage/designfiles/display1920x1080.psd b/sites/all/themes/unl_digitalsignage/designfiles/display1920x1080.psd new file mode 100644 index 0000000000000000000000000000000000000000..8cc3d10902cfad42915902c7e0ba2f9de880e694 Binary files /dev/null and b/sites/all/themes/unl_digitalsignage/designfiles/display1920x1080.psd differ diff --git a/sites/all/themes/unl_digitalsignage/designfiles/kiosk1080.psd b/sites/all/themes/unl_digitalsignage/designfiles/kiosk1080.psd new file mode 100644 index 0000000000000000000000000000000000000000..6314aecd36b671b9ed0cb33ea22d19e7252730b0 Binary files /dev/null and b/sites/all/themes/unl_digitalsignage/designfiles/kiosk1080.psd differ diff --git a/sites/all/themes/unl_digitalsignage/designfiles/kiosk1280.psd b/sites/all/themes/unl_digitalsignage/designfiles/kiosk1280.psd new file mode 100644 index 0000000000000000000000000000000000000000..8b5af743919c8f35978c9b2af059c9039eb3b788 Binary files /dev/null and b/sites/all/themes/unl_digitalsignage/designfiles/kiosk1280.psd differ diff --git a/sites/all/themes/unl_digitalsignage/designfiles/kiosk1920x1080.jpg b/sites/all/themes/unl_digitalsignage/designfiles/kiosk1920x1080.jpg new file mode 100644 index 0000000000000000000000000000000000000000..58b7ef4aaae59969b523f1813f2ed4f1499f8b70 Binary files /dev/null and b/sites/all/themes/unl_digitalsignage/designfiles/kiosk1920x1080.jpg differ diff --git a/sites/all/themes/unl_digitalsignage/designfiles/kiosk1920x1280.jpg b/sites/all/themes/unl_digitalsignage/designfiles/kiosk1920x1280.jpg new file mode 100644 index 0000000000000000000000000000000000000000..53919ce2702223f5dd442572f5d18a29f55c05bf Binary files /dev/null and b/sites/all/themes/unl_digitalsignage/designfiles/kiosk1920x1280.jpg differ diff --git a/sites/all/themes/unl_digitalsignage/images/background-connected.png b/sites/all/themes/unl_digitalsignage/images/background-connected.png new file mode 100644 index 0000000000000000000000000000000000000000..840617cb6879e95bbb63fa5390a78abc245b9a44 Binary files /dev/null and b/sites/all/themes/unl_digitalsignage/images/background-connected.png differ diff --git a/sites/all/themes/unl_digitalsignage/images/background-crosshatch.png b/sites/all/themes/unl_digitalsignage/images/background-crosshatch.png new file mode 100644 index 0000000000000000000000000000000000000000..584a4f9382a7b714be262af30cef4bd345d01f61 Binary files /dev/null and b/sites/all/themes/unl_digitalsignage/images/background-crosshatch.png differ diff --git a/sites/all/themes/unl_digitalsignage/images/background-droplets.png b/sites/all/themes/unl_digitalsignage/images/background-droplets.png new file mode 100644 index 0000000000000000000000000000000000000000..3ba1ef2202b50a5bf735e0e6cbd41798b98e5bce Binary files /dev/null and b/sites/all/themes/unl_digitalsignage/images/background-droplets.png differ diff --git a/sites/all/themes/unl_digitalsignage/images/background-rays.png b/sites/all/themes/unl_digitalsignage/images/background-rays.png new file mode 100644 index 0000000000000000000000000000000000000000..1c17b7a72156c1e22a919b88da4755b2995916b6 Binary files /dev/null and b/sites/all/themes/unl_digitalsignage/images/background-rays.png differ diff --git a/sites/all/themes/unl_digitalsignage/images/background.jpg b/sites/all/themes/unl_digitalsignage/images/background.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b340519c6d684180917cbbab8cebff1dc7d86f4a Binary files /dev/null and b/sites/all/themes/unl_digitalsignage/images/background.jpg differ diff --git a/sites/all/themes/unl_digitalsignage/images/background.png b/sites/all/themes/unl_digitalsignage/images/background.png new file mode 100644 index 0000000000000000000000000000000000000000..75857f96abc450c0fa267faa0d478d4861128e9f Binary files /dev/null and b/sites/all/themes/unl_digitalsignage/images/background.png differ diff --git a/sites/all/themes/unl_digitalsignage/node--digital-sign.tpl.php b/sites/all/themes/unl_digitalsignage/node--digital-sign.tpl.php new file mode 100644 index 0000000000000000000000000000000000000000..35f5ddf409c0e26da4586f265a2ea267fa19a83a --- /dev/null +++ b/sites/all/themes/unl_digitalsignage/node--digital-sign.tpl.php @@ -0,0 +1,99 @@ +<?php + +/** + * @file + * unl_digitalsignage theme implementation to display a node. + * + * Available variables: + * - $title: the (sanitized) title of the node. + * - $content: An array of node items. Use render($content) to print them all, + * or print a subset such as render($content['field_example']). Use + * hide($content['field_example']) to temporarily suppress the printing of a + * given element. + * - $user_picture: The node author's picture from user-picture.tpl.php. + * - $date: Formatted creation date. Preprocess functions can reformat it by + * calling format_date() with the desired parameters on the $created variable. + * - $name: Themed username of node author output from theme_username(). + * - $node_url: Direct url of the current node. + * - $display_submitted: Whether submission information should be displayed. + * - $submitted: Submission information created from $name and $date during + * template_preprocess_node(). + * - $classes: String of classes that can be used to style contextually through + * CSS. It can be manipulated through the variable $classes_array from + * preprocess functions. The default values can be one or more of the + * following: + * - node: The current template type, i.e., "theming hook". + * - node-[type]: The current node type. For example, if the node is a + * "Blog entry" it would result in "node-blog". Note that the machine + * name will often be in a short form of the human readable label. + * - node-teaser: Nodes in teaser form. + * - node-preview: Nodes in preview mode. + * The following are controlled through the node publishing options. + * - node-promoted: Nodes promoted to the front page. + * - node-sticky: Nodes ordered above other non-sticky nodes in teaser + * listings. + * - node-unpublished: Unpublished nodes visible only to administrators. + * - $title_prefix (array): An array containing additional output populated by + * modules, intended to be displayed in front of the main title tag that + * appears in the template. + * - $title_suffix (array): An array containing additional output populated by + * modules, intended to be displayed after the main title tag that appears in + * the template. + * + * Other variables: + * - $node: Full node object. Contains data that may not be safe. + * - $type: Node type, i.e. story, page, blog, etc. + * - $comment_count: Number of comments attached to the node. + * - $uid: User ID of the node author. + * - $created: Time the node was published formatted in Unix timestamp. + * - $classes_array: Array of html class attribute values. It is flattened + * into a string within the variable $classes. + * - $zebra: Outputs either "even" or "odd". Useful for zebra striping in + * teaser listings. + * - $id: Position of the node. Increments each time it's output. + * + * Node status variables: + * - $view_mode: View mode, e.g. 'full', 'teaser'... + * - $teaser: Flag for the teaser state (shortcut for $view_mode == 'teaser'). + * - $page: Flag for the full page state. + * - $promote: Flag for front page promotion state. + * - $sticky: Flags for sticky post setting. + * - $status: Flag for published status. + * - $comment: State of comment settings for the node. + * - $readmore: Flags true if the teaser content of the node cannot hold the + * main body content. + * - $is_front: Flags true when presented in the front page. + * - $logged_in: Flags true when the current user is a logged-in member. + * - $is_admin: Flags true when the current user is an administrator. + * + * Field variables: for each field instance attached to the node a corresponding + * variable is defined, e.g. $node->body becomes $body. When needing to access + * a field's raw values, developers/themers are strongly encouraged to use these + * variables. Otherwise they will have to explicitly specify the desired field + * language, e.g. $node->body['en'], thus overriding any language negotiation + * rule that was previously applied. + * + * @see template_preprocess() + * @see template_preprocess_node() + * @see template_process() + */ +?> +<div id="node-<?php print $node->nid; ?>" class="<?php print $classes; ?> clearfix"<?php print $attributes; ?>> + + <?php print render($title_prefix); ?> + <?php if (!$page): ?> + <h2<?php print $title_attributes; ?>><a href="<?php print $node_url; ?>"><?php print $title; ?></a></h2> + <?php endif; ?> + <?php print render($title_suffix); ?> + + <div class="content"<?php print $content_attributes; ?>> + <div id="unl_digitalsignage_background"></div> + <?php + // We hide the comments and links now so that we can render them later. + hide($content['comments']); + hide($content['links']); + print render($content); + ?> + </div> + +</div> diff --git a/sites/all/themes/unl_digitalsignage/page.tpl.php b/sites/all/themes/unl_digitalsignage/page.tpl.php new file mode 100644 index 0000000000000000000000000000000000000000..7ba63da1de29aa5291f76c30c2c6605ee5242340 --- /dev/null +++ b/sites/all/themes/unl_digitalsignage/page.tpl.php @@ -0,0 +1,97 @@ +<?php + +/** + * @file + * unl_digitalsignage theme implementation to display a single Drupal page. + * + * Available variables: + * + * General utility variables: + * - $base_path: The base URL path of the Drupal installation. At the very + * least, this will always default to /. + * - $directory: The directory the template is located in, e.g. modules/system + * or themes/bartik. + * - $is_front: TRUE if the current page is the front page. + * - $logged_in: TRUE if the user is registered and signed in. + * - $is_admin: TRUE if the user has permission to access administration pages. + * + * Site identity: + * - $front_page: The URL of the front page. Use this instead of $base_path, + * when linking to the front page. This includes the language domain or + * prefix. + * - $logo: The path to the logo image, as defined in theme configuration. + * - $site_name: The name of the site, empty when display has been disabled + * in theme settings. + * - $site_slogan: The slogan of the site, empty when display has been disabled + * in theme settings. + * + * Navigation: + * - $main_menu (array): An array containing the Main menu links for the + * site, if they have been configured. + * - $secondary_menu (array): An array containing the Secondary menu links for + * the site, if they have been configured. + * - $breadcrumb: The breadcrumb trail for the current page. + * + * Page content (in order of occurrence in the default page.tpl.php): + * - $title_prefix (array): An array containing additional output populated by + * modules, intended to be displayed in front of the main title tag that + * appears in the template. + * - $title: The page title, for use in the actual HTML content. + * - $title_suffix (array): An array containing additional output populated by + * modules, intended to be displayed after the main title tag that appears in + * the template. + * - $messages: HTML for status and error messages. Should be displayed + * prominently. + * - $tabs (array): Tabs linking to any sub-pages beneath the current page + * (e.g., the view and edit tabs when displaying a node). + * - $action_links (array): Actions local to the page, such as 'Add menu' on the + * menu administration interface. + * - $feed_icons: A string of all feed icons for the current page. + * - $node: The node object, if there is an automatically-loaded node + * associated with the page, and the node ID is the second argument + * in the page's path (e.g. node/12345 and node/12345/revisions, but not + * comment/reply/12345). + * + * Regions: + * - $page['help']: Dynamic help text, mostly for admin pages. + * - $page['highlighted']: Items for the highlighted content region. + * - $page['content']: The main content of the current page. + * - $page['sidebar_first']: Items for the first sidebar. + * - $page['sidebar_second']: Items for the second sidebar. + * - $page['header']: Items for the header region. + * - $page['footer']: Items for the footer region. + * + * @see template_preprocess() + * @see template_preprocess_page() + * @see template_process() + */ +?> + <div id="page-wrapper"><div id="page"> + + + <div id="main-wrapper"><div id="main" class="clearfix"> + + <div id="content" class="column"><div class="section"> + <?php if ($page['highlighted']): ?><div id="highlighted"><?php print render($page['highlighted']); ?></div><?php endif; ?> + <a id="main-content"></a> + <?php print render($title_prefix); ?> + <?php if ($title): ?><h1 class="title" id="page-title"><?php print $title; ?></h1><?php endif; ?> + <?php print render($title_suffix); ?> + + + + <?php print render($page['content']); ?> + + + + </div></div> <!-- /.section, /#content --> + + + + </div></div> <!-- /#main, /#main-wrapper --> + + <div id="footer"><div class="section"> + <?php print render($page['footer']); ?> + </div></div> <!-- /.section, /#footer --> + + </div></div> <!-- /#page, /#page-wrapper --> \ No newline at end of file diff --git a/sites/all/themes/unl_digitalsignage/proxy.php b/sites/all/themes/unl_digitalsignage/proxy.php new file mode 100644 index 0000000000000000000000000000000000000000..6cad4c4fc21babf5382e7473e6df0357d3a2e8fc --- /dev/null +++ b/sites/all/themes/unl_digitalsignage/proxy.php @@ -0,0 +1,34 @@ +<?php +/** + * Twitter api proxy + */ + +/** + * Determines if a network in the form of 192.168.17.1/16 or + * 127.0.0.1/255.255.255.255 or 10.0.0.1 matches a given ip + * @param $network The network and mask + * @param $ip The ip to check + * @return bool true or false + */ +function net_match($network, $ip) { + $ip_arr = explode('/', $network); + $network_long = ip2long($ip_arr[0]); + $x = ip2long($ip_arr[1]); + $mask = long2ip($x) == $ip_arr[1] ? $x : 0xffffffff << (32 - $ip_arr[1]); + $ip_long = ip2long($ip); + return ($ip_long & $mask) == ($network_long & $mask); +} + +if (isset($_SERVER['REMOTE_ADDR'])) { + $validIPs = array('129.93.0.0/16','65.123.32.0/19','64.39.240.0/20','216.128.208.0/20'); + foreach ($validIPs as $range) { + if (net_match($range, $_SERVER['REMOTE_ADDR'])) { + $result = file_get_contents($_GET['u']); + header('Content-Type:·application/json;·charset=utf-8'); + echo $result; + exit(); + } + } +} + +exit(); diff --git a/sites/all/themes/unl_digitalsignage/screenshot.png b/sites/all/themes/unl_digitalsignage/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..f6a688b9c49f60486edc1225e1e2120b752749df Binary files /dev/null and b/sites/all/themes/unl_digitalsignage/screenshot.png differ diff --git a/sites/all/themes/unl_digitalsignage/scripts/jquery.ThreeDots.js b/sites/all/themes/unl_digitalsignage/scripts/jquery.ThreeDots.js new file mode 100644 index 0000000000000000000000000000000000000000..0e0854a382b3605ef37606bf692e8fd4687b153c --- /dev/null +++ b/sites/all/themes/unl_digitalsignage/scripts/jquery.ThreeDots.js @@ -0,0 +1,649 @@ +/****************************************************************************************************** + + jQuery.ThreeDots + + Author Jeremy Horn + Version 1.0.10 (Developed in Aptana Studio 1.5.1) + Date: 1/25/2010 + + Copyright (c) 2010 Jeremy Horn- jeremydhorn(at)gmail(dot)c0m | http://tpgblog.com + Dual licensed under MIT and GPL. + + For more detailed documentation, including the latest updates and links to more usage and + examples, go to: + + http://tpgblog.com/ThreeDots/ + + KNOWN BUGS + None + + DESCRIPTION + + Sometimes the text ... + ... is too long ... + ... won't fit within the number of rows you have available. + + Sometimes all you need is ... ThreeDots! + + ThreeDots is a customizable jQuery plugin for the smart truncation of text. It shortens + provided text to fit specified dimensions and appends the desired ellipsis style + if/when truncation occurs. + + For example --- + + This: + There was once a brown fox + that liked to eat chocolate + pudding. + + When restricted to 2 lines by ThreeDots, can become: + There was once a brown fox + that liked to eat ... + + Or: + There was once a brown fox + that liked to (click for more) + + ... and most any other permutation you desire. + + + BY DEFAULT + The three dots ellipsis ("...") is used, as shown in the prior example, and limits + text to a maximum of 2 lines. These and many other characteristics are fully customizable, + and fully itemized and explained below. + + + IMPLEMENTATION + + HTML: <div class='text_here'><span class='ellipsis_text'>TEXT</span></div> + JS: $('.text_here').ThreeDots(); // USE DEFAULTS + $('.text_here2').ThreeDots({ { max_rows:3 }); + + + COMPATIBILITY + + Tested in FF3.5, IE7, Chrome + With jQuery 1.3.x, 1.4 + + METHODS + + ThreeDots() + + When intialized the ThreeDots plugin creates and assigns the full set of provided text + to each container element as a publically accessible attribute, 'threedots'. Method + implementation supports chaining and returns jQuery object. + + Note that to implement, the text that you wish to ellipsize must be wrapped in a span + assigned either the default class 'ellipsis_text' or other custom class of your + preference -- customizable via the options/settings. + + If the text becomes truncated to fit within the constrained space defined by the + container element that holds the 'ellipsis_text' span then an additional span is + appended within the container object, and after the 'ellipsis_text' span. + + Note, that the span class of 'threedots_ellipsis' can also be customized via the + options/settings and have it's own CSS/jQuery styles/actions/etc. applied to it as + desired. + + If any of the specified settings are invalid or the 'ellipsis_text' span is missing + nothing will happen. + + IMPORTANT: The horizontal constrains placed upon each row are controled by the + container object. The container object is the object specified in the + primary selector. + + e.g. $('container_object').ThreeDots(); + + So, remember to set container_object's WIDTH. + + ThreeDots.update() + Refreshes the contents of the text within the target object inline with the + options provided. Note, that the current implementation of options/settings + are destructive. This means that whenever OPTIONS are specified they are + merged with the DEFAULT options and applied to the current object(s), and + destroy/override any previously specified options/settings. + + example: + var obj = $('.text_here').ThreeDots(); // uses DEFAULT: max_rows = 2 + obj.update({max_rows:3}); // update the text with max_rows = 3 + + CUSTOMIZATION + + ThreeDots(OPTIONS) + e.g. $('.text_here').ThreeDots({ max_rows: 4 }); + + + valid_delimiters: character array of special characters upon which the text string may be broken up; + defines what characters can be used to express the bounds of a word + + all elements in this array must be 1 character in length; any delimiter less than + or greater than 1 character will be ignored + + + ellipsis_string: defines what to display at the tail end of the text provided if the text becomes + truncated to fit within the space defined by the container object + + + max_rows: specifies the upper limit for the number of rows that the object's text can use + + + text_span_class: by default ThreeDots will look within the specified object(s) for a span + of the class 'ellipsis_text' + + + e_span_class: if an ellipsis_string is displayed at the tail end of the selected object's + text due to truncation of that text then it will be displayed wrapped within + a span associated with the class defined by e_span_class and immediately + following the text_span_class' span + + + whole_word: when fitting the provided text to the max_rows within the container object + this boolean setting defines whether or not the + + if true + THEN don't truncate any words; ellipsis can ONLY be placed after + the last whole word that fits within the provided space, OR + + if false + THEN maximuze the text within the provided space, allowing the + PARTIAL display of words before the ellipsis + + + allow_dangle: a dangling ellipsis is an ellipsis that typically occurs due to words that + are longer than a single row of text, resulting, upon text truncation in + the ellipsis being displayed on a row all by itself + + if allow_dangle is set to false, whole_words is overridden ONLY in the + circumstances where a dangling ellipsis occurs and the displayed text + is adjusted to minimize the occurence of such dangling + + + alt_text_e: alt_text_e is a shortcut to enabling the user of the product that + made use of ThreeDots to see the full text, prior to truncation + + if the value is set to true, then the ellipsis span's title property + is set to the full, original text (pre-truncation) + + + alt_text_t: alt_text_t is a shortcut to enabling the user of the product that + made use of ThreeDots to see the full text, prior to truncation + + if the value is set to true AND the ellipsis is displayed, then the + text span's title property is set to the full, original text + (pre-truncation) + + + MORE + + For latest updates and links to more usage and examples, go to: + http://tpgblog.com/ThreeDots/ + + FUTURE NOTE + + Do not write any code dependent on the c_settings variable. If you don't know what this is + cool -- you don't need to. ;-) c_settings WILL BE DEPRECATED. + + Further optimizations in progress... + +******************************************************************************************************/ + + +(function($) { + + /********************************************************************************** + + METHOD + ThreeDots {PUBLIC} + + DESCRIPTION + ThreeDots method constructor + + allows for the customization of ellipsis, delimiters, etc., and smart + truncation of provided objects' text + + e.g. $(something).ThreeDots(); + + **********************************************************************************/ + + $.fn.ThreeDots = function(options) { + var return_value = this; + + // check for new & valid options + if ((typeof options == 'object') || (options == undefined)) { + $.fn.ThreeDots.the_selected = this; + + var return_value = $.fn.ThreeDots.update(options); + + } + + return return_value; + }; + + + /********************************************************************************** + + METHOD + ThreeDots.update {PUBLIC} + + DESCRIPTION + applies the core logic of ThreeDots + + allows for the customization of ellipsis, delimiters, etc., and smart + truncation of provided objects' text + + updates the objects' visible text to fit within its container(s) + + TODO + instead of having all options/settings calls be constructive have + settings associated w/ object returned also accessible from HERE + [STATIC settings, associated w/ the initial call] + + **********************************************************************************/ + + $.fn.ThreeDots.update = function(options) { + // initialize local variables + var curr_this, last_word = null; + var lineh, paddingt, paddingb, innerh, temp_height; + var curr_text_span, lws; /* last word structure */ + var last_text, three_dots_value, last_del; + + // check for new & valid options + if ((typeof options == 'object') || (options == undefined)) { + + // then update the settings + // CURRENTLY, settings are not CONSTRUCTIVE, but merged with the DEFAULTS every time + $.fn.ThreeDots.c_settings = $.extend({}, $.fn.ThreeDots.settings, options); + var max_rows = $.fn.ThreeDots.c_settings.max_rows; + if (max_rows < 1) { + return $.fn.ThreeDots.the_selected; + } + + // make sure at least 1 valid delimiter + var valid_delimiter_exists = false; + jQuery.each($.fn.ThreeDots.c_settings.valid_delimiters, function(i, curr_del) { + if (((new String(curr_del)).length == 1)) { + valid_delimiter_exists = true; + } + }); + if (valid_delimiter_exists == false) { + return $.fn.ThreeDots.the_selected; + } + + // process all provided objects + $.fn.ThreeDots.the_selected.each(function() { + + // element-specific code here + curr_this = $(this); + + // obtain the text span + if ($(curr_this).children('.'+$.fn.ThreeDots.c_settings.text_span_class).length == 0) { + // if span doesnt exist, then go to next + return true; + } + curr_text_span = $(curr_this).children('.'+$.fn.ThreeDots.c_settings.text_span_class).get(0); + + // pre-calc fixed components of num_rows + var nr_fixed = num_rows(curr_this, true); + + // remember where it all began so that we can see if we ended up exactly where we started + var init_text_span = $(curr_text_span).text(); + + // preprocessor + the_bisector(curr_this, curr_text_span, nr_fixed); + var init_post_b = $(curr_text_span).text(); + + // if the object has been initialized, then user must be calling UPDATE + // THEREFORE refresh the text area before re-operating + if ((three_dots_value = $(curr_this).attr('threedots')) != undefined) { + $(curr_text_span).text(three_dots_value); + $(curr_this).children('.'+$.fn.ThreeDots.c_settings.e_span_class).remove(); + } + + last_text = $(curr_text_span).text(); + if (last_text.length <= 0) { + last_text = ''; + } + $(curr_this).attr('threedots', init_text_span); + + if (num_rows(curr_this, nr_fixed) > max_rows) { + // append the ellipsis span & remember the original text + curr_ellipsis = $(curr_this).append('<span style="white-space:nowrap" class="' + + $.fn.ThreeDots.c_settings.e_span_class + '">' + + $.fn.ThreeDots.c_settings.ellipsis_string + + '</span>'); + + // remove 1 word at a time UNTIL max_rows + while (num_rows(curr_this, nr_fixed) > max_rows) { + + lws = the_last_word($(curr_text_span).text());// HERE + $(curr_text_span).text(lws.updated_string); + last_word = lws.word; + last_del = lws.del; + + if (last_del == null) { + break; + } + } // while (num_rows(curr_this, nr_fixed) > max_rows) + + // check for super long words + if (last_word != null) { + var is_dangling = dangling_ellipsis(curr_this, nr_fixed); + + if ((num_rows(curr_this, nr_fixed) <= max_rows - 1) + || (is_dangling) + || (!$.fn.ThreeDots.c_settings.whole_word)) { + + last_text = $(curr_text_span).text(); + if (lws.del != null) { + $(curr_text_span).text(last_text + last_del); + } + + if (num_rows(curr_this, nr_fixed) > max_rows) { + // undo what i just did and stop + $(curr_text_span).text(last_text); + } else { + // keep going + $(curr_text_span).text($(curr_text_span).text() + last_word); + + // break up the last word IFF (1) word is longer than a line, OR (2) whole_word == false + if ((num_rows(curr_this, nr_fixed) > max_rows + 1) + || (!$.fn.ThreeDots.c_settings.whole_word) + || (init_post_b == last_word) + || is_dangling) { + // remove 1 char at a time until it all fits + while ((num_rows(curr_this, nr_fixed) > max_rows)) { + if ($(curr_text_span).text().length > 0) { + $(curr_text_span).text( + $(curr_text_span).text().substr(0, $(curr_text_span).text().length - 1) + ); + } else { + /* + there is no hope for you; you are crazy; + either pick a shorter ellipsis_string OR + use a wider object --- geeze! + */ + break; + } + } + } + } + } + } + } + + // if nothing has changed, remove the ellipsis + if (init_text_span == $($(curr_this).children('.' + $.fn.ThreeDots.c_settings.text_span_class).get(0)).text()) { + $(curr_this).children('.' + $.fn.ThreeDots.c_settings.e_span_class).remove(); + } else { + // only add any title text if the ellipsis is visible + if (($(curr_this).children('.' + $.fn.ThreeDots.c_settings.e_span_class)).length > 0) { + if ($.fn.ThreeDots.c_settings.alt_text_t) { + $(curr_this).children('.' + $.fn.ThreeDots.c_settings.text_span_class).attr('title', init_text_span); + } + + if ($.fn.ThreeDots.c_settings.alt_text_e) { + $(curr_this).children('.' + $.fn.ThreeDots.c_settings.e_span_class).attr('title', init_text_span); + } + + } + } + }); // $.fn.ThreeDots.the_selected.each(function() + } + + return $.fn.ThreeDots.the_selected; + }; + + + /********************************************************************************** + + METHOD + ThreeDots.settings {PUBLIC} + + DESCRIPTION + data structure containing the max_rows, ellipsis string, and other + behavioral settings + + can be directly accessed by '$.fn.ThreeDots.settings = ...... ;' + + **********************************************************************************/ + + $.fn.ThreeDots.settings = { + valid_delimiters: [' ', ',', '.'], // what defines the bounds of a word to you? + ellipsis_string: '...', + max_rows: 2, + text_span_class: 'ellipsis_text', + e_span_class: 'threedots_ellipsis', + whole_word: true, + allow_dangle: false, + alt_text_e: false, // if true, mouse over of ellipsis displays the full text + alt_text_t: false // if true & if ellipsis displayed, mouse over of text displays the full text + }; + + + /********************************************************************************** + + METHOD + dangling_ellipsis {private} + + DESCRIPTION + determines whether or not the currently calculated ellipsized text + is displaying a dangling ellipsis (= an ellipsis on a line by itself) + + returns true if ellipsis is dangling, otherwise false + + **********************************************************************************/ + + function dangling_ellipsis(obj, nr_fixed){ + if ($.fn.ThreeDots.c_settings.allow_dangle == true) { + return false; // why do when no doing need be done? + } + + // initialize variables + var ellipsis_obj = $(obj).children('.'+$.fn.ThreeDots.c_settings.e_span_class).get(0); + var remember_display = $(ellipsis_obj).css('display'); + var num_rows_before = num_rows(obj, nr_fixed); + + // temporarily hide ellipsis + $(ellipsis_obj).css('display','none'); + var num_rows_after = num_rows(obj, nr_fixed); + + // restore ellipsis + $(ellipsis_obj).css('display',remember_display); + + if (num_rows_before > num_rows_after) { + return true; // ASSUMPTION: removing the ellipsis changed the height + // THEREFORE the ellipsis was on a row all by its lonesome + } else { + return false; // nothing dangling here + } + } + + + /********************************************************************************** + + METHOD + num_rows {private} + + DESCRIPTION + returns the number of rows/lines that the current object's text covers if + cstate is an object + + this function can be initially called to pre-calculate values that will + stay fixed throughout the truncation process for the current object so + that the values do not have to be called every time; to do this the + num_rows function is called with a boolean value within the cstate + + when boolean cstate, an object is returned containing padding and line + height information that is then passed in as the cstate object on + subsequent calls to the function + + **********************************************************************************/ + + function num_rows(obj, cstate){ + var the_type = typeof cstate; + + if ( (the_type == 'object') + || (the_type == undefined) ) { + + // do the math & return + return $(obj).height() / cstate.lh; + + } else if (the_type == 'boolean') { + var lineheight = lineheight_px($(obj)); + + return { + lh: lineheight + }; + } + } + + + /********************************************************************************** + + METHOD + the_last_word {private} + + DESCRIPTION + return a data structure containing... + + [word] the last word within the specified text defined + by the specified valid_delimiters, + [del] the delimiter occurring directly before the + word, and + [updated_string] the updated text minus the last word + + [del] is null if the last word is the first and/or only word in the text + string + + **********************************************************************************/ + + function the_last_word(str){ + var temp_word_index; + var v_del = $.fn.ThreeDots.c_settings.valid_delimiters; + + // trim the string + str = jQuery.trim(str); + + // initialize variables + var lastest_word_idx = -1; + var lastest_word = null; + var lastest_del = null; + + // for all given delimiters, determine which delimiter results in the smallest word cut + jQuery.each(v_del, function(i, curr_del){ + if (((new String(curr_del)).length != 1) + || (curr_del == null)) { // implemented to handle IE NULL condition; if only typeof could say CHAR :( + return false; // INVALID delimiter; must be 1 character in length + } + + var tmp_word_index = str.lastIndexOf(curr_del); + if (tmp_word_index != -1) { + if (tmp_word_index > lastest_word_idx) { + lastest_word_idx = tmp_word_index; + lastest_word = str.substring(lastest_word_idx+1); + lastest_del = curr_del; + } + } + }); + + // return data structure of word reduced string and the last word + if (lastest_word_idx > 0) { + return { + updated_string: jQuery.trim(str.substring(0, lastest_word_idx/*-1*/)), + word: lastest_word, + del: lastest_del + }; + } else { // the lastest word + return { + updated_string: '', + word: jQuery.trim(str), + del: null + }; + } + } + + + /********************************************************************************** + + METHOD + lineheight_px {private} + + DESCRIPTION + returns the line height of a row of the provided text (within the text + span) in pixels + + **********************************************************************************/ + + function lineheight_px(obj) { + // shhhh... show + $(obj).append("<div id='temp_ellipsis_div' style='position:absolute; visibility:hidden'>H</div>"); + // measure + var temp_height = $('#temp_ellipsis_div').height(); + // cut + $('#temp_ellipsis_div').remove(); + + return temp_height; + } + + /********************************************************************************** + + METHOD + the_bisector (private) + + DESCRIPTION + updates the target objects current text to shortest overflowing string + length (if overflowing is occurring) by adding/removing halves (like + binary search) + + because... + taking some bigger steps at the beginning should save us some real + time in the end + + **********************************************************************************/ + + function the_bisector(obj, curr_text_span, nr_fixed){ + var init_text = $(curr_text_span).text(); + var curr_text = init_text; + var max_rows = $.fn.ThreeDots.c_settings.max_rows; + var front_half, back_half, front_of_back_half, middle, back_middle; + var start_index; + + if (num_rows(obj, nr_fixed) <= max_rows) { + // do nothing + return; + } else { + // zero in on the solution + start_index = 0; + curr_length = curr_text.length; + + curr_middle = Math.floor((curr_length - start_index) / 2); + front_half = init_text.substring(start_index, start_index+curr_middle); + back_half = init_text.substring(start_index + curr_middle); + + while (curr_middle != 0) { + $(curr_text_span).text(front_half); + + if (num_rows(obj, nr_fixed) <= (max_rows)) { + // text = text + front half of back-half + back_middle = Math.floor(back_half.length/2); + front_of_back_half = back_half.substring(0, back_middle); + + start_index = front_half.length; + curr_text = front_half+front_of_back_half; + curr_length = curr_text.length; + + $(curr_text_span).text(curr_text); + } else { + // text = front half (which it already is) + curr_text = front_half; + curr_length = curr_text.length; + } + + curr_middle = Math.floor((curr_length - start_index) / 2); + front_half = init_text.substring(0, start_index+curr_middle); + back_half = init_text.substring(start_index + curr_middle); + } + } + } + +})(jQuery); \ No newline at end of file diff --git a/sites/all/themes/unl_digitalsignage/scripts/jquery.ThreeDots.min.js b/sites/all/themes/unl_digitalsignage/scripts/jquery.ThreeDots.min.js new file mode 100644 index 0000000000000000000000000000000000000000..bbe56ef3b4ed9ed7eabe53885a250284b532c056 --- /dev/null +++ b/sites/all/themes/unl_digitalsignage/scripts/jquery.ThreeDots.min.js @@ -0,0 +1,11 @@ +/* +jQuery.ThreeDots.min + +Author Jeremy Horn +Version 1.0.10 +Date: 1/25/2009 +More: http://tpgblog.com/ThreeDots/ +compiled by http://yui.2clics.net/ +*/ + +(function(e){e.fn.ThreeDots=function(h){var g=this;if((typeof h=="object")||(h==undefined)){e.fn.ThreeDots.the_selected=this;var g=e.fn.ThreeDots.update(h)}return g};e.fn.ThreeDots.update=function(u){var k,t=null;var m,j,s,q,o;var l,i;var r,h,n;if((typeof u=="object")||(u==undefined)){e.fn.ThreeDots.c_settings=e.extend({},e.fn.ThreeDots.settings,u);var p=e.fn.ThreeDots.c_settings.max_rows;if(p<1){return e.fn.ThreeDots.the_selected}var g=false;jQuery.each(e.fn.ThreeDots.c_settings.valid_delimiters,function(v,w){if(((new String(w)).length==1)){g=true}});if(g==false){return e.fn.ThreeDots.the_selected}e.fn.ThreeDots.the_selected.each(function(){k=e(this);if(e(k).children("."+e.fn.ThreeDots.c_settings.text_span_class).length==0){return true}l=e(k).children("."+e.fn.ThreeDots.c_settings.text_span_class).get(0);var y=a(k,true);var x=e(l).text();d(k,l,y);var v=e(l).text();if((h=e(k).attr("threedots"))!=undefined){e(l).text(h);e(k).children("."+e.fn.ThreeDots.c_settings.e_span_class).remove()}r=e(l).text();if(r.length<=0){r=""}e(k).attr("threedots",x);if(a(k,y)>p){curr_ellipsis=e(k).append('<span style="white-space:nowrap" class="'+e.fn.ThreeDots.c_settings.e_span_class+'">'+e.fn.ThreeDots.c_settings.ellipsis_string+"</span>");while(a(k,y)>p){i=b(e(l).text());e(l).text(i.updated_string);t=i.word;n=i.del;if(n==null){break}}if(t!=null){var w=c(k,y);if((a(k,y)<=p-1)||(w)||(!e.fn.ThreeDots.c_settings.whole_word)){r=e(l).text();if(i.del!=null){e(l).text(r+n)}if(a(k,y)>p){e(l).text(r)}else{e(l).text(e(l).text()+t);if((a(k,y)>p+1)||(!e.fn.ThreeDots.c_settings.whole_word)||(v==t)||w){while((a(k,y)>p)){if(e(l).text().length>0){e(l).text(e(l).text().substr(0,e(l).text().length-1))}else{break}}}}}}}if(x==e(e(k).children("."+e.fn.ThreeDots.c_settings.text_span_class).get(0)).text()){e(k).children("."+e.fn.ThreeDots.c_settings.e_span_class).remove()}else{if((e(k).children("."+e.fn.ThreeDots.c_settings.e_span_class)).length>0){if(e.fn.ThreeDots.c_settings.alt_text_t){e(k).children("."+e.fn.ThreeDots.c_settings.text_span_class).attr("title",x)}if(e.fn.ThreeDots.c_settings.alt_text_e){e(k).children("."+e.fn.ThreeDots.c_settings.e_span_class).attr("title",x)}}}})}return e.fn.ThreeDots.the_selected};e.fn.ThreeDots.settings={valid_delimiters:[" ",",","."],ellipsis_string:"...",max_rows:2,text_span_class:"ellipsis_text",e_span_class:"threedots_ellipsis",whole_word:true,allow_dangle:false,alt_text_e:false,alt_text_t:false};function c(k,h){if(e.fn.ThreeDots.c_settings.allow_dangle==true){return false}var l=e(k).children("."+e.fn.ThreeDots.c_settings.e_span_class).get(0);var g=e(l).css("display");var i=a(k,h);e(l).css("display","none");var j=a(k,h);e(l).css("display",g);if(i>j){return true}else{return false}}function a(i,j){var g=typeof j;if((g=="object")||(g==undefined)){return e(i).height()/j.lh}else{if(g=="boolean"){var h=f(e(i));return{lh:h}}}}function b(k){var j;var i=e.fn.ThreeDots.c_settings.valid_delimiters;k=jQuery.trim(k);var g=-1;var h=null;var l=null;jQuery.each(i,function(m,o){if(((new String(o)).length!=1)||(o==null)){return false}var n=k.lastIndexOf(o);if(n!=-1){if(n>g){g=n;h=k.substring(g+1);l=o}}});if(g>0){return{updated_string:jQuery.trim(k.substring(0,g)),word:h,del:l}}else{return{updated_string:"",word:jQuery.trim(k),del:null}}}function f(h){e(h).append("<div id='temp_ellipsis_div' style='position:absolute; visibility:hidden'>H</div>");var g=e("#temp_ellipsis_div").height();e("#temp_ellipsis_div").remove();return g}function d(k,l,m){var q=e(l).text();var i=q;var o=e.fn.ThreeDots.c_settings.max_rows;var h,g,n,r,j;var p;if(a(k,m)<=o){return}else{p=0;curr_length=i.length;curr_middle=Math.floor((curr_length-p)/2);h=q.substring(p,p+curr_middle);g=q.substring(p+curr_middle);while(curr_middle!=0){e(l).text(h);if(a(k,m)<=(o)){j=Math.floor(g.length/2);n=g.substring(0,j);p=h.length;i=h+n;curr_length=i.length;e(l).text(i)}else{i=h;curr_length=i.length}curr_middle=Math.floor((curr_length-p)/2);h=q.substring(0,p+curr_middle);g=q.substring(p+curr_middle)}}}})(jQuery); \ No newline at end of file diff --git a/sites/all/themes/unl_digitalsignage/scripts/jquery.animate-colors.js b/sites/all/themes/unl_digitalsignage/scripts/jquery.animate-colors.js new file mode 100644 index 0000000000000000000000000000000000000000..edffb4276ccac2a3a8ddaf86bb0113d19e863dfd --- /dev/null +++ b/sites/all/themes/unl_digitalsignage/scripts/jquery.animate-colors.js @@ -0,0 +1,103 @@ +/**! + * @preserve Color animation jQuery-plugin + * http://www.bitstorm.org/jquery/color-animation/ + * Copyright 2011 Edwin Martin <edwin@bitstorm.org> + * Released under the MIT and GPL licenses. + */ + +(function($) { + /** + * Check whether the browser supports RGBA color mode. + * + * Author Mehdi Kabab <http://pioupioum.fr> + * @return {boolean} True if the browser support RGBA. False otherwise. + */ + function isRGBACapable() { + var $script = $('script:first'), + color = $script.css('color'), + result = false; + if (/^rgba/.test(color)) { + result = true; + } else { + try { + result = ( color != $script.css('color', 'rgba(0, 0, 0, 0.5)').css('color') ); + $script.css('color', color); + } catch (e) { + } + } + + return result; + } + + $.extend(true, $, { + support: { + 'rgba': isRGBACapable() + } + }); + + var properties = ['color', 'backgroundColor', 'borderBottomColor', 'borderLeftColor', 'borderRightColor', 'borderTopColor', 'outlineColor']; + $.each(properties, function(i, property) { + $.fx.step[property] = function(fx) { + if (!fx.init) { + fx.begin = parseColor($(fx.elem).css(property)); + fx.end = parseColor(fx.end); + fx.init = true; + } + + fx.elem.style[property] = calculateColor(fx.begin, fx.end, fx.pos); + } + }); + + // borderColor doesn't fit in standard fx.step above. + $.fx.step.borderColor = function(fx) { + if (!fx.init) { + fx.end = parseColor(fx.end); + } + var borders = properties.slice(2, 6); // All four border properties + $.each(borders, function(i, property) { + if (!fx.init) { + fx[property] = {begin: parseColor($(fx.elem).css(property))}; + } + + fx.elem.style[property] = calculateColor(fx[property].begin, fx.end, fx.pos); + }); + fx.init = true; + } + + // Calculate an in-between color. Returns "#aabbcc"-like string. + function calculateColor(begin, end, pos) { + var color = 'rgb' + ($.support['rgba'] ? 'a' : '') + '(' + + parseInt((begin[0] + pos * (end[0] - begin[0])), 10) + ',' + + parseInt((begin[1] + pos * (end[1] - begin[1])), 10) + ',' + + parseInt((begin[2] + pos * (end[2] - begin[2])), 10); + if ($.support['rgba']) { + color += ',' + (begin && end ? parseFloat(begin[3] + pos * (end[3] - begin[3])) : 1); + } + color += ')'; + return color; + } + + // Parse an CSS-syntax color. Outputs an array [r, g, b] + function parseColor(color) { + var match, triplet; + + // Match #aabbcc + if (match = /#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})/.exec(color)) { + triplet = [parseInt(match[1], 16), parseInt(match[2], 16), parseInt(match[3], 16), 1]; + + // Match #abc + } else if (match = /#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])/.exec(color)) { + triplet = [parseInt(match[1], 16) * 17, parseInt(match[2], 16) * 17, parseInt(match[3], 16) * 17, 1]; + + // Match rgb(n, n, n) + } else if (match = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(color)) { + triplet = [parseInt(match[1]), parseInt(match[2]), parseInt(match[3]), 1]; + + } else if (match = /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9\.]*)\s*\)/.exec(color)) { + triplet = [parseInt(match[1], 10), parseInt(match[2], 10), parseInt(match[3], 10),parseFloat(match[4])]; + + // No browser returns rgb(n%, n%, n%), so little reason to support this format. + } + return triplet; + } +})(jQuery); \ No newline at end of file diff --git a/sites/all/themes/unl_digitalsignage/scripts/jquery.animate-textshadow.js b/sites/all/themes/unl_digitalsignage/scripts/jquery.animate-textshadow.js new file mode 100644 index 0000000000000000000000000000000000000000..a67f4d3b56bdb9e1b16c5c927dd051cb361550aa --- /dev/null +++ b/sites/all/themes/unl_digitalsignage/scripts/jquery.animate-textshadow.js @@ -0,0 +1,130 @@ +/*! + * Text shadow animation jQuery-plugin + * http://alexpeattie.com/projects/text-shadow-animation + * Copyright 2011 Alex Peattie <alexpeattie@gmail.com> + * Contributor: Edwin Martin <edwin@bitstorm.org> + * Released under the MIT and GPL licenses. + */ + +jQuery(function($) { + /** + * Check whether the browser supports RGBA color mode. + * + * Author Mehdi Kabab <http://pioupioum.fr> + * @return {boolean} True if the browser support RGBA. False otherwise. + */ + function isRGBACapable() { + var $script = $('script:first'), + color = $script.css('color'), + result = false; + if (/^rgba/.test(color)) { + result = true; + } else { + try { + result = ( color != $script.css('color', 'rgba(0, 0, 0, 0.5)').css('color') ); + $script.css('color', color); + } catch (e) { + } + } + + return result; + } + + $.extend(true, $, { + support: { + 'rgba': isRGBACapable() + } + }); + + /*************************************/ + + // Extend the animate-function + $.fx.step['textShadow'] = function(fx) { + if (!fx.init) { + //We have to pass the font size to the parseShadow method, to allow the use of em units + var fontSize = $(fx.elem).get(0).style['fontSize'] || $(fx.elem).css('fontSize') + var beginShadow = $(fx.elem).get(0).style['textShadow'] || $(fx.elem).css('textShadow') + + //In cases where text-shadow is none, or is not returned by browser (e.g. current versions of Opera) + //then set the beginning and ending shadow to be equal, so no animation + if(beginShadow == ('' || 'none')) { beginShadow = fx.end } + + fx.begin = parseShadow(beginShadow, fontSize); + fx.end = $.extend({}, fx.begin, parseShadow(fx.end, fontSize)); + fx.init = true; + } + fx.elem.style.textShadow = calculateShadow(fx.begin, fx.end, fx.pos); + } + + + // Calculate an in-between shadow. + function calculateShadow(begin, end, pos) { + var parts = []; + + if (typeof end.right != 'undefined') { + parts.push(parseInt(begin.right + pos * (end.right - begin.right)) + 'px ' + + parseInt(begin.bottom + pos * (end.bottom - begin.bottom)) + 'px'); + } + if (typeof end.blur != 'undefined') { + parts.push(parseInt(begin.blur + pos * (end.blur - begin.blur)) + 'px'); + } + + if (typeof end.color != 'undefined') { + var color = 'rgb' + ($.support.rgba ? 'a' : '') + '(' + + parseInt((begin.color[0] + pos * (end.color[0] - begin.color[0]))) + ',' + + parseInt((begin.color[1] + pos * (end.color[1] - begin.color[1]))) + ',' + + parseInt((begin.color[2] + pos * (end.color[2] - begin.color[2]))); + if ($.support.rgba) { + color += ',' + parseFloat(begin.color[3] + pos * (end.color[3] - begin.color[3])); + } + color += ')'; + parts.push(color); + } + var value = parts.join(' '); + return value; + } + + // Parse the shadow value and extract the values. + function parseShadow(shadow, fontSize) { + var match, color, lengths, valpx, parsedShadow = {}; + + // Parse an CSS-syntax color. Outputs an array [r, g, b] + // Match #aabbcc + if (match = /#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})/.exec(shadow)) { + color = [parseInt(match[1], 16), parseInt(match[2], 16), parseInt(match[3], 16), 1]; + + // Match #abc + } else if (match = /#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])/.exec(shadow)) { + color = [parseInt(match[1], 16) * 17, parseInt(match[2], 16) * 17, parseInt(match[3], 16) * 17, 1]; + + // Match rgb(n, n, n) + } else if (match = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(shadow)) { + color = [parseInt(match[1]), parseInt(match[2]), parseInt(match[3]), 1]; + + // Match rgba(n, n, n, n) + } else if (match = /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9\.]*)\s*\)/.exec(shadow)) { + color = [parseInt(match[1]), parseInt(match[2]), parseInt(match[3]),parseFloat(match[4])]; + + // No browser returns rgb(n%, n%, n%), so little reason to support this format. + } + + // Remove the color value from the string for the next round of parsing + lengths = shadow.replace(match[0], ''); + + // Parse offset and blur + if (match = /(-*[0-9.]+(?:px|em|pt)?)\s+(-*[0-9.]+(?:px|em|pt)?)\s+(-*[0-9.]+(?:px|em|pt)?)/.exec(lengths)) { + + // Rough and ready em/pt > px conversion + valpx = match.slice(1).map(function(v) { + var unit = v.match(/em|pt/); + if(unit == "em") return parseFloat(v) * parseInt(fontSize); + if(unit == "pt") return parseInt(v)/72*96; + return parseInt(v); + }); + parsedShadow = {right: valpx[0], bottom: valpx[1], blur: valpx[2]}; + } + + parsedShadow.color = color; + return parsedShadow; + } +}); \ No newline at end of file diff --git a/sites/all/themes/unl_digitalsignage/scripts/jquery.color.js b/sites/all/themes/unl_digitalsignage/scripts/jquery.color.js new file mode 100644 index 0000000000000000000000000000000000000000..634719d37f0955071881acef1f6cc7c177e6e119 --- /dev/null +++ b/sites/all/themes/unl_digitalsignage/scripts/jquery.color.js @@ -0,0 +1,123 @@ +/* + * jQuery Color Animations + * Copyright 2007 John Resig + * Released under the MIT and GPL licenses. + */ + +(function(jQuery){ + + // We override the animation for all of these color styles + jQuery.each(['backgroundColor', 'borderBottomColor', 'borderLeftColor', 'borderRightColor', 'borderTopColor', 'color', 'outlineColor'], function(i,attr){ + jQuery.fx.step[attr] = function(fx){ + if ( fx.state == 0 ) { + fx.start = getColor( fx.elem, attr ); + fx.end = getRGB( fx.end ); + } + + fx.elem.style[attr] = "rgb(" + [ + Math.max(Math.min( parseInt((fx.pos * (fx.end[0] - fx.start[0])) + fx.start[0]), 255), 0), + Math.max(Math.min( parseInt((fx.pos * (fx.end[1] - fx.start[1])) + fx.start[1]), 255), 0), + Math.max(Math.min( parseInt((fx.pos * (fx.end[2] - fx.start[2])) + fx.start[2]), 255), 0) + ].join(",") + ")"; + } + }); + + // Color Conversion functions from highlightFade + // By Blair Mitchelmore + // http://jquery.offput.ca/highlightFade/ + + // Parse strings looking for color tuples [255,255,255] + function getRGB(color) { + var result; + + // Check if we're already dealing with an array of colors + if ( color && color.constructor == Array && color.length == 3 ) + return color; + + // Look for rgb(num,num,num) + if (result = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(color)) + return [parseInt(result[1]), parseInt(result[2]), parseInt(result[3])]; + + // Look for rgb(num%,num%,num%) + if (result = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(color)) + return [parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55]; + + // Look for #a0b1c2 + if (result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(color)) + return [parseInt(result[1],16), parseInt(result[2],16), parseInt(result[3],16)]; + + // Look for #fff + if (result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(color)) + return [parseInt(result[1]+result[1],16), parseInt(result[2]+result[2],16), parseInt(result[3]+result[3],16)]; + + // Otherwise, we're most likely dealing with a named color + return colors[jQuery.trim(color).toLowerCase()]; + } + + function getColor(elem, attr) { + var color; + + do { + color = jQuery.curCSS(elem, attr); + + // Keep going until we find an element that has color, or we hit the body + if ( color != '' && color != 'transparent' || jQuery.nodeName(elem, "body") ) + break; + + attr = "backgroundColor"; + } while ( elem = elem.parentNode ); + + return getRGB(color); + }; + + // Some named colors to work with + // From Interface by Stefan Petre + // http://interface.eyecon.ro/ + + var colors = { + aqua:[0,255,255], + azure:[240,255,255], + beige:[245,245,220], + black:[0,0,0], + blue:[0,0,255], + brown:[165,42,42], + cyan:[0,255,255], + darkblue:[0,0,139], + darkcyan:[0,139,139], + darkgrey:[169,169,169], + darkgreen:[0,100,0], + darkkhaki:[189,183,107], + darkmagenta:[139,0,139], + darkolivegreen:[85,107,47], + darkorange:[255,140,0], + darkorchid:[153,50,204], + darkred:[139,0,0], + darksalmon:[233,150,122], + darkviolet:[148,0,211], + fuchsia:[255,0,255], + gold:[255,215,0], + green:[0,128,0], + indigo:[75,0,130], + khaki:[240,230,140], + lightblue:[173,216,230], + lightcyan:[224,255,255], + lightgreen:[144,238,144], + lightgrey:[211,211,211], + lightpink:[255,182,193], + lightyellow:[255,255,224], + lime:[0,255,0], + magenta:[255,0,255], + maroon:[128,0,0], + navy:[0,0,128], + olive:[128,128,0], + orange:[255,165,0], + pink:[255,192,203], + purple:[128,0,128], + violet:[128,0,128], + red:[255,0,0], + silver:[192,192,192], + white:[255,255,255], + yellow:[255,255,0] + }; + +})(jQuery); diff --git a/sites/all/themes/unl_digitalsignage/scripts/unl_digitalsignage.js b/sites/all/themes/unl_digitalsignage/scripts/unl_digitalsignage.js new file mode 100644 index 0000000000000000000000000000000000000000..5b12b88d450bf04717d8913ce03dd7eeeece4eac --- /dev/null +++ b/sites/all/themes/unl_digitalsignage/scripts/unl_digitalsignage.js @@ -0,0 +1,338 @@ +UNL.digitalSignage = (function() { + var maxItems = { + 'news' : 5, + 'videos' : 10 + }; + + return { + // Populated by template.php + feeds : {}, + + init : function() { + console.log('UNL.digitalSignage.init called'); + + for (feed in UNL.digitalSignage.feeds) { + if (UNL.digitalSignage.feeds.hasOwnProperty(feed)) { + UNL.digitalSignage.setupFeed(feed); + } + } + + UNL.digitalSignage.rotateBeautyShots(); + }, + + setupFeed : function(field) { + console.log('UNL.digitalSignage.setupFeed called for: '+field); + + // How often to grab a new set of results from the feed + var time; + // Call it once to start us off, later we'll setInterval on this + UNL.digitalSignage.updateFeed(field); + + switch(field) { + case 'field_newssources': + time = 600000; + break; + default: + time = 500000; + } + + // Video feed will be updated by calling updateFeed with an ended event on the last video + if (field != 'field_videosources') { + //setInterval(function(){UNL.digitalSignage.updateFeed(field);}, time); + } + return false; + }, + + updateFeed : function(field) { + // Switch out the - with _ if this is a recursive call + field = field.replace('-', '_'); + + console.log('UNL.digitalSignage.updateFeed feed called for: '+field); + console.log('Getting data from: '+UNL.digitalSignage.feeds[field]); + + jQuery.getJSON(UNL.digitalSignage.feeds[field], function(data) { + console.log('Success! Got the '+field+' data: '+data); + // Switch out the _ with the - that is used by css identifiers + field = field.replace('_', '-'); + // Empty out the existing content + if (field !== 'field-twitter') { + jQuery('.field-name-'+field).html(''); + } + + switch(field) { + case 'field-newssources': + var news = []; + jQuery.each(data.query.results.item, function(key, val) { + if (news.length >= maxItems['news']) { + return false; + } + + news.push('<li class="field-item '+(key%2 ? 'odd' : 'even')+'">'+ + '<h3><span>'+val.title+'</span></h3>'+ + '<div class="'+field+'-desc">'+val.description+'</div>'+ + '<div class="'+field+'-link">'+val.link+'</div>'+ + '<div class="'+field+'-qrcode"></div>'+ + '</li>'); + + // Small QR Code + UNL.digitalSignage.addQrCode('.field-name-'+field+' .field-items .field-item:nth-child('+news.length+')', 'background-image', '42', val.link); + // Big QR Code + UNL.digitalSignage.addQrCode('.field-name-'+field+' .field-items .field-item:nth-child('+news.length+') .'+field+'-qrcode', 'img', '120', val.link); + }); + + // Add the list of news stories + jQuery('<ul />', { + 'class' : 'field-items', + 'html' : news.join('') + }).appendTo('.field-name-'+field); + + // Add the div that will display the story that is showing + jQuery('<div />', { + 'class' : 'field-display', + 'html' : '<div class="qrcode"></div><div class="link"></div><div class="desc"></div>' + }).appendTo('.field-name-'+field); + + UNL.digitalSignage.rotateNews(); + break; + case 'field-videosources': + var videos = []; + var videoCounter = 0; + jQuery.each(data.query.results.item, function(key, val) { + videos.push({link:val.link, title:val.title, description:val.description}); + }); + + jQuery('<div/>', { + 'class': 'field-items', + 'html': '<div class="field-item even">'+ + '<div id="unl-digitalsignage-video-wrapper">'+ + '<video autoplay id="unl-digitalsignage-video"></video>'+ + '</div>'+ + '<div class="'+field+'-desc"></div>'+ + '</div>' + }).appendTo('.field-name-'+field); + + var video = document.getElementById('unl-digitalsignage-video'); + + // "Helper" function due to trouble removing a listener that calls a function with parameters i.e. video.addEventListener('ended', callVideoUpdate(i+1), false); + var callVideoUpdate = function() { + videoUpdate(videoCounter+1); + }; + + var videoUpdate = function() { + video.removeEventListener('ended', callVideoUpdate, false); + + video.src = videos[videoCounter].link; + console.log('src for video #'+videoCounter+' loaded'); + + // Add the description + jQuery('.'+field+'-desc').html(videos[videoCounter].description); + // Truncate with ellipsis plugin if too long + jQuery('.field-videosources-desc').wrapInner('<span class="ellipsis_text" />'); + jQuery('.field-videosources-desc').ThreeDots({ max_rows:14 }); + // Add title of video, must be done after ellipsis call to avoid stripping h3 tag + jQuery('.'+field+'-desc').prepend('<h3>'+videos[videoCounter].title+'</h3>'); + + // Set up recursion + if (videos[videoCounter+1] !== undefined && videoCounter < maxItems['videos']) { + console.log('Added "ended" listener that will call callVideoUpdate() i.e. videoUpdate('+videoCounter+'+1)'); + // If there are more videos to show set up listener that will swap out video src + video.addEventListener('ended', callVideoUpdate, false); + } else { + // Otherwise start over by grabbing the video feed again + console.log('Reached the end of the video list, "ended" listener scheduled to call updateFeed() to restart'); + video.addEventListener('ended', function(){UNL.digitalSignage.updateFeed(field);}, false); + } + videoCounter++; + }; + + // Attach the first video to the dom to get the ball rolling + videoUpdate(); + break; + case 'field-twitter': + var tweets = []; + jQuery.each(data, function(key, val) { + tweets.push({ + retweeted_status : { + user : { + name : (val.retweeted_status ? val.retweeted_status.user.name : undefined), + screen_name : (val.retweeted_status ? val.retweeted_status.user.screen_name : undefined), + profile_image_url : (val.retweeted_status ? val.retweeted_status.user.profile_image_url : undefined) + }, + text : (val.retweeted_status ? val.retweeted_status.text : undefined) + }, + text : val.text, + user : { + name : val.user.name, + screen_name : val.user.screen_name, + profile_image_url : val.user.profile_image_url + } + }); + }); + UNL.digitalSignage.rotateTweets(tweets); + break; + default: + } + + }); + return false; + }, + + rotateBeautyShots : function() { + // Store the initial css values of the page title + var pageTitle = []; + pageTitle['padding-left'] = jQuery('#page-title').css('padding-left'); + pageTitle['color'] = jQuery('#page-title').css('color'); + pageTitle['text-shadow'] = jQuery('#page-title').css('text-shadow'); + + // Things to give opacity to when beauty shot is full screen + var opacityElements = '.field-name-field-videosources .field-videosources-desc, .field-name-field-newssources .field-items, .field-name-field-newssources .field-display, .field-name-field-twitter'; + + // Populate this var to make code below easier to read + var fi = '.field-name-field-beautyshots .field-items .field-item'; + + var rotate = function() { + // Get the first image + var current = (jQuery(fi+'.show') ? jQuery(fi+'.show') : jQuery(fi+':first')); + // Get next image, when it reaches the end, rotate it back to the first image + var next = ((current.next().length) ? ((current.next().hasClass('show')) ? jQuery(fi+':first') : current.next()) : jQuery(fi+':first')); + + // Hide the current image + current.removeClass('show').animate({opacity: 0.0}, 3000); + // Show the next image + next.css({opacity: 0.0}).addClass('show'); + + // Decide how to animate fading in the new image and whether to move the background + if (current.width() < '1920' && next.width() >= '1920') { + next.animate({opacity: 1.0}, 3000, function() { + jQuery('#page-title').animate({ + backgroundColor : 'rgba(255, 255, 255, 0.50)', + paddingLeft : '20px', + color : 'rgba(60, 60, 60, 1.0)', + textShadow : '#FFFFFF 0 0 0' + }, 2000); + jQuery('#unl_digitalsignage_background').animate({'left' : '-1920px'}, 2000, function() { + jQuery(opacityElements).css('background-image','none'); + }); + }); + } else if (current.width() >= '1920' && next.width() < '1920') { + jQuery('#page-title').animate({ + backgroundColor : 'rgba(255, 255, 255, 0)', + paddingLeft : pageTitle['padding-left'], + color : pageTitle['color'], + textShadow : pageTitle['text-shadow'] + }, 2000); + jQuery('#unl_digitalsignage_background').animate({'left' : '0px'}, 2000, function() { + next.animate({opacity : 1.0}, 3000); + jQuery(opacityElements).css('background-image','inherit'); + }); + } else { + next.animate({opacity : 1.0}, 3000); + } + }; + + // Set the opacity of all images to 0 + jQuery(fi).css({opacity: 0.0}); + + // Get the first image and display it (gets set to full opacity) + jQuery(fi+':first').css({opacity: 1.0}); + + // Call the rotator function to run the slideshow, (2000 = change to next image after 2 seconds) + rotate(); + setInterval(function(){rotate()}, 20000); + }, + + rotateNews : function() { + var fi = '.field-name-field-newssources .field-items .field-item'; + var rotate = function() { + // Get the first story + var current = (jQuery(fi+'.show') ? jQuery(fi+'.show') : jQuery(fi+':first')); + // Get next story, when it reaches the end, rotate it back to the first story + var next = ((current.next().length) ? ((current.next().hasClass('show')) ? jQuery(fi+':first') : current.next()) : jQuery(fi+':first')); + + // Populate the display area with the content from the current (.show) li + jQuery('.field-name-field-newssources .field-display .desc').html(next.find('.field-newssources-desc').html()); + jQuery('.field-name-field-newssources .field-display .link').html(next.find('.field-newssources-link').html()); + jQuery('.field-name-field-newssources .field-display .qrcode').html(next.find('.field-newssources-qrcode').html()); + + next.addClass('show'); + current.removeClass('show'); + }; + + // Call the rotator function to run the slideshow, (2000 = change to next story after 2 seconds) + rotate(); + setInterval(function(){rotate()}, 10000); + }, + + rotateTweets : function(tweets) { + var fi = '.field-name-field-twitter .field-items .field-item'; + var counter = 0; + var tweet = []; + var rotate = function() { + if (counter > tweets.length-1) { + clearInterval(tweetInterval); + UNL.digitalSignage.updateFeed('field_twitter'); + return false; + } + + if (tweets[counter].retweeted_status && tweets[counter].retweeted_status.user + && tweets[counter].retweeted_status.user.screen_name && tweets[counter].retweeted_status.user.profile_image_url && tweets[counter].retweeted_status.text) { + // Retweet + tweet['screen_name'] = tweets[counter].retweeted_status.user.screen_name; + tweet['name'] = tweets[counter].retweeted_status.user.name; + tweet['profile_image_url'] = tweets[counter].retweeted_status.user.profile_image_url; + tweet['text'] = tweets[counter].retweeted_status.text; + tweet['retweeted_by'] = tweets[counter].user.screen_name; + } else { + // Regular tweet + tweet['screen_name'] = tweets[counter].user.screen_name; + tweet['name'] = tweets[counter].user.name; + tweet['profile_image_url'] = tweets[counter].user.profile_image_url; + tweet['text'] = tweets[counter].text; + tweet['retweeted_by'] = undefined; + } + + jQuery(fi).fadeOut('slow', function() { + jQuery(fi).html('<div class="tweet">'+ + '<img src="'+tweet['profile_image_url']+'" alt="Twitter Profile Icon" />'+ + '<div class="tweet-user"><span class="tweet-user-name">@'+tweet['screen_name']+'</span><span class="tweet-full-name">'+tweet['name']+'</span></div>'+ + '<div class="tweet-text">'+tweet['text']+'</div>'+ + '</div>'); + + if (tweet['retweeted_by']) { + jQuery(fi).append('<div class="retweet"><span>retweeted by</span> @'+tweet['retweeted_by']+'</div>'); + } + jQuery(fi).fadeIn(); + counter++; + }); + + }; + + // Call the rotator function (2000 = change to next tweet after 2 seconds) + rotate(); + var tweetInterval = setInterval(function(){rotate()}, 15000); + }, + + addQrCode : function(element, type, size, url) { + jQuery.post('http://go.unl.edu/api_create.php', { 'theURL' : url }, function(data) { + console.log("GoURL generated URL is: "+data); + var qrlink = 'http://chart.apis.google.com/chart?cht=qr&chs='+size+'&chld=L|0&chl='+data; + if (type == 'background-image') { + jQuery(element+' h3').css('background-image','url("'+qrlink+'")'); + // Change the long story url to the newly created GoURL + jQuery(element+' .field-newssources-link').html(data); + } else { + jQuery(element).html('<img src="'+qrlink+'" alt="QR Code" />'); + } + }); + }, + + clock : function() { + + + } + }; +})(); + +jQuery(document).ready(function() { + UNL.digitalSignage.init(); +}); diff --git a/sites/all/themes/unl_digitalsignage/scripts/unl_digitalsignage_unlalert.js b/sites/all/themes/unl_digitalsignage/scripts/unl_digitalsignage_unlalert.js new file mode 100644 index 0000000000000000000000000000000000000000..6df35f49e001290a90a73a41302fa26b853dce75 --- /dev/null +++ b/sites/all/themes/unl_digitalsignage/scripts/unl_digitalsignage_unlalert.js @@ -0,0 +1,138 @@ +/* Constructor */ +var unlAlerts = function() {}; + +UNL.digitalSignage.unlalert = (function() { + var activeIds = [], calltimeout, pulseTimeout; + + var data_url = 'http://alert1.unl.edu/json/unlcap.js'; + //var data_url = 'http://ucommrasmussen.unl.edu/unlcap.js'; + + return { + + init : function() { + console.log('Initializing the UNL Alert Plugin'); + if ("https:" != document.location.protocol) { + // Don't break authenticated sessions + UNL.digitalSignage.unlalert._callServer(); + } + }, + + dataReceived: function() { + console.log('UNL Alert data received'); + clearTimeout(calltimeout); + calltimeout = setTimeout(UNL.digitalSignage.unlalert._callServer, 30000); + }, + + _callServer: function() { + console.log('Checking the alert server at '+data_url); + var head = document.getElementsByTagName('head').item(0); + var old = document.getElementById('lastLoadedCmds'); + if (old) { + head.removeChild(old); + } + var currdate = new Date(); + script = document.createElement('script'); + script.src = data_url+'?'+currdate.getTime(); + script.type = 'text/javascript'; + script.defer = true; + script.id = 'lastLoadedCmds'; + head.appendChild(script); + }, + + alertUser: function(root) { + if (jQuery('#unlalert-wrapper').length == false) { + // Pause the video + try { + document.getElementById('unl-digitalsignage-video').pause(); + } catch (e) {} + // Insert the alert div + jQuery('body').prepend('<div id="unlalert-wrapper">'+ + '<div id="unlalert-sent"></div>'+ + '<div id="unlalert-desc">'+root.info.description+'</div>'+ + '<div id="unlalert-contact">University Police:<br />402-472-2222 or 911</div>'+ + '<div id="unlalert-bg-1"></div>'+ + '<div id="unlalert-bg-2"></div>'+ + '</div>'); + UNL.digitalSignage.unlalert.pulseStart(); + } + jQuery('#unlalert-desc').html(root.info.description); + jQuery('#unlalert-sent').html(UNL.digitalSignage.unlalert.formatDate(root.sent)); + }, + + closeAlert: function() { + try { + document.getElementById('unl-digitalsignage-video').play(); + } catch (e) {} + if (jQuery('#unlalert-wrapper')) { + jQuery('#unlalert-wrapper').remove(); + } + cleartimeout(pulseTimeout); + // Start the video + }, + + formatDate : function(date) { + // Parse date from feed + var d = new Date(Date.parse(date)); + var h = d.getHours(); + var m = d.getMinutes(); + var dd = "AM"; + if (h >= 12) { + h = h-12; + dd = "PM"; + } + if (h == 0) { + h = 12; + } + m = m<10?"0"+m:m; + + // Current time + var cur = new Date(); + + // Difference between two + var diff = Date.parse(cur) - Date.parse(date); + var ago = Math.round(diff/1000/60); + + return 'Issued '+ago+' minutes ago at '+h+':'+m+' '+dd; + }, + + pulseStart : function() { + pulseTimeout = setInterval(function() { + jQuery('#unlalert-bg-2').fadeIn(900); + jQuery('#unlalert-wrapper').animate({ + color : '#eee' + }, 900, function(){ + jQuery('#unlalert-bg-2').fadeOut(900); + jQuery('#unlalert-wrapper').animate({ + color : '#111' + }, 700); + }); + }, 6000); + } + + }; +})(); + +// unlAlerts.server.init called from within the data_url script that is appended to the dom +unlAlerts.server = { + init: function() { + UNL.digitalSignage.unlalert.dataReceived(); + + // unlAlerts.data comes from the data_url js file + var alert = unlAlerts.data.alert; + + if (alert.info) { + console.log("Found an alert, calling UNL.digitalSignage.unlalert.alertUser()"); + UNL.digitalSignage.unlalert.alertUser(alert); + return true; + } else { + console.log("No urgent alert, calling UNL.digitalSignage.unlalert.closeAlert()"); + UNL.digitalSignage.unlalert.closeAlert(); + } + + return false; + } +}; + +jQuery(document).ready(function(){ + UNL.digitalSignage.unlalert.init(); +}); \ No newline at end of file diff --git a/sites/all/themes/unl_digitalsignage/template.php b/sites/all/themes/unl_digitalsignage/template.php new file mode 100644 index 0000000000000000000000000000000000000000..0b389e171e7ad5464657dc970c026a858daaf708 --- /dev/null +++ b/sites/all/themes/unl_digitalsignage/template.php @@ -0,0 +1,65 @@ +<?php +/** + * Implements template_preprocess_page(). + */ +function unl_digitalsignage_preprocess_page(&$vars) { + drupal_add_js('if (typeof(UNL) == "undefined") {var UNL = (function(){return {};})();}', 'inline'); + drupal_add_js(drupal_get_path('theme', 'unl_digitalsignage') . '/scripts/unl_digitalsignage.js'); + drupal_add_js(drupal_get_path('theme', 'unl_digitalsignage') . '/scripts/unl_digitalsignage_unlalert.js'); + drupal_add_js(drupal_get_path('theme', 'unl_digitalsignage') . '/scripts/jquery.animate-colors.js'); //http://www.bitstorm.org/jquery/color-animation/ + drupal_add_js(drupal_get_path('theme', 'unl_digitalsignage') . '/scripts/jquery.animate-textshadow.js'); //http://plugins.jquery.com/project/animate-textshadow + drupal_add_js(drupal_get_path('theme', 'unl_digitalsignage') . '/scripts/jquery.ThreeDots.js'); //http://tpgblog.com/2009/12/21/threedots-the-jquery-ellipsis-plugin/ +} + +/** + * Implements template_preprocess_field(). + */ +function unl_digitalsignage_preprocess_field(&$vars, $hook) { + + switch($vars['element']['#bundle']) { + case 'digital_sign': + switch($vars['element']['#field_name']) { + case 'body': + break; + case 'field_newssources': + _unl_digitalsignage_yqlfeed($vars['items'], 'field_newssources'); + break; + case 'field_videosources': + _unl_digitalsignage_yqlfeed($vars['items'], 'field_videosources'); + break; + case 'field_twitter': + _unl_digitalsignage_twitterfeed($vars['items'][0]['#markup'], 'field_twitter'); + break; + case 'field_beautyshots': + break; + } + break; + case 'digital_sign_housing': + break; + case 'digital_sign_union': + break; + default: + } + +} + +function _unl_digitalsignage_yqlfeed($items, $fieldname) { + foreach ($items as $key => $item) { + $feeds[] = "'".urlencode(html_entity_decode($item['#markup']))."'"; + } + $yqlfeeds = implode('%2C',$feeds); + // select * from rss where url in ('http://ucommrasmussen.unl.edu/workspace/drupal7/ascweb/news.xml','http://scarlet.unl.edu/?feed=rss2&tag=college-of-arts-and-sciences') | sort(field="pubDate",descending="true") + $yql = 'http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20rss%20where%20url%20in%20('.$yqlfeeds.')%20%7C%20sort(field%3D%22pubDate%22%2Cdescending%3D%22true%22)&format=json'; + return drupal_add_js('UNL.digitalSignage.feeds["'.$fieldname.'"] = "'.$yql.'"', 'inline'); +} + +function _unl_digitalsignage_twitterfeed($item, $fieldname) { + global $base_path; + if (strpos($item, '/') === false) { + $twitterapi = 'https://api.twitter.com/1/statuses/user_timeline.json?screen_name='.$item.'&include_rts=true&count=20&include_entities=true'; + } else { + $twitterapi = 'https://api.twitter.com/1/lists/statuses.json?slug=unl&owner_screen_name=unlnews&page=1&include_entities=true'; + } + $proxy = $base_path.'sites/all/themes/unl_digitalsignage/proxy.php?u='.urlencode($twitterapi); + return drupal_add_js('UNL.digitalSignage.feeds["'.$fieldname.'"] = "'.$proxy.'"', 'inline'); +} \ No newline at end of file diff --git a/sites/all/themes/unl_digitalsignage/unl_digitalsignage.info b/sites/all/themes/unl_digitalsignage/unl_digitalsignage.info new file mode 100644 index 0000000000000000000000000000000000000000..dbbbbc9b8d7fe297a59d1a6384be9b1b7b187fd7 --- /dev/null +++ b/sites/all/themes/unl_digitalsignage/unl_digitalsignage.info @@ -0,0 +1,16 @@ +name = UNL Digital Signage +description = Theme for digital signs at UNL +screenshot = screenshot.png +core = 7.x +engine = phptemplate + +stylesheets[all][] = css/style.css + +regions[content] = Main Content +regions[highlighted] = Highlighted +regions[sidebar_first] = Sidebar first +regions[sidebar_second] = Sidebar second + +regions[news] = News + + \ No newline at end of file