summaryrefslogtreecommitdiff
path: root/modules/gallery
diff options
context:
space:
mode:
Diffstat (limited to 'modules/gallery')
-rw-r--r--modules/gallery/controllers/l10n_client.php17
-rw-r--r--modules/gallery/css/l10n_client.css30
-rw-r--r--modules/gallery/helpers/MY_url.php14
-rw-r--r--modules/gallery/helpers/access.php4
-rw-r--r--modules/gallery/helpers/gallery_installer.php18
-rw-r--r--modules/gallery/helpers/graphics.php5
-rw-r--r--modules/gallery/helpers/model_cache.php14
-rw-r--r--modules/gallery/js/l10n_client.js49
-rw-r--r--modules/gallery/models/item.php4
-rw-r--r--modules/gallery/module.info2
-rw-r--r--modules/gallery/tests/Access_Helper_Test.php1
-rw-r--r--modules/gallery/tests/Album_Helper_Test.php2
-rw-r--r--modules/gallery/tests/Gallery_I18n_Test.php5
-rw-r--r--modules/gallery/tests/Gallery_Installer_Test.php2
-rw-r--r--modules/gallery/tests/Gallery_Rest_Helper_Test.php12
-rw-r--r--modules/gallery/tests/Item_Model_Test.php2
-rw-r--r--modules/gallery/tests/Url_Security_Test.php43
-rw-r--r--modules/gallery/tests/xss_data.txt2
-rw-r--r--modules/gallery/views/l10n_client.html.php2
19 files changed, 163 insertions, 65 deletions
diff --git a/modules/gallery/controllers/l10n_client.php b/modules/gallery/controllers/l10n_client.php
index 71df1cf1..e20bab50 100644
--- a/modules/gallery/controllers/l10n_client.php
+++ b/modules/gallery/controllers/l10n_client.php
@@ -29,9 +29,9 @@ class L10n_Client_Controller extends Controller {
$key = $input->post("l10n-message-key");
$root_message = ORM::factory("incoming_translation")
- ->where(array("key" => $key,
- "locale" => "root"))
- ->find();
+ ->where("key", "=", $key)
+ ->where("locale", "=", "root")
+ ->find();
if (!$root_message->loaded()) {
throw new Exception("@todo bad request data / illegal state");
@@ -56,8 +56,8 @@ class L10n_Client_Controller extends Controller {
}
$entry = ORM::factory("outgoing_translation")
- ->where(array("key" => $key,
- "locale" => $locale))
+ ->where("key", "=", $key)
+ ->where("locale", "=", $locale)
->find();
if (!$entry->loaded()) {
@@ -70,8 +70,8 @@ class L10n_Client_Controller extends Controller {
$entry->translation = serialize($translation);
$entry_from_incoming = ORM::factory("incoming_translation")
- ->where(array("key" => $key,
- "locale" => $locale))
+ ->where("key", "=", $key)
+ ->where("locale", "=", $locale)
->find();
if (!$entry_from_incoming->loaded()) {
@@ -102,10 +102,9 @@ class L10n_Client_Controller extends Controller {
}
private static function _l10n_client_search_form() {
- $form = new Forge("l10n_client/search", "", "post", array("id" => "g-l10n-search-form"));
+ $form = new Forge("#", "", "post", array("id" => "g-l10n-search-form"));
$group = $form->group("l10n_search");
$group->input("l10n-search")->id("g-l10n-search");
- $group->submit("l10n-search-filter-clear")->value(t("X"));
return $form;
}
diff --git a/modules/gallery/css/l10n_client.css b/modules/gallery/css/l10n_client.css
index 542da8e6..3771c049 100644
--- a/modules/gallery/css/l10n_client.css
+++ b/modules/gallery/css/l10n_client.css
@@ -86,7 +86,7 @@
}
#l10n-client .string-list {
- height:17em;
+ height: 16em;
overflow:auto;
list-style:none; list-style-image:none;
margin:0em; padding:0em;}
@@ -129,10 +129,9 @@
font-weight:bold;}
#l10n-client #g-l10n-search-form {
- background:#eee;
- text-align:center;
- height:2em; line-height:2em;
- margin:0em; padding:.5em .5em;
+ background: #eee;
+ margin: 0em;
+ padding: .25em .25em;
}
#l10n-client #g-l10n-search-form .form-item,
@@ -148,8 +147,14 @@
padding:0em;
}
-#l10n-client #g-l10n-search-form input.form-text {
- width:80%;
+#l10n-client #g-l10n-search-form fieldset {
+ margin-bottom: 0;
+ padding: .25em .25em;
+}
+
+
+#l10n-client #g-l10n-search-form input {
+ width: 96.75%;
}
#l10n-client #g-l10n-search-form #search-filter-clear {
@@ -164,7 +169,9 @@
#l10n-client-string-editor .source {
overflow:hidden;
- width:50%; float:left;}
+ width:50%;
+ float:left;
+}
#l10n-client-string-editor .source .source-text {
line-height:1.5em;
@@ -178,10 +185,13 @@
#l10n-client-string-editor .translation {
overflow:hidden;
- width:49%; float:right;}
+ width:49%;
+ float: right;
+}
#g-l10n-client-save-form {
- padding:0em;}
+ padding: 0em;
+}
#l10n-client form ul,
#l10n-client form li,
diff --git a/modules/gallery/helpers/MY_url.php b/modules/gallery/helpers/MY_url.php
index 74284951..8a7909b6 100644
--- a/modules/gallery/helpers/MY_url.php
+++ b/modules/gallery/helpers/MY_url.php
@@ -89,4 +89,18 @@ class url extends url_Core {
static function abs_current($qs=false) {
return self::abs_site(url::current($qs));
}
+
+ /**
+ * Just like url::merge except that it escapes any XSS in the path.
+ */
+ static function merge($params) {
+ return htmlspecialchars(parent::merge($params));
+ }
+
+ /**
+ * Just like url::current except that it escapes any XSS in the path.
+ */
+ static function current($qs=false, $suffix=false) {
+ return htmlspecialchars(parent::current($qs, $suffix));
+ }
}
diff --git a/modules/gallery/helpers/access.php b/modules/gallery/helpers/access.php
index 8ce7e436..e0a0e979 100644
--- a/modules/gallery/helpers/access.php
+++ b/modules/gallery/helpers/access.php
@@ -66,8 +66,8 @@
* the Access_Intent_Model
*/
class access_Core {
- const DENY = false;
- const ALLOW = true;
+ const DENY = "0";
+ const ALLOW = "1";
const INHERIT = null; // access_intent
const UNKNOWN = null; // cache (access_cache, items)
diff --git a/modules/gallery/helpers/gallery_installer.php b/modules/gallery/helpers/gallery_installer.php
index 410b6413..1e0ad28c 100644
--- a/modules/gallery/helpers/gallery_installer.php
+++ b/modules/gallery/helpers/gallery_installer.php
@@ -240,7 +240,7 @@ class gallery_installer {
100);
graphics::add_rule(
"gallery", "resize", "gallery_graphics::resize",
- array("width" => 640, "height" => 480, "master" => Image::AUTO),
+ array("width" => 640, "height" => 640, "master" => Image::AUTO),
100);
// Instantiate default themes (site and admin)
@@ -440,6 +440,22 @@ class gallery_installer {
module::set_var("gallery", "simultaneous_upload_limit", 5);
module::set_version("gallery", $version = 21);
}
+
+ // Update the graphics rules table so that the maximum height for resizes is 640 not 480.
+ // Fixes ticket #671
+ if ( $version == 21) {
+ $resize_rule = ORM::factory("graphics_rule")
+ ->where("id", "=", "2")
+ ->find();
+ // make sure it hasn't been changed already
+ $args = unserialize($resize_rule->args);
+ if ($args["height"] == 480 && $args["width"] == 640) {
+ $args["height"] = 640;
+ $resize_rule->args = serialize($args);
+ $resize_rule->save();
+ }
+ module::set_version("gallery", $version = 22);
+ }
}
static function uninstall() {
diff --git a/modules/gallery/helpers/graphics.php b/modules/gallery/helpers/graphics.php
index 7577d7ac..5a290905 100644
--- a/modules/gallery/helpers/graphics.php
+++ b/modules/gallery/helpers/graphics.php
@@ -60,11 +60,12 @@ class graphics_Core {
* @param string $operation the name of the operation(<defining class>::method)
*/
static function remove_rule($module_name, $target, $operation) {
- ORM::factory("graphics_rule")
+ db::build()
+ ->delete("graphics_rules")
->where("module_name", "=", $module_name)
->where("target", "=", $target)
->where("operation", "=", $operation)
- ->delete_all();
+ ->execute();
self::mark_dirty($target == "thumb", $target == "resize");
}
diff --git a/modules/gallery/helpers/model_cache.php b/modules/gallery/helpers/model_cache.php
index 302e42d9..88756407 100644
--- a/modules/gallery/helpers/model_cache.php
+++ b/modules/gallery/helpers/model_cache.php
@@ -18,27 +18,25 @@
* Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
*/
class model_cache_Core {
- private static $cache;
+ private static $cache = array();
static function get($model_name, $id, $field_name="id") {
- if (TEST_MODE || empty(self::$cache->$model_name->$field_name->$id)) {
+ if (TEST_MODE || empty(self::$cache[$model_name][$field_name][$id])) {
$model = ORM::factory($model_name)->where($field_name, "=", $id)->find();
if (!$model->loaded()) {
throw new Exception("@todo MISSING_MODEL $model_name:$id");
}
- self::$cache->$model_name->$field_name->$id = $model;
+ self::$cache[$model_name][$field_name][$id] = $model;
}
- return self::$cache->$model_name->$field_name->$id;
+ return self::$cache[$model_name][$field_name][$id];
}
static function clear() {
- self::$cache = new stdClass();
+ self::$cache = array();
}
static function set($model) {
- self::$cache->{$model->object_name}
- ->{$model->primary_key}
- ->{$model->{$model->primary_key}} = $model;
+ self::$cache[$model->object_name][$model->primary_key][$model->{$model->primary_key}] = $model;
}
}
diff --git a/modules/gallery/js/l10n_client.js b/modules/gallery/js/l10n_client.js
index 9acb6ca8..d0d6f619 100644
--- a/modules/gallery/js/l10n_client.js
+++ b/modules/gallery/js/l10n_client.js
@@ -17,7 +17,6 @@ $(document).ready(function() {
Gallery.attachBehaviors(this);
});
-
// Store all l10n_client related data + methods in its own object
jQuery.extend(Gallery, {
l10nClient: new (function() {
@@ -51,7 +50,8 @@ jQuery.extend(Gallery, {
this.filter(false);
break;
}
- }
+ };
+
// Toggle the l10nclient
this.toggle = function(state) {
switch(state) {
@@ -82,15 +82,21 @@ jQuery.extend(Gallery, {
$.cookie('Gallery_l10n_client', '0', {expires: 7, path: '/'});
break;
}
- }
+ };
+
// Get a string from the DOM tree
this.getString = function(index, type) {
- return l10n_client_data[index][type];
- }
+ if (index < l10n_client_data.length) {
+ return l10n_client_data[index][type];
+ }
+ return "";
+ };
+
// Set a string in the DOM tree
this.setString = function(index, data) {
l10n_client_data[index]['translation'] = data;
- }
+ };
+
// Display the source message
this.showSourceMessage = function(source, is_plural) {
if (is_plural) {
@@ -101,10 +107,11 @@ jQuery.extend(Gallery, {
var pretty_source = $('#source-text-tmp-space').text(source).html();
}
$('#l10n-client-string-editor .source-text').html(pretty_source);
- }
+ };
this.isPluralMessage = function(message) {
return typeof(message) == 'object';
- }
+ };
+
this.updateTranslationForm = function(translation, is_plural) {
$('.translationField').addClass('hidden');
if (is_plural) {
@@ -122,10 +129,11 @@ jQuery.extend(Gallery, {
$('#plural-' + form).removeClass('hidden');
}
} else {
- $('#l10n-edit-translation').attr('value', translation);
+ $('#l10n-edit-translation').attr('value', translation);
$('#l10n-edit-translation').removeClass('hidden');
}
- }
+ };
+
// Filter the string list by a search string
this.filter = function(search) {
if(search == false || search == '') {
@@ -140,7 +148,7 @@ jQuery.extend(Gallery, {
$('#l10n-client #g-l10n-search').val(search);
}
}
- }
+ };
this.copySourceText = function() {
var index = Gallery.l10nClient.selected;
@@ -166,7 +174,7 @@ jQuery.extend(Gallery, {
}
}
- }
+ };
})
});
@@ -205,8 +213,8 @@ Gallery.behaviors.l10nClient = function(context) {
Gallery.l10nClient.toggle(0);
}
});
-
- // Close the l10n client using an AJAX call and refreshing the page
+
+ // Close the l10n client using an AJAX call and refreshing the page
$('#g-close-l10n').click(function(event) {
$.ajax({
type: "GET",
@@ -222,10 +230,15 @@ Gallery.behaviors.l10nClient = function(context) {
// Register keybindings using jQuery hotkeys
// TODO: Either remove hotkeys code or add query.hotkeys.js.
if($.hotkeys) {
- $.hotkeys.add(Gallery.l10nClient.keys['toggle'], function(){Gallery.l10nClient.key('toggle')});
- $.hotkeys.add(Gallery.l10nClient.keys['clear'], {target:'#l10n-client #g-l10n-search', type:'keyup'}, function(){Gallery.l10nClient.key('clear')});
+ $.hotkeys.add(Gallery.l10nClient.keys['toggle'], function(){Gallery.l10nClient.key('toggle');});
+ $.hotkeys.add(Gallery.l10nClient.keys['clear'], {target:'#l10n-client #g-l10n-search', type:'keyup'}, function(){Gallery.l10nClient.key('clear');});
}
+ // never actually submit the form as the search is done in the browser
+ $('#g-l10n-search-form').submit(function() {
+ return false;
+ });
+
// Custom listener for l10n_client livesearch
$('#l10n-client #g-l10n-search').keyup(function(key) {
Gallery.l10nClient.filter($('#l10n-client #g-l10n-search').val());
@@ -246,9 +259,9 @@ Gallery.behaviors.l10nClient = function(context) {
var num_plural_forms = plural_forms.length;
// Store translation in local js
+ var translation = {};
if (is_plural) {
- var translation = {};
- for (var i = 0; i < num_plural_forms; i++) {
+ for (var i = 0; i < num_plural_forms; i++) {
var form = plural_forms[i];
translation[form] = $('#g-l10n-client-save-form #l10n-edit-plural-translation-' + form).attr('value');
}
diff --git a/modules/gallery/models/item.php b/modules/gallery/models/item.php
index 4a3d26e9..6851e1a3 100644
--- a/modules/gallery/models/item.php
+++ b/modules/gallery/models/item.php
@@ -416,9 +416,11 @@ class Item_Model extends ORM_MPTT {
$send_event = 1;
}
}
+
+ $original = clone $this->original();
parent::save();
if (isset($send_event)) {
- module::event("item_updated", $this->original(), $this);
+ module::event("item_updated", $original, $this);
}
return $this;
}
diff --git a/modules/gallery/module.info b/modules/gallery/module.info
index b3366f7d..107d9a12 100644
--- a/modules/gallery/module.info
+++ b/modules/gallery/module.info
@@ -1,4 +1,4 @@
name = "Gallery 3"
description = "Gallery core application"
-version = 21
+version = 22
diff --git a/modules/gallery/tests/Access_Helper_Test.php b/modules/gallery/tests/Access_Helper_Test.php
index 084bfb47..b2244766 100644
--- a/modules/gallery/tests/Access_Helper_Test.php
+++ b/modules/gallery/tests/Access_Helper_Test.php
@@ -74,6 +74,7 @@ class Access_Helper_Test extends Unit_Test_Case {
access::deny(identity::everybody(), "view", $item);
access::deny(identity::registered_users(), "view", $item);
+ $item->reload();
$user = identity::create_user("access_test", "Access Test", "");
foreach ($user->groups() as $group) {
diff --git a/modules/gallery/tests/Album_Helper_Test.php b/modules/gallery/tests/Album_Helper_Test.php
index 1284b8cc..ef0905da 100644
--- a/modules/gallery/tests/Album_Helper_Test.php
+++ b/modules/gallery/tests/Album_Helper_Test.php
@@ -38,7 +38,7 @@ class Album_Helper_Test extends Unit_Test_Case {
}
public function create_conflicting_album_test() {
- $rand = rand();
+ $rand = "name_" . rand();
$root = ORM::factory("item", 1);
$album1 = album::create($root, $rand, $rand, $rand);
$album2 = album::create($root, $rand, $rand, $rand);
diff --git a/modules/gallery/tests/Gallery_I18n_Test.php b/modules/gallery/tests/Gallery_I18n_Test.php
index 895e3051..5d2fd994 100644
--- a/modules/gallery/tests/Gallery_I18n_Test.php
+++ b/modules/gallery/tests/Gallery_I18n_Test.php
@@ -28,9 +28,10 @@ class Gallery_I18n_Test extends Unit_Test_Case {
'locale_dir' => VARPATH . 'locale/');
$this->i18n = Gallery_I18n::instance($config);
- ORM::factory("incoming_translation")
+ db::build()
+ ->delete("incoming_translations")
->where("locale", "=", "te_ST")
- ->delete_all();
+ ->execute();
$messages_te_ST = array(
array('Hello world', 'Hallo Welt'),
diff --git a/modules/gallery/tests/Gallery_Installer_Test.php b/modules/gallery/tests/Gallery_Installer_Test.php
index 43399fb4..74a07b1a 100644
--- a/modules/gallery/tests/Gallery_Installer_Test.php
+++ b/modules/gallery/tests/Gallery_Installer_Test.php
@@ -41,7 +41,7 @@ class Gallery_Installer_Test extends Unit_Test_Case {
$this->assert_equal("Gallery", $root->title);
$this->assert_equal(1, $root->left_ptr);
$this->assert_equal($max_right_ptr, $root->right_ptr);
- $this->assert_equal(null, $root->parent_id);
+ $this->assert_equal(0, $root->parent_id);
$this->assert_equal(1, $root->level);
}
}
diff --git a/modules/gallery/tests/Gallery_Rest_Helper_Test.php b/modules/gallery/tests/Gallery_Rest_Helper_Test.php
index cd0aabae..f8cf6190 100644
--- a/modules/gallery/tests/Gallery_Rest_Helper_Test.php
+++ b/modules/gallery/tests/Gallery_Rest_Helper_Test.php
@@ -94,8 +94,8 @@ class Gallery_Rest_Helper_Test extends Unit_Test_Case {
"path" => $photo->relative_url(),
"thumb_url" => $photo->thumb_url(),
"thumb_dimensions" => array(
- "width" => $photo->thumb_width,
- "height" => $photo->thumb_height),
+ "width" => (string)$photo->thumb_width,
+ "height" => (string)$photo->thumb_height),
"has_thumb" => true,
"title" => $photo->title))))),
gallery_rest::get($request));
@@ -115,14 +115,14 @@ class Gallery_Rest_Helper_Test extends Unit_Test_Case {
"parent_path" => $child->relative_url(),
"title" => $photo->title,
"thumb_url" => $photo->thumb_url(),
- "thumb_size" => array("height" => $photo->thumb_height,
- "width" => $photo->thumb_width),
+ "thumb_size" => array("height" => (string)$photo->thumb_height,
+ "width" => (string)$photo->thumb_width),
"resize_url" => $photo->resize_url(),
"resize_size" => array("height" => $photo->resize_height,
"width" => $photo->resize_width),
"url" => $photo->file_url(),
- "size" => array("height" => $photo->height,
- "width" => $photo->width),
+ "size" => array("height" => (string)$photo->height,
+ "width" => (string)$photo->width),
"description" => $photo->description,
"slug" => $photo->slug))),
gallery_rest::get($request));
diff --git a/modules/gallery/tests/Item_Model_Test.php b/modules/gallery/tests/Item_Model_Test.php
index bf5fca1a..d03a03f4 100644
--- a/modules/gallery/tests/Item_Model_Test.php
+++ b/modules/gallery/tests/Item_Model_Test.php
@@ -52,7 +52,7 @@ class Item_Model_Test extends Unit_Test_Case {
public function updating_view_count_only_doesnt_change_updated_date_test() {
$item = self::_create_random_item();
$item->reload();
- $this->assert_same(0, $item->view_count);
+ $this->assert_equal(0, $item->view_count);
// Force the updated date to something well known
db::build()
diff --git a/modules/gallery/tests/Url_Security_Test.php b/modules/gallery/tests/Url_Security_Test.php
new file mode 100644
index 00000000..de25880f
--- /dev/null
+++ b/modules/gallery/tests/Url_Security_Test.php
@@ -0,0 +1,43 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2009 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+class Url_Security_Test extends Unit_Test_Case {
+ public function setup() {
+ $this->save = array(Router::$current_uri, Router::$complete_uri, $_GET);
+ }
+
+ public function teardown() {
+ list(Router::$current_uri, Router::$complete_uri, $_GET) = $this->save;
+ }
+
+ public function xss_in_current_url_test() {
+ Router::$current_uri = "foo/<xss>/bar";
+ Router::$complete_uri = "foo/<xss>/bar?foo=bar";
+ $this->assert_same("foo/&lt;xss&gt;/bar", url::current());
+ $this->assert_same("foo/&lt;xss&gt;/bar?foo=bar", url::current(true));
+ }
+
+ public function xss_in_merged_url_test() {
+ Router::$current_uri = "foo/<xss>/bar";
+ Router::$complete_uri = "foo/<xss>/bar?foo=bar";
+ $_GET = array("foo" => "bar");
+ $this->assert_same("foo/&lt;xss&gt;/bar?foo=bar", url::merge(array()));
+ $this->assert_same("foo/&lt;xss&gt;/bar?foo=bar&amp;a=b", url::merge(array("a" => "b")));
+ }
+} \ No newline at end of file
diff --git a/modules/gallery/tests/xss_data.txt b/modules/gallery/tests/xss_data.txt
index a264286c..1530c73e 100644
--- a/modules/gallery/tests/xss_data.txt
+++ b/modules/gallery/tests/xss_data.txt
@@ -137,7 +137,7 @@ modules/gallery/views/l10n_client.html.php 26 DIRTY $strin
modules/gallery/views/l10n_client.html.php 32 DIRTY $l10n_search_form
modules/gallery/views/l10n_client.html.php 41 DIRTY access::csrf_form_field()
modules/gallery/views/l10n_client.html.php 42 DIRTY form::hidden("l10n-message-key")
-modules/gallery/views/l10n_client.html.php 43 DIRTY form::textarea("l10n-edit-translation","",' rows="5" class="translationField"')
+modules/gallery/views/l10n_client.html.php 43 DIRTY form::textarea("l10n-edit-translation","",' id="l10n-edit-translation" rows="5" class="translationField"')
modules/gallery/views/l10n_client.html.php 46 DIRTY form::textarea("l10n-edit-plural-translation-zero","",' rows="2"')
modules/gallery/views/l10n_client.html.php 50 DIRTY form::textarea("l10n-edit-plural-translation-one","",' rows="2"')
modules/gallery/views/l10n_client.html.php 54 DIRTY form::textarea("l10n-edit-plural-translation-two","",' rows="2"')
diff --git a/modules/gallery/views/l10n_client.html.php b/modules/gallery/views/l10n_client.html.php
index 9d14bbb2..47a45e29 100644
--- a/modules/gallery/views/l10n_client.html.php
+++ b/modules/gallery/views/l10n_client.html.php
@@ -40,7 +40,7 @@
<form method="post" action="<?= url::site("l10n_client/save") ?>" id="g-l10n-client-save-form">
<?= access::csrf_form_field() ?>
<?= form::hidden("l10n-message-key") ?>
- <?= form::textarea("l10n-edit-translation", "", ' rows="5" class="translationField"') ?>
+ <?= form::textarea("l10n-edit-translation", "", ' id="l10n-edit-translation" rows="5" class="translationField"') ?>
<div id="plural-zero" class="translationField hidden">
<label for="l10n-edit-plural-translation-zero">[zero]</label>
<?= form::textarea("l10n-edit-plural-translation-zero", "", ' rows="2"') ?>