Merge lp:~alexharrington/xibo/server-160-rc1-preview into lp:xibo/1.6

Proposed by Alex Harrington
Status: Rejected
Rejected by: Alex Harrington
Proposed branch: lp:~alexharrington/xibo/server-160-rc1-preview
Merge into: lp:xibo/1.6
Diff against target: 1723 lines (+1370/-99) (has conflicts)
17 files modified
server/install/database/66.sql (+13/-1)
server/install/master/data.sql (+11/-3)
server/lib/modules/module.class.php (+58/-2)
server/lib/pages/content.class.php (+7/-0)
server/lib/pages/layout.class.php (+9/-2)
server/lib/pages/module.class.php (+1/-78)
server/lib/pages/preview.class.php (+125/-0)
server/modules/flash.module.php (+12/-1)
server/modules/image.module.php (+64/-3)
server/modules/preview/html-preview.css (+92/-0)
server/modules/preview/html-preview.js (+499/-0)
server/modules/preview/html5Preloader.js (+448/-0)
server/modules/video.module.php (+12/-1)
server/theme/default/html/layout_designer.php (+2/-1)
server/theme/default/html/layout_page_grid.php (+2/-2)
server/theme/default/html/library_page_grid.php (+13/-3)
server/theme/default/js/xibo-layout-designer.js (+2/-2)
Text conflict in server/install/database/66.sql
Text conflict in server/install/master/data.sql
To merge this branch: bzr merge lp:~alexharrington/xibo/server-160-rc1-preview
Reviewer Review Type Date Requested Status
Dan Garner Needs Fixing
Review via email: mp+206045@code.launchpad.net

Description of the change

First pass HTML5 layout preview

To post a comment you must log in.
Revision history for this message
Dan Garner (dangarner) wrote :

You'll need to add your preview page to the pages table and give it an appropriate page group. Have you tested a non-admin user?

review: Needs Fixing
311. By Alex Harrington

[server] Add page security for the new preview page

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'server/install/database/66.sql'
2--- server/install/database/66.sql 2014-01-20 17:50:31 +0000
3+++ server/install/database/66.sql 2014-02-12 21:14:59 +0000
4@@ -78,6 +78,7 @@
5 (78, 'User', 'SetPassword', 'manual/single.php?p=users/users#Set_Password'),
6 (79, 'DataSet', 'ImportCSV', 'manual/single.php?p=content/content_dataset#Import_CSV');
7
8+<<<<<<< TREE
9 INSERT INTO `setting` (
10 `settingid` ,
11 `setting` ,
12@@ -94,6 +95,17 @@
13 NULL , 'SETTING_LIBRARY_TIDY_ENABLED', 'Off', 'dropdown', NULL , 'On|Off', 'general', '0'
14 );
15
16+=======
17+INSERT INTO `setting` VALUES (`settingid`,`setting`,`value`,`type`,`helptext`,`options`,`cat`,`userChange`)
18+(NULL, 'SENDFILE_MODE', 'Off', 'dropdown', 'When a user downloads a file from the library or previews a layout, should we attempt to use Apache X-Sendfile, Nginx X-Accel, or PHP (Off) to return the file from the library?', 'Off|Apache|Nginx', 'general', '1');
19+
20+INSERT INTO `pages` (`name`, `pagegroupID`) VALUES
21+('preview', 3);
22+
23+INSERT INTO `lkpagegroup` (`lkpagegroupID`, `pageID`, `groupID`) VALUES
24+(63, 41, 1);
25+
26+>>>>>>> MERGE-SOURCE
27 UPDATE `version` SET `app_ver` = '1.6.0-rc1', `XmdsVersion` = 3;
28 UPDATE `setting` SET `value` = 0 WHERE `setting` = 'PHONE_HOME_DATE';
29-UPDATE `version` SET `DBVersion` = '66';
30\ No newline at end of file
31+UPDATE `version` SET `DBVersion` = '66';
32
33=== modified file 'server/install/master/data.sql'
34--- server/install/master/data.sql 2014-01-20 17:50:31 +0000
35+++ server/install/master/data.sql 2014-02-12 21:14:59 +0000
36@@ -152,7 +152,8 @@
37 (37, 'campaign', 3),
38 (38, 'transition', 4),
39 (39, 'timeline', 3),
40-(40, 'sessions', 9);
41+(40, 'sessions', 9),
42+(41, 'preview', 3);
43
44 INSERT INTO `menuitem` (`MenuItemID`, `MenuID`, `PageID`, `Args`, `Text`, `Class`, `Img`, `Sequence`, `External`) VALUES
45 (1, 1, 2, NULL, 'Schedule', NULL, NULL, 1, 0),
46@@ -251,9 +252,14 @@
47 (63, 'GLOBAL_THEME_NAME', 'default', 'text', 'The Theme to apply to all pages by default', NULL, 'general', 1),
48 (64, 'DEFAULT_LAT', '51.504', 'text', 'The Latitude to apply for any Geo aware Previews', NULL, 'general', 1),
49 (65, 'DEFAULT_LONG', '-0.104', 'text', 'The Longitude to apply for any Geo aware Previews', NULL, 'general', 1),
50+<<<<<<< TREE
51 (66, 'SCHEDULE_WITH_VIEW_PERMISSION', 'No', 'dropdown', 'Should users with View permissions on displays be allowed to schedule to them?', 'Yes|No', 'permissions', '1'),
52 (67, 'SETTING_IMPORT_ENABLED', 'Off', 'dropdown', NULL , 'On|Off', 'general', '0'),
53 (68, 'SETTING_LIBRARY_TIDY_ENABLED', 'Off', 'dropdown', NULL , 'On|Off', 'general', '0');
54+=======
55+(66, 'SCHEDULE_WITH_VIEW_PERMISSION', 'No', 'dropdown', 'Should users with View permissions on displays be allowed to schedule to them?', 'Yes|No', 'permissions', '1'),
56+(69, 'SENDFILE_MODE', 'Off', 'dropdown', 'When a user downloads a file from the library or previews a layout, should we attempt to use Apache X-Sendfile, Nginx X-Accel, or PHP (Off) to return the file from the library?', 'Off|Apache|Nginx', 'general', '1');
57+>>>>>>> MERGE-SOURCE
58
59 INSERT INTO `usertype` (`usertypeid`, `usertype`) VALUES
60 (1, 'Super Admin'),
61@@ -303,7 +309,9 @@
62 (38, 19, 1),
63 (48, 5, 1),
64 (51, 7, 1),
65-(54, 24, 1);
66+(54, 24, 1),
67+(63, 41, 1);
68+
69
70 INSERT INTO `lktemplategroup` (`LkTemplateGroupID`, `TemplateID`, `GroupID`, `View`, `Edit`, `Del`) VALUES
71 (1, 1, 2, 1, 0, 0),
72@@ -330,4 +338,4 @@
73
74 INSERT INTO `datasetcolumntype` (`DataSetColumnTypeID`, `DataSetColumnType`) VALUES
75 (1, 'Value'),
76-(2, 'Formula');
77\ No newline at end of file
78+(2, 'Formula');
79
80=== modified file 'server/lib/modules/module.class.php'
81--- server/lib/modules/module.class.php 2014-01-19 14:06:56 +0000
82+++ server/lib/modules/module.class.php 2014-02-12 21:14:59 +0000
83@@ -51,6 +51,7 @@
84 protected $deleteFromRegion;
85 protected $showRegionOptions;
86 protected $originalUserId;
87+ protected $storedAs;
88
89 // Track the error state
90 private $error;
91@@ -223,7 +224,7 @@
92 $this->assignedMedia = false;
93
94 // Load what we know about this media into the object
95- $SQL = "SELECT duration, name, UserId FROM media WHERE mediaID = '$mediaid'";
96+ $SQL = "SELECT duration, name, UserId, storedAs FROM media WHERE mediaID = '$mediaid'";
97
98 Debug::LogEntry('audit', $SQL, 'Module', 'SetMediaInformation');
99
100@@ -239,6 +240,7 @@
101 $this->duration = $row[0];
102 $this->name = $row[1];
103 $this->originalUserId = $row[2];
104+ $this->storedAs = $row[3];
105 }
106 else
107 return $this->SetError(__('Unable to find media record with the provided ID'));
108@@ -1896,5 +1898,59 @@
109 // Defaults: Stored media is valid, region specific is unknown
110 return ($this->regionSpecific) ? 0 : 1;
111 }
112+
113+ /**
114+ * Return filebased media items to the browser for Download/Preview
115+ * @return
116+ * @param $download Boolean
117+ */
118+ public function ReturnFile($fileName = '') {
119+ // Return the raw flash file with appropriate headers
120+ $library = Config::GetSetting("LIBRARY_LOCATION");
121+
122+ # If we weren't passed in a filename then use the default
123+ if ($fileName == '') {
124+ $fileName = $library . $this->storedAs;
125+ }
126+
127+ $download = Kit::GetParam('download', _REQUEST, _BOOLEAN, False);
128+
129+ $size = filesize($fileName);
130+
131+ if ($download) {
132+ header('Content-Type: application/octet-stream');
133+ header("Content-Transfer-Encoding: Binary");
134+ header("Content-disposition: attachment; filename=\"" . basename($fileName) . "\"");
135+ }
136+ else {
137+ $fi = new finfo( FILEINFO_MIME_TYPE );
138+ $mime = $fi->file( $fileName );
139+ header("Content-Type: {$mime}");
140+ }
141+
142+ //Output a header
143+ header('Pragma: public');
144+ header('Cache-Control: max-age=86400');
145+ header('Expires: '. gmdate('D, d M Y H:i:s \G\M\T', time() + 86400));
146+ header('Content-Length: ' . $size);
147+
148+ // Send via Apache X-Sendfile header?
149+ if (Config::GetSetting('SENDFILE_MODE') == 'Apache') {
150+ header("X-Sendfile: $fileName");
151+ exit();
152+ }
153+
154+ // Send via Nginx X-Accel-Redirect?
155+ if (Config::GetSetting('SENDFILE_MODE') == 'Nginx') {
156+ header("X-Accel-Redirect: /download/" . $this->storedAs);
157+ exit();
158+ }
159+
160+ // Return the file with PHP
161+ // Disable any buffering to prevent OOM errors.
162+ @ob_end_clean();
163+ @ob_end_flush();
164+ readfile($fileName);
165+ }
166 }
167-?>
168\ No newline at end of file
169+?>
170
171=== modified file 'server/lib/pages/content.class.php'
172--- server/lib/pages/content.class.php 2014-02-08 14:55:15 +0000
173+++ server/lib/pages/content.class.php 2014-02-12 21:14:59 +0000
174@@ -147,6 +147,13 @@
175 'text' => __('Permissions')
176 );
177 }
178+
179+ // Download
180+ $row['buttons'][] = array(
181+ 'id' => 'content_button_download',
182+ 'url' => 'index.php?p=module&mod=' . $row['mediatype'] . '&q=Exec&method=GetResource&download=1&mediaid=' . $row['mediaid'],
183+ 'text' => __('Download')
184+ );
185
186 // Add to the collection
187 $rows[] = $row;
188
189=== modified file 'server/lib/pages/layout.class.php'
190--- server/lib/pages/layout.class.php 2014-02-08 14:55:15 +0000
191+++ server/lib/pages/layout.class.php 2014-02-12 21:14:59 +0000
192@@ -136,6 +136,7 @@
193 Theme::Set('layout_form_edit_background_url', 'index.php?p=layout&q=BackgroundForm&modify=true&layoutid=' . $this->layoutid);
194 Theme::Set('layout_form_savetemplate_url', 'index.php?p=template&q=TemplateForm&layoutid=' . $this->layoutid);
195 Theme::Set('layout_form_addregion_url', 'index.php?p=timeline&q=AddRegion&layoutid=' . $this->layoutid);
196+ Theme::Set('layout_form_preview_url', 'index.php?p=preview&q=render&ajax=true&layoutid=' . $this->layoutid);
197 Theme::Set('layout', $this->layout);
198
199 Kit::ClassLoader('campaign');
200@@ -398,6 +399,12 @@
201 'text' => __('Schedule Now')
202 );
203
204+ $row['buttons'][] = array(
205+ 'id' => 'layout_button_preview',
206+ 'url' => 'index.php?p=preview&q=render&ajax=true&layoutid=' . $layout['layoutid'],
207+ 'text' => __('Preview Layout')
208+ );
209+
210 // Only proceed if we have edit permissions
211 if ($layout['edit']) {
212
213@@ -545,7 +552,7 @@
214 $bgImageInfo = explode('.', $backgroundImage);
215 $bgImageId = $bgImageInfo[0];
216
217- $thumbBgImage = "index.php?p=module&q=GetImage&id=$bgImageId&width=80&height=80&dynamic";
218+ $thumbBgImage = "index.php?p=module&mod=image&q=Exec&method=GetResource&mediaid=$bgImageId&width=80&height=80&dynamic";
219 }
220 else
221 {
222@@ -685,7 +692,7 @@
223 $bgImageInfo = explode('.', $bgImage);
224 $bgImageId = $bgImageInfo[0];
225
226- $background_css = "url('index.php?p=module&q=GetImage&id=$bgImageId&width=$width&height=$height&dynamic&proportional=0') top center no-repeat; background-color:$bgColor";
227+ $background_css = "url('index.php?p=module&mod=image&q=Exec&method=GetResource&mediaid=$bgImageId&width=$width&height=$height&dynamic&proportional=0') top center no-repeat; background-color:$bgColor";
228 }
229
230 $width = $width . "px";
231
232=== modified file 'server/lib/pages/module.class.php'
233--- server/lib/pages/module.class.php 2014-01-18 09:47:41 +0000
234+++ server/lib/pages/module.class.php 2014-02-12 21:14:59 +0000
235@@ -271,82 +271,5 @@
236 $response->Respond();
237 }
238 }
239-
240- /**
241- * Returns an image stream to the browser - for the mediafile specified.
242- * @return
243- */
244- function GetImage()
245- {
246- $db =& $this->db;
247-
248- $mediaID = Kit::GetParam('id', _GET, _INT, 0);
249- $proportional = Kit::GetParam('proportional', _GET, _BOOL, true);
250- $thumb = Kit::GetParam('thumb', _GET, _BOOL, false);
251- $dynamic = isset($_REQUEST['dynamic']);
252-
253- if ($mediaID == 0)
254- die ('No media ID provided');
255-
256- // Get the file URI
257- $SQL = sprintf("SELECT StoredAs FROM media WHERE MediaID = %d", $mediaID);
258-
259- if (!$file = $db->GetSingleValue($SQL, 'StoredAs', _STRING))
260- die ('No media found for that media ID');
261-
262- //File upload directory.. get this from the settings object
263- $library = Config::GetSetting("LIBRARY_LOCATION");
264- $fileName = $library . $file;
265-
266- // If we are a thumb request then output the cached thumbnail
267- if ($thumb)
268- $fileName = $library . 'tn_' . $file;
269-
270- // If the thumbnail doesnt exist then create one
271- if (!file_exists($fileName))
272- {
273- Debug::LogEntry('audit', 'File doesnt exist, creating a thumbnail for ' . $fileName);
274-
275- if (!$info = getimagesize($library . $file))
276- die($library . $file . ' is not an image');
277-
278- ResizeImage($library . $file, $fileName, 80, 80, $proportional, 'file');
279- }
280-
281- // Get the info for this new temporary file
282- if (!$info = getimagesize($fileName))
283- {
284- echo $fileName . ' is not an image';
285- exit;
286- }
287-
288- if ($dynamic && $info[2])
289- {
290- $width = Kit::GetParam('width', _GET, _INT);
291- $height = Kit::GetParam('height', _GET, _INT);
292-
293- // dynamically create an image of the correct size - used for previews
294- ResizeImage($fileName, '', $width, $height, $proportional, 'browser');
295-
296- exit;
297- }
298-
299- if (!$image = file_get_contents($fileName))
300- {
301- //not sure
302- Debug::LogEntry('audit', "Cant find: $uid", 'module', 'GetImage');
303-
304- $fileName = 'theme/default/img/forms/filenotfound.png';
305- $image = file_get_contents($fileName);
306- }
307-
308- $size = getimagesize($fileName);
309-
310- //Output the image header
311- header("Content-type: {$size['mime']}");
312-
313- echo $image;
314- exit;
315- }
316 }
317-?>
318\ No newline at end of file
319+?>
320
321=== added file 'server/lib/pages/preview.class.php'
322--- server/lib/pages/preview.class.php 1970-01-01 00:00:00 +0000
323+++ server/lib/pages/preview.class.php 2014-02-12 21:14:59 +0000
324@@ -0,0 +1,125 @@
325+<?php
326+/*
327+ * Xibo - Digital Signage - http://www.xibo.org.uk
328+ * Copyright (C) 2014 Alex Harrington
329+ *
330+ * This file is part of Xibo.
331+ *
332+ * Xibo is free software: you can redistribute it and/or modify
333+ * it under the terms of the GNU Affero General Public License as published by
334+ * the Free Software Foundation, either version 3 of the License, or
335+ * any later version.
336+ *
337+ * Xibo is distributed in the hope that it will be useful,
338+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
339+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
340+ * GNU Affero General Public License for more details.
341+ *
342+ * You should have received a copy of the GNU Affero General Public License
343+ * along with Xibo. If not, see <http://www.gnu.org/licenses/>.
344+ */
345+defined('XIBO') or die("Sorry, you are not allowed to directly access this page.<br /> Please press the back button in your browser.");
346+
347+class previewDAO
348+{
349+ private $db;
350+ private $user;
351+ private $auth;
352+ private $has_permissions = true;
353+
354+ private $layoutid;
355+ private $layout;
356+ private $retired;
357+ private $description;
358+ private $tags;
359+
360+ private $xml;
361+
362+ function __construct(database $db, user $user)
363+ {
364+ $this->db =& $db;
365+ $this->user =& $user;
366+ $this->layoutid = Kit::GetParam('layoutid', _REQUEST, _INT);
367+
368+ // Include the layout data class
369+ include_once("lib/data/layout.data.class.php");
370+
371+ //if we have modify selected then we need to get some info
372+ if ($this->layoutid != '')
373+ {
374+ // get the permissions
375+ Debug::LogEntry('audit', 'Loading permissions for layoutid ' . $this->layoutid);
376+
377+ $this->auth = $user->LayoutAuth($this->layoutid, true);
378+
379+ if (!$this->auth->view)
380+ trigger_error(__("You do not have permissions to view this layout"), E_USER_ERROR);
381+
382+ $sql = " SELECT layout, description, userid, retired, tags, xml FROM layout ";
383+ $sql .= sprintf(" WHERE layoutID = %d ", $this->layoutid);
384+
385+ if (!$results = $db->query($sql))
386+ {
387+ trigger_error($db->error());
388+ trigger_error(__("Cannot retrieve the Information relating to this layout. The layout may be corrupt."), E_USER_ERROR);
389+ }
390+
391+ if ($db->num_rows($results) == 0)
392+ $this->has_permissions = false;
393+
394+ while($aRow = $db->get_row($results))
395+ {
396+ $this->layout = Kit::ValidateParam($aRow[0], _STRING);
397+ $this->description = Kit::ValidateParam($aRow[1], _STRING);
398+ $this->retired = Kit::ValidateParam($aRow[3], _INT);
399+ $this->tags = Kit::ValidateParam($aRow[4], _STRING);
400+ $this->xml = $aRow[5];
401+ }
402+ }
403+ }
404+
405+ function displayPage()
406+ {
407+ return false;
408+ }
409+
410+ function render()
411+ {
412+ // Render a specific layout in the previewer
413+ // layoutid must be provided
414+ $output = <<<EOT
415+ <!DOCTYPE html>
416+ <html>
417+ <head>
418+ <meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
419+ <title>Preview for Layout $this->layoutid</title>
420+ <link rel="stylesheet" type="text/css" href="modules/preview/html-preview.css" />
421+ <script type="text/JavaScript" src="theme/default/libraries/jquery/jquery-1.9.1.js"></script>
422+ <script type="text/JavaScript" src="modules/preview/html5Preloader.js"></script>
423+ <script type="text/JavaScript" src="modules/preview/html-preview.js"></script>
424+ </head>
425+ <body onload="dsInit($this->layoutid)">
426+ <div id="player">
427+ <div id="info"></div>
428+ <div id="log"></div>
429+ <div id="screen">
430+ <div id="splash">
431+ <div id="loader"></div>
432+ <div id="loaderCaption"><p>Loading layout...</p></div>
433+ </div>
434+ <div id="end"><a href="javascript:history.go(0)" style="text-decoration: none; color: #ffffff">Play again?</a></div>
435+ </div>
436+
437+ </div>
438+ </body>
439+ </html>
440+EOT;
441+ print $output;
442+ }
443+
444+ function getXlf()
445+ {
446+ print $this->xml;
447+ }
448+}
449+?>
450
451=== modified file 'server/modules/flash.module.php'
452--- server/modules/flash.module.php 2014-01-18 09:47:41 +0000
453+++ server/modules/flash.module.php 2014-02-12 21:14:59 +0000
454@@ -113,5 +113,16 @@
455 // Client dependant
456 return 2;
457 }
458+
459+ /**
460+ * Get Resource
461+ */
462+ public function GetResource($displayId = 0)
463+ {
464+ $this->ReturnFile();
465+
466+ exit();
467+ }
468+
469 }
470-?>
471\ No newline at end of file
472+?>
473
474=== modified file 'server/modules/image.module.php'
475--- server/modules/image.module.php 2014-01-18 09:47:41 +0000
476+++ server/modules/image.module.php 2014-02-12 21:14:59 +0000
477@@ -115,7 +115,7 @@
478 return parent::Preview ($width, $height);
479
480 // Show the image - scaled to the aspect ratio of this region (get from GET)
481- return sprintf('<div style="text-align:center;"><img src="index.php?p=module&q=GetImage&id=%d&width=%d&height=%d&dynamic" /></div>', $this->mediaid, $width, $height);
482+ return sprintf('<div style="text-align:center;"><img src="index.php?p=module&mod=image&q=Exec&method=GetResource&mediaid=%d&width=%d&height=%d&dynamic=true" /></div>', $this->mediaid, $width, $height);
483 }
484
485 public function HoverPreview()
486@@ -123,10 +123,71 @@
487 // Default Hover window contains a thumbnail, media type and duration
488 $output = parent::HoverPreview();
489 $output .= '<div class="hoverPreview">';
490- $output .= ' <img src="index.php?p=module&q=GetImage&id=' . $this->mediaid . '&width=200&height=200&dynamic=true" alt="Hover Preview">';
491+ $output .= ' <img src="index.php?p=module&mod=image&q=Exec&method=GetResource&mediaid=' . $this->mediaid . '&width=200&height=200&dynamic=true" alt="Hover Preview">';
492 $output .= '</div>';
493
494 return $output;
495 }
496+
497+ /**
498+ * Get Resource
499+ */
500+ public function GetResource($displayId = 0)
501+ {
502+ $proportional = Kit::GetParam('proportional', _GET, _BOOL, true);
503+ $thumb = Kit::GetParam('thumb', _GET, _BOOL, false);
504+ $dynamic = isset($_REQUEST['dynamic']);
505+ $file = $this->storedAs;
506+
507+ //File upload directory.. get this from the settings object
508+ $library = Config::GetSetting("LIBRARY_LOCATION");
509+ $fileName = $library . $file;
510+
511+ // If we are a thumb request then output the cached thumbnail
512+ if ($thumb)
513+ $fileName = $library . 'tn_' . $file;
514+
515+ // If the thumbnail doesnt exist then create one
516+ if (!file_exists($fileName))
517+ {
518+ Debug::LogEntry('audit', 'File doesnt exist, creating a thumbnail for ' . $fileName);
519+
520+ if (!$info = getimagesize($library . $file))
521+ die($library . $file . ' is not an image');
522+
523+ ResizeImage($library . $file, $fileName, 80, 80, $proportional, 'file');
524+ }
525+
526+ // Get the info for this new temporary file
527+ if (!$info = getimagesize($fileName))
528+ {
529+ echo $fileName . ' is not an image';
530+ exit;
531+ }
532+
533+ if ($dynamic && $info[2])
534+ {
535+ $width = Kit::GetParam('width', _GET, _INT);
536+ $height = Kit::GetParam('height', _GET, _INT);
537+
538+ // dynamically create an image of the correct size - used for previews
539+ ResizeImage($fileName, '', $width, $height, $proportional, 'browser');
540+
541+ exit;
542+ }
543+
544+ if (!file_exists($fileName))
545+ {
546+ //not sure
547+ Debug::LogEntry('audit', "Cant find: $uid", 'module', 'GetResource');
548+
549+ $fileName = 'theme/default/img/forms/filenotfound.png';
550+ }
551+
552+ $this->ReturnFile($fileName);
553+
554+ exit();
555+
556+ }
557 }
558-?>
559\ No newline at end of file
560+?>
561
562=== added file 'server/modules/preview/html-preview.css'
563--- server/modules/preview/html-preview.css 1970-01-01 00:00:00 +0000
564+++ server/modules/preview/html-preview.css 2014-02-12 21:14:59 +0000
565@@ -0,0 +1,92 @@
566+/* Make the player fill the screen */
567+html, body {
568+ height: 100%;
569+ margin: 0;
570+ padding: 0;
571+ /* cursor: none; */
572+}
573+
574+div {
575+ margin: 0;
576+ padding: 0;
577+}
578+
579+/* Make the screen div fill the player */
580+div#player {
581+ height: 100%;
582+ z-index: 1;
583+ background-color: #000000;
584+}
585+
586+div#screen {
587+ height: 100%;
588+ z-index: 1;
589+}
590+
591+/* Style the Splash Screen */
592+div#splash {
593+ height: 100%;
594+ background:#000000 url('../../theme/default/img/xibologo.png') no-repeat center center;
595+ background-size: 50%;
596+}
597+
598+div#loader {
599+ height: 19px;
600+ width: 220px;
601+ position: relative;
602+ margin:0 auto;
603+ top: 80%;
604+ background:#000000 url('loader.gif') no-repeat center center;
605+ background-size: 100%;
606+}
607+
608+div#loaderCaption p {
609+ position: absolute;
610+ font-family: sans-serif;
611+ color: #ffffff;
612+ text-align:center;
613+ top: 85%;
614+ width: 100%;
615+}
616+
617+div#log {
618+ height: 20px;
619+ background:#ffffff;
620+ position:absolute;
621+ bottom:0;
622+ width:100%;
623+ font-family: sans-serif;
624+ opacity:0.4;
625+ z-index: 100;
626+}
627+
628+div#info {
629+ position: absolute;
630+ height: 400px;
631+ width: 500px;
632+ background:#ffffff;
633+ font-family: sans-serif;
634+ opacity:0.6;
635+ margin:0 auto;
636+ z-index: 100;
637+}
638+
639+div#end {
640+ position: absolute;
641+ height: 100%;
642+ width: 100%;
643+ background:#000000;
644+ font-family: sans-serif;
645+ color:#ffffff;
646+ font-size: 300%;
647+ margin:0 auto;
648+ z-index: 100;
649+ text-align: center;
650+ vertical-align: middle;
651+ line-height: 400px;
652+}
653+
654+video {
655+ width: 100%;
656+ height: 100%;
657+}
658
659=== added file 'server/modules/preview/html-preview.js'
660--- server/modules/preview/html-preview.js 1970-01-01 00:00:00 +0000
661+++ server/modules/preview/html-preview.js 2014-02-12 21:14:59 +0000
662@@ -0,0 +1,499 @@
663+/*
664+ * Xibo - Digital Signage - http://www.xibo.org.uk
665+ * Copyright (C) 2014 Alex Harrington
666+ *
667+ * This file is part of Xibo.
668+ *
669+ * Xibo is free software: you can redistribute it and/or modify
670+ * it under the terms of the GNU Affero General Public License as published by
671+ * the Free Software Foundation, either version 3 of the License, or
672+ * any later version.
673+ *
674+ * Xibo is distributed in the hope that it will be useful,
675+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
676+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
677+ * GNU Affero General Public License for more details.
678+ *
679+ * You should have received a copy of the GNU Affero General Public License
680+ * along with Xibo. If not, see <http://www.gnu.org/licenses/>.
681+ */
682+
683+/* Int: Current logging level */
684+var LOG_LEVEL;
685+
686+/* String: Client Version */
687+var VERSION = "1.6.0";
688+
689+/* Int: Counter to ensure unique IDs */
690+var ID_COUNTER = 0;
691+
692+var PRELOAD;
693+
694+function dsInit(layoutid) {
695+ LOG_LEVEL = 10;
696+
697+ /* Hide the info and log divs */
698+ $("#log").css("display", "none");
699+ $("#info").css("display", "none");
700+ $("#end").css("display", "none");
701+
702+ /* Setup a keypress handler for local commands */
703+ document.onkeypress = keyHandler;
704+
705+ playLog(0, "info", "Xibo HTML Preview v" + VERSION + " Starting Up", true);
706+
707+ PRELOAD = html5Preloader();
708+ runningLayout = new layout(layoutid);
709+}
710+
711+/* Generate a unique ID for region DIVs, media nodes etc */
712+function nextId() {
713+ if (ID_COUNTER > 500) {
714+ ID_COUNTER = 0;
715+ }
716+
717+ ID_COUNTER = ID_COUNTER + 1;
718+ return ID_COUNTER;
719+}
720+
721+/* OnScreen Log */
722+function playLog(logLevel, logClass, logMessage, logToScreen) {
723+ if (logLevel <= LOG_LEVEL)
724+ {
725+ var msg = timestamp() + " " + logClass.toUpperCase() + ": " + logMessage;
726+ console.log(msg);
727+
728+ if (logToScreen) {
729+ document.getElementById("log").innerHTML = msg;
730+ }
731+ }
732+}
733+
734+/* Timestamp Function for Logs */
735+function timestamp() {
736+ var str = "";
737+
738+ var currentTime = new Date()
739+ var day = currentTime.getDate()
740+ var month = currentTime.getMonth() + 1
741+ var year = currentTime.getFullYear()
742+ var hours = currentTime.getHours()
743+ var minutes = currentTime.getMinutes()
744+ var seconds = currentTime.getSeconds()
745+
746+ if (minutes < 10) {
747+ minutes = "0" + minutes
748+ }
749+ if (seconds < 10) {
750+ seconds = "0" + seconds
751+ }
752+ str += day + "/" + month + "/" + year + " ";
753+ str += hours + ":" + minutes + ":" + seconds;
754+ return str;
755+}
756+
757+/* Function to handle key presses */
758+function keyHandler(event) {
759+ var chCode = ('charCode' in event) ? event.charCode : event.keyCode;
760+ var letter = String.fromCharCode(chCode);
761+
762+ if (letter == 'l') {
763+ if ($("#log").css("display") == 'none') {
764+ $("#log").css("display", "block");
765+ }
766+ else {
767+ $("#log").css("display", "none");
768+ }
769+ }
770+ else if (letter == 'i') {
771+ if ($("#info").css("display") == 'none') {
772+ /* Position the info div */
773+ sw = $("#screen").width();
774+ sh = $("#screen").height();
775+
776+ x = Math.round((sw - 500) / 2);
777+ y = Math.round((sh - 400) / 2);
778+
779+ if (x > 0) {
780+ $("#info").css("left", x);
781+ }
782+
783+ if (y > 0) {
784+ $("#info").css("top", y);
785+ }
786+
787+ /* Make info div visible */
788+ $("#info").css("display", "block");
789+ }
790+ else {
791+ $("#info").css("display", "none");
792+ }
793+ }
794+}
795+
796+function layout(id) {
797+ /* Layout Object */
798+ /* Parses a layout and when run runs it in containerName */
799+
800+ var self = this;
801+
802+ self.parseXlf = function(data) {
803+ playLog(10, "debug", "Parsing Layout " + self.id, false);
804+ self.containerName = "L" + self.id + "-" + nextId();
805+
806+ /* Create a hidden div to show the layout in */
807+ $("#screen").append('<div id="' + self.containerName + '"></div>');
808+ $("#" + self.containerName).css("display", "none");
809+ $("#" + self.containerName).css("outline", "red solid thin");
810+
811+ /* Calculate the screen size */
812+ self.sw = $("#screen").width();
813+ self.sh = $("#screen").height();
814+ playLog(7, "debug", "Screen is (" + self.sw + "x" + self.sh + ") pixels");
815+
816+ /* Find the Layout node in the XLF */
817+ self.layoutNode = data;
818+
819+ /* Get Layout Size */
820+ self.xw = $(self.layoutNode).filter(":first").attr('width');
821+ self.xh = $(self.layoutNode).filter(":first").attr('height');
822+ playLog(7, "debug", "Layout is (" + self.xw + "x" + self.xh + ") pixels");
823+
824+ /* Calculate Scale Factor */
825+ self.scaleFactor = Math.min((self.sw/self.xw), (self.sh/self.xh));
826+ self.sWidth = Math.round(self.xw * self.scaleFactor);
827+ self.sHeight = Math.round(self.xh * self.scaleFactor);
828+ self.offsetX = Math.abs(self.sw - self.sWidth) / 2;
829+ self.offsetY = Math.abs(self.sh - self.sHeight) / 2;
830+ playLog(7, "debug", "Scale Factor is " + self.scaleFactor);
831+ playLog(7, "debug", "Render will be (" + self.sWidth + "x" + self.sHeight + ") pixels");
832+ playLog(7, "debug", "Offset will be (" + self.offsetX + "," + self.offsetY + ") pixels");
833+
834+ /* Scale the Layout Container */
835+ $("#" + self.containerName).css("width", self.sWidth + "px");
836+ $("#" + self.containerName).css("height", self.sHeight + "px");
837+ $("#" + self.containerName).css("position", "absolute");
838+ $("#" + self.containerName).css("left", self.offsetX + "px");
839+ $("#" + self.containerName).css("top", self.offsetY + "px");
840+
841+ /* Set the layout background */
842+ self.bgColour = $(self.layoutNode).filter(":first").attr('bgcolor');
843+ self.bgImage = $(self.layoutNode).filter(":first").attr('background');
844+
845+ $("#" + self.containerName).css("background-color", self.bgColour);
846+
847+ if (!(self.bgImage == "" || self.bgImage == undefined)) {
848+ /* Extract the image ID from the filename */
849+ self.bgId = self.bgImage.substring(0, self.bgImage.indexOf('.'));
850+
851+ PRELOAD.addFiles("index.php?p=module&mod=image&q=Exec&method=GetResource&mediaid=" + self.bgId + "&width=" + self.sWidth + "&height=" + self.sHeight + "&dynamic&proportional=0");
852+ $("#" + self.containerName).css("background", "url('index.php?p=module&mod=image&q=Exec&method=GetResource&mediaid=" + self.bgId + "&width=" + self.sWidth + "&height=" + self.sHeight + "&dynamic&proportional=0')");
853+ $("#" + self.containerName).css("background-repeat", "no-repeat");
854+ $("#" + self.containerName).css("background-size", self.sWidth + "px " + self.sHeight + "px");
855+ $("#" + self.containerName).css("background-position", "0px 0px");
856+ }
857+
858+ $(self.layoutNode).find("region").each(function() { playLog(4, "debug", "Creating region " + $(this).attr('id'), false);
859+ self.regionObjects.push(new region(self, $(this).attr('id'), this));
860+ });
861+ playLog(4, "debug", "Layout " + self.id + " has " + self.regionObjects.length + " regions");
862+ self.ready = true;
863+ PRELOAD.on('finish', self.run);
864+ }
865+
866+ self.run = function() {
867+ playLog(4, "debug", "Running Layout ID " + self.id, false);
868+ if (self.ready) {
869+ $("#" + self.containerName).css("display", "block");
870+ $("#splash").css("display", "none");
871+ for (var i = 0; i < self.regionObjects.length; i++) {
872+ playLog(4, "debug", "Running region " + self.regionObjects[i].id, false);
873+ self.regionObjects[i].run();
874+ }
875+ }
876+ else {
877+ playLog(4, "error", "Attempted to run Layout ID " + self.id + " before it was ready.", false);
878+ }
879+ }
880+
881+ self.end = function() {
882+ /* Ask the layout to gracefully stop running now */
883+ for (var i = 0; i < self.regionObjects.length; i++) {
884+ self.regionObjects[i].end();
885+ }
886+ }
887+
888+ self.destroy = function() {
889+ /* Forcibly remove the layout and destroy this object
890+ Layout Object may not be reused after this */
891+ }
892+
893+ self.regionExpired = function() {
894+ /* One of the regions on the layout expired
895+ Check if all the regions have expired, and if they did
896+ end the layout */
897+ playLog(5, "debug", "A region expired. Checking if all regions have expired.", false);
898+
899+ self.allExpired = true;
900+
901+ for (var i = 0; i < self.regionObjects.length; i++) {
902+ playLog(4, "debug", "Region " + self.regionObjects[i].id + ": " + self.regionObjects[i].complete, false);
903+ if (! self.regionObjects[i].complete) {
904+ self.allExpired = false;
905+ }
906+ }
907+
908+ if (self.allExpired) {
909+ playLog(4, "debug", "All regions have expired", false);
910+ self.end();
911+ }
912+ }
913+
914+ self.regionEnded = function() {
915+ /* One of the regions completed it's exit transition
916+ Check al the regions have completed exit transitions.
917+ If they did, bring on the next layout */
918+
919+ playLog(5, "debug", "A region ended. Checking if all regions have ended.", false);
920+
921+ self.allEnded = true;
922+
923+ for (var i = 0; i < self.regionObjects.length; i++) {
924+ playLog(4, "debug", "Region " + self.regionObjects[i].id + ": " + self.regionObjects[i].ended, false);
925+ if (! self.regionObjects[i].ended) {
926+ self.allEnded = false;
927+ }
928+ }
929+
930+ if (self.allEnded) {
931+ playLog(4, "debug", "All regions have ended", false);
932+ $("#end").css("display", "block");
933+ $("#" + self.containerName).remove();
934+ }
935+
936+ }
937+
938+ self.ready = false;
939+ self.id = id;
940+ self.regionObjects = new Array();
941+
942+ playLog(3, "debug", "Loading Layout " + self.id , true);
943+
944+ $.get('index.php', {p: 'preview', q: 'getXlf', layoutid: self.id, ajax: 'true'}, self.parseXlf);
945+}
946+
947+function region(parent, id, xml) {
948+ var self = this;
949+ self.layout = parent;
950+ self.id = id;
951+ self.xml = xml;
952+ self.mediaObjects = new Array();
953+ self.currentMedia = -1;
954+ self.complete = false;
955+ self.containerName = "R-" + self.id + "-" + nextId();
956+ self.ending = false;
957+ self.ended = false;
958+ self.oneMedia = false;
959+ self.oldMedia = undefined;
960+ self.curMedia = undefined;
961+
962+ self.finished = function() {
963+ self.complete = true;
964+ self.layout.regionExpired()
965+ }
966+
967+ self.exitTransition = function() {
968+ /* TODO: Actually implement region exit transitions */
969+ $("#" + self.containerName).css("display", "none");
970+ self.exitTransitionComplete();
971+ }
972+
973+ self.end = function() {
974+ self.ending = true;
975+ /* The Layout has finished running */
976+ /* Do any region exit transition then clean up */
977+ self.exitTransition();
978+ }
979+
980+ self.exitTransitionComplete = function() {
981+ self.ended = true;
982+ self.layout.regionEnded();
983+ }
984+
985+ self.transitionNodes = function(oldMedia, newMedia) {
986+ /* TODO: Actually support the transition */
987+
988+ if (oldMedia == newMedia) {
989+ return;
990+ }
991+
992+ newMedia.run();
993+
994+ if (oldMedia) {
995+ $("#" + oldMedia.containerName).css("display", "none");
996+ }
997+
998+ $("#" + newMedia.containerName).css("display", "block");
999+ }
1000+
1001+ self.nextMedia = function() {
1002+ /* The current media has finished running */
1003+ /* Show the next item */
1004+
1005+ if (self.ended) {
1006+ return;
1007+ }
1008+
1009+ if (self.curMedia) {
1010+ playLog(8, "debug", "nextMedia -> Old: " + self.curMedia.id);
1011+ self.oldMedia = self.curMedia;
1012+ }
1013+ else {
1014+ self.oldMedia = undefined;
1015+ }
1016+
1017+ self.currentMedia = self.currentMedia + 1;
1018+
1019+ if (self.currentMedia >= self.mediaObjects.length) {
1020+ self.finished();
1021+ self.currentMedia = 0;
1022+ }
1023+
1024+ playLog(8, "debug", "nextMedia -> Next up is media " + (self.currentMedia + 1) + " of " + self.mediaObjects.length);
1025+
1026+ self.curMedia = self.mediaObjects[self.currentMedia];
1027+
1028+ playLog(8, "debug", "nextMedia -> New: " + self.curMedia.id);
1029+
1030+ /* Do the transition */
1031+ self.transitionNodes(self.oldMedia, self.curMedia);
1032+ }
1033+
1034+ self.run = function() {
1035+ self.nextMedia();
1036+ }
1037+
1038+ self.sWidth = $(xml).attr("width") * self.layout.scaleFactor;
1039+ self.sHeight = $(xml).attr("height") * self.layout.scaleFactor;
1040+ self.offsetX = $(xml).attr("left") * self.layout.scaleFactor;
1041+ self.offsetY = $(xml).attr("top") * self.layout.scaleFactor;
1042+
1043+ $("#" + self.layout.containerName).append('<div id="' + self.containerName + '"></div>');
1044+
1045+ /* Scale the Layout Container */
1046+ $("#" + self.containerName).css("width", self.sWidth + "px");
1047+ $("#" + self.containerName).css("height", self.sHeight + "px");
1048+ $("#" + self.containerName).css("position", "absolute");
1049+ $("#" + self.containerName).css("left", self.offsetX + "px");
1050+ $("#" + self.containerName).css("top", self.offsetY + "px");
1051+
1052+ playLog(4, "debug", "Created region " + self.id, false);
1053+ playLog(7, "debug", "Render will be (" + self.sWidth + "x" + self.sHeight + ") pixels");
1054+ playLog(7, "debug", "Offset will be (" + self.offsetX + "," + self.offsetY + ") pixels");
1055+
1056+ $(self.xml).find("media").each(function() { playLog(5, "debug", "Creating media " + $(this).attr('id'), false);
1057+ self.mediaObjects.push(new media(self, $(this).attr('id'), this));
1058+ });
1059+
1060+ playLog(4, "debug", "Region " + self.id + " has " + self.mediaObjects.length + " media items");
1061+}
1062+
1063+function media(parent, id, xml) {
1064+ var self = this;
1065+ self.region = parent
1066+ self.xml = xml
1067+ self.id = id
1068+ self.containerName = "M-" + self.id + "-" + nextId();
1069+
1070+ self.run = function() {
1071+ playLog(5, "debug", "Running media " + self.id + " for " + self.duration + " seconds")
1072+
1073+ if (($(self.xml).attr('type') == "video")) {
1074+ $("#" + self.containerName + "-vid").get(0).play();
1075+ }
1076+
1077+ if (self.duration == 0) {
1078+ if (($(self.xml).attr('type') == "video")) {
1079+ $("#" + self.containerName + "-vid").bind("ended", self.region.nextMedia);
1080+ }
1081+ else {
1082+ self.duration = 10;
1083+ setTimeout(self.region.nextMedia, self.duration * 1000);
1084+ }
1085+ }
1086+ else {
1087+ setTimeout(self.region.nextMedia, self.duration * 1000);
1088+ }
1089+ }
1090+
1091+ self.divWidth = self.region.sWidth;
1092+ self.divHeight = self.region.sHeight;
1093+
1094+ /* Build Media Options */
1095+ self.duration = $(self.xml).attr('duration');
1096+ self.lkid = $(self.xml).attr('lkid');
1097+ self.options = new Array();
1098+
1099+ $(self.xml).find('options').children().each(function() { playLog(9, "debug", "Option " + this.nodeName.toLowerCase() + " -> " + $(this).text(), false);
1100+ self.options[this.nodeName.toLowerCase()] = $(this).text();
1101+ });
1102+
1103+ $("#" + self.region.containerName).append('<div id="' + self.containerName + '"></div>');
1104+
1105+ /* Scale the Content Container */
1106+ $("#" + self.containerName).css("display", "none");
1107+ $("#" + self.containerName).css("width", self.divWidth + "px");
1108+ $("#" + self.containerName).css("height", self.divHeight + "px");
1109+ $("#" + self.containerName).css("position", "absolute");
1110+ /* $("#" + self.containerName).css("left", self.offsetX + "px");
1111+ $("#" + self.containerName).css("top", self.offsetY + "px"); */
1112+
1113+ if ($(self.xml).attr('type') == "image") {
1114+ var tmpUrl = "index.php?p=module&mod=image&q=Exec&method=GetResource&layoutid=" + self.region.layout.id + "&regionid=" + self.region.id + "&mediaid=" + self.id + "&lkid=" + self.lkid;
1115+ PRELOAD.addFiles(tmpUrl);
1116+ $("#" + self.containerName).css("background-image", "url('" + tmpUrl + "')");
1117+ }
1118+ else if (($(self.xml).attr('type') == "text")) {
1119+ $("#" + self.containerName).append('<iframe scrolling="no" id="innerIframe" src="index.php?p=module&mod=text&q=Exec&method=GetResource&raw=true&preview=true&layoutid=' + self.region.layout.id + '&regionid=' + self.region.id + '&mediaid=' + self.id + '&lkid=&width=' + self.divWidth + '&height=' + self.divHeight + '" width="' + self.divWidth + 'px" height="' + self.divHeight + 'px" style="border:0;"></iframe>');
1120+ }
1121+ else if (($(self.xml).attr('type') == "ticker")) {
1122+ $("#" + self.containerName).append('<iframe scrolling="no" id="innerIframe" src="index.php?p=module&mod=ticker&q=Exec&method=GetResource&raw=true&preview=true&layoutid=' + self.region.layout.id + '&regionid=' + self.region.id + '&mediaid=' + self.id + '&lkid=&width=' + self.divWidth + '&height=' + self.divHeight + '" width="' + self.divWidth + 'px" height="' + self.divHeight + 'px" style="border:0;"></iframe>');
1123+ /* Check if the ticker duration is based on the number of items in the feed */
1124+ if (self.options['durationisperitem'] == '1') {
1125+ var regex = new RegExp("<!-- NUMITEMS=(.*?) -->");
1126+ jQuery.ajax({
1127+ url: 'index.php?p=module&mod=ticker&q=Exec&method=GetResource&raw=true&preview=true&layoutid=' + self.region.layout.id + '&regionid=' + self.region.id + '&mediaid=' + self.id + '&lkid=&width=' + self.divWidth + '&height=' + self.divHeight,
1128+ success: function(html) {
1129+ res = regex.exec(html);
1130+ if (res != null) {
1131+ /* The ticker is duration per item, so multiply the duration
1132+ by the number of items from the feed */
1133+ self.duration = parseInt(self.duration) * parseInt(res[1]);
1134+ }
1135+ },
1136+ async:false
1137+ });
1138+ }
1139+ }
1140+ else if (($(self.xml).attr('type') == "video")) {
1141+ var tmpUrl = "index.php?p=module&mod=video&q=Exec&method=GetResource&layoutid=" + self.region.layout.id + "&regionid=" + self.region.id + "&mediaid=" + self.id + "&lkid=" + self.lkid;
1142+ PRELOAD.addFiles(tmpUrl);
1143+ $("#" + self.containerName).append('<video id="' + self.containerName + '-vid" preload="auto"><source src="' + tmpUrl + '"></video>');
1144+ }
1145+ else if (($(self.xml).attr('type') == "flash")) {
1146+ var tmpUrl = "index.php?p=module&mod=flash&q=Exec&method=GetResource&layoutid=" + self.region.layout.id + "&regionid=" + self.region.id + "&mediaid=" + self.id + "&lkid=" + self.lkid;
1147+ var embedCode = '<OBJECT classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,0,0" WIDTH="100%" HEIGHT="100%" id="Yourfilename" ALIGN="">';
1148+ embedCode = embedCode + '<PARAM NAME=movie VALUE="' + tmpUrl + '"> <PARAM NAME=quality VALUE=high> <param name="wmode" value="transparent"> <EMBED src="' + tmpUrl + '" quality="high" wmode="transparent" WIDTH="100%" HEIGHT="100%" NAME="Yourfilename" ALIGN="" TYPE="application/x-shockwave-flash" PLUGINSPAGE="http://www.macromedia.com/go/getflashplayer"></EMBED> </OBJECT>';
1149+ PRELOAD.addFiles(tmpUrl);
1150+ $("#" + self.containerName).append(embedCode);
1151+ }
1152+ else {
1153+ $("#" + self.containerName).css("outline", "red solid thin");
1154+ }
1155+ $("#" + self.containerName).css("background-size", "contain");
1156+ $("#" + self.containerName).css("background-repeat", "no-repeat");
1157+ $("#" + self.containerName).css("background-position", "center");
1158+
1159+ playLog(5, "debug", "Created media " + self.id)
1160+}
1161+
1162
1163=== added file 'server/modules/preview/html5Preloader.js'
1164--- server/modules/preview/html5Preloader.js 1970-01-01 00:00:00 +0000
1165+++ server/modules/preview/html5Preloader.js 2014-02-12 21:14:59 +0000
1166@@ -0,0 +1,448 @@
1167+var html5Preloader = (function () {
1168+
1169+var XHR = typeof XMLHttpRequest === 'undefined' ? function () { // IE FIX
1170+ try {
1171+ return new ActiveXObject("Msxml2.XMLHTTP.6.0");
1172+ } catch (err1) {}
1173+ try {
1174+ return new ActiveXObject("Msxml2.XMLHTTP.3.0");
1175+ } catch (err2) {}
1176+
1177+ return null;
1178+ } : XMLHttpRequest,
1179+ AudioElement = typeof Audio !== 'undefined' ? // IE FIX
1180+ function(){
1181+ return new Audio();
1182+ } :
1183+ function(){
1184+ return document.createElement('audio');
1185+ },
1186+ VideoElement = typeof Video !== 'undefined' ? // IE FIX
1187+ function () {
1188+ return new Video();
1189+ } :
1190+ function () {
1191+ return document.createElement('video');
1192+ },
1193+ ImageElement = function () {
1194+ return new Image();
1195+ },
1196+ codecs = { // Chart from jPlayer
1197+ oga: { // OGG
1198+ codec: 'audio/ogg; codecs="vorbis"',
1199+ media: 'audio'
1200+ },
1201+ wav: { // PCM
1202+ codec: 'audio/wav; codecs="1"',
1203+ media: 'audio'
1204+ },
1205+ webma: { // WEBM
1206+ codec: 'audio/webm; codecs="vorbis"',
1207+ media: 'audio'
1208+ },
1209+ mp3: {
1210+ codec: 'audio/mpeg; codecs="mp3"',
1211+ media: 'audio'
1212+ },
1213+ m4a: { // AAC / MP4
1214+ codec: 'audio/mp4; codecs="mp4a.40.2"',
1215+ media: 'audio'
1216+ },
1217+ ogv: { // OGG
1218+ codec: 'video/ogg; codecs="theora, vorbis"',
1219+ media: 'video'
1220+ },
1221+ webmv: { // WEBM
1222+ codec: 'video/webm; codecs="vorbis, vp8"',
1223+ media: 'video'
1224+ },
1225+ m4v: { // H.264 / MP4
1226+ codec: 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"',
1227+ media: 'video'
1228+ }
1229+ },
1230+ support = {
1231+ imageTypes: ['jpg', 'png', 'jpeg', 'tiff', 'gif']
1232+ },
1233+ ID_PREFIX = 'FILE@';
1234+/* :) may fail sometimes, but these are the most common cases */
1235+codecs.ogg = codecs.oga;
1236+codecs.mp4 = codecs.m4v;
1237+codecs.webm = codecs.webmv;
1238+
1239+function isIn (needle, haystack) {
1240+ for (var i=0; i<haystack.length; i++) {
1241+ if (haystack[i] === needle) {
1242+ return true;
1243+ }
1244+ }
1245+
1246+ return false;
1247+}
1248+
1249+function map (arr, callback) {
1250+ if (arr.map) {
1251+ return arr.map(callback);
1252+ }
1253+
1254+ var r = [],
1255+ i;
1256+ for (i=0; i<arr.length; i++) {
1257+ r.push(callback(arr[i]));
1258+ }
1259+
1260+ return r;
1261+}
1262+
1263+function bind (func, self) {
1264+ return func.bind ? func.bind(self) : function () {
1265+ return func.apply(self, arguments);
1266+ };
1267+}
1268+
1269+function delay (callback) {
1270+ var args = [].slice.call(arguments, 1);
1271+ setTimeout(function () {
1272+ callback.apply(this, args);
1273+ }, 0);
1274+}
1275+
1276+function EventEmitter () {
1277+ var k;
1278+ for (k in EventEmitter.prototype) {
1279+ if (EventEmitter.prototype.hasOwnProperty(k)) {
1280+ this[k] = EventEmitter.prototype[k];
1281+ }
1282+ }
1283+ this._listeners = {};
1284+};
1285+
1286+EventEmitter.prototype = {
1287+ _listeners: null,
1288+
1289+ emit: function (name, args) {
1290+ args = args || [];
1291+ if (this._listeners[name]) {
1292+ for (var i=0; i<this._listeners[name].length; i++) {
1293+ this._listeners[name][i].apply(this, args);
1294+ }
1295+ }
1296+ return this;
1297+ },
1298+
1299+ on: function (name, listener) {
1300+ this._listeners[name] = this._listeners[name] || [];
1301+ this._listeners[name].push(listener);
1302+ return this;
1303+ },
1304+
1305+ off: function (name, listener) {
1306+ if (this._listeners[name]) {
1307+ if (!listener) {
1308+ delete this._listeners[name];
1309+ return this;
1310+ }
1311+ for (var i=0; i<this._listeners[name].length; i++) {
1312+ if (this._listeners[name][i] === listener) {
1313+ this._listeners[name].splice(i--, 1);
1314+ }
1315+ }
1316+ this._listeners[name].length || delete this._listeners[name];
1317+ }
1318+ return this;
1319+ },
1320+
1321+ once: function (name, listener) {
1322+ function ev () {
1323+ this.off(ev);
1324+ return listener.apply(this, arguments);
1325+ }
1326+
1327+ return this.on(name, ev);
1328+ }
1329+};
1330+
1331+function loadFile (file, callback, timeout) {
1332+ if (!(this instanceof loadFile)) {
1333+ return new loadFile(file, callback, timeout);
1334+ }
1335+
1336+ var self = this,
1337+ alternates = [],
1338+ a, b, c, t;
1339+
1340+ if (typeof file === 'string') {
1341+ a = file.split('*:');
1342+ b = a[ a[1] ? 1 : 0 ].split('||');
1343+ self.id = a[1] ? a[0] : b[0];
1344+ self.alternates = alternates;
1345+
1346+ for (a=0; a<b.length; a++) {
1347+ c = b[a].split('.');
1348+ c = c[c.length - 1].toLowerCase();
1349+
1350+ t = codecs[c] ? codecs[c].media : isIn(c, support.imageTypes) ? 'image' : 'document';
1351+
1352+ if (codecs[c] && !codecs[c].supported) {
1353+ continue;
1354+ }
1355+
1356+ alternates.push({
1357+ type: t,
1358+ path: b[a]
1359+ });
1360+ }
1361+
1362+ alternates.length || alternates.push({
1363+ type: t,
1364+ path: b[a-1]
1365+ });
1366+ } else {
1367+ delay(callback, TypeError('Invalid path'), self);
1368+ return;
1369+ }
1370+
1371+ function loadNext() {
1372+ var file = alternates.shift(),
1373+ _timeoutTimer = null;
1374+
1375+ if (!file) {
1376+ delay(callback, {e: Error('No viable alternatives')}, null);
1377+ return;
1378+ }
1379+
1380+ if (typeof timeout === 'number') {
1381+ _timeoutTimer = setTimeout(function() {
1382+ delay(callback, {e: Error('Load event not fired within ' + timeout + 'ms')}, self);
1383+ }, timeout);
1384+ }
1385+
1386+ new loadFile[file.type](
1387+ file.path,
1388+ function (e, f) {
1389+
1390+ _timeoutTimer && clearTimeout(_timeoutTimer);
1391+
1392+ self.dom = f && f.dom;
1393+
1394+ if (e && self.alternates.length) {
1395+ return loadNext();
1396+ }
1397+
1398+ callback(e, self);
1399+ });
1400+ }
1401+
1402+ loadNext();
1403+}
1404+
1405+function MediaFile (construct) {
1406+ return function (filename, callback) {
1407+ var self = this,
1408+ file = construct();
1409+
1410+ function onready () {
1411+ file.onload = file.onerror = null;
1412+ file.removeEventListener && file.removeEventListener('canplaythrough', onready, true);
1413+
1414+ callback(null, self);
1415+ }
1416+
1417+ file.addEventListener && file.addEventListener('canplaythrough', onready, true);
1418+ file.onload = onready;
1419+ file.onerror = function (e) {
1420+ callback(e, self);
1421+ };
1422+
1423+ self.dom = file;
1424+ file.src = filename;
1425+
1426+ file.load && file.load();
1427+ };
1428+}
1429+
1430+loadFile.audio = MediaFile(AudioElement);
1431+loadFile.video = MediaFile(VideoElement);
1432+loadFile.image = MediaFile(ImageElement);
1433+
1434+loadFile.document = function (file, callback) {
1435+ var self = this,
1436+ parsedUrl = /(\[(!)?(.+)?\])?$/.exec(file),
1437+ mimeType = parsedUrl[3],
1438+ xhr = self.dom = new XHR();
1439+
1440+ if (!xhr) {
1441+ delay(callback, Error('No XHR!'), self);
1442+ return;
1443+ }
1444+
1445+ file = file.substr(0, file.length - parsedUrl[0].length);
1446+ file += parsedUrl[2] ? (file.indexOf('?') === -1 ? '?' : '&') + 'fobarz=' + (+new Date) : '';
1447+
1448+ mimeType && xhr.overrideMimeType(mimeType === '@' ? 'text/plain; charset=x-user-defined' : mimeType);
1449+
1450+ xhr.onreadystatechange = function () {
1451+ if (xhr.readyState !== 4) return;
1452+
1453+ try {
1454+ self.dom = xhr.responseXML && xhr.responseXML.documentElement ?
1455+ xhr.responseXML :
1456+ String(xhr.responseText || '') ;
1457+
1458+ xhr.status === 200 ?
1459+ callback(null, self) :
1460+ callback({e: Error('Request failed: ' + xhr.status)}, self) ;
1461+ } catch (e) {
1462+ callback({e: e}, self);
1463+ }
1464+ };
1465+
1466+ xhr.onerror = function (e) {
1467+ callback(e, self);
1468+ };
1469+
1470+ xhr.open('GET', file, true);
1471+ xhr.send();
1472+};
1473+
1474+(function () {
1475+ var dummyAudio = AudioElement(),
1476+ dummyVideo = VideoElement(),
1477+ i;
1478+
1479+ support.audio = !!dummyAudio.canPlayType;
1480+ support.video = !!dummyVideo.canPlayType;
1481+
1482+ support.audioTypes = [];
1483+ support.videoTypes = [];
1484+
1485+ for (i in codecs) {
1486+ if (codecs.hasOwnProperty(i)) {
1487+ if (codecs[i].media === 'video') {
1488+ (codecs[i].supported = support.video &&
1489+ dummyVideo.canPlayType(codecs[i].codec)) &&
1490+ support.videoTypes.push(i);
1491+ } else if (codecs[i].media === 'audio') {
1492+ (codecs[i].supported = support.audio &&
1493+ dummyAudio.canPlayType(codecs[i].codec)) &&
1494+ support.audioTypes.push(i);
1495+ }
1496+ }
1497+ }
1498+}());
1499+
1500+if (!support.audio) {
1501+ loadFile.audio = function (a, callback) {
1502+ delay(callback, Error('<AUDIO> not supported.'), a);
1503+ };
1504+}
1505+if (!support.video) {
1506+ loadFile.video = function (a, callback) {
1507+ delay(callback, Error('<VIDEO> not supported.'), a);
1508+ };
1509+}
1510+
1511+function html5Preloader () {
1512+ var self = this,
1513+ args = arguments;
1514+
1515+ if (!(self instanceof html5Preloader)) {
1516+ self = new html5Preloader();
1517+ args.length && self.loadFiles.apply(self, args);
1518+ return self;
1519+ }
1520+
1521+ self.files = [];
1522+
1523+ html5Preloader.EventEmitter.call(self);
1524+
1525+ self.loadCallback = bind(self.loadCallback, self);
1526+
1527+ args.length && self.loadFiles.apply(self, args);
1528+}
1529+
1530+html5Preloader.prototype = {
1531+ active: false,
1532+ files: null,
1533+ filesLoading: 0,
1534+ filesLoaded: 0,
1535+ filesLoadedMap: {},
1536+ timeout: null,
1537+
1538+ loadCallback: function (e, f) {
1539+
1540+ if (!this.filesLoadedMap[f.id]) {
1541+ this.filesLoaded++;
1542+ this.filesLoadedMap[f.id] = f;
1543+ }
1544+
1545+ this.emit(e ? 'error' : 'fileloaded', e ? [e, f] : [f]);
1546+
1547+ if (this.filesLoading - this.filesLoaded === 0) {
1548+ this.active = false;
1549+ this.emit('finish');
1550+ this.filesLoading = 0;
1551+ this.filesLoaded = 0;
1552+ }
1553+ },
1554+
1555+ getFile: function (id) {
1556+ return typeof id === 'undefined' ? map(this.files, function (f) {
1557+ return f.dom;
1558+ }) :
1559+ typeof id === 'number' ? this.files[id].dom :
1560+ typeof id === 'string' ? this.files[ID_PREFIX + id].dom :
1561+ null;
1562+ },
1563+
1564+ removeFile: function (id) {
1565+ var f, i;
1566+ switch (typeof id) {
1567+ case 'undefined':
1568+ this.files = [];
1569+ break;
1570+ case 'number':
1571+ f = this.files[id];
1572+ this.files[ID_PREFIX + f.id] && delete this.files[ID_PREFIX + f.id];
1573+ this.files.splice(id, 1);
1574+ break;
1575+ case 'string':
1576+ f = this.files[ID_PREFIX + id];
1577+ f && delete this.files[ID_PREFIX + id];
1578+
1579+ for (i=0; i<this.files.length; i++) {
1580+ this.files[i] === f && this.files.splice(i--, 1);
1581+ }
1582+ }
1583+ },
1584+
1585+ loadFiles: function () {
1586+ var files = [].slice.call(arguments),
1587+ i, f;
1588+
1589+ for (i=0; i<files.length; i++) {
1590+ f = html5Preloader.loadFile(files[i], this.loadCallback, this.timeout);
1591+ this.files.push(f);
1592+ this.files[ID_PREFIX + f.id] = f;
1593+ this.filesLoading++;
1594+ }
1595+
1596+ this.active = this.active || !!this.filesLoading;
1597+ },
1598+
1599+ addFiles: function (list) {
1600+ return this.loadFiles.apply(this, list instanceof Array ? list : arguments);
1601+ },
1602+
1603+ getProgress: function () {
1604+ return this.filesLoading ? this.filesLoaded / this.filesLoading : 1.0;
1605+ }
1606+};
1607+
1608+html5Preloader.support = support;
1609+html5Preloader.loadFile = loadFile;
1610+html5Preloader.EventEmitter = EventEmitter;
1611+
1612+return html5Preloader;
1613+
1614+}());
1615
1616=== added file 'server/modules/preview/loader.gif'
1617Binary files server/modules/preview/loader.gif 1970-01-01 00:00:00 +0000 and server/modules/preview/loader.gif 2014-02-12 21:14:59 +0000 differ
1618=== modified file 'server/modules/video.module.php'
1619--- server/modules/video.module.php 2014-01-18 09:47:41 +0000
1620+++ server/modules/video.module.php 2014-02-12 21:14:59 +0000
1621@@ -108,5 +108,16 @@
1622 {
1623 return $this->EditLibraryMedia();
1624 }
1625+
1626+ /**
1627+ * Get Resource
1628+ */
1629+ public function GetResource($displayId = 0)
1630+ {
1631+ $this->ReturnFile();
1632+
1633+ exit();
1634+
1635+ }
1636 }
1637-?>
1638\ No newline at end of file
1639+?>
1640
1641=== modified file 'server/theme/default/html/layout_designer.php'
1642--- server/theme/default/html/layout_designer.php 2014-01-18 09:47:41 +0000
1643+++ server/theme/default/html/layout_designer.php 2014-02-12 21:14:59 +0000
1644@@ -45,6 +45,7 @@
1645 <li><a class="XiboFormButton" href="<?php echo Theme::Get('layout_form_edit_background_url'); ?>" title="<?php echo Theme::Translate('Background'); ?>"><span><?php echo Theme::Translate('Background'); ?></span></a></li>
1646 <li><a class="XiboFormButton" href="<?php echo Theme::Get('layout_form_edit_url'); ?>" title="<?php echo Theme::Translate('Edit the Layout Properties'); ?>"><span><?php echo Theme::Translate('Properties'); ?></span></a></li>
1647 <li class="divider"></li>
1648+ <li><a href="<?php echo Theme::Get('layout_form_preview_url'); ?>" title="<?php echo Theme::Translate('Preview Layout'); ?>" target="_blank"><span><?php echo Theme::Translate('Preview Layout'); ?></span></a></li>
1649 <li><a class="XiboFormButton" href="<?php echo Theme::Get('layout_form_schedulenow_url'); ?>" title="<?php echo Theme::Translate('Schedule Now'); ?>"><span><?php echo Theme::Translate('Schedule Now'); ?></span></a></li>
1650 <li><a class="XiboFormButton" href="<?php echo Theme::Get('layout_form_savetemplate_url'); ?>" title="<?php echo Theme::Translate('Save Template'); ?>"><span><?php echo Theme::Translate('Save Template'); ?></span></a></li>
1651 </ul>
1652@@ -85,4 +86,4 @@
1653 <div class="XiboData"></div>
1654 </div>
1655 </div>
1656-</div>
1657\ No newline at end of file
1658+</div>
1659
1660=== modified file 'server/theme/default/html/layout_page_grid.php'
1661--- server/theme/default/html/layout_page_grid.php 2014-02-08 14:55:15 +0000
1662+++ server/theme/default/html/layout_page_grid.php 2014-02-12 21:14:59 +0000
1663@@ -59,7 +59,7 @@
1664 </button>
1665 <ul class="dropdown-menu">
1666 <?php foreach($row['buttons'] as $button) { ?>
1667- <li class="<?php echo (($button['id'] == 'layout_button_design') ? 'XiboRedirectButton' : 'XiboFormButton'); ?>" href="<?php echo $button['url']; ?>"><a tabindex="-1" href="#"><?php echo $button['text']; ?></a></li>
1668+ <li class="<?php echo ((($button['id'] == 'layout_button_design') or ($button['id'] == 'layout_button_preview')) ? 'XiboRedirectButton' : 'XiboFormButton'); ?>" href="<?php echo $button['url']; ?>" <?php echo (($button['id'] == 'layout_button_preview') ? ' target="_blank"' : ''); ?> ><a tabindex="-1" href="#"><?php echo $button['text']; ?></a></li>
1669 <?php } ?>
1670 </ul>
1671 </div>
1672@@ -67,4 +67,4 @@
1673 </tr>
1674 <?php } ?>
1675 </tbody>
1676-</table>
1677\ No newline at end of file
1678+</table>
1679
1680=== modified file 'server/theme/default/html/library_page_grid.php'
1681--- server/theme/default/html/library_page_grid.php 2014-02-08 14:55:15 +0000
1682+++ server/theme/default/html/library_page_grid.php 2014-02-12 21:14:59 +0000
1683@@ -68,8 +68,18 @@
1684 <span class="icon-tasks"></span>
1685 </button>
1686 <ul class="dropdown-menu">
1687- <?php foreach($row['buttons'] as $button) { ?>
1688- <li class="XiboFormButton" href="<?php echo $button['url']; ?>"><a tabindex="-1" href="#"><?php echo $button['text']; ?></a></li>
1689+ <?php foreach($row['buttons'] as $button) {
1690+ if ($button['id'] == 'content_button_download') {
1691+ ?>
1692+ <li><a tabindex="-1" href="<?php echo $button['url']; ?>"><?php echo $button['text']; ?></a></li>
1693+ <?php
1694+ }
1695+ else {
1696+ ?>
1697+ <li class="XiboFormButton" href="<?php echo $button['url']; ?>"><a tabindex="-1" href="#"><?php echo $button['text']; ?></a></li>
1698+ <?php
1699+ } ?>
1700+
1701 <?php } ?>
1702 </ul>
1703 </div>
1704@@ -77,4 +87,4 @@
1705 </tr>
1706 <?php } ?>
1707 </tbody>
1708-</table>
1709\ No newline at end of file
1710+</table>
1711
1712=== modified file 'server/theme/default/js/xibo-layout-designer.js'
1713--- server/theme/default/js/xibo-layout-designer.js 2014-01-18 09:47:41 +0000
1714+++ server/theme/default/js/xibo-layout-designer.js 2014-02-12 21:14:59 +0000
1715@@ -310,5 +310,5 @@
1716 //Want to attach an onchange event to the drop down for the bg-image
1717 var id = $('#bg_image').val();
1718
1719- $('#bg_image_image').attr("src", "index.php?p=module&q=GetImage&id=" + id + "&width=80&height=80&dynamic");
1720-}
1721\ No newline at end of file
1722+ $('#bg_image_image').attr("src", "index.php?p=module&mod=image&q=Exec&method=GetResource&mediaid=" + id + "&width=80&height=80&dynamic");
1723+}

Subscribers

People subscribed via source and target branches