Unified usage of the horizontal ellipsis character
authorMarcel Werk <burntime@woltlab.com>
Tue, 25 Dec 2012 21:06:37 +0000 (22:06 +0100)
committerMarcel Werk <burntime@woltlab.com>
Tue, 25 Dec 2012 21:06:37 +0000 (22:06 +0100)
wcfsetup/install/files/lib/system/template/plugin/PagesFunctionTemplatePlugin.class.php
wcfsetup/install/files/lib/util/StringUtil.class.php
wcfsetup/install/lang/de.xml
wcfsetup/install/lang/en.xml

index 5f64bd3886b7515a466c43df77dc9e50260ab309..393cf893a39cfcca769f7e00161891bb58f8e820 100644 (file)
@@ -176,7 +176,7 @@ class PagesFunctionTemplatePlugin implements IFunctionTemplatePlugin {
                                        $html .= $this->makeLink($link, 2, $tagArgs['page']);
                                }
                                else {
-                                       $html .= '<li class="button jumpTo"><a title="'.WCF::getLanguage()->getDynamicVariable('wcf.global.page.jumpTo').'" class="jsTooltip">&hellip;</a></li>'."\n";
+                                       $html .= '<li class="button jumpTo"><a title="'.WCF::getLanguage()->getDynamicVariable('wcf.global.page.jumpTo').'" class="jsTooltip">'.StringUtil::HELLIP.'</a></li>'."\n";
                                }
                        }
                        
@@ -191,7 +191,7 @@ class PagesFunctionTemplatePlugin implements IFunctionTemplatePlugin {
                                        $html .= $this->makeLink($link, $tagArgs['pages'] - 1, $tagArgs['page']);
                                }
                                else {
-                                       $html .= '<li class="button jumpTo"><a title="'.WCF::getLanguage()->getDynamicVariable('wcf.global.page.jumpTo').'" class="jsTooltip">&hellip;</a></li>'."\n";
+                                       $html .= '<li class="button jumpTo"><a title="'.WCF::getLanguage()->getDynamicVariable('wcf.global.page.jumpTo').'" class="jsTooltip">'.StringUtil::HELLIP.'</a></li>'."\n";
                                }
                        }
                        
index 06a347b5f1b975e07ce4eb3e5d26bf7703cd2650..228275b25778bf1c06cb8d6c51087bcd4ae6fa23 100644 (file)
@@ -5,7 +5,7 @@ use wcf\system\WCF;
 /**
  * Contains string-related functions.
  * 
- * @author     Marcel Werk
+ * @author     Oliver Kliebisch, Marcel Werk 
  * @copyright  2001-2012 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    com.woltlab.wcf
@@ -19,6 +19,12 @@ final class StringUtil {
                        ))*\s*/?>~ix';
        const HTML_COMMENT_PATTERN = '~<!--(.*?)-->~';
        
+       /**
+        * utf8 bytes of the horizontal ellipsis char
+        * @var string
+        */
+       const HELLIP = "\xE2\x80\xA6";
+       
        /**
         * Returns a salted hash of the given value.
         * 
@@ -587,7 +593,7 @@ final class StringUtil {
         * @param       boolean         $breakWords     should words be broken in the middle
         * @return      string
         */
-       public static function truncate($string, $length = 80, $etc = '…', $breakWords = false) {
+       public static function truncate($string, $length = 80, $etc = StringUtil::HELLIP, $breakWords = false) {
                if ($length == 0) {
                        return '';
                }
@@ -606,6 +612,118 @@ final class StringUtil {
                }
        }
        
+       /**
+        * Truncates a string containing HTML code and keeps the HTML syntax intact.
+        *
+        * @param       string          $string                 string which shall be truncated
+        * @param       integer         $length                 string length after truncating
+        * @param       boolean         $wordWrap               if true words will not be split and the return string might be shorter than $length
+        * @param       string          $etc                    ending string which will be appended after truncating
+        * @return      string                                  truncated string
+        */
+       public static function truncateHTML($string, $length = 500, $wordWrap = true, $etc = StringUtil::HELLIP) {
+               if (StringUtil::length(StringUtil::stripHTML($string)) <= $length) {
+                       return $string;
+               }
+               $openTags = array();
+               $truncatedString = '';
+       
+               // initalize length counter with the ending length
+               $totalLength = StringUtil::length($etc);
+       
+               preg_match_all('/(<\/?([\w+]+)[^>]*>)?([^<>]*)/', $string, $tags, PREG_SET_ORDER);
+       
+               foreach ($tags as $tag) {
+                       // filter standalone html tags
+                       if (!preg_match('/area|base|basefont|br|col|frame|hr|img|input|isindex|link|meta|param/s', $tag[2])) {
+                               // look for opening tags
+                               if (preg_match('/<[\w]+[^>]*>/s', $tag[0])) {
+                                       array_unshift($openTags, $tag[2]);
+                               }
+                               /**
+                                * look for closing tags and check if this tag has a corresponding opening tag
+                                * and omit the opening tag if it has been closed already
+                                */
+                               else if (preg_match('/<\/([\w]+)[^>]*>/s', $tag[0], $closeTag)) {
+                                       $position = array_search($closeTag[1], $openTags);
+                                       if ($position !== false) {
+                                               array_splice($openTags, $position, 1);
+                                       }
+                               }
+                       }
+                       // append tag
+                       $truncatedString .= $tag[1];
+       
+                       // get length of the content without entities. If the content is too long, keep entities intact
+                       $contentLength = StringUtil::length(StringUtil::decodeHTML($tag[3]));
+                       if ($contentLength + $totalLength > $length) {
+                               $left = $length - $totalLength;
+                               $entitiesLength = 0;
+                               if (preg_match_all('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i', $tag[3], $entities, PREG_OFFSET_CAPTURE)) {
+                                       foreach ($entities[0] as $entity) {
+                                               if ($entity[1] + 1 - $entitiesLength <= $left) {
+                                                       $left--;
+                                                       $entitiesLength += StringUtil::length($entity[0]);
+                                               }
+                                               else {
+                                                       break;
+                                               }
+                                       }
+                               }
+                               $truncatedString .= StringUtil::substring($tag[3], 0 , $left + $entitiesLength);
+                               break;
+                       }
+                       else {
+                               $truncatedString .= $tag[3];
+                               $totalLength += $contentLength;
+                       }
+                       if ($totalLength >= $length) {
+                               break;
+                       }
+               }
+       
+               // if word wrap is active search for the last word change
+               if ($wordWrap) {
+                       $lastWhitespace = StringUtil::lastIndexOf($truncatedString, ' ');
+                       // check if inside a tag
+                       $lastOpeningTag = StringUtil::lastIndexOf($truncatedString, '<');
+                       if ($lastOpeningTag < $lastWhitespace) {
+                               $lastClosingTag = StringUtil::lastIndexOf($truncatedString, '>');
+                               if (($lastClosingTag === false || $lastClosingTag > $lastWhitespace) && $lastClosingTag > $lastOpeningTag) {
+                                       $lastWhitespace = $lastOpeningTag;
+                                       array_shift($openTags);
+                                       if ($truncatedString[$lastWhitespace] != ' ') {
+                                               $firstPart = StringUtil::substring($truncatedString, 0, $lastWhitespace);
+                                               $secondPart = StringUtil::substring($truncatedString, $lastWhitespace + 1);
+                                               $truncatedString = $firstPart.' '.$secondPart;
+                                       }
+                               }
+                       }
+                       if ($lastWhitespace !== false) {
+                               $endString = StringUtil::substring($truncatedString, $lastWhitespace);
+                               preg_match_all('/<\/([a-z]+)>/', $endString, $tagOverhead, PREG_SET_ORDER);
+                               if (count($tagOverhead)) {
+                                       foreach ($tagOverhead as $tag) {
+                                               if (!in_array($tag[1], $openTags)) {
+                                                       array_unshift($openTags, $tag[1]);
+                                               }
+                                       }
+                               }
+                               $truncatedString = StringUtil::substring($truncatedString, 0, $lastWhitespace);
+                       }
+               }
+       
+               // close all open tags
+               foreach ($openTags as $tag) {
+                       $truncatedString .= '</'.$tag.'>';
+               }
+       
+               // add etc
+               $truncatedString .= $etc;
+               
+               return $truncatedString;
+       }
+       
        /**
         * Splits given string into smaller chunks.
         * 
index 64b5999ff80d0da188051cd87ccaa13d816fc0c8..f98f68c385e2a6b81ed706e96bf511dc4a937ecc 100644 (file)
                <item name="wcf.acp.package.installation.packageStatus.installed"><![CDATA[bereits installiert]]></item>
                <item name="wcf.acp.package.installation.packageStatus.missing"><![CDATA[fehlt]]></item>
                <item name="wcf.acp.package.installation.packageStatus.missingVersion"><![CDATA[fehlende Paket-Version (installierte Version: {$package.existingVersion})]]></item>
-               <item name="wcf.acp.package.installation.step.install"><![CDATA[„{$packageName}“ wird installiert &hellip;]]></item>
+               <item name="wcf.acp.package.installation.step.install"><![CDATA[„{$packageName}“ wird installiert ]]></item>
                <item name="wcf.acp.package.installation.step.install.success"><![CDATA[Installation abgeschlossen]]></item>
-               <item name="wcf.acp.package.installation.step.prepare"><![CDATA[Installation wird vorbereitet &hellip;]]></item>
+               <item name="wcf.acp.package.installation.step.prepare"><![CDATA[Installation wird vorbereitet ]]></item>
                <item name="wcf.acp.package.installation.title"><![CDATA[Installation]]></item>
                <item name="wcf.acp.package.installation.requiredVersion"><![CDATA[Benötigte Version]]></item>
                <item name="wcf.acp.package.list"><![CDATA[Paketliste]]></item>
index 0b66f850ae7cd7d294db0b6602acd4813aced358..58dc396cf369cf6851712d4f2aa5002f9fd2dd44 100644 (file)
                <item name="wcf.acp.package.installation.packageStatus.installed"><![CDATA[already installed]]></item>
                <item name="wcf.acp.package.installation.packageStatus.missing"><![CDATA[missing]]></item>
                <item name="wcf.acp.package.installation.packageStatus.missingVersion"><![CDATA[missing package version (installed version: {$package.existingVersion})]]></item>
-               <item name="wcf.acp.package.installation.step.install"><![CDATA[Installing “{$packageName}” &hellip;]]></item>
+               <item name="wcf.acp.package.installation.step.install"><![CDATA[Installing “{$packageName}” ]]></item>
                <item name="wcf.acp.package.installation.step.install.success"><![CDATA[Installation completed]]></item>
-               <item name="wcf.acp.package.installation.step.prepare"><![CDATA[Prepairing installation &hellip;]]></item>
+               <item name="wcf.acp.package.installation.step.prepare"><![CDATA[Prepairing installation ]]></item>
                <item name="wcf.acp.package.installation.title"><![CDATA[Installation]]></item>
                <item name="wcf.acp.package.installation.requiredVersion"><![CDATA[Required version]]></item>
                <item name="wcf.acp.package.list"><![CDATA[Package list]]></item>