1: <?php
2: /**
3: * Copyright 2007 Maintainable Software, LLC
4: * Copyright 2006-2012 Horde LLC (http://www.horde.org/)
5: *
6: * @author Mike Naberezny <mike@maintainable.com>
7: * @author Derek DeVries <derek@maintainable.com>
8: * @author Chuck Hagenbuch <chuck@horde.org>
9: * @license http://www.horde.org/licenses/bsd
10: * @category Horde
11: * @package View
12: * @subpackage Helper
13: */
14:
15: /**
16: * View helpers for text
17: *
18: * @author Mike Naberezny <mike@maintainable.com>
19: * @author Derek DeVries <derek@maintainable.com>
20: * @author Chuck Hagenbuch <chuck@horde.org>
21: * @license http://www.horde.org/licenses/bsd
22: * @category Horde
23: * @package View
24: * @subpackage Helper
25: */
26: class Horde_View_Helper_Text extends Horde_View_Helper_Base
27: {
28: /**
29: * @var array
30: */
31: protected $_cycles = array();
32:
33: /**
34: * @var Horde_Support_Inflector
35: */
36: protected $_inflector;
37:
38: /**
39: * Escapes a value for output in a view template.
40: *
41: * <code>
42: * <p><?php echo $this->h($this->templateVar) ?></p>
43: * </code>
44: *
45: * @param mixed $var The output to escape.
46: *
47: * @return mixed The escaped value.
48: */
49: public function h($var)
50: {
51: return htmlspecialchars($var, ENT_QUOTES, $this->_view->getEncoding());
52: }
53:
54: /**
55: * Pluralizes the $singular word unless $count is one. If $plural
56: * form is not supplied, inflector will be used.
57: *
58: * @param integer $count Count determines singular or plural.
59: * @param string $singular Singular form.
60: * @param string $plural Plural form (optional).
61: */
62: public function pluralize($count, $singular, $plural = null)
63: {
64: if ($count == '1') {
65: $word = $singular;
66: } elseif ($plural) {
67: $word = $plural;
68: } else {
69: if (!$this->_inflector) {
70: $this->_inflector = new Horde_Support_Inflector();
71: }
72: $word = $this->_inflector->pluralize($singular);
73: }
74:
75: return "$count $word";
76: }
77:
78: /**
79: * Creates a Cycle object whose __toString() method cycles through elements
80: * of an array every time it is called.
81: *
82: * This can be used for example, to alternate classes for table rows:
83: *
84: * <code>
85: * <?php foreach($items as $item): ?>
86: * <tr class="<?php echo $this->cycle("even", "odd") ?>">
87: * <td>item</td>
88: * </tr>
89: * <?php endforeach ?>
90: * </code>
91: *
92: * You can use named cycles to allow nesting in loops. Passing an array as
93: * the last parameter with a <tt>name</tt> key will create a named cycle.
94: * You can manually reset a cycle by calling resetCycle() and passing the
95: * name of the cycle:
96: *
97: * <code>
98: * <?php foreach($items as $item): ?>
99: * <tr class="<?php echo $this->cycle('even', 'odd', array('name' => 'row_class')) ?>">
100: * <td>
101: * <?php foreach ($item->values as $value): ?>
102: * <span style="color:<?php echo $this->cycle('red', 'green', 'blue', array('name' => 'colors')) ?>">
103: * <?php echo $value ?>
104: * </span>
105: * <?php endforeach ?>
106: * <?php $this->resetCycle('colors') ?>
107: * </td>
108: * </tr>
109: * <?php endforeach ?>
110: * </code>
111: */
112: public function cycle($firstValue)
113: {
114: $values = func_get_args();
115:
116: $last = end($values);
117: if (is_array($last)) {
118: $options = array_pop($values);
119: $name = isset($options['name']) ? $options['name'] : 'default';
120: } else {
121: $name = 'default';
122: }
123:
124: if (empty($this->_cycles[$name]) ||
125: $this->_cycles[$name]->getValues() != $values) {
126: $this->_cycles[$name] = new Horde_View_Helper_Text_Cycle($values);
127: }
128:
129: return $this->_cycles[$name];
130: }
131:
132: /**
133: * Resets a cycle so that it starts from the first element the next time
134: * it is called.
135: *
136: * Pass in $name to reset a named cycle.
137: *
138: * @param string $name Name of cycle to reset.
139: */
140: public function resetCycle($name = 'default')
141: {
142: if (isset($this->_cycles[$name])) {
143: $this->_cycles[$name]->reset();
144: }
145: }
146:
147: /**
148: * Highlights a phrase where it is found in the text by surrounding it
149: * like <strong class="highlight">I'm highlighted</strong>.
150: *
151: * The Highlighter can be customized by passing $highlighter as a string
152: * containing $1 as a placeholder where the phrase is supposed to be
153: * inserted.
154: *
155: * @param string $text A text containing phrases to highlight.
156: * @param string $phrase A phrase to highlight in $text.
157: * @param string $highlighter A highlighting replacement.
158: *
159: * @return string The highlighted text.
160: */
161: public function highlight($text, $phrase, $highlighter = null)
162: {
163: if (empty($highlighter)) {
164: $highlighter = '<strong class="highlight">$1</strong>';
165: }
166: if (empty($phrase) || empty($text)) {
167: return $text;
168: }
169: return preg_replace('/(' . preg_quote($phrase, '/') . ')/',
170: $highlighter,
171: $text);
172: }
173:
174: /**
175: * If $text is longer than $length, $text will be truncated to the length
176: * of $length and the last three characters will be replaced with the
177: * $truncateString.
178: *
179: * <code>
180: * $this->truncate('Once upon a time in a world far far away', 14);
181: * // => Once upon a...
182: * </code>
183: *
184: * @param string $text A text to truncate.
185: * @param integer $length The maximum length of the text
186: * @param string $truncateString Replacement string for the truncated
187: * text.
188: *
189: * @return string The truncated text.
190: */
191: public function truncate($text, $length = 30, $truncateString = '...')
192: {
193: if (empty($text)) {
194: return $text;
195: }
196: $l = $length - strlen($truncateString);
197: return strlen($text) > $length
198: ? substr($text, 0, $l) . $truncateString
199: : $text;
200: }
201:
202: /**
203: * Limits a string to a given maximum length in a smarter way than just
204: * using substr().
205: *
206: * Namely, cut from the MIDDLE instead of from the end so that if we're
207: * doing this on (for instance) a bunch of binder names that start off with
208: * the same verbose description, and then are different only at the very
209: * end, they'll still be different from one another after truncating.
210: *
211: * <code>
212: * $str = 'The quick brown fox jumps over the lazy dog tomorrow morning.';
213: * $shortStr = $this->truncateMiddle($str, 40);
214: * // $shortStr == 'The quick brown fox... tomorrow morning.'
215: * </code>
216: *
217: * @param string $str A text to truncate.
218: * @param integer $maxLength The maximum length of the text
219: * @param string $joiner Replacement string for the truncated text.
220: *
221: * @return string The truncated text.
222: */
223: public function truncateMiddle($str, $maxLength = 80, $joiner = '...')
224: {
225: if (strlen($str) <= $maxLength) {
226: return $str;
227: }
228: $maxLength = $maxLength - strlen($joiner);
229: if ($maxLength <= 0) {
230: return $str;
231: }
232: $startPieceLength = (int) ceil($maxLength / 2);
233: $endPieceLength = (int) floor($maxLength / 2);
234: $trimmedString = substr($str, 0, $startPieceLength) . $joiner;
235: if ($endPieceLength > 0) {
236: $trimmedString .= substr($str, (-1 * $endPieceLength));
237: }
238: return $trimmedString;
239: }
240:
241: /**
242: * Inserts HTML code to allow linebreaks in a string after slashes or
243: * underscores.
244: *
245: * @param string $str A string to mark up with linebreak markers.
246: *
247: * @return string The marked-up string.
248: */
249: public function makeBreakable($str)
250: {
251: return str_replace(
252: array('/', '_'),
253: array('/<wbr>', '_<wbr>'),
254: $str
255: );
256: }
257:
258: /**
259: * Removes smart quotes.
260: *
261: * @see http://shiflett.org/blog/2005/oct/convert-smart-quotes-with-php
262: *
263: * @param string $str A string with potential smart quotes.
264: *
265: * @return string The cleaned-up string.
266: */
267: public function cleanSmartQuotes($str)
268: {
269: $search = array(
270: '/\x96/',
271: '/\xE2\x80\x93/',
272: '/\x97/',
273: '/\xE2\x80\x94/',
274: '/\x91/',
275: '/\xE2\x80\x98/',
276: '/\x92/',
277: '/\xE2\x80\x99/',
278: '/\x93/',
279: '/\xE2\x80\x9C/',
280: '/\x94/',
281: '/\xE2\x80\x9D/',
282: '/\x85/',
283: '/\xE2\x80\xA6/',
284: '/\x95/',
285: '/\xE2\x80\xA2/',
286: '/\x09/',
287:
288: // The order of these is very important.
289: '/\xC2\xBC/',
290: '/\xBC/',
291: '/\xC2\xBD/',
292: '/\xBD/',
293: '/\xC2\xBE/',
294: '/\xBE/',
295: );
296:
297: $replace = array(
298: '-',
299: '-',
300: '--',
301: '--',
302: "'",
303: "'",
304: "'",
305: "'",
306: '"',
307: '"',
308: '"',
309: '"',
310: '...',
311: '...',
312: '*',
313: '*',
314: ' ',
315:
316: '1/4',
317: '1/4',
318: '1/2',
319: '1/2',
320: '3/4',
321: '3/4',
322: );
323:
324: return preg_replace($search, $replace, $str);
325: }
326: }
327: