<parent>wcf.acp.menu.link.cms</parent>
<permissions>admin.content.cms.canManagePage</permissions>
</acpmenuitem>
-
<acpmenuitem name="wcf.acp.menu.link.cms.page.add">
<controller><![CDATA[wcf\acp\form\PageAddForm]]></controller>
<parent>wcf.acp.menu.link.cms.page.list</parent>
<parent>wcf.acp.menu.link.cms</parent>
<permissions>admin.content.cms.canManageMenu</permissions>
</acpmenuitem>
-
<acpmenuitem name="wcf.acp.menu.link.cms.menu.add">
<controller><![CDATA[wcf\acp\form\MenuAddForm]]></controller>
<parent>wcf.acp.menu.link.cms.menu.list</parent>
<parent>wcf.acp.menu.link.cms</parent>
<permissions>admin.content.cms.canManageBox</permissions>
</acpmenuitem>
-
<acpmenuitem name="wcf.acp.menu.link.cms.box.add">
<controller><![CDATA[wcf\acp\form\BoxAddForm]]></controller>
<parent>wcf.acp.menu.link.cms.box.list</parent>
<permissions>admin.content.cms.canManageBox</permissions>
<icon>fa-plus</icon>
</acpmenuitem>
+
+ <acpmenuitem name="wcf.acp.menu.link.cms.media.list">
+ <controller><![CDATA[wcf\acp\page\MediaListPage]]></controller>
+ <parent>wcf.acp.menu.link.cms</parent>
+ <permissions>admin.content.cms.canManageMedia</permissions>
+ </acpmenuitem>
+ <acpmenuitem name="wcf.acp.menu.link.cms.media.add">
+ <controller><![CDATA[wcf\acp\form\MediaAddForm]]></controller>
+ <parent>wcf.acp.menu.link.cms.media.list</parent>
+ <permissions>admin.content.cms.canManageMedia</permissions>
+ <icon>fa-plus</icon>
+ </acpmenuitem>
</import>
</data>
</pages>
</action>
<!-- com.woltlab.wcf.tag -->
+
+ <!-- com.woltlab.wcf.media -->
+ <action name="insert">
+ <actionclassname><![CDATA[wcf\system\clipboard\action\MediaClipboardAction]]></actionclassname>
+ <showorder>1</showorder>
+ <pages>
+ <!--
+ the clipboard API requires a page but since the media clipboard can be used
+ in dialogs, we use this wildcard instead of a real page class
+ -->
+ <page><![CDATA[*]]></page>
+ </pages>
+ </action>
+ <action name="delete">
+ <actionclassname><![CDATA[wcf\system\clipboard\action\MediaClipboardAction]]></actionclassname>
+ <showorder>2</showorder>
+ <pages>
+ <page><![CDATA[*]]></page>
+ </pages>
+ </action>
+ <!-- /com.woltlab.wcf.media -->
</import>
</data>
<definitionname>com.woltlab.wcf.clipboardItem</definitionname>
<listclassname><![CDATA[wcf\data\tag\TagList]]></listclassname>
</type>
+ <type>
+ <name>com.woltlab.wcf.media</name>
+ <definitionname>com.woltlab.wcf.clipboardItem</definitionname>
+ <listclassname><![CDATA[wcf\data\media\ViewableMediaList]]></listclassname>
+ </type>
<!-- /clipboard items -->
<type>
<category name="dashboard.sidebar.recentActivities">
<parent>dashboard.sidebar</parent>
</category>
+
+ <!-- cms -->
+ <category name="cms"></category>
+ <category name="cms.media">
+ <parent>cms</parent>
+ </category>
+ <category name="cms.media.thumbnail">
+ <parent>cms.media</parent>
+ </category>
+ <!-- /cms -->
</categories>
<options>
<maxvalue>100</maxvalue>
</option>
<!-- /message.general.poll -->
+
+ <!-- cms.media.thumnail -->
+ <option name="media_small_thumbnail_width">
+ <categoryname>cms.media.thumbnail</categoryname>
+ <optiontype>integer</optiontype>
+ <defaultvalue>280</defaultvalue> <!-- TODO: temporary value -->
+ <minvalue>145</minvalue> <!-- TODO: temporary value -->
+ <maxvalue>400</maxvalue> <!-- TODO: temporary value -->
+ </option>
+ <option name="media_small_thumbnail_height">
+ <categoryname>cms.media.thumbnail</categoryname>
+ <optiontype>integer</optiontype>
+ <defaultvalue>210</defaultvalue> <!-- TODO: temporary value -->
+ <minvalue>145</minvalue> <!-- TODO: temporary value -->
+ <maxvalue>300</maxvalue> <!-- TODO: temporary value -->
+ </option>
+ <option name="media_small_thumbnail_retain_dimensions">
+ <categoryname>cms.media.thumbnail</categoryname>
+ <optiontype>boolean</optiontype>
+ <defaultvalue>1</defaultvalue>
+ </option>
+ <option name="media_medium_thumbnail_width">
+ <categoryname>cms.media.thumbnail</categoryname>
+ <optiontype>integer</optiontype>
+ <defaultvalue>560</defaultvalue> <!-- TODO: temporary value -->
+ <minvalue>400</minvalue> <!-- TODO: temporary value -->
+ <maxvalue>900</maxvalue> <!-- TODO: temporary value -->
+ </option>
+ <option name="media_medium_thumbnail_height">
+ <categoryname>cms.media.thumbnail</categoryname>
+ <optiontype>integer</optiontype>
+ <defaultvalue>420</defaultvalue> <!-- TODO: temporary value -->
+ <minvalue>300</minvalue> <!-- TODO: temporary value -->
+ <maxvalue>700</maxvalue> <!-- TODO: temporary value -->
+ </option>
+ <option name="media_medium_thumbnail_retain_dimensions">
+ <categoryname>cms.media.thumbnail</categoryname>
+ <optiontype>boolean</optiontype>
+ <defaultvalue>1</defaultvalue>
+ </option>
+ <option name="media_large_thumbnail_width">
+ <categoryname>cms.media.thumbnail</categoryname>
+ <optiontype>integer</optiontype>
+ <defaultvalue>1200</defaultvalue> <!-- TODO: temporary value -->
+ <minvalue>900</minvalue> <!-- TODO: temporary value -->
+ </option>
+ <option name="media_large_thumbnail_height">
+ <categoryname>cms.media.thumbnail</categoryname>
+ <optiontype>integer</optiontype>
+ <defaultvalue>900</defaultvalue> <!-- TODO: temporary value -->
+ <minvalue>700</minvalue> <!-- TODO: temporary value -->
+ </option>
+ <option name="media_large_thumbnail_retain_dimensions">
+ <categoryname>cms.media.thumbnail</categoryname>
+ <optiontype>boolean</optiontype>
+ <defaultvalue>1</defaultvalue>
+ </option>
+ <!-- cms.media.thumnail -->
</options>
</import>
</header>
{/if}
+{*TODO: remove dummy media manager dialog demonstration code later on*}
+<p class="button" id="mediaManagerButton">click</p>
+
+<script data-relocate="true">
+ $(function() {
+ require(['WoltLab/WCF/Media/Manager', 'Language', 'Permission'], function(MediaManager, Language, Permission) {
+ Language.addObject({
+ 'wcf.global.button.insert': '{lang}wcf.global.button.insert{/lang}',
+
+ 'wcf.media.insert': '{lang}wcf.media.insert{/lang}',
+ 'wcf.media.insert.imageSize': '{lang}wcf.media.insert.imageSize{/lang}',
+ 'wcf.media.insert.imageSize.small': '{lang __literal=true}wcf.media.insert.imageSize.small{/lang}',
+ 'wcf.media.insert.imageSize.medium': '{lang __literal=true}wcf.media.insert.imageSize.medium{/lang}',
+ 'wcf.media.insert.imageSize.large': '{lang __literal=true}wcf.media.insert.imageSize.large{/lang}',
+ 'wcf.media.insert.imageSize.original': '{lang __literal=true}wcf.media.insert.imageSize.original{/lang}',
+ 'wcf.media.manager': '{lang}wcf.media.manager{/lang}',
+ 'wcf.media.edit': '{lang}wcf.media.edit{/lang}',
+ 'wcf.media.imageDimensions.value': '{lang __literal=true}wcf.media.imageDimensions.value{/lang}',
+ 'wcf.media.button.insert': '{lang}wcf.media.button.insert{/lang}',
+ 'wcf.media.search.filetype': '{lang}wcf.media.search.filetype{/lang}',
+ 'wcf.media.search.noResults': '{lang}wcf.media.search.noResults{/lang}'
+ });
+
+ Permission.add('admin.content.cms.canManageMedia', {if $__wcf->session->getPermission('admin.content.cms.canManageMedia')}true{else}false{/if});
+
+ new MediaManager();
+ });
+ });
+</script>
+{* /end *}
+
{include file='userNotice'}
<div class="contentNavigation">
'wcf.global.form.error.greaterThan': '{lang __literal=true}wcf.global.form.error.greaterThan{/lang}',
'wcf.global.form.error.lessThan': '{lang __literal=true}wcf.global.form.error.lessThan{/lang}',
'wcf.global.form.input.maxItems': '{lang}wcf.global.form.input.maxItems{/lang}',
+ 'wcf.global.form.error.multilingual': '{lang}wcf.global.form.error.multilingual{/lang}',
'wcf.global.language.noSelection': '{lang}wcf.global.language.noSelection{/lang}',
'wcf.global.loading': '{lang}wcf.global.loading{/lang}',
'wcf.global.page.jumpTo': '{lang}wcf.global.page.jumpTo{/lang}',
--- /dev/null
+{if !$label|isset}{assign var='label' value='wcf.user.language'}{/if}
+
+{if $languages|count}
+ <dl{if $errorField|isset && $errorField == 'languageID'} class="formError"{/if}>
+ <dt>{lang}{$label}{/lang}</dt>
+ <dd id="languageIDContainer">
+ <noscript>
+ <select name="languageID" id="languageID">
+ {foreach from=$languages item=__language}
+ <option value="{@$__language->languageID}">{$__language}</option>
+ {/foreach}
+ </select>
+ </noscript>
+ </dd>
+ </dl>
+
+ <script data-relocate="true">
+ require(['WoltLab/WCF/Language/Chooser'], function(LanguageChooser) {
+ var languages = {
+ {implode from=$languages item=__language}
+ '{@$__language->languageID}': {
+ iconPath: '{@$__language->getIconPath()}',
+ languageName: '{$__language}'
+ }
+ {/implode}
+ };
+
+ LanguageChooser.init('languageIDContainer', 'languageID', {$languageID}, languages)
+ });
+ </script>
+{/if}
--- /dev/null
+<div id="mediaThumbnail" class="framed"></div>
+
+<div class="box48">
+ <span id="mediaFileIcon" class="icon icon48 fa-file-o"></span>
+
+ <dl class="plain dataList">
+ <dt>{lang}wcf.media.filename{/lang}</dt>
+ <dd id="mediaFilename"></dd>
+
+ <dt>{lang}wcf.media.filesize{/lang}</dt>
+ <dd id="mediaFilesize"></dd>
+
+ <dt>{lang}wcf.media.imageDimensions{/lang}</dt>
+ <dd id="mediaImageDimensions"></dd>
+
+ <dt>{lang}wcf.media.uploader{/lang}</dt>
+ <dd id="mediaUploader"></dd>
+ </dl>
+</div>
+
+<fieldset class="marginTop">
+ <legend>{lang}wcf.global.form.data{/lang}</legend>
+
+ <dl>
+ <dt></dt>
+ <dd>
+ <label>
+ <input type="checkbox" id="isMultilingual" name="isMultilingual" value="1" />
+ <span>{lang}wcf.media.isMultilingual{/lang}</span>
+ </label>
+ </dd>
+ </dl>
+
+ {include file='languageChooser' label='wcf.media.languageID'}
+
+ <dl>
+ <dt>{lang}wcf.global.title{/lang}</dt>
+ <dd>
+ <input type="text" id="title" name="title" class="long" />
+ </dd>
+ </dl>
+ {include file='multipleLanguageInputJavascript' elementIdentifier='title' forceSelection=true}
+
+ <dl>
+ <dt>{lang}wcf.media.caption{/lang}</dt>
+ <dd>
+ <textarea id="caption" name="caption" cols="40" rows="3"></textarea>
+ </dd>
+ </dl>
+ {include file='multipleLanguageInputJavascript' elementIdentifier='caption' forceSelection=true}
+
+ <dl>
+ <dt>{lang}wcf.media.altText{/lang}</dt>
+ <dd>
+ <input type="text" id="altText" name="altText" class="long" />
+ </dd>
+ </dl>
+ {include file='multipleLanguageInputJavascript' elementIdentifier='altText' forceSelection=true}
+
+ {event name='dataFields'}
+</fieldset>
+
+<div class="formSubmit">
+ <button data-type="submit" class="buttonPrimary">{lang}wcf.global.button.submit{/lang}</button>
+</div>
--- /dev/null
+{foreach from=$mediaList item=media}
+ <li class="jsClipboardObject" data-object-id="{@$media->mediaID}">
+ <div class="mediaThumbnail">
+ {@$media->getElementTag(96)}
+ </div>
+
+ <div class="mediaInformation">
+ <p class="mediaTitle">{if $media->title}{$media->title}{else}{$media->filename}{/if}</p>
+ </div>
+
+ <nav class="buttonGroupNavigation">
+ <ul class="smallButtons buttonGroup">
+ <li>
+ <input type="checkbox" class="jsClipboardItem jsMediaCheckbox" data-object-id="{@$media->mediaID}" />
+ </li>
+ {if $__wcf->session->getPermission('admin.content.cms.canManageMedia')}
+ <li>
+ <a><span class="icon icon16 fa-pencil jsTooltip jsMediaEditIcon" data-object-id="{@$media->mediaID}" title="{lang}wcf.global.button.edit{/lang}"></span></a>
+ </li>
+ <li>
+ <a><span class="icon icon16 fa-times jsTooltip jsMediaDeleteIcon" data-object-id="{@$media->mediaID}" title="{lang}wcf.global.button.delete{/lang}"></span></a>
+ </li>
+ {/if}
+ <li>
+ <a><span class="icon icon16 fa-plus jsTooltip jsMediaInsertIcon" data-object-id="{@$media->mediaID}" title="{lang}wcf.media.button.insert{/lang}"></span></a>
+ </li>
+ </ul>
+ </nav>
+ </li>
+{/foreach}
--- /dev/null
+<div class="inputAddon dropdown" id="mediaManagerSearch">
+ <span class="button dropdownToggle inputPrefix">
+ <span class="active">{lang}wcf.media.search.filetype{/lang}</span>
+ </span>
+ <ul class="dropdownMenu">
+ <li data-file-type="image"><span>{lang}wcf.media.search.filetype.image{/lang}</span></li>
+ <li data-file-type="text"><span>{lang}wcf.media.search.filetype.text{/lang}</span></li>
+ <li data-file-type="pdf"><span>{lang}wcf.media.search.filetype.pdf{/lang}</span></li>
+ <li data-file-type="other"><span>{lang}wcf.media.search.filetype.other{/lang}</span></li>
+ {event name='filetype'}
+ <li class="dropdownDivider"></li>
+ <li data-file-type="all"><span>{lang}wcf.media.search.filetype.all{/lang}</span></li>
+ </ul>
+ <input type="text" id="mediaManagerSearchField" placeholder="{lang}wcf.media.search.placeholder{/lang}" />
+ <span class="inputSuffix">
+ <span id="mediaManagerSearchCancelButton" class="icon icon16 fa-times pointer jsTooltip" title="{lang}wcf.media.search.cancel{/lang}"></span>
+ </span>
+</div>
+
+{if $__wcf->session->getPermission('admin.content.cms.canManageMedia')}
+ <div id="mediaManagerMediaUploadButton" class="marginTop"></div>
+{/if}
+
+<div class="jsClipboardContainer marginTop" data-type="com.woltlab.wcf.media">
+ <input type="checkbox" class="jsClipboardMarkAll" style="display: none;" />
+ <ul id="mediaManagerMediaList">
+ {include file='mediaListItems'}
+ </ul>
+
+ <div class="contentNavigation">
+ <nav class="jsClipboardEditor" data-types="[ 'com.woltlab.wcf.media' ]"></nav>
+ </div>
+</div>
{if $availableLanguages|count > 1}
<script data-relocate="true">
- //<![CDATA[
- $(function() {
- var $availableLanguages = { {implode from=$availableLanguages key=languageID item=languageName}{@$languageID}: '{$languageName}'{/implode} };
- var $values = { {implode from=$i18nValues[$elementIdentifier] key=languageID item=value}'{@$languageID}': '{$value}'{/implode} };
- new WCF.MultipleLanguageInput('{@$elementIdentifier}', {if $forceSelection}true{else}false{/if}, $values, $availableLanguages);
+ require(['Language', 'WoltLab/WCF/Language/Input'], function(Language, LanguageInput) {
+ Language.addObject({
+ 'wcf.global.button.disabledI18n': '{lang}wcf.global.button.disabledI18n{/lang}'
+ });
+
+ var availableLanguages = { {implode from=$availableLanguages key=languageID item=languageName}{@$languageID}: '{$languageName}'{/implode} };
+ var values = { {implode from=$i18nValues[$elementIdentifier] key=languageID item=value}'{@$languageID}': '{$value}'{/implode} };
+
+ LanguageInput.init('{@$elementIdentifier}', values, availableLanguages, {if $forceSelection}true{else}false{/if});
});
- //]]>
</script>
-{/if}
\ No newline at end of file
+{/if}
</div>
{/foreach}
</div>
-
- <script data-relocate="true">
- //<![CDATA[
- $(function() {
- // fix anchor
- var $location = location.toString().replace(location.hash, '');
- $('.sitemap .tabMenu a').each(function(index, link) {
- var $link = $(link);
- $link.attr('href', $location + $link.attr('href'));
- });
-
- WCF.TabMenu.init();
- });
- //]]>
- </script>
{hascontentelse}
{@$sitemap}
{/hascontent}
<admindefaultvalue>1</admindefaultvalue>
<usersonly>1</usersonly>
</option>
-
+
<option name="admin.content.cms.canManagePage">
<categoryname>admin.content</categoryname>
<optiontype>boolean</optiontype>
<usersonly>1</usersonly>
</option>
+ <option name="admin.content.cms.canManageMedia">
+ <categoryname>admin.content</categoryname>
+ <optiontype>boolean</optiontype>
+ <defaultvalue>0</defaultvalue>
+ <admindefaultvalue>1</admindefaultvalue>
+ <usersonly>1</usersonly>
+ </option>
+
+ <option name="admin.content.cms.canUseMedia">
+ <categoryname>admin.content</categoryname>
+ <optiontype>boolean</optiontype>
+ <defaultvalue>0</defaultvalue>
+ <admindefaultvalue>1</admindefaultvalue>
+ <usersonly>1</usersonly>
+ </option>
+
<!-- user.message -->
<option name="user.message.canUseSmilies">
<categoryname>user.message</categoryname>
*
* @param integer styleID
* @param string tmpHash
+ * @deprecated use WoltLab/WCF/Acp/Ui/Style/Image/Upload
*/
WCF.ACP.Style.ImageUpload = WCF.Upload.extend({
/**
--- /dev/null
+{if !$label|isset}{assign var='label' value='wcf.user.language'}{/if}
+
+{if $languages|count}
+ <dl{if $errorField == 'languageID'} class="formError"{/if}>
+ <dt>{lang}{$label}{/lang}</dt>
+ <dd id="languageIDContainer">
+ <noscript>
+ <select name="languageID" id="languageID">
+ {foreach from=$languages item=__language}
+ <option value="{@$__language->languageID}">{$__language}</option>
+ {/foreach}
+ </select>
+ </noscript>
+ </dd>
+ </dl>
+
+ <script data-relocate="true">
+ require(['WoltLab/WCF/Language/Chooser'], function(LanguageChooser) {
+ var languages = {
+ {implode from=$languages item=__language}
+ '{@$__language->languageID}': {
+ iconPath: '{@$__language->getIconPath()}',
+ languageName: '{$__language}'
+ }
+ {/implode}
+ };
+
+ LanguageChooser.init('languageIDContainer', 'languageID', {$languageID}, languages)
+ });
+ </script>
+{/if}
--- /dev/null
+{include file='header' pageTitle='wcf.acp.media.'|concat:$action}
+
+{if $action == 'add'}
+ <script data-relocate="true">
+ require(['EventHandler', 'WoltLab/WCF/Media/Upload'], function(EventHandler, MediaUpload) {
+ new MediaUpload('uploadButton', 'mediaFile');
+
+ // redirect the user to the edit form after uploading the file
+ EventHandler.add('com.woltlab.wcf.media.upload', 'success', function(data) {
+ for (var index in data.media) {
+ window.location = '{link controller='MediaEdit' id=2147483648 encode=false}{/link}'.replace(2147483648, data.media[index].mediaID);
+ }
+ });
+ });
+ </script>
+{/if}
+
+<header class="boxHeadline">
+ <h1 id="mediaActionTitle">{lang}wcf.acp.media.{$action}{/lang}</h1>
+</header>
+
+{if $action == 'edit'}
+ {include file='formError'}
+{/if}
+
+{if $success|isset}
+ <p class="success">{lang}wcf.global.success.{$action}{/lang}</p>
+{/if}
+
+<div class="contentNavigation">
+ <nav>
+ <ul>
+ <li><a href="{link controller='MediaList'}{/link}" class="button"><span class="icon icon16 fa-list"></span> <span>{lang}wcf.acp.menu.link.media.list{/lang}</span></a></li>
+
+ {event name='contentNavigationButtons'}
+ </ul>
+ </nav>
+</div>
+
+{if $action == 'add'}
+ <section class="marginTop">
+ <h1>{lang}wcf.media.file{/lang}</h1>
+
+ <dl>
+ <dt></dt>
+ <dd>
+ <div id="mediaFile"></div>
+ <div id="uploadButton"></div>
+ </dd>
+ </dl>
+ </section>
+{else}
+ <form method="post" action="{link controller='MediaEdit' object=$media}{/link}">
+ <section class="marginTop">
+ <h1>{lang}wcf.global.form.data{/lang}</h1>
+
+ <dl>
+ <dt>{lang}wcf.media.file{/lang}</dt>
+ <dd>{$media->filename} {*TODO: better output *}</dd>
+ </dl>
+
+ <dl>
+ <dt></dt>
+ <dd>
+ <label>
+ <input type="checkbox" id="isMultilingual" name="isMultilingual" value="1"{if $isMultilingual} checked="checked"{/if} />
+ <span>{lang}wcf.media.isMultilingual{/lang}</span>
+ </label>
+ </dd>
+ </dl>
+
+ {include file='languageChooser' label='wcf.media.languageID'}
+
+ <dl{if $errorField == 'title'} class="formError"{/if}>
+ <dt>{lang}wcf.global.title{/lang}</dt>
+ <dd>
+ <input type="text" id="title" name="title" value="{$i18nPlainValues['title']}" class="long" />
+ {if $errorField == 'title'}
+ <small class="innerError">
+ {if $errorType == 'title' || $errorType == 'multilingual'}
+ {lang}wcf.global.form.error.{@$errorType}{/lang}
+ {else}
+ {lang}wcf.media.title.error.{@$errorType}{/lang}
+ {/if}
+ </small>
+ {/if}
+ </dd>
+ </dl>
+ {include file='multipleLanguageInputJavascript' elementIdentifier='title' forceSelection=true}
+
+ <dl{if $errorField == 'caption'} class="formError"{/if}>
+ <dt>{lang}wcf.media.caption{/lang}</dt>
+ <dd>
+ <textarea id="caption" name="caption" cols="40" rows="3">{$i18nPlainValues['caption']}</textarea>
+ {if $errorField == 'caption'}
+ <small class="innerError">
+ {if $errorType == 'title' || $errorType == 'multilingual'}
+ {lang}wcf.global.form.error.{@$errorType}{/lang}
+ {else}
+ {lang}wcf.media.caption.error.{@$errorType}{/lang}
+ {/if}
+ </small>
+ {/if}
+ </dd>
+ </dl>
+ {include file='multipleLanguageInputJavascript' elementIdentifier='caption' forceSelection=true}
+
+ <dl{if $errorField == 'altText'} class="formError"{/if}>
+ <dt>{lang}wcf.media.altText{/lang}</dt>
+ <dd>
+ <input type="text" id="altText" name="altText" value="{$i18nPlainValues['altText']}" class="long" />
+ {if $errorField == 'altText'}
+ <small class="innerError">
+ {if $errorType == 'title' || $errorType == 'multilingual'}
+ {lang}wcf.global.form.error.{@$errorType}{/lang}
+ {else}
+ {lang}wcf.media.altText.error.{@$errorType}{/lang}
+ {/if}
+ </small>
+ {/if}
+ </dd>
+ </dl>
+ {include file='multipleLanguageInputJavascript' elementIdentifier='altText' forceSelection=true}
+
+ {event name='dataFields'}
+ </section>
+
+ {event name='sections'}
+
+ <div class="formSubmit">
+ <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s" />
+ {@SECURITY_TOKEN_INPUT_TAG}
+ </div>
+ </form>
+{/if}
+
+{if $action == 'edit'}
+ {* this code needs to be put after all multipleLanguageInputJavascript template have been included *}
+ <script data-relocate="true">
+ require(['WoltLab/WCF/Language/Input'], function(LanguageInput) {
+ function updateLanguageFields() {
+ var languageIdContainer = elById('languageIDContainer').parentNode;
+
+ if (elById('isMultilingual').checked) {
+ LanguageInput.enable('title');
+ LanguageInput.enable('caption');
+ LanguageInput.enable('altText');
+
+ elHide(languageIdContainer);
+ }
+ else {
+ LanguageInput.disable('title');
+ LanguageInput.disable('caption');
+ LanguageInput.disable('altText');
+
+ elShow(languageIdContainer);
+ }
+ };
+
+ elById('isMultilingual').addEventListener('change', updateLanguageFields);
+
+ updateLanguageFields();
+
+ {if !$isMultilingual}
+ elById('title').value = '{$i18nPlainValues['title']|encodeJs}';
+ elById('title').caption = '{$i18nPlainValues['caption']|encodeJs}';
+ elById('title').altText = '{$i18nPlainValues['altText']|encodeJs}';
+ {/if}
+ });
+ </script>
+{/if}
+
+{include file='footer'}
--- /dev/null
+{include file='header' pageTitle='wcf.acp.media.list'}
+
+<header class="boxHeadline">
+ <h1>{lang}wcf.acp.media.list{/lang}</h1>
+ <p>{lang}wcf.acp.media.stats{/lang}</p>
+</header>
+
+{include file='formError'}
+
+{*
+TODO: add file search
+<form method="post" action="{link controller='MediaList'}{/link}">
+ <div class="container containerPadding marginTop">
+ <fieldset>
+ <legend>{lang}wcf.global.filter{/lang}</legend>
+
+ <dl>
+ <dt><label for="username">{lang}wcf.user.username{/lang}</label></dt>
+ <dd>
+ <input type="text" id="username" name="username" value="{$username}" class="long" />
+ </dd>
+ </dl>
+
+ <dl>
+ <dt><label for="filename">{lang}wcf.media.filename{/lang}</label></dt>
+ <dd>
+ <input type="text" id="filename" name="filename" value="{$filename}" class="long" />
+ </dd>
+ </dl>
+
+ <dl>
+ <dt><label for="fileType">{lang}wcf.media.fileType{/lang}</label></dt>
+ <dd>
+ <select name="fileType" id="fileType">
+ <option value="">{lang}wcf.global.noSelection{/lang}</option>
+ {htmlOptions options=$availableFileTypes selected=$fileType}
+ </select>
+ </dd>
+ </dl>
+ </fieldset>
+ </div>
+
+ <div class="formSubmit">
+ <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s" />
+ {@SECURITY_TOKEN_INPUT_TAG}
+ </div>
+</form>
+*}
+
+<div class="contentNavigation">
+ {assign var='linkParameters' value=''}
+ {*
+ {if $username}{capture append=linkParameters}&username={@$username|rawurlencode}{/capture}{/if}
+ {if $filename}{capture append=linkParameters}&filename={@$filename|rawurlencode}{/capture}{/if}
+ {if $fileType}{capture append=linkParameters}&fileType={@$fileType|rawurlencode}{/capture}{/if}
+ *}
+
+ {pages print=true assign=pagesLinks controller="MediaList" link="pageNo=%d&sortField=$sortField&sortOrder=$sortOrder$linkParameters"}
+
+ <nav>
+ <ul>
+ <li><a href="{link controller='MediaAdd'}{/link}" class="button"><span class="icon icon16 fa-plus"></span> <span>{lang}wcf.acp.media.add{/lang}</span></a></li>
+
+ {event name='contentNavigationButtonsTop'}
+ </ul>
+ </nav>
+</div>
+
+{if $objects|count}
+ <div class="tabularBox tabularBoxTitle marginTop">
+ <header>
+ <h2>{lang}wcf.acp.media.list{/lang} <span class="badge badgeInverse">{#$items}</span></h2>
+ </header>
+
+ <table class="table">
+ <thead>
+ <tr>
+ <th class="columnID columnMediaID{if $sortField == 'mediaID'} active {@$sortOrder}{/if}" colspan="2"><a href="{link controller='MediaList'}pageNo={@$pageNo}&sortField=mediaID&sortOrder={if $sortField == 'mediaID' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{@$linkParameters}{/link}">{lang}wcf.global.objectID{/lang}</a></th>
+ <th class="columnTitle columnFilename{if $sortField == 'filename'} active {@$sortOrder}{/if}"><a href="{link controller='MediaList'}pageNo={@$pageNo}&sortField=filename&sortOrder={if $sortField == 'filename' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{@$linkParameters}{/link}">{lang}wcf.media.filename{/lang}</a></th>
+ <th class="columnDate columnUploadTime{if $sortField == 'uploadTime'} active {@$sortOrder}{/if}"><a href="{link controller='MediaList'}pageNo={@$pageNo}&sortField=uploadTime&sortOrder={if $sortField == 'uploadTime' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{@$linkParameters}{/link}">{lang}wcf.media.uploadTime{/lang}</a></th>
+ <th class="columnDigits columnFilesize{if $sortField == 'filesize'} active {@$sortOrder}{/if}"><a href="{link controller='MediaList'}pageNo={@$pageNo}&sortField=filesize&sortOrder={if $sortField == 'filesize' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{@$linkParameters}{/link}">{lang}wcf.media.filesize{/lang}</a></th>
+
+ {event name='columnHeads'}
+ </tr>
+ </thead>
+
+ <tbody>
+ {foreach from=$objects item=media}
+ <tr class="jsMediaRow">
+ <td class="columnIcon">
+ <span class="icon icon16 fa-times jsDeleteButton jsTooltip pointer" title="{lang}wcf.global.button.delete{/lang}" data-object-id="{@$media->media}" data-confirm-message="{lang}wcf.media.delete.confirmMessage{/lang}"></span>
+
+ {event name='rowButtons'}
+ </td>
+ <td class="columnID columnMediaID">{@$media->mediaID}</td>
+ <td class="columnTitle columnFilename">
+ <div class="box48">
+ {@$media->getElementTag(48)}
+
+ <div>
+ <p><a href="{link controller='MediaEdit' object=$media}{/link}">{$media->filename|tableWordwrap}</a></p>
+ <p><small>{if $media->userID}{if $__wcf->session->getPermission('admin.user.canEditUser')}<a href="{link controller='UserEdit' id=$media->userID}{/link}">{$media->username}</a>{else}{$media->username}{/if}{else}{lang}wcf.user.guest{/lang}{/if}</small></p>
+ </div>
+ </div>
+ </td>
+ <td class="columnDate columnUploadTime">{@$media->uploadTime|time}</td>
+ <td class="columnDigits columnFilesize">{@$media->filesize|filesize}</td>
+
+ {event name='columns'}
+ </tr>
+ {/foreach}
+ </tbody>
+ </table>
+ </div>
+
+ <div class="contentNavigation">
+ {@$pagesLinks}
+
+ <nav>
+ <ul>
+ <li><a href="{link controller='MediaAdd'}{/link}" class="button"><span class="icon icon16 fa-plus"></span> <span>{lang}wcf.acp.media.add{/lang}</span></a></li>
+
+ {event name='contentNavigationButtonsBottom'}
+ </ul>
+ </nav>
+ </div>
+{else}
+ <p class="info">{lang}wcf.global.noItems{/lang}</p>
+{/if}
+
+{include file='footer'}
LanguageInput.init('{@$elementIdentifier}', values, availableLanguages, {if $forceSelection}true{else}false{/if});
});
</script>
-{/if}
\ No newline at end of file
+{/if}
{js application='wcf' acp='true' file='WCF.ACP.Style'}
{js application='wcf' file='WCF.ColorPicker' bundle='WCF.Combined'}
<script data-relocate="true">
- require(['WoltLab/WCF/Acp/Ui/Style/Editor'], function(AcpUiStyleEditor) {
+ require(['WoltLab/WCF/Acp/Ui/Style/Image/Upload', 'WoltLab/WCF/Acp/Ui/Style/Editor'], function(AcpUiStyleImageUpload, AcpUiStyleEditor) {
AcpUiStyleEditor.setup({
isTainted: {if $isTainted}true{else}false{/if},
styleId: {if $action === 'edit'}{@$style->styleID}{else}0{/if},
styleRuleMap: styleRuleMap
});
+
+ new AcpUiStyleImageUpload({if $action == 'add'}0{else}{@$style->styleID}{/if}, '{$tmpHash}');
});
$(function() {
'wcf.style.colorPicker.button.apply': '{lang}wcf.style.colorPicker.button.apply{/lang}',
'wcf.acp.style.image.error.invalidExtension': '{lang}wcf.acp.style.image.error.invalidExtension{/lang}'
});
- new WCF.ACP.Style.ImageUpload({if $action == 'add'}0{else}{@$style->styleID}{/if}, '{$tmpHash}');
new WCF.ACP.Style.LogoUpload('{$tmpHash}', '{@$__wcf->getPath()}images/');
{if $action == 'edit'}
--- /dev/null
+/**
+ * Handles uploading style preview images.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2015 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module WoltLab/WCF/Acp/Ui/Style/Image/Upload
+ */
+define(['Core', 'Dom/Traverse', 'Language', 'Ui/Notification', 'Upload'], function(Core, DomTraverse, Language, UiNotification, Upload) {
+ "use strict";
+
+ /**
+ * @constructor
+ */
+ function AcpUiStyleImageUpload(styleId, tmpHash) {
+ this._styleId = ~~styleId;
+ this._tmpHash = tmpHash;
+
+ Upload.call(this, 'uploadImage', 'styleImage', {
+ className: 'wcf\\data\\style\\StyleAction'
+ });
+ }
+ Core.inherit(AcpUiStyleImageUpload, Upload, {
+ /**
+ * @see WoltLab/WCF/Upload#_createFileElement
+ */
+ _createFileElement: function(file) {
+ return this._target;
+ },
+
+ /**
+ * @see WoltLab/WCF/Upload#_getParameters
+ */
+ _getParameters: function() {
+ return {
+ styleId: this._styleId,
+ tmpHash: this._tmpHash
+ };
+ },
+
+ /**
+ * @see WoltLab/WCF/Upload#_success
+ */
+ _success: function(uploadId, data) {
+ var error = DomTraverse.childByClass(this._button.parentNode, 'innerError');
+ if (data.returnValues.url) {
+ elAttr(this._target, 'src', data.returnValues.url + '?timestamp=' + Date.now());
+
+ if (error) {
+ error.parentNode.removeChild(error);
+ }
+
+ UiNotification.show();
+ }
+ else if (data.returnValues.errorType) {
+ if (!error) {
+ error = elCreate('small');
+ error.className = 'innerError';
+
+ this._button.parentNode.appendChild(error);
+ }
+
+ error.textContent = Language.get('wcf.acp.style.image.error.' + data.returnValues.errorType)
+ }
+ }
+ });
+
+ return AcpUiStyleImageUpload;
+});
--- /dev/null
+
+define(['Core', 'Dom/ChangeListener', 'Dom/Traverse', 'Language', 'List', 'Upload'], function(Core, DomChangeListener, DomTraverse, Language, List, Upload) {
+ "use strict";
+
+ function AttachmentUpload(buttonContainerId, targetId, tmpHash, objectType, objectId, parentObjectId, maxUploads, maxSize, wysiwygContainerId) {
+ this._tmpHash = tmpHash;
+ this._objectType = objectType;
+ this._objectId = ~~objectId;
+ this._parentObjectId = ~~parentObjectID;
+ this._wysiwygContainerId = wysiwygContainerId;
+
+ this._autoInsert = new List();
+
+ Upload.call(this, 'uploadImage', 'styleImage', {
+ className: 'wcf\\data\\attachment\\AttachmentAction',
+ maxSize: ~~maxSize,
+ maxUploads: ~~maxUploads,
+ multiple: true
+ });
+
+ // add event listeners
+ DomTraverse.childByClass(this._button, '.button').addEventListener('click', this._validateLimit.bind(this));
+ elByClass(this._target, 'jsButtonInsertAttachment').addEventListener('click', this._insert.bind(this));
+ elByClass(this._target, 'jsButtonAttachmentInsertThumbnail').addEventListener('click', this._insert.bind(this));
+ elByClass(this._target, 'jsButtonAttachmentInsertFull').addEventListener('click', this._insert.bind(this));
+
+ // TODO: WCF.System.Event.addListener('com.woltlab.wcf.action.delete', 'attachment_' + this._wysiwygContainerId, $.proxy(this._removeLimitError, this));
+
+ // TODO: this._makeSortable();
+
+ this._insertAllButton = elCreate('p');
+ this._insertAllButton.className = 'button jsButtonAttachmentInsertAll';
+ this._insertAllButton.textContent = Language.get('wcf.attachment.insertAll');
+ if (DomTraverse.childBySel(this._target, 'li:not(.uploadFailed)')) {
+ this._insertAllButton.style.setProperty('display', 'none');
+ }
+ this._insertAllButton.addEventListener('click', this._insertAll.bind(this));
+ this._button.appendChild(this._insertAllButton);
+
+ if (this._wysiwygContainerId) {
+ // TODO: WCF.System.Event.addListener('com.woltlab.wcf.messageOptionsInline', 'submit_' + this._wysiwygContainerId, $.proxy(this._submitInline, this));
+ // TODO: WCF.System.Event.addListener('com.woltlab.wcf.messageOptionsInline', 'prepareExtended_' + this._wysiwygContainerId, $.proxy(this._prepareExtended, this));
+ // TODO: WCF.System.Event.addListener('com.woltlab.wcf.redactor', 'reset', $.proxy(this._reset, this));
+ // TODO: WCF.System.Event.addListener('com.woltlab.wcf.redactor', 'upload_' + this._wysiwygContainerId, $.proxy(this._editorUpload, this));
+ // TODO: WCF.System.Event.addListener('com.woltlab.wcf.redactor', 'getImageAttachments_' + this._wysiwygContainerId, $.proxy(this._getImageAttachments, this));
+ }
+ };
+ Core.inherit(AttachmentUpload, Upload,{
+ /**
+ * @see WoltLab/WCF/Upload#_createFileElement
+ */
+ _createFileElement: function(file) {
+ var listItem = elCreate('li');
+ listItem.className = 'box64';
+ elAttr(listItem, 'data-filename', filename);
+ this._target.appendChild(listItem);
+ this._target.style.removeProperty('display');
+
+ var span = elCreate('span');
+ if (this._options.maxSize >= file.size) {
+ span.className = 'icon icon48 fa-spinnner';
+ }
+ else {
+ span.className = 'icon icon48 fa-ban-circle';
+ }
+ listItem.appendChild(span);
+
+ var div = elCreate('div');
+ listItem.appendChild(div);
+
+ var div2 = elCreate('div');
+ div.appendChild(div2);
+
+ var p = elCreate('p');
+ p.textContent = file.name;
+ div2.appendChild(p);
+
+ var small = elCreate('small');
+ div2.appendChild(small);
+
+ if (this._options.maxSize >= file.size) {
+ var progress = elCreate('progress');
+ elAttr(progress, 'max', 100);
+ }
+
+ div.appendChild(elCreate('ul'));
+
+ if (this._options.maxSize < file.size) {
+ small = elCreate('small');
+ small.className = 'innerError';
+ small.textContent = Language.get('wcf.attachment.upload.error.tooLarge');
+ div2.appendChild(small);
+
+ listItem.classList.add('uploadFailed');
+ }
+ },
+
+ /**
+ * @see WoltLab/WCF/Upload#_createFileElements
+ */
+ _createFileElements: function(files) {
+ var failedUploads = DomTraverse.childrenBySel(this._target, 'li.uploadFailed');
+ for (var i = 0, length = failedUploads.length; i < length; i++) {
+ this._target.removeChild(failedUploads[i]);
+ }
+
+ return Upload.prototype._createFileElements.call(this, files);
+ },
+
+ /**
+ * @see WoltLab/WCF/Upload#_getParameters
+ */
+ _getParameters: function() {
+ return {
+ objectID: this._objectIdh,
+ objectType: this._objectType,
+ parentObjectID: this._parentObjectId,
+ tmpHash: this._tmpHas
+ };
+ },
+
+ /**
+ * @see WoltLab/WCF/Upload#_success
+ */
+ _success: function(uploadId, data) {
+ for (var i = 0, length = this._fileElements[uploadId].length; i < length; i++) {
+ var listItem = this._fileElements[uploadId][i];
+
+ var progress = elByTag(listItem, 'PROGRESS');
+ progress.parentNode.removeChild(progress);
+
+ var filename = elAttr(listItem, 'data-filename');
+ var internalFileId = elAttr(listItem, 'data-internal-file-id');
+
+ var icon = DomTraverse.childByClass(listItem, 'fa-spinner');
+
+ if (data.returnValues && data.returnValues.attachments[internalFileId]) {
+ var attachment = data.returnValues.attachments[internalFileId];
+ if (attachment.tinyURL) {
+ var img = elCreate('img');
+ img.className = 'attachmentTinyThumbnail';
+ elAttr(img, 'src', attachment.tinyURL);
+ elAttr(img, 'alt', '');
+ icon.parentNode.replaceChild(icon, img);
+
+ elAttr(listItem, 'data-height', attachment.height);
+ elAttr(listItem, 'data-width', attachment.width);
+ }
+ else {
+ // TODO: Use FileUtil.getIconClassByMimeType()?
+ icon.classList.remove('fa-spinner');
+ icon.classList.add('fa-paper-clip');
+ }
+
+ var p = elByTag(listItem, 'P');
+ p.innerHtml = '';
+
+ var a = elCreate('a');
+ a.textContent = filename;
+ elAttr(a, 'href', attachment.url);
+
+ if (attachment.isImage) {
+ a.className = 'jsImageViewer';
+ elAttr(a, 'title', filename);
+ }
+
+ p.appendChild(a);
+
+ elByTag(listItem, 'SMALL').textContent = attachment.formattedFilesize;
+
+ var ul = elByTag(listItem, 'UL');
+ ul.classList.add('buttonGroup');
+
+ var deleteButton = elCreate('li');
+ ul.appendChild(deleteButton);
+
+ var span = elCreate('span');
+ span.className = 'button small jsDeleteButton';
+ span.textContent = Language.get('wcf.global.button.delete');
+ elAttr(span, 'data-object-id', attachment.attachmentID);
+ elAttr(span, 'data-confirm-message', Language.get('wcf.attachment.delete.sure'));
+ if (this._wysiwygContainerId) {
+ elAttr(span, 'data-event-name', 'attachment_' + this._wysiwygContainerId);
+ }
+ deleteButton.appendChild(span);
+
+ elAttr(span, 'data-object-id', attachment.attachmentID);
+
+ if (this._wysiwygContainerId) {
+ if (attachment.tinyURL) {
+ var insertThumbnailButton = elCreate('li');
+ ul.appendChild(insertThumbnailButton);
+
+ span = elCreate('span');
+ span.className = 'button small jsButtonAttachmentInsertThumbnail';
+ span.textContent = Language.get('wcf.global.button.insertThumbnail');
+ elAttr(span, 'data-object-id', attachment.attachmentID);
+ span.addEventListener('click', this._insert.bind(this));
+ insertThumbnailButton.appendChild(span);
+
+ var insertOriginalButton = elCreate('li');
+ ul.appendChild(insertOriginalButton);
+
+ span = elCreate('span');
+ span.className = 'button small jsButtonAttachmentInsertFull';
+ span.textContent = Language.get('wcf.global.button.insertFull');
+ elAttr(span, 'data-object-id', attachment.attachmentID);
+ span.addEventListener('click', this._insert.bind(this));
+ insertOriginalButton.appendChild(span);
+ }
+ else {
+ var insertPlainButton elCreate('li');
+ ul.appendChild(insertPlainButton);
+
+ span = elCreate('span');
+ span.className = 'button small jsButtonAttachmentInsertPlain';
+ span.textContent = Language.get('wcf.global.button.insert');
+ elAttr(span, 'data-object-id', attachment.attachmentID);
+ span.addEventListener('click', this._insert.bind(this));
+ insertPlainButton.appendChild(span);
+ }
+ }
+ }
+ else {
+ icon.classList.removeClass('fa-spinner');
+ icon.classList.addClass('fa-ban-circle');
+
+ var errorType = 'uploadFailed';
+ if (data.returnValues && data.returnValues.errors[internalFileId]) {
+ errorType = data.returnValues.errors[internalFileId].errorType;
+ }
+
+ var small = elCreate('small');
+ small.className = 'innerError';
+ small.textContent = Language.get('wcf.attachment.upload.error.' + errorType);
+ elBySel(listItem, 'div > div').appendChild(small);
+
+ listItem.classList.add('uploadFailed');
+ }
+
+ // fix WebKit rendinering bug
+ // TODO: still necessary?
+ listItem.style.setProperty('display', 'block');
+
+ if (this._autoInsert.has(uploadId)) {
+ this._autoInsert['delete'](uploadId);
+
+ if (listItem.classList.contains('uploadFailed')) {
+ // TODO: WCF.System.Event.fireEvent('com.woltlab.wcf.attachment', 'autoInsert_' + this._wysiwygContainerId, {
+ // attachment: '[attach=' + attachment.attachmentID + '][/attach]',
+ // uploadID: uploadId
+ //});
+ }
+ }
+ }
+
+ // TODO: this._makeSortable();
+
+ if (DomTraverse.childrenBySel(this._target, 'li:not(.uploadFailed)').length) {
+ this._insertAllButton.style.removeProperty('display');
+ }
+ else {
+ this._insertAllButton.style.setProperty('display', 'none');
+ }
+
+ DomChangeListener.trigger();
+ },
+
+ /**
+ * @see WoltLab/WCF/Upload#_upload
+ */
+ _upload: function(event, file, blob) {
+ if (this._validateLimit()) {
+ Upload.prototype._upload.call(this, event, file, blob);
+ }
+ },
+
+ _validateLimit: function() {
+ var innerError = DomTraverse.nextBySel(this._button, 'small.innerError');
+
+ var remainingUploads = this._options.maxUploads - DomTraverse.childrenBySel(this._target, 'li:not(.uploadFailed)').length;
+ if (remainingUploads <= 0 || remainingUploads < this._fileUpload.files.length) {
+ if (!innerError) {
+ innerError = elCreate('small');
+ innerError.className = 'innerError';
+ DomUtil.insertAfter(innerError, this._button);
+ }
+
+ if (remainingUploads <= 0) {
+ innerError.textContent = Language.get('wcf.attachment.upload.error.reachedLimit');
+ }
+ else {
+ innerError.textContent = Language.get('wcf.attachment.upload.error.reachedRemainingLimit', {
+ remaining: remainingUploads
+ });
+ }
+
+ return false;
+ }
+
+ if (innerError) {
+ innerError.parentNode.removeChild(innerError);
+ }
+
+ return true;
+ }
+ });
+});
}
DomChangeListener.add('WoltLab/WCF/Controller/Clipboard', this._initContainers.bind(this));
+ DomChangeListener.add('WoltLab/WCF/Controller/Clipboard', this._initEditors.bind(this));
},
/**
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @module WoltLab/WCF/Dictionary
*/
-define([], function() {
+define(['Core'], function(Core) {
"use strict";
var _hasMap = objOwns(window, 'Map') && typeof window.Map === 'function';
this.set(key, value);
}).bind(this));
}
+ },
+
+ /**
+ * Returns the object representation of the dictionary.
+ *
+ * @return {object} dictionary's object representation
+ */
+ toObject: function() {
+ if (!_hasMap) return Core.clone(this._dictionary);
+
+ var object = { };
+ this._dictionary.forEach(function(value, key) {
+ object[key] = value;
+ });
+
+ return object;
}
};
--- /dev/null
+/**
+ * Provides helper functions to work with files.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2015 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module WoltLab/WCF/File/Util
+ */
+define([], function() {
+ /**
+ * @exports WoltLab/WCF/File/Util
+ */
+ var FileUtil = {
+ /**
+ * Returns the FontAwesome icon CSS class name for a mime type.
+ *
+ * @param {string} mimeType mime type of the relevant file
+ * @return {string} FontAwesome icon CSS class name for the mime type
+ */
+ getIconClassByMimeType: function(mimeType) {
+ if (mimeType.substr(0, 6) == 'image/') {
+ return 'fa-file-image-o';
+ }
+ else if (mimeType.substr(0, 6) == 'video/') {
+ return 'fa-file-video-o';
+ }
+ else if (mimeType.substr(0, 6) == 'audio/') {
+ return 'fa-file-sound-o';
+ }
+ else if (mimeType.substr(0, 5) == 'text/') {
+ return 'fa-file-text-o';
+ }
+ else {
+ switch (mimeType) {
+ case 'application/msword':
+ case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
+ return 'fa-file-word-o';
+ break;
+
+ case 'application/pdf':
+ return 'fa-file-pdf-o';
+ break;
+
+ case 'application/vnd.ms-powerpoint':
+ case 'application/vnd.openxmlformats-officedocument.presentationml.presentation':
+ return 'fa-file-powerpoint-o';
+ break;
+
+ case 'application/vnd.ms-excel':
+ case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':
+ return 'fa-file-excel-o';
+ break;
+
+ case 'application/zip':
+ case 'application/x-tar':
+ case 'application/x-gzip':
+ return 'fa-file-archive-o';
+ break;
+
+ case 'application/xml':
+ return 'fa-file-text-o';
+ break;
+ }
+ }
+
+ return 'fa-file-o';
+ }
+ };
+
+ return FileUtil;
+});
--- /dev/null
+/**
+ * Dropdown language chooser.
+ *
+ * @author Alexander Ebert, Matthias Schmidt
+ * @copyright 2001-2015 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module WoltLab/WCF/Language/Chooser
+ */
+define(['Dictionary', 'Language', 'Dom/Traverse', 'Dom/Util', 'Ui/SimpleDropdown'], function(Dictionary, Language, DomTraverse, DomUtil, UiSimpleDropdown) {
+ "use strict";
+
+ var _choosers = new Dictionary();
+ var _didInit = false;
+
+ var _callbackDropdownToggle = null;
+ var _callbackSubmit = null;
+
+ /**
+ * @exports WoltLab/WCF/Language/Chooser
+ */
+ var LanguageChooser = {
+ /**
+ * Initializes a language chooser.
+ *
+ * @param {string} containerId input element conainer id
+ * @param {string} chooserId input element id
+ * @param {integer} languageId selected language id
+ * @param {object<integer, object<string, string>>} languages data of available languages
+ * @param callback
+ * @param allowEmptyValue
+ */
+ init: function(containerId, chooserId, languageId, languages, callback, allowEmptyValue) {
+ if (_choosers.has(chooserId)) {
+ return;
+ }
+
+ var container = elById(containerId);
+ if (container === null) {
+ throw new Error("Expected a valid container id, cannot find '" + chooserId + "'.");
+ }
+
+ var element = elById(chooserId);
+ if (element === null) {
+ element = elCreate('input');
+ elAttr(element, 'type', 'hidden');
+ elAttr(element, 'id', chooserId);
+ elAttr(element, 'name', chooserId);
+ elAttr(element, 'value', languageId);
+
+ container.appendChild(element);
+ }
+
+ // todo: callback
+
+ this._initElement(chooserId, element, languageId, languages, allowEmptyValue);
+ },
+
+ /**
+ * Caches common event listener callbacks.
+ */
+ _setup: function() {
+ if (_didInit) return;
+ _didInit = true;
+
+ _callbackSubmit = this._submit.bind(this);
+ },
+
+ _initElement: function(chooserId, element, languageId, languages, allowEmptyValue) {
+ var container = element.parentNode;
+ if (!container.classList.contains('inputAddon')) {
+ container = elCreate('div');
+ container.className = 'inputAddon';
+ elAttr(container, 'data-input-id', chooserId);
+
+ element.parentNode.insertBefore(container, element);
+ container.appendChild(element);
+ }
+
+ container.classList.add('dropdown');
+ var dropdownToggle = elCreate('div');
+ dropdownToggle.className = 'dropdownToggle boxFlag box24 inputPrefix';
+ container.insertBefore(dropdownToggle, element);
+
+ var dropdownMenu = elCreate('ul');
+ dropdownMenu.className = 'dropdownMenu';
+ DomUtil.insertAfter(dropdownMenu, dropdownToggle);
+
+ var callbackClick = (function(event) {
+ var languageId = ~~event.currentTarget.getAttribute('data-language-id');
+
+ var activeItem = DomTraverse.childByClass(dropdownMenu, 'active');
+ if (activeItem !== null) activeItem.classList.remove('active');
+
+ if (languageId) event.currentTarget.classList.add('active');
+
+ this._select(chooserId, languageId, event.currentTarget);
+ }).bind(this);
+
+ // add language dropdown items
+ for (var availableLanguageId in languages) {
+ if (languages.hasOwnProperty(availableLanguageId)) {
+ var language = languages[availableLanguageId];
+
+ var listItem = elCreate('li');
+ listItem.className = 'boxFlag';
+ listItem.addEventListener('click', callbackClick)
+ elAttr(listItem, 'data-language-id', availableLanguageId);
+ dropdownMenu.appendChild(listItem);
+
+ var a = elCreate('a');
+ a.className = 'box24';
+ listItem.appendChild(a);
+
+ var div = elCreate('div');
+ div.className = 'framed';
+ a.appendChild(div);
+
+ var img = elCreate('img');
+ elAttr(img, 'src', language.iconPath);
+ elAttr(img, 'alt', '');
+ img.className = 'iconFlag';
+ div.appendChild(img);
+
+ div = elCreate('div');
+ a.appendChild(div);
+
+ var h3 = elCreate('h3');
+ h3.textContent = language.languageName;
+ div.appendChild(h3);
+
+ if (availableLanguageId == languageId) {
+ dropdownToggle.innerHTML = listItem.firstChild.innerHTML;
+ }
+ }
+ }
+
+ // add dropdown item for "no selection"
+ if (allowEmptyValue) {
+ var listItem = elCreate('li');
+ listItem.className = 'dropdownDivider';
+ dropdownMenu.appendChild(listItem);
+
+ listItem = elCreate('li');
+ elAttr(listItem, 'data-language-id', availableLanguageId);
+ listItem.addEventListener('click', callbackClick);
+ dropdownMenu.appendChild(listItem);
+
+ var a = elCreate('a');
+ a.textContent = Language.get('wcf.global.language.noSelection');
+ listItem.appendChild(a);
+
+ if (languageId === 0) {
+ dropdownToggle.innerHTML = listItem.firstChild.innerHTML;
+ }
+
+ listItem.addEventListener('click', callbackClick)
+ }
+ else if (languageId === 0) {
+ dropdownToggle.innerHTML = null;
+
+ var div = elCreate('div');
+ dropdownToggle.appendChild(div);
+
+ var span = elCreate('span');
+ span.className = 'icon icon24 fa-question';
+ div.appendChild(span);
+
+ div = elCreate('div');
+ dropdownToggle.appendChild(div);
+
+ var h3 = elCreate('h3');
+ h3.textContent = Language.get('wcf.global.language.noSelection');
+ div.appendChild(h3);
+ }
+
+ UiSimpleDropdown.init(dropdownToggle);
+
+ _choosers.set(chooserId, {
+ dropdownMenu: dropdownMenu,
+ dropdownToggle: dropdownToggle,
+ element: element
+ });
+
+ // bind to submit event
+ var submit = DomTraverse.parentByTag(element, 'FORM');
+ if (submit !== null) {
+ submit.addEventListener('submit', _callbackSubmit);
+
+ // TODO: WHAT?
+ return;
+ var chooserIds = _forms.get(submit);
+ if (chooserIds === undefined) {
+ chooserIds = [];
+ _forms.set(submit, chooserIds);
+ }
+
+ chooserIds.push(chooserId);
+ }
+ },
+
+ /**
+ * Selects a language from the dropdown list.
+ *
+ * @param {string} chooserId input element id
+ * @param {integer} languageId language id or `0` to disable i18n
+ * @param {Element} listItem selected list item
+ */
+ _select: function(chooserId, languageId, listItem) {
+ var chooser = _choosers.get(chooserId);
+
+ if (listItem === undefined) {
+ var listItems = chooser.dropdownMenu.childNodes;
+ for (var i = 0, length = listItems.length; i < length; i++) {
+ var _listItem = listItems[i];
+ if (elAttr(_listItem, 'data-language-id') == languageId) {
+ listItem = _listItem;
+ break;
+ }
+ }
+
+ if (listItem === undefined) {
+ throw new Error("Cannot select unknown language id '" + languageId + "'");
+ }
+ }
+
+ chooser.element.value = languageId;
+
+ chooser.dropdownToggle.innerHTML = listItem.firstChild.innerHTML;
+
+ _choosers.set(chooserId, chooser);
+ },
+
+ /**
+ * Returns the chooser for an input field.
+ *
+ * @param {string} chooserId input element id
+ * @return {Dictionary} data of the chooser
+ */
+ getChooser: function(chooserId) {
+ var chooser = _choosers.get(chooserId);
+ if (chooser === undefined) {
+ throw new Error("Expected a valid language chooser input element, '" + chooserId + "' is not i18n input field.");
+ }
+
+ return chooser;
+ },
+
+ /**
+ * Returns the selected language for a certain chooser.
+ *
+ * @param {string} chooserId input element id
+ * @return {integer} choosen language id
+ */
+ getLanguageId: function(chooserId) {
+ return ~~this.getChooser(chooserId).element.value;
+ },
+
+ /**
+ * Sets the language for a certain chooser.
+ *
+ * @param {string} chooserId input element id
+ * @param {integer} languageId language id to be set
+ */
+ setLanguageId: function(chooserId, languageId) {
+ if (_choosers.get(chooserId) === undefined) {
+ throw new Error("Expected a valid input element, '" + chooserId + "' is not i18n input field.");
+ }
+
+ this._select(chooserId, languageId);
+ }
+ };
+
+ return LanguageChooser;
+});
--- /dev/null
+/**
+ * Handles editing media files via dialog.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2015 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module WoltLab/WCF/Media/Editor
+ */
+define(
+ [
+ 'Ajax', 'Core', 'Dictionary', 'Dom/ChangeListener',
+ 'Dom/Traverse', 'Language', 'Ui/Dialog', 'WoltLab/WCF/Language/Chooser',
+ 'WoltLab/WCF/Language/Input', 'WoltLab/WCF/File/Util'
+ ],
+ function(
+ Ajax, Core, Dictionary, DomChangeListener,
+ DomTraverse, Language, UiDialog, LanguageChooser,
+ LanguageInput, FileUtil
+ )
+{
+ "use strict";
+
+ /**
+ * @constructor
+ */
+ function MediaEditor(callbackObject) {
+ // todo: validate callbackObject
+
+ this._callbackObject = callbackObject;
+ this._media = null;
+
+ this._elements = {};
+ };
+ MediaEditor.prototype = {
+ /**
+ * Returns the data for Ajax to setup the Ajax/Request object.
+ *
+ * @return {object} setup data for Ajax/Request object
+ */
+ _ajaxSetup: function() {
+ return {
+ data: {
+ actionName: 'update',
+ className: 'wcf\\data\\media\\MediaAction'
+ }
+ };
+ },
+
+ /**
+ * Handles successful AJAX requests.
+ *
+ * @param {object} data response data
+ */
+ _ajaxSuccess: function(data) {
+ // TODO: check if there are validation errors?
+
+ // TODO: success message
+
+ this._callbackObject._editorSuccess(this._media);
+
+ UiDialog.close('mediaEditor');
+
+ this._media = null;
+ },
+
+ /**
+ * Is called if the editor is manually closed by the user.
+ */
+ _close: function() {
+ this._media = null;
+
+ this._callbackObject._editorClose();
+ },
+
+ /**
+ * Returns the data for Ui/Dialog to setup the editor dialog.
+ *
+ * @return {object} data to setup the editor dialog
+ */
+ _dialogSetup: function() {
+ return {
+ id: 'mediaEditor',
+ options: {
+ backdropCloseOnClick: false,
+ onClose: this._close.bind(this),
+ title: Language.get('wcf.media.edit')
+ },
+ source: {
+ after: (function(content, data) {
+ var editor = UiDialog.getDialog('mediaEditor').content;
+
+ // data elements
+ this._elements.thumbnail = elById('mediaThumbnail');
+ this._elements.filename = elById('mediaFilename');
+ this._elements.filesize = elById('mediaFilesize');
+ this._elements.imageDimensions = elById('mediaImageDimensions');
+ this._elements.fileIcon = elById('mediaFileIcon');
+ this._elements.uploader = elById('mediaUploader');
+
+ // input elements
+ this._elements.altText = elById('altText');
+ this._elements.caption = elById('caption');
+ this._elements.isMultilingual = elById('isMultilingual');
+ this._elements.isMultilingual.addEventListener('change', this._updateLanguageFields.bind(this));
+ this._elements.title = elById('title');
+ this._elements.languageIdContainer = elById('languageIDContainer');
+
+ var keyPress = this._keyPress.bind(this);
+ this._elements.altText.addEventListener('keypress', keyPress);
+ this._elements.title.addEventListener('keypress', keyPress);
+
+ setTimeout(this._setData.bind(this), 100);
+
+ elBySel('button[data-type="submit"]', editor).addEventListener('click', this._saveData.bind(this));
+ }).bind(this),
+ data: {
+ actionName: 'getEditorDialog',
+ className: 'wcf\\data\\media\\MediaAction'
+ }
+ }
+ };
+ },
+
+ /**
+ * Handles the `[ENTER]` key to submit the form.
+ *
+ * @param {object} event event object
+ */
+ _keyPress: function(event) {
+ // 13 = [ENTER]
+ if (event.charCode === 13) {
+ event.preventDefault();
+
+ this._saveData();
+ }
+ },
+
+ /**
+ * Saves the data of the currently edited media.
+ */
+ _saveData: function() {
+ var hasError = false;
+ var altTextError = DomTraverse.childByClass(this._elements.altText.parentNode.parentNode, 'innerError');
+ var captionError = DomTraverse.childByClass(this._elements.caption.parentNode.parentNode, 'innerError');
+ var titleError = DomTraverse.childByClass(this._elements.title.parentNode.parentNode, 'innerError');
+
+ this._media.isMultilingual = ~~this._elements.isMultilingual.checked;
+ this._media.languageID = this._media.isMultilingual ? null : LanguageChooser.getLanguageId('languageID');
+
+ this._media.altText = {};
+ this._media.caption = {};
+ this._media.title = {};
+ if (this._media.isMultilingual) {
+ if (!LanguageInput.validate('altText', true)) {
+ hasError = true;
+ if (!altTextError) {
+ var error = elCreate('small');
+ error.className = 'innerError';
+ error.textContent = Language.get('wcf.global.form.error.multilingual');
+ this._elements.altText.parentNode.parentNode.appendChild(error);
+ }
+ }
+ if (!LanguageInput.validate('caption', true)) {
+ hasError = true;
+ if (!captionError) {
+ var error = elCreate('small');
+ error.className = 'innerError';
+ error.textContent = Language.get('wcf.global.form.error.multilingual');
+ this._elements.caption.parentNode.parentNode.appendChild(error);
+ }
+ }
+ if (!LanguageInput.validate('title', true)) {
+ hasError = true;
+ if (!titleError) {
+ var error = elCreate('small');
+ error.className = 'innerError';
+ error.textContent = Language.get('wcf.global.form.error.multilingual');
+ this._elements.title.parentNode.parentNode.appendChild(error);
+ }
+ }
+
+ this._media.altText = LanguageInput.getValues('altText').toObject();
+ this._media.caption = LanguageInput.getValues('caption').toObject();
+ this._media.title = LanguageInput.getValues('title').toObject();
+ }
+ else {
+ this._media.altText[this._media.languageID] = this._elements.altText.value;
+ this._media.caption[this._media.languageID] = this._elements.caption.value;
+ this._media.title[this._media.languageID] = this._elements.title.value;
+ }
+
+ if (!hasError) {
+ if (altTextError) elRemove(altTextError);
+ if (captionError) elRemove(captionError);
+ if (titleError) elRemove(titleError);
+
+ Ajax.api(this, {
+ actionName: 'update',
+ objectIDs: [ this._media.mediaID ],
+ parameters: {
+ altText: this._media.altText,
+ caption: this._media.caption,
+ data: {
+ isMultilingual: this._media.isMultilingual,
+ languageID: this._media.languageID
+ },
+ title: this._media.title
+ }
+ });
+ }
+ },
+
+ /**
+ * Inserts the data of the currently edited media into the dialog.
+ */
+ _setData: function() {
+ this._elements.thumbnail.innerHTML = '';
+ this._elements.fileIcon.parentNode.classList.remove('marginTop');
+
+ this._elements.filename.textContent = this._media.filename;
+ this._elements.filesize.textContent = this._media.formattedFilesize;
+
+ this._elements.uploader.innerHTML = '';
+ if (this._media.userLink) {
+ var a = elCreate('a');
+ a.className = 'userLink';
+ elAttr(a, 'href', this._media.userLink);
+ elData(a, 'user-id', this._media.userID);
+ a.textContent = this._media.username;
+
+ this._elements.uploader.appendChild(a);
+ }
+ else {
+ this._elements.uploader.textContent = this._media.username;
+ }
+
+ if (this._media.isImage) {
+ if (this._media.smallThumbnailLink) {
+ var img = elCreate('img');
+ elAttr(img, 'src', this._media.smallThumbnailLink);
+ elAttr(img, 'alt', '');
+
+ this._elements.thumbnail.appendChild(img);
+
+ this._elements.fileIcon.parentNode.classList.add('marginTop');
+ }
+
+ this._elements.imageDimensions.textContent = Language.get('wcf.media.imageDimensions.value', {
+ height: this._media.height,
+ width: this._media.width
+ });
+ elShow(this._elements.imageDimensions);
+ elShow(this._elements.imageDimensions.previousElementSibling);
+
+ this._elements.fileIcon.className = 'icon icon48 fa-file-image-o';
+ }
+ else {
+ elHide(this._elements.imageDimensions);
+ elHide(this._elements.imageDimensions.previousElementSibling);
+
+ this._elements.fileIcon.className = 'icon icon48 ' + FileUtil.getIconClassByMimeType(this._media.fileType);
+ }
+
+ this._elements.isMultilingual.checked = this._media.isMultilingual;
+
+ LanguageChooser.setLanguageId('languageID', this._media.languageID || LANGUAGE_ID);
+
+ if (this._media.isMultilingual) {
+ LanguageInput.setValues('altText', Dictionary.fromObject(this._media.altText || { }));
+ LanguageInput.setValues('caption', Dictionary.fromObject(this._media.caption || { }));
+ LanguageInput.setValues('title', Dictionary.fromObject(this._media.title || { }));
+ }
+ else {
+ this._elements.altText.value = this._media.altText ? this._media.altText[languageId] : '';
+ this._elements.caption.value = this._media.caption ? this._media.caption[this._media.languageID] : '';
+ this._elements.title.value = this._media.title ? this._media.title[this._media.languageID] : '';
+ }
+
+ this._updateLanguageFields();
+
+ DomChangeListener.trigger();
+ },
+
+ /**
+ * Updates language-related input fields depending on whether multilingualism
+ * is enabled.
+ */
+ _updateLanguageFields: function() {
+ if (this._elements.isMultilingual.checked) {
+ LanguageInput.enable('title');
+ LanguageInput.enable('caption');
+ LanguageInput.enable('altText');
+
+ elHide(this._elements.languageIdContainer.parentNode);
+ }
+ else {
+ LanguageInput.disable('title');
+ LanguageInput.disable('caption');
+ LanguageInput.disable('altText');
+
+ elShow(this._elements.languageIdContainer.parentNode);
+ }
+ },
+
+ /**
+ * Edits the media with the given data.
+ *
+ * @param {object} media data of the edited media
+ */
+ edit: function(media) {
+ if (this._media !== null) {
+ throw new Error("Cannot edit media with id '" + media.mediaID + "' while editing media with id '" + this._media.mediaID + "'")
+ }
+
+ this._media = media;
+
+ if (UiDialog.getDialog('mediaEditor') !== undefined) {
+ this._setData();
+ }
+ UiDialog.open(this);
+ }
+ };
+
+ return MediaEditor;
+});
--- /dev/null
+/**
+ * Provides the media manager dialog.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2015 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module WoltLab/WCF/Controller/Media/Manager
+ */
+define(
+ [
+ 'Core', 'Dictionary', 'Dom/ChangeListener', 'Dom/Traverse',
+ 'Dom/Util', 'EventHandler', 'Language', 'List',
+ 'Permission', 'Ui/Dialog', 'Ui/Notification', 'WoltLab/WCF/Controller/Clipboard',
+ 'WoltLab/WCF/Media/Editor', 'WoltLab/WCF/Media/Upload', 'WoltLab/WCF/Media/Search'
+
+
+ ],
+ function(
+ Core, Dictionary, DomChangeListener, DomTraverse,
+ DomUtil, EventHandler, Language, List,
+ Permission, UiDialog, UiNotification, Clipboard,
+ MediaEditor, MediaUpload, MediaSearch
+ )
+{
+ "use strict";
+
+ /**
+ * @constructor
+ */
+ function MediaManager() {
+ this._media = new Dictionary();
+ this._mediaData = new Dictionary();
+ this._mediaCache = null;
+ this._mediaManagerMediaList = null;
+ this._search = null;
+
+ if (Permission.get('admin.content.cms.canManageMedia')) {
+ this._mediaEditor = new MediaEditor(this);
+ }
+
+ elById('mediaManagerButton').addEventListener('click', this._click.bind(this));
+
+ DomChangeListener.add('WoltLab/WCF/Controller/Media/Manager', this._addButtonEventListeners.bind(this));
+ };
+ MediaManager.prototype = {
+ /**
+ * Adds click event listeners to media buttons.
+ */
+ _addButtonEventListeners: function() {
+ if (!this._mediaManagerMediaList) return;
+
+ var listItems = DomTraverse.childrenByTag(this._mediaManagerMediaList, 'LI');
+ for (var i = 0, length = listItems.length; i < length; i++) {
+ var listItem = listItems[i];
+
+ if (Permission.get('admin.content.cms.canManageMedia')) {
+ var editIcon = elByClass('jsMediaEditIcon', listItem)[0];
+ if (editIcon) {
+ editIcon.classList.remove('jsMediaEditIcon');
+ editIcon.addEventListener('click', this._editMedia.bind(this));
+ }
+ }
+
+ var insertIcon = elByClass('jsMediaInsertIcon', listItem)[0];
+ if (insertIcon) {
+ insertIcon.classList.remove('jsMediaInsertIcon');
+ insertIcon.addEventListener('click', this._openInsertDialog.bind(this));
+ }
+ }
+ },
+
+ /**
+ * Handles clicks on the media manager button.
+ *
+ * @param {object} event event object
+ */
+ _click: function(event) {
+ event.preventDefault();
+
+ UiDialog.open(this);
+ },
+
+ /**
+ * Reacts to executed clipboard actions.
+ *
+ * @param {object<string, *>} actionData data of the executed clipboard action
+ */
+ _clipboardAction: function(actionData) {
+ // only consider events if the action has been executed
+ if (actionData.responseData === null) {
+ return;
+ }
+
+ switch (actionData.data.actionName) {
+ case 'com.woltlab.wcf.media.delete':
+ var mediaIds = actionData.responseData.objectIDs;
+ for (var i = 0, length = mediaIds.length; i < length; i++) {
+ this.removeMedia(~~mediaIds[i], true);
+ }
+
+ UiNotification.show();
+
+ break;
+ case 'com.woltlab.wcf.media.insert':
+ // TODO
+ break;
+ }
+ },
+
+ /**
+ * Returns all data to setup the media manager dialog.
+ *
+ * @return {object} dialog setup data
+ */
+ _dialogSetup: function() {
+ return {
+ id: 'mediaManager',
+ options: {
+ title: Language.get('wcf.media.manager')
+ },
+ source: {
+ after: this._initDialog.bind(this),
+ data: {
+ actionName: 'getManagementDialog',
+ className: 'wcf\\data\\media\\MediaAction'
+ }
+ }
+ };
+ },
+
+ /**
+ * Opens the media editor for a media file.
+ *
+ * @param {Event} event event object for clicks on edit icons
+ */
+ _editMedia: function(event) {
+ if (!Permission.get('admin.content.cms.canManageMedia')) {
+ throw new Error("You are not allowed to edit media files.");
+ }
+
+ UiDialog.close('mediaManager');
+
+ this._mediaEditor.edit(this._mediaData.get(~~elData(event.currentTarget, 'object-id')));
+ },
+
+ /**
+ * Re-opens the manager dialog after closing the editor dialog.
+ */
+ _editorClose: function() {
+ UiDialog.open(this);
+ },
+
+ /**
+ * Re-opens the manager dialog and updates the media data after
+ * successfully editing a media file.
+ *
+ * @param {object} media updated media file data
+ */
+ _editorSuccess: function(media) {
+ UiDialog.open(this);
+
+ this._mediaData.set(~~media.mediaID, media);
+
+ var listItem = this._media.get(~~media.mediaID);
+ var p = elByClass('mediaTitle', listItem)[0];
+ if (media.isMultilingual) {
+ p.textContent = media.title[LANGUAGE_ID] || media.filename;
+ }
+ else {
+ p.textContent = media.title[media.languageID] || media.filename;
+ }
+ },
+
+ /**
+ * Initializes the dialog when first loaded.
+ *
+ * @param {string} content dialog content
+ * @param {object} data AJAX request's response data
+ */
+ _initDialog: function(content, data) {
+ // store media data locally
+ var media = data.returnValues.media || { };
+ for (var mediaId in media) {
+ if (media.hasOwnProperty(mediaId)) {
+ this._mediaData.set(~~mediaId, media[mediaId]);
+ }
+ }
+
+ this._mediaManagerMediaList = elById('mediaManagerMediaList');
+
+ // store list items locally
+ var listItems = DomTraverse.childrenByTag(this._mediaManagerMediaList, 'LI');
+ for (var i = 0, length = listItems.length; i < length; i++) {
+ var listItem = listItems[i];
+
+ this._media.set(~~elData(listItem, 'object-id'), listItem);
+ }
+
+ if (Permission.get('admin.content.cms.canManageMedia')) {
+ new MediaUpload('mediaManagerMediaUploadButton', 'mediaManagerMediaList', {
+ mediaManager: this
+ });
+
+ Clipboard.setup({
+ hasMarkedItems: data.returnValues.hasMarkedItems ? true : false,
+ pageClassName: '*'
+ });
+
+ EventHandler.add('com.woltlab.wcf.clipboard', 'com.woltlab.wcf.media', this._clipboardAction.bind(this));
+ }
+
+ this._search = new MediaSearch(this);
+
+ if (!listItems.length) {
+ this._search.hideSearch();
+
+ if (true) {
+ elById('mediaManagerMediaUploadButton').classList.remove('marginTop');
+ }
+ }
+ },
+
+ _insertMedia: function() {
+ // TODO
+ },
+
+ _openInsertDialog: function(event) {
+ var media = this._mediaData.get(~~elData(event.currentTarget, 'object-id'));
+
+ // check if media file is image and has at least small thumbnail
+ // to show insertion options
+ if (media.isImage && media.smallThumbnailType) {
+ UiDialog.close(this);
+ var dialogId = 'mediaInsert' + media.mediaID;
+ if (UiDialog.getDialog(dialogId)) {
+ UiDialog.openStatic(dialogId);
+ }
+ else {
+ var dialog = elCreate('div');
+
+ var fieldset = elCreate('fieldset');
+ dialog.appendChild(fieldset);
+
+ var dl = elCreate('dl');
+ fieldset.appendChild(dl);
+
+ var dt = elCreate('dt');
+ dt.textContent = Language.get('wcf.media.insert.imageSize');
+ dl.appendChild(dt);
+
+ var dd = elCreate('dd');
+ dl.appendChild(dd);
+
+ var select = elCreate('select');
+ dd.appendChild(select);
+
+ var sizes = ['small', 'medium', 'large'];
+ var size, option;
+ for (var i = 0, length = sizes.length; i < length; i++) {
+ size = sizes[i];
+
+ if (media[size + 'ThumbnailType']) {
+ option = elCreate('option');
+ elAttr(option, 'value', size);
+ option.textContent = Language.get('wcf.media.insert.imageSize.' + size, {
+ height: media[size + 'ThumbnailHeight'],
+ width: media[size + 'ThumbnailWidth']
+ });
+ select.appendChild(option);
+ }
+ }
+
+ option = elCreate('option');
+ elAttr(option, 'value', 'original');
+ option.textContent = Language.get('wcf.media.insert.imageSize.original', {
+ height: media.height,
+ width: media.width
+ });
+ select.appendChild(option);
+
+ var formSubmit = elCreate('div');
+ formSubmit.className = 'formSubmit';
+ dialog.appendChild(formSubmit);
+
+ var submitButton = elCreate('button');
+ submitButton.className = 'buttonPrimary';
+ submitButton.textContent = Language.get('wcf.global.button.insert');
+ elData(submitButton, 'object-id', media.mediaID);
+ submitButton.addEventListener('click', this._insertMedia.bind(this));
+ formSubmit.appendChild(submitButton);
+
+ UiDialog.open({
+ _dialogSetup: (function() {
+ return {
+ id: dialogId,
+ options: {
+ onClose: this._editorClose.bind(this),
+ title: Language.get('wcf.media.insert')
+ },
+ source: dialog.outerHTML
+ }
+ }).bind(this)
+ });
+ }
+ }
+ else {
+ // insert media
+ // TODO
+ }
+ },
+
+ _setMedia: function(media, listItems) {
+ if (Core.isPlainObject(media)) {
+ this._media = Dictionary.fromObject(media);
+ }
+ else {
+ this._media = media;
+ }
+
+ var info = DomTraverse.nextByClass(this._mediaManagerMediaList, 'info');
+
+ if (this._media.size) {
+ if (info) {
+ elHide(info);
+ }
+ }
+ else {
+ if (info === null) {
+ info = elCreate('p');
+ info.className = 'info';
+ info.textContent = Language.get('wcf.media.search.noResults');
+ }
+
+ elShow(info);
+ DomUtil.insertAfter(info, this._mediaManagerMediaList);
+ }
+
+ var mediaListItems = DomTraverse.childrenByTag(this._mediaManagerMediaList, 'LI');
+ for (var i = 0, length = mediaListItems.length; i < length; i++) {
+ var listItem = mediaListItems[i];
+
+ if (!this._media.has(elData(listItem, 'object-id'))) {
+ elHide(listItem);
+ }
+ else {
+ elShow(listItem);
+ }
+ }
+
+ DomChangeListener.trigger();
+
+ Clipboard.reload();
+ },
+
+ /**
+ * Adds a media file to the manager.
+ *
+ * @param {object} media data of the media file
+ * @param {Element} listItem list item representing the file
+ */
+ addMedia: function(media, listItem) {
+ if (!media.languageID) media.isMultilingual = 1;
+
+ this._mediaData.set(~~media.mediaID, media);
+ this._media.set(~~media.mediaID, listItem);
+
+ if (this._media.size === 1) {
+ this._search.showSearch();
+
+ if (true) {
+ elById('mediaManagerMediaUploadButton').classList.add('marginTop');
+ }
+ }
+ },
+
+ /**
+ * Removes a media file.
+ *
+ * @param {int} mediaId id of the removed media file
+ * @param {boolean|undefined} checkCache media file will also be removed from the local cache if true
+ */
+ removeMedia: function(mediaId, checkCache) {
+ if (this._media.has(mediaId)) {
+ // remove list item
+ elRemove(this._media.get(mediaId));
+
+ this._media.delete(mediaId);
+ this._mediaData.delete(mediaId);
+ }
+
+ if (checkCache && this._mediaCache && this._mediaCache.has(mediaId)) {
+ this._mediaCache.delete(mediaId);
+ }
+ },
+
+ /**
+ * Changes the displayed media to the previously displayed media.
+ */
+ resetMedia: function() {
+ if (this._mediaCache !== null) {
+ this._setMedia(this._mediaCache);
+
+ this._mediaCache = null;
+
+ this._search.resetSearch();
+ }
+ },
+
+ /**
+ * Sets the media files currently displayed.
+ *
+ * @param {object} media media data
+ * @param {string} template
+ */
+ setMedia: function(media, template) {
+ if (!this._mediaCache) {
+ this._mediaCache = this._media;
+ }
+
+ var hasMedia = false;
+ for (var mediaId in media) {
+ if (media.hasOwnProperty(mediaId)) {
+ hasMedia = true;
+ }
+ }
+
+ var newListItems = [];
+ if (hasMedia) {
+ var ul = elCreate('ul');
+ ul.innerHTML = template;
+
+ var listItems = DomTraverse.childrenByTag(ul, 'LI');
+ for (var i = 0, length = listItems.length; i < length; i++) {
+ var listItem = listItems[i];
+ if (!this._mediaData.has(~~elData(listItem, 'object-id'))) {
+ this._mediaData.set(elData(listItem, 'object-id'), listItem);
+
+ this._mediaManagerMediaList.appendChild(listItem);
+ }
+ }
+ }
+
+ this._setMedia(media);
+ }
+ };
+
+ return MediaManager;
+});
--- /dev/null
+/**
+ * Provides the media search for the media manager.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2015 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module WoltLab/WCF/Controller/Media/Search
+ */
+define(['Ajax', 'Dom/Traverse', 'Dom/Util', 'Language', 'Ui/SimpleDropdown'], function(Ajax, DomTraverse, DomUtil, Language, UiSimpleDropdown) {
+ "use strict";
+
+ /**
+ * @constructor
+ */
+ function MediaSearch(mediaManager) {
+ this._mediaManager = mediaManager;
+ this._searchMode = false;
+ this._fileType = 'all';
+
+ this._input = elById('mediaManagerSearchField');
+ this._input.addEventListener('keypress', this._keyPress.bind(this));
+
+ this._cancelButton = elById('mediaManagerSearchCancelButton');
+ this._cancelButton.addEventListener('click', this._cancelSearch.bind(this));
+
+ this._fileTypes = DomTraverse.childrenBySel(UiSimpleDropdown.getDropdownMenu('mediaManagerSearch'), 'li:not(.dropdownDivider)');
+ var selectFileType = this._selectFileType.bind(this);
+ for (var i = 0, length = this._fileTypes.length; i < length; i++) {
+ this._fileTypes[i].addEventListener('click', selectFileType);
+ }
+
+ UiSimpleDropdown.registerCallback('mediaManagerSearch', this._updateFileTypeDropdown.bind(this));
+ };
+ MediaSearch.prototype = {
+ /**
+ * Returns the data for Ajax to setup the Ajax/Request object.
+ *
+ * @return {object} setup data for Ajax/Request object
+ */
+ _ajaxSetup: function() {
+ return {
+ data: {
+ actionName: 'getSearchResultList',
+ className: 'wcf\\data\\media\\MediaAction',
+ interfaceName: 'wcf\\data\\ISearchAction'
+ }
+ };
+ },
+
+ /**
+ * Handles successful AJAX requests.
+ *
+ * @param {object} data response data
+ */
+ _ajaxSuccess: function(data) {
+ this._mediaManager.setMedia(data.returnValues.media || { }, data.returnValues.template || '');
+ },
+
+ /**
+ * Cancels the search after clicking on the cancel search button.
+ */
+ _cancelSearch: function() {
+ if (this._searchMode) {
+ this._searchMode = false;
+
+ this._mediaManager.resetMedia();
+ this.resetSearch();
+ }
+ },
+
+ /**
+ * Handles the `[ENTER]` key to submit the form.
+ *
+ * @param {Event} event event object
+ */
+ _keyPress: function(event) {
+ // 13 = [ENTER]
+ if (event.charCode === 13) {
+ event.preventDefault();
+
+ var innerInfo = DomTraverse.childByClass(this._input.parentNode, '.innerInfo');
+
+ // TODO: treshold option?
+ if (this._input.value.length >= 3) {
+ if (innerInfo) {
+ elHide(innerInfo);
+ }
+
+ this._search();
+ }
+ else {
+ if (innerInfo) {
+ elShow(innerInfo);
+ }
+ else {
+ innerInfo = elCreate('p');
+ innerInfo.className = 'innerInfo';
+ innerInfo.textContent = Language.get('wcf.media.search.info.searchStringTreshold');
+
+ this._input.parentNode.appendChild(innerInfo);
+ }
+ }
+ }
+ },
+
+ /**
+ * Sends an AJAX request to fetch seach results.
+ */
+ _search: function() {
+ this._searchMode = true;
+
+ Ajax.api(this, {
+ parameters: {
+ data: {
+ fileType: this._fileType,
+ // TODO: treshold option?
+ searchString: this._input.value.length > 3 ? this._input.value : ''
+ }
+ }
+ });
+ },
+
+ /**
+ * Selects a certain file type after clicking on it in the dropdown menu.
+ *
+ * @param {Event} event
+ */
+ _selectFileType: function(event) {
+ this._fileType = elData(event.currentTarget, 'file-type');
+
+ this._updateDropdownButtonLabel();
+
+ this._search();
+ },
+
+ /**
+ * Updates the label of the dropdown button based on the currently selected file type.
+ */
+ _updateDropdownButtonLabel: function() {
+ var dropdown = UiSimpleDropdown.getDropdown('mediaManagerSearch');
+ var buttonLabel = DomTraverse.childBySel(DomTraverse.childByClass(dropdown, 'dropdownToggle'), 'SPAN');
+
+ if (this._fileType !== 'all') {
+ buttonLabel.textContent = DomTraverse.childBySel(event.currentTarget, 'SPAN').textContent;
+ }
+ else {
+ buttonLabel.textContent = Language.get('wcf.media.search.filetype');
+ }
+ },
+
+ /**
+ * Updates the file type dropdown by correctly marking the currently selected file type.
+ */
+ _updateFileTypeDropdown: function() {
+ for (var i = 0, length = this._fileTypes.length; i < length; i++) {
+ var listItem = this._fileTypes[i];
+
+ listItem.classList[elData(listItem, 'file-type') === this._fileType ? 'add' : 'remove']('active');
+ }
+ },
+
+ /**
+ * Hides the media search.
+ */
+ hideSearch: function() {
+ elHide(elById('mediaManagerSearch'));
+ },
+
+ /**
+ * Resets the media search.
+ */
+ resetSearch: function() {
+ this._input.value = '';
+ this._fileType = 'all';
+
+ this._updateDropdownButtonLabel();
+ },
+
+ /**
+ * Shows the media search.
+ */
+ showSearch: function() {
+ elShow(elById('mediaManagerSearch'));
+ }
+ };
+
+ return MediaSearch;
+});
--- /dev/null
+/**
+ * Uploads media files.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2015 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module WoltLab/WCF/Controller/Media/Upload
+ */
+define(
+ [
+ 'Core', 'Dom/ChangeListener', 'Dom/Traverse', 'Dom/Util',
+ 'EventHandler', 'Language', 'Permission', 'Upload',
+ 'WoltLab/WCF/File/Util'
+ ],
+ function(
+ Core, DomChangeListener, DomTraverse, DomUtil,
+ EventHandler, Language, Permission, Upload,
+ FileUtil
+ )
+{
+ "use strict";
+
+ /**
+ * @constructor
+ */
+ function MediaUpload(buttonContainerId, targetId, options) {
+ options = options || {};
+
+ this._mediaManager = null;
+ if (options.mediaManager) {
+ this._mediaManager = options.mediaManager;
+ delete options.mediaManager;
+ }
+
+ Upload.call(this, buttonContainerId, targetId, Core.extend({
+ className: 'wcf\\data\\media\\MediaAction',
+ multiple: this._mediaManager ? true : false,
+ singleFileRequests: true
+ }, options));
+ };
+ Core.inherit(MediaUpload, Upload, {
+ /**
+ * @see WoltLab/WCF/Upload#_createFileElement
+ */
+ _createFileElement: function(file) {
+ var fileElement;
+ if (this._target.nodeName === 'OL' || this._target.nodeName === 'UL') {
+ fileElement = elCreate('li');
+ }
+ else {
+ fileElement = elCreate('p');
+ }
+
+ var thumbnail = elCreate('div');
+ thumbnail.className = 'mediaThumbnail';
+ fileElement.appendChild(thumbnail);
+
+ var fileIcon = elCreate('span');
+ fileIcon.className = 'icon icon96 fa-spinner';
+ thumbnail.appendChild(fileIcon);
+
+ var mediaInformation = elCreate('div');
+ mediaInformation.className = 'mediaInformation';
+ fileElement.appendChild(mediaInformation);
+
+ var p = elCreate('p');
+ p.className = 'mediaTitle';
+ p.textContent = file.name;
+ mediaInformation.appendChild(p);
+
+ var progress = elCreate('progress');
+ elAttr(progress, 'max', 100);
+ mediaInformation.appendChild(progress);
+
+ DomUtil.prepend(fileElement, this._target);
+
+ DomChangeListener.trigger();
+
+ return fileElement;
+ },
+
+ /**
+ * @see WoltLab/WCF/Upload#_success
+ */
+ _success: function(uploadId, data) {
+ var files = this._fileElements[uploadId];
+
+ for (var i = 0, length = files.length; i < length; i++) {
+ var file = files[i];
+ var internalFileId = elData(file, 'internal-file-id');
+ var media = data.returnValues.media[internalFileId];
+
+ if (media) {
+ var fileIcon = DomTraverse.childByTag(DomTraverse.childByClass(file, 'mediaThumbnail'), 'SPAN');
+ if (media.tinyThumbnailType) {
+ var parentNode = fileIcon.parentNode;
+ elRemove(fileIcon);
+
+ var img = elCreate('img');
+ elAttr(img, 'src', media.tinyThumbnailLink);
+ elAttr(img, 'alt', '');
+ img.style.setProperty('width', '96px');
+ img.style.setProperty('height', '96px');
+ parentNode.appendChild(img);
+ }
+ else {
+ fileIcon.classList.remove('fa-spinner');
+ fileIcon.classList.add(FileUtil.getIconClassByMimeType(media.fileType));
+ }
+
+ file.className = 'jsClipboardObject';
+ elData(file, 'object-id', media.mediaID);
+
+ var mediaInformation = DomTraverse.childByClass(file, 'mediaInformation');
+
+ elRemove(DomTraverse.childByTag(mediaInformation, 'PROGRESS'));
+
+ if (this._mediaManager) {
+ var buttonGroupNavigation = elCreate('nav');
+ buttonGroupNavigation.className = 'buttonGroupNavigation';
+ mediaInformation.parentNode.appendChild(buttonGroupNavigation);
+
+ var smallButtons = elCreate('ul');
+ smallButtons.className = 'smallButtons buttonGroup';
+ buttonGroupNavigation.appendChild(smallButtons);
+
+ var listItem = elCreate('li');
+ smallButtons.appendChild(listItem);
+
+ var checkbox = elCreate('input');
+ checkbox.className = 'jsClipboardItem jsMediaCheckbox';
+ elAttr(checkbox, 'type', 'checkbox');
+ elData(checkbox, 'object-id', media.mediaID);
+ listItem.appendChild(checkbox);
+
+ if (Permission.get('admin.content.cms.canManageMedia')) {
+ listItem = elCreate('li');
+ smallButtons.appendChild(listItem);
+
+ var a = elCreate('a');
+ listItem.appendChild(a);
+
+ var icon = elCreate('span');
+ icon.className = 'icon icon16 fa-pencil jsTooltip jsMediaEditIcon';
+ elData(icon, 'object-id', media.mediaID);
+ elAttr(icon, 'title', Language.get('wcf.global.button.edit'));
+ a.appendChild(icon);
+
+ listItem = elCreate('li');
+ smallButtons.appendChild(listItem);
+
+ a = elCreate('a');
+ listItem.appendChild(a);
+
+ icon = elCreate('span');
+ icon.className = 'icon icon16 fa-times jsTooltip jsMediaDeleteIcon';
+ elData(icon, 'object-id', media.mediaID);
+ elAttr(icon, 'title', Language.get('wcf.global.button.delete'));
+ a.appendChild(icon);
+ }
+
+ listItem = elCreate('li');
+ smallButtons.appendChild(listItem);
+
+ var a = elCreate('a');
+ listItem.appendChild(a);
+
+ var icon = elCreate('span');
+ icon.className = 'icon icon16 fa-plus jsTooltip jsMediaInsertIcon';
+ elData(icon, 'object-id', media.mediaID);
+ elAttr(icon, 'title', Language.get('wcf.media.button.insert'));
+ a.appendChild(icon);
+
+ this._mediaManager.resetMedia();
+ this._mediaManager.addMedia(media, file);
+ }
+
+ DomChangeListener.trigger();
+ }
+ else {
+ // error: TODO
+ }
+ }
+
+ EventHandler.fire('com.woltlab.wcf.media.upload', 'success', {
+ files: files,
+ media: data.returnValues.media,
+ upload: this
+ });
+ }
+ });
+
+ return MediaUpload;
+});
var content = elCreate('div');
content.innerHTML = html;
+ var scripts = elBySelAll('script', content);
+ for (var i = 0, length = scripts.length; i < length; i++) {
+ var script = scripts[i];
+ var newScript = elCreate('script');
+ newScript.innerHTML = script.innerHTML;
+ content.appendChild(newScript);
+
+ script.parentNode.removeChild(script);
+ }
+
data.content.appendChild(content);
}
else {
var p = elCreate('p');
p.appendChild(progress);
-
+
this._target.appendChild(p);
-
+
return p;
}
},
formData.append('actionName', this._options.action);
formData.append('className', this._options.className);
+ formData.append('interfaceName', 'wcf\\data\\IUploadAction');
var additionalParameters = this._getParameters();
for (var name in additionalParameters) {
formData.append('parameters[' + name + ']', additionalParameters[name]);
data: formData,
contentType: false,
failure: this._failure.bind(this, uploadId),
+ silent: true,
success: this._success.bind(this, uploadId),
uploadProgress: this._progress.bind(this, uploadId),
url: this._options.url
'Language': 'WoltLab/WCF/Language',
'List': 'WoltLab/WCF/List',
'ObjectMap': 'WoltLab/WCF/ObjectMap',
+ 'Permission': 'WoltLab/WCF/Permission',
'StringUtil': 'WoltLab/WCF/StringUtil',
'Ui/Alignment': 'WoltLab/WCF/Ui/Alignment',
'Ui/CloseOverlay': 'WoltLab/WCF/Ui/CloseOverlay',
'Ui/Notification': 'WoltLab/WCF/Ui/Notification',
'Ui/ReusableDropdown': 'WoltLab/WCF/Ui/Dropdown/Reusable',
'Ui/SimpleDropdown': 'WoltLab/WCF/Ui/Dropdown/Simple',
- 'Ui/TabMenu': 'WoltLab/WCF/Ui/TabMenu'
+ 'Ui/TabMenu': 'WoltLab/WCF/Ui/TabMenu',
+ 'Upload': 'WoltLab/WCF/Upload'
}
}
});
--- /dev/null
+<?php
+namespace wcf\acp\form;
+use wcf\data\media\Media;
+use wcf\data\media\MediaAction;
+use wcf\form\AbstractForm;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\exception\UserInputException;
+use wcf\system\language\I18nHandler;
+use wcf\system\language\LanguageFactory;
+use wcf\system\WCF;
+use wcf\util\ArrayUtil;
+
+/**
+ * Shows the form to edit a media file.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2015 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf
+ * @subpackage acp.form
+ * @category Community Framework
+ * @since 2.2
+ */
+class MediaEditForm extends AbstractForm {
+ /**
+ * @inheritdoc
+ */
+ public $activeMenuItem = 'wcf.acp.menu.link.cms.media.list';
+
+ /**
+ * is 1 if media data is multilingual
+ * @var integer
+ */
+ public $isMultilingual = 0;
+
+ /**
+ * id of the selected language
+ * @var integer
+ */
+ public $languageID = 0;
+
+ /**
+ * edited media
+ * @var Media
+ */
+ public $media = null;
+
+ /**
+ * id of the edited media
+ * @var integer
+ */
+ public $mediaID = 0;
+
+ /**
+ * @inheritdoc
+ */
+ public $neededPermissions = ['admin.content.cms.canManageMedia'];
+
+ /**
+ * @inheritdoc
+ */
+ public function assignVariables() {
+ parent::assignVariables();
+
+ I18nHandler::getInstance()->assignVariables();
+
+ WCF::getTPL()->assign([
+ 'action' => 'edit',
+ 'isMultilingual' => $this->isMultilingual,
+ 'languages' => LanguageFactory::getInstance()->getLanguages(),
+ 'languageID' => $this->languageID,
+ 'media' => $this->media
+ ]);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function readData() {
+ parent::readData();
+
+ if (empty($_POST)) {
+ $this->isMultilingual = $this->media->isMultilingual;
+ if (!$this->isMultilingual && !$this->media->languageID) {
+ $this->isMultilingual = 1;
+ }
+
+ if ($this->media->languageID) {
+ $this->languageID = $this->media->languageID;
+ }
+ else {
+ $this->languageID = WCF::getUser()->languageID;
+ }
+
+ $contentData = $this->media->getI18nData();
+ if (!empty($contentData)) {
+ I18nHandler::getInstance()->setValues('altText', $contentData['altText']);
+ I18nHandler::getInstance()->setValues('caption', $contentData['caption']);
+ I18nHandler::getInstance()->setValues('title', $contentData['title']);
+ }
+ }
+
+ if (!$this->languageID) {
+ $this->languageID = WCF::getUser()->languageID;
+ }
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function readFormParameters() {
+ parent::readFormParameters();
+
+ if (isset($_POST['isMultilingual'])) $this->isMultilingual = intval($_POST['isMultilingual']);
+ if (!$this->isMultilingual) {
+ if (isset($_POST['languageID'])) $this->languageID = intval($_POST['languageID']);
+ }
+ I18nHandler::getInstance()->readValues();
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function readParameters() {
+ parent::readParameters();
+
+ if (isset($_REQUEST['id'])) $this->mediaID = intval($_REQUEST['id']);
+
+ $this->media = new Media($this->mediaID);
+ if (!$this->media->mediaID) {
+ throw new IllegalLinkException();
+ }
+
+ I18nHandler::getInstance()->register('title');
+ I18nHandler::getInstance()->register('caption');
+ I18nHandler::getInstance()->register('altText');
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function save() {
+ parent::save();
+
+ $this->objectAction = new MediaAction([$this->media], 'update', [
+ 'data' => [
+ 'isMultilingual' => $this->isMultilingual,
+ 'languageID' => $this->languageID ?: null
+ ],
+ 'altText' => I18nHandler::getInstance()->getValues('altText'),
+ 'caption' => I18nHandler::getInstance()->getValues('caption'),
+ 'title' => I18nHandler::getInstance()->getValues('title')
+ ]);
+ $this->objectAction->executeAction();
+
+ $this->saved();
+
+ WCF::getTPL()->assign('success', true);
+ }
+
+ /**
+ * @inheritdoc
+ * @throws UserInputException
+ */
+ public function validate() {
+ parent::validate();
+
+ if ($this->languageID && !LanguageFactory::getInstance()->getLanguage($this->languageID)) {
+ throw new UserInputException('languageID');
+ }
+
+ foreach (['title', 'caption', 'altText'] as $i18nData) {
+ if (!I18nHandler::getInstance()->validateValue($i18nData, $this->isMultilingual? true : false, false)) {
+ if ($this->isMultilingual) {
+ // in contrast to I18nHandler::validateValues(), we allow all fields to be empty
+ if (empty(ArrayUtil::trim(I18nHandler::getInstance()->getValues($i18nData)))) {
+ continue;
+ }
+
+ throw new UserInputException($i18nData, 'multilingual');
+ }
+ else {
+ throw new UserInputException($i18nData);
+ }
+ }
+ }
+ }
+}
--- /dev/null
+<?php
+namespace wcf\acp\page;
+use wcf\page\AbstractPage;
+use wcf\system\WCF;
+
+/**
+ * Shows the page to upload a media file.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2015 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf
+ * @subpackage acp.page
+ * @category Community Framework
+ * @since 2.2
+ */
+class MediaAddPage extends AbstractPage {
+ /**
+ * @inheritdoc
+ */
+ public $activeMenuItem = 'wcf.acp.menu.link.cms.media.add';
+
+ /**
+ * @inheritdoc
+ */
+ public $neededPermissions = ['admin.content.cms.canManageMedia'];
+
+ /**
+ * @inheritdoc
+ */
+ public function assignVariables() {
+ parent::assignVariables();
+
+ WCF::getTPL()->assign('action', 'add');
+ }
+}
--- /dev/null
+<?php
+namespace wcf\acp\page;
+use wcf\data\media\ViewableMediaList;
+use wcf\page\SortablePage;
+
+/**
+ * Shows the list of media entries.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2015 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf
+ * @subpackage acp.page
+ * @category Community Framework
+ * @since 2.2
+ */
+class MediaListPage extends SortablePage {
+ /**
+ * @inheritdoc
+ */
+ public $activeMenuItem = 'wcf.acp.menu.link.media.list';
+
+ /**
+ * @inheritdoc
+ */
+ public $defaultSortField = 'uploadTime';
+
+ /**
+ * @inheritdoc
+ */
+ public $defaultSortOrder = 'DESC';
+
+ /**
+ * @inheritdoc
+ */
+ public $neededPermissions = ['admin.content.cms.canManageMedia'];
+
+ /**
+ * @inheritdoc
+ */
+ public $objectListClassName = ViewableMediaList::class;
+
+ /**
+ * @inheritdoc
+ */
+ public $validSortFields = [
+ 'filename',
+ 'filesize',
+ 'mediaID',
+ 'title',
+ 'uploadTime'
+ ];
+
+ /**
+ * @inheritdoc
+ */
+ protected function readObjects() {
+ if ($this->sqlOrderBy && $this->sortField == 'mediaID') {
+ $this->sqlOrderBy = 'media.'.$this->sortField.' '.$this->sortOrder;
+ }
+
+ parent::readObjects();
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data;
+
+/**
+ * Every database object representing a file should implement this interface.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2015 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf
+ * @subpackage data
+ * @category Community Framework
+ * @since 2.2
+ */
+interface IFile extends IStorableObject {
+ /**
+ * Returns the physical location of the file.
+ *
+ * @return string
+ */
+ public function getLocation();
+}
--- /dev/null
+<?php
+namespace wcf\data;
+
+/**
+ * Every database object representing a file supporting thumbnails should implement
+ * this interface.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2015 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf
+ * @subpackage data
+ * @category Community Framework
+ * @since 2.2
+ */
+interface IThumbnailFile extends IFile {
+ /**
+ * Returns the link to the thumbnail file with the given size.
+ *
+ * @param string $size
+ * @return sting
+ */
+ public function getThumbnailLink($size);
+
+ /**
+ * Returns the physical location of the thumbnail file with the given size.
+ *
+ * @param string $size
+ * @return sting
+ */
+ public function getThumbnailLocation($size);
+
+ /**
+ * Returns the available thumbnail sizes.
+ *
+ * @return array
+ */
+ public static function getThumbnailSizes();
+}
--- /dev/null
+<?php
+namespace wcf\data;
+
+/**
+ * Every database object action supporting file upload has to implement this interface.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2015 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf
+ * @subpackage data
+ * @category Community Framework
+ * @since 2.2
+ */
+interface IUploadAction {
+ /**
+ * Validates the 'upload' action.
+ */
+ public function validateUpload();
+
+ /**
+ * Saves uploaded files and returns the data of the uploaded files.
+ *
+ * @return array
+ */
+ public function upload();
+}
namespace wcf\data\attachment;
use wcf\data\object\type\ObjectTypeCache;
use wcf\data\DatabaseObject;
+use wcf\data\IThumbnailFile;
use wcf\system\request\IRouteController;
use wcf\system\WCF;
use wcf\util\FileUtil;
+use wcf\system\request\LinkHandler;
/**
* Represents an attachment.
* @subpackage data.attachment
* @category Community Framework
*/
-class Attachment extends DatabaseObject implements IRouteController {
+class Attachment extends DatabaseObject implements IRouteController, IThumbnailFile {
/**
- * @see \wcf\data\DatabaseObject::$databaseTableName
+ * @inheritdoc
*/
protected static $databaseTableName = 'attachment';
/**
- * @see \wcf\data\DatabaseObject::$databaseTableIndexName
+ * @inheritdoc
*/
protected static $databaseTableIndexName = 'attachmentID';
/**
* user permissions for attachment access
- * @var array<boolean>
+ * @var boolean[]
*/
- protected $permissions = array();
+ protected $permissions = [];
/**
* Returns true if a user has the permission to download this attachment.
$objectType = ObjectTypeCache::getInstance()->getObjectType($this->objectTypeID);
$processor = $objectType->getProcessor();
if ($processor !== null) {
- $this->permissions[$permission] = call_user_func(array($processor, $permission), $this->objectID);
+ $this->permissions[$permission] = call_user_func([$processor, $permission], $this->objectID);
}
}
}
/**
* Sets the permissions for attachment access.
*
- * @param array<boolean> $permissions
+ * @param boolean[] $permissions
*/
public function setPermissions(array $permissions) {
$this->permissions = $permissions;
}
/**
- * Returns the physical location of this attachment.
- *
- * @return string
+ * @inheritdoc
*/
public function getLocation() {
return self::getStorage() . substr($this->fileHash, 0, 2) . '/' . ($this->attachmentID) . '-' . $this->fileHash;
* @return string
*/
public function getTinyThumbnailLocation() {
- return self::getStorage() . substr($this->fileHash, 0, 2) . '/' . ($this->attachmentID) . '-tiny-' . $this->fileHash;
+ return $this->getThumbnailLocation('tiny');
}
/**
- * Returns the physical location of the standard thumbnail.
- *
- * @return string
+ * @inheritdoc
*/
- public function getThumbnailLocation() {
+ public function getThumbnailLocation($size = '') {
+ if ($size == 'tiny') {
+ return self::getStorage() . substr($this->fileHash, 0, 2) . '/' . ($this->attachmentID) . '-tiny-' . $this->fileHash;
+ }
+
return self::getStorage() . substr($this->fileHash, 0, 2) . '/' . ($this->attachmentID) . '-thumbnail-' . $this->fileHash;
}
/**
- * @see \wcf\system\request\IRouteController::getTitle()
+ * @inheritdoc
+ */
+ public function getThumbnailLink($size = '') {
+ $parameters = [
+ 'id' => $this->attachmentID
+ ];
+
+ if ($size == 'tiny') {
+ $parameters['tiny'] = 1;
+ }
+
+ return LinkHandler::getInstance()->getLink('Attachment', $parameters);
+ }
+
+ /**
+ * @inheritdoc
*/
public function getTitle() {
return $this->filename;
return WCF_DIR . 'attachments/';
}
+
+ /**
+ * @inheritdoc
+ */
+ public static function getThumbnailSizes() {
+ return [
+ 'tiny' => [
+ 'height' => 144,
+ 'retainDimensions' => false,
+ 'width' => 144
+ ],
+ // standard thumbnail size
+ '' => [
+ 'height' => ATTACHMENT_THUMBNAIL_HEIGHT,
+ 'retainDimensions' => ATTACHMENT_RETAIN_DIMENSIONS,
+ 'width' => ATTACHMENT_THUMBNAIL_WIDTH
+ ]
+ ];
+ }
}
<?php
namespace wcf\data\attachment;
+use wcf\data\ISortableAction;
+use wcf\data\IUploadAction;
use wcf\data\object\type\ObjectTypeCache;
use wcf\data\AbstractDatabaseObjectAction;
use wcf\system\attachment\AttachmentHandler;
use wcf\system\exception\UserInputException;
use wcf\system\image\ImageHandler;
use wcf\system\request\LinkHandler;
+use wcf\system\upload\DefaultUploadFileSaveStrategy;
use wcf\system\upload\DefaultUploadFileValidationStrategy;
use wcf\system\WCF;
use wcf\util\ArrayUtil;
* @subpackage data.attachment
* @category Community Framework
*/
-class AttachmentAction extends AbstractDatabaseObjectAction {
+class AttachmentAction extends AbstractDatabaseObjectAction implements ISortableAction, IUploadAction {
/**
- * @see \wcf\data\AbstractDatabaseObjectAction::$allowGuestAccess
+ * @inheritdoc
*/
- protected $allowGuestAccess = array('delete', 'updatePosition', 'upload');
+ protected $allowGuestAccess = ['delete', 'updatePosition', 'upload'];
/**
- * @see \wcf\data\AbstractDatabaseObjectAction::$className
+ * @inheritdoc
*/
- protected $className = 'wcf\data\attachment\AttachmentEditor';
+ protected $className = AttachmentEditor::class;
/**
* current attachment object, used to communicate with event listeners
- * @var \wcf\data\attachment\Attachment
+ * @var Attachment
*/
public $eventAttachment = null;
* current data, used to communicate with event listeners.
* @var array
*/
- public $eventData = array();
+ public $eventData = [];
/**
- * Validates the delete action.
+ * @inheritdoc
*/
public function validateDelete() {
// read objects
}
/**
- * Validates the upload action.
+ * @inheritdoc
*/
public function validateUpload() {
// IE<10 fallback
// check max count of uploads
$handler = new AttachmentHandler($this->parameters['objectType'], intval($this->parameters['objectID']), $this->parameters['tmpHash']);
if ($handler->count() + count($this->parameters['__files']->getFiles()) > $processor->getMaxCount()) {
- throw new UserInputException('files', 'exceededQuota', array(
+ throw new UserInputException('files', 'exceededQuota', [
'current' => $handler->count(),
'quota' => $processor->getMaxCount()
- ));
+ ]);
}
// check max filesize, allowed file extensions etc.
}
/**
- * Handles uploaded attachments.
+ * @inheritdoc
*/
public function upload() {
// get object type
$objectType = ObjectTypeCache::getInstance()->getObjectTypeByName('com.woltlab.wcf.attachment.objectType', $this->parameters['objectType']);
// save files
- $thumbnails = $attachments = $failedUploads = array();
- $files = $this->parameters['__files']->getFiles();
- foreach ($files as $file) {
- if ($file->getValidationErrorType()) {
- $failedUploads[] = $file;
- continue;
- }
-
- $data = array(
- 'objectTypeID' => $objectType->objectTypeID,
- 'objectID' => intval($this->parameters['objectID']),
- 'userID' => (WCF::getUser()->userID ?: null),
- 'tmpHash' => (!$this->parameters['objectID'] ? $this->parameters['tmpHash'] : ''),
- 'filename' => $file->getFilename(),
- 'filesize' => $file->getFilesize(),
- 'fileType' => $file->getMimeType(),
- 'fileHash' => sha1_file($file->getLocation()),
- 'uploadTime' => TIME_NOW
- );
-
- // get image data
- if (($imageData = $file->getImageData()) !== null) {
- $data['width'] = $imageData['width'];
- $data['height'] = $imageData['height'];
- $data['fileType'] = $imageData['mimeType'];
-
- if (preg_match('~^image/(gif|jpe?g|png)$~i', $data['fileType'])) {
- $data['isImage'] = 1;
- }
- }
-
- // create attachment
- $attachment = AttachmentEditor::create($data);
-
- // check attachment directory
- // and create subdirectory if necessary
- $dir = dirname($attachment->getLocation());
- if (!@file_exists($dir)) {
- FileUtil::makePath($dir);
- }
-
- // move uploaded file
- if (@move_uploaded_file($file->getLocation(), $attachment->getLocation())) {
- if ($attachment->isImage) {
- $thumbnails[] = $attachment;
-
- // rotate image based on the exif data
- $neededMemory = $attachment->width * $attachment->height * ($attachment->fileType == 'image/png' ? 4 : 3) * 2.1;
- if (FileUtil::getMemoryLimit() == -1 || FileUtil::getMemoryLimit() > (memory_get_usage() + $neededMemory)) {
- $exifData = ExifUtil::getExifData($attachment->getLocation());
- if (!empty($exifData)) {
- $orientation = ExifUtil::getOrientation($exifData);
- if ($orientation != ExifUtil::ORIENTATION_ORIGINAL) {
- $adapter = ImageHandler::getInstance()->getAdapter();
- $adapter->loadFile($attachment->getLocation());
-
- $newImage = null;
- switch ($orientation) {
- case ExifUtil::ORIENTATION_180_ROTATE:
- $newImage = $adapter->rotate(180);
- break;
-
- case ExifUtil::ORIENTATION_90_ROTATE:
- $newImage = $adapter->rotate(90);
- break;
-
- case ExifUtil::ORIENTATION_270_ROTATE:
- $newImage = $adapter->rotate(270);
- break;
-
- case ExifUtil::ORIENTATION_HORIZONTAL_FLIP:
- case ExifUtil::ORIENTATION_VERTICAL_FLIP:
- case ExifUtil::ORIENTATION_VERTICAL_FLIP_270_ROTATE:
- case ExifUtil::ORIENTATION_HORIZONTAL_FLIP_270_ROTATE:
- // unsupported
- break;
- }
-
- if ($newImage !== null) {
- $adapter->load($newImage, $adapter->getType());
- }
-
- $adapter->writeImage($attachment->getLocation());
-
- if ($newImage !== null) {
- // update width, height and filesize of the attachment
- if ($orientation == ExifUtil::ORIENTATION_90_ROTATE || $orientation == ExifUtil::ORIENTATION_270_ROTATE) {
- $attachmentEditor = new AttachmentEditor($attachment);
- $attachmentEditor->update(array(
- 'height' => $attachment->width,
- 'width' => $attachment->height,
- 'filesize' => filesize($attachment->getLocation())
- ));
- }
- }
- }
- }
- }
- }
- else {
- // check whether we can create thumbnails for this file
- $this->eventAttachment = $attachment;
- $this->eventData = array('hasThumbnail' => false);
- EventHandler::getInstance()->fireAction($this, 'checkThumbnail');
- if ($this->eventData['hasThumbnail']) $thumbnails[] = $attachment;
- }
- $attachments[$file->getInternalFileID()] = $attachment;
- }
- else {
- // moving failed; delete attachment
- $editor = new AttachmentEditor($attachment);
- $editor->delete();
- }
- }
+ $saveStrategy = new DefaultUploadFileSaveStrategy(self::class, [
+ 'generateThumbnails' => ATTACHMENT_ENABLE_THUMBNAILS,
+ 'rotateImages' => true
+ ], [
+ 'objectID' => intval($this->parameters['objectID']),
+ 'objectTypeID' => $objectType->objectTypeID,
+ 'tmpHash' => (!$this->parameters['objectID'] ? $this->parameters['tmpHash'] : '')
+ ]);
- // generate thumbnails
- if (ATTACHMENT_ENABLE_THUMBNAILS) {
- if (!empty($thumbnails)) {
- $action = new AttachmentAction($thumbnails, 'generateThumbnails');
- $action->executeAction();
- }
- }
+ $this->parameters['__files']->saveFiles($saveStrategy);
+ $attachments = $saveStrategy->getObjects();
// return result
- $result = array('attachments' => array(), 'errors' => array());
+ $result = ['attachments' => [], 'errors' => []];
if (!empty($attachments)) {
// get attachment ids
- $attachmentIDs = $attachmentToFileID = array();
+ $attachmentIDs = $attachmentToFileID = [];
foreach ($attachments as $internalFileID => $attachment) {
$attachmentIDs[] = $attachment->attachmentID;
$attachmentToFileID[$attachment->attachmentID] = $internalFileID;
$attachmentList->readObjects();
foreach ($attachmentList as $attachment) {
- $result['attachments'][$attachmentToFileID[$attachment->attachmentID]] = array(
+ $result['attachments'][$attachmentToFileID[$attachment->attachmentID]] = [
'filename' => $attachment->filename,
'filesize' => $attachment->filesize,
'formattedFilesize' => FileUtil::formatFilesize($attachment->filesize),
'isImage' => $attachment->isImage,
'attachmentID' => $attachment->attachmentID,
- 'tinyURL' => ($attachment->tinyThumbnailType ? LinkHandler::getInstance()->getLink('Attachment', array('object' => $attachment), 'tiny=1') : ''),
- 'thumbnailURL' => ($attachment->thumbnailType ? LinkHandler::getInstance()->getLink('Attachment', array('object' => $attachment), 'thumbnail=1') : ''),
- 'url' => LinkHandler::getInstance()->getLink('Attachment', array('object' => $attachment)),
+ 'tinyURL' => ($attachment->tinyThumbnailType ? LinkHandler::getInstance()->getLink('Attachment', ['object' => $attachment], 'tiny=1') : ''),
+ 'thumbnailURL' => ($attachment->thumbnailType ? LinkHandler::getInstance()->getLink('Attachment', ['object' => $attachment], 'thumbnail=1') : ''),
+ 'url' => LinkHandler::getInstance()->getLink('Attachment', ['object' => $attachment]),
'height' => $attachment->height,
'width' => $attachment->width
- );
+ ];
}
}
- foreach ($failedUploads as $failedUpload) {
- $result['errors'][$failedUpload->getInternalFileID()] = array(
- 'filename' => $failedUpload->getFilename(),
- 'filesize' => $failedUpload->getFilesize(),
- 'errorType' => $failedUpload->getValidationErrorType()
- );
+ $files = $this->parameters['__files']->getFiles();
+ foreach ($files as $file) {
+ if ($file->getValidationErrorType()) {
+ $result['errors'][$file->getInternalFileID()] = [
+ 'filename' => $file->getFilename(),
+ 'filesize' => $file->getFilesize(),
+ 'errorType' => $file->getValidationErrorType()
+ ];
+ }
}
return $result;
$this->readObjects();
}
+ $saveStrategy = new DefaultUploadFileSaveStrategy(self::class);
+
foreach ($this->objects as $attachment) {
if (!$attachment->isImage) {
// create thumbnails for every file that isn't an image
$this->eventAttachment = $attachment;
- $this->eventData = array();
+ $this->eventData = [];
EventHandler::getInstance()->fireAction($this, 'generateThumbnail');
continue;
}
- if ($attachment->width <= 144 && $attachment->height < 144) {
- continue; // image smaller than thumbnail size; skip
- }
-
- $adapter = ImageHandler::getInstance()->getAdapter();
-
- // check memory limit
- $neededMemory = $attachment->width * $attachment->height * ($attachment->fileType == 'image/png' ? 4 : 3) * 2.1;
- if (FileUtil::getMemoryLimit() != -1 && FileUtil::getMemoryLimit() < (memory_get_usage() + $neededMemory)) {
- continue;
- }
-
- $adapter->loadFile($attachment->getLocation());
- $updateData = array();
- // remove / reset old thumbnails
- if ($attachment->tinyThumbnailType) {
- @unlink($attachment->getTinyThumbnailLocation());
- $updateData['tinyThumbnailType'] = '';
- $updateData['tinyThumbnailSize'] = 0;
- $updateData['tinyThumbnailWidth'] = 0;
- $updateData['tinyThumbnailHeight'] = 0;
- }
- if ($attachment->thumbnailType) {
- @unlink($attachment->getThumbnailLocation());
- $updateData['thumbnailType'] = '';
- $updateData['thumbnailSize'] = 0;
- $updateData['thumbnailWidth'] = 0;
- $updateData['thumbnailHeight'] = 0;
- }
-
- // create tiny thumbnail
- $tinyThumbnailLocation = $attachment->getTinyThumbnailLocation();
- $thumbnail = $adapter->createThumbnail(144, 144, false);
- $adapter->writeImage($thumbnail, $tinyThumbnailLocation);
- if (file_exists($tinyThumbnailLocation) && ($imageData = @getImageSize($tinyThumbnailLocation)) !== false) {
- $updateData['tinyThumbnailType'] = $imageData['mime'];
- $updateData['tinyThumbnailSize'] = @filesize($tinyThumbnailLocation);
- $updateData['tinyThumbnailWidth'] = $imageData[0];
- $updateData['tinyThumbnailHeight'] = $imageData[1];
- }
-
- // create standard thumbnail
- if ($attachment->width > ATTACHMENT_THUMBNAIL_WIDTH || $attachment->height > ATTACHMENT_THUMBNAIL_HEIGHT) {
- $thumbnailLocation = $attachment->getThumbnailLocation();
- $thumbnail = $adapter->createThumbnail(ATTACHMENT_THUMBNAIL_WIDTH, ATTACHMENT_THUMBNAIL_HEIGHT, ATTACHMENT_RETAIN_DIMENSIONS);
- $adapter->writeImage($thumbnail, $thumbnailLocation);
- if (file_exists($thumbnailLocation) && ($imageData = @getImageSize($thumbnailLocation)) !== false) {
- $updateData['thumbnailType'] = $imageData['mime'];
- $updateData['thumbnailSize'] = @filesize($thumbnailLocation);
- $updateData['thumbnailWidth'] = $imageData[0];
- $updateData['thumbnailHeight'] = $imageData[1];
- }
- }
-
- if (!empty($updateData)) {
- $attachment->update($updateData);
- }
+ $saveStrategy->generateThumbnails($attachment->getDecoratedObject());
}
}
/**
- * Validates parameters to update the attachments show order.
+ * @inheritdoc
*/
public function validateUpdatePosition() {
$this->readInteger('objectID', true);
$this->parameters['attachmentIDs'] = ArrayUtil::toIntegerArray($this->parameters['attachmentIDs']);
// check attachment ids
- $attachmentIDs = array();
+ $attachmentIDs = [];
$conditions = new PreparedStatementConditionBuilder();
- $conditions->add("attachmentID IN (?)", array($this->parameters['attachmentIDs']));
- $conditions->add("objectTypeID = ?", array($objectType->objectTypeID));
+ $conditions->add("attachmentID IN (?)", [$this->parameters['attachmentIDs']]);
+ $conditions->add("objectTypeID = ?", [$objectType->objectTypeID]);
if (!empty($this->parameters['objectID'])) {
- $conditions->add("objectID = ?", array($this->parameters['objectID']));
+ $conditions->add("objectID = ?", [$this->parameters['objectID']]);
}
else {
- $conditions->add("tmpHash = ?", array($this->parameters['tmpHash']));
+ $conditions->add("tmpHash = ?", [$this->parameters['tmpHash']]);
}
$sql = "SELECT attachmentID
}
/**
- * Updates the attachments show order.
+ * @inheritdoc
*/
public function updatePosition() {
$sql = "UPDATE wcf".WCF_N."_attachment
WCF::getDB()->beginTransaction();
$showOrder = 1;
foreach ($this->parameters['attachmentIDs'] as $attachmentID) {
- $statement->execute(array(
+ $statement->execute([
$showOrder++,
$attachmentID
- ));
+ ]);
}
WCF::getDB()->commitTransaction();
}
$targetObjectType = ObjectTypeCache::getInstance()->getObjectTypeByName('com.woltlab.wcf.attachment.objectType', $this->parameters['targetObjectType']);
$attachmentList = new AttachmentList();
- $attachmentList->getConditionBuilder()->add("attachment.objectTypeID = ?", array($sourceObjectType->objectTypeID));
- $attachmentList->getConditionBuilder()->add("attachment.objectID = ?", array($this->parameters['sourceObjectID']));
+ $attachmentList->getConditionBuilder()->add("attachment.objectTypeID = ?", [$sourceObjectType->objectTypeID]);
+ $attachmentList->getConditionBuilder()->add("attachment.objectID = ?", [$this->parameters['sourceObjectID']]);
$attachmentList->readObjects();
- $newAttachmentIDs = array();
+ $newAttachmentIDs = [];
foreach ($attachmentList as $attachment) {
- $newAttachment = AttachmentEditor::create(array(
+ $newAttachment = AttachmentEditor::create([
'objectTypeID' => $targetObjectType->objectTypeID,
'objectID' => $this->parameters['targetObjectID'],
'userID' => $attachment->userID,
'lastDownloadTime' => $attachment->lastDownloadTime,
'uploadTime' => $attachment->uploadTime,
'showOrder' => $attachment->showOrder
- ));
+ ]);
// copy attachment
@copy($attachment->getLocation(), $newAttachment->getLocation());
$newAttachmentIDs[$attachment->attachmentID] = $newAttachment->attachmentID;
}
- return array(
+ return [
'attachmentIDs' => $newAttachmentIDs
- );
+ ];
}
}
--- /dev/null
+<?php
+namespace wcf\data\media;
+use wcf\data\DatabaseObject;
+use wcf\data\IThumbnailFile;
+use wcf\system\request\IRouteController;
+use wcf\system\request\LinkHandler;
+use wcf\system\WCF;
+
+/**
+ * Represents a madia file.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2015 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf
+ * @subpackage data.media
+ * @category Community Framework
+ * @since 2.2
+ */
+class Media extends DatabaseObject implements IRouteController, IThumbnailFile {
+ /**
+ * i18n media data grouped by language id for all language
+ * @var string[][]
+ */
+ protected $i18nData = null;
+
+ /**
+ * @inheritdoc
+ */
+ protected static $databaseTableName = 'media';
+
+ /**
+ * @inheritdoc
+ */
+ protected static $databaseTableIndexName = 'mediaID';
+
+ /**
+ * data of the different thumbnail sizes
+ * @var array
+ */
+ protected static $thumbnailSizes = [
+ 'tiny' => [
+ 'height' => 144,
+ 'retainDimensions' => false,
+ 'width' => 144
+ ],
+ 'small' => [
+ 'height' => MEDIA_SMALL_THUMBNAIL_HEIGHT,
+ 'retainDimensions' => MEDIA_SMALL_THUMBNAIL_RETAIN_DIMENSIONS,
+ 'width' => MEDIA_SMALL_THUMBNAIL_WIDTH
+ ],
+ 'medium' => [
+ 'height' => MEDIA_MEDIUM_THUMBNAIL_HEIGHT,
+ 'retainDimensions' => MEDIA_MEDIUM_THUMBNAIL_RETAIN_DIMENSIONS,
+ 'width' => MEDIA_MEDIUM_THUMBNAIL_WIDTH
+ ],
+ 'large' => [
+ 'height' => MEDIA_LARGE_THUMBNAIL_HEIGHT,
+ 'retainDimensions' => MEDIA_LARGE_THUMBNAIL_RETAIN_DIMENSIONS,
+ 'width' => MEDIA_LARGE_THUMBNAIL_WIDTH
+ ]
+ ];
+
+ /**
+ * @inheritcoc
+ */
+ public function getLocation() {
+ return self::getStorage().substr($this->fileHash, 0, 2).'/'.$this->mediaID.'-'.$this->fileHash;
+ }
+
+ /**
+ * @inheritcoc
+ */
+ public function getThumbnailLink($size) {
+ if (!isset(self::$thumbnailSizes[$size])) {
+ throw new SystemException("Unknown thumbnail size '".$size."'");
+ }
+
+ return LinkHandler::getInstance()->getLink('Media', [
+ 'forceFrontend' => true,
+ 'object' => $this,
+ 'thumbnail' => $size
+ ]);
+ }
+
+ /**
+ * @inheritcoc
+ */
+ public function getThumbnailLocation($size) {
+ if (!isset(self::$thumbnailSizes[$size])) {
+ throw new SystemException("Unknown thumbnail size '".$size."'");
+ }
+
+ return self::getStorage().substr($this->fileHash, 0, 2).'/'.$this->mediaID.'-'.$size.'-'.$this->fileHash;
+ }
+
+ /**
+ * @inheritcoc
+ */
+ public function getTitle() {
+ return $this->filename;
+ }
+
+ /**
+ * Returns the i18n media data grouped by language id for all language.
+ *
+ * @return string[][]
+ */
+ public function getI18nData() {
+ if ($this->i18nData === null) {
+ $this->i18nData = [
+ 'altText' => [],
+ 'caption' => [],
+ 'title' => []
+ ];
+
+ $sql = "SELECT *
+ FROM wcf".WCF_N."_media_content
+ WHERE mediaID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute([$this->mediaID]);
+
+ while ($row = $statement->fetchArray()) {
+ $this->i18nData['altText'][$row['languageID']] = $row['altText'];
+ $this->i18nData['caption'][$row['languageID']] = $row['caption'];
+ $this->i18nData['title'][$row['languageID']] = $row['title'];
+ }
+ }
+
+ return $this->i18nData;
+ }
+
+ /**
+ * Returns the storage path of the media files.
+ *
+ * @return string
+ */
+ public static function getStorage() {
+ return WCF_DIR.'media/';
+ }
+
+ /**
+ * @inheritcoc
+ */
+ public static function getThumbnailSizes() {
+ return static::$thumbnailSizes;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\media;
+use wcf\data\AbstractDatabaseObjectAction;
+use wcf\data\ISearchAction;
+use wcf\data\IUploadAction;
+use wcf\system\clipboard\ClipboardHandler;
+use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\exception\PermissionDeniedException;
+use wcf\system\language\I18nHandler;
+use wcf\system\language\LanguageFactory;
+use wcf\system\request\Linkhandler;
+use wcf\system\upload\DefaultUploadFileSaveStrategy;
+use wcf\system\WCF;
+use wcf\util\FileUtil;
+
+/**
+ * Executes madia file-related actions.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2015 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf
+ * @subpackage data.media
+ * @category Community Framework
+ * @since 2.2
+ */
+class MediaAction extends AbstractDatabaseObjectAction implements ISearchAction, IUploadAction {
+ /**
+ * condition builder for searched media file type
+ * @var PreparedStatementConditionBuilder
+ */
+ public $fileTypeConditionBuilder = null;
+
+ /**
+ * @inheritdoc
+ */
+ public function validateUpload() {
+ WCF::getSession()->checkPermissions(['admin.content.cms.canManageMedia']);
+
+ // TODO
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function upload() {
+ // save files
+ $saveStrategy = new DefaultUploadFileSaveStrategy(self::class, [
+ 'generateThumbnails' => true,
+ 'rotateImages' => true
+ ], [
+ 'username' => WCF::getUser()->username
+ ]);
+
+ $this->parameters['__files']->saveFiles($saveStrategy);
+ $mediaFiles = $saveStrategy->getObjects();
+
+ $result = [
+ 'errors' => [],
+ 'media' => []
+ ];
+
+ if (!empty($mediaFiles)) {
+ // get attachment ids
+ $mediaIDs = $mediaToFileID = array();
+ foreach ($mediaFiles as $internalFileID => $media) {
+ $mediaIDs[] = $media->mediaID;
+ $mediaToFileID[$media->mediaID] = $internalFileID;
+ }
+
+ // get attachments from database (check thumbnail status)
+ $mediaList = new MediaList();
+ $mediaList->setObjectIDs($mediaIDs);
+ $mediaList->readObjects();
+
+ foreach ($mediaList as $media) {
+ $result['media'][$mediaToFileID[$media->mediaID]] = $this->getMediaData($media);
+ }
+ }
+
+ $files = $this->parameters['__files']->getFiles();
+ foreach ($files as $file) {
+ if ($file->getValidationErrorType()) {
+ $result['errors'][$file->getInternalFileID()] = array(
+ 'filename' => $file->getFilename(),
+ 'filesize' => $file->getFilesize(),
+ 'errorType' => $file->getValidationErrorType()
+ );
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Returns the data of the media file to be returned by AJAX requests.
+ *
+ * @param object $media media files whose data will be returned
+ * @return string[]
+ */
+ protected function getMediaData($media) {
+ return [
+ 'altText' => $media instanceof ViewableMedia ? $media->altText : [],
+ 'caption' => $media instanceof ViewableMedia ? $media->caption : [],
+ 'fileHash' => $media->fileHash,
+ 'filename' => $media->filename,
+ 'filesize' => $media->filesize,
+ 'formattedFilesize' => FileUtil::formatFilesize($media->filesize),
+ 'fileType' => $media->fileType,
+ 'height' => $media->height,
+ 'languageID' => $media->languageID,
+ 'isImage' => $media->isImage,
+ 'isMultilingual' => $media->isMultilingual,
+ 'largeThumbnailHeight' => $media->largeThumbnailHeight,
+ 'largeThumbnailLink' => $media->largeThumbnailType ? $media->getThumbnailLink('large') : '',
+ 'largeThumbnailType' => $media->largeThumbnailType,
+ 'largeThumbnailWidth' => $media->largeThumbnailWidth,
+ 'mediaID' => $media->mediaID,
+ 'mediumThumbnailHeight' => $media->mediumThumbnailHeight,
+ 'mediumThumbnailLink' => $media->mediumThumbnailType ? $media->getThumbnailLink('medium') : '',
+ 'mediumThumbnailType' => $media->mediumThumbnailType,
+ 'mediumThumbnailWidth' => $media->mediumThumbnailWidth,
+ 'smallThumbnailHeight' => $media->smallThumbnailHeight,
+ 'smallThumbnailLink' => $media->smallThumbnailType ? $media->getThumbnailLink('small') : '',
+ 'smallThumbnailType' => $media->smallThumbnailType,
+ 'smallThumbnailWidth' => $media->smallThumbnailWidth,
+ 'tinyThumbnailHeight' => $media->tinyThumbnailHeight,
+ 'tinyThumbnailLink' => $media->tinyThumbnailType ? $media->getThumbnailLink('tiny') : '',
+ 'tinyThumbnailType' => $media->tinyThumbnailType,
+ 'tinyThumbnailWidth' => $media->tinyThumbnailWidth,
+ 'title' => $media instanceof ViewableMedia ? $media->title : [],
+ 'uploadTime' => $media->uploadTime,
+ 'userID' => $media->userID,
+ 'userLink' => $media->userID ? LinkHandler::getInstance()->getLink('User', [
+ 'id' => $media->userID,
+ 'title' => $media->username
+ ]) : '',
+ 'username' => $media->username,
+ 'width' => $media->width
+ ];
+ }
+
+ /**
+ * Validates the 'getManagementDialog' action.
+ */
+ public function validateGetManagementDialog() {
+ if (!WCF::getSession()->getPermission('admin.content.cms.canManageMedia') && !WCF::getSession()->getPermission('admin.content.cms.canUseMedia')) {
+ throw new PermissionDeniedException();
+ }
+ }
+
+ /**
+ * Returns the dialog to manage media.
+ *
+ * @return string[]
+ */
+ public function getManagementDialog() {
+ $mediaList = new ViewableMediaList();
+ $mediaList->readObjects();
+
+ return [
+ 'hasMarkedItems' => ClipboardHandler::getInstance()->hasMarkedItems(ClipboardHandler::getInstance()->getObjectTypeID('com.woltlab.wcf.media')),
+ 'media' => $this->getI18nMediaData($mediaList),
+ 'template' => WCF::getTPL()->fetch('mediaManager', 'wcf', [
+ 'mediaList' => $mediaList
+ ])
+ ];
+ }
+
+ /**
+ * Returns the complete i18n data of the media files in the given list.
+ *
+ * @param MediaList $mediaList
+ * @return array
+ */
+ protected function getI18nMediaData(MediaList $mediaList) {
+ $conditionBuilder = new PreparedStatementConditionBuilder();
+ $conditionBuilder->add('mediaID IN (?)', [$mediaList->getObjectIDs()]);
+
+ $sql = "SELECT *
+ FROM wcf".WCF_N."_media_content
+ ".$conditionBuilder;
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute($conditionBuilder->getParameters());
+
+ $mediaData = [];
+ while ($row = $statement->fetchArray()) {
+ if (!isset($mediaData[$row['mediaID']])) {
+ $mediaData[$row['mediaID']] = [
+ 'altText' => [],
+ 'caption' => [],
+ 'title' => [],
+ ];
+ }
+
+ $mediaData[$row['mediaID']]['altText'][intval($row['languageID'])] = $row['altText'];
+ $mediaData[$row['mediaID']]['caption'][intval($row['languageID'])] = $row['caption'];
+ $mediaData[$row['mediaID']]['title'][intval($row['languageID'])] = $row['title'];
+ }
+
+ $i18nMediaData = [];
+ foreach ($mediaList as $media) {
+ if (!isset($mediaData[$media->mediaID])) {
+ $mediaData[$media->mediaID] = [];
+ }
+
+ $i18nMediaData[$media->mediaID] = array_merge($this->getMediaData($media), $mediaData[$media->mediaID]);
+ }
+
+ return $i18nMediaData;
+ }
+
+ /**
+ * Validates the 'getEditorDialog' action.
+ */
+ public function validateGetEditorDialog() {
+ WCF::getSession()->checkPermissions(['admin.content.cms.canManageMedia']);
+ }
+
+ /**
+ * Returns the template for the media editor.
+ *
+ * @return string[]
+ */
+ public function getEditorDialog() {
+ I18nHandler::getInstance()->register('title');
+ I18nHandler::getInstance()->register('caption');
+ I18nHandler::getInstance()->register('altText');
+ I18nHandler::getInstance()->assignVariables();
+
+ return [
+ 'template' => WCF::getTPL()->fetch('mediaEditor', 'wcf', [
+ 'languageID' => WCF::getUser()->languageID,
+ 'languages' => LanguageFactory::getInstance()->getLanguages()
+ ])
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function validateUpdate() {
+ WCF::getSession()->checkPermissions(['admin.content.cms.canManageMedia']);
+
+ if (empty($this->objects)) {
+ $this->readObjects();
+
+ if (empty($this->objects)) {
+ throw new UserInputException('objectIDs');
+ }
+ }
+
+ // TODO: check data
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function update() {
+ if (empty($this->objects)) {
+ $this->readObjects();
+ }
+
+ parent::update();
+
+ if (count($this->objects) == 1 && (isset($this->parameters['title']) || isset($this->parameters['caption']) || isset($this->parameters['altText']))) {
+ $media = reset($this->objects);
+
+ $isMultilingual = $media->isMultilingual;
+ if (isset($this->parameters['data']['isMultilingual'])) {
+ $isMultilingual = $this->parameters['data']['isMultilingual'];
+ }
+
+ $sql = "DELETE FROM wcf".WCF_N."_media_content
+ WHERE mediaID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute([$media->mediaID]);
+
+ $sql = "INSERT INTO wcf".WCF_N."_media_content
+ (mediaID, languageID, title, caption, altText)
+ VALUES (?, ?, ?, ?, ?)";
+ $statement = WCF::getDB()->prepareStatement($sql);
+
+ if (!$isMultilingual) {
+ $languageID = $media->languageID;
+ if (isset($this->parameters['data']['languageID'])) {
+ $languageID = $this->parameters['data']['languageID'];
+ }
+ $statement->execute([
+ $media->mediaID,
+ $languageID,
+ isset($this->parameters['title'][$languageID]) ? $this->parameters['title'][$languageID] : '',
+ isset($this->parameters['caption'][$languageID]) ? $this->parameters['caption'][$languageID] : '',
+ isset($this->parameters['altText'][$languageID]) ? $this->parameters['altText'][$languageID] : ''
+ ]);
+ }
+ else {
+ $languages = LanguageFactory::getInstance()->getLanguages();
+ foreach ($languages as $language) {
+ $title = $caption = $altText = '';
+ foreach (['title', 'caption', 'altText'] as $type) {
+ if (isset($this->parameters[$type])) {
+ if (is_array($this->parameters[$type])) {
+ if (isset($this->parameters[$type][$language->languageID])) {
+ $$type = $this->parameters[$type][$language->languageID];
+ }
+ }
+ else {
+ $$type = $this->parameters[$type];
+ }
+ }
+ }
+
+ $statement->execute([
+ $media->mediaID,
+ $language->languageID,
+ $title,
+ $caption,
+ $altText
+ ]);
+ }
+ }
+ }
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function validateGetSearchResultList() {
+ if (!WCF::getSession()->getPermission('admin.content.cms.canManageMedia') && !WCF::getSession()->getPermission('admin.content.cms.canUseMedia')) {
+ throw new PermissionDeniedException();
+ }
+
+ $this->readString('searchString', true, 'data');
+ $this->readString('fileType', true, 'data');
+
+ if (!$this->parameters['data']['searchString'] && !$this->parameters['data']['fileType']) {
+ throw new UserInputException('searchString');
+ }
+
+ $this->fileTypeConditionBuilder = new PreparedStatementConditionBuilder(false);
+ switch ($this->parameters['data']['fileType']) {
+ case 'other':
+ $this->fileTypeConditionBuilder->add('media.fileType NOT LIKE ?', ['image/%']);
+ $this->fileTypeConditionBuilder->add('media.fileType <> ?', ['application/pdf']);
+ $this->fileTypeConditionBuilder->add('media.fileType NOT LIKE ?', ['text/%']);
+ break;
+
+ case 'image':
+ $this->fileTypeConditionBuilder->add('media.fileType LIKE ?', ['image/%']);
+ break;
+
+ case 'pdf':
+ $this->fileTypeConditionBuilder->add('media.fileType = ?', ['application/pdf']);
+ break;
+
+ case 'text':
+ $this->fileTypeConditionBuilder->add('media.fileType LIKE ?', ['text/%']);
+ break;
+ }
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getSearchResultList() {
+ $searchString = '%'.addcslashes($this->parameters['data']['searchString'], '_%').'%';
+
+ $sql = "SELECT media.mediaID
+ FROM wcf".WCF_N."_media media
+ LEFT JOIN wcf".WCF_N."_media_content media_content
+ ON (media_content.mediaID = media.mediaID)
+ WHERE (media_content.title LIKE ?
+ OR media_content.caption LIKE ?
+ OR media_content.altText LIKE ?
+ OR media.filename LIKE ?)";
+ if (!empty($this->fileTypeConditionBuilder->__toString())) {
+ $sql .= " AND ".$this->fileTypeConditionBuilder;
+ }
+ $statement = WCF::getDB()->prepareStatement($sql, 0, 10);
+ $statement->execute(array_merge([
+ $searchString,
+ $searchString,
+ $searchString,
+ $searchString
+ ], $this->fileTypeConditionBuilder->getParameters()));
+
+ $mediaIDs = [];
+ while ($mediaID = $statement->fetchColumn()) {
+ $mediaIDs[] = $mediaID;
+ }
+
+ if (empty($mediaIDs)) {
+ return [
+ 'template' => WCF::getLanguage()->getDynamicVariable('wcf.media.search.noResults')
+ ];
+ }
+
+ $mediaList = new ViewableMediaList();
+ $mediaList->setObjectIDs($mediaIDs);
+ $mediaList->readObjects();
+
+ return [
+ 'media' => $this->getI18nMediaData($mediaList),
+ 'template' => WCF::getTPL()->fetch('mediaListItems', 'wcf', [
+ 'mediaList' => $mediaList
+ ])
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function validateDelete() {
+ WCF::getSession()->checkPermissions(['admin.content.cms.canManageMedia']);
+
+ if (empty($this->objects)) {
+ $this->readObjects();
+
+ if (empty($this->objects)) {
+ throw new UserInputException('objectIDs');
+ }
+ }
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function delete() {
+ if (empty($this->objects)) {
+ $this->readObjects();
+ }
+
+ /** @var MediaEditor $mediaEditor */
+ foreach ($this->objects as $mediaEditor) {
+ $mediaEditor->deleteFiles();
+ }
+
+ parent::delete();
+
+ $this->unmarkItems();
+ }
+
+ /**
+ * Unmarks the media files with the given ids. If no media ids are given,
+ * all media files currently loaded are unmarked.
+ *
+ * @param integer[] $mediaIDs ids of the media files to be unmarked
+ */
+ protected function unmarkItems(array $mediaIDs = []) {
+ if (empty($mediaIDs)) {
+ foreach ($this->objects as $media) {
+ $mediaIDs[] = $media->mediaID;
+ }
+ }
+
+ if (!empty($mediaIDs)) {
+ ClipboardHandler::getInstance()->unmark($mediaIDs, ClipboardHandler::getInstance()->getObjectTypeID('com.woltlab.wcf.media'));
+ }
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\media;
+use wcf\data\DatabaseObjectEditor;
+
+/**
+ * Procides functions to edit media files.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2015 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf
+ * @subpackage data.media
+ * @category Community Framework
+ * @since 2.2
+ */
+class MediaEditor extends DatabaseObjectEditor {
+ /**
+ * @inheritdoc
+ */
+ protected static $baseClass = Media::class;
+
+ /**
+ * Deletes the physical files of the media file.
+ */
+ public function deleteFiles() {
+ @unlink($this->getLocation());
+
+ // delete thumbnails
+ if ($this->isImage) {
+ foreach (Media::getThumbnailSizes() as $size => $data) {
+ @unlink($this->getThumbnailLocation($size));
+ }
+ }
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\media;
+use wcf\data\DatabaseObjectList;
+
+/**
+ * Represents a list of madia files.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2015 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf
+ * @subpackage data.media
+ * @category Community Framework
+ * @since 2.2
+ */
+class MediaList extends DatabaseObjectList {
+ /**
+ * @inheritdoc
+ */
+ public $className = Media::class;
+}
--- /dev/null
+<?php
+namespace wcf\data\media;
+use wcf\data\DatabaseObjectDecorator;
+use wcf\util\StringUtil;
+use wcf\util\FileUtil;
+
+/**
+ * Represents a viewable madia file.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2015 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf
+ * @subpackage data.media
+ * @category Community Framework
+ * @since 2.2
+ */
+class ViewableMedia extends DatabaseObjectDecorator {
+ /**
+ * @inheritdoc
+ */
+ protected static $baseClass = Media::class;
+
+ /**
+ * Returns a tag to display the media element.
+ *
+ * @param string $size
+ * @return string
+ */
+ public function getElementTag($size) {
+ // todo: validate $size
+ if ($this->isImage && $this->tinyThumbnailType) {
+ return '<img src="'.$this->getThumbnailLink('tiny').'" alt="" style="width: '.$size.'px; height: '.$size.'px;" />';
+ }
+
+ return '<span class="icon icon'.$size.' '.FileUtil::getIconClassByMimeType($this->fileType).'"></span>';
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\media;
+use wcf\system\WCF;
+
+/**
+ * Represents a list of viewable madia files.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2015 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf
+ * @subpackage data.media
+ * @category Community Framework
+ * @since 2.2
+ */
+class ViewableMediaList extends MediaList {
+ /**
+ * @inheritdoc
+ */
+ public $decoratorClassName = ViewableMedia::class;
+
+ /**
+ * @inheritdoc
+ */
+ public function __construct() {
+ parent::__construct();
+
+ // fetch content data
+ $this->sqlSelects .= "media_content.*";
+ $this->sqlJoins .= " LEFT JOIN wcf".WCF_N."_media_content media_content ON (media_content.mediaID = media.mediaID AND media_content.languageID = COALESCE(media.languageID, ".WCF::getUser()->languageID."))";
+ }
+}
namespace wcf\data\style;
use wcf\data\AbstractDatabaseObjectAction;
use wcf\data\IToggleAction;
+use wcf\data\IUploadAction;
use wcf\system\cache\builder\StyleCacheBuilder;
use wcf\system\exception\IllegalLinkException;
use wcf\system\exception\PermissionDeniedException;
* @subpackage data.style
* @category Community Framework
*/
-class StyleAction extends AbstractDatabaseObjectAction implements IToggleAction {
+class StyleAction extends AbstractDatabaseObjectAction implements IToggleAction, IUploadAction {
/**
- * @see \wcf\data\AbstractDatabaseObjectAction::$allowGuestAccess
+ * @inheritdoc
*/
- protected $allowGuestAccess = array('changeStyle', 'getStyleChooser');
+ protected $allowGuestAccess = ['changeStyle', 'getStyleChooser'];
/**
- * @see \wcf\data\AbstractDatabaseObjectAction::$className
+ * @inheritdoc
*/
protected $className = 'wcf\data\style\StyleEditor';
/**
- * @see \wcf\data\AbstractDatabaseObjectAction::$permissionsDelete
+ * @inheritdoc
*/
- protected $permissionsDelete = array('admin.style.canManageStyle');
+ protected $permissionsDelete = ['admin.style.canManageStyle'];
/**
- * @see \wcf\data\AbstractDatabaseObjectAction::$permissionsUpdate
+ * @inheritdoc
*/
- protected $permissionsUpdate = array('admin.style.canManageStyle');
+ protected $permissionsUpdate = ['admin.style.canManageStyle'];
/**
- * @see \wcf\data\AbstractDatabaseObjectAction::$requireACP
+ * @inheritdoc
*/
- protected $requireACP = array('copy', 'delete', 'markAsTainted', 'setAsDefault', 'toggle', 'update', 'upload', 'uploadLogo');
+ protected $requireACP = ['copy', 'delete', 'markAsTainted', 'setAsDefault', 'toggle', 'update', 'upload', 'uploadLogo'];
/**
* style object
- * @var \wcf\data\style\Style
+ * @var Style
*/
public $style = null;
/**
* style editor object
- * @var \wcf\data\style\StyleEditor
+ * @var StyleEditor
*/
public $styleEditor = null;
/**
- * @see \wcf\data\AbstractDatabaseObjectAction::create()
+ * @inheritdoc
*/
public function create() {
$style = parent::create();
}
/**
- * @see \wcf\data\AbstractDatabaseObjectAction::update()
+ * @inheritdoc
*/
public function update() {
parent::update();
}
/**
- * @see \wcf\data\AbstractDatabaseObjectAction::delete()
+ * @inheritdoc
*/
public function delete() {
$count = parent::delete();
/**
* Updates style variables for given style.
*
- * @param \wcf\data\style\Style $style
- * @param boolean $removePreviousVariables
+ * @param Style $style
+ * @param boolean $removePreviousVariables
*/
protected function updateVariables(Style $style, $removePreviousVariables = false) {
if (!isset($this->parameters['variables']) || !is_array($this->parameters['variables'])) {
FROM wcf".WCF_N."_style_variable";
$statement = WCF::getDB()->prepareStatement($sql);
$statement->execute();
- $variables = array();
+ $variables = [];
while ($row = $statement->fetchArray()) {
$variableName = $row['variableName'];
$sql = "DELETE FROM wcf".WCF_N."_style_variable_value
WHERE styleID = ?";
$statement = WCF::getDB()->prepareStatement($sql);
- $statement->execute(array($style->styleID));
+ $statement->execute([$style->styleID]);
}
// insert variables that differ from default values
WCF::getDB()->beginTransaction();
foreach ($variables as $variableID => $variableValue) {
- $statement->execute(array(
+ $statement->execute([
$style->styleID,
$variableID,
$variableValue
- ));
+ ]);
}
WCF::getDB()->commitTransaction();
}
/**
* Updates style preview image.
*
- * @param \wcf\data\style\Style $style
+ * @param Style $style
*/
protected function updateStylePreviewImage(Style $style) {
if (!isset($this->parameters['tmpHash'])) {
SET image = ?
WHERE styleID = ?";
$statement = WCF::getDB()->prepareStatement($sql);
- $statement->execute(array(
+ $statement->execute([
$filename,
$style->styleID
- ));
+ ]);
}
}
else {
}
/**
- * Validates the upload action.
+ * @inheritdoc
*/
public function validateUpload() {
// check upload permissions
}
// check max filesize, allowed file extensions etc.
- $this->parameters['__files']->validateFiles(new DefaultUploadFileValidationStrategy(PHP_INT_MAX, array('jpg', 'jpeg', 'png', 'gif')));
+ $this->parameters['__files']->validateFiles(new DefaultUploadFileValidationStrategy(PHP_INT_MAX, ['jpg', 'jpeg', 'png', 'gif']));
}
/**
- * Handles uploaded preview images.
- *
- * @return array<string>
+ * @inheritdoc
*/
public function upload() {
// save files
if ($this->parameters['styleID']) {
$this->updateStylePreviewImage($this->style);
- return array(
+ return [
'url' => WCF::getPath().'images/stylePreview-'.$this->parameters['styleID'].'.'.$file->getFileExtension()
- );
+ ];
}
// return result
- return array(
+ return [
'url' => WCF::getPath().'images/stylePreview-'.$this->parameters['tmpHash'].'.'.$file->getFileExtension()
- );
+ ];
}
else {
throw new UserInputException('image', 'uploadFailed');
$file->setValidationErrorType($e->getType());
}
- return array('errorType' => $file->getValidationErrorType());
+ return ['errorType' => $file->getValidationErrorType()];
}
/**
/**
* Handles logo upload.
*
- * @return array<string>
+ * @return string[]
*/
public function uploadLogo() {
// save files
WCF::getSession()->register('styleLogo-'.$this->parameters['tmpHash'], $file->getFileExtension());
// return result
- return array(
+ return [
'url' => WCF::getPath().'images/styleLogo-'.$this->parameters['tmpHash'].'.'.$file->getFileExtension()
- );
+ ];
}
else {
throw new UserInputException('image', 'uploadFailed');
$file->setValidationErrorType($e->getType());
}
- return array('errorType' => $file->getValidationErrorType());
+ return ['errorType' => $file->getValidationErrorType()];
}
/**
/**
* Copies a style.
*
- * @return array<string>
+ * @return string[]
*/
public function copy() {
// get unique style name
WHERE styleName LIKE ?
AND styleID <> ?";
$statement = WCF::getDB()->prepareStatement($sql);
- $statement->execute(array(
+ $statement->execute([
$this->styleEditor->styleName.'%',
$this->styleEditor->styleID
- ));
- $numbers = array();
+ ]);
+ $numbers = [];
$regEx = new Regex('\((\d+)\)$');
while ($row = $statement->fetchArray()) {
$styleName = $row['styleName'];
$styleName = $this->styleEditor->styleName . ' ('.$number.')';
// create the new style
- $newStyle = StyleEditor::create(array(
+ $newStyle = StyleEditor::create([
'styleName' => $styleName,
'templateGroupID' => $this->styleEditor->templateGroupID,
'isDisabled' => 1, // newly created styles are disabled by default
'authorName' => $this->styleEditor->authorName,
'authorURL' => $this->styleEditor->authorURL,
'imagePath' => $this->styleEditor->imagePath
- ));
+ ]);
// check if style description uses i18n
if (preg_match('~^wcf.style.styleDescription\d+$~', $newStyle->styleDescription)) {
FROM wcf".WCF_N."_language_item
WHERE languageItem = ?";
$statement = WCF::getDB()->prepareStatement($sql);
- $statement->execute(array($newStyle->styleDescription));
+ $statement->execute([$newStyle->styleDescription]);
// update style description
$styleEditor = new StyleEditor($newStyle);
- $styleEditor->update(array(
+ $styleEditor->update([
'styleDescription' => $styleDescription
- ));
+ ]);
}
// copy style variables
FROM wcf".WCF_N."_style_variable_value value
WHERE value.styleID = ?";
$statement = WCF::getDB()->prepareStatement($sql);
- $statement->execute(array($this->styleEditor->styleID));
+ $statement->execute([$this->styleEditor->styleID]);
// copy preview image
if ($this->styleEditor->image) {
SET image = ?
WHERE styleID = ?";
$statement = WCF::getDB()->prepareStatement($sql);
- $statement->execute(array(
+ $statement->execute([
'stylePreview-'.$newStyle->styleID.$fileExtension,
$newStyle->styleID
- ));
+ ]);
}
}
SET imagePath = ?
WHERE styleID = ?";
$statement = WCF::getDB()->prepareStatement($sql);
- $statement->execute(array(
+ $statement->execute([
$newPath,
$newStyle->styleID
- ));
+ ]);
}
StyleCacheBuilder::getInstance()->reset();
- return array(
- 'redirectURL' => LinkHandler::getInstance()->getLink('StyleEdit', array('id' => $newStyle->styleID))
- );
+ return [
+ 'redirectURL' => LinkHandler::getInstance()->getLink('StyleEdit', ['id' => $newStyle->styleID])
+ ];
}
/**
- * @see \wcf\data\IToggleAction::validateToggle()
+ * @inheritdoc
*/
public function validateToggle() {
parent::validateUpdate();
}
/**
- * @see \wcf\data\IToggleAction::toggle()
+ * @inheritdoc
*/
public function toggle() {
foreach ($this->objects as $style) {
$isDisabled = ($style->isDisabled) ? 0 : 1;
- $style->update(array('isDisabled' => $isDisabled));
+ $style->update(['isDisabled' => $isDisabled]);
}
}
/**
* Returns the style chooser dialog.
*
- * @return array<string>
+ * @return string[]
*/
public function getStyleChooser() {
$styleList = new StyleList();
if (!WCF::getSession()->getPermission('admin.style.canUseDisabledStyle')) {
- $styleList->getConditionBuilder()->add("style.isDisabled = ?", array(0));
+ $styleList->getConditionBuilder()->add("style.isDisabled = ?", [0]);
}
$styleList->sqlOrderBy = "style.styleName ASC";
$styleList->readObjects();
- WCF::getTPL()->assign(array(
+ WCF::getTPL()->assign([
'styleList' => $styleList
- ));
+ ]);
- return array(
+ return [
'actionName' => 'getStyleChooser',
'template' => WCF::getTPL()->fetch('styleChooser')
- );
+ ];
}
/**
--- /dev/null
+<?php
+namespace wcf\page;
+use wcf\data\media\Media;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\request\LinkHandler;
+use wcf\util\FileReader;
+use wcf\util\StringUtil;
+
+/**
+ * Shows a media file.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2015 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf
+ * @subpackage page
+ * @category Community Framework
+ * @since 2.2
+ */
+class MediaPage extends AbstractPage {
+ /**
+ * etag for the media file
+ * @var string
+ */
+ public $eTag = null;
+
+ /**
+ * file reader object
+ * @var FileReader
+ */
+ public $fileReader = null;
+
+ /**
+ * requested media file
+ * @var Media
+ */
+ public $media = null;
+
+ /**
+ * id of the requested media file
+ * @var integer
+ */
+ public $mediaID = 0;
+
+ /**
+ * size of the requested thumbnail
+ * @var string
+ */
+ public $thumbnail = '';
+
+ /**
+ * @inheritdoc
+ */
+ public $useTemplate = false;
+
+ /**
+ * list of mime types which belong to files that are displayed inline
+ * @var string[]
+ */
+ public static $inlineMimeTypes = [
+ 'image/gif',
+ 'image/jpeg',
+ 'image/png',
+ 'image/x-png',
+ 'application/pdf',
+ 'image/pjpeg'
+ ];
+
+ /**
+ * @inheritdoc
+ */
+ public function checkPermissions() {
+ parent::checkPermissions();
+
+ // TODO
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function readData() {
+ parent::readData();
+
+ // get file data
+ if ($this->thumbnail) {
+ $mimeType = $this->media->{$this->thumbnail.'ThumbnailType'};
+ $filesize = $this->media->{$this->thumbnail.'ThumbnailSize'};
+ $location = $this->media->getThumbnailLocation($this->thumbnail);
+ $this->eTag = strtoupper($this->thumbnail).'_'.$this->mediaID;
+ }
+ else {
+ $mimeType = $this->media->fileType;
+ $filesize = $this->media->filesize;
+ $location = $this->media->getLocation();
+ $this->eTag = $this->mediaID;
+ }
+
+ // init file reader
+ $this->fileReader = new FileReader($location, [
+ 'filename' => $this->media->filename,
+ 'mimeType' => $mimeType,
+ 'filesize' => $filesize,
+ 'showInline' => (in_array($mimeType, self::$inlineMimeTypes)),
+ 'enableRangeSupport' => ($this->thumbnail ? true : false),
+ 'lastModificationTime' => $this->media->uploadTime,
+ 'expirationDate' => TIME_NOW + 31536000,
+ 'maxAge' => 31536000
+ ]);
+
+ if ($this->eTag !== null) {
+ $this->fileReader->addHeader('ETag', '"'.$this->eTag.'"');
+ }
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function readParameters() {
+ parent::readParameters();
+
+ if (isset($_REQUEST['id'])) $this->mediaID = intval($_REQUEST['id']);
+ $this->media = new Media($this->mediaID);
+ if (!$this->media->mediaID) {
+ throw new IllegalLinkException();
+ }
+
+ if (isset($_REQUEST['thumbnail'])) $this->thumbnail = StringUtil::trim($_REQUEST['thumbnail']);
+ if ($this->thumbnail && !isset(Media::getThumbnailSizes()[$this->thumbnail])) {
+ throw new IllegalLinkException();
+ }
+
+ $parameters = [
+ 'object' => $this->media
+ ];
+ if ($this->thumbnail && $this->media->{$this->thumbnail.'ThumbnailType'}) {
+ $parameters['thumbnail'] = $this->thumbnail;
+ }
+ else {
+ $this->thumbnail = '';
+ }
+
+ $this->canonicalURL = LinkHandler::getInstance()->getLink('Media', $parameters);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function show() {
+ parent::show();
+
+ // etag caching
+ if (isset($_SERVER['HTTP_IF_NONE_MATCH']) && $_SERVER['HTTP_IF_NONE_MATCH'] == '"'.$this->eTag.'"') {
+ @header('HTTP/1.1 304 Not Modified');
+ exit;
+ }
+
+ // send file to client
+ $this->fileReader->send();
+ exit;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\clipboard\action;
+use wcf\data\clipboard\action\ClipboardAction;
+use wcf\data\media\MediaAction;
+use wcf\system\WCF;
+
+/**
+ * Clipboard action implementation for media files.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2015 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf
+ * @subpackage system.clipboard.action
+ * @category Community Framework
+ * @since 2.2
+ */
+class MediaClipboardAction extends AbstractClipboardAction {
+ /**
+ * @inheritdoc
+ */
+ protected $actionClassActions = [ 'delete' ];
+
+ /**
+ * @inheritdoc
+ */
+ protected $supportedActions = [
+ 'delete',
+ 'insert'
+ ];
+
+ /**
+ * @inheritdoc
+ */
+ public function execute(array $objects, ClipboardAction $action) {
+ $item = parent::execute($objects, $action);
+
+ if ($item === null) {
+ return null;
+ }
+
+ // handle actions
+ switch ($action->actionName) {
+ case 'delete':
+ $item->addInternalData('confirmMessage', WCF::getLanguage()->getDynamicVariable('wcf.clipboard.item.com.woltlab.wcf.media.delete.confirmMessage', [
+ 'count' => $item->getCount()
+ ]));
+ break;
+ }
+
+ return $item;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getClassName() {
+ return MediaAction::class;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getTypeName() {
+ return 'com.woltlab.wcf.media';
+ }
+
+ /**
+ * Returns the ids of the media files which can be deleted.
+ *
+ * @return integer[]
+ */
+ public function validateDelete() {
+ if (!WCF::getSession()->getPermission('admin.content.cms.canManageMedia')) {
+ return [];
+ }
+
+ return array_keys($this->objects);
+ }
+
+ /**
+ * Returns the ids of the media files which can be inserted.
+ *
+ * @return integer[]
+ */
+ public function validateInsert() {
+ return array_keys($this->objects);
+ }
+}
* Sets the value for the given element. If the element is multilingual,
* the given value is set for every available language.
*
- * @param integer $elementID
+ * @param string $elementID
* @param string $plainValue
*/
public function setValue($elementID, $plainValue) {
* Sets the values for the given element. If the element is not multilingual,
* use I18nHandler::setValue() instead.
*
- * @param integer $elementID
- * @param array<array> $i18nValues
+ * @param string $elementID
+ * @param string[] $i18nValues
*/
public function setValues($elementID, array $i18nValues) {
if (empty($i18nValues)) {
--- /dev/null
+<?php
+namespace wcf\system\upload;
+use wcf\system\WCF;
+use wcf\data\AbstractDatabaseObjectAction;
+use wcf\data\IFile;
+use wcf\data\IThumbnailFile;
+use wcf\util\FileUtil;
+use wcf\system\image\ImageHandler;
+use wcf\util\ExifUtil;
+use wcf\system\event\EventHandler;
+
+/**
+ * Default implementation for saving uploaded files.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2015 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf
+ * @subpackage system.upload
+ * @category Community Framework
+ * @since 2.2
+ */
+class DefaultUploadFileSaveStrategy implements IUploadFileSaveStrategy {
+ /**
+ * name of the database object action class name
+ * @var string
+ */
+ public $actionClassName = '';
+
+ /**
+ * name of the database object editor class name
+ * @var string
+ */
+ public $editorClassName = '';
+
+ /**
+ * additional data stored with the default file data
+ * @var array
+ */
+ public $data = [];
+
+ /**
+ * created objects
+ * @var IFile[]
+ */
+ public $objects = [];
+
+ /**
+ * options handing saving details
+ *
+ * - bool rotateImages: if true, images are automatically rotated
+ * - bool generateThumbnails: if true, thumbnails are automatically generated after saving file
+ *
+ * @var array
+ */
+ public $options = [];
+
+ /**
+ * Creates a new instance of DefaultUploadFileSaveStrategy.
+ *
+ * @param string $actionClassName
+ * @param array $options
+ * @param array $data
+ */
+ public function __construct($actionClassName, array $options = [ ], array $data = [ ]) {
+ $this->actionClassName = $actionClassName;
+ $this->options = $options;
+ $this->data = $data;
+
+ if (!is_subclass_of($this->actionClassName, AbstractDatabaseObjectAction::class)) {
+ throw new SystemException("'".$this->actionClassName."' does not extend '".AbstractDatabaseObjectAction::class."'");
+ }
+
+ $this->editorClassName = (new $this->actionClassName([ ], ''))->getClassName();
+ $baseClass = call_user_func([ $this->editorClassName, 'getBaseClass' ]);
+ if (!is_subclass_of($baseClass, IFile::class)) {
+ throw new SystemException("'".$this->editorClassName."' does not implement '".IFile::class."'");
+ }
+ if (is_subclass_of($baseClass, IThumbnailFile::class)) {
+ $this->options['thumbnailSizes'] = call_user_func([ $baseClass, 'getThumbnailSizes' ]);
+ }
+ }
+
+ /**
+ * Returns the successfully created file objects.
+ *
+ * @return IFile[]
+ */
+ public function getObjects() {
+ return $this->objects;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function save(UploadFile $uploadFile) {
+ $data = array_merge([
+ 'filename' => $uploadFile->getFilename(),
+ 'filesize' => $uploadFile->getFilesize(),
+ 'fileType' => $uploadFile->getMimeType(),
+ 'fileHash' => sha1_file($uploadFile->getLocation()),
+ 'uploadTime' => TIME_NOW,
+ 'userID' => (WCF::getUser()->userID ?: null)
+ ], $this->data);
+
+ // get image data
+ if (($imageData = $uploadFile->getImageData()) !== null) {
+ $data['width'] = $imageData['width'];
+ $data['height'] = $imageData['height'];
+ $data['fileType'] = $imageData['mimeType'];
+
+ if (preg_match('~^image/(gif|jpe?g|png)$~i', $data['fileType'])) {
+ $data['isImage'] = 1;
+ }
+ }
+
+ $action = new $this->actionClassName([ ], 'create', [
+ 'data' => $data
+ ]);
+ $object = $action->executeAction()['returnValues'];
+
+ $dir = dirname($object->getLocation());
+ if (!@file_exists($dir)) {
+ FileUtil::makePath($dir, 0777);
+ }
+
+ // move uploaded filex
+ if (@move_uploaded_file($uploadFile->getLocation(), $object->getLocation())) {
+ // rotate image based on the exif data
+ if (!empty($this->options['rotateImages'])) {
+ if ($object->isImage) {
+ if (FileUtil::checkMemoryLimit($object->width * $object->height * ($object->fileType == 'image/png' ? 4 : 3) * 2.1)) {
+ $exifData = ExifUtil::getExifData($object->getLocation());
+ if (!empty($exifData)) {
+ $orientation = ExifUtil::getOrientation($exifData);
+ if ($orientation != ExifUtil::ORIENTATION_ORIGINAL) {
+ $adapter = ImageHandler::getInstance()->getAdapter();
+ $adapter->loadFile($object->getLocation());
+
+ $newImage = null;
+ switch ($orientation) {
+ case ExifUtil::ORIENTATION_180_ROTATE:
+ $newImage = $adapter->rotate(180);
+ break;
+
+ case ExifUtil::ORIENTATION_90_ROTATE:
+ $newImage = $adapter->rotate(90);
+ break;
+
+ case ExifUtil::ORIENTATION_270_ROTATE:
+ $newImage = $adapter->rotate(270);
+ break;
+
+ case ExifUtil::ORIENTATION_HORIZONTAL_FLIP:
+ case ExifUtil::ORIENTATION_VERTICAL_FLIP:
+ case ExifUtil::ORIENTATION_VERTICAL_FLIP_270_ROTATE:
+ case ExifUtil::ORIENTATION_HORIZONTAL_FLIP_270_ROTATE:
+ // unsupported
+ break;
+ }
+
+ if ($newImage !== null) {
+ $adapter->load($newImage, $adapter->getType());
+ }
+
+ $adapter->writeImage($object->getLocation());
+
+ // update width, height and filesize of the attachment
+ if ($newImage !== null && ($orientation == ExifUtil::ORIENTATION_90_ROTATE || $orientation == ExifUtil::ORIENTATION_270_ROTATE)) {
+ (new $this->editorClassName($object))->update([
+ 'height' => $object->width,
+ 'width' => $object->height,
+ 'filesize' => filesize($object->getLocation())
+ ]);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ $this->objects[$uploadFile->getInternalFileID()] = $object;
+ }
+ else {
+ (new $this->editorClassName($object))->delete();
+ }
+
+ if ($object->isImage && !empty($this->options['generateThumbnails']) && $object instanceof IThumbnailFile) {
+ $this->generateThumbnails($object);
+ }
+ }
+
+ /**
+ * Generates thumbnails for the given file.
+ *
+ * @param IThumbnailFile $file
+ */
+ public function generateThumbnails(IThumbnailFile $file) {
+ $smallestThumbnailSize = reset($this->options['thumbnailSizes']);
+
+ // image is smaller than smallest thumbnail size
+ if ($file->width <= $smallestThumbnailSize['width'] && $file->height <= $smallestThumbnailSize['height']) {
+ return;
+ }
+
+ // check memory limit
+ if (!FileUtil::checkMemoryLimit($file->width * $file->height * ($file->fileType == 'image/png' ? 4 : 3) * 2.1)) {
+ return;
+ }
+
+ $adapter = ImageHandler::getInstance()->getAdapter();
+ $adapter->loadFile($file->getLocation());
+
+ $updateData = [ ];
+ foreach ($this->options['thumbnailSizes'] as $type => $sizeData) {
+ $prefix = 'thumbnail';
+ if (!empty($type)) {
+ $prefix = $type.'Thumbnail';
+ }
+
+ $thumbnailLocation = $file->getThumbnailLocation($type);
+
+ // delete old thumbnails
+ if ($file->{$prefix.'Type'}) {
+ @unlink($thumbnailLocation);
+ $updateData[$prefix.'Type'] = '';
+ $updateData[$prefix.'Size'] = 0;
+ $updateData[$prefix.'Width'] = 0;
+ $updateData[$prefix.'Height'] = 0;
+ }
+
+ if ($file->width > $sizeData['width'] || $file->height > $sizeData['height']) {
+ $thumbnail = $adapter->createThumbnail($sizeData['width'], $sizeData['height'], isset($sizeData['retainDimensions']) ? $sizeData['retainDimensions'] : true);
+ $adapter->writeImage($thumbnail, $thumbnailLocation);
+ if (file_exists($thumbnailLocation) && ($imageData = @getimagesize($thumbnailLocation)) !== false) {
+ $updateData[$prefix.'Type'] = $imageData['mime'];
+ $updateData[$prefix.'Size'] = @filesize($thumbnailLocation);
+ $updateData[$prefix.'Width'] = $imageData[0];
+ $updateData[$prefix.'Height'] = $imageData[1];
+ }
+ }
+ }
+
+ if (!empty($updateData)) {
+ (new $this->editorClassName($file))->update($updateData);
+ }
+ }
+}
public static function getRealPath($path) {
$path = self::unifyDirSeparator($path);
- $result = array();
+ $result = [];
$pathA = explode('/', $path);
if ($pathA[0] === '') {
$result[] = '';
* @deprecated This method currently only is a wrapper around \wcf\util\HTTPRequest. Please use
* HTTPRequest from now on, as this method may be removed in the future.
*/
- public static function downloadFileFromHttp($httpUrl, $prefix = 'package', array $options = array(), array $postParameters = array(), &$headers = array()) {
+ public static function downloadFileFromHttp($httpUrl, $prefix = 'package', array $options = [], array $postParameters = [], &$headers = []) {
$request = new HTTPRequest($httpUrl, $options, $postParameters);
$request->execute();
$reply = $request->getReply();
return self::getMemoryLimit() == -1 || self::getMemoryLimit() > (memory_get_usage() + $neededMemory);
}
+ /**
+ * Returns the FontAwesome icon CSS class name for a file with the given
+ * mime type.
+ *
+ * @param string $mimeType
+ * @return string
+ */
+ public static function getIconClassByMimeType($mimeType) {
+ if (StringUtil::startsWith($mimeType, 'image/')) {
+ return 'fa-file-image-o';
+ }
+ else if (StringUtil::startsWith($mimeType, 'video/')) {
+ return 'fa-file-video-o';
+ }
+ else if (StringUtil::startsWith($mimeType, 'audio/')) {
+ return 'fa-file-sound-o';
+ }
+ else if (StringUtil::startsWith($mimeType, 'text/')) {
+ return 'fa-file-text-o';
+ }
+ else {
+ switch ($mimeType) {
+ case 'application/msword':
+ case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
+ return 'fa-file-word-o';
+ break;
+
+ case 'application/pdf':
+ return 'fa-file-pdf-o';
+ break;
+
+ case 'application/vnd.ms-powerpoint':
+ case 'application/vnd.openxmlformats-officedocument.presentationml.presentation':
+ return 'fa-file-powerpoint-o';
+ break;
+
+ case 'application/vnd.ms-excel':
+ case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':
+ return 'fa-file-excel-o';
+ break;
+
+ case 'application/zip':
+ case 'application/x-tar':
+ case 'application/x-gzip':
+ return 'fa-file-archive-o';
+ break;
+
+ case 'application/xml':
+ return 'fa-file-text-o';
+ break;
+ }
+ }
+
+ return 'fa-file-o';
+ }
+
private function __construct() { }
}
--- /dev/null
+deny from all
\ No newline at end of file
color: $wcfStatusWarningText;
}
-/* inline errors */
-.innerError {
- background-color: rgb(242, 222, 222);
- color: rgb(169, 68, 66);
+.innerError,
+.innerInfo {
display: table;
line-height: 1.5;
margin-top: 8px;
padding: 5px 10px;
position: relative;
-
+
/* pointer */
&::before {
border: 6px solid transparent;
- border-bottom-color: rgb(242, 222, 222);
border-top-width: 0;
content: "";
display: inline-block;
z-index: 101;
}
}
+
+/* inline errors */
+.innerError {
+ background-color: rgb(242, 222, 222);
+ color: rgb(169, 68, 66);
+
+ &::before {
+ border-bottom-color: rgb(242, 222, 222);
+ }
+}
+
+/* inline infos */
+/* TODO: use other colors */
+.innerInfo {
+ background-color: $wcfStatusInfoBackground;
+ color: $wcfStatusInfoText;
+
+ &::before {
+ border-bottom-color: $wcfStatusInfoBorder;
+ }
+}
--- /dev/null
+#mediaManagerMediaUploadButton > .button {
+ box-sizing: border-box;
+ margin: 0;
+ text-align: center;
+ width: 100%;
+
+ > input {
+ width: 100%;
+ }
+}
+
+#mediaManagerMediaList {
+ font-size: 0;
+
+ @extend .clearfix;
+
+ > li {
+ float: left;
+ height: 120px;
+ width: 120px;
+ position: relative;
+ border: 1px solid #aaa;
+ overflow: hidden;
+ font-size: 1rem;
+ margin: 0 $wcfGapSmall $wcfGapSmall 0;
+
+ &:hover {
+ > .buttonGroupNavigation {
+ height: auto;
+ padding: $wcfGapSmall;
+ }
+ }
+
+ &.jsMarked {
+ > .mediaInformation,
+ > .buttonGroupNavigation {
+ // TODO background-color: fade($wcfSelectedBackgroundColor, 90%);
+ // TODO color: $wcfSelectedColor;
+
+ a {
+ //TODO: color: $wcfSelectedColor;
+ }
+
+ .icon {
+ //TODO: color: $wcfSelectedColor;
+ text-shadow: none;
+ }
+ }
+ }
+
+ > .mediaThumbnail {
+ height: 96px;
+ width: 96px;
+ padding: 12px;
+ }
+
+ > .mediaInformation {
+ position: absolute;
+ bottom: 0;
+ background: rgba(0,0,0,0.6);
+ color: #fff;
+ width: 100%;
+ padding: $wcfGapSmall;
+ box-sizing: border-box;
+
+ @extend .wcfFontSmall;
+ }
+
+ > .buttonGroupNavigation {
+ position: absolute;
+ top: 0;
+ right: 0;
+ background: rgba(0,0,0,0.6);
+ height: 0;
+ overflow: hidden;
+
+ /* TODO: transition */
+
+ .icon {
+ color: #fff;
+ @include textShadow(#000);
+ }
+ }
+ }
+}
+
+#mediaEditor {
+ #mediaThumbnail {
+ text-align: center;
+
+ + .box48 > dl {
+ font-size: $wcfFontSizeSmall;
+ }
+ }
+}
<category name="wcf.clipboard">
<item name="wcf.clipboard.item.unmarkAll"><![CDATA[Unmark All]]></item>
+ <item name="wcf.clipboard.item.com.woltlab.wcf.media.delete"><![CDATA[Delete ({#$count})]]></item>
+ <item name="wcf.clipboard.item.com.woltlab.wcf.media.insert"><![CDATA[Insert ({#$count})]]></item>
+
<item name="wcf.clipboard.item.com.woltlab.wcf.tag.delete"><![CDATA[Delete ({#$count})]]></item>
<item name="wcf.clipboard.item.com.woltlab.wcf.tag.delete.confirmMessage"><![CDATA[Do you really want to delete {#$count} tag{if $count != 1}s{/if}?]]></item>
<item name="wcf.clipboard.item.com.woltlab.wcf.tag.setAsSynonyms"><![CDATA[Set as Synonyms ({#$count})]]></item>
<item name="wcf.clipboard.item.com.woltlab.wcf.user.sendNewPassword"><![CDATA[Send New Password ({#$count})]]></item>
<item name="wcf.clipboard.item.com.woltlab.wcf.user.sendNewPassword.confirmMessage"><![CDATA[Do you really want to send a new password to {#$count} user{if $count != 1}s{/if}?]]></item>
+ <item name="wcf.clipboard.label.com.woltlab.wcf.media.marked"><![CDATA[{#$count} File{if $count != 1}s{/if} marked]]></item>
<item name="wcf.clipboard.label.com.woltlab.wcf.tag.marked"><![CDATA[{#$count} Tag{if $count != 1}s{/if} marked]]></item>
<item name="wcf.clipboard.label.com.woltlab.wcf.user.marked"><![CDATA[{#$count} User{if $count != 1}s{/if} marked]]></item>
</category>
<item name="wcf.global.button.enable"><![CDATA[Enable]]></item>
<item name="wcf.global.button.fullscreen"><![CDATA[Full Screen Mode]]></item>
<item name="wcf.global.button.hide"><![CDATA[Hide]]></item>
+ <item name="wcf.global.button.insert"><![CDATA[Insert]]></item>
<item name="wcf.global.button.next"><![CDATA[Next »]]></item>
<item name="wcf.global.button.preview"><![CDATA[Preview]]></item>
<item name="wcf.global.button.refresh"><![CDATA[Refresh]]></item>
<item name="wcf.map.useLocationSuggestion"><![CDATA[Use Location]]></item>
</category>
+ <category name="wcf.media">
+ <item name="wcf.media.altText"><![CDATA[Alternate Text]]></item>
+ <item name="wcf.media.button.insert"><![CDATA[Insert]]></item>
+ <item name="wcf.media.caption"><![CDATA[Caption]]></item>
+ <item name="wcf.media.edit"><![CDATA[Edit Media File]]></item>
+ <item name="wcf.media.filename"><![CDATA[Filename]]></item>
+ <item name="wcf.media.filesize"><![CDATA[Filesize]]></item>
+ <item name="wcf.media.imageDimensions"><![CDATA[Dimensions]]></item>
+ <item name="wcf.media.imageDimensions.value"><![CDATA[{#$width}×{#$height}]]></item>
+ <item name="wcf.media.insert"><![CDATA[Insert File]]></item>
+ <item name="wcf.media.insert.imageSize"><![CDATA[Image Size]]></item>
+ <item name="wcf.media.insert.imageSize.large"><![CDATA[Large Thumbnail ({#$width}×{#$height})]]></item>
+ <item name="wcf.media.insert.imageSize.medium"><![CDATA[Medium Thumbnail ({#$width}×{#$height})]]></item>
+ <item name="wcf.media.insert.imageSize.original"><![CDATA[Original Image ({#$width}×{#$height})]]></item>
+ <item name="wcf.media.insert.imageSize.small"><![CDATA[Small Thumbnail ({#$width}×{#$height})]]></item>
+ <item name="wcf.media.isMultilingual"><![CDATA[Enable Multilingualism]]></item>
+ <item name="wcf.media.languageID"><![CDATA[Language]]></item>
+ <item name="wcf.media.manager"><![CDATA[Manage Media]]></item>
+ <item name="wcf.media.search.cancel"><![CDATA[Cancel Search]]></item>
+ <item name="wcf.media.search.filetype"><![CDATA[File Types]]></item>
+ <item name="wcf.media.search.filetype.all"><![CDATA[All File Types]]></item>
+ <item name="wcf.media.search.filetype.image"><![CDATA[Images]]></item>
+ <item name="wcf.media.search.filetype.other"><![CDATA[Other]]></item>
+ <item name="wcf.media.search.filetype.text"><![CDATA[Texts]]></item>
+ <item name="wcf.media.search.placeholder"><![CDATA[Search Files]]></item>
+ <item name="wcf.media.uploader"><![CDATA[Uploaded By]]></item>
+ </category>
+
<category name="wcf.message">
<item name="wcf.message.autosave.prompt"><![CDATA[Restore saved draft?]]></item>
<item name="wcf.message.autosave.prompt.confirm"><![CDATA[Restore draft]]></item>
isImage TINYINT(1) NOT NULL DEFAULT 0,
width SMALLINT(5) NOT NULL DEFAULT 0,
- height SMALLINT(5) NOT NULL DEFAULT 0,
+ height SMALLINT(5) NOT NULL DEFAULT 0,
tinyThumbnailType VARCHAR(255) NOT NULL DEFAULT '',
tinyThumbnailSize INT(10) NOT NULL DEFAULT 0,
UNIQUE KEY (objectTypeID, objectID)
);
+DROP TABLE IF EXISTS wcf1_media;
+CREATE TABLE wcf1_media (
+ mediaID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+
+ filename VARCHAR(255) NOT NULL DEFAULT '',
+ filesize INT(10) NOT NULL DEFAULT 0,
+ fileType VARCHAR(255) NOT NULL DEFAULT '',
+ fileHash VARCHAR(255) NOT NULL DEFAULT '',
+ uploadTime INT(10) NOT NULL DEFAULT 0,
+ userID INT(10),
+ username VARCHAR(255) NOT NULL,
+ languageID INT(10),
+ isMultilingual TINYINT(1) NOT NULL DEFAULT 0,
+
+ isImage TINYINT(1) NOT NULL DEFAULT 0,
+ width SMALLINT(5) NOT NULL DEFAULT 0,
+ height SMALLINT(5) NOT NULL DEFAULT 0,
+
+ tinyThumbnailType VARCHAR(255) NOT NULL DEFAULT '',
+ tinyThumbnailSize INT(10) NOT NULL DEFAULT 0,
+ tinyThumbnailWidth SMALLINT(5) NOT NULL DEFAULT 0,
+ tinyThumbnailHeight SMALLINT(5) NOT NULL DEFAULT 0,
+
+ smallThumbnailType VARCHAR(255) NOT NULL DEFAULT '',
+ smallThumbnailSize INT(10) NOT NULL DEFAULT 0,
+ smallThumbnailWidth SMALLINT(5) NOT NULL DEFAULT 0,
+ smallThumbnailHeight SMALLINT(5) NOT NULL DEFAULT 0,
+
+ mediumThumbnailType VARCHAR(255) NOT NULL DEFAULT '',
+ mediumThumbnailSize INT(10) NOT NULL DEFAULT 0,
+ mediumThumbnailWidth SMALLINT(5) NOT NULL DEFAULT 0,
+ mediumThumbnailHeight SMALLINT(5) NOT NULL DEFAULT 0,
+
+ largeThumbnailType VARCHAR(255) NOT NULL DEFAULT '',
+ largeThumbnailSize INT(10) NOT NULL DEFAULT 0,
+ largeThumbnailWidth SMALLINT(5) NOT NULL DEFAULT 0,
+ largeThumbnailHeight SMALLINT(5) NOT NULL DEFAULT 0
+);
+
+DROP TABLE IF EXISTS wcf1_media_content;
+CREATE TABLE wcf1_media_content (
+ mediaID INT(10) NOT NULL,
+ languageID INT(10),
+ title VARCHAR(255) NOT NULL,
+ caption TEXT,
+ altText VARCHAR(255) NOT NULL DEFAULT '',
+ UNIQUE KEY (mediaID, languageID)
+);
+
DROP TABLE IF EXISTS wcf1_menu;
CREATE TABLE wcf1_menu (
menuID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
ALTER TABLE wcf1_language_item ADD FOREIGN KEY (languageCategoryID) REFERENCES wcf1_language_category (languageCategoryID) ON DELETE CASCADE;
ALTER TABLE wcf1_language_item ADD FOREIGN KEY (packageID) REFERENCES wcf1_package (packageID) ON DELETE CASCADE;
+ALTER TABLE wcf1_media ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE SET NULL;
+ALTER TABLE wcf1_media ADD FOREIGN KEY (languageID) REFERENCES wcf1_language (languageID) ON DELETE SET NULL;
+
+ALTER TABLE wcf1_media_content ADD FOREIGN KEY (mediaID) REFERENCES wcf1_media (mediaID) ON DELETE CASCADE;
+ALTER TABLE wcf1_media_content ADD FOREIGN KEY (languageID) REFERENCES wcf1_language (languageID) ON DELETE CASCADE;
+
ALTER TABLE wcf1_menu ADD FOREIGN KEY (packageID) REFERENCES wcf1_package (packageID) ON DELETE CASCADE;
ALTER TABLE wcf1_menu_item ADD FOREIGN KEY (menuID) REFERENCES wcf1_menu (menuID) ON DELETE CASCADE;