File: /home/posscale/subdomains/xibo/lib/Controller/Library.php
<?php
/*
* Xibo - Digital Signage - http://www.xibo.org.uk
* Copyright (C) 2006-2015 Daniel Garner, Spring Signage Ltd
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Controller;
use Stash\Interfaces\PoolInterface;
use Xibo\Entity\Media;
use Xibo\Entity\Widget;
use Xibo\Exception\AccessDeniedException;
use Xibo\Exception\ConfigurationException;
use Xibo\Exception\LibraryFullException;
use Xibo\Exception\XiboException;
use Xibo\Factory\DataSetFactory;
use Xibo\Factory\DayPartFactory;
use Xibo\Factory\DisplayFactory;
use Xibo\Factory\DisplayGroupFactory;
use Xibo\Factory\LayoutFactory;
use Xibo\Factory\MediaFactory;
use Xibo\Factory\ModuleFactory;
use Xibo\Factory\PermissionFactory;
use Xibo\Factory\PlaylistFactory;
use Xibo\Factory\RegionFactory;
use Xibo\Factory\ScheduleFactory;
use Xibo\Factory\TagFactory;
use Xibo\Factory\UserFactory;
use Xibo\Factory\UserGroupFactory;
use Xibo\Factory\WidgetFactory;
use Xibo\Helper\ByteFormatter;
use Xibo\Helper\XiboUploadHandler;
use Xibo\Service\ConfigServiceInterface;
use Xibo\Service\DateServiceInterface;
use Xibo\Service\LogServiceInterface;
use Xibo\Service\SanitizerServiceInterface;
use Xibo\Storage\StorageServiceInterface;
/**
* Class Library
* @package Xibo\Controller
*/
class Library extends Base
{
/**
* @var StorageServiceInterface
*/
private $store;
/** @var PoolInterface */
private $pool;
/**
* @var UserFactory
*/
private $userFactory;
/**
* @var ModuleFactory
*/
private $moduleFactory;
/**
* @var TagFactory
*/
private $tagFactory;
/**
* @var MediaFactory
*/
private $mediaFactory;
/**
* @var WidgetFactory
*/
private $widgetFactory;
/**
* @var PlaylistFactory
*/
private $playlistFactory;
/**
* @var LayoutFactory
*/
private $layoutFactory;
/**
* @var PermissionFactory
*/
private $permissionFactory;
/**
* @var UserGroupFactory
*/
private $userGroupFactory;
/** @var DisplayGroupFactory */
private $displayGroupFactory;
/** @var RegionFactory */
private $regionFactory;
/** @var DataSetFactory */
private $dataSetFactory;
/** @var DisplayFactory */
private $displayFactory;
/** @var ScheduleFactory */
private $scheduleFactory;
/** @var DayPartFactory */
private $dayPartFactory;
/**
* Set common dependencies.
* @param LogServiceInterface $log
* @param SanitizerServiceInterface $sanitizerService
* @param \Xibo\Helper\ApplicationState $state
* @param \Xibo\Entity\User $user
* @param \Xibo\Service\HelpServiceInterface $help
* @param DateServiceInterface $date
* @param ConfigServiceInterface $config
* @param StorageServiceInterface $store
* @param PoolInterface $pool
* @param UserFactory $userFactory
* @param ModuleFactory $moduleFactory
* @param TagFactory $tagFactory
* @param MediaFactory $mediaFactory
* @param WidgetFactory $widgetFactory
* @param PermissionFactory $permissionFactory
* @param LayoutFactory $layoutFactory
* @param PlaylistFactory $playlistFactory
* @param UserGroupFactory $userGroupFactory
* @param DisplayGroupFactory $displayGroupFactory
* @param RegionFactory $regionFactory
* @param DataSetFactory $dataSetFactory
* @param DisplayFactory $displayFactory
* @param ScheduleFactory $scheduleFactory
* @param DayPartFactory $dayPartFactory
*/
public function __construct($log, $sanitizerService, $state, $user, $help, $date, $config, $store, $pool, $userFactory, $moduleFactory, $tagFactory, $mediaFactory, $widgetFactory, $permissionFactory, $layoutFactory, $playlistFactory, $userGroupFactory, $displayGroupFactory, $regionFactory, $dataSetFactory, $displayFactory, $scheduleFactory, $dayPartFactory)
{
$this->setCommonDependencies($log, $sanitizerService, $state, $user, $help, $date, $config);
$this->store = $store;
$this->moduleFactory = $moduleFactory;
$this->mediaFactory = $mediaFactory;
$this->widgetFactory = $widgetFactory;
$this->pool = $pool;
$this->userFactory = $userFactory;
$this->tagFactory = $tagFactory;
$this->permissionFactory = $permissionFactory;
$this->layoutFactory = $layoutFactory;
$this->playlistFactory = $playlistFactory;
$this->userGroupFactory = $userGroupFactory;
$this->displayGroupFactory = $displayGroupFactory;
$this->regionFactory = $regionFactory;
$this->dataSetFactory = $dataSetFactory;
$this->displayFactory = $displayFactory;
$this->scheduleFactory = $scheduleFactory;
$this->dayPartFactory = $dayPartFactory;
}
/**
* Get Module Factory
* @return ModuleFactory
*/
public function getModuleFactory()
{
return $this->moduleFactory;
}
/**
* Get Media Factory
* @return MediaFactory
*/
public function getMediaFactory()
{
return $this->mediaFactory;
}
/**
* Get Permission Factory
* @return PermissionFactory
*/
public function getPermissionFactory()
{
return $this->permissionFactory;
}
/**
* Get Widget Factory
* @return WidgetFactory
*/
public function getWidgetFactory()
{
return $this->widgetFactory;
}
/**
* Get Layout Factory
* @return LayoutFactory
*/
public function getLayoutFactory()
{
return $this->layoutFactory;
}
/**
* Get Playlist Factory
* @return PlaylistFactory
*/
public function getPlaylistFactory()
{
return $this->playlistFactory;
}
/**
* Get UserGroup Factory
* @return UserGroupFactory
*/
public function getUserGroupFactory()
{
return $this->userGroupFactory;
}
/**
* Get RegionFactory
* @return RegionFactory
*/
public function getRegionFactory()
{
return $this->regionFactory;
}
/**
* Get DisplayGroup Factory
* @return DisplayGroupFactory
*/
public function getDisplayGroupFactory()
{
return $this->displayGroupFactory;
}
/**
* @return DataSetFactory
*/
public function getDataSetFactory()
{
return $this->dataSetFactory;
}
/**
* @return DisplayFactory
*/
public function getDisplayFactory()
{
return $this->displayFactory;
}
/**
* @return ScheduleFactory
*/
public function getScheduleFactory()
{
return $this->scheduleFactory;
}
/**
* Displays the page logic
*/
function displayPage()
{
// Users we have permission to see
$this->getState()->template = 'library-page';
$this->getState()->setData([
'users' => $this->userFactory->query(),
'modules' => $this->moduleFactory->query(['module'], ['regionSpecific' => 0, 'enabled' => 1]),
'groups' => $this->userGroupFactory->query()
]);
}
/**
* Prints out a Table of all media items
*
* @SWG\Get(
* path="/library",
* operationId="librarySearch",
* tags={"library"},
* summary="Library Search",
* description="Search the Library for this user",
* @SWG\Parameter(
* name="mediaId",
* in="formData",
* description="Filter by Media Id",
* type="integer",
* required=false
* ),
* @SWG\Parameter(
* name="media",
* in="formData",
* description="Filter by Media Name",
* type="string",
* required=false
* ),
* @SWG\Parameter(
* name="type",
* in="formData",
* description="Filter by Media Type",
* type="string",
* required=false
* ),
* @SWG\Parameter(
* name="ownerId",
* in="formData",
* description="Filter by Owner Id",
* type="integer",
* required=false
* ),
* @SWG\Parameter(
* name="retired",
* in="formData",
* description="Filter by Retired",
* type="integer",
* required=false
* ),
* @SWG\Parameter(
* name="tags",
* in="formData",
* description="Filter by Tags - comma seperated",
* type="string",
* required=false
* ),
* @SWG\Parameter(
* name="exactTags",
* in="formData",
* description="A flag indicating whether to treat the tags filter as an exact match",
* type="integer",
* required=false
* ),
* @SWG\Parameter(
* name="duration",
* in="formData",
* description="Filter by Duration - a number or less-than,greater-than,less-than-equal or great-than-equal followed by a | followed by a number",
* type="string",
* required=false
* ),
* @SWG\Parameter(
* name="fileSize",
* in="formData",
* description="Filter by File Size - a number or less-than,greater-than,less-than-equal or great-than-equal followed by a | followed by a number",
* type="string",
* required=false
* ),
* @SWG\Parameter(
* name="ownerUserGroupId",
* in="formData",
* description="Filter by users in this UserGroupId",
* type="integer",
* required=false
* ),
* @SWG\Response(
* response=200,
* description="successful operation",
* @SWG\Schema(
* type="array",
* @SWG\Items(ref="#/definitions/Media")
* )
* )
* )
*/
function grid()
{
$user = $this->getUser();
// Construct the SQL
$mediaList = $this->mediaFactory->query($this->gridRenderSort(), $this->gridRenderFilter([
'mediaId' => $this->getSanitizer()->getInt('mediaId'),
'name' => $this->getSanitizer()->getString('media'),
'type' => $this->getSanitizer()->getString('type'),
'tags' => $this->getSanitizer()->getString('tags'),
'exactTags' => $this->getSanitizer()->getCheckbox('exactTags'),
'ownerId' => $this->getSanitizer()->getInt('ownerId'),
'retired' => $this->getSanitizer()->getInt('retired'),
'duration' => $this->getSanitizer()->getString('duration'),
'fileSize' => $this->getSanitizer()->getString('fileSize'),
'ownerUserGroupId' => $this->getSanitizer()->getInt('ownerUserGroupId')
]));
// Add some additional row content
foreach ($mediaList as $media) {
/* @var \Xibo\Entity\Media $media */
$media->revised = ($media->parentId != 0) ? 1 : 0;
// Thumbnail URL
$media->thumbnail = '';
$media->thumbnailUrl = '';
$media->downloadUrl = '';
if ($media->mediaType == 'image') {
$download = $this->urlFor('library.download', ['id' => $media->mediaId]) . '?preview=1';
$media->thumbnail = '<a class="img-replace" data-toggle="lightbox" data-type="image" href="' . $download . '"><img src="' . $download . '&width=100&height=56&cache=1" /></i></a>';
$media->thumbnailUrl = $download . '&width=100&height=56&cache=1';
$media->downloadUrl = $download;
}
$media->fileSizeFormatted = ByteFormatter::format($media->fileSize);
if ($this->isApi())
break;
$media->includeProperty('buttons');
$media->buttons = array();
// Buttons
if ($user->checkEditable($media)) {
// Edit
$media->buttons[] = array(
'id' => 'content_button_edit',
'url' => $this->urlFor('library.edit.form', ['id' => $media->mediaId]),
'text' => __('Edit')
);
}
if ($user->checkDeleteable($media)) {
// Delete
$media->buttons[] = array(
'id' => 'content_button_delete',
'url' => $this->urlFor('library.delete.form', ['id' => $media->mediaId]),
'text' => __('Delete')
);
}
if ($user->checkPermissionsModifyable($media)) {
// Permissions
$media->buttons[] = array(
'id' => 'content_button_permissions',
'url' => $this->urlFor('user.permissions.form', ['entity' => 'Media', 'id' => $media->mediaId]),
'text' => __('Permissions')
);
}
// Download
$media->buttons[] = array(
'id' => 'content_button_download',
'linkType' => '_self', 'external' => true,
'url' => $this->urlFor('library.download', ['id' => $media->mediaId]) . '?attachment=' . $media->fileName,
'text' => __('Download')
);
$media->buttons[] = ['divider' => true];
$media->buttons[] = array(
'id' => 'usage_report_button',
'url' => $this->urlFor('library.usage.form', ['id' => $media->mediaId]),
'text' => __('Usage Report')
);
}
$this->getState()->template = 'grid';
$this->getState()->recordsTotal = $this->mediaFactory->countLast();
$this->getState()->setData($mediaList);
}
/**
* Media Delete Form
* @param int $mediaId
*/
public function deleteForm($mediaId)
{
$media = $this->mediaFactory->getById($mediaId);
if (!$this->getUser()->checkDeleteable($media))
throw new AccessDeniedException();
$media->setChildObjectDependencies($this->layoutFactory, $this->widgetFactory, $this->displayGroupFactory, $this->displayFactory, $this->scheduleFactory);
$media->load(['deleting' => true]);
$this->getState()->template = 'library-form-delete';
$this->getState()->setData([
'media' => $media,
'help' => $this->getHelp()->link('Library', 'Delete')
]);
}
/**
* Delete Media
* @param int $mediaId
*
* @SWG\Delete(
* path="/library/{mediaId}",
* operationId="libraryDelete",
* tags={"library"},
* summary="Delete Media",
* description="Delete Media from the Library",
* @SWG\Parameter(
* name="mediaId",
* in="path",
* description="The Media ID to Delete",
* type="integer",
* required=true
* ),
* @SWG\Parameter(
* name="forceDelete",
* in="formData",
* description="If the media item has been used should it be force removed from items that uses it?",
* type="integer",
* required=true
* ),
* @SWG\Response(
* response=204,
* description="successful operation"
* )
* )
*/
public function delete($mediaId)
{
$media = $this->mediaFactory->getById($mediaId);
if (!$this->getUser()->checkDeleteable($media))
throw new AccessDeniedException();
// Check
$media->setChildObjectDependencies($this->layoutFactory, $this->widgetFactory, $this->displayGroupFactory, $this->displayFactory, $this->scheduleFactory);
$media->load(['deleting' => true]);
if ($media->isUsed() && $this->getSanitizer()->getCheckbox('forceDelete') == 0)
throw new \InvalidArgumentException(__('This library item is in use.'));
// Delete
$media->delete();
// Do we need to reassess fonts?
if ($media->mediaType == 'font') {
$this->installFonts();
}
// Return
$this->getState()->hydrate([
'httpStatus' => 204,
'message' => sprintf(__('Deleted %s'), $media->name)
]);
}
/**
* Add a file to the library
* expects to be fed by the blueimp file upload handler
* @throws \Exception
*
* @SWG\Post(
* path="/library",
* operationId="libraryAdd",
* tags={"library"},
* summary="Add Media",
* description="Add Media to the Library",
* @SWG\Parameter(
* name="files",
* in="formData",
* description="The Uploaded File",
* type="file",
* required=true
* ),
* @SWG\Parameter(
* name="name",
* in="formData",
* description="Optional Media Name",
* type="string",
* required=false
* ),
* @SWG\Parameter(
* name="oldMediaId",
* in="formData",
* description="Id of an existing media file which should be replaced with the new upload",
* type="integer",
* required=false
* ),
* @SWG\Parameter(
* name="updateInLayouts",
* in="formData",
* description="Flag (0, 1), set to 1 to update this media in all layouts (use with oldMediaId) ",
* type="integer",
* required=false
* ),
* @SWG\Parameter(
* name="deleteOldRevisions",
* in="formData",
* description="Flag (0 , 1), to either remove or leave the old file revisions (use with oldMediaId)",
* type="integer",
* required=false
* ),
* @SWG\Response(
* response=200,
* description="successful operation"
* )
* )
*
* @param array $options
*/
public function add($options = [])
{
$options = array_merge([
'oldMediaId' => null,
'updateInLayouts' => 0,
'deleteOldRevisions' => 0,
'allowMediaTypeChange' => 0
], $options);
$libraryFolder = $this->getConfig()->GetSetting('LIBRARY_LOCATION');
// Make sure the library exists
self::ensureLibraryExists($libraryFolder);
// Get Valid Extensions
if ($this->getSanitizer()->getInt('oldMediaId') !== null) {
$media = $this->mediaFactory->getById($this->getSanitizer()->getInt('oldMediaId'));
$validExt = $this->moduleFactory->getValidExtensions(['type' => $media->mediaType]);
}
else
$validExt = $this->moduleFactory->getValidExtensions();
// Make sure there is room in the library
$libraryLimit = $this->getConfig()->GetSetting('LIBRARY_SIZE_LIMIT_KB') * 1024;
$options = array(
'userId' => $this->getUser()->userId,
'controller' => $this,
'oldMediaId' => $this->getSanitizer()->getInt('oldMediaId', $options['oldMediaId']),
'widgetId' => $this->getSanitizer()->getInt('widgetId'),
'updateInLayouts' => $this->getSanitizer()->getCheckbox('updateInLayouts', $options['updateInLayouts']),
'deleteOldRevisions' => $this->getSanitizer()->getCheckbox('deleteOldRevisions', $options['deleteOldRevisions']),
'allowMediaTypeChange' => $options['allowMediaTypeChange'],
'playlistId' => $this->getSanitizer()->getInt('playlistId'),
'upload_dir' => $libraryFolder . 'temp/',
'download_via_php' => true,
'script_url' => $this->urlFor('library.add'),
'upload_url' => $this->urlFor('library.add'),
'image_versions' => array(),
'accept_file_types' => '/\.' . implode('|', $validExt) . '$/i',
'libraryLimit' => $libraryLimit,
'libraryQuotaFull' => ($libraryLimit > 0 && $this->libraryUsage() > $libraryLimit)
);
// Output handled by UploadHandler
$this->setNoOutput(true);
$this->getLog()->debug('Hand off to Upload Handler with options: %s', json_encode($options));
// Hand off to the Upload Handler provided by jquery-file-upload
new XiboUploadHandler($options);
}
/**
* Edit Form
* @param int $mediaId
*/
public function editForm($mediaId)
{
$media = $this->mediaFactory->getById($mediaId);
if (!$this->getUser()->checkEditable($media))
throw new AccessDeniedException();
$this->getState()->template = 'library-form-edit';
$this->getState()->setData([
'media' => $media,
'validExtensions' => implode('|', $this->moduleFactory->getValidExtensions(['type' => $media->mediaType])),
'help' => $this->getHelp()->link('Library', 'Edit')
]);
}
/**
* Edit Media
* @param int $mediaId
*
* @SWG\Put(
* path="/library/{mediaId}",
* operationId="libraryEdit",
* tags={"library"},
* summary="Edit Media",
* description="Edit a Media Item in the Library",
* @SWG\Parameter(
* name="mediaId",
* in="path",
* description="The Media ID to Edit",
* type="integer",
* required=true
* ),
* @SWG\Parameter(
* name="name",
* in="formData",
* description="Media Item Name",
* type="string",
* required=true
* ),
* @SWG\Parameter(
* name="duration",
* in="formData",
* description="The duration in seconds for this Media Item",
* type="integer",
* required=true
* ),
* @SWG\Parameter(
* name="retired",
* in="formData",
* description="Flag indicating if this media is retired",
* type="integer",
* required=true
* ),
* @SWG\Parameter(
* name="tags",
* in="formData",
* description="Comma separated list of Tags",
* type="string",
* required=false
* ),
* @SWG\Parameter(
* name="updateInLayouts",
* in="formData",
* description="Flag indicating whether to update the duration in all Layouts the Media is assigned to",
* type="integer",
* required=false
* ),
* @SWG\Response(
* response=200,
* description="successful operation",
* @SWG\Schema(ref="#/definitions/Media")
* )
* )
*/
public function edit($mediaId)
{
$media = $this->mediaFactory->getById($mediaId);
if (!$this->getUser()->checkEditable($media))
throw new AccessDeniedException();
if ($media->mediaType == 'font')
throw new \InvalidArgumentException(__('Sorry, Fonts do not have any editable properties.'));
$media->name = $this->getSanitizer()->getString('name');
$media->duration = $this->getSanitizer()->getInt('duration');
$media->retired = $this->getSanitizer()->getCheckbox('retired');
$media->replaceTags($this->tagFactory->tagsFromString($this->getSanitizer()->getString('tags')));
// Should we update the media in all layouts?
if ($this->getSanitizer()->getCheckbox('updateInLayouts') == 1) {
foreach ($this->widgetFactory->getByMediaId($media->mediaId) as $widget) {
/* @var Widget $widget */
$widget->duration = $media->duration;
$widget->save();
}
}
$media->save();
// Are we a font
if ($media->mediaType == 'font') {
// We may have made changes and need to regenerate
$this->installFonts();
}
// Return
$this->getState()->hydrate([
'message' => sprintf(__('Edited %s'), $media->name),
'id' => $media->mediaId,
'data' => $media
]);
}
/**
* Tidy Library
*/
public function tidyForm()
{
if ($this->getConfig()->GetSetting('SETTING_LIBRARY_TIDY_ENABLED') != 1)
throw new ConfigurationException(__('Sorry this function is disabled.'));
// Work out how many files there are
$media = $this->mediaFactory->query(null, ['unusedOnly' => 1, 'ownerId' => $this->getUser()->userId]);
$sumExcludingGeneric = 0;
$countExcludingGeneric = 0;
$sumGeneric = 0;
$countGeneric = 0;
foreach ($media as $item) {
if ($item->mediaType == 'genericfile') {
$countGeneric++;
$sumGeneric = $sumGeneric + $item->fileSize;
}
else {
$countExcludingGeneric++;
$sumExcludingGeneric = $sumExcludingGeneric + $item->fileSize;
}
}
$this->getState()->template = 'library-form-tidy';
$this->getState()->setData([
'sumExcludingGeneric' => ByteFormatter::format($sumExcludingGeneric),
'sumGeneric' => ByteFormatter::format($sumGeneric),
'countExcludingGeneric' => $countExcludingGeneric,
'countGeneric' => $countGeneric,
'help' => $this->getHelp()->link('Content', 'TidyLibrary')
]);
}
/**
* Tidies up the library
*
* @SWG\Delete(
* path="/library/tidy",
* operationId="libraryTidy",
* tags={"library"},
* summary="Tidy Library",
* description="Routine tidy of the library, removing unused files.",
* @SWG\Parameter(
* name="tidyGenericFiles",
* in="formData",
* description="Also delete generic files?",
* type="integer",
* required=false
* ),
* @SWG\Response(
* response=200,
* description="successful operation"
* )
* )
*/
public function tidy()
{
if ($this->getConfig()->GetSetting('SETTING_LIBRARY_TIDY_ENABLED') != 1)
throw new ConfigurationException(__('Sorry this function is disabled.'));
$tidyGenericFiles = $this->getSanitizer()->getCheckbox('tidyGenericFiles');
// Get a list of media that is not in use (for this user)
$media = $this->mediaFactory->query(null, ['unusedOnly' => 1, 'ownerId' => $this->getUser()->userId]);
$i = 0;
foreach ($media as $item) {
/* @var Media $item */
if ($tidyGenericFiles != 1 && $item->mediaType == 'genericfile')
continue;
// Eligable for delete
$i++;
$item->setChildObjectDependencies($this->layoutFactory, $this->widgetFactory, $this->displayGroupFactory, $this->displayFactory, $this->scheduleFactory);
$item->load();
$item->delete();
}
// Return
$this->getState()->hydrate([
'message' => __('Library Tidy Complete'),
'countDeleted' => $i
]);
}
/**
* Make sure the library exists
* @param string $libraryFolder
* @throws ConfigurationException when the library is not writable
*/
public static function ensureLibraryExists($libraryFolder)
{
// Check that this location exists - and if not create it..
if (!file_exists($libraryFolder))
mkdir($libraryFolder, 0777, true);
if (!file_exists($libraryFolder . '/temp'))
mkdir($libraryFolder . '/temp', 0777, true);
if (!file_exists($libraryFolder . '/cache'))
mkdir($libraryFolder . '/cache', 0777, true);
if (!file_exists($libraryFolder . '/screenshots'))
mkdir($libraryFolder . '/screenshots', 0777, true);
// Check that we are now writable - if not then error
if (!is_writable($libraryFolder))
throw new ConfigurationException(__('Library not writable'));
}
/**
* @return string
*/
public function getLibraryCacheUri()
{
return $this->getConfig()->GetSetting('LIBRARY_LOCATION') . '/cache';
}
/**
* Library Usage
* @return int
*/
public function libraryUsage()
{
$results = $this->store->select('SELECT IFNULL(SUM(FileSize), 0) AS SumSize FROM media', array());
return $this->getSanitizer()->int($results[0]['SumSize']);
}
/**
* Gets a file from the library
* @param int $mediaId
* @param string $type
*
* @SWG\Get(
* path="/library/download/{mediaId}/{type}",
* operationId="libraryDownload",
* tags={"library"},
* summary="Download Media",
* description="Download a Media file from the Library",
* produces={"application/octet-stream"},
* @SWG\Parameter(
* name="mediaId",
* in="path",
* description="The Media ID to Download",
* type="integer",
* required=true
* ),
* @SWG\Parameter(
* name="type",
* in="path",
* description="The Module Type of the Download",
* type="string",
* required=true
* ),
* @SWG\Response(
* response=200,
* description="successful operation",
* @SWG\Schema(type="file"),
* @SWG\Header(
* header="X-Sendfile",
* description="Apache Send file header - if enabled.",
* type="string"
* ),
* @SWG\Header(
* header="X-Accel-Redirect",
* description="nginx send file header - if enabled.",
* type="string"
* )
* )
* )
*/
public function download($mediaId, $type = '')
{
$media = $this->mediaFactory->getById($mediaId);
$this->getLog()->debug('Download request for mediaId ' . $mediaId . ' and type ' . $type . '. Media is a ' . $media->mediaType);
if ($media->mediaType === 'module') {
// Make sure that our user has this mediaId assigned to a Widget they can view
// we can't test for normal media permissions, because no user has direct access to these "module" files
// https://github.com/xibosignage/xibo/issues/1304
if (count($this->widgetFactory->query(null, ['mediaId' => $mediaId])) <= 0) {
throw new AccessDeniedException();
}
} else if (!$this->getUser()->checkViewable($media)) {
throw new AccessDeniedException();
}
if ($type != '') {
$widget = $this->moduleFactory->create($type);
$widgetOverride = $this->widgetFactory->createEmpty();
$widgetOverride->assignMedia($media->mediaId);
$widget->setWidget($widgetOverride);
} else {
// Make a media module
$widget = $this->moduleFactory->createWithMedia($media);
}
$widget->getResource();
$this->setNoOutput(true);
}
/**
* Return the CMS flavored font css
*/
public function fontCss()
{
// Regenerate the CSS for fonts
$css = $this->installFonts(['invalidateCache' => false]);
// Work out the etag
$app = $this->getApp();
$app->response()->header('Content-Type', 'text/css');
$app->etag(md5($css['css']));
// Return the CSS to the browser as a file
$out = fopen('php://output', 'w');
fputs($out, $css['css']);
fclose($out);
$this->setNoOutput(true);
}
/**
* Get font CKEditor config
* @return string
*/
public function fontCKEditorConfig()
{
if (DBVERSION < 125)
return null;
// Regenerate the CSS for fonts
$css = $this->installFonts(['invalidateCache' => false]);
return $css['ckeditor'];
}
/**
* Installs fonts
* @param array $options
* @return array
*/
public function installFonts($options = [])
{
$options = array_merge([
'invalidateCache' => true
], $options);
$this->getLog()->debug('Install Fonts called with options: %s', json_encode($options));
// Get the item from the cache
$cssItem = $this->pool->getItem('fontCss' . $this->getUser()->userId);
// Get the CSS
$cssDetails = $cssItem->get();
if ($options['invalidateCache'] || $cssItem->isMiss()) {
$this->getLog()->info('Regenerating font cache');
$fontTemplate = '@font-face {
font-family: \'[family]\';
src: url(\'[url]\');
}';
// Save a fonts.css file to the library for use as a module
$fonts = $this->mediaFactory->getByMediaType('font');
$css = '';
$localCss = '';
$ckEditorString = '';
if (count($fonts) > 0) {
foreach ($fonts as $font) {
/* @var Media $font */
// Skip unreleased fonts
if ($font->released == 0)
continue;
// Separate out the display name and the referenced name (referenced name cannot contain any odd characters or numbers)
$displayName = $font->name;
$familyName = strtolower(preg_replace('/\s+/', ' ', preg_replace('/\d+/u', '', $font->name)));
// Css for the client contains the actual stored as location of the font.
$css .= str_replace('[url]', $font->storedAs, str_replace('[family]', $familyName, $fontTemplate));
// Test to see if this user should have access to this font
if (!$this->getUser()->checkViewable($font))
continue;
// Css for the local CMS contains the full download path to the font
$url = $this->urlFor('library.download', ['type' => 'font', 'id' => $font->mediaId]) . '?download=1&downloadFromLibrary=1';
$localCss .= str_replace('[url]', $url, str_replace('[family]', $familyName, $fontTemplate));
// CKEditor string
$ckEditorString .= $displayName . '/' . $familyName . ';';
}
// Make sure the library exists, otherwise we can't copy the temporary file.
$this->ensureLibraryExists($this->getConfig()->GetSetting('LIBRARY_LOCATION'));
// Put the player CSS into the temporary library location
$tempUrl = $this->getConfig()->GetSetting('LIBRARY_LOCATION') . 'temp/fonts.css';
file_put_contents($tempUrl, $css);
// Install it (doesn't expire, isn't a system file, force update)
$media = $this->mediaFactory->createModuleSystemFile('fonts.css', $tempUrl);
$media->expires = 0;
$media->moduleSystemFile = true;
$media->isSaveRequired = true;
$media->save();
// We can remove the temp file
@unlink($tempUrl);
$cssDetails = [
'css' => $localCss,
'ckeditor' => $ckEditorString
];
$cssItem->set($cssDetails);
$cssItem->expiresAfter(new \DateInterval('P30D'));
$this->pool->saveDeferred($cssItem);
// Clear the display cache
$this->pool->deleteItem('/display');
}
} else {
$this->getLog()->debug('CMS font CSS returned from Cache.');
}
// Return a fonts css string for use locally (in the CMS)
return $cssDetails;
}
/**
* Installs all files related to the enabled modules
*/
public function installAllModuleFiles()
{
$this->getLog()->info('Installing all module files');
// Do this for all enabled modules
foreach ($this->moduleFactory->getEnabled() as $module) {
/* @var \Xibo\Entity\Module $module */
// Install Files for this module
$moduleObject = $this->moduleFactory->create($module->type);
$moduleObject->installFiles();
}
// Dump the cache on all displays
foreach ($this->displayFactory->query() as $display) {
/** @var \Xibo\Entity\Display $display */
$display->notify();
}
}
/**
* Remove temporary files
*/
public function removeTempFiles()
{
$libraryTemp = $this->getConfig()->GetSetting('LIBRARY_LOCATION') . 'temp';
if (!is_dir($libraryTemp))
return;
// Dump the files in the temp folder
foreach (scandir($libraryTemp) as $item) {
if ($item == '.' || $item == '..')
continue;
// Has this file been written to recently?
if (filemtime($libraryTemp . DIRECTORY_SEPARATOR . $item) > (time() - 86400)) {
$this->getLog()->debug('Skipping active file: ' . $item);
continue;
}
$this->getLog()->debug('Deleting temp file: ' . $item);
unlink($libraryTemp . DIRECTORY_SEPARATOR . $item);
}
}
/**
* Removes all expired media files
*/
public function removeExpiredFiles()
{
// Get a list of all expired files and delete them
foreach ($this->mediaFactory->query(null, array('expires' => time(), 'allModules' => 1, 'length' => 100)) as $entry) {
/* @var \Xibo\Entity\Media $entry */
// If the media type is a module, then pretend its a generic file
$this->getLog()->info('Removing Expired File %s', $entry->name);
$entry->setChildObjectDependencies($this->layoutFactory, $this->widgetFactory, $this->displayGroupFactory, $this->displayFactory, $this->scheduleFactory);
$entry->delete();
}
}
/**
* @param $mediaId
* @throws LibraryFullException
*/
public function mcaas($mediaId)
{
// This is only available through the API
if (!$this->isApi())
throw new AccessDeniedException(__('Route is available through the API'));
// We need to get the access token we used to authorize this request.
// as we are API we can expect that in the $app.
/** @var $accessToken \League\OAuth2\Server\Entity\AccessTokenEntity */
$accessToken = $this->getApp()->server->getAccessToken();
// Call Add with the oldMediaId
$this->add([
'oldMediaId' => $mediaId,
'updateInLayouts' => 1,
'deleteOldRevisions' => 1,
'allowMediaTypeChange' => 1
]);
// Expire the token
$accessToken->expire();
}
/**
* @SWG\Post(
* path="/library/{mediaId}/tag",
* operationId="mediaTag",
* tags={"library"},
* summary="Tag Media",
* description="Tag a Media with one or more tags",
* @SWG\Parameter(
* name="mediaId",
* in="path",
* description="The Media Id to Tag",
* type="integer",
* required=true
* ),
* @SWG\Parameter(
* name="tag",
* in="formData",
* description="An array of tags",
* type="array",
* required=true,
* @SWG\Items(type="string")
* ),
* @SWG\Response(
* response=200,
* description="successful operation",
* @SWG\Schema(ref="#/definitions/Media")
* )
* )
*
* @param $mediaId
* @throws \Xibo\Exception\NotFoundException
*/
public function tag($mediaId)
{
// Edit permission
// Get the media
$media = $this->mediaFactory->getById($mediaId);
// Check Permissions
if (!$this->getUser()->checkEditable($media))
throw new AccessDeniedException();
$tags = $this->getSanitizer()->getStringArray('tag');
if (count($tags) <= 0)
throw new \InvalidArgumentException(__('No tags to assign'));
foreach ($tags as $tag) {
$media->assignTag($this->tagFactory->tagFromString($tag));
}
$media->save(['validate' => false]);
// Return
$this->getState()->hydrate([
'message' => sprintf(__('Tagged %s'), $media->name),
'id' => $media->mediaId,
'data' => $media
]);
}
/**
* @SWG\Delete(
* path="/library/{mediaId}/untag",
* operationId="mediaUntag",
* tags={"library"},
* summary="Untag Media",
* description="Untag a Media with one or more tags",
* @SWG\Parameter(
* name="mediaId",
* in="path",
* description="The Media Id to Untag",
* type="integer",
* required=true
* ),
* @SWG\Parameter(
* name="tag",
* in="formData",
* description="An array of tags",
* type="array",
* required=true,
* @SWG\Items(type="string")
* ),
* @SWG\Response(
* response=200,
* description="successful operation",
* @SWG\Schema(ref="#/definitions/Media")
* )
* )
*
* @param $mediaId
* @throws \Xibo\Exception\NotFoundException
*/
public function untag($mediaId)
{
// Edit permission
// Get the media
$media = $this->mediaFactory->getById($mediaId);
// Check Permissions
if (!$this->getUser()->checkEditable($media))
throw new AccessDeniedException();
$tags = $this->getSanitizer()->getStringArray('tag');
if (count($tags) <= 0)
throw new \InvalidArgumentException(__('No tags to unassign'));
foreach ($tags as $tag) {
$media->unassignTag($this->tagFactory->tagFromString($tag));
}
$media->save(['validate' => false]);
// Return
$this->getState()->hydrate([
'message' => sprintf(__('Untagged %s'), $media->name),
'id' => $media->mediaId,
'data' => $media
]);
}
/**
* Library Usage Report Form
* @param int $mediaId
*/
public function usageForm($mediaId)
{
$media = $this->mediaFactory->getById($mediaId);
if (!$this->getUser()->checkViewable($media))
throw new AccessDeniedException();
// Get a list of displays that this mediaId is used on
$displays = $this->displayFactory->query($this->gridRenderSort(), $this->gridRenderFilter(['disableUserCheck' => 1, 'mediaId' => $mediaId]));
$this->getState()->template = 'library-form-usage';
$this->getState()->setData([
'media' => $media,
'countDisplays' => count($displays)
]);
}
/**
* @SWG\Get(
* path="/library/usage/{mediaId}",
* operationId="libraryUsageReport",
* tags={"library"},
* summary="Get Library Item Usage Report",
* description="Get the records for the library item usage report",
* @SWG\Response(
* response=200,
* description="successful operation"
* )
* )
*
* @param int $mediaId
*/
public function usage($mediaId)
{
$media = $this->mediaFactory->getById($mediaId);
if (!$this->getUser()->checkViewable($media))
throw new AccessDeniedException();
// Get a list of displays that this mediaId is used on by direct assignment
$displays = $this->displayFactory->query($this->gridRenderSort(), $this->gridRenderFilter(['mediaId' => $mediaId]));
// have we been provided with a date/time to restrict the scheduled events to?
$mediaDate = $this->getSanitizer()->getDate('mediaEventDate');
if ($mediaDate !== null) {
// Get a list of scheduled events that this mediaId is used on, based on the date provided
$toDate = $mediaDate->copy()->addDay();
$events = $this->scheduleFactory->query(null, [
'futureSchedulesFrom' => $mediaDate->format('U'),
'futureSchedulesTo' => $toDate->format('U'),
'mediaId' => $mediaId
]);
} else {
// All scheduled events for this mediaId
$events = $this->scheduleFactory->query(null, [
'mediaId' => $mediaId
]);
}
// Total records returned from the schedules query
$totalRecords = $this->scheduleFactory->countLast();
foreach ($events as $row) {
/* @var \Xibo\Entity\Schedule $row */
// Generate this event
$row->setDayPartFactory($this->dayPartFactory);
// Assess the date?
if ($mediaDate !== null) {
try {
$scheduleEvents = $row->getEvents($mediaDate, $toDate);
} catch (XiboException $e) {
$this->getLog()->error('Unable to getEvents for ' . $row->eventId);
continue;
}
// Skip events that do not fall within the specified days
if (count($scheduleEvents) <= 0)
continue;
$this->getLog()->debug('EventId ' . $row->eventId . ' as events: ' . json_encode($scheduleEvents));
}
// Load the display groups
$row->load();
foreach ($row->displayGroups as $displayGroup) {
foreach ($this->displayFactory->getByDisplayGroupId($displayGroup->displayGroupId) as $display) {
$found = false;
// Check to see if our ID is already in our list
foreach ($displays as $existing) {
if ($existing->displayId === $display->displayId) {
$found = true;
break;
}
}
if (!$found)
$displays[] = $display;
}
}
}
$this->getState()->template = 'grid';
$this->getState()->recordsTotal = $totalRecords;
$this->getState()->setData($displays);
}
/**
* @SWG\Get(
* path="/library/usage/layouts/{mediaId}",
* operationId="libraryUsageLayoutsReport",
* tags={"library"},
* summary="Get Library Item Usage Report for Layouts",
* description="Get the records for the library item usage report for Layouts",
* @SWG\Response(
* response=200,
* description="successful operation"
* )
* )
*
* @param int $mediaId
*/
public function usageLayouts($mediaId)
{
$media = $this->mediaFactory->getById($mediaId);
if (!$this->getUser()->checkViewable($media))
throw new AccessDeniedException();
$layouts = $this->layoutFactory->query(null, ['mediaId' => $mediaId]);
if (!$this->isApi()) {
foreach ($layouts as $layout) {
$layout->includeProperty('buttons');
// Add some buttons for this row
if ($this->getUser()->checkEditable($layout)) {
// Design Button
$layout->buttons[] = array(
'id' => 'layout_button_design',
'linkType' => '_self', 'external' => true,
'url' => $this->urlFor('layout.designer', array('id' => $layout->layoutId)),
'text' => __('Design')
);
}
// Preview
$layout->buttons[] = array(
'id' => 'layout_button_preview',
'linkType' => '_blank',
'external' => true,
'url' => $this->urlFor('layout.preview', ['id' => $layout->layoutId]),
'text' => __('Preview Layout')
);
}
}
$this->getState()->template = 'grid';
$this->getState()->recordsTotal = $this->layoutFactory->countLast();
$this->getState()->setData($layouts);
}
}