From ba1db01078f414acb4c2280df2ebfd0cabe97b41 Mon Sep 17 00:00:00 2001 From: Thierry Parmentelat Date: Tue, 4 Mar 2008 09:03:43 +0000 Subject: [PATCH] initial import from onelab svn codebase --- CHANGELOG.txt | 542 ++++ INSTALL.mysql.txt | 85 + INSTALL.pgsql.txt | 53 + INSTALL.txt | 218 ++ LICENSE.txt | 342 +++ MAINTAINERS.txt | 78 + Makefile | 24 + PLCAPI/index.html | 6 + PLCWWW.spec | 99 + UPGRADE.txt | 18 + boot/getnodeid.php | 29 + boot/index.php | 80 + boot/uudecode.gz | Bin 0 -> 4317 bytes cron.php | 31 + database/database.4.0.mysql | 873 ++++++ database/database.4.1.mysql | 930 ++++++ database/database.pgsql | 915 ++++++ database/updates.inc | 2015 +++++++++++++ drupal.conf | 92 + includes/bootstrap.inc | 796 ++++++ includes/common.inc | 1424 ++++++++++ includes/database.inc | 390 +++ includes/database.mysql.inc | 370 +++ includes/database.mysqli.inc | 371 +++ includes/database.pgsql.inc | 360 +++ includes/file.inc | 715 +++++ includes/form.inc | 1178 ++++++++ includes/image.inc | 303 ++ includes/install.inc | 81 + includes/locale.inc | 1548 ++++++++++ includes/menu.inc | 1362 +++++++++ includes/module.inc | 219 ++ includes/pager.inc | 430 +++ includes/path.inc | 201 ++ includes/session.inc | 91 + includes/tablesort.inc | 189 ++ includes/theme.inc | 1034 +++++++ includes/unicode.inc | 487 ++++ includes/xmlrpc.inc | 476 ++++ includes/xmlrpcs.inc | 318 +++ index.php | 35 + misc/arrow-asc.png | Bin 0 -> 118 bytes misc/arrow-desc.png | Bin 0 -> 119 bytes misc/autocomplete.js | 276 ++ misc/blog.png | Bin 0 -> 109 bytes misc/collapse.js | 70 + misc/degree2decimal.py | 32 + misc/drupal.css | 676 +++++ misc/drupal.js | 367 +++ misc/druplicon.png | Bin 0 -> 4072 bytes misc/favicon.ico | Bin 0 -> 8430 bytes misc/feed.png | Bin 0 -> 764 bytes misc/forum-closed.png | Bin 0 -> 200 bytes misc/forum-default.png | Bin 0 -> 181 bytes misc/forum-hot-new.png | Bin 0 -> 237 bytes misc/forum-hot.png | Bin 0 -> 229 bytes misc/forum-new.png | Bin 0 -> 175 bytes misc/forum-sticky.png | Bin 0 -> 329 bytes misc/google-pl1.png | Bin 0 -> 2548 bytes misc/google-plc.png | Bin 0 -> 1660 bytes misc/google-ple.png | Bin 0 -> 1162 bytes misc/googlemap-notes.txt | 29 + misc/googlemap.js | 94 + misc/grippie.png | Bin 0 -> 162 bytes misc/maintenance.css | 55 + misc/menu-collapsed.png | Bin 0 -> 108 bytes misc/menu-expanded.png | Bin 0 -> 106 bytes misc/menu-leaf.png | Bin 0 -> 194 bytes misc/powered-black-135x42.png | Bin 0 -> 2817 bytes misc/powered-black-80x15.png | Bin 0 -> 1467 bytes misc/powered-black-88x31.png | Bin 0 -> 2106 bytes misc/powered-blue-135x42.png | Bin 0 -> 3027 bytes misc/powered-blue-80x15.png | Bin 0 -> 1011 bytes misc/powered-blue-88x31.png | Bin 0 -> 2113 bytes misc/powered-gray-135x42.png | Bin 0 -> 2722 bytes misc/powered-gray-80x15.png | Bin 0 -> 758 bytes misc/powered-gray-88x31.png | Bin 0 -> 2071 bytes misc/print.css | 26 + misc/progress.gif | Bin 0 -> 790 bytes misc/progress.js | 118 + misc/textarea.js | 122 + misc/throbber.gif | Bin 0 -> 1336 bytes misc/update.js | 33 + misc/upload.js | 75 + misc/watchdog-error.png | Bin 0 -> 799 bytes misc/watchdog-warning.png | Bin 0 -> 339 bytes misc/xml.png | Bin 0 -> 396 bytes modules/aggregator.module | 1371 +++++++++ modules/archive.module | 291 ++ modules/block.module | 660 +++++ modules/blog.module | 305 ++ modules/blogapi.module | 737 +++++ modules/book.module | 1026 +++++++ modules/comment.module | 1787 ++++++++++++ modules/contact.module | 549 ++++ modules/drupal.module | 364 +++ modules/filter.module | 1372 +++++++++ modules/forum.module | 1126 ++++++++ modules/help.module | 135 + modules/legacy.module | 202 ++ modules/locale.module | 417 +++ modules/menu.module | 764 +++++ modules/node.module | 2481 +++++++++++++++++ modules/page.module | 100 + modules/path.module | 353 +++ modules/ping.module | 68 + modules/planetlab.module | 319 +++ modules/poll.module | 501 ++++ modules/profile.module | 788 ++++++ modules/search.module | 1262 +++++++++ modules/statistics.module | 495 ++++ modules/story.module | 86 + modules/system.module | 1272 +++++++++ modules/taxonomy.module | 1365 +++++++++ modules/taxonomy_block/LICENSE.txt | 274 ++ modules/taxonomy_block/README.txt | 5 + modules/taxonomy_block/taxonomy_block.install | 46 + modules/taxonomy_block/taxonomy_block.module | 324 +++ modules/throttle.module | 158 ++ modules/tracker.module | 129 + modules/upload.module | 756 +++++ modules/user.module | 2134 ++++++++++++++ modules/watchdog.module | 194 ++ planetlab/about.php | 41 + planetlab/addresses/index.php | 40 + planetlab/events/calendar.php | 191 ++ planetlab/events/calendar/CalendarPopup.js | 504 ++++ planetlab/events/index.php | 435 +++ planetlab/includes/comon.png | Bin 0 -> 2694 bytes planetlab/includes/delete.png | Bin 0 -> 2279 bytes planetlab/includes/event.png | Bin 0 -> 2065 bytes planetlab/includes/js/bsn.Ajax.js | 84 + planetlab/includes/js/bsn.AutoSuggest.js | 336 +++ planetlab/includes/js/bsn.DOM.js | 223 ++ planetlab/includes/plc_drupal.php | 51 + planetlab/includes/plc_footer.php | 22 + planetlab/includes/plc_functions.php | 383 +++ planetlab/includes/plc_header.php | 33 + planetlab/includes/plc_login.php | 27 + planetlab/includes/plc_script.js | 35 + planetlab/includes/plc_session.php | 157 ++ planetlab/includes/plc_sorts.php | 78 + planetlab/includes/plc_style.css | 297 ++ planetlab/login.php | 90 + planetlab/logout.php | 30 + planetlab/nodes/add_node.php | 303 ++ planetlab/nodes/all_hosts.php | 5 + planetlab/nodes/all_ips.php | 5 + planetlab/nodes/alpha_hosts.php | 5 + planetlab/nodes/alpha_ips.php | 5 + planetlab/nodes/beta_hosts.php | 5 + planetlab/nodes/beta_ips.php | 5 + planetlab/nodes/comon.php | 122 + planetlab/nodes/etc_hosts.php | 5 + planetlab/nodes/index.php | 519 ++++ planetlab/nodes/known_hosts.php | 1 + planetlab/nodes/node_actions.php | 452 +++ planetlab/nodes/node_groups.php | 302 ++ planetlab/nodes/node_networks.php | 245 ++ planetlab/nodes/production_hosts.php | 5 + planetlab/nodes/production_ips.php | 5 + planetlab/nodes/setting_action.php | 131 + planetlab/nodes/settings.php | 234 ++ planetlab/nodes/sliver_action.php | 60 + planetlab/nodes/slivers.php | 144 + planetlab/nodes/test.php | 39 + planetlab/peers/index.php | 108 + planetlab/persons/index.php | 473 ++++ planetlab/persons/person_action.php | 229 ++ planetlab/persons/register.php | 349 +++ planetlab/persons/reset_password.php | 73 + planetlab/persons/test.php | 39 + planetlab/persons/update.php | 181 ++ planetlab/pub/feedback.php | 165 ++ planetlab/pub/sites.php | 55 + planetlab/pub/slices.php | 158 ++ planetlab/sirius/index.php | 764 +++++ planetlab/sirius/sirius_func.php | 370 +++ planetlab/sites/delete_site.php | 78 + planetlab/sites/expire.php | 74 + planetlab/sites/index.php | 445 +++ planetlab/sites/join_request.php | 343 +++ planetlab/sites/pcu.php | 219 ++ planetlab/sites/peers.php | 137 + planetlab/sites/register.php | 221 ++ planetlab/sites/site_action.php | 55 + planetlab/sites/site_form.php | 246 ++ planetlab/sites/switch_site.php | 78 + planetlab/sites/test.php | 39 + planetlab/sites/update_site.php | 151 + planetlab/slices/add_slice.php | 169 ++ planetlab/slices/attrib_action.php | 133 + planetlab/slices/attributes.php | 177 ++ planetlab/slices/delete_slice.php | 62 + planetlab/slices/index.php | 509 ++++ planetlab/slices/renew_slice.php | 173 ++ planetlab/slices/slice_action.php | 64 + planetlab/slices/slice_nodes.php | 264 ++ planetlab/slices/slice_users.php | 208 ++ planetlab/slices/test.php | 69 + planetlab/slices/update_slice.php | 111 + planetlab/sulogout.php | 28 + plot-latlong/.mapimages/Africa.png | Bin 0 -> 15572 bytes plot-latlong/.mapimages/Australia.png | Bin 0 -> 4121 bytes plot-latlong/.mapimages/Belgium.png | Bin 0 -> 2512 bytes plot-latlong/.mapimages/Canada.png | Bin 0 -> 10643 bytes plot-latlong/.mapimages/Caribbean.png | Bin 0 -> 4977 bytes plot-latlong/.mapimages/CentralAmerica.png | Bin 0 -> 6830 bytes plot-latlong/.mapimages/China.png | Bin 0 -> 5031 bytes plot-latlong/.mapimages/Europe.png | Bin 0 -> 5549 bytes plot-latlong/.mapimages/France.png | Bin 0 -> 3751 bytes plot-latlong/.mapimages/Germany.png | Bin 0 -> 3120 bytes plot-latlong/.mapimages/Hawaii.png | Bin 0 -> 1452 bytes plot-latlong/.mapimages/India.png | Bin 0 -> 9865 bytes plot-latlong/.mapimages/Italy.png | Bin 0 -> 4321 bytes plot-latlong/.mapimages/Japan.png | Bin 0 -> 4136 bytes plot-latlong/.mapimages/Korea.png | Bin 0 -> 4077 bytes plot-latlong/.mapimages/MalaysiaIndonesia.png | Bin 0 -> 9017 bytes plot-latlong/.mapimages/MiddleEast.png | Bin 0 -> 7591 bytes plot-latlong/.mapimages/NOSEFI.png | Bin 0 -> 12348 bytes plot-latlong/.mapimages/Netherlands.png | Bin 0 -> 4552 bytes plot-latlong/.mapimages/NewZealand.png | Bin 0 -> 3129 bytes plot-latlong/.mapimages/Philippines.png | Bin 0 -> 6106 bytes plot-latlong/.mapimages/SouthAmerica.png | Bin 0 -> 9405 bytes plot-latlong/.mapimages/UK.png | Bin 0 -> 5022 bytes plot-latlong/.mapimages/USA100.png | Bin 0 -> 14800 bytes plot-latlong/.mapimages/USA200.png | Bin 0 -> 19362 bytes plot-latlong/.mapimages/USA50-new.png | Bin 0 -> 8661 bytes plot-latlong/.mapimages/World100.png | Bin 0 -> 15324 bytes plot-latlong/.mapimages/World50-new.png | Bin 0 -> 31829 bytes plot-latlong/.mapinfo | 35 + plot-latlong/CONFIG | 135 + plot-latlong/README | 236 ++ plot-latlong/plot-latlong | 390 +++ scripts/code-clean.sh | 10 + scripts/code-style.pl | 162 ++ scripts/cron-curl.sh | 4 + scripts/cron-lynx.sh | 4 + scripts/prefix.sh | 30 + sites/default/settings.php | 162 ++ themes/bluemarine/block.tpl.php | 4 + themes/bluemarine/box.tpl.php | 5 + themes/bluemarine/comment.tpl.php | 9 + themes/bluemarine/logo.png | Bin 0 -> 1767 bytes themes/bluemarine/node.tpl.php | 10 + themes/bluemarine/page.tpl.php | 58 + themes/bluemarine/screenshot.png | Bin 0 -> 4136 bytes themes/bluemarine/style.css | 356 +++ themes/chameleon/background.png | Bin 0 -> 200 bytes themes/chameleon/chameleon.theme | 172 ++ themes/chameleon/common.css | 151 + themes/chameleon/logo.png | Bin 0 -> 1762 bytes themes/chameleon/marvin/bullet.png | Bin 0 -> 210 bytes .../chameleon/marvin/druplicon-watermark.png | Bin 0 -> 2454 bytes themes/chameleon/marvin/logo.png | Bin 0 -> 1762 bytes themes/chameleon/marvin/screenshot.png | Bin 0 -> 2800 bytes themes/chameleon/marvin/style.css | 118 + themes/chameleon/screenshot.png | Bin 0 -> 3450 bytes themes/chameleon/style.css | 90 + themes/engines/phptemplate/block.tpl.php | 4 + themes/engines/phptemplate/box.tpl.php | 6 + themes/engines/phptemplate/comment.tpl.php | 15 + themes/engines/phptemplate/default.tpl.php | 1 + themes/engines/phptemplate/node.tpl.php | 18 + themes/engines/phptemplate/phptemplate.engine | 344 +++ themes/pushbutton/arrow-next-hover.png | Bin 0 -> 173 bytes themes/pushbutton/arrow-next-visited.png | Bin 0 -> 173 bytes themes/pushbutton/arrow-next.png | Bin 0 -> 173 bytes themes/pushbutton/arrow-prev-hover.png | Bin 0 -> 174 bytes themes/pushbutton/arrow-prev-visited.png | Bin 0 -> 178 bytes themes/pushbutton/arrow-prev.png | Bin 0 -> 175 bytes themes/pushbutton/arrow-up-hover.png | Bin 0 -> 175 bytes themes/pushbutton/arrow-up-visited.png | Bin 0 -> 173 bytes themes/pushbutton/arrow-up.png | Bin 0 -> 173 bytes themes/pushbutton/background.png | Bin 0 -> 2445 bytes themes/pushbutton/block.tpl.php | 4 + themes/pushbutton/box.tpl.php | 6 + themes/pushbutton/comment.tpl.php | 11 + themes/pushbutton/forum-container.jpg | Bin 0 -> 10722 bytes themes/pushbutton/forum-link.png | Bin 0 -> 711 bytes themes/pushbutton/header-a.jpg | Bin 0 -> 440 bytes themes/pushbutton/header-b.jpg | Bin 0 -> 5770 bytes themes/pushbutton/header-c.png | Bin 0 -> 195 bytes themes/pushbutton/icon-block.png | Bin 0 -> 310 bytes themes/pushbutton/icon-comment.png | Bin 0 -> 317 bytes themes/pushbutton/logo-active.jpg | Bin 0 -> 2466 bytes themes/pushbutton/logo-background.jpg | Bin 0 -> 950 bytes themes/pushbutton/logo-hover.jpg | Bin 0 -> 2712 bytes themes/pushbutton/logo.png | Bin 0 -> 103 bytes themes/pushbutton/node.tpl.php | 12 + themes/pushbutton/page.tpl.php | 120 + themes/pushbutton/screenshot.png | Bin 0 -> 7594 bytes themes/pushbutton/style.css | 617 ++++ themes/pushbutton/tabs-off.png | Bin 0 -> 317 bytes themes/pushbutton/tabs-on.png | Bin 0 -> 272 bytes themes/pushbutton/tabs-option-hover.png | Bin 0 -> 296 bytes themes/pushbutton/tabs-option-off.png | Bin 0 -> 294 bytes themes/pushbutton/tabs-option-on.png | Bin 0 -> 263 bytes update.php | 718 +++++ xmlrpc.php | 14 + 300 files changed, 61699 insertions(+) create mode 100644 CHANGELOG.txt create mode 100644 INSTALL.mysql.txt create mode 100644 INSTALL.pgsql.txt create mode 100644 INSTALL.txt create mode 100644 LICENSE.txt create mode 100644 MAINTAINERS.txt create mode 100644 Makefile create mode 100644 PLCAPI/index.html create mode 100644 PLCWWW.spec create mode 100644 UPGRADE.txt create mode 100644 boot/getnodeid.php create mode 100644 boot/index.php create mode 100755 boot/uudecode.gz create mode 100644 cron.php create mode 100644 database/database.4.0.mysql create mode 100644 database/database.4.1.mysql create mode 100644 database/database.pgsql create mode 100644 database/updates.inc create mode 100644 drupal.conf create mode 100644 includes/bootstrap.inc create mode 100644 includes/common.inc create mode 100644 includes/database.inc create mode 100644 includes/database.mysql.inc create mode 100644 includes/database.mysqli.inc create mode 100644 includes/database.pgsql.inc create mode 100644 includes/file.inc create mode 100644 includes/form.inc create mode 100644 includes/image.inc create mode 100644 includes/install.inc create mode 100644 includes/locale.inc create mode 100644 includes/menu.inc create mode 100644 includes/module.inc create mode 100644 includes/pager.inc create mode 100644 includes/path.inc create mode 100644 includes/session.inc create mode 100644 includes/tablesort.inc create mode 100644 includes/theme.inc create mode 100644 includes/unicode.inc create mode 100644 includes/xmlrpc.inc create mode 100644 includes/xmlrpcs.inc create mode 100644 index.php create mode 100644 misc/arrow-asc.png create mode 100644 misc/arrow-desc.png create mode 100644 misc/autocomplete.js create mode 100644 misc/blog.png create mode 100644 misc/collapse.js create mode 100755 misc/degree2decimal.py create mode 100644 misc/drupal.css create mode 100644 misc/drupal.js create mode 100644 misc/druplicon.png create mode 100644 misc/favicon.ico create mode 100644 misc/feed.png create mode 100644 misc/forum-closed.png create mode 100644 misc/forum-default.png create mode 100644 misc/forum-hot-new.png create mode 100644 misc/forum-hot.png create mode 100644 misc/forum-new.png create mode 100644 misc/forum-sticky.png create mode 100644 misc/google-pl1.png create mode 100644 misc/google-plc.png create mode 100644 misc/google-ple.png create mode 100644 misc/googlemap-notes.txt create mode 100644 misc/googlemap.js create mode 100644 misc/grippie.png create mode 100644 misc/maintenance.css create mode 100644 misc/menu-collapsed.png create mode 100644 misc/menu-expanded.png create mode 100644 misc/menu-leaf.png create mode 100644 misc/powered-black-135x42.png create mode 100644 misc/powered-black-80x15.png create mode 100644 misc/powered-black-88x31.png create mode 100644 misc/powered-blue-135x42.png create mode 100644 misc/powered-blue-80x15.png create mode 100644 misc/powered-blue-88x31.png create mode 100644 misc/powered-gray-135x42.png create mode 100644 misc/powered-gray-80x15.png create mode 100644 misc/powered-gray-88x31.png create mode 100644 misc/print.css create mode 100644 misc/progress.gif create mode 100644 misc/progress.js create mode 100644 misc/textarea.js create mode 100644 misc/throbber.gif create mode 100644 misc/update.js create mode 100644 misc/upload.js create mode 100644 misc/watchdog-error.png create mode 100644 misc/watchdog-warning.png create mode 100644 misc/xml.png create mode 100644 modules/aggregator.module create mode 100644 modules/archive.module create mode 100644 modules/block.module create mode 100644 modules/blog.module create mode 100644 modules/blogapi.module create mode 100644 modules/book.module create mode 100644 modules/comment.module create mode 100644 modules/contact.module create mode 100644 modules/drupal.module create mode 100644 modules/filter.module create mode 100644 modules/forum.module create mode 100644 modules/help.module create mode 100644 modules/legacy.module create mode 100644 modules/locale.module create mode 100644 modules/menu.module create mode 100644 modules/node.module create mode 100644 modules/page.module create mode 100644 modules/path.module create mode 100644 modules/ping.module create mode 100644 modules/planetlab.module create mode 100644 modules/poll.module create mode 100644 modules/profile.module create mode 100644 modules/search.module create mode 100644 modules/statistics.module create mode 100644 modules/story.module create mode 100644 modules/system.module create mode 100644 modules/taxonomy.module create mode 100644 modules/taxonomy_block/LICENSE.txt create mode 100644 modules/taxonomy_block/README.txt create mode 100644 modules/taxonomy_block/taxonomy_block.install create mode 100644 modules/taxonomy_block/taxonomy_block.module create mode 100644 modules/throttle.module create mode 100644 modules/tracker.module create mode 100644 modules/upload.module create mode 100644 modules/user.module create mode 100644 modules/watchdog.module create mode 100644 planetlab/about.php create mode 100644 planetlab/addresses/index.php create mode 100644 planetlab/events/calendar.php create mode 100644 planetlab/events/calendar/CalendarPopup.js create mode 100644 planetlab/events/index.php create mode 100644 planetlab/includes/comon.png create mode 100644 planetlab/includes/delete.png create mode 100644 planetlab/includes/event.png create mode 100644 planetlab/includes/js/bsn.Ajax.js create mode 100644 planetlab/includes/js/bsn.AutoSuggest.js create mode 100644 planetlab/includes/js/bsn.DOM.js create mode 100644 planetlab/includes/plc_drupal.php create mode 100644 planetlab/includes/plc_footer.php create mode 100644 planetlab/includes/plc_functions.php create mode 100644 planetlab/includes/plc_header.php create mode 100644 planetlab/includes/plc_login.php create mode 100644 planetlab/includes/plc_script.js create mode 100644 planetlab/includes/plc_session.php create mode 100644 planetlab/includes/plc_sorts.php create mode 100644 planetlab/includes/plc_style.css create mode 100644 planetlab/login.php create mode 100644 planetlab/logout.php create mode 100644 planetlab/nodes/add_node.php create mode 100644 planetlab/nodes/all_hosts.php create mode 100644 planetlab/nodes/all_ips.php create mode 100644 planetlab/nodes/alpha_hosts.php create mode 100644 planetlab/nodes/alpha_ips.php create mode 100644 planetlab/nodes/beta_hosts.php create mode 100644 planetlab/nodes/beta_ips.php create mode 100644 planetlab/nodes/comon.php create mode 100644 planetlab/nodes/etc_hosts.php create mode 100644 planetlab/nodes/index.php create mode 100644 planetlab/nodes/known_hosts.php create mode 100644 planetlab/nodes/node_actions.php create mode 100644 planetlab/nodes/node_groups.php create mode 100644 planetlab/nodes/node_networks.php create mode 100644 planetlab/nodes/production_hosts.php create mode 100644 planetlab/nodes/production_ips.php create mode 100644 planetlab/nodes/setting_action.php create mode 100644 planetlab/nodes/settings.php create mode 100644 planetlab/nodes/sliver_action.php create mode 100644 planetlab/nodes/slivers.php create mode 100644 planetlab/nodes/test.php create mode 100644 planetlab/peers/index.php create mode 100644 planetlab/persons/index.php create mode 100644 planetlab/persons/person_action.php create mode 100644 planetlab/persons/register.php create mode 100644 planetlab/persons/reset_password.php create mode 100644 planetlab/persons/test.php create mode 100644 planetlab/persons/update.php create mode 100644 planetlab/pub/feedback.php create mode 100644 planetlab/pub/sites.php create mode 100644 planetlab/pub/slices.php create mode 100644 planetlab/sirius/index.php create mode 100644 planetlab/sirius/sirius_func.php create mode 100644 planetlab/sites/delete_site.php create mode 100644 planetlab/sites/expire.php create mode 100644 planetlab/sites/index.php create mode 100644 planetlab/sites/join_request.php create mode 100644 planetlab/sites/pcu.php create mode 100644 planetlab/sites/peers.php create mode 100644 planetlab/sites/register.php create mode 100644 planetlab/sites/site_action.php create mode 100644 planetlab/sites/site_form.php create mode 100644 planetlab/sites/switch_site.php create mode 100644 planetlab/sites/test.php create mode 100644 planetlab/sites/update_site.php create mode 100644 planetlab/slices/add_slice.php create mode 100644 planetlab/slices/attrib_action.php create mode 100644 planetlab/slices/attributes.php create mode 100644 planetlab/slices/delete_slice.php create mode 100644 planetlab/slices/index.php create mode 100644 planetlab/slices/renew_slice.php create mode 100644 planetlab/slices/slice_action.php create mode 100644 planetlab/slices/slice_nodes.php create mode 100644 planetlab/slices/slice_users.php create mode 100644 planetlab/slices/test.php create mode 100644 planetlab/slices/update_slice.php create mode 100644 planetlab/sulogout.php create mode 100644 plot-latlong/.mapimages/Africa.png create mode 100644 plot-latlong/.mapimages/Australia.png create mode 100644 plot-latlong/.mapimages/Belgium.png create mode 100644 plot-latlong/.mapimages/Canada.png create mode 100644 plot-latlong/.mapimages/Caribbean.png create mode 100644 plot-latlong/.mapimages/CentralAmerica.png create mode 100644 plot-latlong/.mapimages/China.png create mode 100644 plot-latlong/.mapimages/Europe.png create mode 100644 plot-latlong/.mapimages/France.png create mode 100644 plot-latlong/.mapimages/Germany.png create mode 100644 plot-latlong/.mapimages/Hawaii.png create mode 100644 plot-latlong/.mapimages/India.png create mode 100644 plot-latlong/.mapimages/Italy.png create mode 100644 plot-latlong/.mapimages/Japan.png create mode 100644 plot-latlong/.mapimages/Korea.png create mode 100644 plot-latlong/.mapimages/MalaysiaIndonesia.png create mode 100644 plot-latlong/.mapimages/MiddleEast.png create mode 100644 plot-latlong/.mapimages/NOSEFI.png create mode 100644 plot-latlong/.mapimages/Netherlands.png create mode 100644 plot-latlong/.mapimages/NewZealand.png create mode 100644 plot-latlong/.mapimages/Philippines.png create mode 100644 plot-latlong/.mapimages/SouthAmerica.png create mode 100644 plot-latlong/.mapimages/UK.png create mode 100644 plot-latlong/.mapimages/USA100.png create mode 100644 plot-latlong/.mapimages/USA200.png create mode 100644 plot-latlong/.mapimages/USA50-new.png create mode 100644 plot-latlong/.mapimages/World100.png create mode 100644 plot-latlong/.mapimages/World50-new.png create mode 100644 plot-latlong/.mapinfo create mode 100644 plot-latlong/CONFIG create mode 100644 plot-latlong/README create mode 100755 plot-latlong/plot-latlong create mode 100644 scripts/code-clean.sh create mode 100644 scripts/code-style.pl create mode 100644 scripts/cron-curl.sh create mode 100644 scripts/cron-lynx.sh create mode 100644 scripts/prefix.sh create mode 100644 sites/default/settings.php create mode 100644 themes/bluemarine/block.tpl.php create mode 100644 themes/bluemarine/box.tpl.php create mode 100644 themes/bluemarine/comment.tpl.php create mode 100644 themes/bluemarine/logo.png create mode 100644 themes/bluemarine/node.tpl.php create mode 100644 themes/bluemarine/page.tpl.php create mode 100644 themes/bluemarine/screenshot.png create mode 100644 themes/bluemarine/style.css create mode 100644 themes/chameleon/background.png create mode 100644 themes/chameleon/chameleon.theme create mode 100644 themes/chameleon/common.css create mode 100644 themes/chameleon/logo.png create mode 100644 themes/chameleon/marvin/bullet.png create mode 100644 themes/chameleon/marvin/druplicon-watermark.png create mode 100644 themes/chameleon/marvin/logo.png create mode 100644 themes/chameleon/marvin/screenshot.png create mode 100644 themes/chameleon/marvin/style.css create mode 100644 themes/chameleon/screenshot.png create mode 100644 themes/chameleon/style.css create mode 100644 themes/engines/phptemplate/block.tpl.php create mode 100644 themes/engines/phptemplate/box.tpl.php create mode 100644 themes/engines/phptemplate/comment.tpl.php create mode 100644 themes/engines/phptemplate/default.tpl.php create mode 100644 themes/engines/phptemplate/node.tpl.php create mode 100644 themes/engines/phptemplate/phptemplate.engine create mode 100644 themes/pushbutton/arrow-next-hover.png create mode 100644 themes/pushbutton/arrow-next-visited.png create mode 100644 themes/pushbutton/arrow-next.png create mode 100644 themes/pushbutton/arrow-prev-hover.png create mode 100644 themes/pushbutton/arrow-prev-visited.png create mode 100644 themes/pushbutton/arrow-prev.png create mode 100644 themes/pushbutton/arrow-up-hover.png create mode 100644 themes/pushbutton/arrow-up-visited.png create mode 100644 themes/pushbutton/arrow-up.png create mode 100644 themes/pushbutton/background.png create mode 100644 themes/pushbutton/block.tpl.php create mode 100644 themes/pushbutton/box.tpl.php create mode 100644 themes/pushbutton/comment.tpl.php create mode 100644 themes/pushbutton/forum-container.jpg create mode 100644 themes/pushbutton/forum-link.png create mode 100644 themes/pushbutton/header-a.jpg create mode 100644 themes/pushbutton/header-b.jpg create mode 100644 themes/pushbutton/header-c.png create mode 100644 themes/pushbutton/icon-block.png create mode 100644 themes/pushbutton/icon-comment.png create mode 100644 themes/pushbutton/logo-active.jpg create mode 100644 themes/pushbutton/logo-background.jpg create mode 100644 themes/pushbutton/logo-hover.jpg create mode 100644 themes/pushbutton/logo.png create mode 100644 themes/pushbutton/node.tpl.php create mode 100644 themes/pushbutton/page.tpl.php create mode 100644 themes/pushbutton/screenshot.png create mode 100644 themes/pushbutton/style.css create mode 100644 themes/pushbutton/tabs-off.png create mode 100644 themes/pushbutton/tabs-on.png create mode 100644 themes/pushbutton/tabs-option-hover.png create mode 100644 themes/pushbutton/tabs-option-off.png create mode 100644 themes/pushbutton/tabs-option-on.png create mode 100644 update.php create mode 100644 xmlrpc.php diff --git a/CHANGELOG.txt b/CHANGELOG.txt new file mode 100644 index 0000000..decbbf2 --- /dev/null +++ b/CHANGELOG.txt @@ -0,0 +1,542 @@ +// $Id: CHANGELOG.txt 144 2007-03-28 07:52:20Z thierry $ + +Drupal 4.7.3, 2006-08-02 +------------------------ +- fixed security issue (XSS), see SA-2006-011 + +Drupal 4.7.2, 2006-06-01 +------------------------ +- fixed critical upload issue, see SA-2006-007 +- fixed taxonomy XSS issue, see SA-2006-008 +- fixed a variety of small bugs. + +Drupal 4.7.1, 2006-05-24 +------------------------ +- fixed critical SQL issue, see SA-2006-005 +- fixed a serious upgrade related bug. +- fixed a variety of small bugs. + +Drupal 4.7.0, 2006-05-01 +------------------------ +- added free tagging support. +- added a site-wide contact form. +- theme system: + * added the PHPTemplate theme engine and removed the Xtemplate engine. + * converted the bluemarine theme from XTemplate to PHPTemplate. + * converted the pushbutton theme from XTemplate to PHPTemplate. +- usability: + * reworked the 'request new password' functionality. + * reworked the node and comment edit forms. + * made it easy to add nodes to the navigation menu. + * added site 'offline for maintenance' feature. + * added support for auto-complete forms (AJAX). + * added support for collapsible page sections (JS). + * added support for resizable text fields (JS). + * improved file upload functionality (AJAX). + * reorganized some settings pages. + * added friendly database error screens. + * improved styling of update.php. +- refactored the forms API. + * made it possible to alter, extend or theme forms. +- comment system: + * added support for "mass comment operations" to ease repetitive tasks. + * comment moderation has been removed. +- node system: + * reworked the revision functionality. + * removed the bookmarklet code. Third-party modules can now handle + this. +- upgrade system: + * allows contributed modules to plug into the upgrade system. +- profiles: + * added a block to display author information along with posts. + * added support for private profile fields. +- statistics module: + * added the ability to track page generation times. + * made it possible to block certain IPs/hostnames. +- block system: + * added support for theme-specific block regions. +- syndication: + * made the aggregator module parse Atom feeds. + * made the aggregator generate RSS feeds. + * added RSS feed settings. +- XML-RPC: + * replaced the XML-RPC library by a better one. +- performance: + * added 'loose caching' option for high-traffic sites. + * improved performance of path aliasing. + * added the ability to track page generation times. +- internationalization: + * improved Unicode string handling API. + * added support for PHP's multibyte string module. +- added support for PHP5's 'mysqli' extension. +- search module: + * made indexer smarter and more robust. + * added advanced search operators (e.g. phrase, node type, ...). + * added customizable result ranking. +- PostgreSQL support: + * removed dependency on PL/pgSQL procedural language. +- menu system: + * added support for external URLs. +- queue module: + * removed from core. +- HTTP handling: + * added support for a tolerant Base URL. + * output URIs relative to the root, without a base tag. + +Drupal 4.6.6, 2006-03-13 +------------------------ +- fixed bugs, including 4 security vulnerabilities. + +Drupal 4.6.5, 2005-12-12 +------------------------ +- fixed bugs: no critical bugs were identified. + +Drupal 4.6.4, 2005-11-30 +------------------------ +- fixed bugs, including 3 security vulnerabilities. + +Drupal 4.6.3, 2005-08-15 +------------------------ +- fixed bugs, including a critical "arbitrary PHP code execution" bug. + +Drupal 4.6.2, 2005-06-29 +------------------------ +- fixed bugs, including two critical "arbitrary PHP code execution" bugs. + +Drupal 4.6.1, 2005-06-01 +------------------------ +- fixed bugs, including a critical input validation bug. + +Drupal 4.6.0, 2005-04-15 +------------------------ +- PHP5 compliance +- search: + * added UTF-8 support to make it work with all languages. + * improved search indexing algorithm. + * improved search output. + * impose a throttle on indexing of large sites. + * added search block. +- syndication: + * made the ping module ping pingomatic.com which, in turn, will ping all the major ping services. + * made Drupal generate RSS 2.0 feeds. + * made RSS feeds extensible. + * added categories to RSS feeds. + * added enclosures to RSS feeds. +- flood control mechanism: + * added a mechanism to throttle certain operations. +- usability: + * refactored the block configuration pages. + * refactored the statistics pages. + * refactored the watchdog pages. + * refactored the throttle module configuration. + * refactored the access rules page. + * refactored the content administration page. + * introduced forum configuration pages. + * added a 'add child page' link to book pages. +- contact module: + * added a simple contact module that allows users to contact each other using e-mail. +- multi-site configuration: + * made it possible to run multiple sites from a single code base. +- added an image API: enables better image handling. +- block system: + * extended the block visibility settings. +- theme system: + * added new theme functions. +- database backend: + * the PEAR database backend is no longer supported. +- performance: + * improved performance of the forum topics block. + * improved performance of the tracker module. + * improved performance of the node pages. +- documentation: + * improved and extended PHPDoc/Doxygen comments. + +Drupal 4.5.8, 2006-03-13 +------------------------ +- fixed bugs, including 3 security vulnerabilities. + +Drupal 4.5.7, 2005-12-12 +------------------------ +- fixed bugs: no critical bugs were identified. + +Drupal 4.5.6, 2005-11-30 +------------------------ +- fixed bugs, including 3 security vulnerabilities. + +Drupal 4.5.5, 2005-08-15 +------------------------ +- fixed bugs, including a critical "arbitrary PHP code execution" bug. + +Drupal 4.5.4, 2005-06-29 +------------------------ +- fixed bugs, including two critical "arbitrary PHP code execution" bugs. + +Drupal 4.5.3, 2005-06-01 +------------------------ +- fixed bugs, including a critical input validation bug. + +Drupal 4.5.2, 2005-01-15 +------------------------ +- fixed bugs: a cross-site scripting (XSS) vulnerability has been fixed. + +Drupal 4.5.1, 2004-12-01 +------------------------ +- fixed bugs: no critical bugs were identified. + +Drupal 4.5.0, 2004-10-18 +------------------------ +- navigation: + * made it possible to add, delete, rename and move menu items. + * introduced tabs and subtabs for local tasks. + * reorganized the navigation menus. +- user management: + * added support for multiple roles per user. + * made it possible to add custom profile fields. + * made it possible to browse user profiles by field. +- node system: + * added support for node-level permissions. +- comment module: + * made it possible to leave contact information without having to register. +- upload module: + * added support for uploading documents (includes images). +- forum module: + * added support for sticky forum topics. + * made it possible to track forum topics. +- syndication: + * added support for RSS ping-notifications of http://technorati.com/. + * refactored the categorization of syndicated news items. + * added an URL alias for 'rss.xml'. + * improved date parsing. +- database backend: + * added support for multiple database connections. + * the PostgreSQL backend does no longer require PEAR. +- theme system: + * changed all GIFs to PNGs. + * reorganised the handling of themes, template engines, templates and styles. + * unified and extended the available theme settings. + * added theme screenshots. +- blocks: + * added 'recent comments' block. + * added 'categories' block. +- blogger API: + * added support for auto-discovery of blogger API via RSD. +- performance: + * added support for sending gzip compressed pages. + * improved performance of the forum module. +- accessibility: + * improved the accessibility of the archive module's calendar. + * improved form handling and error reporting. + * added HTTP redirects to prevent submitting twice when refreshing right after a form submission. +- refactored 403 (forbidden) handling and added support for custom 403 pages. +- documentation: + * added PHPDoc/Doxygen comments. +- filter system: + * added support for using multiple input formats on the site + * expanded the embedded PHP-code feature so it can be used everywhere + * added support for role-dependant filtering, through input formats +- UI translation: + * managing translations is now completely done through the administration interface + * added support for importing/exporting gettext .po files + +Drupal 4.4.3, 2005-06-01 +------------------------ +- fixed bugs, including a critical input validation bug. + +Drupal 4.4.2, 2004-07-04 +------------------------ +- fixed bugs: no critical bugs were identified. + +Drupal 4.4.1, 2004-05-01 +------------------------ +- fixed bugs: no critical bugs were identified. + +Drupal 4.4.0, 2004-04-01 +------------------------ +- added support for the MetaWeblog API and MovableType extensions. +- added a file API: enables better document management. +- improved the watchdog and search module to log search keys. +- news aggregator: + * added support for conditional GET. + * added OPML feed subscription list. + * added support for , , , , and . +- comment module: + * made it possible to disable the "comment viewing controls". +- performance: + * improved module loading when serving cached pages. + * made it possible to automatically disable modules when under heavy load. + * made it possible to automatically disable blocks when under heavy load. + * improved performance and memory footprint of the locale module. +- theme system: + * made all theme functions start with 'theme_'. + * made all theme functions return their output. + * migrated away from using the BaseTheme class. + * added many new theme functions and refactored existing theme functions. + * added avatar support to 'Xtemplate'. + * replaced theme 'UnConeD' by 'Chameleon'. + * replaced theme 'Marvin' by 'Pushbutton'. +- usability: + * added breadcrumb navigation to all pages. + * made it possible to add context-sensitive help to all pages. + * replaced drop-down menus by radio buttons where appropriate. + * removed the 'magic_quotes_gpc = 0' requirement. + * added a 'book navigation' block. +- accessibility: + * made themes degrade gracefully in absence of CSS. + * grouped form elements using '
' and '' tags. + * added '
\n"; + +} + +/** + * Format a radio button. + * + * @param $element + * An associative array containing the properties of the element. + * Properties used: required, return_value, value, attributes, title, description + * @return + * A themed HTML string representing the form item group. + */ +function theme_radio($element) { + _form_set_class($element, array('form-radio')); + $output = ''; + if (!is_null($element['#title'])) { + $output = ''; + } + return theme('form_element', NULL, $output, $element['#description'], $element['#id'], $element['#required'], form_get_error($element)); +} + +/** + * Format a set of radio buttons. + * + * @param $element + * An associative array containing the properties of the element. + * Properties used: title, value, options, description, required and attributes. + * @return + * A themed HTML string representing the radio button set. + */ +function theme_radios($element) { + if ($element['#title'] || $element['#description']) { + return theme('form_element', $element['#title'], $element['#children'], $element['#description'], NULL, $element['#required'], form_get_error($element)); + } + else { + return $element['#children']; + } +} + +/** + * Format a password_confirm item. + * + * @param $element + * An associative array containing the properties of the element. + * Properties used: title, value, id, required, error. + * @return + * A themed HTML string representing the form item. + */ +function theme_password_confirm($element) { + return theme('form_element', $element['#title'], '
'. $element['#children']. '
', $element['#description'], $element['#id'], $element['#required'], form_get_error($element)); +} + +/* + * Expand a password_confirm field into two text boxes. + */ +function expand_password_confirm($element) { + $element['pass1'] = array('#type' => 'password', '#size' => 12, '#value' => $element['#value']['pass1']); + $element['pass2'] = array('#type' => 'password', '#size' => 12, '#value' => $element['#value']['pass2']); + $element['#validate'] = array('password_confirm_validate' => array()); + $element['#tree'] = TRUE; + + return $element; +} + +/** + * Validate password_confirm element. + */ +function password_confirm_validate($form) { + $pass1 = trim($form['pass1']['#value']); + if (!empty($pass1)) { + $pass2 = trim($form['pass2']['#value']); + if ($pass1 != $pass2) { + form_error($form, t('The specified passwords do not match.')); + } + } + elseif ($form['#required'] && !empty($_POST['edit'])) { + form_error($form, t('Password field is required.')); + } + + // Password field must be converted from a two-element array into a single + // string regardless of validation results. + form_set_value($form['pass1'], NULL); + form_set_value($form['pass2'], NULL); + form_set_value($form, $pass1); + + return $form; +} + +/** + * Format a date selection element. + * + * @param $element + * An associative array containing the properties of the element. + * Properties used: title, value, options, description, required and attributes. + * @return + * A themed HTML string representing the date selection boxes. + */ +function theme_date($element) { + $output = '
' . $element['#children'] . '
'; + return theme('form_element', $element['#title'], $output, $element['#description'], $element['#id'], $element['#required'], form_get_error($element)); +} + +/** + * Roll out a single date element. + */ +function expand_date($element) { + // Default to current date + if (!isset($element['#value'])) { + $element['#value'] = array('day' => format_date(time(), 'custom', 'j'), + 'month' => format_date(time(), 'custom', 'n'), + 'year' => format_date(time(), 'custom', 'Y')); + } + + $element['#tree'] = TRUE; + + // Determine the order of day, month, year in the site's chosen date format. + $format = variable_get('date_format_short', 'm/d/Y'); + $sort = array(); + $sort['day'] = max(strpos($format, 'd'), strpos($format, 'j')); + $sort['month'] = max(strpos($format, 'm'), strpos($format, 'M')); + $sort['year'] = strpos($format, 'Y'); + asort($sort); + $order = array_keys($sort); + + // Output multi-selector for date + foreach ($order as $type) { + switch ($type) { + case 'day': + $options = drupal_map_assoc(range(1, 31)); + break; + case 'month': + $options = drupal_map_assoc(range(1, 12), 'map_month'); + break; + case 'year': + $options = drupal_map_assoc(range(1900, 2050)); + break; + } + $parents = $element['#parents']; + $parents[] = $type; + $element[$type] = array( + '#type' => 'select', + '#value' => $element['#value'][$type], + '#attributes' => $element['#attributes'], + '#options' => $options, + ); + } + + return $element; +} + +/** + * Validates the FAPI date type to stop dates like 30/Feb/2006 + */ +function date_validate($form) { + if (!checkdate($form['#value']['month'], $form['#value']['day'], $form['#value']['year'])) { + form_error($form, t('The specified date is invalid.')); + } +} + +/** + * Helper function for usage with drupal_map_assoc to display month names. + */ +function map_month($month) { + return format_date(gmmktime(0, 0, 0, $month, 2, 1970), 'custom', 'M', 0); +} + +/** + * Helper function to load value from default value for checkboxes + */ +function checkboxes_value(&$form) { + $value = array(); + foreach ((array)$form['#default_value'] as $key) { + $value[$key] = 1; + } + $form['#value'] = $value; +} + +/** + * If no default value is set for weight select boxes, use 0. + */ +function weight_value(&$form) { + if (isset($form['#default_value'])) { + $form['#value'] = $form['#default_value']; + } + else { + $form['#value'] = 0; + } +} + +/** + * Roll out a single radios element to a list of radios, + * using the options array as index. + */ +function expand_radios($element) { + if (count($element['#options']) > 0) { + foreach ($element['#options'] as $key => $choice) { + if (!isset($element[$key])) { + $element[$key] = array('#type' => 'radio', '#title' => $choice, '#return_value' => $key, '#default_value' => $element['#default_value'], '#attributes' => $element['#attributes'], '#parents' => $element['#parents'], '#spawned' => TRUE); + } + } + } + return $element; +} + +/** + * Format a form item. + * + * @param $element + * An associative array containing the properties of the element. + * Properties used: title, value, description, required, error + * @return + * A themed HTML string representing the form item. + */ +function theme_item($element) { + return theme('form_element', $element['#title'], $element['#value'] . $element['#children'], $element['#description'], $element['#id'], $element['#required'], $element['#error']); +} + +/** + * Format a checkbox. + * + * @param $element + * An associative array containing the properties of the element. + * Properties used: title, value, return_value, description, required + * @return + * A themed HTML string representing the checkbox. + */ +function theme_checkbox($element) { + _form_set_class($element, array('form-checkbox')); + $checkbox = ''; + + if (!is_null($element['#title'])) { + $checkbox = ''; + } + + return theme('form_element', NULL, $checkbox, $element['#description'], $element['#id'], $element['#required'], form_get_error($element)); +} + +/** + * Format a set of checkboxes. + * + * @param $element + * An associative array containing the properties of the element. + * @return + * A themed HTML string representing the checkbox set. + */ +function theme_checkboxes($element) { + if ($element['#title'] || $element['#description']) { + return theme('form_element', $element['#title'], $element['#children'], $element['#description'], NULL, $element['#required'], form_get_error($element)); + } + else { + return $element['#children']; + } +} + +function expand_checkboxes($element) { + $value = is_array($element['#value']) ? $element['#value'] : array(); + $element['#tree'] = TRUE; + if (count($element['#options']) > 0) { + if (!isset($element['#default_value']) || $element['#default_value'] == 0) { + $element['#default_value'] = array(); + } + foreach ($element['#options'] as $key => $choice) { + if (!isset($element[$key])) { + $element[$key] = array('#type' => 'checkbox', '#processed' => TRUE, '#title' => $choice, '#return_value' => $key, '#default_value' => isset($value[$key]), '#attributes' => $element['#attributes']); + } + } + } + return $element; +} + +function theme_submit($element) { + return theme('button', $element); +} + +function theme_button($element) { + //Make sure not to overwrite classes + if (isset($element['#attributes']['class'])) { + $element['#attributes']['class'] = 'form-'. $element['#button_type'] .' '. $element['#attributes']['class']; + } + else { + $element['#attributes']['class'] = 'form-'. $element['#button_type']; + } + + return '\n"; +} + +/** + * Format a hidden form field. + * + * @param $element + * An associative array containing the properties of the element. + * Properties used: value, edit + * @return + * A themed HTML string representing the hidden form field. + */ +function theme_hidden($element) { + return '\n"; +} + +/** + * Format a textfield. + * + * @param $element + * An associative array containing the properties of the element. + * Properties used: title, value, description, size, maxlength, required, attributes autocomplete_path + * @return + * A themed HTML string representing the textfield. + */ +function theme_textfield($element) { + $size = $element['#size'] ? ' size="' . $element['#size'] . '"' : ''; + $class = array('form-text'); + $extra = ''; + if ($element['#autocomplete_path']) { + drupal_add_js('misc/autocomplete.js'); + $class[] = 'form-autocomplete'; + $extra = ''; + } + _form_set_class($element, $class); + $output = ''; + return theme('form_element', $element['#title'], $output, $element['#description'], $element['#id'], $element['#required'], form_get_error($element)). $extra; +} + +/** + * Format a form. + * + * @param $element + * An associative array containing the properties of the element. + * Properties used: action, method, attributes, children + * @return + * A themed HTML string representing the form. + */ +function theme_form($element) { + // Anonymous div to satisfy XHTML compliance. + $action = $element['#action'] ? 'action="' . check_url($element['#action']) . '" ' : ''; + return '
\n
". $element['#children'] ."\n
\n"; +} + +/** + * Format a textarea. + * + * @param $element + * An associative array containing the properties of the element. + * Properties used: title, value, description, rows, cols, required, attributes + * @return + * A themed HTML string representing the textarea. + */ +function theme_textarea($element) { + $class = array('form-textarea'); + if ($element['#resizable'] !== false) { + drupal_add_js('misc/textarea.js'); + $class[] = 'resizable'; + } + + $cols = $element['#cols'] ? ' cols="'. $element['#cols'] .'"' : ''; + _form_set_class($element, $class); + return theme('form_element', $element['#title'], ''. check_plain($element['#value']) .'', $element['#description'], $element['#id'], $element['#required'], form_get_error($element)); +} + +/** + * Format HTML markup for use in forms. + * + * This is used in more advanced forms, such as theme selection and filter format. + * + * @param $element + * An associative array containing the properties of the element. + * Properties used: prefix, value, children and suffix. + * @return + * A themed HTML string representing the HTML markup. + */ + +function theme_markup($element) { + return $element['#value'] . $element['#children']; +} + +/** +* Format a password field. +* +* @param $element +* An associative array containing the properties of the element. +* Properties used: title, value, description, size, maxlength, required, attributes +* @return +* A themed HTML string representing the form. +*/ +function theme_password($element) { + $size = $element['#size'] ? ' size="'. $element['#size'] .'" ' : ''; + + _form_set_class($element, array('form-text')); + $output = ''; + + return theme('form_element', $element['#title'], $output, $element['#description'], $element['#id'], $element['#required'], form_get_error($element)); +} + +/** + * Format a weight selection menu. + * + * @param $element + * An associative array containing the properties of the element. + * Properties used: title, delta, description + * @return + * A themed HTML string representing the form. + */ +function theme_weight($element) { + for ($n = (-1 * $element['#delta']); $n <= $element['#delta']; $n++) { + $weights[$n] = $n; + } + $element['#options'] = $weights; + $element['#type'] = 'select'; + + return form_render($element); +} + +/** + * Format a file upload field. + * + * @param $title + * The label for the file upload field. + * @param $name + * The internal name used to refer to the field. + * @param $size + * A measure of the visible size of the field (passed directly to HTML). + * @param $description + * Explanatory text to display after the form item. + * @param $required + * Whether the user must upload a file to the field. + * @return + * A themed HTML string representing the field. + * + * For assistance with handling the uploaded file correctly, see the API + * provided by file.inc. + */ +function theme_file($element) { + _form_set_class($element, array('form-file')); + return theme('form_element', $element['#title'], '\n", $element['#description'], $element['#id'], $element['#required'], form_get_error($element)); +} + +/** + * Sets a form element's class attribute. + * + * Adds 'required' and 'error' classes as needed. + * + * @param &$element + * The form element + * @param $name + * Array of new class names to be added + */ +function _form_set_class(&$element, $class = array()) { + if ($element['#required']) { + $class[] = 'required'; + } + if (form_get_error($element)){ + $class[] = 'error'; + } + if (isset($element['#attributes']['class'])) { + $class[] = $element['#attributes']['class']; + } + $element['#attributes']['class'] = implode(' ', $class); +} + +/** + * Remove invalid characters from an HTML ID attribute string. + * + * @param $id + * The ID to clean + * @return + * The cleaned ID + */ +function form_clean_id($id = NULL) { + $id = str_replace('][', '-', $id); + return $id; +} + +/** + * @} End of "defgroup form". + */ diff --git a/includes/image.inc b/includes/image.inc new file mode 100644 index 0000000..cebb648 --- /dev/null +++ b/includes/image.inc @@ -0,0 +1,303 @@ + descriptive title. + */ +function image_get_available_toolkits() { + $toolkits = file_scan_directory('includes', 'image\..*\.inc$'); + + $output = array(); + foreach ($toolkits as $file => $toolkit) { + include_once "./$file"; + $function = str_replace('.', '_', $toolkit->name) .'_info'; + if (function_exists($function)) { + $info = $function(); + $output[$info['name']] = $info['title']; + } + } + $output['gd'] = t('Built-in GD2 toolkit'); + return $output; +} + +/** + * Retrieve the name of the currently used toolkit. + * + * @return String containing the name of the toolkit. + */ +function image_get_toolkit() { + static $toolkit; + if (!$toolkit) { + $toolkit = variable_get('image_toolkit', 'gd'); + $toolkit_file = './includes/image.'.$toolkit.'.inc'; + if ($toolkit != 'gd' && file_exists($toolkit_file)) { + include_once $toolkit_file; + } + elseif (!image_gd_check_settings()) { + $toolkit = false; + } + } + + return $toolkit; +} + +/** + * Invokes the given method using the currently selected toolkit. + * + * @param $method A string containing the method to invoke. + * @param $params An optional array of parameters to pass to the toolkit method. + * + * @return Mixed values (typically Boolean for successful operation). + */ +function image_toolkit_invoke($method, $params = array()) { + if ($toolkit = image_get_toolkit()) { + $function = 'image_'. $toolkit .'_'. $method; + if (function_exists($function)) { + return call_user_func_array($function, $params); + } + else { + watchdog('php', t("The selected image handling toolkit '%toolkit' can not correctly process '%function'.", array('%toolkit' => "$toolkit", '%function' => "$function")), WATCHDOG_ERROR); + return false; + } + } + else { + if ($method == 'settings') { + return image_gd_settings(); + } + } +} + + +/** + * Get details about an image. + * + * @return array containing information about the image + * 'width': image's width in pixels + * 'height': image's height in pixels + * 'extension': commonly used extension for the image + * 'mime_type': image's MIME type ('image/jpeg', 'image/gif', etc.) + * 'file_size': image's physical size (in bytes) + */ +function image_get_info($file) { + if (!is_file($file)) { + return false; + } + + $details = false; + $data = @getimagesize($file); + $file_size = @filesize($file); + + if (isset($data) && is_array($data)) { + $extensions = array('1' => 'gif', '2' => 'jpg', '3' => 'png'); + $extension = array_key_exists($data[2], $extensions) ? $extensions[$data[2]] : ''; + $details = array('width' => $data[0], + 'height' => $data[1], + 'extension' => $extension, + 'file_size' => $file_size, + 'mime_type' => $data['mime']); + } + + return $details; +} + +/** + * Scales an image to the given width and height while maintaining aspect + * ratio. + * + * @param $source The filepath of the source image + * @param $destination The file path of the destination image + * @param $width The target width + * @param $height The target height + * + * @return True or false, based on success + */ +function image_scale($source, $destination, $width, $height) { + $info = image_get_info($source); + + // don't scale up + if ($width > $info['width'] && $height > $info['height']) { + return false; + } + + $aspect = $info['height'] / $info['width']; + if ($aspect < $height / $width) { + $width = (int)min($width, $info['width']); + $height = (int)round($width * $aspect); + } + else { + $height = (int)min($height, $info['height']); + $width = (int)round($height / $aspect); + } + + return image_toolkit_invoke('resize', array($source, $destination, $width, $height)); +} + +/** + * Resize an image to the given dimensions (ignoring aspect ratio). + * + * @param $source The filepath of the source image. + * @param $destination The file path of the destination image. + * @param $width The target width. + * @param $height The target height. + */ +function image_resize($source, $destination, $width, $height) { + return image_toolkit_invoke('resize', array($source, $destination, $width, $height)); +} + +/** + * Rotate an image by the given number of degrees. + * + * @param $source The filepath of the source image + * @param $destination The file path of the destination image + * @param $degrees The number of (clockwise) degrees to rotate the image + */ +function image_rotate($source, $destination, $degrees) { + return image_toolkit_invoke('rotate', array($source, $destination, $degrees)); +} + +/** + * Crop an image to the rectangle specified by the given rectangle. + * + * @param $source The filepath of the source image + * @param $destination The file path of the destination image + * @param $x The top left co-ordinate of the crop area (x axis value) + * @param $y The top left co-ordinate of the crop area (y axis value) + * @param $width The target width + * @param $height The target height + */ +function image_crop($source, $destination, $x, $y, $width, $height) { + return image_toolkit_invoke('crop', array($source, $destination, $x, $y, $width, $height)); +} + +/** + * GD2 toolkit functions + * With the minimal requirements of PHP 4.3 for Drupal, we use the built-in version of GD. + */ + +/** + * Retrieve settings for the GD2 toolkit (not used). + */ +function image_gd_settings() { + if (image_gd_check_settings()) { + return t('The built-in GD2 toolkit is installed and working properly.'); + } + else { + form_set_error('image_toolkit', t("The built-in GD image toolkit requires that the GD module for PHP be installed and configured properly. For more information see %url.", array('%url' => 'http://php.net/image'))); + return false; + } +} + +/** + * Verify GD2 settings (that the right version is actually installed). + * + * @return boolean + */ +function image_gd_check_settings() { + if ($check = get_extension_funcs('gd')) { + if (in_array('imagegd2', $check)) { + // GD2 support is available. + return true; + } + } + return false; +} + +/** + * Scale an image to the specified size using GD. + */ +function image_gd_resize($source, $destination, $width, $height) { + if (!file_exists($source)) { + return false; + } + + $info = image_get_info($source); + if (!$info) { + return false; + } + + $im = image_gd_open($source, $info['extension']); + if (!$im) { + return false; + } + + $res = imageCreateTrueColor($width, $height); + imageCopyResampled($res, $im, 0, 0, 0, 0, $width, $height, $info['width'], $info['height']); + $result = image_gd_close($res, $destination, $info['extension']); + + imageDestroy($res); + imageDestroy($im); + + return $result; +} + +/** + * Rotate an image the given number of degrees. + */ +function image_gd_rotate($source, $destination, $degrees, $bg_color = 0) { + if (!function_exists('imageRotate')) { + return false; + } + + $info = image_get_info($source); + if (!$info) { + return false; + } + + $im = image_gd_open($source, $info['extension']); + if (!$im) { + return false; + } + + $res = imageRotate($im, $degrees, $bg_color); + $result = image_gd_close($res, $destination, $info['extension']); + + return $result; +} + +/** + * Crop an image using the GD toolkit. + */ +function image_gd_crop($source, $destination, $x, $y, $width, $height) { + $info = image_get_info($source); + if (!$info) { + return false; + } + + $im = image_gd_open($source, $info['extension']); + $res = imageCreateTrueColor($width, $height); + imageCopy($res, $im, 0, 0, $x, $y, $width, $height); + $result = image_gd_close($res, $destination, $info['extension']); + + imageDestroy($res); + imageDestroy($im); + + return $result; +} + +/** + * GD helper function to create an image resource from a file. + */ +function image_gd_open($file, $extension) { + $extension = str_replace('jpg', 'jpeg', $extension); + $open_func = 'imageCreateFrom'. $extension; + if (!function_exists($open_func)) { + return false; + } + return $open_func($file); +} + +/** + * GD helper to write an image resource to a destination file. + */ +function image_gd_close($res, $destination, $extension) { + $extension = str_replace('jpg', 'jpeg', $extension); + $close_func = 'image'. $extension; + if (!function_exists($close_func)) { + return false; + } + return $close_func($res, $destination); +} + + diff --git a/includes/install.inc b/includes/install.inc new file mode 100644 index 0000000..7a12c31 --- /dev/null +++ b/includes/install.inc @@ -0,0 +1,81 @@ +name] = $row->schema_version; + } + } + + return $versions[$module]; +} + +/** + * Update the installed version information for a module. + * + * @param $module + * A module name. + * @param $version + * The new schema version. + */ +function drupal_set_installed_schema_version($module, $version) { + db_query("UPDATE {system} SET schema_version = %d WHERE name = '%s'", $version, $module); +} diff --git a/includes/locale.inc b/includes/locale.inc new file mode 100644 index 0000000..5c6d83e --- /dev/null +++ b/includes/locale.inc @@ -0,0 +1,1548 @@ +lid, $code); + } + + // If only the language was added, and not a PO file import triggered + // the language addition, we need to inform the user on how to start + // a translation + if ($onlylanguage) { + drupal_set_message(t('The language %locale has been created, and can now be used to import a translation. More information is available in the help screen.', array('%locale' => theme('placeholder', t($name)), '%locale-help' => url('admin/help/locale')))); + } + else { + drupal_set_message(t('The language %locale has been created.', array('%locale' => theme('placeholder', t($name))))); + } + + watchdog('locale', t('The %language language (%locale) has been created.', array('%language' => theme('placeholder', $name), '%locale' => theme('placeholder', $code)))); +} + +/** + * User interface for the language management screen. + */ +function _locale_admin_manage_screen() { + $languages = locale_supported_languages(TRUE, TRUE); + + $options = array(); + $form['name'] = array('#tree' => TRUE); + foreach ($languages['name'] as $key => $lang) { + $options[$key] = ''; + $status = db_fetch_object(db_query("SELECT isdefault, enabled FROM {locales_meta} WHERE locale = '%s'", $key)); + if ($status->enabled) { + $enabled[] = $key; + } + if ($status->isdefault) { + $isdefault = $key; + } + if ($key == 'en') { + $form['name']['en'] = array('#value' => check_plain($lang)); + } + else { + $original = db_fetch_object(db_query("SELECT COUNT(*) AS strings FROM {locales_source}")); + $translation = db_fetch_object(db_query("SELECT COUNT(*) AS translation FROM {locales_target} WHERE locale = '%s' AND translation != ''", $key)); + + $ratio = ($original->strings > 0 && $translation->translation > 0) ? round(($translation->translation/$original->strings)*100., 2) : 0; + + $form['name'][$key] = array('#type' => 'textfield', + '#default_value' => $lang, + '#size' => 15, + '#maxlength' => 64, + ); + $form['translation'][$key] = array('#value' => "$translation->translation/$original->strings ($ratio%)"); + } + } + $form['enabled'] = array('#type' => 'checkboxes', + '#options' => $options, + '#default_value' => $enabled, + ); + $form['site_default'] = array('#type' => 'radios', + '#options' => $options, + '#default_value' => $isdefault, + ); + $form['submit'] = array('#type' => 'submit', '#value' => t('Save configuration')); + + return drupal_get_form('_locale_admin_manage_screen', $form, 'locale_admin_manage_screen'); +} + +/** + * Theme the locale admin manager form. + */ +function theme_locale_admin_manage_screen($form) { + foreach ($form['name'] as $key => $element) { + // Do not take form control structures. + if (is_array($element) && element_child($key)) { + $rows[] = array(check_plain($key), form_render($form['name'][$key]), form_render($form['enabled'][$key]), form_render($form['site_default'][$key]), ($key != 'en' ? form_render($form['translation'][$key]) : message_na()), ($key != 'en' ? l(t('delete'), 'admin/locale/language/delete/'. $key) : '')); + } + } + $header = array(array('data' => t('Code')), array('data' => t('English name')), array('data' => t('Enabled')), array('data' => t('Default')), array('data' => t('Translated')), array('data' => t('Operations'))); + $output = theme('table', $header, $rows); + $output .= form_render($form); + + return $output; +} + +/** + * Process locale admin manager form submissions. + */ +function _locale_admin_manage_screen_submit($form_id, $form_values) { + // Save changes to existing languages. + $languages = locale_supported_languages(FALSE, TRUE); + foreach($languages['name'] as $key => $value) { + if ($form_values['site_default'] == $key) { + $form_values['enabled'][$key] = 1; // autoenable the default language + } + $enabled = $form_values['enabled'][$key] ? 1 : 0; + if ($key == 'en') { + // Disallow name change for English locale. + db_query("UPDATE {locales_meta} SET isdefault = %d, enabled = %d WHERE locale = 'en'", ($form_values['site_default'] == $key), $enabled); + } + else { + db_query("UPDATE {locales_meta} SET name = '%s', isdefault = %d, enabled = %d WHERE locale = '%s'", $form_values['name'][$key], ($form_values['site_default'] == $key), $enabled, $key); + } + } + drupal_set_message(t('Configuration saved.')); + + // Changing the locale settings impacts the interface: + cache_clear_all(); + + return 'admin/locale/language/overview'; +} + +/** + * User interface for the language addition screen. + */ +function _locale_admin_manage_add_screen() { + $isocodes = _locale_prepare_iso_list(); + + $form = array(); + $form['language list'] = array('#type' => 'fieldset', + '#title' => t('Language list'), + '#collapsible' => TRUE, + ); + $form['language list']['langcode'] = array('#type' => 'select', + '#title' => t('Language name'), + '#default_value' => key($isocodes), + '#options' => $isocodes, + '#description' => t('Select your language here, or add it below, if you are unable to find it.'), + ); + $form['language list']['submit'] = array('#type' => 'submit', '#value' => t('Add language')); + + $output = drupal_get_form('locale_add_language_form', $form); + + $form = array(); + $form['custom language'] = array('#type' => 'fieldset', + '#title' => t('Custom language'), + '#collapsible' => TRUE, + ); + $form['custom language']['langcode'] = array('#type' => 'textfield', + '#title' => t('Language code'), + '#size' => 12, + '#maxlength' => 60, + '#required' => TRUE, + '#description' => t("Commonly this is an ISO 639 language code with an optional country code for regional variants. Examples include 'en', 'en-US' and 'zh-cn'.", array('%iso-codes' => 'http://www.w3.org/WAI/ER/IG/ert/iso639.htm')), + ); + $form['custom language']['langname'] = array('#type' => 'textfield', + '#title' => t('Language name in English'), + '#maxlength' => 64, + '#required' => TRUE, + '#description' => t('Name of the language. Will be available for translation in all languages.'), + ); + $form['custom language']['submit'] = array('#type' => 'submit', '#value' => t('Add custom language')); + + // Use the validation and submit functions of the add language form. + $output .= drupal_get_form('locale_custom_language_form', $form, 'locale_add_language_form'); + + return $output; +} + +/** + * Validate the language addition form. + */ +function locale_add_language_form_validate($form_id, $form_values) { + if ($duplicate = db_num_rows(db_query("SELECT locale FROM {locales_meta} WHERE locale = '%s'", $form_values['langcode'])) != 0) { + form_set_error(t('The language %language (%code) already exists.', array('%language' => theme('placeholder', check_plain($form_values['langname'])), '%code' => theme('placeholder', $form_values['langcode'])))); + } + + if (!isset($form_values['langname'])) { + $isocodes = _locale_get_iso639_list(); + if (!isset($isocodes[$form_values['langcode']])) { + form_set_error('langcode', t('Invalid language code.')); + } + } +} + +/** + * Process the language addition form submission. + */ +function locale_add_language_form_submit($form_id, $form_values) { + if (isset($form_values['langname'])) { + // Custom language form. + _locale_add_language($form_values['langcode'], $form_values['langname']); + } + else { + $isocodes = _locale_get_iso639_list(); + _locale_add_language($form_values['langcode'], $isocodes[$form_values['langcode']][0]); + } + + return 'admin/locale'; +} + +/** + * User interface for the translation import screen. + */ +function _locale_admin_import_screen() { + $languages = locale_supported_languages(FALSE, TRUE); + $languages = array_map('t', $languages['name']); + unset($languages['en']); + + if (!count($languages)) { + $languages = _locale_prepare_iso_list(); + } + else { + $languages = array( + t('Already added languages') => $languages, + t('Languages not yet added') => _locale_prepare_iso_list() + ); + } + + $form = array(); + $form['import'] = array('#type' => 'fieldset', + '#title' => t('Import translation'), + ); + $form['import']['file'] = array('#type' => 'file', + '#title' => t('Language file'), + '#size' => 50, + '#description' => t('A gettext Portable Object (.po) file.'), + ); + $form['import']['langcode'] = array('#type' => 'select', + '#title' => t('Import into'), + '#options' => $languages, + '#description' => t('Choose the language you want to add strings into. If you choose a language which is not yet set up, then it will be added.'), + ); + $form['import']['mode'] = array('#type' => 'radios', + '#title' => t('Mode'), + '#default_value' => 'overwrite', + '#options' => array('overwrite' => t('Strings in the uploaded file replace existing ones, new ones are added'), 'keep' => t('Existing strings are kept, only new strings are added')), + ); + $form['import']['submit'] = array('#type' => 'submit', '#value' => t('Import')); + $form['#attributes']['enctype'] = 'multipart/form-data'; + + return drupal_get_form('_locale_admin_import', $form); +} + +/** + * Process the locale import form submission. + */ +function _locale_admin_import_submit($form_id, $form_values) { + // Add language, if not yet supported + $languages = locale_supported_languages(TRUE, TRUE); + if (!isset($languages['name'][$form_values['langcode']])) { + $isocodes = _locale_get_iso639_list(); + _locale_add_language($form_values['langcode'], $isocodes[$form_values['langcode']][0], FALSE); + } + + // Now import strings into the language + $file = file_check_upload('file'); + if ($ret = _locale_import_po($file, $form_values['langcode'], $form_values['mode']) == FALSE) { + $message = t('The translation import of %filename failed.', array('%filename' => theme('placeholder', $file->filename))); + drupal_set_message($message, 'error'); + watchdog('locale', $message, WATCHDOG_ERROR); + } + + return 'admin/locale'; +} + +/** + * User interface for the translation export screen + */ +function _locale_admin_export_screen() { + $languages = locale_supported_languages(FALSE, TRUE); + $languages = array_map('t', $languages['name']); + unset($languages['en']); + + // Offer language specific export if any language is set up + if (count($languages)) { + $form = array(); + $form['export'] = array('#type' => 'fieldset', + '#title' => t('Export translation'), + '#collapsible' => TRUE, + ); + $form['export']['langcode'] = array('#type' => 'select', + '#title' => t('Language name'), + '#options' => $languages, + '#description' => t('Select the language you would like to export in gettext Portable Object (.po) format.'), + ); + $form['export']['submit'] = array('#type' => 'submit', '#value' => t('Export')); + $output = drupal_get_form('_locale_export_po', $form); + } + + // Complete template export of the strings + $form = array(); + $form['export'] = array('#type' => 'fieldset', + '#title' => t('Export template'), + '#collapsible' => TRUE, + '#description' => t('Generate a gettext Portable Object Template (.pot) file with all the interface strings from the Drupal locale database.'), + ); + $form['export']['submit'] = array('#type' => 'submit', '#value' => t('Export')); + $output .= drupal_get_form('_locale_export_pot', $form, '_locale_export_po'); + + return $output; +} + +/** + * Process a locale export form submissions. + */ +function _locale_export_po_submit($form_id, $form_values) { + _locale_export_po($form_values['langcode']); +} + +/** + * User interface for the string search screen + */ +function _locale_string_seek_form() { + // Get *all* languages set up + $languages = locale_supported_languages(FALSE, TRUE); + asort($languages['name']); unset($languages['name']['en']); + $languages['name'] = array_map('check_plain', $languages['name']); + + // Present edit form preserving previous user settings + $query = _locale_string_seek_query(); + $form = array(); + $form['search'] = array('#type' => 'fieldset', + '#title' => t('Search'), + ); + $form['search']['string'] = array('#type' => 'textfield', + '#title' => t('Strings to search for'), + '#default_value' => $query->string, + '#size' => 30, + '#maxlength' => 30, + '#description' => t('Leave blank to show all strings. The search is case sensitive.'), + ); + $form['search']['language'] = array('#type' => 'radios', + '#title' => t('Language'), + '#default_value' => ($query->language ? $query->language : 'all'), + '#options' => array_merge(array('all' => t('All languages'), 'en' => t('English (provided by Drupal)')), $languages['name']), + ); + $form['search']['searchin'] = array('#type' => 'radios', + '#title' => t('Search in'), + '#default_value' => ($query->searchin ? $query->searchin : 'all'), + '#options' => array('all' => t('All strings in that language'), 'translated' => t('Only translated strings'), 'untranslated' => t('Only untranslated strings')), + ); + $form['search']['submit'] = array('#type' => 'submit', '#value' => t('Search')); + $form['#redirect'] = FALSE; + + return drupal_get_form('_locale_string_seek', $form); +} + +/** + * User interface for string editing. + */ +function _locale_string_edit($lid) { + $languages = locale_supported_languages(FALSE, TRUE); + unset($languages['name']['en']); + + $result = db_query('SELECT DISTINCT s.source, t.translation, t.locale FROM {locales_source} s INNER JOIN {locales_target} t ON s.lid = t.lid WHERE s.lid = %d', $lid); + $form = array(); + $form['translations'] = array('#tree' => TRUE); + while ($translation = db_fetch_object($result)) { + $orig = $translation->source; + + // Approximate the number of rows in a textfield with a maximum of 10. + $rows = min(ceil(str_word_count($orig) / 12), 10); + + $form['translations'][$translation->locale] = array( + '#type' => 'textarea', + '#title' => $languages['name'][$translation->locale], + '#default_value' => $translation->translation, + '#rows' => $rows, + ); + unset($languages['name'][$translation->locale]); + } + + // Handle erroneous lid. + if (!isset($orig)){ + drupal_set_message(t('String not found.')); + drupal_goto('admin/locale/string/search'); + } + + // Add original text. Assign negative weight so that it floats to the top. + $form['item'] = array('#type' => 'item', + '#title' => t('Original text'), + '#value' => check_plain(wordwrap($orig, 0)), + '#weight' => -1, + ); + + foreach ($languages['name'] as $key => $lang) { + $form['translations'][$key] = array( + '#type' => 'textarea', + '#title' => $lang, + '#rows' => $rows, + ); + } + + $form['lid'] = array('#type' => 'value', '#value' => $lid); + $form['submit'] = array('#type' => 'submit', '#value' => t('Save translations')); + + return drupal_get_form('_locale_string_edit', $form); +} + +/** + * Process string editing form submissions. + * Saves all translations of one string submitted from a form. + */ +function _locale_string_edit_submit($form_id, $form_values) { + $lid = $form_values['lid']; + foreach ($form_values['translations'] as $key => $value) { + $value = filter_xss_admin($value); + $trans = db_fetch_object(db_query("SELECT translation FROM {locales_target} WHERE lid = %d AND locale = '%s'", $lid, $key)); + if (isset($trans->translation)) { + db_query("UPDATE {locales_target} SET translation = '%s' WHERE lid = %d AND locale = '%s'", $value, $lid, $key); + } + else { + db_query("INSERT INTO {locales_target} (lid, translation, locale) VALUES (%d, '%s', '%s')", $lid, $value, $key); + } + } + drupal_set_message(t('The string has been saved.')); + + // Refresh the locale cache. + locale_refresh_cache(); + // Rebuild the menu, strings may have changed. + menu_rebuild(); + + return 'admin/locale/string/search'; +} + +/** + * Delete a language string. + */ +function _locale_string_delete($lid) { + db_query('DELETE FROM {locales_source} WHERE lid = %d', $lid); + db_query('DELETE FROM {locales_target} WHERE lid = %d', $lid); + locale_refresh_cache(); + drupal_set_message(t('The string has been removed.')); + + drupal_goto('admin/locale/string/search'); +} + +/** + * Parses Gettext Portable Object file information and inserts into database + * + * @param $file Object contains properties of local file to be imported + * @param $lang Language code + * @param $mode should existing translations be replaced? + */ +function _locale_import_po($file, $lang, $mode) { + // If not in 'safe mode', increase the maximum execution time: + if (!ini_get('safe_mode')) { + set_time_limit(240); + } + + // Check if we have the language already in the database + if (!db_fetch_object(db_query("SELECT locale FROM {locales_meta} WHERE locale = '%s'", $lang))) { + drupal_set_message(t('The language selected for import is not supported.'), 'error'); + return FALSE; + } + + // Get strings from file (returns on failure after a partial import, or on success) + $status = _locale_import_read_po($file, $mode, $lang); + if ($status === FALSE) { + // error messages are set in _locale_import_read_po + return FALSE; + } + + // Get status information on import process + list($headerdone, $additions, $updates) = _locale_import_one_string('report', $mode); + + if (!$headerdone) { + drupal_set_message(t('The translation file %filename appears to have a missing or malformed header.', array('%filename' => theme('placeholder', $file->filename))), 'error'); + } + + // rebuild locale cache + cache_clear_all("locale:$lang"); + + // rebuild the menu, strings may have changed + menu_rebuild(); + + drupal_set_message(t('The translation was successfully imported. There are %number newly created translated strings and %update strings were updated.', array('%number' => $additions, '%update' => $updates))); + watchdog('locale', t('Imported %file into %locale: %number new strings added and %update updated.', array('%file' => theme('placeholder', $file->filename), '%locale' => theme('placeholder', $lang), '%number' => $additions, '%update' => $updates))); + return TRUE; +} + +/** + * Parses Gettext Portable Object file into an array + * + * @param $file Object with properties of local file to parse + * @author Jacobo Tarrio + */ +function _locale_import_read_po($file, $mode, $lang) { + + $message = theme('placeholder', $file->filename); + $fd = fopen($file->filepath, "rb"); // File will get closed by PHP on return + if (!$fd) { + drupal_set_message(t('The translation import failed, because the file %filename could not be read.', array('%filename' => $message)), 'error'); + return FALSE; + } + + $context = "COMMENT"; // Parser context: COMMENT, MSGID, MSGID_PLURAL, MSGSTR and MSGSTR_ARR + $current = array(); // Current entry being read + $plural = 0; // Current plural form + $lineno = 0; // Current line + + while (!feof($fd)) { + $line = fgets($fd, 10*1024); // A line should not be this long + $lineno++; + $line = trim(strtr($line, array("\\\n" => ""))); + + if (!strncmp("#", $line, 1)) { // A comment + if ($context == "COMMENT") { // Already in comment context: add + $current["#"][] = substr($line, 1); + } + elseif (($context == "MSGSTR") || ($context == "MSGSTR_ARR")) { // End current entry, start a new one + _locale_import_one_string($current, $mode, $lang); + $current = array(); + $current["#"][] = substr($line, 1); + $context = "COMMENT"; + } + else { // Parse error + drupal_set_message(t('The translation file %filename contains an error: "msgstr" was expected but not found on line %line.', array('%filename' => $message, '%line' => $lineno)), 'error'); + return FALSE; + } + } + elseif (!strncmp("msgid_plural", $line, 12)) { + if ($context != "MSGID") { // Must be plural form for current entry + drupal_set_message(t('The translation file %filename contains an error: "msgid_plural" was expected but not found on line %line.', array('%filename' => $message, '%line' => $lineno)), 'error'); + return FALSE; + } + $line = trim(substr($line, 12)); + $quoted = _locale_import_parse_quoted($line); + if ($quoted === false) { + drupal_set_message(t('The translation file %filename contains a syntax error on line %line.', array('%filename' => $message, '%line' => $lineno)), 'error'); + return FALSE; + } + $current["msgid"] = $current["msgid"] ."\0". $quoted; + $context = "MSGID_PLURAL"; + } + elseif (!strncmp("msgid", $line, 5)) { + if ($context == "MSGSTR") { // End current entry, start a new one + _locale_import_one_string($current, $mode, $lang); + $current = array(); + } + elseif ($context == "MSGID") { // Already in this context? Parse error + drupal_set_message(t('The translation file %filename contains an error: "msgid" is unexpected on line %line.', array('%filename' => $message, '%line' => $lineno)), 'error'); + return FALSE; + } + $line = trim(substr($line, 5)); + $quoted = _locale_import_parse_quoted($line); + if ($quoted === false) { + drupal_set_message(t('The translation file %filename contains a syntax error on line %line.', array('%filename' => $message, '%line' => $lineno)), 'error'); + return FALSE; + } + $current["msgid"] = $quoted; + $context = "MSGID"; + } + elseif (!strncmp("msgstr[", $line, 7)) { + if (($context != "MSGID") && ($context != "MSGID_PLURAL") && ($context != "MSGSTR_ARR")) { // Must come after msgid, msgid_plural, or msgstr[] + drupal_set_message(t('The translation file %filename contains an error: "msgstr[]" is unexpected on line %line.', array('%filename' => $message, '%line' => $lineno)), 'error'); + return FALSE; + } + if (strpos($line, "]") === false) { + drupal_set_message(t('The translation file %filename contains a syntax error on line %line.', array('%filename' => $message, '%line' => $lineno)), 'error'); + return FALSE; + } + $frombracket = strstr($line, "["); + $plural = substr($frombracket, 1, strpos($frombracket, "]") - 1); + $line = trim(strstr($line, " ")); + $quoted = _locale_import_parse_quoted($line); + if ($quoted === false) { + drupal_set_message(t('The translation file %filename contains a syntax error on line %line.', array('%filename' => $message, '%line' => $lineno)), 'error'); + return FALSE; + } + $current["msgstr"][$plural] = $quoted; + $context = "MSGSTR_ARR"; + } + elseif (!strncmp("msgstr", $line, 6)) { + if ($context != "MSGID") { // Should come just after a msgid block + drupal_set_message(t('The translation file %filename contains an error: "msgstr" is unexpected on line %line.', array('%filename' => $message, '%line' => $lineno)), 'error'); + return FALSE; + } + $line = trim(substr($line, 6)); + $quoted = _locale_import_parse_quoted($line); + if ($quoted === false) { + drupal_set_message(t('The translation file %filename contains a syntax error on line %line.', array('%filename' => $message, '%line' => $lineno)), 'error'); + return FALSE; + } + $current["msgstr"] = $quoted; + $context = "MSGSTR"; + } + elseif ($line != "") { + $quoted = _locale_import_parse_quoted($line); + if ($quoted === false) { + drupal_set_message(t('The translation file %filename contains a syntax error on line %line.', array('%filename' => $message, '%line' => $lineno)), 'error'); + return FALSE; + } + if (($context == "MSGID") || ($context == "MSGID_PLURAL")) { + $current["msgid"] .= $quoted; + } + elseif ($context == "MSGSTR") { + $current["msgstr"] .= $quoted; + } + elseif ($context == "MSGSTR_ARR") { + $current["msgstr"][$plural] .= $quoted; + } + else { + drupal_set_message(t('The translation file %filename contains an error: there is an unexpected string on line %line.', array('%filename' => $message, '%line' => $lineno)), 'error'); + return FALSE; + } + } + } + + // End of PO file, flush last entry + if (($context == "MSGSTR") || ($context == "MSGSTR_ARR")) { + _locale_import_one_string($current, $mode, $lang); + } + elseif ($context != "COMMENT") { + drupal_set_message(t('The translation file %filename ended unexpectedly at line %line.', array('%filename' => $message, '%line' => $lineno)), 'error'); + return FALSE; + } + +} + +/** + * Imports a string into the database + * + * @param $value Information about the string + * @author Jacobo Tarrio + */ +function _locale_import_one_string($value, $mode, $lang = NULL) { + static $additions = 0; + static $updates = 0; + static $headerdone = FALSE; + + // Report the changes made (called at end of import) + if ($value == 'report') { + return array($headerdone, $additions, $updates); + } + // Current string is the header information + elseif ($value['msgid'] == '') { + $hdr = _locale_import_parse_header($value['msgstr']); + + // Get the plural formula + if ($hdr["Plural-Forms"] && $p = _locale_import_parse_plural_forms($hdr["Plural-Forms"], $file->filename)) { + list($nplurals, $plural) = $p; + db_query("UPDATE {locales_meta} SET plurals = %d, formula = '%s' WHERE locale = '%s'", $nplurals, $plural, $lang); + } + else { + db_query("UPDATE {locales_meta} SET plurals = %d, formula = '%s' WHERE locale = '%s'", 0, '', $lang); + } + $headerdone = TRUE; + } + // Some real string to import + else { + $comments = filter_xss_admin(_locale_import_shorten_comments($value['#'])); + + // Handle a translation for some plural string + if (strpos($value['msgid'], "\0")) { + $english = explode("\0", $value['msgid'], 2); + $entries = array_keys($value['msgstr']); + for ($i = 3; $i <= count($entries); $i++) { + $english[] = $english[1]; + } + $translation = array_map('_locale_import_append_plural', $value['msgstr'], $entries); + $english = array_map('_locale_import_append_plural', $english, $entries); + foreach ($translation as $key => $trans) { + if ($key == 0) { + $plid = 0; + } + $loc = db_fetch_object(db_query("SELECT lid FROM {locales_source} WHERE source = '%s'", $english[$key])); + if ($loc->lid) { // a string exists + $lid = $loc->lid; + // update location field + db_query("UPDATE {locales_source} SET location = '%s' WHERE lid = %d", $comments, $lid); + $trans2 = db_fetch_object(db_query("SELECT lid, translation, plid, plural FROM {locales_target} WHERE lid = %d AND locale = '%s'", $lid, $lang)); + if (!$trans2->lid) { // no translation in current language + db_query("INSERT INTO {locales_target} (lid, locale, translation, plid, plural) VALUES (%d, '%s', '%s', %d, %d)", $lid, $lang, filter_xss_admin($trans), $plid, $key); + $additions++; + } // translation exists + else if ($mode == 'overwrite' || $trans2->translation == '') { + db_query("UPDATE {locales_target} SET translation = '%s', plid = %d, plural = %d WHERE locale = '%s' AND lid = %d", filter_xss_admin($trans), $plid, $key, $lang, $lid); + if ($trans2->translation == '') { + $additions++; + } + else { + $updates++; + } + } + } + else { // no string + db_query("INSERT INTO {locales_source} (location, source) VALUES ('%s', '%s')", $comments, filter_xss_admin($english[$key])); + $loc = db_fetch_object(db_query("SELECT lid FROM {locales_source} WHERE source = '%s'", $english[$key])); + $lid = $loc->lid; + db_query("INSERT INTO {locales_target} (lid, locale, translation, plid, plural) VALUES (%d, '%s', '%s', %d, %d)", $lid, $lang, filter_xss_admin($trans), $plid, $key); + if ($trans != '') { + $additions++; + } + } + $plid = $lid; + } + } + + // A simple translation + else { + $english = $value['msgid']; + $translation = $value['msgstr']; + $loc = db_fetch_object(db_query("SELECT lid FROM {locales_source} WHERE source = '%s'", $english)); + if ($loc->lid) { // a string exists + $lid = $loc->lid; + // update location field + db_query("UPDATE {locales_source} SET location = '%s' WHERE source = '%s'", $comments, $english); + $trans = db_fetch_object(db_query("SELECT lid, translation FROM {locales_target} WHERE lid = %d AND locale = '%s'", $lid, $lang)); + if (!$trans->lid) { // no translation in current language + db_query("INSERT INTO {locales_target} (lid, locale, translation) VALUES (%d, '%s', '%s')", $lid, $lang, filter_xss_admin($translation)); + $additions++; + } // translation exists + else if ($mode == 'overwrite') { //overwrite in any case + db_query("UPDATE {locales_target} SET translation = '%s' WHERE locale = '%s' AND lid = %d", filter_xss_admin($translation), $lang, $lid); + if ($trans->translation == '') { + $additions++; + } + else { + $updates++; + } + } // overwrite if empty string + else if ($trans->translation == '') { + db_query("UPDATE {locales_target} SET translation = '%s' WHERE locale = '%s' AND lid = %d", $translation, $lang, $lid); + $additions++; + } + } + else { // no string + db_query("INSERT INTO {locales_source} (location, source) VALUES ('%s', '%s')", $comments, $english); + $loc = db_fetch_object(db_query("SELECT lid FROM {locales_source} WHERE source = '%s'", $english)); + $lid = $loc->lid; + db_query("INSERT INTO {locales_target} (lid, locale, translation) VALUES (%d, '%s', '%s')", $lid, $lang, filter_xss_admin($translation)); + if ($translation != '') { + $additions++; + } + } + } + } +} + +/** + * Parses a Gettext Portable Object file header + * + * @param $header A string containing the complete header + * @return An associative array of key-value pairs + * @author Jacobo Tarrio + */ +function _locale_import_parse_header($header) { + $hdr = array(); + + $lines = explode("\n", $header); + foreach ($lines as $line) { + $line = trim($line); + if ($line) { + list($tag, $contents) = explode(":", $line, 2); + $hdr[trim($tag)] = trim($contents); + } + } + + return $hdr; +} + +/** + * Parses a Plural-Forms entry from a Gettext Portable Object file header + * + * @param $pluralforms A string containing the Plural-Forms entry + * @param $filename A string containing the filename + * @return An array containing the number of plurals and a + * formula in PHP for computing the plural form + * @author Jacobo Tarrio + */ +function _locale_import_parse_plural_forms($pluralforms, $filename) { + // First, delete all whitespace + $pluralforms = strtr($pluralforms, array(" " => "", "\t" => "")); + + // Select the parts that define nplurals and plural + $nplurals = strstr($pluralforms, "nplurals="); + if (strpos($nplurals, ";")) { + $nplurals = substr($nplurals, 9, strpos($nplurals, ";") - 9); + } + else { + return FALSE; + } + $plural = strstr($pluralforms, "plural="); + if (strpos($plural, ";")) { + $plural = substr($plural, 7, strpos($plural, ";") - 7); + } + else { + return FALSE; + } + + // Get PHP version of the plural formula + $plural = _locale_import_parse_arithmetic($plural); + + if ($plural !== FALSE) { + return array($nplurals, $plural); + } + else { + drupal_set_message(t('The translation file %filename contains an error: the plural formula could not be parsed.', array('%filename' => theme('placeholder', $filename))), 'error'); + return FALSE; + } +} + +/** + * Parses and sanitizes an arithmetic formula into a PHP expression + * + * While parsing, we ensure, that the operators have the right + * precedence and associativity. + * + * @param $string A string containing the arithmetic formula + * @return The PHP version of the formula + * @author Jacobo Tarrio + */ +function _locale_import_parse_arithmetic($string) { + // Operator precedence table + $prec = array("(" => -1, ")" => -1, "?" => 1, ":" => 1, "||" => 3, "&&" => 4, "==" => 5, "!=" => 5, "<" => 6, ">" => 6, "<=" => 6, ">=" => 6, "+" => 7, "-" => 7, "*" => 8, "/" => 8, "%" => 8); + // Right associativity + $rasc = array("?" => 1, ":" => 1); + + $tokens = _locale_import_tokenize_formula($string); + + // Parse by converting into infix notation then back into postfix + $opstk = array(); + $elstk = array(); + + foreach ($tokens as $token) { + $ctok = $token; + + // Numbers and the $n variable are simply pushed into $elarr + if (is_numeric($token)) { + $elstk[] = $ctok; + } + elseif ($ctok == "n") { + $elstk[] = '$n'; + } + elseif ($ctok == "(") { + $opstk[] = $ctok; + } + elseif ($ctok == ")") { + $topop = array_pop($opstk); + while (($topop != NULL) && ($topop != "(")) { + $elstk[] = $topop; + $topop = array_pop($opstk); + } + } + elseif ($prec[$ctok]) { + // If it's an operator, then pop from $oparr into $elarr until the + // precedence in $oparr is less than current, then push into $oparr + $topop = array_pop($opstk); + while (($topop != NULL) && ($prec[$topop] >= $prec[$ctok]) && !(($prec[$topop] == $prec[$ctok]) && $rasc[$topop] && $rasc[$ctok])) { + $elstk[] = $topop; + $topop = array_pop($opstk); + } + if ($topop) { + $opstk[] = $topop; // Return element to top + } + $opstk[] = $ctok; // Parentheses are not needed + } + else { + return false; + } + } + + // Flush operator stack + $topop = array_pop($opstk); + while ($topop != NULL) { + $elstk[] = $topop; + $topop = array_pop($opstk); + } + + // Now extract formula from stack + $prevsize = count($elstk) + 1; + while (count($elstk) < $prevsize) { + $prevsize = count($elstk); + for ($i = 2; $i < count($elstk); $i++) { + $op = $elstk[$i]; + if ($prec[$op]) { + $f = ""; + if ($op == ":") { + $f = $elstk[$i - 2] ."):". $elstk[$i - 1] .")"; + } + elseif ($op == "?") { + $f = "(". $elstk[$i - 2] ."?(". $elstk[$i - 1]; + } + else { + $f = "(". $elstk[$i - 2] . $op . $elstk[$i - 1] .")"; + } + array_splice($elstk, $i - 2, 3, $f); + break; + } + } + } + + // If only one element is left, the number of operators is appropriate + if (count($elstk) == 1) { + return $elstk[0]; + } + else { + return FALSE; + } +} + +/** + * Backward compatible implementation of token_get_all() for formula parsing + * + * @param $string A string containing the arithmetic formula + * @return The PHP version of the formula + * @author Gerhard Killesreiter + */ +function _locale_import_tokenize_formula($formula) { + $formula = str_replace(" ", "", $formula); + $tokens = array(); + for ($i = 0; $i < strlen($formula); $i++) { + if (is_numeric($formula[$i])) { + $num = $formula[$i]; + $j = $i + 1; + while($j < strlen($formula) && is_numeric($formula[$j])) { + $num .= $formula[$j]; + $j++; + } + $i = $j - 1; + $tokens[] = $num; + } + elseif ($pos = strpos(" =<>!&|", $formula[$i])) { // We won't have a space + $next = $formula[$i + 1]; + switch ($pos) { + case 1: + case 2: + case 3: + case 4: + if ($next == '=') { + $tokens[] = $formula[$i] .'='; + $i++; + } + else { + $tokens[] = $formula[$i]; + } + break; + case 5: + if ($next == '&') { + $tokens[] = '&&'; + $i++; + } + else { + $tokens[] = $formula[$i]; + } + break; + case 6: + if ($next == '|') { + $tokens[] = '||'; + $i++; + } + else { + $tokens[] = $formula[$i]; + } + break; + } + } + else { + $tokens[] = $formula[$i]; + } + } + return $tokens; +} + +/** + * Modify a string to contain proper count indices + * + * This is a callback function used via array_map() + * + * @param $entry An array element + * @param $key Index of the array element + */ +function _locale_import_append_plural($entry, $key) { + // No modifications for 0, 1 + if ($key == 0 || $key == 1) { + return $entry; + } + + // First remove any possibly false indices, then add new ones + $entry = preg_replace('/(%count)\[[0-9]\]/', '\\1', $entry); + return preg_replace('/(%count)/', "\\1[$key]", $entry); +} + +/** + * Generate a short, one string version of the passed comment array + * + * @param $comment An array of strings containing a comment + * @return Short one string version of the comment + */ +function _locale_import_shorten_comments($comment) { + $comm = ''; + while (count($comment)) { + $test = $comm . substr(array_shift($comment), 1) .', '; + if (strlen($comm) < 130) { + $comm = $test; + } + else { + break; + } + } + return substr($comm, 0, -2); +} + +/** + * Parses a string in quotes + * + * @param $string A string specified with enclosing quotes + * @return The string parsed from inside the quotes + */ +function _locale_import_parse_quoted($string) { + if (substr($string, 0, 1) != substr($string, -1, 1)) { + return FALSE; // Start and end quotes must be the same + } + $quote = substr($string, 0, 1); + $string = substr($string, 1, -1); + if ($quote == '"') { // Double quotes: strip slashes + return stripcslashes($string); + } + elseif ($quote == "'") { // Simple quote: return as-is + return $string; + } + else { + return FALSE; // Unrecognized quote + } +} + +/** + * Exports a Portable Object (Template) file for a language + * + * @param $language Selects a language to generate the output for + */ +function _locale_export_po($language) { + global $user; + + // Get language specific strings, or all strings + if ($language) { + $meta = db_fetch_object(db_query("SELECT * FROM {locales_meta} WHERE locale = '%s'", $language)); + $result = db_query("SELECT s.lid, s.source, s.location, t.translation, t.plid, t.plural FROM {locales_source} s INNER JOIN {locales_target} t ON s.lid = t.lid WHERE t.locale = '%s' ORDER BY t.plid, t.plural", $language); + } + else { + $result = db_query("SELECT s.lid, s.source, s.location, t.plid, t.plural FROM {locales_source} s INNER JOIN {locales_target} t ON s.lid = t.lid ORDER BY t.plid, t.plural"); + } + + // Build array out of the database results + $parent = array(); + while ($child = db_fetch_object($result)) { + if ($child->source != '') { + $parent[$child->lid]['comment'] = $child->location; + $parent[$child->lid]['msgid'] = $child->source; + if ($child->plid) { + $parent[$child->lid][$child->plid]['plural'] = $child->lid; + $parent[$child->lid][$child->plid]['translation'] = $child->translation; + $parent[$child->lid][$child->plid]['msgid'] = $child->source; + } + else { + $parent[$child->lid]['translation'] = $child->translation; + } + } + } + + // Generating Portable Object file for a language + if ($language) { + $filename = $language .'.po'; + $header .= "# $meta->name translation of ". variable_get('site_name', 'Drupal') ."\n"; + $header .= '# Copyright (c) '. date('Y') .' '. $user->name .' <'. $user->mail .">\n"; + $header .= "#\n"; + $header .= "msgid \"\"\n"; + $header .= "msgstr \"\"\n"; + $header .= "\"Project-Id-Version: PROJECT VERSION\\n\"\n"; + $header .= "\"POT-Creation-Date: ". date("Y-m-d H:iO") ."\\n\"\n"; + $header .= "\"PO-Revision-Date: ". date("Y-m-d H:iO") ."\\n\"\n"; + $header .= "\"Last-Translator: ". $user->name .' <'. $user->mail .">\\n\"\n"; + $header .= "\"Language-Team: ". $meta->name .' <'. $user->mail .">\\n\"\n"; + $header .= "\"MIME-Version: 1.0\\n\"\n"; + $header .= "\"Content-Type: text/plain; charset=utf-8\\n\"\n"; + $header .= "\"Content-Transfer-Encoding: 8bit\\n\"\n"; + if ($meta->formula && $meta->plurals) { + $header .= "\"Plural-Forms: nplurals=". $meta->plurals ."; plural=". strtr($meta->formula, '$', '') .";\\n\"\n"; + } + $header .= "\n"; + watchdog('locale', t('Exported %locale translation file: %filename.', array('%locale' => theme('placeholder', $meta->name), '%filename' => theme('placeholder', $filename)))); + } + + // Generating Portable Object Template + else { + $filename = 'drupal.pot'; + $header .= "# LANGUAGE translation of PROJECT\n"; + $header .= "# Copyright (c) YEAR NAME \n"; + $header .= "#\n"; + $header .= "msgid \"\"\n"; + $header .= "msgstr \"\"\n"; + $header .= "\"Project-Id-Version: PROJECT VERSION\\n\"\n"; + $header .= "\"POT-Creation-Date: ". date("Y-m-d H:iO") ."\\n\"\n"; + $header .= "\"PO-Revision-Date: YYYY-mm-DD HH:MM+ZZZZ\\n\"\n"; + $header .= "\"Last-Translator: NAME \\n\"\n"; + $header .= "\"Language-Team: LANGUAGE \\n\"\n"; + $header .= "\"MIME-Version: 1.0\\n\"\n"; + $header .= "\"Content-Type: text/plain; charset=utf-8\\n\"\n"; + $header .= "\"Content-Transfer-Encoding: 8bit\\n\"\n"; + $header .= "\"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\\n\"\n"; + $header .= "\n"; + watchdog('locale', t('Exported translation file: %filename.', array('%filename' => theme('placeholder', $filename)))); + } + + // Start download process + header("Content-Disposition: attachment; filename=$filename"); + header("Content-Type: text/plain; charset=utf-8"); + + print $header; + + foreach ($parent as $lid => $message) { + if (!isset($done[$lid])) { + if ($message['comment']) { + print '#: '. $message['comment'] ."\n"; + } + print 'msgid '. _locale_export_print($message['msgid']); + if (isset($message[1]['plural'])) { + print 'msgid_plural '. _locale_export_print($message[1]['msgid']); + if ($language) { + for ($i = 0; $i < $meta->plurals; $i++) { + print 'msgstr['. $i .'] '. _locale_export_print(_locale_export_remove_plural($message[${i}]['translation'])); + $done[$message[${i}]['plural']] = 1; + } + } + else { + print 'msgstr[0] ""'. "\n"; + print 'msgstr[1] ""'. "\n"; + $done[$message[0]['plural']] = 1; + $done[$message[1]['plural']] = 1; + } + } + else { + if ($language) { + print 'msgstr '. _locale_export_print($message['translation']); + } + else { + print 'msgstr ""'. "\n"; + } + } + print "\n"; + } + } + die(); +} + +/** + * Print out a string on multiple lines + */ +function _locale_export_print($str) { + $stri = addcslashes($str, "\0..\37\\\""); + $parts = array(); + + // Cut text into several lines + while ($stri != "") { + $i = strpos($stri, "\\n"); + if ($i === FALSE) { + $curstr = $stri; + $stri = ""; + } + else { + $curstr = substr($stri, 0, $i + 2); + $stri = substr($stri, $i + 2); + } + $curparts = explode("\n", _locale_export_wrap($curstr, 70)); + $parts = array_merge($parts, $curparts); + } + + if (count($parts) > 1) { + return "\"\"\n\"". implode("\"\n\"", $parts) ."\"\n"; + } + else { + return "\"$parts[0]\"\n"; + } +} + +/** + * Custom word wrapping for Portable Object (Template) files. + * + * @author Jacobo Tarrio + */ +function _locale_export_wrap($str, $len) { + $words = explode(' ', $str); + $ret = array(); + + $cur = ""; + $nstr = 1; + while (count($words)) { + $word = array_shift($words); + if ($nstr) { + $cur = $word; + $nstr = 0; + } + elseif (strlen("$cur $word") > $len) { + $ret[] = $cur . " "; + $cur = $word; + } + else { + $cur = "$cur $word"; + } + } + $ret[] = $cur; + + return implode("\n", $ret); +} + +/** + * Removes plural index information from a string + */ +function _locale_export_remove_plural($entry) { + return preg_replace('/(%count)\[[0-9]\]/', '\\1', $entry); +} + +/** + * List languages in search result table + */ +function _locale_string_language_list($translation) { + $languages = locale_supported_languages(FALSE, TRUE); + unset($languages['name']['en']); + $output = ''; + foreach ($languages['name'] as $key => $value) { + if (isset($translation[$key])) { + $output .= ($translation[$key] != '') ? $key .' ' : "$key "; + } + } + + return $output; +} + +/** + * Build object out of search criteria specified in request variables + */ +function _locale_string_seek_query() { + static $query = NULL; + + if (is_null($query)) { + $fields = array('string', 'language', 'searchin'); + $query = new StdClass; + if (is_array($_REQUEST['edit'])) { + foreach ($_REQUEST['edit'] as $key => $value) { + if (!empty($value) && in_array($key, $fields)) { + $query->$key = $value; + } + } + } + else { + foreach ($_REQUEST as $key => $value) { + if (!empty($value) && in_array($key, $fields)) { + $query->$key = strpos(',', $value) ? explode(',', $value) : $value; + } + } + } + } + return $query; +} + +/** + * Perform a string search and display results in a table + */ +function _locale_string_seek() { + // We have at least one criterion to match + if ($query = _locale_string_seek_query()) { + $join = "SELECT s.source, s.location, s.lid, t.translation, t.locale FROM {locales_source} s INNER JOIN {locales_target} t ON s.lid = t.lid "; + + $arguments = array(); + // Compute LIKE section + switch ($query->searchin) { + case 'translated': + $where = "WHERE (t.translation LIKE '%%%s%%' AND t.translation != '')"; + $orderby = "ORDER BY t.translation"; + $arguments[] = $query->string; + break; + case 'untranslated': + $where = "WHERE (s.source LIKE '%%%s%%' AND t.translation = '')"; + $orderby = "ORDER BY s.source"; + $arguments[] = $query->string; + break; + case 'all' : + default: + $where = "WHERE (s.source LIKE '%%%s%%' OR t.translation LIKE '%%%s%%')"; + $orderby = ''; + $arguments[] = $query->string; + $arguments[] = $query->string; + break; + } + + switch ($query->language) { + // Force search in source strings + case "en": + $sql = $join ." WHERE s.source LIKE '%%%s%%' ORDER BY s.source"; + $arguments = array($query->string); // $where is not used, discard its arguments + break; + // Search in all languages + case "all": + $sql = "$join $where $orderby"; + break; + // Some different language + default: + $sql = "$join $where AND t.locale = '%s' $orderby"; + $arguments[] = $query->language; + } + + $result = pager_query($sql, 50, 0, NULL, $arguments); + + $header = array(t('String'), t('Locales'), array('data' => t('Operations'), 'colspan' => '2')); + $arr = array(); + while ($locale = db_fetch_object($result)) { + $arr[$locale->lid]['locales'][$locale->locale] = $locale->translation; + $arr[$locale->lid]['location'] = $locale->location; + $arr[$locale->lid]['source'] = $locale->source; + } + foreach ($arr as $lid => $value) { + $rows[] = array(array('data' => check_plain(truncate_utf8($value['source'], 150, FALSE, TRUE)) .'
'. $value['location'] .''), array('data' => _locale_string_language_list($value['locales']), 'align' => 'center'), array('data' => l(t('edit'), "admin/locale/string/edit/$lid"), 'class' => 'nowrap'), array('data' => l(t('delete'), "admin/locale/string/delete/$lid"), 'class' => 'nowrap')); + } + + $request = array(); + if (count($query)) { + foreach ($query as $key => $value) { + $request[$key] = (is_array($value)) ? implode(',', $value) : $value; + } + } + + if (count($rows)) { + $output .= theme('table', $header, $rows); + } + if ($pager = theme('pager', NULL, 50, 0, $request)) { + $output .= $pager; + } + } + + return $output; +} + +// --------------------------------------------------------------------------------- +// List of some of the most common languages (administration only) + +/** + * Prepares the language code list for a select form item with only the unsupported ones + */ +function _locale_prepare_iso_list() { + $languages = locale_supported_languages(FALSE, TRUE); + $isocodes = _locale_get_iso639_list(); + foreach ($isocodes as $key => $value) { + if (isset($languages['name'][$key])) { + unset($isocodes[$key]); + continue; + } + if (count($value) == 2) { + $tname = t($value[0]); + $isocodes[$key] = ($tname == $value[1]) ? $tname : "$tname ($value[1])"; + } + else { + $isocodes[$key] = t($value[0]); + } + } + asort($isocodes); + return $isocodes; +} + +/** + * Some of the common languages with their English and native names + * + * Based on ISO 639 and http://people.w3.org/rishida/names/languages.html + */ +function _locale_get_iso639_list() { + return array( + "aa" => array("Afar"), + "ab" => array("Abkhazian", "аҧсуа бызшәа"), + "ae" => array("Avestan"), + "af" => array("Afrikaans"), + "ak" => array("Akan"), + "am" => array("Amharic", "አማርኛ"), + "ar" => array("Arabic", "العربية"), + "as" => array("Assamese"), + "av" => array("Avar"), + "ay" => array("Aymara"), + "az" => array("Azerbaijani", "azərbaycan"), + "ba" => array("Bashkir"), + "be" => array("Belarusian", "Беларуская"), + "bg" => array("Bulgarian", "Български"), + "bh" => array("Bihari"), + "bi" => array("Bislama"), + "bm" => array("Bambara", "Bamanankan"), + "bn" => array("Bengali"), + "bo" => array("Tibetan"), + "br" => array("Breton"), + "bs" => array("Bosnian", "Bosanski"), + "ca" => array("Catalan", "Català"), + "ce" => array("Chechen"), + "ch" => array("Chamorro"), + "co" => array("Corsican"), + "cr" => array("Cree"), + "cs" => array("Czech", "Čeština"), + "cu" => array("Old Slavonic"), + "cv" => array("Welsh", "Cymraeg"), + "cy" => array("Welch"), + "da" => array("Danish", "Dansk"), + "de" => array("German", "Deutsch"), + "dv" => array("Maldivian"), + "dz" => array("Bhutani"), + "ee" => array("Ewe", "Ɛʋɛ"), + "el" => array("Greek", "Ελληνικά"), + "en" => array("English"), + "eo" => array("Esperanto"), + "es" => array("Spanish", "Español"), + "et" => array("Estonian", "Eesti"), + "eu" => array("Basque", "Euskera"), + "fa" => array("Persian", "فارسی"), + "ff" => array("Fulah", "Fulfulde"), + "fi" => array("Finnish", "Suomi"), + "fj" => array("Fiji"), + "fo" => array("Faeroese"), + "fr" => array("French", "Français"), + "fy" => array("Frisian", "Frysk"), + "ga" => array("Irish", "Gaeilge"), + "gd" => array("Scots Gaelic"), + "gl" => array("Galician", "Galego"), + "gn" => array("Guarani"), + "gu" => array("Gujarati"), + "gv" => array("Manx"), + "ha" => array("Hausa"), + "he" => array("Hebrew", "עברית"), + "hi" => array("Hindi", "हिन्दी"), + "ho" => array("Hiri Motu"), + "hr" => array("Croatian", "Hrvatski"), + "hu" => array("Hungarian", "Magyar"), + "hy" => array("Armenian", "Հայերեն"), + "hz" => array("Herero"), + "ia" => array("Interlingua"), + "id" => array("Indonesian", "Bahasa Indonesia"), + "ie" => array("Interlingue"), + "ig" => array("Igbo"), + "ik" => array("Inupiak"), + "is" => array("Icelandic", "Íslenska"), + "it" => array("Italian", "Italiano"), + "iu" => array("Inuktitut"), + "ja" => array("Japanese", "日本語"), + "jv" => array("Javanese"), + "ka" => array("Georgian"), + "kg" => array("Kongo"), + "ki" => array("Kikuyu"), + "kj" => array("Kwanyama"), + "kk" => array("Kazakh", "Қазақ"), + "kl" => array("Greenlandic"), + "km" => array("Cambodian"), + "kn" => array("Kannada", "ಕನ್ನಡ"), + "ko" => array("Korean", "한국어"), + "kr" => array("Kanuri"), + "ks" => array("Kashmiri"), + "ku" => array("Kurdish", "Kurdî"), + "kv" => array("Komi"), + "kw" => array("Cornish"), + "ky" => array("Kirghiz", "Кыргыз"), + "la" => array("Latin", "Latina"), + "lb" => array("Luxembourgish"), + "lg" => array("Luganda"), + "ln" => array("Lingala"), + "lo" => array("Laothian"), + "lt" => array("Lithuanian", "Lietuviškai"), + "lv" => array("Latvian", "Latviešu"), + "mg" => array("Malagasy"), + "mh" => array("Marshallese"), + "mi" => array("Maori"), + "mk" => array("Macedonian", "Македонски"), + "ml" => array("Malayalam", "മലയാളം"), + "mn" => array("Mongolian"), + "mo" => array("Moldavian"), + "mr" => array("Marathi"), + "ms" => array("Malay", "Bahasa Melayu"), + "mt" => array("Maltese", "Malti"), + "my" => array("Burmese"), + "na" => array("Nauru"), + "nd" => array("North Ndebele"), + "ne" => array("Nepali"), + "ng" => array("Ndonga"), + "nl" => array("Dutch", "Nederlands"), + "no" => array("Norwegian", "Norsk"), + "nr" => array("South Ndebele"), + "nv" => array("Navajo"), + "ny" => array("Chichewa"), + "oc" => array("Occitan"), + "om" => array("Oromo"), + "or" => array("Oriya"), + "os" => array("Ossetian"), + "pa" => array("Punjabi"), + "pi" => array("Pali"), + "pl" => array("Polish", "Polski"), + "ps" => array("Pashto", "پښتو"), + "pt" => array("Portuguese", "Português"), + "qu" => array("Quechua"), + "rm" => array("Rhaeto-Romance"), + "rn" => array("Kirundi"), + "ro" => array("Romanian", "Română"), + "ru" => array("Russian", "Русский"), + "rw" => array("Kinyarwanda"), + "sa" => array("Sanskrit"), + "sc" => array("Sardinian"), + "sd" => array("Sindhi"), + "se" => array("Northern Sami"), + "sg" => array("Sango"), + "sh" => array("Serbo-Croatian"), + "si" => array("Singhalese"), + "sk" => array("Slovak", "Slovenčina"), + "sl" => array("Slovenian", "Slovenščina"), + "sm" => array("Samoan"), + "sn" => array("Shona"), + "so" => array("Somali"), + "sq" => array("Albanian", "Shqip"), + "sr" => array("Serbian", "Српски"), + "ss" => array("Siswati"), + "st" => array("Sesotho"), + "su" => array("Sudanese"), + "sv" => array("Swedish", "Svenska"), + "sw" => array("Swahili", "Kiswahili"), + "ta" => array("Tamil", "தமிழ்"), + "te" => array("Telugu", "తెలుగు"), + "tg" => array("Tajik"), + "th" => array("Thai", "ภาษาไทย"), + "ti" => array("Tigrinya"), + "tk" => array("Turkmen"), + "tl" => array("Tagalog"), + "tn" => array("Setswana"), + "to" => array("Tonga"), + "tr" => array("Turkish", "Türkçe"), + "ts" => array("Tsonga"), + "tt" => array("Tatar", "Tatarça"), + "tw" => array("Twi"), + "ty" => array("Tahitian"), + "ug" => array("Uighur"), + "uk" => array("Ukrainian", "Українська"), + "ur" => array("Urdu", "اردو"), + "uz" => array("Uzbek", "o'zbek"), + "ve" => array("Venda"), + "vi" => array("Vietnamese", "Tiếng Việt"), + "wo" => array("Wolof"), + "xh" => array("Xhosa", "isiXhosa"), + "yi" => array("Yiddish"), + "yo" => array("Yoruba", "Yorùbá"), + "za" => array("Zhuang"), + "zh-hans" => array("Chinese, Simplified", "简体中文"), + "zh-hant" => array("Chinese, Traditional", "繁體中文"), + "zu" => array("Zulu", "isiZulu"), + ); +} diff --git a/includes/menu.inc b/includes/menu.inc new file mode 100644 index 0000000..c5fdd45 --- /dev/null +++ b/includes/menu.inc @@ -0,0 +1,1362 @@ + $item['description'])). + * - 'children' - A linear list of the menu ID's of this item's children. + * + * Menu ID 0 is the "root" of the menu. The children of this item are the + * menus themselves (they will have no associated path). Menu ID 1 will + * always be one of these children; it is the default "Navigation" menu. + */ +function menu_get_menu() { + global $_menu; + global $user; + global $locale; + + if (!isset($_menu['items'])) { + // _menu_build() may indirectly call this function, so prevent infinite loops. + $_menu['items'] = array(); + + $cid = "menu:$user->uid:$locale"; + if ($cached = cache_get($cid)) { + $_menu = unserialize($cached->data); + } + else { + _menu_build(); + // Cache the menu structure for this user, to expire after one day. + cache_set($cid, serialize($_menu), time() + (60 * 60 * 24)); + } + + // Make sure items that cannot be cached are added. + _menu_append_contextual_items(); + + // Reset the cached $menu in menu_get_item(). + menu_get_item(NULL, NULL, TRUE); + } + + return $_menu; +} + +/** + * Return the local task tree. + * + * Unlike the rest of the menu structure, the local task tree cannot be cached + * nor determined too early in the page request, because the user's current + * location may be changed by a menu_set_location() call, and the tasks shown + * (just as the breadcrumb trail) need to reflect the changed location. + */ +function menu_get_local_tasks() { + global $_menu; + + // Don't cache the local task tree, as it varies by location and tasks are + // allowed to be dynamically determined. + if (!isset($_menu['local tasks'])) { + // _menu_build_local_tasks() may indirectly call this function, so prevent + // infinite loops. + $_menu['local tasks'] = array(); + $pid = menu_get_active_nontask_item(); + if (!_menu_build_local_tasks($pid)) { + // If the build returned FALSE, the tasks need not be displayed. + $_menu['local tasks'][$pid]['children'] = array(); + } + } + + return $_menu['local tasks']; +} + +/** + * Retrieves the menu item specified by $mid, or by $path if $mid is not given. + * + * @param $mid + * The menu ID of the menu item to retrieve. + * @param $path + * The internal path of the menu item to retrieve. Defaults to NULL. Only + * used if $mid is not set. + * @param $reset + * Optional flag that resets the static variable cache of the menu tree, if + * set to TRUE. Default is FALSE. + * + * @return + * The menu item found in the site menu, or an empty array if none could be + * found. + */ +function menu_get_item($mid, $path = NULL, $reset = FALSE) { + static $menu; + + if (!isset($menu) || $reset) { + $menu = menu_get_menu(); + } + + if (isset($mid)) { + return $menu['items'][$mid]; + } + + if (isset($path)) { + return $menu['items'][$menu['path index'][$path]]; + } + + return array(); +} + +/** + * Retrieves the menu ID and title of all root menus. + * + * @return + * Array containing all menus (but not menu items), in the form mid => title. + */ +function menu_get_root_menus() { + $menu = menu_get_menu(); + $root_menus = array(); + + foreach ($menu['items'][0]['children'] as $mid) { + $root_menus[$mid] = $menu['items'][$mid]['title']; + } + + return $root_menus; +} + +/** + * Change the current menu location of the user. + * + * Frequently, modules may want to make a page or node act as if it were + * in the menu tree somewhere, even though it was not registered in a + * hook_menu() implementation. If the administrator has rearranged the menu, + * the newly set location should respect this in the breadcrumb trail and + * expanded/collapsed status of menu items in the tree. This function + * allows this behavior. + * + * @param $location + * An array specifying a complete or partial breadcrumb trail for the + * new location, in the same format as the return value of hook_menu(). + * The last element of this array should be the new location itself. + * + * This function will set the new breadcrumb trail to the passed-in value, + * but if any elements of this trail are visible in the site tree, the + * trail will be "spliced in" to the existing site navigation at that point. + */ +function menu_set_location($location) { + global $_menu; + $temp_id = min(array_keys($_menu['items'])) - 1; + $prev_id = 0; + + // Don't allow this function to change the actual current path, just the + // position in the menu tree. + $location[count($location) - 1]['path'] = $_GET['q']; + + foreach (array_reverse($location) as $item) { + if (isset($_menu['path index'][$item['path']])) { + $mid = $_menu['path index'][$item['path']]; + if (isset($_menu['visible'][$mid])) { + // Splice in the breadcrumb at this location. + if ($prev_id) { + $_menu['items'][$prev_id]['pid'] = $mid; + } + $prev_id = 0; + break; + } + else { + // A hidden item; show it, but only temporarily. + $_menu['items'][$mid]['type'] |= MENU_VISIBLE_IN_BREADCRUMB; + if ($prev_id) { + $_menu['items'][$prev_id]['pid'] = $mid; + } + $prev_id = $mid; + } + } + else { + $item['type'] |= MENU_VISIBLE_IN_BREADCRUMB; + if ($prev_id) { + $_menu['items'][$prev_id]['pid'] = $temp_id; + } + $_menu['items'][$temp_id] = $item; + $_menu['path index'][$item['path']] = $temp_id; + + $prev_id = $temp_id; + $temp_id--; + } + } + + if ($prev_id) { + // Didn't find a home, so attach this to the main navigation menu. + $_menu['items'][$prev_id]['pid'] = 1; + } + + $final_item = array_pop($location); + menu_set_active_item($final_item['path']); +} + +/** + * Execute the handler associated with the active menu item. + * + * This is called early in the page request. The active menu item is at + * this point determined exclusively by the URL. The handler that is called + * here may, as a side effect, change the active menu item so that later + * menu functions (that display the menus and breadcrumbs, for example) + * act as if the user were in a different location on the site. + */ +function menu_execute_active_handler() { + if (_menu_site_is_offline()) { + return MENU_SITE_OFFLINE; + } + + $menu = menu_get_menu(); + + // Determine the menu item containing the callback. + $path = $_GET['q']; + while ($path && !isset($menu['callbacks'][$path])) { + $path = substr($path, 0, strrpos($path, '/')); + } + + if (!isset($menu['callbacks'][$path])) { + return MENU_NOT_FOUND; + } + + if (!function_exists($menu['callbacks'][$path]['callback'])) { + return MENU_NOT_FOUND; + } + + if (!_menu_item_is_accessible(menu_get_active_item())) { + return MENU_ACCESS_DENIED; + } + + // We found one, and are allowed to execute it. + $arguments = isset($menu['callbacks'][$path]['callback arguments']) ? $menu['callbacks'][$path]['callback arguments'] : array(); + $arg = substr($_GET['q'], strlen($path) + 1); + if (strlen($arg)) { + $arguments = array_merge($arguments, explode('/', $arg)); + } + + return call_user_func_array($menu['callbacks'][$path]['callback'], $arguments); +} + +/** + * Returns the ID of the active menu item. + */ +function menu_get_active_item() { + return menu_set_active_item(); +} + +/** + * Sets the path of the active menu item. + */ +function menu_set_active_item($path = NULL) { + static $stored_mid; + + if (!isset($stored_mid) || isset($path)) { + $menu = menu_get_menu(); + if (!isset($path)) { + $path = $_GET['q']; + } + else { + $_GET['q'] = $path; + } + + while ($path && !isset($menu['path index'][$path])) { + $path = substr($path, 0, strrpos($path, '/')); + } + $stored_mid = isset($menu['path index'][$path]) ? $menu['path index'][$path] : 0; + + // Search for default local tasks to activate instead of this item. + $continue = TRUE; + while ($continue) { + $continue = FALSE; + if (isset($menu['items'][$stored_mid]['children'])) { + foreach ($menu['items'][$stored_mid]['children'] as $cid) { + if ($menu['items'][$cid]['type'] & MENU_LINKS_TO_PARENT) { + $stored_mid = $cid; + $continue = TRUE; + } + } + } + } + + // Reset the cached $menu in menu_get_item(). + menu_get_item(NULL, NULL, TRUE); + } + + return $stored_mid; +} + +/** + * Returns the ID of the current menu item or, if the current item is a + * local task, the menu item to which this task is attached. + */ +function menu_get_active_nontask_item() { + $mid = menu_get_active_item(); + + // Find the first non-task item: + while ($mid) { + $item = menu_get_item($mid); + + if (!($item['type'] & MENU_IS_LOCAL_TASK)) { + return $mid; + } + + $mid = $item['pid']; + } +} + +/** + * Returns the title of the active menu item. + */ +function menu_get_active_title() { + if ($mid = menu_get_active_nontask_item()) { + $item = menu_get_item($mid); + return $item['title']; + } +} + +/** + * Returns the help associated with the active menu item. + */ +function menu_get_active_help() { + $path = $_GET['q']; + $output = ''; + + if (!_menu_item_is_accessible(menu_get_active_item())) { + // Don't return help text for areas the user cannot access. + return; + } + + foreach (module_list() as $name) { + if (module_hook($name, 'help')) { + if ($temp = module_invoke($name, 'help', $path)) { + $output .= $temp . "\n"; + } + if (module_hook('help', 'page')) { + if (substr($path, 0, 6) == "admin/") { + if (module_invoke($name, 'help', 'admin/help#' . substr($path, 6))) { + $output .= theme("more_help_link", url('admin/help/' . substr($path, 6))); + } + } + } + } + } + return $output; +} + +/** + * Returns an array of rendered menu items in the active breadcrumb trail. + */ +function menu_get_active_breadcrumb() { + + // No breadcrumb for the front page. + if (drupal_is_front_page()) { + return array(); + } + + $links[] = l(t('Home'), variable_get('site_frontpage', 'node')); + + $trail = _menu_get_active_trail(); + foreach ($trail as $mid) { + $item = menu_get_item($mid); + if ($item['type'] & MENU_VISIBLE_IN_BREADCRUMB) { + $links[] = menu_item_link($mid); + } + } + + // The last item in the trail is the page title; don't display it here. + array_pop($links); + + return $links; +} + +/** + * Returns true when the menu item is in the active trail. + */ +function menu_in_active_trail($mid) { + $trail = _menu_get_active_trail(); + + return in_array($mid, $trail); +} + +/** + * Returns true when the menu item is in the active trail within a + * specific subsection of the menu tree. + * + * @param $mid + * The menu item being considered. + * @param $pid + * The root of the subsection of the menu tree in which to look. + */ +function menu_in_active_trail_in_submenu($mid, $pid) { + $trail = _menu_get_active_trail_in_submenu($pid); + + if (!$trail) { + return FALSE; + } + + return in_array($mid, $trail); +} + +/** + * Populate the database representation of the menu. + * + * This need only be called at the start of pages that modify the menu. + */ +function menu_rebuild() { + // Clear the page cache, so that changed menus are reflected for anonymous users. + cache_clear_all(); + // Also clear the menu cache. + cache_clear_all('menu:', TRUE); + + _menu_build(); + + if (module_exist('menu')) { + $menu = menu_get_menu(); + + // Fill a queue of new menu items which are modifiable. + $new_items = array(); + foreach ($menu['items'] as $mid => $item) { + if ($mid < 0 && ($item['type'] & MENU_MODIFIABLE_BY_ADMIN)) { + $new_items[$mid] = $item; + } + } + + $old_count = -1; + // Save the new items updating the pids in each iteration + while (($c = count($new_items)) && ($c != $old_count)) { + $old_count = count($new_items); + foreach($new_items as $mid => $item) { + // If the item has a valid parent, save it + if ($item['pid'] >= 0) { + // The new menu ID gets passed back by reference as $item['mid'] + menu_save_item($item); + // Fix parent IDs for the children of the menu item just saved + if ($item['children']) { + foreach ($item['children'] as $child) { + if (isset($new_items[$child])) { + $new_items[$child]['pid'] = $item['mid']; + } + } + } + // remove the item + unset($new_items[$mid]); + } + } + } + // Rebuild the menu to account for the changes. + _menu_build(); + } + + // Reset the cached $menu in menu_get_item(). + menu_get_item(NULL, NULL, TRUE); + +} + +/** + * Generate the HTML for a menu tree. + * + * @param $pid + * The parent id of the menu. + * + * @ingroup themeable + */ +function theme_menu_tree($pid = 1) { + if ($tree = menu_tree($pid)) { + return "\n
    \n". $tree ."\n
\n"; + } +} + +/** + * Returns a rendered menu tree. + * + * @param $pid + * The parent id of the menu. + */ +function menu_tree($pid = 1) { + $menu = menu_get_menu(); + $output = ''; + + if (isset($menu['visible'][$pid]) && $menu['visible'][$pid]['children']) { + foreach ($menu['visible'][$pid]['children'] as $mid) { + $type = isset($menu['visible'][$mid]['type']) ? $menu['visible'][$mid]['type'] : NULL; + $children = isset($menu['visible'][$mid]['children']) ? $menu['visible'][$mid]['children'] : NULL; + $output .= theme('menu_item', $mid, menu_in_active_trail($mid) || ($type & MENU_EXPANDED) ? theme('menu_tree', $mid) : '', count($children) == 0); + } + } + + return $output; +} + +/** + * Generate the HTML output for a single menu item. + * + * @param $mid + * The menu id of the item. + * @param $children + * A string containing any rendered child items of this menu. + * @param $leaf + * A boolean indicating whether this menu item is a leaf. + * + * @ingroup themeable + */ +function theme_menu_item($mid, $children = '', $leaf = TRUE) { + return '
  • '. menu_item_link($mid) . $children ."
  • \n"; +} + +/** + * Generate the HTML representing a given menu item ID. + * + * @param $item + * The menu item to render. + * @param $link_item + * The menu item which should be used to find the correct path. + * + * @ingroup themeable + */ +function theme_menu_item_link($item, $link_item) { + return l($item['title'], $link_item['path'], isset($item['description']) ? array('title' => $item['description']) : array()); +} + +/** + * Returns the rendered link to a menu item. + * + * @param $mid + * The menu item id to render. + */ +function menu_item_link($mid) { + $item = menu_get_item($mid); + $link_item = $item; + + while ($link_item['type'] & MENU_LINKS_TO_PARENT) { + $link_item = menu_get_item($link_item['pid']); + } + + return theme('menu_item_link', $item, $link_item); +} + +/** + * Returns the rendered local tasks. The default implementation renders + * them as tabs. + * + * @ingroup themeable + */ +function theme_menu_local_tasks() { + $output = ''; + + if ($primary = menu_primary_local_tasks()) { + $output .= "
      \n". $primary ."
    \n"; + } + if ($secondary = menu_secondary_local_tasks()) { + $output .= "
      \n". $secondary ."
    \n"; + } + + return $output; +} + +/** + * Returns the rendered HTML of the primary local tasks. + */ +function menu_primary_local_tasks() { + $local_tasks = menu_get_local_tasks(); + $pid = menu_get_active_nontask_item(); + $output = ''; + + if (count($local_tasks[$pid]['children'])) { + foreach ($local_tasks[$pid]['children'] as $mid) { + $output .= theme('menu_local_task', $mid, menu_in_active_trail($mid), TRUE); + } + } + + return $output; +} + +/** + * Returns the rendered HTML of the secondary local tasks. + */ +function menu_secondary_local_tasks() { + $local_tasks = menu_get_local_tasks(); + $pid = menu_get_active_nontask_item(); + $output = ''; + + if (count($local_tasks[$pid]['children'])) { + foreach ($local_tasks[$pid]['children'] as $mid) { + if (menu_in_active_trail($mid) && count($local_tasks[$mid]['children']) > 1) { + foreach ($local_tasks[$mid]['children'] as $cid) { + $output .= theme('menu_local_task', $cid, menu_in_active_trail($cid), FALSE); + } + } + } + } + + return $output; +} + +/** + * Generate the HTML representing a given menu item ID as a tab. + * + * @param $mid + * The menu ID to render. + * @param $active + * Whether this tab or a subtab is the active menu item. + * @param $primary + * Whether this tab is a primary tab or a subtab. + * + * @ingroup themeable + */ +function theme_menu_local_task($mid, $active, $primary) { + if ($active) { + return '
  • '. menu_item_link($mid) ."
  • \n"; + } + else { + return '
  • '. menu_item_link($mid) ."
  • \n"; + } +} + +/** + * Returns an array containing the primary links. + * Can optionally descend from the root of the Primary links menu towards the + * current node for a specified number of levels and return that submenu. + * Used to generate a primary/secondary menu from different levels of one menu. + * + * @param $start_level + * This optional parameter can be used to retrieve a context-sensitive array + * of links at $start_level levels deep into the Primary links menu. + * The default is to return the top-level links. + * @param $pid + * The parent menu ID from which to search for children. Defaults to the + * menu_primary_menu setting. + * @return An array containing the themed links as the values. The keys of + * the array contain some extra encoded information about the results. + * The format of the key is {level}-{num}{-active}. + * level is the depth within the menu tree of this list. + * num is the number within this array, used only to make the key unique. + * -active is appended if this element is in the active trail. + */ +function menu_primary_links($start_level = 1, $pid = 0) { + if (!module_exist('menu')) { + return NULL; + } + if (!$pid) { + $pid = variable_get('menu_primary_menu', 0); + } + if (!$pid) { + return NULL; + } + + if ($start_level < 1) { + $start_level = 1; + } + + if ($start_level > 1) { + $trail = _menu_get_active_trail_in_submenu($pid); + if (!$trail) { + return NULL; + } + else { + $pid = $trail[$start_level - 1]; + } + } + + $menu = menu_get_menu(); + $links = array(); + if ($pid && is_array($menu['visible'][$pid]) && isset($menu['visible'][$pid]['children'])) { + $count = 1; + foreach ($menu['visible'][$pid]['children'] as $cid) { + $index = "menu-$start_level-$count"; + if (menu_in_active_trail_in_submenu($cid, $pid)) { + $index .= "-active"; + } + $links[$index] = menu_item_link($cid); + $count++; + } + } + + // Special case - provide link to admin/menu if primary links is empty. + if (empty($links) && $start_level == 1 && $pid == variable_get('menu_primary_menu', 0)) { + $links['1-1'] = l(t('edit primary links'),'admin/menu'); + } + + return $links; +} + +/** + * Returns an array containing the secondary links. + * Secondary links can be either a second level of the Primary links + * menu or generated from their own menu. + */ +function menu_secondary_links() { + $msm = variable_get('menu_secondary_menu', 0); + if ($msm == 0) { + return NULL; + } + + if ($msm == variable_get('menu_primary_menu', 0)) { + return menu_primary_links(2, $msm); + } + + return menu_primary_links(1, $msm); +} + +/** + * Returns the themed HTML for primary and secondary links. + * Note that this function is overridden by most core themes because + * those themes display links in "link | link" format, not from a list. + * Also note that by default links rendered with this function are + * displayed with the same CSS as is used for the local tasks. + * If a theme wishes to render links from a ul it is expected that + * the theme will provide suitable CSS. + * + * @param $links + * An array containing links to render. + * @return + * A string containing the themed links. + * + * @ingroup themeable + */ +function theme_menu_links($links) { + if (!count($links)) { + return ''; + } + $level_tmp = explode('-', key($links)); + $level = $level_tmp[0]; + $output = "