--- /dev/null
+<?php
+/**
+ * Copyright 2007 Maintainable Software, LLC
+ * Copyright 2006-2009 The Horde Project (http://www.horde.org/)
+ *
+ * @author Mike Naberezny <mike@maintainable.com>
+ * @author Derek DeVries <derek@maintainable.com>
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ * @license http://opensource.org/licenses/bsd-license.php
+ * @category Horde
+ * @package Horde_View
+ * @subpackage Helper
+ */
+
+/**
+ * View helpers for URLs
+ *
+ * @author Mike Naberezny <mike@maintainable.com>
+ * @author Derek DeVries <derek@maintainable.com>
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ * @license http://opensource.org/licenses/bsd-license.php
+ * @category Horde
+ * @package Horde_View
+ * @subpackage Helper
+ */
+class Horde_View_Helper_Text extends Horde_View_Helper_Base
+{
+ /**
+ * @var array
+ */
+ protected $_cycles = array();
+
+ /**
+ * @var Horde_Support_Inflector
+ */
+ protected $_inflector;
+
+ /**
+ * Escapes a value for output in a view template.
+ *
+ * <code>
+ * <p><?= $this->h($this->templateVar) ?></p>
+ * </code>
+ *
+ * @param mixed $var The output to escape.
+ * @return mixed The escaped value.
+ */
+ public function h($var)
+ {
+ return htmlentities($var, ENT_QUOTES, 'utf-8');
+ }
+
+ /**
+ * Pluralize the $singular word unless $count is one. If $plural
+ * form is not supplied, inflector will be used.
+ *
+ * @param integer $count Count determines singular or plural
+ * @param string $singular Singular form
+ * @param string|null $plural Plural form (optional)
+ */
+ public function pluralize($count, $singular, $plural = null)
+ {
+ if ($count == '1') {
+ $word = $singular;
+ } else if ($plural) {
+ $word = $plural;
+ } else {
+ if (!$this->_inflector) { $this->_inflector = new Horde_Support_Inflector(); }
+ $word = $this->_inflector->pluralize($singular);
+ }
+
+ return "$count $word";
+ }
+
+ /**
+ * Creates a Cycle object whose __toString() method cycles through elements of an
+ * array every time it is called. This can be used for example, to alternate
+ * classes for table rows:
+ *
+ * <? foreach($items as $item): ?>
+ * <tr class="<?= $this->cycle("even", "odd") ?>">
+ * <td>item</td>
+ * </tr>
+ * <% endforeach %>
+ *
+ * You can use named cycles to allow nesting in loops. Passing an array as
+ * the last parameter with a <tt>name</tt> key will create a named cycle.
+ * You can manually reset a cycle by calling resetCycle() and passing the
+ * name of the cycle.
+ *
+ * <? foreach($items as $item): ?>
+ * <tr class="<?= $this->cycle("even", "odd", array('name' => "row_class")) ?>">
+ * <td>
+ * <? foreach ($item->values as $value) ?>
+ * <span style="color:<?= $this->cycle("red", "green", "blue", array('name' => "colors")) ?>">
+ * value
+ * </span>
+ * <% end %>
+ * <? $this->resetCycle("colors") ?>
+ * </td>
+ * </tr>
+ * <% endforeach %>
+ *
+ */
+ public function cycle($firstValue)
+ {
+ $values = func_get_args();
+
+ $last = end($values);
+ if (is_array($last)) {
+ $options = array_pop($values);
+ $name = isset($options['name']) ? $options['name'] : 'default';
+ } else {
+ $name = 'default';
+ }
+
+ if (empty($this->_cycles[$name]) || $this->_cycles[$name]->getValues() != $values) {
+ $this->_cycles[$name] = new Horde_View_Helper_Text_Cycle($values);
+ }
+ return $this->_cycles[$name];
+ }
+
+ /**
+ * Resets a cycle so that it starts from the first element the next time
+ * it is called. Pass in $name to reset a named cycle.
+ *
+ * @param string $name Name of cycle to reset (defaults to "default")
+ * @return void
+ */
+ public function resetCycle($name = 'default')
+ {
+ if (isset($this->_cycles[$name])) {
+ $this->_cycles[$name]->reset();
+ }
+ }
+
+ /**
+ * Highlights the phrase where it is found in the text by surrounding it like
+ * <strong class="highlight">I'm highlighted</strong>. The Highlighter can
+ * be customized by passing highlighter as a single-quoted string with $1
+ * where the prhase is supposed to be inserted.
+ *
+ * @param string $text
+ * @param string $phrase
+ * @param string $highlighter
+ */
+ public function highlight($text, $phrase, $highlighter=null)
+ {
+ if (empty($highlighter)) {
+ $highlighter='<strong class="highlight">$1</strong>';
+ }
+ if (empty($phrase) || empty($text)) {
+ return $text;
+ }
+ return preg_replace("/($phrase)/", $highlighter, $text);
+ }
+
+ /**
+ * If $text is longer than $length, $text will be truncated to the
+ * length of $length and the last three characters will be replaced
+ * with the $truncateString.
+ *
+ * <code>
+ * $this->truncate("Once upon a time in a world far far away", 14);
+ * => Once upon a...
+ * </code>
+ *
+ * @param string $text
+ * @param integer $length
+ * @param string $truncateString
+ * @return string
+ */
+ public function truncate($text, $length=30, $truncateString = '...')
+ {
+ if (empty($text)) { return $text; }
+ $l = $length - strlen($truncateString);
+ return strlen($text) > $length ? substr($text, 0, $l).$truncateString : $text;
+ }
+
+ /**
+ * Limit a string to a given maximum length in a smarter way than just using
+ * substr. Namely, cut from the MIDDLE instead of from the end so that if
+ * we're doing this on (for instance) a bunch of binder names that start off
+ * with the same verbose description, and then are different only at the
+ * very end, they'll still be different from one another after truncating.
+ *
+ * <code>
+ * <?php
+ * ...
+ * $str = "The quick brown fox jumps over the lazy dog tomorrow morning.";
+ * $shortStr = truncateMiddle($str, 40);
+ * // $shortStr = "The quick brown fox... tomorrow morning."
+ * ...
+ * ?>
+ * </code>
+ *
+ * @todo This is not a Rails helper...
+ * @param string $str
+ * @param int $maxLength
+ * @param string $joiner
+ * @return string
+ */
+ public function truncateMiddle($str, $maxLength=80, $joiner='...')
+ {
+ if (strlen($str) <= $maxLength) {
+ return $str;
+ }
+ $maxLength = $maxLength - strlen($joiner);
+ if ($maxLength <= 0) {
+ return $str;
+ }
+ $startPieceLength = (int) ceil($maxLength / 2);
+ $endPieceLength = (int) floor($maxLength / 2);
+ $trimmedString = substr($str, 0, $startPieceLength) . $joiner;
+ if ($endPieceLength > 0) {
+ $trimmedString .= substr($str, (-1 * $endPieceLength));
+ }
+ return $trimmedString;
+ }
+
+ /**
+ * Allow linebreaks in a string after slashes or underscores
+ *
+ * @todo This is not a Rails helper...
+ * @param string $str
+ * @return string
+ */
+ public function makeBreakable($str)
+ {
+ return str_replace(
+ array('/', '_'),
+ array('/<wbr>', '_<wbr>'),
+ $str
+ );
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * Copyright 2007 Maintainable Software, LLC
+ * Copyright 2006-2009 The Horde Project (http://www.horde.org/)
+ *
+ * @author Mike Naberezny <mike@maintainable.com>
+ * @author Derek DeVries <derek@maintainable.com>
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ * @license http://opensource.org/licenses/bsd-license.php
+ * @category Horde
+ * @package Horde_View
+ * @subpackage Helper
+ */
+
+/**
+ * View helpers for URLs
+ *
+ * @author Mike Naberezny <mike@maintainable.com>
+ * @author Derek DeVries <derek@maintainable.com>
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ * @license http://opensource.org/licenses/bsd-license.php
+ * @category Horde
+ * @package Horde_View
+ * @subpackage Helper
+ */
+class Horde_View_Helper_Text_Cycle
+{
+ /**
+ * Array of values to cycle through
+ * @var array
+ */
+ private $_values;
+
+ /**
+ * Construct a new cycler
+ *
+ * @param array $values Values to cycle through
+ */
+ public function __construct($values)
+ {
+ if (func_num_args() != 1 || !is_array($values)) {
+ throw new InvalidArgumentException();
+ }
+
+ if (count($values) < 2) {
+ throw new InvalidArgumentException('must have at least two values');
+ }
+
+ $this->_values = $values;
+ $this->reset();
+ }
+
+ /**
+ * Returns the current element in the cycle
+ * and advance the cycle
+ *
+ * @return mixed Current element
+ */
+ public function __toString()
+ {
+ $value = next($this->_values);
+ return strval(($value !== false) ? $value : reset($this->_values));
+ }
+
+ /**
+ * Reset the cycle
+ */
+ public function reset()
+ {
+ end($this->_values);
+ }
+
+ /**
+ * Returns the values of this cycler.
+ *
+ * @return array
+ */
+ public function getValues()
+ {
+ return $this->_values;
+ }
+
+}
<file name="FormTag.php" role="php" />
<file name="Javascript.php" role="php" />
<file name="Tag.php" role="php" />
+ <dir name="Text">
+ <file name="Cycle.php" role="php" />
+ </dir> <!-- /lib/Horde/View/Helper/Text -->
+ <file name="Text.php" role="php" />
<file name="Url.php" role="php" />
</dir> <!-- /lib/Horde/View/Helper -->
<file name="Base.php" role="php" />
<install name="lib/Horde/View/Helper/FormTag.php" as="Horde/View/Helper/FormTag.php" />
<install name="lib/Horde/View/Helper/Javascript.php" as="Horde/View/Helper/Javascript.php" />
<install name="lib/Horde/View/Helper/Tag.php" as="Horde/View/Helper/Tag.php" />
+ <install name="lib/Horde/View/Helper/Text/Cycle.php" as="Horde/View/Helper/Text/Cycle.php" />
+ <install name="lib/Horde/View/Helper/Text.php" as="Horde/View/Helper/Text.php" />
<install name="lib/Horde/View/Helper/Url.php" as="Horde/View/Helper/Url.php" />
<install name="lib/Horde/View/Base.php" as="Horde/View/Base.php" />
<install name="lib/Horde/View/Exception.php" as="Horde/View/Exception.php" />
--- /dev/null
+<?php
+/**
+ * Copyright 2007-2008 Maintainable Software, LLC
+ * Copyright 2008-2009 The Horde Project (http://www.horde.org/)
+ *
+ * @author Mike Naberezny <mike@maintainable.com>
+ * @author Derek DeVries <derek@maintainable.com>
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ * @license http://opensource.org/licenses/bsd-license.php
+ * @category Horde
+ * @package Horde_View
+ * @subpackage UnitTests
+ */
+
+/**
+ * @group view
+ * @author Mike Naberezny <mike@maintainable.com>
+ * @author Derek DeVries <derek@maintainable.com>
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ * @license http://opensource.org/licenses/bsd-license.php
+ * @category Horde
+ * @package Horde_View
+ * @subpackage UnitTests
+ */
+class Horde_View_Helper_TextTest extends Horde_Test_Case
+{
+ public function setUp()
+ {
+ $this->helper = new Horde_View_Helper_Text(new Horde_View());
+ }
+
+ // test escaping data
+ public function testEscape()
+ {
+ $text = "Test 'escaping html' \"quotes\" and & amps";
+ $expected = "Test 'escaping html' "quotes" and & amps";
+ $this->assertEquals($expected, $this->helper->h($text));
+ }
+
+ // test truncate
+ public function testTruncate()
+ {
+ $str = 'The quick brown fox jumps over the lazy dog tomorrow morning.';
+ $expected = 'The quick brown fox jumps over the la...';
+ $this->assertEquals($expected, $this->helper->truncate($str, 40));
+ }
+
+ // test truncate
+ public function testTruncateMiddle()
+ {
+ $str = 'The quick brown fox jumps over the lazy dog tomorrow morning.';
+ $expected = 'The quick brown fox... tomorrow morning.';
+ $this->assertEquals($expected, $this->helper->truncateMiddle($str, 40));
+ }
+
+ // text too short to trucate
+ public function testTruncateMiddleTooShort()
+ {
+ $str = 'The quick brown fox jumps over the dog.';
+ $expected = 'The quick brown fox jumps over the dog.';
+ $this->assertEquals($expected, $this->helper->truncateMiddle($str, 40));
+ }
+
+
+ // test highlighter
+ public function testHighlightDefault()
+ {
+ $str = 'The quick brown fox jumps over the dog.';
+ $expected = 'The quick <strong class="highlight">brown</strong> fox jumps over the dog.';
+ $this->assertEquals($expected, $this->helper->highlight($str, 'brown'));
+ }
+
+ // test failure to highlight
+ public function testHighlightCustom()
+ {
+ $str = 'The quick brown fox jumps over the dog.';
+ $expected = 'The quick <em>brown</em> fox jumps over the dog.';
+ $this->assertEquals($expected, $this->helper->highlight($str, 'brown', '<em>$1</em>'));
+ }
+
+ // test failure to highlight
+ public function testHighlightNoMatch()
+ {
+ $str = 'The quick brown fox jumps over the dog.';
+ $this->assertEquals($str, $this->helper->highlight($str, 'black'));
+ }
+
+ public function testCycleClass()
+ {
+ $value = new Horde_View_Helper_Text_Cycle(array('one', 2, '3'));
+
+ $this->assertEquals('one', (string)$value);
+ $this->assertEquals('2', (string)$value);
+ $this->assertEquals('3', (string)$value);
+ $this->assertEquals('one', (string)$value);
+ $value->reset();
+ $this->assertEquals('one', (string)$value);
+ $this->assertEquals('2', (string)$value);
+ $this->assertEquals('3', (string)$value);
+ }
+
+ public function testCycleClassWithInvalidArguments()
+ {
+ try {
+ $value = new Horde_View_Helper_Text_Cycle('bad');
+ $this->fail();
+ } catch (InvalidArgumentException $e) {}
+
+ try {
+ $value = new Horde_View_Helper_Text_Cycle(array('foo'));
+ $this->fail();
+ } catch (InvalidArgumentException $e) {}
+
+ try {
+ $value = new Horde_View_Helper_Text_Cycle(array('foo', 'bar'), 'bad-arg');
+ $this->fail();
+ } catch (InvalidArgumentException $e) {}
+ }
+
+ public function testCycleResetsWithNewValues()
+ {
+ $this->assertEquals('even', (string)$this->helper->cycle('even', 'odd'));
+ $this->assertEquals('odd', (string)$this->helper->cycle('even', 'odd'));
+ $this->assertEquals('even', (string)$this->helper->cycle('even', 'odd'));
+ $this->assertEquals('1', (string)$this->helper->cycle(1, 2, 3));
+ $this->assertEquals('2', (string)$this->helper->cycle(1, 2, 3));
+ $this->assertEquals('3', (string)$this->helper->cycle(1, 2, 3));
+ }
+
+ public function testNamedCycles()
+ {
+ $this->assertEquals('1', (string)$this->helper->cycle(1, 2, 3, array('name' => 'numbers')));
+ $this->assertEquals('red', (string)$this->helper->cycle('red', 'blue', array('name' => 'colors')));
+ $this->assertEquals('2', (string)$this->helper->cycle(1, 2, 3, array('name' => 'numbers')));
+ $this->assertEquals('blue', (string)$this->helper->cycle('red', 'blue', array('name' => 'colors')));
+ $this->assertEquals('3', (string)$this->helper->cycle(1, 2, 3, array('name' => 'numbers')));
+ $this->assertEquals('red', (string)$this->helper->cycle('red', 'blue', array('name' => 'colors')));
+ }
+
+ public function testDefaultNamedCycle()
+ {
+ $this->assertEquals('1', (string)$this->helper->cycle(1, 2, 3));
+ $this->assertEquals('2', (string)$this->helper->cycle(1, 2, 3, array('name' => 'default')));
+ $this->assertEquals('3', (string)$this->helper->cycle(1, 2, 3));
+ }
+
+ public function testResetCycle()
+ {
+ $this->assertEquals('1', (string)$this->helper->cycle(1, 2, 3));
+ $this->assertEquals('2', (string)$this->helper->cycle(1, 2, 3));
+ $this->helper->resetCycle();
+ $this->assertEquals('1', (string)$this->helper->cycle(1, 2, 3));
+ }
+
+ public function testResetUnknownCycle()
+ {
+ $this->helper->resetCycle('colors');
+ }
+
+ public function testResetNamedCycle()
+ {
+ $this->assertEquals('1', (string)$this->helper->cycle(1, 2, 3, array('name' => 'numbers')));
+ $this->assertEquals('red', (string)$this->helper->cycle('red', 'blue', array('name' => 'colors')));
+ $this->helper->resetCycle('numbers');
+ $this->assertEquals('1', (string)$this->helper->cycle(1, 2, 3, array('name' => 'numbers')));
+ $this->assertEquals('blue', (string)$this->helper->cycle('red', 'blue', array('name' => 'colors')));
+ $this->assertEquals('2', (string)$this->helper->cycle(1, 2, 3, array('name' => 'numbers')));
+ $this->assertEquals('red', (string)$this->helper->cycle('red', 'blue', array('name' => 'colors')));
+ }
+
+ public function testPluralization()
+ {
+ $this->assertEquals('1 count', $this->helper->pluralize(1, 'count'));
+ $this->assertEquals('2 counts', $this->helper->pluralize(2, 'count'));
+ $this->assertEquals('1 count', $this->helper->pluralize('1', 'count'));
+ $this->assertEquals('2 counts', $this->helper->pluralize('2', 'count'));
+ $this->assertEquals('1,066 counts', $this->helper->pluralize('1,066', 'count'));
+ $this->assertEquals('1.25 counts', $this->helper->pluralize('1.25', 'count'));
+ $this->assertEquals('2 counters', $this->helper->pluralize('2', 'count', 'counters'));
+ }
+
+}