Merge lp:~dangarner/xibo/server170 into lp:xibo/1.7

Proposed by Dan Garner
Status: Merged
Merged at revision: 355
Proposed branch: lp:~dangarner/xibo/server170
Merge into: lp:xibo/1.7
Diff against target: 6714 lines (+3283/-1421)
69 files modified
manual/generate.php (+4/-164)
manual/manualgenerator.class.php (+211/-0)
manual/source/en/install_environment.md (+7/-139)
manual/source/en/install_environment_linux.md (+93/-0)
manual/source/en/install_environment_windows_iis.md (+195/-0)
manual/source/en/install_environment_windows_xampp.md (+47/-0)
manual/source/en/release_notes.md (+1/-0)
manual/source/en/release_notes_1.7.0-rc1.md (+53/-0)
manual/source/fr/tour.md (+65/-0)
manual/template/template.php (+21/-1)
server/config/client.config.php (+24/-2)
server/install/database/60.sql (+13/-0)
server/install/database/80.sql (+1/-1)
server/install/database/81.sql (+1/-1)
server/install/database/83.sql (+5/-0)
server/install/master/data.sql (+3/-3)
server/install/master/structure.sql (+7/-7)
server/lib/app/kit.class.php (+58/-0)
server/lib/app/thememanager.class.php (+2/-2)
server/lib/data/dataset.data.class.php (+27/-10)
server/lib/data/datasetdata.data.class.php (+2/-2)
server/lib/data/displayprofile.data.class.php (+2/-2)
server/lib/data/layout.data.class.php (+8/-2)
server/lib/data/media.data.class.php (+1/-6)
server/lib/data/nonce.data.class.php (+11/-0)
server/lib/data/userdata.data.class.php (+85/-0)
server/lib/include.php (+1/-1)
server/lib/modules/module.class.php (+47/-12)
server/lib/pages/admin.class.php (+4/-7)
server/lib/pages/content.class.php (+53/-48)
server/lib/pages/dataset.class.php (+11/-1)
server/lib/pages/display.class.php (+25/-16)
server/lib/pages/layout.class.php (+8/-3)
server/lib/pages/log.class.php (+2/-0)
server/lib/pages/module.class.php (+40/-30)
server/lib/pages/stats.class.php (+27/-3)
server/lib/pages/timeline.class.php (+9/-2)
server/lib/pages/user.class.php (+22/-35)
server/lib/service/service_v3.wsdl (+0/-9)
server/lib/service/xmdssoap3.class.php (+566/-599)
server/lib/service/xmdssoap4.class.php (+39/-31)
server/modules/3rdparty/twitter-oauth/OAuth.php (+872/-0)
server/modules/3rdparty/twitter-oauth/twitteroauth.php (+243/-0)
server/modules/clock.module.php (+0/-19)
server/modules/datasetview.module.php (+0/-8)
server/modules/embedded.module.php (+0/-14)
server/modules/forecastio.module.php (+43/-37)
server/modules/image.module.php (+1/-1)
server/modules/module_template.php (+2/-8)
server/modules/module_user_general.php (+41/-0)
server/modules/text.module.php (+0/-14)
server/modules/theme/forecastio/current-day.template.json (+7/-0)
server/modules/theme/forecastio/daily.template.json (+7/-0)
server/modules/theme/forecastio/picture.template.json (+7/-0)
server/modules/theme/ticker/media-rss-with-title.template.json (+6/-0)
server/modules/theme/ticker/prominent-title-with-desc-and-name-separator.template.json (+6/-0)
server/modules/theme/ticker/title-only.template.json (+6/-0)
server/modules/theme/twitter/tweet-only.template.json (+6/-0)
server/modules/theme/twitter/tweet-with-profileimage-left.template.json (+6/-0)
server/modules/theme/twitter/tweet-with-profileimage-right.template.json (+6/-0)
server/modules/ticker.module.php (+55/-41)
server/modules/twitter.module.php (+152/-53)
server/modules/webpage.module.php (+0/-13)
server/theme/default/config.php (+0/-1)
server/theme/default/html/display_form_mediainventory.php (+0/-52)
server/theme/default/html/library_form_media_add.php (+11/-4)
server/theme/default/html/schedule_page.php (+1/-1)
server/theme/default/js/xibo-forms.js (+0/-12)
server/theme/default/libraries/jquery-file-upload/js/jquery.fileupload-ui.js (+4/-4)
To merge this branch: bzr merge lp:~dangarner/xibo/server170
Reviewer Review Type Date Requested Status
Xibo Maintainters Pending
Review via email: mp+245939@code.launchpad.net
To post a comment you must log in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'manual/generate.php'
2--- manual/generate.php 2014-11-06 17:57:03 +0000
3+++ manual/generate.php 2015-01-09 11:27:10 +0000
4@@ -1,170 +1,10 @@
5 <?php
6 include_once('libraries/parsedown/parsedown.php');
7+include_once('manualgenerator.class.php');
8 include_once('template/template.php');
9
10-$OUTPUT_ROOT = 'output/';
11-
12 // Generates a complete manual.
13-
14-// Copy the bootstrap, jquery folders and the img folder
15-if (!is_dir($OUTPUT_ROOT . 'libraries'))
16- mkdir($OUTPUT_ROOT . 'libraries');
17-
18-xcopy('libraries/bootstrap', $OUTPUT_ROOT . 'libraries/bootstrap');
19-xcopy('libraries/jquery', $OUTPUT_ROOT . 'libraries/jquery');
20-xcopy('template/img', $OUTPUT_ROOT . 'img');
21-xcopy('template/manual.css', $OUTPUT_ROOT . 'manual.css');
22-
23-$languages = array();
24-
25-// Get a list of folders
26-foreach (array_diff(scandir('source'), array('..', '.')) as $langDir) {
27-
28- if (is_dir('source/' . $langDir)) {
29- echo 'Found ' . $langDir . PHP_EOL;
30-
31- // Make sure our output folder is empty
32- if (is_dir($OUTPUT_ROOT . $langDir)) {
33- delete($OUTPUT_ROOT . $langDir);
34- sleep(3);
35- }
36-
37- mkdir($OUTPUT_ROOT . $langDir);
38-
39- // Make sure a full suite of images is present.
40- xcopy('source/en/img', $OUTPUT_ROOT . $langDir . '/img');
41-
42- // And layer in any language specific replacements
43- if (is_dir('source/' . $langDir . '/img'))
44- xcopy('source/' . $langDir . '/img', $OUTPUT_ROOT . $langDir . '/img');
45-
46- if ($langDir != 'en')
47- $languages[] = $langDir;
48- }
49-}
50-
51-// Build a langs string
52-$langsString = '<a href="../en/index.html">en</a>';
53-foreach ($languages as $lang) {
54- $langsString .= ' | <a href="../' . $lang . '/index.html">' . $lang . '</a>';
55-}
56-
57-// Scan files in the EN folder:
58-foreach (array_diff(scandir('source/en'), array('..', '.')) as $file) {
59- if (stripos($file, '.md')) {
60- // Process each file in turn
61- processFile($langsString, $OUTPUT_ROOT, 'en', str_replace('.md', '', $file));
62-
63- // Process for the other languages.
64- foreach ($languages as $lang) {
65- processFile($langsString, $OUTPUT_ROOT, $lang, str_replace('.md', '', $file));
66- }
67- }
68-}
69-
70-echo PHP_EOL;
71-
72-function processFile($langs, $folder, $lang, $file) {
73-
74- echo '.';
75- flush();
76-
77- // Get the page content
78- $pageContent = Parsedown::instance()->text(processReplacements($lang, file_get_contents_or_default($lang, $file . '.md')));
79- $toc = strtok($pageContent, "\n");
80- $toc = str_replace('-->', '', str_replace('<!--toc=', '', $toc));
81-
82- // Header
83- $string = processReplacements($lang, file_get_contents('template/header.html'));
84- $string .= processReplacements($lang, file_get_contents('template/footer.html'));
85-
86- $string = str_replace('[[TOCNAME]]', $toc, $string);
87- $string = str_replace('[[PAGE]]', $pageContent, $string);
88- $string = str_replace('[[NAVBAR]]', file_get_contents_or_default($lang, '/toc/nav_bar.html'), $string);
89-
90- // Handle the TOC
91- $string = str_replace('[[TOC]]', Parsedown::instance()->text(file_get_contents_or_default($lang, '/toc/' . $toc . '.md')), $string);
92-
93- // Replace the languages
94- $string = str_replace('[[LANGS]]', $langs, $string);
95-
96- file_put_contents($folder . $lang . DIRECTORY_SEPARATOR . $file . '.html', $string);
97-}
98-
99-function file_get_contents_or_default($lang, $file) {
100- if (file_exists('source' . DIRECTORY_SEPARATOR . $lang . DIRECTORY_SEPARATOR . $file))
101- return file_get_contents('source' . DIRECTORY_SEPARATOR . $lang . DIRECTORY_SEPARATOR . $file);
102- else
103- return file_get_contents('source' . DIRECTORY_SEPARATOR . 'en' . DIRECTORY_SEPARATOR . $file);
104-}
105-
106-function processReplacements($lang, $string) {
107- // Replace the nav bar
108- $string = str_replace('[[PRODUCTNAME]]', PRODUCT_NAME, $string);
109- $string = str_replace('[[PRODUCTVERSION]]', PRODUCT_VERSION, $string);
110- $string = str_replace('[[PRODUCTHOME]]', PRODUCT_HOME, $string);
111- $string = str_replace('[[PRODUCTSUPPORTURL]]', PRODUCT_SUPPORT_URL, $string);
112- $string = str_replace('[[PRODUCTFAQURL]]', PRODUCT_FAQ_URL, $string);
113-
114- return $string;
115-}
116-
117-/**
118- * Copy a file, or recursively copy a folder and its contents
119- * @param string $source Source path
120- * @param string $dest Destination path
121- * @param string $permissions New folder creation permissions
122- * @return bool Returns true on success, false on failure
123- */
124-function xcopy($source, $dest, $permissions = 0755)
125-{
126- // Check for symlinks
127- if (is_link($source)) {
128- return symlink(readlink($source), $dest);
129- }
130-
131- // Simple copy for a file
132- if (is_file($source)) {
133- return copy($source, $dest);
134- }
135-
136- // Make destination directory
137- if (!is_dir($dest)) {
138- mkdir($dest, $permissions);
139- }
140-
141- // Loop through the folder
142- $dir = dir($source);
143- while (false !== $entry = $dir->read()) {
144- // Skip pointers
145- if ($entry == '.' || $entry == '..') {
146- continue;
147- }
148-
149- // Deep copy directories
150- xcopy("$source/$entry", "$dest/$entry");
151- }
152-
153- // Clean up
154- $dir->close();
155- return true;
156-}
157-
158-function delete($path)
159-{
160- if (is_dir($path) === true) {
161- $files = array_diff(scandir($path), array('.', '..'));
162-
163- foreach ($files as $file) {
164- delete(realpath($path) . '/' . $file);
165- }
166-
167- return rmdir($path);
168- }
169- else if (is_file($path) === true) {
170- return unlink($path);
171- }
172-
173- return false;
174-}
175+$manual = new ManualGenerator(PRODUCT_NAME, PRODUCT_VERSION, PRODUCT_HOME, PRODUCT_SUPPORT_URL, PRODUCT_FAQ_URL);
176+$manual->build('', 'output/');
177+
178 ?>
179
180=== added file 'manual/manualgenerator.class.php'
181--- manual/manualgenerator.class.php 1970-01-01 00:00:00 +0000
182+++ manual/manualgenerator.class.php 2015-01-09 11:27:10 +0000
183@@ -0,0 +1,211 @@
184+<?php
185+/*
186+ * Xibo - Digital Signage - http://www.xibo.org.uk
187+ * Copyright (C) 2006-2014 Spring Signage Ltd
188+ *
189+ * This file is part of Xibo.
190+ *
191+ * Xibo is free software: you can redistribute it and/or modify
192+ * it under the terms of the GNU Affero General Public License as published by
193+ * the Free Software Foundation, either version 3 of the License, or
194+ * any later version.
195+ *
196+ * Xibo is distributed in the hope that it will be useful,
197+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
198+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
199+ * GNU Affero General Public License for more details.
200+ *
201+ * You should have received a copy of the GNU Affero General Public License
202+ * along with Xibo. If not, see <http://www.gnu.org/licenses/>.
203+ */
204+class ManualGenerator
205+{
206+ private $productName;
207+ private $productVersion;
208+ private $productHome;
209+ private $productSupportUrl;
210+ private $productFaqUrl;
211+
212+ private $sourcePath;
213+ private $outputPath;
214+
215+ public function __construct($productName, $productVersion, $productHome, $productSupportUrl, $productFaqUrl)
216+ {
217+ $this->productName = $productName;
218+ $this->productVersion = $productVersion;
219+ $this->productHome = $productHome;
220+ $this->productSupportUrl = $productSupportUrl;
221+ $this->productFaqUrl = $productFaqUrl;
222+ }
223+
224+ public function build($sourcePath, $outputPath)
225+ {
226+ $this->outputPath = $outputPath;
227+ $this->sourcePath = $sourcePath;
228+
229+ // Copy the bootstrap, jquery folders and the img folder
230+ if (!is_dir($this->outputPath . 'libraries'))
231+ mkdir($this->outputPath . 'libraries');
232+
233+ $this->xcopy($this->sourcePath . 'libraries/bootstrap', $this->outputPath . 'libraries/bootstrap');
234+ $this->xcopy($this->sourcePath . 'libraries/jquery', $this->outputPath . 'libraries/jquery');
235+ $this->xcopy($this->sourcePath . 'template/img', $this->outputPath . 'img');
236+ $this->xcopy($this->sourcePath . 'template/manual.css', $this->outputPath . 'manual.css');
237+
238+ $languages = array();
239+
240+ // Get a list of folders
241+ foreach (array_diff(scandir($this->sourcePath . 'source'), array('..', '.')) as $langDir) {
242+
243+ if (is_dir($this->sourcePath . 'source/' . $langDir)) {
244+ echo 'Found ' . $langDir . PHP_EOL;
245+
246+ // Make sure our output folder is empty
247+ if (is_dir($this->outputPath . $langDir)) {
248+ $this->delete($this->outputPath . $langDir);
249+ sleep(3);
250+ }
251+
252+ mkdir($this->outputPath . $langDir);
253+
254+ // Make sure a full suite of images is present.
255+ $this->xcopy($this->sourcePath . 'source/en/img', $this->outputPath . $langDir . '/img');
256+
257+ // And layer in any language specific replacements
258+ if (is_dir($this->sourcePath . 'source/' . $langDir . '/img'))
259+ $this->xcopy($this->sourcePath . 'source/' . $langDir . '/img', $this->outputPath . $langDir . '/img');
260+
261+ if ($langDir != 'en')
262+ $languages[] = $langDir;
263+ }
264+ }
265+
266+ // Build a langs string
267+ $langsString = '<a href="../en/index.html">en</a>';
268+ foreach ($languages as $lang) {
269+ $langsString .= ' | <a href="../' . $lang . '/index.html">' . $lang . '</a>';
270+ }
271+
272+ // Scan files in the EN folder:
273+ foreach (array_diff(scandir($this->sourcePath . 'source/en'), array('..', '.')) as $file) {
274+ if (stripos($file, '.md')) {
275+ // Process each file in turn
276+ $this->processFile($langsString, $this->outputPath, 'en', str_replace('.md', '', $file));
277+
278+ // Process for the other languages.
279+ foreach ($languages as $lang) {
280+ $this->processFile($langsString, $this->outputPath, $lang, str_replace('.md', '', $file));
281+ }
282+ }
283+ }
284+
285+ echo PHP_EOL;
286+ }
287+
288+ private function processFile($langs, $folder, $lang, $file)
289+ {
290+ echo '.';
291+ flush();
292+
293+ // Get the page content
294+ $pageContent = Parsedown::instance()->text($this->processReplacements($lang, $this->file_get_contents_or_default($lang, $file . '.md')));
295+ $toc = strtok($pageContent, "\n");
296+ $toc = str_replace('-->', '', str_replace('<!--toc=', '', $toc));
297+
298+ // Header
299+ $string = $this->processReplacements($lang, file_get_contents($this->sourcePath . 'template/header.html'));
300+ $string .= $this->processReplacements($lang, file_get_contents($this->sourcePath . 'template/footer.html'));
301+
302+ $string = str_replace('[[TOCNAME]]', $toc, $string);
303+ $string = str_replace('[[PAGE]]', $pageContent, $string);
304+ $string = str_replace('[[NAVBAR]]', $this->file_get_contents_or_default($lang, '/toc/nav_bar.html'), $string);
305+
306+ // Handle the TOC
307+ $string = str_replace('[[TOC]]', Parsedown::instance()->text($this->file_get_contents_or_default($lang, '/toc/' . $toc . '.md')), $string);
308+
309+ // Replace the languages
310+ $string = str_replace('[[LANGS]]', $langs, $string);
311+
312+ file_put_contents($folder . $lang . DIRECTORY_SEPARATOR . $file . '.html', $string);
313+ }
314+
315+ private function file_get_contents_or_default($lang, $file)
316+ {
317+ if (file_exists($this->sourcePath . 'source' . DIRECTORY_SEPARATOR . $lang . DIRECTORY_SEPARATOR . $file))
318+ return file_get_contents($this->sourcePath . 'source' . DIRECTORY_SEPARATOR . $lang . DIRECTORY_SEPARATOR . $file);
319+ else
320+ return file_get_contents($this->sourcePath . 'source' . DIRECTORY_SEPARATOR . 'en' . DIRECTORY_SEPARATOR . $file);
321+ }
322+
323+ private function processReplacements($lang, $string)
324+ {
325+ // Replace the nav bar
326+ $string = str_replace('[[PRODUCTNAME]]', $this->productName, $string);
327+ $string = str_replace('[[PRODUCTVERSION]]', $this->productVersion, $string);
328+ $string = str_replace('[[PRODUCTHOME]]', $this->productHome, $string);
329+ $string = str_replace('[[PRODUCTSUPPORTURL]]', $this->productSupportUrl, $string);
330+ $string = str_replace('[[PRODUCTFAQURL]]', $this->productFaqUrl, $string);
331+
332+ return $string;
333+ }
334+
335+ /**
336+ * Copy a file, or recursively copy a folder and its contents
337+ * @param string $source Source path
338+ * @param string $dest Destination path
339+ * @param string $permissions New folder creation permissions
340+ * @return bool Returns true on success, false on failure
341+ */
342+ function xcopy($source, $dest, $permissions = 0755)
343+ {
344+ // Check for symlinks
345+ if (is_link($source)) {
346+ return symlink(readlink($source), $dest);
347+ }
348+
349+ // Simple copy for a file
350+ if (is_file($source)) {
351+ return copy($source, $dest);
352+ }
353+
354+ // Make destination directory
355+ if (!is_dir($dest)) {
356+ mkdir($dest, $permissions);
357+ }
358+
359+ // Loop through the folder
360+ $dir = dir($source);
361+ while (false !== $entry = $dir->read()) {
362+ // Skip pointers
363+ if ($entry == '.' || $entry == '..') {
364+ continue;
365+ }
366+
367+ // Deep copy directories
368+ $this->xcopy("$source/$entry", "$dest/$entry");
369+ }
370+
371+ // Clean up
372+ $dir->close();
373+ return true;
374+ }
375+
376+ function delete($path)
377+ {
378+ if (is_dir($path) === true) {
379+ $files = array_diff(scandir($path), array('.', '..'));
380+
381+ foreach ($files as $file) {
382+ $this->delete(realpath($path) . '/' . $file);
383+ }
384+
385+ return rmdir($path);
386+ }
387+ else if (is_file($path) === true) {
388+ return unlink($path);
389+ }
390+
391+ return false;
392+ }
393+}
394+?>
395
396=== added file 'manual/source/en/img/install_environment_windows_cms_install_step1.png'
397Binary files manual/source/en/img/install_environment_windows_cms_install_step1.png 1970-01-01 00:00:00 +0000 and manual/source/en/img/install_environment_windows_cms_install_step1.png 2015-01-09 11:27:10 +0000 differ
398=== added file 'manual/source/en/img/install_environment_windows_cms_install_step2.png'
399Binary files manual/source/en/img/install_environment_windows_cms_install_step2.png 1970-01-01 00:00:00 +0000 and manual/source/en/img/install_environment_windows_cms_install_step2.png 2015-01-09 11:27:10 +0000 differ
400=== added file 'manual/source/en/img/install_environment_windows_cms_install_step3.png'
401Binary files manual/source/en/img/install_environment_windows_cms_install_step3.png 1970-01-01 00:00:00 +0000 and manual/source/en/img/install_environment_windows_cms_install_step3.png 2015-01-09 11:27:10 +0000 differ
402=== added file 'manual/source/en/img/install_environment_windows_iismgr_install.png'
403Binary files manual/source/en/img/install_environment_windows_iismgr_install.png 1970-01-01 00:00:00 +0000 and manual/source/en/img/install_environment_windows_iismgr_install.png 2015-01-09 11:27:10 +0000 differ
404=== added file 'manual/source/en/img/install_environment_windows_mysql_install_step1.png'
405Binary files manual/source/en/img/install_environment_windows_mysql_install_step1.png 1970-01-01 00:00:00 +0000 and manual/source/en/img/install_environment_windows_mysql_install_step1.png 2015-01-09 11:27:10 +0000 differ
406=== added file 'manual/source/en/img/install_environment_windows_mysql_install_step2.png'
407Binary files manual/source/en/img/install_environment_windows_mysql_install_step2.png 1970-01-01 00:00:00 +0000 and manual/source/en/img/install_environment_windows_mysql_install_step2.png 2015-01-09 11:27:10 +0000 differ
408=== added file 'manual/source/en/img/install_environment_windows_mysql_install_step3.png'
409Binary files manual/source/en/img/install_environment_windows_mysql_install_step3.png 1970-01-01 00:00:00 +0000 and manual/source/en/img/install_environment_windows_mysql_install_step3.png 2015-01-09 11:27:10 +0000 differ
410=== added file 'manual/source/en/img/install_environment_windows_mysql_install_step4.png'
411Binary files manual/source/en/img/install_environment_windows_mysql_install_step4.png 1970-01-01 00:00:00 +0000 and manual/source/en/img/install_environment_windows_mysql_install_step4.png 2015-01-09 11:27:10 +0000 differ
412=== added file 'manual/source/en/img/install_environment_windows_mysql_install_step5.png'
413Binary files manual/source/en/img/install_environment_windows_mysql_install_step5.png 1970-01-01 00:00:00 +0000 and manual/source/en/img/install_environment_windows_mysql_install_step5.png 2015-01-09 11:27:10 +0000 differ
414=== added file 'manual/source/en/img/install_environment_windows_mysql_install_step6.png'
415Binary files manual/source/en/img/install_environment_windows_mysql_install_step6.png 1970-01-01 00:00:00 +0000 and manual/source/en/img/install_environment_windows_mysql_install_step6.png 2015-01-09 11:27:10 +0000 differ
416=== added file 'manual/source/en/img/install_environment_windows_php_install_step1.png'
417Binary files manual/source/en/img/install_environment_windows_php_install_step1.png 1970-01-01 00:00:00 +0000 and manual/source/en/img/install_environment_windows_php_install_step1.png 2015-01-09 11:27:10 +0000 differ
418=== added file 'manual/source/en/img/install_environment_windows_php_install_step2.png'
419Binary files manual/source/en/img/install_environment_windows_php_install_step2.png 1970-01-01 00:00:00 +0000 and manual/source/en/img/install_environment_windows_php_install_step2.png 2015-01-09 11:27:10 +0000 differ
420=== added file 'manual/source/en/img/install_environment_windows_php_install_step3.png'
421Binary files manual/source/en/img/install_environment_windows_php_install_step3.png 1970-01-01 00:00:00 +0000 and manual/source/en/img/install_environment_windows_php_install_step3.png 2015-01-09 11:27:10 +0000 differ
422=== added file 'manual/source/en/img/install_environment_windows_php_install_step4.png'
423Binary files manual/source/en/img/install_environment_windows_php_install_step4.png 1970-01-01 00:00:00 +0000 and manual/source/en/img/install_environment_windows_php_install_step4.png 2015-01-09 11:27:10 +0000 differ
424=== added file 'manual/source/en/img/install_environment_windows_php_install_step5.png'
425Binary files manual/source/en/img/install_environment_windows_php_install_step5.png 1970-01-01 00:00:00 +0000 and manual/source/en/img/install_environment_windows_php_install_step5.png 2015-01-09 11:27:10 +0000 differ
426=== added file 'manual/source/en/img/install_environment_windows_php_install_step6.png'
427Binary files manual/source/en/img/install_environment_windows_php_install_step6.png 1970-01-01 00:00:00 +0000 and manual/source/en/img/install_environment_windows_php_install_step6.png 2015-01-09 11:27:10 +0000 differ
428=== added file 'manual/source/en/img/install_environment_windows_phpmyadmin_install_step1.png'
429Binary files manual/source/en/img/install_environment_windows_phpmyadmin_install_step1.png 1970-01-01 00:00:00 +0000 and manual/source/en/img/install_environment_windows_phpmyadmin_install_step1.png 2015-01-09 11:27:10 +0000 differ
430=== added file 'manual/source/en/img/install_environment_windows_phpmyadmin_install_step2.png'
431Binary files manual/source/en/img/install_environment_windows_phpmyadmin_install_step2.png 1970-01-01 00:00:00 +0000 and manual/source/en/img/install_environment_windows_phpmyadmin_install_step2.png 2015-01-09 11:27:10 +0000 differ
432=== added file 'manual/source/en/img/install_environment_windows_phpmyadmin_install_step3.png'
433Binary files manual/source/en/img/install_environment_windows_phpmyadmin_install_step3.png 1970-01-01 00:00:00 +0000 and manual/source/en/img/install_environment_windows_phpmyadmin_install_step3.png 2015-01-09 11:27:10 +0000 differ
434=== added file 'manual/source/en/img/install_environment_windows_phpmyadmin_install_step4.png'
435Binary files manual/source/en/img/install_environment_windows_phpmyadmin_install_step4.png 1970-01-01 00:00:00 +0000 and manual/source/en/img/install_environment_windows_phpmyadmin_install_step4.png 2015-01-09 11:27:10 +0000 differ
436=== added file 'manual/source/en/img/install_environment_windows_phpmyadmin_install_step5.png'
437Binary files manual/source/en/img/install_environment_windows_phpmyadmin_install_step5.png 1970-01-01 00:00:00 +0000 and manual/source/en/img/install_environment_windows_phpmyadmin_install_step5.png 2015-01-09 11:27:10 +0000 differ
438=== added file 'manual/source/en/img/install_environment_windows_phpmyadmin_install_step6.png'
439Binary files manual/source/en/img/install_environment_windows_phpmyadmin_install_step6.png 1970-01-01 00:00:00 +0000 and manual/source/en/img/install_environment_windows_phpmyadmin_install_step6.png 2015-01-09 11:27:10 +0000 differ
440=== added file 'manual/source/en/img/install_environment_windows_phpmyadmin_install_step7.png'
441Binary files manual/source/en/img/install_environment_windows_phpmyadmin_install_step7.png 1970-01-01 00:00:00 +0000 and manual/source/en/img/install_environment_windows_phpmyadmin_install_step7.png 2015-01-09 11:27:10 +0000 differ
442=== added file 'manual/source/en/img/install_environment_windows_wpi_install.png'
443Binary files manual/source/en/img/install_environment_windows_wpi_install.png 1970-01-01 00:00:00 +0000 and manual/source/en/img/install_environment_windows_wpi_install.png 2015-01-09 11:27:10 +0000 differ
444=== modified file 'manual/source/en/install_environment.md'
445--- manual/source/en/install_environment.md 2014-10-30 12:30:48 +0000
446+++ manual/source/en/install_environment.md 2015-01-09 11:27:10 +0000
447@@ -1,141 +1,9 @@
448 <!--toc=getting_started-->
449 # Choosing your Environment
450-
451-## <span id="Install_on_Linux"> Install on Linux </span>
452-
453-Linux makes a good home for a Xibo server. All of the software it needs to support it is provided by the major Linux distributions. We'll cover Ubuntu in detail here, as it's what I use. There are some notes further down for other distributions, however the whole procedure is very similar.
454-
455-### <span id="Setup_Apache_and_PHP"> Setup Apache and PHP </span>
456-
457-#### <span id="Ubuntu"> Ubuntu </span>
458-
459-* Install Apache 2.x Webserver
460-```
461- sudo apt-get install apache2
462-```
463-
464-* Install MySQL Server
465-```
466- sudo apt-get install mysql-server
467-```
468-
469-* Install PHP5&#160;:
470-```
471- sudo apt-get install php5 php5-mysql php5-gd
472-```
473-
474-_On Ubuntu, DOM, libXML, gettext and JSON extension is already compiled on PHP5_
475-
476-* Restart Apache 2 Webserver if necessary
477-```
478- sudo /etc/init.d/apache2 restart
479-```
480-
481-#### <span id="CentOS_5.x_.2F_Redhat_RHEL_5.x"> CentOS 5.x / Redhat RHEL 5.x </span>
482-
483-JSON extension is not include in CentOS 5.x or Redhat RHEL 5.x but you can install it with this steps&#160;:
484-
485-* Install JSON PHP Extension
486-```
487- yum install php-devel
488-```
489-
490-_If you have more than 8MB of memory limit for PHP, install with PEAR&#160;:_
491-
492-```
493- pear install pecl/json
494-```
495-
496-_If you have less than 8MB of memory limit for PHP, PEAR failed to install... Use PECL&#160;: _
497-
498-```
499- pecl install json
500-```
501-
502-* Active JSON extension&#160;:
503-```
504- vim /etc/php.d/json.ini
505-```
506-```
507- # Json Extension
508- extension=json.so
509-```
510-
511-* Save file and restart the Apache webserver&#160;:
512-```
513- /etc/init.d/httpd restart
514-```
515-
516-#### <span id="SuSe"> SuSe </span>
517-
518-* during web install library location says 'full path' but means relative to document root on suse linux
519-```
520-for me this is /srv/www/htdocs/xlib in the box for full path, i put "xlib"
521-```
522-
523-### <span id="Install_Xibo"> Install Xibo </span>
524-
525-* Extract the tarball you downloaded inside your webserver's document root (eg /var/www/xibo) and ensure the webserver has permissions to read and write those files:
526-```
527- cd /var/www
528- sudo tar zxvf ~/xibo-1.0.5-server.tar.gz
529- sudo mv xibo-1.0.5-server xibo
530- sudo chown www-data.www-data -R xibo
531-```
532-
533-* Make a directory for the server library. Make sure the webserver has permission to write to this location:
534-```
535- sudo mkdir /xibo-library
536- sudo chown www-data.www-data -R /xibo-library
537-```
538-
539-* You should now use a webbrowser to visit your webserver - eg [http://myserver/xibo](http://myserver/xibo)
540-
541-* The process is fairly self explanatory. Follow the final part of the [Windows instructions](install_cms.html) for greater detail.
542-
543-## <span id="Install_on_Windows"> Install on Windows </span>
544-
545-This Windows install guide is focused mainly on someone who wants to use a Windows PC to be the server as well as the client, or perhaps has a spare Windows XP machine to use as a server. The instructions should work equally well on Windows Server operating systems.
546-
547-Xibo should also run under IIS, however it's not a platform the Xibo development team currently tests with. If you know your way around IIS, then the latter half of this guide plus the "What you need to know!" section should get you going.
548-
549-### <span id="Install_the_Webserver_.28XAMPP.29"> Install the Webserver (XAMPP) </span>
550-
551-We'll be using XAMPP to install Apache, MySQL, PHP (amongst other things) to support Xibo. This is convenient as it provides the whole system in a single installer, and can be un-installed via Add/Remove Programs at a later date if required. If you already have XAMPP installed, you can skip to the next section.
552-
553-You can download XAMPP from [Apache Friends](http://www.apachefriends.org/en/xampp.html). You need to download the full XAMPP Installer package for Windows systems. Save it to the Desktop.
554-
555-Once XAMPP Installer has downloaded, double click on it to run it. You may be prompted to allow the installer to run as the publisher could not be verified. Click "Run".
556-
557-You should get to the start of the install wizard.
558-
559-![](img/install_environment_Win32_xampp_install_start.jpg)
560-
561-By default, XAMPP installs to "c:\xampp". Unless you need to move it somewhere else, then that's a good choice. If you do select a different directory, remember it for the next step when we install Xibo. Click Install.
562-The installer will now run, and extract a number of files. When it finishes, it will bring you to a command prompt.
563-
564-You'll now be asked a few questions about how XAMPP should install itself. At the following prompts, you may select all default options.
565-
566-![](img/install_environment_Capture-2.jpg)
567-![](img/install_environment_Capture-3.jpg)
568-![](img/install_environment_Capture-4.jpg)
569-![](img/install_environment_Capture-6.jpg)
570-
571-Once the installation scripts complete, you should choose option 1 to load the XAMPP Control Panel. It should open up, and if everything went to plan, look like the screen shot.
572-
573-![](img/install_environment_Control_Panel_Running.jpg)
574-
575-At this point, you need to check the boxes next to "Svc" for both Apache and MySql. As you click each, it will prompt you to install the service for that item. Click OK. Then click the "Start" button on each of these items as well.
576-Your screen should now look like the below:
577-
578-![](img/install_environment_Configured_Control_Panel.jpg)
579-
580-Before we install Xibo, we need to configure a few things on XAMPP to make it a bit more secure. From the XAMPP Control Panel, click the "Admin" button next to MySQL. This will load a web browser and take you to an application called PHPMyAdmin that was installed along with XAMPP. It will let us setup a password for the "root" MySQL account. The "root" account on MySQL has privileges to add new users, create databases etc so needs to have a strong password.
581-
582-From the PHPMyAdmin screen, click "Privileges" at the top of the screen. You'll see the database users that exist already listed. We're interested in the one called "root" that has "localhost" in the "Host" column. Click the blue "Edit Privileges" symbol to the right of the word "Yes".
583-
584-![](img/install_environment_Win32_phpmyadmin_privileges.png)
585-
586-Scroll down the page until you find the "Change Password" box. Enter a new password in both the password boxes and click "Go". On a piece of paper, write down "MySQL Admin User details. Username: root Password:" followed by the password you just chose. We'll need these later!
587-
588-![](img/install_environment_Win32_phpmyadmin_password.png)
589\ No newline at end of file
590+The [[PRODUCTNAME]] CMS can be run on any operating system that supports PHP/MySQL, the most common installation being "LAMP" (Linux, Apache, MySQL and PHP).
591+
592+The following pages contain basic guides to configuring the most common environments.
593+
594+- [Install on Linux](install_environment_linux.html)
595+- [Install on Windows using XAMPP](install_environment_windows_xampp.html)
596+- [Install on Windows using IIS](install_environment_windows_iis.html)
597\ No newline at end of file
598
599=== added file 'manual/source/en/install_environment_linux.md'
600--- manual/source/en/install_environment_linux.md 1970-01-01 00:00:00 +0000
601+++ manual/source/en/install_environment_linux.md 2015-01-09 11:27:10 +0000
602@@ -0,0 +1,93 @@
603+<!--toc=getting_started-->
604+#<span id="Install_on_Linux"> Install on Linux </span>
605+
606+Linux makes a good home for a Xibo server. All of the software it needs to support it is provided by the major Linux distributions. We'll cover Ubuntu in detail here, as it's what I use. There are some notes further down for other distributions, however the whole procedure is very similar.
607+
608+### <span id="Setup_Apache_and_PHP"> Setup Apache and PHP </span>
609+
610+#### <span id="Ubuntu"> Ubuntu </span>
611+
612+* Install Apache 2.x Webserver
613+```
614+ sudo apt-get install apache2
615+```
616+
617+* Install MySQL Server
618+```
619+ sudo apt-get install mysql-server
620+```
621+
622+* Install PHP5&#160;:
623+```
624+ sudo apt-get install php5 php5-mysql php5-gd
625+```
626+
627+_On Ubuntu, DOM, libXML, gettext and JSON extension is already compiled on PHP5_
628+
629+* Restart Apache 2 Webserver if necessary
630+```
631+ sudo /etc/init.d/apache2 restart
632+```
633+
634+#### <span id="CentOS_5.x_.2F_Redhat_RHEL_5.x"> CentOS 5.x / Redhat RHEL 5.x </span>
635+
636+JSON extension is not include in CentOS 5.x or Redhat RHEL 5.x but you can install it with this steps&#160;:
637+
638+* Install JSON PHP Extension
639+```
640+ yum install php-devel
641+```
642+
643+_If you have more than 8MB of memory limit for PHP, install with PEAR&#160;:_
644+
645+```
646+ pear install pecl/json
647+```
648+
649+_If you have less than 8MB of memory limit for PHP, PEAR failed to install... Use PECL&#160;: _
650+
651+```
652+ pecl install json
653+```
654+
655+* Active JSON extension&#160;:
656+```
657+ vim /etc/php.d/json.ini
658+```
659+```
660+ # Json Extension
661+ extension=json.so
662+```
663+
664+* Save file and restart the Apache webserver&#160;:
665+```
666+ /etc/init.d/httpd restart
667+```
668+
669+#### <span id="SuSe"> SuSe </span>
670+
671+* during web install library location says 'full path' but means relative to document root on suse linux
672+```
673+for me this is /srv/www/htdocs/xlib in the box for full path, i put "xlib"
674+```
675+
676+### <span id="Install_Xibo"> Install Xibo </span>
677+
678+* Extract the tarball you downloaded inside your webserver's document root (eg /var/www/xibo) and ensure the webserver has permissions to read and write those files:
679+```
680+ cd /var/www
681+ sudo tar zxvf ~/xibo-1.0.5-server.tar.gz
682+ sudo mv xibo-1.0.5-server xibo
683+ sudo chown www-data.www-data -R xibo
684+```
685+
686+* Make a directory for the server library. Make sure the webserver has permission to write to this location:
687+```
688+ sudo mkdir /xibo-library
689+ sudo chown www-data.www-data -R /xibo-library
690+```
691+
692+* You should now use a webbrowser to visit your webserver - eg [http://myserver/xibo](http://myserver/xibo)
693+
694+* The process is fairly self explanatory. Follow the final part of the [Windows instructions](install_cms.html) for greater detail.
695+
696
697=== added file 'manual/source/en/install_environment_windows_iis.md'
698--- manual/source/en/install_environment_windows_iis.md 1970-01-01 00:00:00 +0000
699+++ manual/source/en/install_environment_windows_iis.md 2015-01-09 11:27:10 +0000
700@@ -0,0 +1,195 @@
701+<!--toc=getting_started-->
702+# Install on Windows using IIS
703+This guide is written by Piermaria Spinazzola.
704+
705+## Install Internet Information Services (IIS) Manager
706+If you are using Windows Server you’ll need to install the role from "Server Manager".
707+
708+On Windows 7 and 8, open "Windows Features" and tick the box next to:
709+1. Internet Information Services
710+
711+2. Web Management Tools
712+
713+3. World Wide Web Services (With all Sub-Features)
714+
715+4. Internet Information Services Hostable Web Core (With all Sub-Features)
716+![IIS Manager Install](img/install_environment_windows_iismgr_install.png)
717+
718+ Click OK, wait for the features to be installed and when finished reboot windows.
719+
720+## Install PHP
721+To get started go on: [http://php.iis.net](http://php.iis.net), You will be prompted to the Microsoft IIS page for PHP.
722+
723+1. Click on install and when prompted on Run and start the installation using the Microsoft Web Platform Installer.
724+![WPI Install](img/install_environment_windows_wpi_install.png)
725+ If WPI is already installed, just open on IIS and click on the icon under management.
726+![Step 1](img/install_environment_windows_php_install_step1.png)
727+
728+2. Click on install a screen like the one below should appear, check that all components have been selected for installation.
729+![Step 2](img/install_environment_windows_php_install_step2.png)
730+
731+3. At the end of the setup, click on Finish to exit the installer.
732+![Step 3](img/install_environment_windows_php_install_step3.png)
733+
734+4. Close IIS
735+
736+5. Go in the PHP installation folder, should be:
737+
738+ C:\Program Files (x86)\PHP\v5.3
739+ Look for a file called "php.ini" is the php configuration file, once located, open it with text editor:
740+
741+ By scrolling or searching the following strings of text check that the values are the ones shown below.
742+
743+ ```
744+ fastcgi.impersonate = 1
745+ fastcgi.logging = 0
746+ cgi.fix_pathinfo=1
747+ cgi.force_redirect = 0
748+ ```
749+
750+6. Open IIS
751+
752+7. Click on Sites and Default Web Site
753+![Step 4](img/install_environment_windows_php_install_step4.png)
754+
755+8. Click on PHP Manager
756+![Step 5](img/install_environment_windows_php_install_step5.png)
757+
758+9. Click on Default Document and check that index.php is enabled.
759+
760+10. Go back and click on "Handler Mappings", if you can't see the line:
761+![Step 6](img/install_environment_windows_php_install_step6.png)
762+
763+11. Click on "Add Module Mapping" and enter the following in the dialog:
764+
765+ ```
766+ Request path: *.php
767+ Module: FastCgiModule
768+ Executable: C:\[Path to PHP installation]\php-cgi.exe
769+ Name: PHP_via_FastCGI
770+ ```
771+
772+12. Click "Request Restrictions" button and then configure the mapping to invoke handler only if request is mapped to a file or a folder;
773+
774+13. Go Back to Default Web Site and click on PHP Manager and click on "Enable or Disable and Extension" and in the actions pane click on "Open php.ini". Find the [ExtensionList] and add:
775+
776+ a. extension=php_fileinfo.dll, or if the line is present, remove the ";" at the beginning.
777+
778+ b. extension=php_mcrypt.dll, or if the line is present, remove the ";" at the beginning.
779+
780+ c. Check that php_gd2.dll and php_mbstring.dll are both present.
781+ You probably want to allow larger files to be uploaded than is currently available with your default PHP configuration.
782+
783+ d. Find the upload_max_filesize line and set with a value > 128M if you want to upload files into [[PRODUCTNAME]] bigger than the default size.
784+
785+ e. Find the post_max_size line and set with a value > 128M if you want to post files into [[PRODUCTNAME]] bigger than the default size.
786+
787+ f. Find the max_execution_time line and set with a value > 120 this in order to limit the maximum exectuion time of each script, the values is in seconds.
788+
789+14. Now Save the file using the same extension, close the text editor.
790+15. Press Win + R and type iisreset.exe and hit enter.
791+
792+## Install MySQL
793+[[PRODUCTNAME]] Performs better on MySQL Community Server Edition, I strongly suggest to use this version rather than MySQL Server.
794+
795+1. Go on http://dev.mysql.com/downloads/ and download the MyQL Community Server MSI installer.
796+
797+2. Once downloaded the installer, run it.
798+
799+3. Select the right installation for you, as for [[PRODUCTNAME]] we need on MySQL server, I will install the Server Only package.
800+![Step 1](img/install_environment_windows_mysql_install_step1.png)
801+
802+4. Once Done, click next and select the right configuration for you server, for this tutorial as I assumed that server is shared with other services i’ve selected "Server Machine" as configuration type.
803+![Step 2](img/install_environment_windows_mysql_install_step2.png)
804+
805+ Tick the box "Show Advanced Options" and click next
806+
807+5. The next step is to set the "root" password, you can also create further users, with different responsibilities.
808+ Write down and store safely the "root" password you will always need it to let [[PRODUCTNAME]] access the my sql server and create a new DB.
809+![Step 3](img/install_environment_windows_mysql_install_step3.png)
810+
811+6. Once done click next.
812+
813+7. Configure the Windows Service
814+![Step 4](img/install_environment_windows_mysql_install_step4.png)
815+
816+ Check that the MySQL server will run as a windows Service, if needed you can personalise the name.
817+ If the service needs to be run with a different account select it now otherwise keep the radio button on standard.
818+ TIP: in Windows Domain is good practice to create special windows account for special services.
819+
820+8. Once done click next.
821+
822+9. Configure Advanced Options:
823+![Step 5](img/install_environment_windows_mysql_install_step5.png)
824+
825+ In this page you can disable or enable the service logs and rename them.
826+ If you’re happy with the basic settings, click next.
827+
828+10. Apply Server Configuration
829+
830+ The installer will now apply the selected configuration.
831+
832+11. Click Finish to exit the installer configurator.
833+
834+12. Now Click next again and then finish.
835+
836+## Install PHP My Admin
837+PHPmyAdmin is a third party graphic interface for the MySQL server, this is not essential but useful if you want to manage and have a look at your MySQL Databases.
838+
839+1. Go at: http://www.phpmyadmin.net/home_page/downloads.php and download the latest version, save it on your disk.
840+
841+2. Once downloaded, open the Zip file and copy the content in your IIS default site folder, usually: "C:\inetpub\wwwroot"
842+![Step 1](img/install_environment_windows_phpmyadmin_install_step1.png)
843+
844+3. Rename the folder with the following name: phpmyadmin and hit enter
845+
846+4. Open IIS
847+
848+5. Click on "Default Site" and right click on the "phpMyAdmin" Folder and from the drop down menu select "Convert to Application"
849+![Step 2](img/install_environment_windows_phpmyadmin_install_step2.png)
850+
851+6. You can leave the default settings and click OK.
852+![Step 3](img/install_environment_windows_phpmyadmin_install_step3.png)
853+
854+7. Now go to the directory that you copied the unzipped PHPMyAdmin folder to and open a file named config.sample.inc.php using WordPad
855+
856+8. Find the line $cfg[‘blowfish_secret’] = ‘Value’; you have to fill in this line with anything that you want.
857+ Tips: The maximum number of characters is 46 and it can be alphanumeric.
858+![Step 4](img/install_environment_windows_phpmyadmin_install_step4.png)
859+
860+9. Go down to the section User for advanced features and uncomment (Remove the // from the front of the line) the two lines under that and change the pmapass to a different password, I will use password.
861+![Step 5](img/install_environment_windows_phpmyadmin_install_step5.png)
862+
863+10. Next, go down to the next section Storage database and tables and uncomment all the lines under it, unless you want to disable access to Database areas (remove the // from the front of the line).
864+![Step 6](img/install_environment_windows_phpmyadmin_install_step6.png)
865+
866+11. Now save and close the file.
867+
868+12. Open your browser and go to: http://localhost/phpmyadmin/
869+
870+13. You should now see the following page:
871+ To login use your "root" username and password to get access.
872+
873+![Step 7](img/install_environment_windows_phpmyadmin_install_step7.png)
874+
875+## Install [[PRODUCTNAME]]
876+1. Download the latest version of [[PRODUCTNAME]] from the website [[PRODUCTHOME]], save it on your disk.
877+
878+2. Once downloaded, open the Zip file and copy the content in your IIS default site folder, should be: "C:\inetpub\wwwroot"
879+
880+3. Rename the folder just copied with the following name: [[PRODUCTNAME]]
881+![Step 1](img/install_environment_windows_cms_install_step1.png)
882+
883+4. Open IIS
884+
885+5. Click on "Default Site" and right click on the "[[PRODUCTNAME]]" Folder and from the drop down menu select "Convert to Application"
886+![Step 2](img/install_environment_windows_cms_install_step2.png)
887+
888+6. You can leave the default settings and click OK.
889+![Step 3](img/install_environment_windows_cms_install_step3.png)
890+
891+7. Now open your browser and type the following address http://localhost/[[PRODUCTNAME]]/ and follow the CMS installation guide you can find on the website.
892+
893+```
894+Rev 30.11.2014 v.1.0 – IIS 8.5, MySQL 5.6.21 Community Server Edition, PHP 5.3, Windows Server 2012.
895+```
896\ No newline at end of file
897
898=== added file 'manual/source/en/install_environment_windows_xampp.md'
899--- manual/source/en/install_environment_windows_xampp.md 1970-01-01 00:00:00 +0000
900+++ manual/source/en/install_environment_windows_xampp.md 2015-01-09 11:27:10 +0000
901@@ -0,0 +1,47 @@
902+<!--toc=getting_started-->
903+# <span id="Install_on_Windows"> Install on Windows </span>
904+
905+This Windows install guide is focused mainly on someone who wants to use a Windows PC to be the server as well as the client, or perhaps has a spare Windows XP machine to use as a server. The instructions should work equally well on Windows Server operating systems.
906+
907+Xibo should also run under IIS, however it's not a platform the Xibo development team currently tests with. If you know your way around IIS, then the latter half of this guide plus the "What you need to know!" section should get you going.
908+
909+### <span id="Install_the_Webserver_.28XAMPP.29"> Install the Webserver (XAMPP) </span>
910+
911+We'll be using XAMPP to install Apache, MySQL, PHP (amongst other things) to support Xibo. This is convenient as it provides the whole system in a single installer, and can be un-installed via Add/Remove Programs at a later date if required. If you already have XAMPP installed, you can skip to the next section.
912+
913+You can download XAMPP from [Apache Friends](http://www.apachefriends.org/en/xampp.html). You need to download the full XAMPP Installer package for Windows systems. Save it to the Desktop.
914+
915+Once XAMPP Installer has downloaded, double click on it to run it. You may be prompted to allow the installer to run as the publisher could not be verified. Click "Run".
916+
917+You should get to the start of the install wizard.
918+
919+![](img/install_environment_Win32_xampp_install_start.jpg)
920+
921+By default, XAMPP installs to "c:\xampp". Unless you need to move it somewhere else, then that's a good choice. If you do select a different directory, remember it for the next step when we install Xibo. Click Install.
922+The installer will now run, and extract a number of files. When it finishes, it will bring you to a command prompt.
923+
924+You'll now be asked a few questions about how XAMPP should install itself. At the following prompts, you may select all default options.
925+
926+![](img/install_environment_Capture-2.jpg)
927+![](img/install_environment_Capture-3.jpg)
928+![](img/install_environment_Capture-4.jpg)
929+![](img/install_environment_Capture-6.jpg)
930+
931+Once the installation scripts complete, you should choose option 1 to load the XAMPP Control Panel. It should open up, and if everything went to plan, look like the screen shot.
932+
933+![](img/install_environment_Control_Panel_Running.jpg)
934+
935+At this point, you need to check the boxes next to "Svc" for both Apache and MySql. As you click each, it will prompt you to install the service for that item. Click OK. Then click the "Start" button on each of these items as well.
936+Your screen should now look like the below:
937+
938+![](img/install_environment_Configured_Control_Panel.jpg)
939+
940+Before we install Xibo, we need to configure a few things on XAMPP to make it a bit more secure. From the XAMPP Control Panel, click the "Admin" button next to MySQL. This will load a web browser and take you to an application called PHPMyAdmin that was installed along with XAMPP. It will let us setup a password for the "root" MySQL account. The "root" account on MySQL has privileges to add new users, create databases etc so needs to have a strong password.
941+
942+From the PHPMyAdmin screen, click "Privileges" at the top of the screen. You'll see the database users that exist already listed. We're interested in the one called "root" that has "localhost" in the "Host" column. Click the blue "Edit Privileges" symbol to the right of the word "Yes".
943+
944+![](img/install_environment_Win32_phpmyadmin_privileges.png)
945+
946+Scroll down the page until you find the "Change Password" box. Enter a new password in both the password boxes and click "Go". On a piece of paper, write down "MySQL Admin User details. Username: root Password:" followed by the password you just chose. We'll need these later!
947+
948+![](img/install_environment_Win32_phpmyadmin_password.png)
949\ No newline at end of file
950
951=== modified file 'manual/source/en/release_notes.md'
952--- manual/source/en/release_notes.md 2014-11-25 14:27:02 +0000
953+++ manual/source/en/release_notes.md 2015-01-09 11:27:10 +0000
954@@ -5,6 +5,7 @@
955 The current release notes are [[[PRODUCTVERSION]]](release_notes_[[PRODUCTVERSION]].html).
956
957 ## Version 1.7 "Tuttle"
958+* [1.7.0-rc1](release_notes_1.7.0-rc1.html)
959 * [1.7.0-beta](release_notes_1.7.0-beta.html)
960 * [1.7.0-alpha2](release_notes_1.7.0-alpha2.html)
961 * [1.7.0-alpha](release_notes_1.7.0-alpha.html)
962
963=== added file 'manual/source/en/release_notes_1.7.0-rc1.md'
964--- manual/source/en/release_notes_1.7.0-rc1.md 1970-01-01 00:00:00 +0000
965+++ manual/source/en/release_notes_1.7.0-rc1.md 2015-01-09 11:27:10 +0000
966@@ -0,0 +1,53 @@
967+<!--toc=getting_started-->
968+# Xibo 1.7.0-beta - Codename "Tuttle"</span>
969+
970+This is a release candidate for Xibo working towards the release of Xibo 1.7.0, the next stable line of Xibo released. **This should NOT BE USED IN PRODUCTION.**.
971+
972+You can download this release from [https://launchpad.net/xibo/1.7/1.7.0-rc1](https://launchpad.net/xibo/1.7/1.7.0-rc1)
973+
974+This release builds on 1.7.0-beta, optimizing the improvements already made, fixing bugs and adding a few additional features. The most notable improvements are installable weather and twitter modules.
975+
976+## Requirements
977+
978+You must use the 1.7.0-rc1 or higher version of the Windows Display Client with this version of the Xibo CMS. The Ubuntu Client is not currently compatible with 1.7 series.
979+
980+Xibo requires PHP 5.3.3 or higher. A full list of module requirements is presented at the point of installation.
981+
982+### Layout Design
983+The Layout format has been changed in 1.7 release and this beta contains a method of upgrading older Layouts to the new format. When viewing an older layout a notification will appear below the layout in the designer. Upgrading a Layout will alter the dimensions of the layout so that they match a particular resolution - it will **not** resize the media content and this will have to be done manually.
984+
985+### Xibo for Windows
986+
987+There are significant changes to the way display client settings are managed. All settings are now managed on the CMS except the settings required to connect to the CMS. You will need to manually migrate your settings into the CMS - we have provided sensible default values. See [display settings](index.php?toc=user_and_display&p=admin/displayprofiles) in the manual for more information.
988+
989+The .NET Framework requirement has been raised to v4.
990+
991+## Upgrading
992+
993+There are significant database schema changes between the 1.7 series of Xibo and prior releases. The upgrade wizard will take a prior database and convert it to a schema suitable for the 1.7 series to date. Note that this is a one-way conversion. **Please do not upgrade your production database to test Xibo 1.7 functionality, and then expect to run a prior series code base against that database.**
994+
995+* Clone your existing Xibo database and grant permissions (see [Release Notes:Clone Database](release_notes_clonedb.html "Clone Database") for details)
996+* Backup settings.php from your installation
997+* Manually take a backup of your database
998+* Replace your existing installation with the new version from the tar.gz or zip file. **Do not copy over the top.**
999+* Replace your backup settings.php file in your Xibo installation directory
1000+* Browse to [http://your.server/path](http://your.server/path) as normal
1001+* You will be prompted that an upgrade is required.
1002+* Enter your xibo_admin password, and follow the upgrade wizard.
1003+* The upgrade should run, and finally ask you to log in as you would normally.
1004+
1005+Instructions for cloning a Xibo database are available here [Clone Database](release_notes_clonedb.html "Clone Database").
1006+
1007+## Help
1008+
1009+Please ask for help / advice in the Answers section of Launchpad: [https://answers.launchpad.net/xibo](https://answers.launchpad.net/xibo)
1010+
1011+Please report any bugs in the Bugs section of Launchpad: [https://bugs.launchpad.net/xibo](https://bugs.launchpad.net/xibo) (if you're not sure that what you have found is a bug, please ask in the Answers section first!)
1012+
1013+Please report any enhancement requests in the Blueprints section of Launchpad: [https://blueprints.launchpad.net/xibo](https://blueprints.launchpad.net/xibo)
1014+
1015+When asking for assistance with this release, please make it clear that you're using the development preview and not a stable release of Xibo.
1016+
1017+## Bug Fixes
1018+
1019+For a full list of bug fixes please refer to the Release Project Page: [https://launchpad.net/xibo/1.7/1.7.0-rc1](https://launchpad.net/xibo/1.7/1.7.0-rc1)
1020\ No newline at end of file
1021
1022=== added file 'manual/source/fr/tour.md'
1023--- manual/source/fr/tour.md 1970-01-01 00:00:00 +0000
1024+++ manual/source/fr/tour.md 2015-01-09 11:27:10 +0000
1025@@ -0,0 +1,65 @@
1026+<!--toc=tour-->
1027+#Vue d'ensemble de la plate-forme
1028+
1029+[[PRODUCTNAME]] est une application puissante et flexible dotée d'un noyau "ethos", dédiée à l'affichage dynamique, dont il est important de comprendre le fondement.
1030+
1031+Une solution [[PRODUCTNAME]] s'appuie sur quatre éléments:
1032+
1033+* Les afficheurs
1034+* Les medias
1035+* Les présentations
1036+* La planification
1037+
1038+Que faut-il entendre par là ? Fondamentalement, [[PRODUCTNAME]] permet à de multiples afficheurs de diffuser des medias sur un grand nombre de présentations d'écran différentes, planifiés dans le temps.
1039+
1040+Ce manuel comprend des sections traitant de chacun de ces éléments, mais pour commencer examinons-les brièvement.
1041+
1042+
1043+###Les afficheurs
1044+Il s'agit de l'équipement relié à l'écran de télévision, de projecteur et de tablette qui diffusent réellement le contenu final. En d'autres termes, les afficheurs pilotent le contenu que les gens regarderont. Chaque afficheur a sa propre identification auprès du CMS de sorte qu'un contenu, une présentation et une planification spécifique peuvent lui être attribués. Chacun d'eux peut aussi être identifié à des fins statistiques.
1045+
1046+###Les medias
1047+Le contenu du media constitue la clé de voûte d'une solution [[PRODUCTNAME]] et de nombreux types de contenu peuvent être utilisés. Ils se répartissent généralement en deux catégories. Le fichier média, chargé et stocké dans la bibliothèque, et le média de présentation auquel n'est pas associé de fichier mais qui au contraire est configuré directement à partir d'un modèle. Une image ou une vidéo correspond à un média de la bibliothèque et un flux RSS ou des données texte correspondent à des médias de présentation.
1048+
1049+###Présentations
1050+Il s'agit de la présentation qui apparaît à l'écran. [[PRODUCTNAME]] permet de partager l'écran en différentes régions, chacune disposant de son propre déroulé chronologique pour la lecture des médias. Une présentation demeure à l'écran jusqu'à ce que les déroulés chronologiques aient été lus en entier, ensuite, on pourra charger une présentation totalement nouvelle, dotée de tailles et d'emplacements différents pour les régions ainsi que de chronologies différentes. Cette flexibilité est à l'origine du comportement dynamique d'un écran d'affichage [[PRODUCTNAME]].
1051+
1052+###Planification
1053+Lorsque les afficheurs ont été enregistrés, que le contenu d'un media est téléchargé et que les présentations sont conçues, il est temps de les assembler et de planifier les présentations sur les afficheurs. Chaque afficheur vérifie régulièrement les nouveaux contenus planifiés et télécharge les éléments planifiés avant leur reproduction. La planification est extrêmement flexible et peut s'effectuer par afficheur ou par groupes spécifiques, par présentations et campagnes spécifiques et de façon récurrente. Chaque afficheur dispose d'une présentation par défaut qui apparaît si rien d'autre n'est planifié.
1054+
1055+## Contenu - que peut afficher [[PRODUCTNAME]]?
1056+
1057+[[PRODUCTNAME]] peut lire un grand nombre de contenus sur fichier ainsi que des contenus depuis internet.
1058+
1059+* Texte
1060+* Images
1061+* Vidéo
1062+* PowerPoint (client d'affichage Windows uniquement)
1063+* Flash (client d'affichage Windows uniquement)
1064+* RSS / Atom
1065+* Fichier CSV (formatés)
1066+* Pages Web
1067+* Contenu personnalisé intégré
1068+
1069+On trouvera sur le site web de [[PRODUCTNAME]] une liste complète des fonctionnalités.
1070+
1071+## CMS - Comment gérer tout cela?
1072+
1073+La gestion d'une solution aussi souple que [[PRODUCTNAME]] nécessite un puissant CMS (content management system = système de gestion de contenu) pour offrir ces fonctions fondamentales de façon rationnelle et maîtrisée. Le CMS de [[PRODUCTNAME]] est doté de fonctionnalités telles que:
1074+
1075+* Groupes d'utilisateurs
1076+* Définition de droits d'accès pour utilisateurs et groupes
1077+* Groupes d'afficheurs
1078+* Définition de droits d'accès pour des groupes d'afficheurs
1079+* Définition de droits d'accès aux menus et aux pages
1080+* Campagnes (groupes de présentations ordonnés)
1081+* Modèles de présentations
1082+* Statistiques concernant les afficheurs
1083+
1084+## Le Manuel
1085+
1086+Ce manuel est structuré en sections correspondant globalement aux principes de base et aux fonctions les plus évoluées du CMS. Nous commençons par la bibliothèque, les présentations et la planification, pour passer ensuite à la gestion des afficheurs et aux options pour utilisateurs et développeurs.
1087+
1088+
1089+## Plus d'informations
1090+Le site web de [[[PRODUCTNAME]] ]([[PRODUCTHOME]]) est une excellente ressource pour en savoir plus car il s'agit du site de la communauté d'utilisateurs (PRODUCTSUPPORTURL) pour la plate-forme.
1091
1092=== modified file 'manual/template/template.php'
1093--- manual/template/template.php 2014-10-27 18:25:50 +0000
1094+++ manual/template/template.php 2015-01-09 11:27:10 +0000
1095@@ -1,9 +1,29 @@
1096 <?php
1097+/*
1098+ * Xibo - Digital Signage - http://www.xibo.org.uk
1099+ * Copyright (C) 2006-2014 Spring Signage Ltd
1100+ *
1101+ * This file is part of Xibo.
1102+ *
1103+ * Xibo is free software: you can redistribute it and/or modify
1104+ * it under the terms of the GNU Affero General Public License as published by
1105+ * the Free Software Foundation, either version 3 of the License, or
1106+ * any later version.
1107+ *
1108+ * Xibo is distributed in the hope that it will be useful,
1109+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1110+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1111+ * GNU Affero General Public License for more details.
1112+ *
1113+ * You should have received a copy of the GNU Affero General Public License
1114+ * along with Xibo. If not, see <http://www.gnu.org/licenses/>.
1115+ */
1116+
1117 # Name of the product
1118 define('PRODUCT_NAME', 'Xibo');
1119
1120 # Product Version
1121-define('PRODUCT_VERSION', '1.7.0-beta');
1122+define('PRODUCT_VERSION', '1.7.0');
1123
1124 # Home page URL
1125 define('PRODUCT_HOME', 'http://xibo.org.uk');
1126
1127=== modified file 'server/config/client.config.php'
1128--- server/config/client.config.php 2014-11-14 15:43:58 +0000
1129+++ server/config/client.config.php 2015-01-09 11:27:10 +0000
1130@@ -285,7 +285,7 @@
1131 'fieldType' => 'checkbox',
1132 'default' => 0,
1133 'helpText' => __('When enabled the client will send the current layout to the CMS each time it changes. Warning: This is bandwidth intensive and should be disabled unless on a LAN.'),
1134- 'enabled' => true,
1135+ 'enabled' => Theme::GetConfig('client_sendCurrentLayoutAsStatusUpdate_enabled', true),
1136 'groupClass' => NULL
1137 ),
1138 array(
1139@@ -296,7 +296,7 @@
1140 'fieldType' => 'number',
1141 'default' => 0,
1142 'helpText' => __('The duration between status screen shots in minutes. 0 to disable. Warning: This is bandwidth intensive.'),
1143- 'enabled' => true,
1144+ 'enabled' => Theme::GetConfig('client_screenShotRequestInterval_enabled', true),
1145 'groupClass' => NULL
1146 )
1147 )
1148@@ -471,6 +471,28 @@
1149 'helpText' => __('Store all HTML resources on the Internal Storage? Should be selected if the device cannot display text, ticker, dataset media.'),
1150 'enabled' => true,
1151 'groupClass' => NULL
1152+ ),
1153+ array(
1154+ 'name' => 'sendCurrentLayoutAsStatusUpdate',
1155+ 'tabId' => 'advanced',
1156+ 'title' => __('Notify current layout'),
1157+ 'type' => _CHECKBOX,
1158+ 'fieldType' => 'checkbox',
1159+ 'default' => 0,
1160+ 'helpText' => __('When enabled the client will send the current layout to the CMS each time it changes. Warning: This is bandwidth intensive and should be disabled unless on a LAN.'),
1161+ 'enabled' => Theme::GetConfig('client_sendCurrentLayoutAsStatusUpdate_enabled', true),
1162+ 'groupClass' => NULL
1163+ ),
1164+ array(
1165+ 'name' => 'screenShotRequestInterval',
1166+ 'tabId' => 'advanced',
1167+ 'title' => __('Screen shot interval'),
1168+ 'type' => _INT,
1169+ 'fieldType' => 'number',
1170+ 'default' => 0,
1171+ 'helpText' => __('The duration between status screen shots in minutes. 0 to disable. Warning: This is bandwidth intensive.'),
1172+ 'enabled' => Theme::GetConfig('client_screenShotRequestInterval_enabled', true),
1173+ 'groupClass' => NULL
1174 )
1175 )
1176 )
1177
1178=== modified file 'server/install/database/60.sql'
1179--- server/install/database/60.sql 2013-05-01 19:24:35 +0000
1180+++ server/install/database/60.sql 2015-01-09 11:27:10 +0000
1181@@ -32,6 +32,19 @@
1182
1183 INSERT INTO `pages` (`pageID`, `name`, `pagegroupID`) VALUES (NULL, 'timeline', '3');
1184
1185+/* Get all of the current permission links between pages in the Layouts group and add a duplicate for the new time line page */
1186+INSERT INTO `lkpagegroup` (`pageID`, `groupID`)
1187+SELECT DISTINCT newpage.pageID, lkpagegroup.groupID
1188+ FROM `lkpagegroup`
1189+ INNER JOIN `pages`
1190+ ON pages.pageID = lkpagegroup.pageID
1191+ CROSS JOIN (
1192+ SELECT pageID
1193+ FROM `pages`
1194+ WHERE `name` = 'timeline'
1195+ ) newpage
1196+ WHERE pages.pagegroupID = 3;
1197+
1198 UPDATE `module` SET `ImageUri` = REPLACE(ImageUri, 'img/forms/', 'forms/') WHERE ImageUri IS NOT NULL;
1199
1200 UPDATE `menuitem` SET `Img` = REPLACE(Img, 'img/dashboard/', 'dashboard/') WHERE Img IS NOT NULL;
1201
1202=== modified file 'server/install/database/80.sql'
1203--- server/install/database/80.sql 2014-11-24 15:49:49 +0000
1204+++ server/install/database/80.sql 2015-01-09 11:27:10 +0000
1205@@ -45,7 +45,7 @@
1206 ALTER TABLE `layout` CHANGE `background` `backgroundImageId` INT( 11 ) NULL DEFAULT NULL;
1207
1208 INSERT INTO `lklayoutmedia` (mediaid, layoutid, regionid)
1209-SELECT backgroundimageid, layoutid, 'background' FROM `layout` WHERE IFNULL(backgroundImageId, 0) <> 0;
1210+SELECT backgroundimageid, layoutid, 'background' FROM `layout` INNER JOIN `media` ON media.mediaid = layout.backgroundImageId WHERE IFNULL(backgroundImageId, 0) <> 0;
1211
1212 ALTER TABLE `setting` CHANGE `type` `fieldType` VARCHAR( 24 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL;
1213
1214
1215=== modified file 'server/install/database/81.sql'
1216--- server/install/database/81.sql 2014-11-11 21:42:27 +0000
1217+++ server/install/database/81.sql 2015-01-09 11:27:10 +0000
1218@@ -1,6 +1,6 @@
1219 INSERT INTO `setting` (`setting` ,`value` ,`fieldType` ,`helptext` ,`options` ,`cat` ,`userChange` ,`title` ,`validation` ,`ordering` ,`default` ,`userSee` ,`type`)
1220 VALUES (
1221- 'DATE_FORMAT', 'Y-m-d', 'text', 'The Date Format to use when displaying dates in the CMS.', NULL , 'regional', '1', 'Date Format', 'required', '30', 'Y-m-d', '1', 'string'
1222+ 'DATE_FORMAT', 'Y-m-d H:i', 'text', 'The Date Format to use when displaying dates in the CMS.', NULL , 'regional', '1', 'Date Format', 'required', '30', 'Y-m-d', '1', 'string'
1223 );
1224
1225 INSERT INTO `setting` (`setting` ,`value` ,`fieldType` ,`helptext` ,`options` ,`cat` ,`userChange` ,`title` ,`validation` ,`ordering` ,`default` ,`userSee` ,`type`)
1226
1227=== added file 'server/install/database/83.sql'
1228--- server/install/database/83.sql 1970-01-01 00:00:00 +0000
1229+++ server/install/database/83.sql 2015-01-09 11:27:10 +0000
1230@@ -0,0 +1,5 @@
1231+ALTER TABLE `user` CHANGE `UserName` `UserName` VARCHAR( 50 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL;
1232+
1233+UPDATE `version` SET `app_ver` = '1.7.0-rc1', `XmdsVersion` = 4, `XlfVersion` = 2;
1234+UPDATE `setting` SET `value` = 0 WHERE `setting` = 'PHONE_HOME_DATE';
1235+UPDATE `version` SET `DBVersion` = '83';
1236\ No newline at end of file
1237
1238=== modified file 'server/install/master/data.sql'
1239--- server/install/master/data.sql 2014-11-26 15:33:06 +0000
1240+++ server/install/master/data.sql 2015-01-09 11:27:10 +0000
1241@@ -1,5 +1,5 @@
1242 INSERT INTO `version` (`app_ver`, `XmdsVersion`, `XlfVersion`, `DBVersion`) VALUES
1243-('1.7.0-beta', 4, 2, 82);
1244+('1.7.0-rc1', 4, 2, 83);
1245
1246 INSERT INTO `group` (`groupID`, `group`, `IsUserSpecific`, `IsEveryone`) VALUES
1247 (1, 'Users', 0, 0),
1248@@ -236,7 +236,7 @@
1249 (44, 'MAINTENANCE_LOG_MAXAGE', '30', 'number', 'Maximum age for log entries. Set to 0 to keep logs indefinitely.', NULL, 'maintenance', 1, 'Max Log Age', '', 60, '30', 1, 'int'),
1250 (45, 'MAINTENANCE_STAT_MAXAGE', '30', 'number', 'Maximum age for statistics entries. Set to 0 to keep statistics indefinitely.', NULL, 'maintenance', 1, 'Max Statistics Age', '', 70, '30', 1, 'int'),
1251 (46, 'MAINTENANCE_ALERT_TOUT', '12', 'number', 'How long in minutes after the last time a client connects should we send an alert? Can be overridden on a per client basis.', NULL, 'maintenance', 1, 'Max Display Timeout', '', 80, '12', 1, 'int'),
1252-(47, 'SHOW_DISPLAY_AS_VNCLINK', '0', 'checkbox', 'Turn the display name in display management into a VNC link using the IP address last collected. The %s is replaced with the IP address. Leave blank to disable.', NULL, 'displays', 1, 'Display a VNC Link?', '', 30, '0', 1, 'checkbox'),
1253+(47, 'SHOW_DISPLAY_AS_VNCLINK', '0', 'text', 'Turn the display name in display management into a VNC link using the IP address last collected. The %s is replaced with the IP address. Leave blank to disable.', NULL, 'displays', '', 'Display a VNC Link?', '', 30, '', 1, 'string'),
1254 (48, 'SHOW_DISPLAY_AS_VNC_TGT', '_top', 'text', 'If the display name is shown as a link in display management, what target should the link have? Set _top to open the link in the same window or _blank to open in a new window.', NULL, 'displays', 1, 'Open VNC Link in new window?', '', 40, '_top', 1, 'string'),
1255 (49, 'MAINTENANCE_ALWAYS_ALERT', 'Off', 'dropdown', 'Should Xibo send an email if a display is in an error state every time the maintenance script runs?', 'On|Off', 'maintenance', 1, 'Send repeat Display Timeouts', '', 80, 'Off', 1, 'word'),
1256 (50, 'SCHEDULE_LOOKAHEAD', 'On', 'dropdown', 'Should Xibo send future schedule information to clients?', 'On|Off', 'general', 0, 'Send Schedule in advance?', '', 40, 'On', 1, 'word'),
1257@@ -263,7 +263,7 @@
1258 (71, 'PROXY_HOST', '', 'text', 'The Proxy URL', NULL, 'network', 1, 'Proxy URL', '', 10, '', 1, 'string'),
1259 (72, 'PROXY_PORT', '0', 'number', 'The Proxy Port', NULL, 'network', 1, 'Proxy Port', '', 20, '0', 1, 'int'),
1260 (73, 'PROXY_AUTH', '', 'text', 'The Authentication information for this proxy. username:password', NULL, 'network', 1, 'Proxy Credentials', '', 30, '', 1, 'string'),
1261-(74, 'DATE_FORMAT', 'Y-m-d', 'text', 'The Date Format to use when displaying dates in the CMS.', NULL , 'regional', '1', 'Date Format', 'required', 30, 'Y-m-d', '1', 'string'),
1262+(74, 'DATE_FORMAT', 'Y-m-d H:i', 'text', 'The Date Format to use when displaying dates in the CMS.', NULL , 'regional', '1', 'Date Format', 'required', 30, 'Y-m-d', '1', 'string'),
1263 (75, 'DETECT_LANGUAGE', '1', 'checkbox', 'Detect the browser language?', NULL , 'regional', '1', 'Detect Language', '', 40, '1', 1, 'checkbox'),
1264 (76, 'DEFAULTS_IMPORTED', '0', 'text', 'Has the default layout been imported?', NULL, 'general', 0, 'Defaults Imported?', 'required', 100, '0', 0, 'checkbox'),
1265 (77, 'FORCE_HTTPS', '0', 'checkbox', 'Force the portal into HTTPS?', NULL, 'network', 1, 'Force HTTPS?', '', 70, '0', 1, 'checkbox'),
1266
1267=== modified file 'server/install/master/structure.sql'
1268--- server/install/master/structure.sql 2014-11-12 17:20:55 +0000
1269+++ server/install/master/structure.sql 2015-01-09 11:27:10 +0000
1270@@ -5,7 +5,7 @@
1271 `Month` int(11) NOT NULL,
1272 `Size` bigint(20) NOT NULL,
1273 PRIMARY KEY (`DisplayID`, `Type`, `Month`)
1274-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
1275+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
1276
1277 CREATE TABLE IF NOT EXISTS `blacklist` (
1278 `BlackListID` int(11) NOT NULL AUTO_INCREMENT,
1279@@ -111,7 +111,7 @@
1280 `UserID` int(11) NOT NULL,
1281 PRIMARY KEY (`FileID`),
1282 KEY `UserID` (`UserID`)
1283-) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
1284+) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
1285
1286 CREATE TABLE IF NOT EXISTS `group` (
1287 `groupID` int(11) NOT NULL AUTO_INCREMENT,
1288@@ -543,7 +543,7 @@
1289 CREATE TABLE IF NOT EXISTS `user` (
1290 `UserID` int(11) NOT NULL AUTO_INCREMENT,
1291 `usertypeid` int(8) NOT NULL,
1292- `UserName` varchar(15) NOT NULL,
1293+ `UserName` varchar(50) NOT NULL,
1294 `UserPassword` varchar(128) NOT NULL,
1295 `loggedin` tinyint(1) NOT NULL DEFAULT '0',
1296 `lastaccessed` datetime DEFAULT NULL,
1297@@ -558,7 +558,7 @@
1298
1299 CREATE TABLE IF NOT EXISTS `usertype` (
1300 `usertypeid` int(8) NOT NULL,
1301- `usertype` varchar(16) CHARACTER SET latin1 NOT NULL,
1302+ `usertype` varchar(16) CHARACTER SET utf8 NOT NULL,
1303 PRIMARY KEY (`usertypeid`)
1304 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
1305
1306@@ -627,7 +627,7 @@
1307 `bandwidthtypeid` int(11) NOT NULL AUTO_INCREMENT,
1308 `name` varchar(25) NOT NULL,
1309 PRIMARY KEY (`bandwidthtypeid`)
1310-) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=12 ;
1311+) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=12 ;
1312
1313 CREATE TABLE IF NOT EXISTS `tag` (
1314 `tagId` int(11) NOT NULL AUTO_INCREMENT,
1315@@ -640,14 +640,14 @@
1316 `tagId` int(11) NOT NULL,
1317 `layoutId` int(11) NOT NULL,
1318 PRIMARY KEY (`lkTagLayoutId`)
1319-) ENGINE=InnoDB DEFAULT CHARSET=utf32 AUTO_INCREMENT=1 ;
1320+) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
1321
1322 CREATE TABLE IF NOT EXISTS `lktagmedia` (
1323 `lkTagMediaId` int(11) NOT NULL AUTO_INCREMENT,
1324 `tagId` int(11) NOT NULL,
1325 `mediaId` int(11) NOT NULL,
1326 PRIMARY KEY (`lkTagMediaId`)
1327-) ENGINE=InnoDB DEFAULT CHARSET=utf16 AUTO_INCREMENT=1 ;
1328+) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
1329
1330 --
1331 -- Constraints for dumped tables
1332
1333=== modified file 'server/lib/app/kit.class.php'
1334--- server/lib/app/kit.class.php 2014-11-25 11:09:21 +0000
1335+++ server/lib/app/kit.class.php 2015-01-09 11:27:10 +0000
1336@@ -689,5 +689,63 @@
1337 if (Config::GetSetting('ISSUE_STS', 0) == 1)
1338 header("strict-transport-security: max-age=" . Config::GetSetting('STS_TTL', 600));
1339 }
1340+
1341+ /**
1342+ * Json Encode, handling and logging errors
1343+ * http://stackoverflow.com/questions/10199017/how-to-solve-json-error-utf8-error-in-php-json-decode
1344+ * @param mixed $mixed The item to encode
1345+ * @return mixed The Encoded Item
1346+ */
1347+ public static function jsonEncode($mixed)
1348+ {
1349+ if (version_compare(PHP_VERSION, '5.4.0') >= 0) {
1350+ $encoded = json_encode($mixed, JSON_PRETTY_PRINT);
1351+ }
1352+ else {
1353+ $encoded = json_encode($mixed);
1354+ }
1355+
1356+ switch (json_last_error()) {
1357+ case JSON_ERROR_NONE:
1358+ return $encoded;
1359+ case JSON_ERROR_DEPTH:
1360+ Debug::Audit('Maximum stack depth exceeded');
1361+ return false;
1362+ case JSON_ERROR_STATE_MISMATCH:
1363+ Debug::Audit('Underflow or the modes mismatch');
1364+ return false;
1365+ case JSON_ERROR_CTRL_CHAR:
1366+ Debug::Audit('Unexpected control character found');
1367+ return false;
1368+ case JSON_ERROR_SYNTAX:
1369+ Debug::Audit('Syntax error, malformed JSON');
1370+ return false;
1371+ case JSON_ERROR_UTF8:
1372+ $clean = Kit::utf8ize($mixed);
1373+ return Kit::jsonEncode($clean);
1374+ default:
1375+ Debug::Audit('Unknown error');
1376+ return false;
1377+ }
1378+ }
1379+
1380+ /**
1381+ * Utf8ize a string or array
1382+ * http://stackoverflow.com/questions/10199017/how-to-solve-json-error-utf8-error-in-php-json-decode
1383+ * @param mixed $mixed The item to uft8ize
1384+ * @return mixed The utf8ized item
1385+ */
1386+ public static function utf8ize($mixed)
1387+ {
1388+ if (is_array($mixed)) {
1389+ foreach ($mixed as $key => $value) {
1390+ $mixed[$key] = Kit::utf8ize($value);
1391+ }
1392+ }
1393+ else if (is_string ($mixed)) {
1394+ return utf8_encode($mixed);
1395+ }
1396+ return $mixed;
1397+ }
1398 }
1399 ?>
1400
1401=== modified file 'server/lib/app/thememanager.class.php'
1402--- server/lib/app/thememanager.class.php 2014-10-15 16:22:31 +0000
1403+++ server/lib/app/thememanager.class.php 2015-01-09 11:27:10 +0000
1404@@ -281,13 +281,13 @@
1405 return Theme::GetInstance()->name;
1406 }
1407
1408- public static function GetConfig($settingName) {
1409+ public static function GetConfig($settingName, $default = null) {
1410 $theme = Theme::GetInstance();
1411
1412 if (isset($theme->config[$settingName]))
1413 return $theme->config[$settingName];
1414 else
1415- return '';
1416+ return $default;
1417 }
1418
1419 /**
1420
1421=== modified file 'server/lib/data/dataset.data.class.php'
1422--- server/lib/data/dataset.data.class.php 2014-11-13 17:44:55 +0000
1423+++ server/lib/data/dataset.data.class.php 2015-01-09 11:27:10 +0000
1424@@ -22,6 +22,30 @@
1425
1426 class DataSet extends Data
1427 {
1428+ public function hasData($dataSetId)
1429+ {
1430+ try {
1431+ $dbh = PDOConnect::init();
1432+
1433+ // First check to see if we have any data
1434+ $sth = $dbh->prepare('SELECT * FROM `datasetdata` INNER JOIN `datasetcolumn` ON datasetcolumn.DataSetColumnID = datasetdata.DataSetColumnID WHERE datasetcolumn.DataSetID = :datasetid');
1435+ $sth->execute(array(
1436+ 'datasetid' => $dataSetId
1437+ ));
1438+
1439+ return ($sth->fetch());
1440+ }
1441+ catch (Exception $e) {
1442+
1443+ Debug::LogEntry('error', $e->getMessage(), get_class(), __FUNCTION__);
1444+
1445+ if (!$this->IsError())
1446+ $this->SetError(1, __('Unknown Error'));
1447+
1448+ return false;
1449+ }
1450+ }
1451+
1452 /**
1453 * Add a data set
1454 * @param <type> $dataSet
1455@@ -137,22 +161,15 @@
1456 try {
1457 $dbh = PDOConnect::init();
1458
1459- // First check to see if we have any data
1460- $sth = $dbh->prepare('SELECT * FROM `datasetdata` INNER JOIN `datasetcolumn` ON datasetcolumn.DataSetColumnID = datasetdata.DataSetColumnID WHERE datasetcolumn.DataSetID = :datasetid');
1461- $sth->execute(array(
1462- 'datasetid' => $dataSetId
1463- ));
1464+ // Delete the Data
1465+ $data = new DataSetData();
1466+ $data->DeleteAll($dataSetId);
1467
1468- if ($row = $sth->fetch())
1469- return $this->SetError(25005, __('There is data assigned to this data set, cannot delete.'));
1470-
1471 // Delete security
1472- Kit::ClassLoader('datasetgroupsecurity');
1473 $security = new DataSetGroupSecurity($this->db);
1474 $security->UnlinkAll($dataSetId);
1475
1476 // Delete columns
1477- Kit::ClassLoader('datasetcolumn');
1478 $dataSetObject = new DataSetColumn($this->db);
1479 if (!$dataSetObject->DeleteAll($dataSetId))
1480 return $this->SetError(25005, __('Cannot delete dataset, columns could not be deleted.'));
1481
1482=== modified file 'server/lib/data/datasetdata.data.class.php'
1483--- server/lib/data/datasetdata.data.class.php 2014-10-15 15:13:38 +0000
1484+++ server/lib/data/datasetdata.data.class.php 2015-01-09 11:27:10 +0000
1485@@ -24,11 +24,11 @@
1486 {
1487 private $updateWatermark;
1488
1489- public function __construct(database $db) {
1490+ public function __construct() {
1491
1492 $this->updateWatermark = true;
1493
1494- parent::__construct($db);
1495+ parent::__construct();
1496 }
1497
1498 /**
1499
1500=== modified file 'server/lib/data/displayprofile.data.class.php'
1501--- server/lib/data/displayprofile.data.class.php 2014-11-14 15:43:58 +0000
1502+++ server/lib/data/displayprofile.data.class.php 2015-01-09 11:27:10 +0000
1503@@ -170,8 +170,8 @@
1504
1505 $count = $sth->fetchColumn(0) + (int)$this->isDefault;
1506
1507- if ($count != 1)
1508- $this->ThrowError(__('Must have 1 default per display type.'));
1509+ if ($count > 1)
1510+ $this->ThrowError(__('Only 1 default per display type is allowed.'));
1511
1512 return true;
1513 }
1514
1515=== modified file 'server/lib/data/layout.data.class.php'
1516--- server/lib/data/layout.data.class.php 2014-11-24 15:49:49 +0000
1517+++ server/lib/data/layout.data.class.php 2015-01-09 11:27:10 +0000
1518@@ -1682,9 +1682,12 @@
1519 $this->ThrowError(__('Layout not found.'));
1520
1521 // Open a ZIP file with the same name as the layout
1522+ File::EnsureLibraryExists();
1523 $zip = new ZipArchive();
1524 $fileName = $libraryPath . 'temp/export_' . Kit::ValidateParam($row['layout'], _FILENAME) . '.zip';
1525- $zip->open($fileName, ZIPARCHIVE::OVERWRITE);
1526+ $result = $zip->open($fileName, ZIPARCHIVE::OVERWRITE);
1527+ if ($result !== true)
1528+ $this->ThrowError(__('Can\'t create ZIP. Error Code: ' . $result));
1529
1530 // Add layout information to the ZIP
1531 $layout = array(
1532@@ -1942,6 +1945,9 @@
1533 'mediaId' => $newMediaId,
1534 'layoutId' => $layoutId
1535 ));
1536+
1537+ // Link
1538+ $this->AddLk($layoutId, 'background', $newMediaId);
1539 }
1540 catch (Exception $e) {
1541
1542@@ -2020,7 +2026,7 @@
1543 Debug::Audit('Found file: ' . $file);
1544
1545 if (stripos($file, '.zip'))
1546- $this->Import($folder . DIRECTORY_SEPARATOR . $file, NULL, 1, false, true, true, false);
1547+ $this->Import($folder . DIRECTORY_SEPARATOR . $file, NULL, 1, false, false, true, false);
1548 }
1549 }
1550 }
1551
1552=== modified file 'server/lib/data/media.data.class.php'
1553--- server/lib/data/media.data.class.php 2014-11-23 13:37:49 +0000
1554+++ server/lib/data/media.data.class.php 2015-01-09 11:27:10 +0000
1555@@ -170,9 +170,6 @@
1556
1557 Debug::LogEntry('error', $e->getMessage(), get_class(), __FUNCTION__);
1558
1559- if (!$this->IsError())
1560- $this->SetError(1, __('Unknown Error'));
1561-
1562 return false;
1563 }
1564 }
1565@@ -832,8 +829,6 @@
1566 */
1567 public function addModuleFileFromUrl($url, $name, $expires, $moduleSystemFile = false, $force = false)
1568 {
1569- Debug::Audit('Adding: ' . $url . ' with Name: ' . $name . '. Expiry: ' . date('Y-m-d h:i:s', $expires));
1570-
1571 // See if we already have it
1572 // It doesn't matter that we might have already done this, its cached.
1573 $media = $this->moduleFileExists($name);
1574@@ -841,7 +836,7 @@
1575 //Debug::Audit('Module File: ' . var_export($media, true));
1576
1577 if ($media === false || $force) {
1578- Debug::Audit('Media not valid, forcing update.');
1579+ Debug::Audit('Adding: ' . $url . ' with Name: ' . $name . '. Expiry: ' . date('Y-m-d h:i:s', $expires));
1580
1581 $fileName = Config::GetSetting('LIBRARY_LOCATION') . 'temp' . DIRECTORY_SEPARATOR . $name;
1582
1583
1584=== modified file 'server/lib/data/nonce.data.class.php'
1585--- server/lib/data/nonce.data.class.php 2014-08-22 16:53:58 +0000
1586+++ server/lib/data/nonce.data.class.php 2015-01-09 11:27:10 +0000
1587@@ -132,6 +132,17 @@
1588 $params['displayId'] = $displayId;
1589 break;
1590
1591+ case 'oldfile':
1592+ if ($this->validateFileStatement == NULL) {
1593+ $this->validateFileStatement = $dbh->prepare('
1594+ SELECT nonceId, nonce, expiry, lastUsed FROM `xmdsnonce` INNER JOIN `media` ON media.mediaid = xmdsnonce.fileId WHERE displayId = :displayId AND media.storedAs = :fileId');
1595+ }
1596+
1597+ $sth = $this->validateFileStatement;
1598+ $params['fileId'] = $fileId;
1599+ $params['displayId'] = $displayId;
1600+ break;
1601+
1602 case 'layout':
1603 if ($this->validateLayoutStatement == NULL) {
1604 $this->validateLayoutStatement = $dbh->prepare('
1605
1606=== modified file 'server/lib/data/userdata.data.class.php'
1607--- server/lib/data/userdata.data.class.php 2014-08-07 15:47:19 +0000
1608+++ server/lib/data/userdata.data.class.php 2015-01-09 11:27:10 +0000
1609@@ -35,6 +35,91 @@
1610 class Userdata extends Data
1611 {
1612 public $userId;
1613+ public $userName;
1614+ public $userTypeId;
1615+ public $loggedIn;
1616+ public $email;
1617+ public $homePage;
1618+ public $lastAccessed;
1619+ public $newUserWizard;
1620+ public $retired;
1621+
1622+ public static function entries($sortOrder = array(), $filterBy = array())
1623+ {
1624+ $entries = array();
1625+
1626+ try {
1627+ $dbh = PDOConnect::init();
1628+
1629+ $params = array();
1630+ $SQL = 'SELECT userId, userName, userTypeId, loggedIn, email, homePage, lastAccessed, newUserWizard, retired ';
1631+ $SQL .= ' FROM `user` ';
1632+ $SQL .= ' WHERE 1 = 1 ';
1633+
1634+ // User Id Provided?
1635+ if (Kit::GetParam('userId', $filterBy, _INT) != 0) {
1636+ $SQL .= " AND user.userId = :userId ";
1637+ $params['userId'] = Kit::GetParam('userId', $filterBy, _INT);
1638+ }
1639+
1640+ // User Type Provided
1641+ if (Kit::GetParam('userTypeId', $filterBy, _INT) != 0) {
1642+ $SQL .= " AND user.userTypeId = :userTypeId ";
1643+ $params['userTypeId'] = Kit::GetParam('userTypeId', $filterBy, _INT);
1644+ }
1645+
1646+ // User Name Provided
1647+ if (Kit::GetParam('userName', $filterBy, _STRING) != 0) {
1648+ $SQL .= " AND user.userName LIKE :userName ";
1649+ $params['userName'] = '%' . Kit::GetParam('userName', $filterBy, _STRING) . '%';
1650+ }
1651+
1652+ // Groups Provided
1653+ $groups = Kit::GetParam('groupIds', $filterBy, _ARRAY_INT);
1654+ Debug::Audit(var_export($groups, true));
1655+ if (count($groups) > 0) {
1656+ $SQL .= " AND user.groupIds IN (" . implode($groups, ',') . ") ";
1657+ }
1658+
1659+ // Retired users?
1660+ if (Kit::GetParam('retired', $filterBy, _INT) != -1) {
1661+ $SQL .= " AND user.retired = :retired ";
1662+ $params['retired'] = Kit::GetParam('retired', $filterBy, _INT);
1663+ }
1664+
1665+ // Sorting?
1666+ if (is_array($sortOrder))
1667+ $SQL .= 'ORDER BY ' . implode(',', $sortOrder);
1668+
1669+ //Debug::Audit(sprintf('Retrieving list of users with SQL: %s. Params: %s', $SQL, var_export($params, true)));
1670+
1671+ $sth = $dbh->prepare($SQL);
1672+ $sth->execute($params);
1673+
1674+ foreach ($sth->fetchAll() as $row) {
1675+ $user = new Userdata();
1676+ $user->userId = Kit::ValidateParam($row['userId'], _INT);
1677+ $user->userName = Kit::ValidateParam($row['userName'], _STRING);
1678+ $user->userTypeId = Kit::ValidateParam($row['userTypeId'], _INT);
1679+ $user->loggedIn = Kit::ValidateParam($row['loggedIn'], _INT);
1680+ $user->email = Kit::ValidateParam($row['email'], _STRING);
1681+ $user->homePage = Kit::ValidateParam($row['homePage'], _STRING);
1682+ $user->lastAccessed = Kit::ValidateParam($row['lastAccessed'], _INT);
1683+ $user->newUserWizard = Kit::ValidateParam($row['newUserWizard'], _INT);
1684+ $user->retired = Kit::ValidateParam($row['retired'], _INT);
1685+
1686+ $entries[] = $user;
1687+ }
1688+
1689+ return $entries;
1690+ }
1691+ catch (Exception $e) {
1692+
1693+ Debug::LogEntry('error', $e->getMessage(), get_class(), __FUNCTION__);
1694+
1695+ return false;
1696+ }
1697+ }
1698
1699 public function Delete() {
1700 if (!isset($this->userId) || $this->userId == 0)
1701
1702=== modified file 'server/lib/include.php'
1703--- server/lib/include.php 2014-11-25 11:09:21 +0000
1704+++ server/lib/include.php 2015-01-09 11:27:10 +0000
1705@@ -20,7 +20,7 @@
1706 */
1707 defined('XIBO') or die("Sorry, you are not allowed to directly access this page.<br /> Please press the back button in your browser.");
1708
1709-define('WEBSITE_VERSION', 82);
1710+define('WEBSITE_VERSION', 83);
1711
1712 // No errors reported until we read the settings from the DB
1713 error_reporting(0);
1714
1715=== modified file 'server/lib/modules/module.class.php'
1716--- server/lib/modules/module.class.php 2014-11-24 15:49:49 +0000
1717+++ server/lib/modules/module.class.php 2015-01-09 11:27:10 +0000
1718@@ -30,7 +30,7 @@
1719 public $auth;
1720
1721 // Module Information
1722- private $module_id;
1723+ protected $module_id;
1724 protected $render_as;
1725 public $displayType;
1726 protected $type;
1727@@ -110,12 +110,12 @@
1728
1729 // Members used by forms (routed through the CMS)
1730 $this->showRegionOptions = Kit::GetParam('showRegionOptions', _REQUEST, _INT, 1);
1731+
1732+ // Log
1733+ Debug::LogEntry('audit', 'Module created with MediaID: ' . $mediaid . ' LayoutID: ' . $layoutid . ' and RegionID: ' . $regionid);
1734
1735 // Determine which type this module is
1736- if (!$this->SetModuleInformation())
1737- return false;
1738-
1739- Debug::LogEntry('audit', 'Module created with MediaID: ' . $mediaid . ' LayoutID: ' . $layoutid . ' and RegionID: ' . $regionid);
1740+ $this->SetModuleInformation();
1741
1742 // Either the information from the region - or some blanks
1743 return ($this->SetMediaInformation($this->layoutid, $this->regionid, $this->mediaid, $this->lkid));
1744@@ -138,8 +138,9 @@
1745 'type' => $this->type
1746 ));
1747
1748- $row = $sth->fetch();
1749-
1750+ if (!$row = $sth->fetch())
1751+ return;
1752+
1753 $this->module_id = Kit::ValidateParam($row['ModuleID'], _INT);
1754 $this->schemaVersion = Kit::ValidateParam($row['SchemaVersion'], _INT);
1755 $this->regionSpecific = Kit::ValidateParam($row['RegionSpecific'], _INT);
1756@@ -172,13 +173,40 @@
1757 catch (Exception $e) {
1758
1759 Debug::LogEntry('error', $e->getMessage());
1760-
1761- return $this->SetError(__('Unable to create Module [No registered modules of this type] - please refer to the Module Documentation.'));
1762+
1763+ if (!$this->IsError())
1764+ $this->SetError(__('Unable to create Module [No registered modules of this type] - please refer to the Module Documentation.'));
1765+
1766+ throw $e;
1767 }
1768
1769 return true;
1770 }
1771
1772+ protected final function saveSettings()
1773+ {
1774+ // Save
1775+ try {
1776+ $dbh = PDOConnect::init();
1777+
1778+ $sth = $dbh->prepare('UPDATE module SET settings = :settings WHERE moduleid = :moduleId');
1779+ Debug::Audit(var_export(array(
1780+ 'moduleId' => $this->module_id,
1781+ 'settings' => json_encode($this->settings)
1782+ ), true));
1783+ $sth->execute(array(
1784+ 'moduleId' => $this->module_id,
1785+ 'settings' => json_encode($this->settings)
1786+ ));
1787+ }
1788+ catch (Exception $e) {
1789+
1790+ Debug::LogEntry('error', $e->getMessage());
1791+
1792+ trigger_error(__('Cannot Save Settings'), E_USER_ERROR);
1793+ }
1794+ }
1795+
1796 /**
1797 * Gets the information about this Media on this region on this layout
1798 * @return
1799@@ -194,6 +222,9 @@
1800
1801 if ($this->mediaid != '' && $this->regionid != '' && $this->layoutid != '')
1802 {
1803+ if ($this->module_id == 0)
1804+ $this->ThrowError(__('Module "' . $this->type . '" does not exist or is not installed. Please visit the module administration page or contact your administrator'));
1805+
1806 // Existing media that is assigned to a layout
1807 $this->existingMedia = true;
1808 $this->assignedMedia = true;
1809@@ -1362,8 +1393,12 @@
1810 * @param <type> $width
1811 * @param <type> $height
1812 */
1813- public function Preview($width, $height) {
1814- return '<div style="text-align:center;"><img alt="' . $this->type . ' thumbnail" src="theme/default/img/forms/' . $this->type . '.gif" /></div>';
1815+ public function Preview($width, $height, $scaleOverride = 0)
1816+ {
1817+ if ($this->previewEnabled == 0)
1818+ return '<div style="text-align:center;"><img alt="' . $this->type . ' thumbnail" src="theme/default/img/forms/' . $this->type . '.gif" /></div>';
1819+
1820+ return $this->PreviewAsClient($width, $height, $scaleOverride);
1821 }
1822
1823 /**
1824@@ -1997,7 +2032,7 @@
1825
1826 protected function ThrowError($errNo, $errMessage = '') {
1827 $this->SetError($errNo, $errMessage);
1828- throw new Exception(sprintf('Module Class: Error Number [%d] Error Message [%s]', $errNo, $errMessage));
1829+ throw new Exception(sprintf('%s [%d]', $this->GetErrorMessage(), $this->GetErrorNumber()));
1830 }
1831
1832 public function IsValid() {
1833
1834=== modified file 'server/lib/pages/admin.class.php'
1835--- server/lib/pages/admin.class.php 2014-11-25 11:09:21 +0000
1836+++ server/lib/pages/admin.class.php 2015-01-09 11:27:10 +0000
1837@@ -283,7 +283,7 @@
1838 public function SetMaxDebug()
1839 {
1840 $response = new ResponseManager();
1841- $setting = new Setting($db);
1842+ $setting = new Setting();
1843
1844 if (!$setting->Edit('audit', 'audit'))
1845 trigger_error(__('Cannot set audit to On'));
1846@@ -298,9 +298,8 @@
1847 */
1848 public function SetMinDebug()
1849 {
1850- $db =& $this->db;
1851 $response = new ResponseManager();
1852- $setting = new Setting($db);
1853+ $setting = new Setting();
1854
1855 if (!$setting->Edit('audit', 'error'))
1856 trigger_error(__('Cannot set audit to Off'), E_USER_ERROR);
1857@@ -315,9 +314,8 @@
1858 */
1859 public function SetServerProductionMode()
1860 {
1861- $db =& $this->db;
1862 $response = new ResponseManager();
1863- $setting = new Setting($db);
1864+ $setting = new Setting();
1865
1866 if (!$setting->Edit('SERVER_MODE', 'Production'))
1867 {
1868@@ -334,9 +332,8 @@
1869 */
1870 public function SetServerTestMode()
1871 {
1872- $db =& $this->db;
1873 $response = new ResponseManager();
1874- $setting = new Setting($db);
1875+ $setting = new Setting();
1876
1877 if (!$setting->Edit('SERVER_MODE', 'Test'))
1878 {
1879
1880=== modified file 'server/lib/pages/content.class.php'
1881--- server/lib/pages/content.class.php 2014-10-31 12:25:45 +0000
1882+++ server/lib/pages/content.class.php 2015-01-09 11:27:10 +0000
1883@@ -57,54 +57,59 @@
1884 Theme::Set('form_meta', '<input type="hidden" name="p" value="content"><input type="hidden" name="q" value="LibraryGrid">');
1885
1886 $formFields = array();
1887- $formFields[] = FormManager::AddText('filter_name', __('Name'), $filter_name, NULL, 'n');
1888- $formFields[] = FormManager::AddCombo(
1889- 'filter_owner',
1890- __('Owner'),
1891- $filter_owner,
1892- $db->GetArray("SELECT 0 AS UserID, 'All' AS UserName UNION SELECT DISTINCT user.UserID, user.UserName FROM `media` INNER JOIN `user` ON media.UserID = user.UserID "),
1893- 'UserID',
1894- 'UserName',
1895- NULL,
1896- 'o');
1897-
1898- $types = $db->GetArray("SELECT Module AS moduleid, Name AS module FROM `module` WHERE RegionSpecific = 0 AND Enabled = 1 ORDER BY 2");
1899- array_unshift($types, array('moduleid' => '', 'module' => 'All'));
1900- $formFields[] = FormManager::AddCombo(
1901- 'filter_type',
1902- __('Type'),
1903- $filter_type,
1904- $types,
1905- 'moduleid',
1906- 'module',
1907- NULL,
1908- 'y');
1909-
1910- $formFields[] = FormManager::AddCombo(
1911- 'filter_retired',
1912- __('Retired'),
1913- $filter_retired,
1914- array(array('retiredid' => 1, 'retired' => 'Yes'), array('retiredid' => 0, 'retired' => 'No')),
1915- 'retiredid',
1916- 'retired',
1917- NULL,
1918- 'r');
1919-
1920- $formFields[] = FormManager::AddCheckbox('filter_duration_in_seconds', __('Duration in Seconds'),
1921- $filter_duration_in_seconds, NULL,
1922- 's');
1923-
1924- $formFields[] = FormManager::AddCheckbox('showTags', __('Show Tags'),
1925- $showTags, NULL,
1926- 't');
1927-
1928- $formFields[] = FormManager::AddCheckbox('filter_showThumbnail', __('Show Thumbnails'),
1929- $filter_showThumbnail, NULL,
1930- 't');
1931-
1932- $formFields[] = FormManager::AddCheckbox('XiboFilterPinned', __('Keep Open'),
1933- $filter_pinned, NULL,
1934- 'k');
1935+ $formFields[] = FormManager::AddText('filter_name', __('Name'), $filter_name, NULL, 'n');
1936+
1937+ // Users we have permission to see
1938+ $users = $this->user->userList();
1939+ array_unshift($users, array('userid' => '', 'username' => 'All'));
1940+
1941+ $formFields[] = FormManager::AddCombo(
1942+ 'filter_owner',
1943+ __('Owner'),
1944+ $filter_owner,
1945+ $users,
1946+ 'userid',
1947+ 'username',
1948+ NULL,
1949+ 'o');
1950+
1951+ $types = $db->GetArray("SELECT Module AS moduleid, Name AS module FROM `module` WHERE RegionSpecific = 0 AND Enabled = 1 ORDER BY 2");
1952+ array_unshift($types, array('moduleid' => '', 'module' => 'All'));
1953+ $formFields[] = FormManager::AddCombo(
1954+ 'filter_type',
1955+ __('Type'),
1956+ $filter_type,
1957+ $types,
1958+ 'moduleid',
1959+ 'module',
1960+ NULL,
1961+ 'y');
1962+
1963+ $formFields[] = FormManager::AddCombo(
1964+ 'filter_retired',
1965+ __('Retired'),
1966+ $filter_retired,
1967+ array(array('retiredid' => 1, 'retired' => 'Yes'), array('retiredid' => 0, 'retired' => 'No')),
1968+ 'retiredid',
1969+ 'retired',
1970+ NULL,
1971+ 'r');
1972+
1973+ $formFields[] = FormManager::AddCheckbox('filter_duration_in_seconds', __('Duration in Seconds'),
1974+ $filter_duration_in_seconds, NULL,
1975+ 's');
1976+
1977+ $formFields[] = FormManager::AddCheckbox('showTags', __('Show Tags'),
1978+ $showTags, NULL,
1979+ 't');
1980+
1981+ $formFields[] = FormManager::AddCheckbox('filter_showThumbnail', __('Show Thumbnails'),
1982+ $filter_showThumbnail, NULL,
1983+ 't');
1984+
1985+ $formFields[] = FormManager::AddCheckbox('XiboFilterPinned', __('Keep Open'),
1986+ $filter_pinned, NULL,
1987+ 'k');
1988
1989 // Call to render the template
1990 Theme::Set('header_text', __('Library'));
1991
1992=== modified file 'server/lib/pages/dataset.class.php'
1993--- server/lib/pages/dataset.class.php 2014-08-15 14:40:03 +0000
1994+++ server/lib/pages/dataset.class.php 2015-01-09 11:27:10 +0000
1995@@ -313,7 +313,12 @@
1996 Theme::Set('form_action', 'index.php?p=dataset&q=DeleteDataSet');
1997 Theme::Set('form_meta', '<input type="hidden" name="datasetid" value="' . $dataSetId . '" />');
1998
1999- Theme::Set('form_fields', array(FormManager::AddMessage(__('Are you sure you want to delete?'))));
2000+ $formFields = array();
2001+ $formFields[] = FormManager::AddMessage(__('Are you sure you want to delete?'));
2002+ $formFields[] = FormManager::AddCheckbox('deleteData', __('Delete any associated data?'), NULL,
2003+ __('Please tick the box if you would like to delete all the Data contained in this DataSet'), 'c');
2004+
2005+ Theme::Set('form_fields', $formFields);
2006
2007 $response->SetFormRequestResponse(NULL, __('Delete this DataSet?'), '350px', '200px');
2008 $response->AddButton(__('Help'), 'XiboHelpRender("' . HelpManager::Link('DataSet', 'Delete') . '")');
2009@@ -339,6 +344,11 @@
2010 trigger_error(__('Access Denied'));
2011
2012 $dataSetObject = new DataSet($db);
2013+
2014+ if ($dataSetObject->hasData($dataSetId) && Kit::GetParam('deleteData', _POST, _CHECKBOX) == 0)
2015+ trigger_error(__('There is data assigned to this data set, cannot delete.'), E_USER_ERROR);
2016+
2017+ // Proceed with the delete
2018 if (!$dataSetObject->Delete($dataSetId))
2019 trigger_error($dataSetObject->GetErrorMessage(), E_USER_ERROR);
2020
2021
2022=== modified file 'server/lib/pages/display.class.php'
2023--- server/lib/pages/display.class.php 2014-11-13 17:44:55 +0000
2024+++ server/lib/pages/display.class.php 2015-01-09 11:27:10 +0000
2025@@ -81,7 +81,7 @@
2026
2027 $formFields[] = FormManager::AddCombo(
2028 'filter_showThumbnail',
2029- __('Thumbnails'),
2030+ __('Screen Shot Thumbnails'),
2031 $filter_showThumbnail,
2032 array(
2033 array('key' => 0, 'value' => __('None')),
2034@@ -374,8 +374,9 @@
2035
2036 $cols = array(
2037 array('name' => 'displayid', 'title' => __('ID')),
2038+ array('name' => 'displayWithLink', 'title' => __('Display')),
2039+ array('name' => 'status', 'title' => __('Status'), 'icons' => true),
2040 array('name' => 'licensed', 'title' => __('License'), 'icons' => true),
2041- array('name' => 'displayWithLink', 'title' => __('Display')),
2042 array('name' => 'description', 'title' => __('Description'), 'hidden' => ($filter_showThumbnail == 1 || $filter_showThumbnail == 2)),
2043 array('name' => 'layout', 'title' => __('Default Layout'), 'hidden' => ($filter_showThumbnail == 1 || $filter_showThumbnail == 2)),
2044 array('name' => 'inc_schedule', 'title' => __('Interleave Default'), 'icons' => true, 'hidden' => ($filter_showThumbnail == 1 || $filter_showThumbnail == 2)),
2045@@ -389,35 +390,38 @@
2046 );
2047
2048 Theme::Set('table_cols', $cols);
2049- Theme::Set('rowClass', 'mediainventorystatus');
2050+ Theme::Set('rowClass', 'rowColor');
2051
2052 $rows = array();
2053
2054- foreach($displays as $row)
2055- {
2056+ foreach($displays as $row) {
2057 // VNC Template as display name?
2058- if ($vncTemplate != '' && $row['clientaddress'] != '')
2059- {
2060+ if ($vncTemplate != '' && $row['clientaddress'] != '') {
2061 if ($linkTarget == '')
2062 $linkTarget = '_top';
2063
2064 $row['displayWithLink'] = sprintf('<a href="' . $vncTemplate . '" title="VNC to ' . $row['display'] . '" target="' . $linkTarget . '">' . Theme::Prepare($row['display']) . '</a>', $row['clientaddress']);
2065 }
2066+ else {
2067+ $row['displayWithLink'] = $row['display'];
2068+ }
2069
2070 // Format last accessed
2071 $row['lastaccessed'] = DateManager::getLocalDate($row['lastaccessed']);
2072
2073 // Create some login lights
2074- $row['mediainventorystatus'] = ($row['mediainventorystatus'] == 1) ? 'success' : (($row['mediainventorystatus'] == 2) ? 'danger' : 'warning');
2075+ $row['rowColor'] = ($row['mediainventorystatus'] == 1) ? 'success' : (($row['mediainventorystatus'] == 2) ? 'danger' : 'warning');
2076+ $row['status'] = ($row['mediainventorystatus'] == 1) ? 1 : (($row['mediainventorystatus'] == 2) ? 0 : -1);
2077
2078 // Thumbnail
2079 $row['thumbnail'] = '';
2080- if ($filter_showThumbnail == 1 && file_exists(Config::GetSetting('LIBRARY_LOCATION') . 'screenshots/' . $row['displayid'] . '_screenshot.jpg')) {
2081+ // If we aren't logged in, and we are showThumbnail == 2, then show a circle
2082+ if ($filter_showThumbnail == 2 && $row['loggedin'] == 0) {
2083+ $row['thumbnail'] = '<i class="fa fa-times-circle"></i>';
2084+ }
2085+ else if ($filter_showThumbnail <> 0 && file_exists(Config::GetSetting('LIBRARY_LOCATION') . 'screenshots/' . $row['displayid'] . '_screenshot.jpg')) {
2086 $row['thumbnail'] = '<a data-toggle="lightbox" data-type="image" href="index.php?p=display&q=ScreenShot&DisplayId=' . $row['displayid'] . '"><img class="display-screenshot" src="index.php?p=display&q=ScreenShot&DisplayId=' . $row['displayid'] . '" /></a>';
2087 }
2088- else if ($filter_showThumbnail == 2) {
2089- $row['thumbnail'] = '<i class="fa fa-times-circle"></i>';
2090- }
2091
2092 // Edit and Delete buttons first
2093 if ($row['edit'] == 1) {
2094@@ -710,6 +714,14 @@
2095 if (!$document->loadXML($mediaInventoryXml))
2096 trigger_error(__('Invalid Media Inventory'), E_USER_ERROR);
2097
2098+ $cols = array(
2099+ array('name' => 'id', 'title' => __('ID')),
2100+ array('name' => 'type', 'title' => __('Type')),
2101+ array('name' => 'complete', 'title' => __('Complete')),
2102+ array('name' => 'last_checked', 'title' => __('Last Checked'))
2103+ );
2104+ Theme::Set('table_cols', $cols);
2105+
2106 // Need to parse the XML and return a set of rows
2107 $xpath = new DOMXPath($document);
2108 $fileNodes = $xpath->query("//file");
2109@@ -731,10 +743,7 @@
2110 // Store the table rows
2111 Theme::Set('table_rows', $rows);
2112
2113- // Initialise the theme and capture the output
2114- $output = Theme::RenderReturn('display_form_mediainventory');
2115-
2116- $response->SetFormRequestResponse($output, __('Media Inventory'), '550px', '350px');
2117+ $response->SetFormRequestResponse(Theme::RenderReturn('table_render'), __('Media Inventory'), '550px', '350px');
2118 $response->AddButton(__('Help'), 'XiboHelpRender("' . HelpManager::Link('Display', 'MediaInventory') . '")');
2119 $response->AddButton(__('Close'), 'XiboDialogClose()');
2120 $response->Respond();
2121
2122=== modified file 'server/lib/pages/layout.class.php'
2123--- server/lib/pages/layout.class.php 2014-11-12 17:20:55 +0000
2124+++ server/lib/pages/layout.class.php 2015-01-09 11:27:10 +0000
2125@@ -127,13 +127,18 @@
2126 $formFields = array();
2127 $formFields[] = FormManager::AddText('filter_layout', __('Name'), $layout, NULL, 'l');
2128 $formFields[] = FormManager::AddText('filter_tags', __('Tags'), $tags, NULL, 't');
2129+
2130+ // Users we have permission to see
2131+ $users = $this->user->userList();
2132+ array_unshift($users, array('userid' => '', 'username' => 'All'));
2133+
2134 $formFields[] = FormManager::AddCombo(
2135 'filter_userid',
2136 __('Owner'),
2137 $owner,
2138- $db->GetArray("SELECT 0 AS UserID, 'All' AS UserName UNION SELECT DISTINCT user.UserID, user.UserName FROM `layout` INNER JOIN `user` ON layout.UserID = user.UserID "),
2139- 'UserID',
2140- 'UserName',
2141+ $users,
2142+ 'userid',
2143+ 'username',
2144 NULL,
2145 'r');
2146 $formFields[] = FormManager::AddCombo(
2147
2148=== modified file 'server/lib/pages/log.class.php'
2149--- server/lib/pages/log.class.php 2014-11-03 11:12:50 +0000
2150+++ server/lib/pages/log.class.php 2015-01-09 11:27:10 +0000
2151@@ -284,6 +284,8 @@
2152 $output = Theme::RenderReturn('table_render');
2153
2154 $response->initialSortOrder = 2;
2155+ $response->dialogClass = 'modal-big';
2156+ $response->dialogTitle = __('Recent Log Messages');
2157 $response->pageSize = 10;
2158 $response->SetGridResponse($output);
2159 $response->Respond();
2160
2161=== modified file 'server/lib/pages/module.class.php'
2162--- server/lib/pages/module.class.php 2014-11-17 15:15:42 +0000
2163+++ server/lib/pages/module.class.php 2015-01-09 11:27:10 +0000
2164@@ -82,36 +82,36 @@
2165 Theme::Set('form_meta', '<input type="hidden" name="p" value="module"><input type="hidden" name="q" value="Grid">');
2166 Theme::Set('pager', ResponseManager::Pager($id));
2167
2168- //
2169 // Do we have any modules to install?!
2170- //
2171- // Get a list of matching files in the modules folder
2172- $files = glob('modules/*.module.php');
2173-
2174- // Get a list of all currently installed modules
2175- try {
2176- $dbh = PDOConnect::init();
2177-
2178- $sth = $dbh->prepare("SELECT CONCAT('modules/', LOWER(Module), '.module.php') AS Module FROM `module`");
2179- $sth->execute();
2180-
2181- $rows = $sth->fetchAll();
2182- $installed = array();
2183-
2184- foreach($rows as $row)
2185- $installed[] = $row['Module'];
2186- }
2187- catch (Exception $e) {
2188- trigger_error(__('Cannot get installed modules'), E_USER_ERROR);
2189- }
2190-
2191- // Compare the two
2192- $to_install = array_diff($files, $installed);
2193-
2194- if (count($to_install) > 0) {
2195- Theme::Set('module_install_url', 'index.php?p=module&q=Install&module=');
2196- Theme::Set('to_install', $to_install);
2197- Theme::Set('prepend', Theme::RenderReturn('module_page_install_modules'));
2198+ if (Config::GetSetting('MODULE_CONFIG_LOCKED_CHECKB') != 'Checked') {
2199+ // Get a list of matching files in the modules folder
2200+ $files = glob('modules/*.module.php');
2201+
2202+ // Get a list of all currently installed modules
2203+ try {
2204+ $dbh = PDOConnect::init();
2205+
2206+ $sth = $dbh->prepare("SELECT CONCAT('modules/', LOWER(Module), '.module.php') AS Module FROM `module`");
2207+ $sth->execute();
2208+
2209+ $rows = $sth->fetchAll();
2210+ $installed = array();
2211+
2212+ foreach($rows as $row)
2213+ $installed[] = $row['Module'];
2214+ }
2215+ catch (Exception $e) {
2216+ trigger_error(__('Cannot get installed modules'), E_USER_ERROR);
2217+ }
2218+
2219+ // Compare the two
2220+ $to_install = array_diff($files, $installed);
2221+
2222+ if (count($to_install) > 0) {
2223+ Theme::Set('module_install_url', 'index.php?p=module&q=Install&module=');
2224+ Theme::Set('to_install', $to_install);
2225+ Theme::Set('prepend', Theme::RenderReturn('module_page_install_modules'));
2226+ }
2227 }
2228
2229 // Call to render the template
2230@@ -138,7 +138,8 @@
2231 $SQL .= ' ValidExtensions, ';
2232 $SQL .= ' ImageUri, ';
2233 $SQL .= ' PreviewEnabled, ';
2234- $SQL .= ' assignable ';
2235+ $SQL .= ' assignable, ';
2236+ $SQL .= ' settings ';
2237 $SQL .= ' FROM `module` ';
2238 $SQL .= ' ORDER BY Name ';
2239
2240@@ -174,6 +175,7 @@
2241 $row['enabled'] = Kit::ValidateParam($module['Enabled'], _INT);
2242 $row['preview_enabled'] = Kit::ValidateParam($module['PreviewEnabled'], _INT);
2243 $row['assignable'] = Kit::ValidateParam($module['assignable'], _INT);
2244+ $row['settings'] = json_decode(Kit::ValidateParam($module['settings'], _HTMLSTRING), true);
2245
2246 // Initialise array of buttons, because we might not have any
2247 $row['buttons'] = array();
2248@@ -189,6 +191,14 @@
2249 );
2250 }
2251
2252+ // Are there any buttons we need to provide as part of the module?
2253+ if (isset($row['settings']['buttons'])) {
2254+ foreach($row['settings']['buttons'] as $button) {
2255+ $button['text'] = __($button['text']);
2256+ $row['buttons'][] = $button;
2257+ }
2258+ }
2259+
2260 $rows[] = $row;
2261 }
2262
2263
2264=== modified file 'server/lib/pages/stats.class.php'
2265--- server/lib/pages/stats.class.php 2014-10-21 14:46:29 +0000
2266+++ server/lib/pages/stats.class.php 2015-01-09 11:27:10 +0000
2267@@ -344,6 +344,17 @@
2268 $toDt = strtotime(Kit::GetParam('todt', _POST, _STRING));
2269 $displayId = Kit::GetParam('displayid', _POST, _INT);
2270
2271+ // Get an array of display id this user has access to.
2272+ $displays = $this->user->DisplayList();
2273+ $displayIds = array();
2274+
2275+ foreach($displays as $display) {
2276+ $displayIds[] = $display['displayid'];
2277+ }
2278+
2279+ if (count($displayIds) <= 0)
2280+ trigger_error(__('No displays with View permissions'), E_USER_ERROR);
2281+
2282 // Get some data for a bandwidth chart
2283 try {
2284 $dbh = PDOConnect::init();
2285@@ -364,7 +375,8 @@
2286 ON display.displayId = stat.displayId
2287 WHERE start <= :end
2288 AND end >= :start
2289- AND type = :type ';
2290+ AND type = :type
2291+ AND display.displayId IN (' . implode(',', $displayIds) . ') ';
2292
2293 if ($displayId != 0) {
2294 $SQL .= ' AND display.displayId = :displayId ';
2295@@ -413,6 +425,17 @@
2296 $fromDt = strtotime(Kit::GetParam('fromdt', _POST, _STRING));
2297 $toDt = strtotime(Kit::GetParam('todt', _POST, _STRING));
2298
2299+ // Get an array of display id this user has access to.
2300+ $displays = $this->user->DisplayList();
2301+ $displayIds = array();
2302+
2303+ foreach($displays as $display) {
2304+ $displayIds[] = $display['displayid'];
2305+ }
2306+
2307+ if (count($displayIds) <= 0)
2308+ trigger_error(__('No displays with View permissions'), E_USER_ERROR);
2309+
2310 // Get some data for a bandwidth chart
2311 try {
2312 $dbh = PDOConnect::init();
2313@@ -439,7 +462,8 @@
2314 ';
2315
2316 $SQL .= ' WHERE month > :month
2317- AND month < :month2 ';
2318+ AND month < :month2
2319+ AND display.displayId IN (' . implode(',', $displayIds) . ') ';
2320
2321 if ($displayId != 0) {
2322 $SQL .= ' AND display.displayid = :displayid ';
2323@@ -493,7 +517,7 @@
2324
2325 // Set up some suffixes
2326 $suffixes = array('bytes', 'k', 'M', 'G', 'T');
2327- Theme::Set('bandwidthWidgetUnits', $suffixes[$base]);
2328+ Theme::Set('bandwidthWidgetUnits', (isset($suffixes[$base]) ? $suffixes[$base] : ''));
2329
2330 $output = Theme::RenderReturn('stats_page_bandwidth');
2331
2332
2333=== modified file 'server/lib/pages/timeline.class.php'
2334--- server/lib/pages/timeline.class.php 2014-11-12 17:20:55 +0000
2335+++ server/lib/pages/timeline.class.php 2015-01-09 11:27:10 +0000
2336@@ -751,8 +751,15 @@
2337 Debug::LogEntry('audit', sprintf('Permission Granted to View MediaID: %s', $mediaId), 'layout', 'TimeLine');
2338
2339 // Create a media module to handle all the complex stuff
2340- require_once("modules/$mediaType.module.php");
2341- $tmpModule = new $mediaType($db, $user, $mediaId, $layoutId, $regionId, $lkId);
2342+ try {
2343+ require_once("modules/$mediaType.module.php");
2344+ $tmpModule = new $mediaType($db, $user, $mediaId, $layoutId, $regionId, $lkId);
2345+ }
2346+ catch (Exception $e) {
2347+ Debug::Audit('Caught exception from Module Create');
2348+ trigger_error($e->getMessage(), E_USER_ERROR);
2349+ }
2350+
2351 $mediaName = $tmpModule->GetName();
2352 $transitionIn = $tmpModule->GetTransition('in');
2353 $transitionOut = $tmpModule->GetTransition('out');
2354
2355=== modified file 'server/lib/pages/user.class.php'
2356--- server/lib/pages/user.class.php 2014-10-17 16:13:12 +0000
2357+++ server/lib/pages/user.class.php 2015-01-09 11:27:10 +0000
2358@@ -122,38 +122,30 @@
2359 // Pinned option?
2360 setSession('user_admin', 'Filter', Kit::GetParam('XiboFilterPinned', _REQUEST, _CHECKBOX, 'off'));
2361
2362+ // Filter our users?
2363+ $filterBy = array();
2364+
2365 // Generate the results
2366 $sql = "SELECT user.UserID, user.UserName, user.usertypeid, user.loggedin, user.lastaccessed, user.email, user.homepage, user.retired ";
2367 $sql .= " FROM `user` ";
2368 $sql .= " WHERE 1 = 1 ";
2369
2370- // Normal users can only see themselves
2371- if ($user->usertypeid == 3)
2372- $sql .= sprintf(" AND userid = %d ", $user->userid);
2373-
2374 // Filter on User Type
2375- if($filter_usertypeid != 0)
2376- $sql .= sprintf(" AND usertypeid = %d ", $filter_usertypeid);
2377+ if ($filter_usertypeid != 0)
2378+ $filterBy['userTypeId'] = $filter_usertypeid;
2379
2380 // Filter on User Name
2381 if ($filter_username != '')
2382- $sql .= " AND UserName LIKE '%" . sprintf("%s", $db->escape_string($filter_username) . "%' ");
2383-
2384- $sql .= " ORDER by UserName";
2385-
2386- //Debug::LogEntry('audit', $sql);
2387+ $filterBy['userName'] = $filter_username;
2388
2389 // Load results into an array
2390- $users = $db->GetArray($sql);
2391+ $users = $user->userList(array('userName'), $filterBy);
2392
2393 if (!is_array($users))
2394- {
2395- trigger_error($db->error());
2396 trigger_error(__('Error getting list of users'), E_USER_ERROR);
2397- }
2398-
2399+
2400 $cols = array(
2401- array('name' => 'UserName', 'title' => __('Name')),
2402+ array('name' => 'username', 'title' => __('Name')),
2403 array('name' => 'homepage', 'title' => __('Homepage')),
2404 array('name' => 'email', 'title' => __('Email')),
2405 array('name' => 'retired', 'title' => __('Retired?'), 'icons' => true)
2406@@ -164,7 +156,7 @@
2407
2408 foreach ($users as $row) {
2409
2410- $row['groupid'] = $user->getGroupFromID($row['UserID'], true);
2411+ $row['groupid'] = $user->getGroupFromID($row['userid'], true);
2412
2413 // Super admins have some buttons
2414 if ($user->usertypeid == 1) {
2415@@ -172,14 +164,14 @@
2416 // Edit
2417 $row['buttons'][] = array(
2418 'id' => 'user_button_edit',
2419- 'url' => 'index.php?p=user&q=DisplayForm&userID=' . $row['UserID'],
2420+ 'url' => 'index.php?p=user&q=DisplayForm&userID=' . $row['userid'],
2421 'text' => __('Edit')
2422 );
2423
2424 // Delete
2425 $row['buttons'][] = array(
2426 'id' => 'user_button_delete',
2427- 'url' => 'index.php?p=user&q=DeleteForm&userID=' . $row['UserID'],
2428+ 'url' => 'index.php?p=user&q=DeleteForm&userID=' . $row['userid'],
2429 'text' => __('Delete')
2430 );
2431
2432@@ -200,21 +192,21 @@
2433 // Applications
2434 $row['buttons'][] = array(
2435 'id' => 'user_button_applications',
2436- 'url' => 'index.php?p=oauth&q=UserTokens&userID=' . $row['UserID'],
2437+ 'url' => 'index.php?p=oauth&q=UserTokens&userID=' . $row['userid'],
2438 'text' => __('Applications')
2439 );
2440
2441 // Set Home Page
2442 $row['buttons'][] = array(
2443 'id' => 'user_button_homepage',
2444- 'url' => 'index.php?p=user&q=SetUserHomePageForm&userid=' . $row['UserID'],
2445+ 'url' => 'index.php?p=user&q=SetUserHomePageForm&userid=' . $row['userid'],
2446 'text' => __('Set Homepage')
2447 );
2448
2449 // Set Password
2450 $row['buttons'][] = array(
2451 'id' => 'user_button_delete',
2452- 'url' => 'index.php?p=user&q=SetPasswordForm&userid=' . $row['UserID'],
2453+ 'url' => 'index.php?p=user&q=SetPasswordForm&userid=' . $row['userid'],
2454 'text' => __('Set Password')
2455 );
2456 }
2457@@ -252,15 +244,12 @@
2458 $initialGroupId = Kit::GetParam('groupid', _POST, _INT);
2459
2460 // Validation
2461- if ($username=="")
2462- {
2463- trigger_error("Please enter a User Name.", E_USER_ERROR);
2464- }
2465+ if ($username == '' || strlen($username) > 50)
2466+ trigger_error(__('User name must be between 1 and 50 characters.'), E_USER_ERROR);
2467+
2468 if ($password=="")
2469- {
2470 trigger_error("Please enter a Password.", E_USER_ERROR);
2471- }
2472-
2473+
2474 if ($homepage == "")
2475 $homepage = "dashboard";
2476
2477@@ -342,10 +331,8 @@
2478 $retired = Kit::GetParam('retired', _POST, _CHECKBOX);
2479
2480 // Validation
2481- if ($username == "")
2482- {
2483- trigger_error(__("Please enter a User Name."), E_USER_ERROR);
2484- }
2485+ if ($username == '' || strlen($username) > 50)
2486+ trigger_error(__('User name must be between 1 and 50 characters.'), E_USER_ERROR);
2487
2488 // Check for duplicate user name
2489 $sqlcheck = " ";
2490@@ -563,7 +550,7 @@
2491 // Render the return and output
2492 $formFields = array();
2493 $formFields[] = FormManager::AddText('edit_username', __('User Name'), $username,
2494- __('The Login Name of the user.'), 'n', 'required');
2495+ __('The Login Name of the user.'), 'n', 'required maxlength="50"');
2496
2497 $formFields[] = FormManager::AddPassword('edit_password', __('Password'), $password,
2498 __('The Password for this user.'), 'p', 'required');
2499
2500=== modified file 'server/lib/service/service_v3.wsdl'
2501--- server/lib/service/service_v3.wsdl 2014-10-31 15:27:56 +0000
2502+++ server/lib/service/service_v3.wsdl 2015-01-09 11:27:10 +0000
2503@@ -42,15 +42,6 @@
2504 <message name="ScheduleResponse">
2505 <part name="ScheduleXml" type="xsd:string" />
2506 </message>
2507- <message name="RecieveXmlLogRequest">
2508- <part name="serverKey" type="xsd:string" />
2509- <part name="hardwareKey" type="xsd:string" />
2510- <part name="xml" type="xsd:string" />
2511- <part name="version" type="xsd:string" />
2512- </message>
2513- <message name="RecieveXmlLogResponse">
2514- <part name="success" type="xsd:boolean" />
2515- </message>
2516 <message name="BlackListRequest">
2517 <part name="serverKey" type="xsd:string" />
2518 <part name="hardwareKey" type="xsd:string" />
2519
2520=== modified file 'server/lib/service/xmdssoap3.class.php'
2521--- server/lib/service/xmdssoap3.class.php 2014-11-25 11:09:21 +0000
2522+++ server/lib/service/xmdssoap3.class.php 2015-01-09 11:27:10 +0000
2523@@ -32,12 +32,6 @@
2524 private $defaultLayoutId;
2525 private $version_instructions;
2526
2527- public function __construct()
2528- {
2529- global $db;
2530- $this->db =& $db;
2531- }
2532-
2533 /**
2534 * Registers a new display
2535 * @param <type> $serverKey
2536@@ -64,7 +58,7 @@
2537 try {
2538 $dbh = PDOConnect::init();
2539 $sth = $dbh->prepare('
2540- SELECT licensed
2541+ SELECT licensed, displayid
2542 FROM display
2543 WHERE license = :hardwareKey');
2544
2545@@ -97,10 +91,10 @@
2546
2547 // Touch the display record
2548 $displayObject = new Display();
2549- $displayObject->Touch($displayid);
2550+ $displayObject->Touch($row['displayid']);
2551
2552 // Log Bandwidth
2553- $this->LogBandwidth($displayid, Bandwidth::$REGISTER, strlen($active));
2554+ $this->LogBandwidth($row['displayid'], Bandwidth::$REGISTER, strlen($active));
2555
2556 Debug::LogEntry('audit', $active, get_class(), __FUNCTION__);
2557
2558@@ -114,18 +108,12 @@
2559 */
2560 function RequiredFiles($serverKey, $hardwareKey, $version)
2561 {
2562- $db =& $this->db;
2563-
2564 // Sanitize
2565- $serverKey = Kit::ValidateParam($serverKey, _STRING);
2566- $hardwareKey = Kit::ValidateParam($hardwareKey, _STRING);
2567- $version = Kit::ValidateParam($version, _STRING);
2568- $rfLookahead = Kit::ValidateParam(Config::GetSetting('REQUIRED_FILES_LOOKAHEAD'), _INT);
2569-
2570- // Make sure we are talking the same language
2571- if (!$this->CheckVersion($version))
2572- throw new SoapFault('Sender', 'Your client is not of the correct version for communication with this server.');
2573-
2574+ $serverKey = Kit::ValidateParam($serverKey, _STRING);
2575+ $hardwareKey = Kit::ValidateParam($hardwareKey, _STRING);
2576+ $rfLookahead = Kit::ValidateParam(Config::GetSetting('REQUIRED_FILES_LOOKAHEAD'), _INT);
2577+
2578+ // Check the serverKey matches
2579 if ($serverKey != Config::GetSetting('SERVER_KEY'))
2580 throw new SoapFault('Sender', 'The Server key you entered does not match with the server key at this address');
2581
2582@@ -140,12 +128,15 @@
2583 throw new SoapFault('Sender', 'This display is not licensed.');
2584
2585 if ($this->isAuditing == 1)
2586- Debug::LogEntry("audit", '[IN] with hardware key: ' . $hardwareKey, 'xmds', 'RequiredFiles', '', $this->displayId);
2587-
2588+ Debug::LogEntry("audit", '[IN] with hardware key: ' . $hardwareKey, "xmds", "RequiredFiles");
2589+
2590+ // Remove all Nonces for this display
2591+ $nonce = new Nonce();
2592+ $nonce->RemoveAllXmdsNonce($this->displayId);
2593+
2594+ // Build a new RF
2595 $requiredFilesXml = new DOMDocument("1.0");
2596- $fileElements = $requiredFilesXml->createElement("files");
2597- $fileElements->setAttribute('version_instructions', $this->version_instructions);
2598-
2599+ $fileElements = $requiredFilesXml->createElement("files");
2600 $requiredFilesXml->appendChild($fileElements);
2601
2602 // Hour to hour time bands for the query
2603@@ -157,158 +148,158 @@
2604 $fromFilter = $fromFilter - ($fromFilter % 3600);
2605 $toFilter = $rfLookahead - ($rfLookahead % 3600);
2606
2607- Debug::LogEntry('audit', sprintf('FromDT = %s. ToDt = %s', date('Y-m-d h:i:s', $fromFilter), date('Y-m-d h:i:s', $toFilter)), 'xmds', 'RequiredFiles', '', $this->displayId);
2608-
2609- // Get a list of all layout ids in the schedule right now.
2610- $SQL = " SELECT DISTINCT layout.layoutID ";
2611- $SQL .= " FROM `campaign` ";
2612- $SQL .= " INNER JOIN schedule_detail ON schedule_detail.CampaignID = campaign.CampaignID ";
2613- $SQL .= " INNER JOIN `lkcampaignlayout` ON lkcampaignlayout.CampaignID = campaign.CampaignID ";
2614- $SQL .= " INNER JOIN `layout` ON lkcampaignlayout.LayoutID = layout.LayoutID ";
2615- $SQL .= " INNER JOIN lkdisplaydg ON lkdisplaydg.DisplayGroupID = schedule_detail.DisplayGroupID ";
2616- $SQL .= sprintf(" WHERE lkdisplaydg.DisplayID = %d ", $this->displayId);
2617- $SQL .= sprintf(" AND schedule_detail.FromDT < %d AND schedule_detail.ToDT > %d ", $toFilter, $fromFilter);
2618- $SQL .= " AND layout.retired = 0 ";
2619-
2620 if ($this->isAuditing == 1)
2621- Debug::LogEntry("audit", $SQL, 'xmds', 'RequiredFiles', '', $this->displayId);
2622-
2623- if (!$results = $db->query($SQL))
2624- {
2625- trigger_error($db->error());
2626+ Debug::LogEntry('audit', sprintf('FromDT = %s. ToDt = %s', date('Y-m-d h:i:s', $fromFilter), date('Y-m-d h:i:s', $toFilter)), 'xmds', 'RequiredFiles', '', $this->displayId);
2627+
2628+ try {
2629+ $dbh = PDOConnect::init();
2630+
2631+ // Get a list of all layout ids in the schedule right now.
2632+ $SQL = " SELECT DISTINCT layout.layoutID ";
2633+ $SQL .= " FROM `campaign` ";
2634+ $SQL .= " INNER JOIN schedule ON schedule.CampaignID = campaign.CampaignID ";
2635+ $SQL .= " INNER JOIN schedule_detail ON schedule_detail.eventID = schedule.eventID ";
2636+ $SQL .= " INNER JOIN `lkcampaignlayout` ON lkcampaignlayout.CampaignID = campaign.CampaignID ";
2637+ $SQL .= " INNER JOIN `layout` ON lkcampaignlayout.LayoutID = layout.LayoutID ";
2638+ $SQL .= " INNER JOIN lkdisplaydg ON lkdisplaydg.DisplayGroupID = schedule_detail.DisplayGroupID ";
2639+ $SQL .= " WHERE lkdisplaydg.DisplayID = :displayId ";
2640+ $SQL .= " AND schedule_detail.FromDT < :fromdt AND schedule_detail.ToDT > :todt ";
2641+ $SQL .= " AND layout.retired = 0 ";
2642+
2643+ $sth = $dbh->prepare($SQL);
2644+ $sth->execute(array(
2645+ 'displayId' => $this->displayId,
2646+ 'fromdt' => $toFilter,
2647+ 'todt' => $fromFilter
2648+ ));
2649+
2650+ // Our layout list will always include the default layout
2651+ $layouts = array();
2652+ $layouts[] = $this->defaultLayoutId;
2653+
2654+ // Build up the other layouts into an array
2655+ foreach ($sth->fetchAll() as $row)
2656+ $layouts[] = Kit::ValidateParam($row['layoutID'], _INT);
2657+ }
2658+ catch (Exception $e) {
2659+ Debug::LogEntry('error', $e->getMessage(), get_class(), __FUNCTION__);
2660 return new SoapFault('Sender', 'Unable to get a list of layouts');
2661 }
2662
2663- // Our layout list will always include the default layout
2664- $layouts = array();
2665- $layouts[] = $this->defaultLayoutId;
2666-
2667- // Build up the other layouts into an array
2668- while ($row = $db->get_assoc_row($results))
2669- $layouts[] = Kit::ValidateParam($row['layoutID'], _INT);
2670-
2671 // Create a comma separated list to pass into the query which gets file nodes
2672 $layoutIdList = implode(',', $layouts);
2673
2674- // Add file nodes to the $fileElements
2675- $SQL = " SELECT 'layout' AS RecordType, layout.layoutID AS path, layout.layoutID AS id, MD5(layout.xml) AS `MD5`, NULL AS FileSize, layout.background, layout.xml AS xml ";
2676- $SQL .= " FROM layout ";
2677- $SQL .= sprintf(" WHERE layout.layoutid IN (%s) ", $layoutIdList);
2678- $SQL .= " UNION ";
2679- $SQL .= " SELECT 'media' AS RecordType, storedAs AS path, media.mediaID AS id, media.`MD5`, media.FileSize, NULL AS background, NULL AS xml ";
2680- $SQL .= " FROM media ";
2681- $SQL .= " INNER JOIN lklayoutmedia ";
2682- $SQL .= " ON lklayoutmedia.MediaID = media.MediaID ";
2683- $SQL .= " INNER JOIN layout ";
2684- $SQL .= " ON layout.LayoutID = lklayoutmedia.LayoutID";
2685- $SQL .= sprintf(" WHERE layout.layoutid IN (%s) ", $layoutIdList);
2686- $SQL .= "
2687- UNION
2688- SELECT 'media' AS RecordType, storedAs AS path, media.mediaID AS id, media.`MD5`, media.FileSize, NULL AS background, NULL AS xml
2689- FROM `media`
2690- INNER JOIN `lkmediadisplaygroup`
2691- ON lkmediadisplaygroup.mediaid = media.MediaID
2692- INNER JOIN lkdisplaydg
2693- ON lkdisplaydg.DisplayGroupID = lkmediadisplaygroup.DisplayGroupID
2694- ";
2695- $SQL .= sprintf(" WHERE lkdisplaydg.DisplayID = %d ", $this->displayId);
2696- $SQL .= " ORDER BY RecordType DESC";
2697-
2698- if ($this->isAuditing == 1)
2699- Debug::LogEntry("audit", $SQL, 'xmds', 'RequiredFiles', '', $this->displayId);
2700-
2701- if (!$results = $db->query($SQL))
2702- {
2703- trigger_error($db->error());
2704+ try {
2705+ $dbh = PDOConnect::init();
2706+
2707+ // Add file nodes to the $fileElements
2708+ $SQL = "
2709+ SELECT 1 AS DownloadOrder, 'media' AS RecordType, storedAs AS path, media.mediaID AS id, media.`MD5`, media.FileSize, NULL AS xml
2710+ FROM `media`
2711+ WHERE media.type = 'font'
2712+ OR (media.type = 'module' AND media.moduleSystemFile = 1)
2713+ UNION
2714+ ";
2715+ $SQL .= " SELECT 4 AS DownloadOrder, 'layout' AS RecordType, layout.layoutID AS path, layout.layoutID AS id, MD5(layout.xml) AS `MD5`, NULL AS FileSize, layout.xml AS xml ";
2716+ $SQL .= " FROM layout ";
2717+ $SQL .= sprintf(" WHERE layout.layoutid IN (%s) ", $layoutIdList);
2718+ $SQL .= " UNION ";
2719+ $SQL .= " SELECT 3 AS DownloadOrder, 'media' AS RecordType, storedAs AS path, media.mediaID AS id, media.`MD5`, media.FileSize, NULL AS xml ";
2720+ $SQL .= " FROM media ";
2721+ $SQL .= " INNER JOIN lklayoutmedia ";
2722+ $SQL .= " ON lklayoutmedia.MediaID = media.MediaID ";
2723+ $SQL .= " INNER JOIN layout ";
2724+ $SQL .= " ON layout.LayoutID = lklayoutmedia.LayoutID";
2725+ $SQL .= sprintf(" WHERE layout.layoutid IN (%s) ", $layoutIdList);
2726+ $SQL .= "
2727+ UNION
2728+ SELECT 2 AS DownloadOrder, 'media' AS RecordType, storedAs AS path, media.mediaID AS id, media.`MD5`, media.FileSize, NULL AS xml
2729+ FROM `media`
2730+ INNER JOIN `lkmediadisplaygroup`
2731+ ON lkmediadisplaygroup.mediaid = media.MediaID
2732+ INNER JOIN lkdisplaydg
2733+ ON lkdisplaydg.DisplayGroupID = lkmediadisplaygroup.DisplayGroupID
2734+ ";
2735+ $SQL .= " WHERE lkdisplaydg.DisplayID = :displayId ";
2736+ $SQL .= " ORDER BY DownloadOrder, RecordType DESC";
2737+
2738+ $sth = $dbh->prepare($SQL);
2739+ $sth->execute(array(
2740+ 'displayId' => $this->displayId
2741+ ));
2742+
2743+ // Prepare a SQL statement in case we need to update the MD5 and FileSize on media nodes.
2744+ $mediaSth = $dbh->prepare('UPDATE media SET `MD5` = :md5, FileSize = :size WHERE MediaID = :mediaid');
2745+
2746+ // What is the send file mode?
2747+ $sendFileMode = Config::GetSetting('SENDFILE_MODE');
2748+
2749+ foreach ($sth->fetchAll() as $row) {
2750+ $recordType = Kit::ValidateParam($row['RecordType'], _WORD);
2751+ $path = Kit::ValidateParam($row['path'], _STRING);
2752+ $id = Kit::ValidateParam($row['id'], _STRING);
2753+ $md5 = Kit::ValidateParam($row['MD5'], _HTMLSTRING);
2754+ $fileSize = Kit::ValidateParam($row['FileSize'], _INT);
2755+ $xml = Kit::ValidateParam($row['xml'], _HTMLSTRING);
2756+ $mediaNonce = '';
2757+
2758+ if ($recordType == 'layout') {
2759+ // For layouts the MD5 column is the layout xml
2760+ $fileSize = strlen($xml);
2761+
2762+ if ($this->isAuditing == 1)
2763+ Debug::LogEntry("audit", 'MD5 for layoutid ' . $id . ' is: [' . $md5 . ']', "xmds", "RequiredFiles");
2764+
2765+ // Add nonce
2766+ $nonce->AddXmdsNonce('layout', $this->displayId, NULL, $fileSize, NULL, $id);
2767+ }
2768+ else if ($recordType == 'media') {
2769+ // If they are empty calculate them and save them back to the media.
2770+ if ($md5 == '' || $fileSize == 0) {
2771+
2772+ $md5 = md5_file($libraryLocation.$path);
2773+ $fileSize = filesize($libraryLocation.$path);
2774+
2775+ // Update the media record with this information
2776+ $mediaSth->execute(array('md5' => $md5, 'size' => $fileSize, 'mediaid' => $id));
2777+ }
2778+
2779+ // Add nonce
2780+ $mediaNonce = $nonce->AddXmdsNonce('file', $this->displayId, $id, $fileSize, $path);
2781+ }
2782+ else {
2783+ continue;
2784+ }
2785+
2786+ // Add the file node
2787+ $file = $requiredFilesXml->createElement("file");
2788+ $file->setAttribute("type", $recordType);
2789+ $file->setAttribute("id", $id);
2790+ $file->setAttribute("size", $fileSize);
2791+ $file->setAttribute("md5", $md5);
2792+ $file->setAttribute("download", 'xmds');
2793+ $file->setAttribute("path", $path);
2794+
2795+ $fileElements->appendChild($file);
2796+ }
2797+ }
2798+ catch (Exception $e) {
2799+ Debug::LogEntry('error', $e->getMessage());
2800 return new SoapFault('Sender', 'Unable to get a list of files');
2801 }
2802
2803- // Added paths
2804- $paths = array();
2805-
2806- while ($row = $db->get_assoc_row($results))
2807- {
2808- $recordType = Kit::ValidateParam($row['RecordType'], _WORD);
2809- $path = Kit::ValidateParam($row['path'], _STRING);
2810- $id = Kit::ValidateParam($row['id'], _STRING);
2811- $md5 = Kit::ValidateParam($row['MD5'], _HTMLSTRING);
2812- $fileSize = Kit::ValidateParam($row['FileSize'], _INT);
2813- $background = Kit::ValidateParam($row['background'], _STRING);
2814- $xml = Kit::ValidateParam($row['xml'], _HTMLSTRING);
2815-
2816- if ($recordType == 'layout')
2817- {
2818- // For layouts the MD5 column is the layout xml
2819- $fileSize = strlen($xml);
2820-
2821- if ($this->isAuditing == 1)
2822- Debug::LogEntry("audit", 'MD5 for layoutid ' . $id . ' is: [' . $md5 . ']', 'xmds', 'RequiredFiles', '', $this->displayId);
2823- }
2824- else if ($recordType == 'media')
2825- {
2826- // If they are empty calculate them and save them back to the media.
2827- if ($md5 == '' || $fileSize == 0)
2828- {
2829- $md5 = md5_file($libraryLocation.$path);
2830- $fileSize = filesize($libraryLocation.$path);
2831-
2832- // Update the media record with this information
2833- $SQL = sprintf("UPDATE media SET `MD5` = '%s', FileSize = %d WHERE MediaID = %d", $md5, $fileSize, $id);
2834-
2835- if (!$db->query($SQL))
2836- trigger_error($db->error());
2837- }
2838- }
2839- else
2840- {
2841- continue;
2842- }
2843-
2844- // Add the file node
2845- $file = $requiredFilesXml->createElement("file");
2846-
2847- $file->setAttribute("type", $recordType);
2848- $file->setAttribute("path", $path);
2849- $file->setAttribute("id", $id);
2850- $file->setAttribute("size", $fileSize);
2851- $file->setAttribute("md5", $md5);
2852-
2853- $fileElements->appendChild($file);
2854-
2855- // Add this to the paths array
2856- $paths[] = $path;
2857-
2858- // If this is a layout type and there is a background then add the background node
2859- // TODO: We need to alter the layout table to have a background ID rather than a path
2860- // TODO: We need to alter the background edit method to create a lklayoutmedia link for
2861- // background images (and maintain it when they change)
2862- if ($recordType == 'layout' && $background != '' && !in_array($background, $paths))
2863- {
2864- // Also append another file node for the background image (if there is one)
2865- $file = $requiredFilesXml->createElement("file");
2866- $file->setAttribute("type", "media");
2867- $file->setAttribute("path", $background);
2868- $file->setAttribute("md5", md5_file($libraryLocation.$background));
2869- $file->setAttribute("size", filesize($libraryLocation.$background));
2870- $fileElements->appendChild($file);
2871-
2872- // Add this to the paths array
2873- $paths[] = $background;
2874- }
2875- }
2876-
2877 Kit::ClassLoader('layout');
2878
2879 // Go through each layout and see if we need to supply any resource nodes.
2880 foreach ($layouts as $layoutId) {
2881 // Load the layout XML and work out if we have any ticker / text / dataset media items
2882- $layout = new Layout($db);
2883+ $layout = new Layout();
2884
2885 $layoutInformation = $layout->LayoutInformation($layoutId);
2886
2887 foreach($layoutInformation['regions'] as $region) {
2888 foreach($region['media'] as $media) {
2889- if ($media['mediatype'] == 'ticker' || $media['mediatype'] == 'text' || $media['mediatype'] == 'datasetview' || $media['mediatype'] == 'webpage') {
2890+ if ($media['render'] == 'html' || $media['mediatype'] == 'ticker' || $media['mediatype'] == 'text' || $media['mediatype'] == 'datasetview' || $media['mediatype'] == 'webpage' || $media['mediatype'] == 'embedded') {
2891 // Append this item to required files
2892 $file = $requiredFilesXml->createElement("file");
2893 $file->setAttribute('type', 'resource');
2894@@ -319,6 +310,8 @@
2895 $file->setAttribute('updated', (isset($media['updated']) ? $media['updated'] : 0));
2896
2897 $fileElements->appendChild($file);
2898+
2899+ $nonce->AddXmdsNonce('resource', $this->displayId, NULL, NULL, NULL, $layoutId, $region['regionid'], $media['mediaid']);
2900 }
2901 }
2902 }
2903@@ -330,70 +323,65 @@
2904
2905 $fileElements->appendChild($blackList);
2906
2907- // Populate
2908- $SQL = "SELECT MediaID
2909- FROM blacklist
2910- WHERE DisplayID = " . $this->displayId . "
2911- AND isIgnored = 0";
2912-
2913- if (!$results = $db->query($SQL))
2914- {
2915- trigger_error($db->error());
2916+ try {
2917+ $dbh = PDOConnect::init();
2918+
2919+ $sth = $dbh->prepare('SELECT MediaID FROM blacklist WHERE DisplayID = :displayid AND isIgnored = 0');
2920+ $sth->execute(array(
2921+ 'displayid' => $this->displayId
2922+ ));
2923+
2924+ // Add a black list element for each file
2925+ foreach ($sth->fetchAll() as $row) {
2926+ $file = $requiredFilesXml->createElement("file");
2927+ $file->setAttribute("id", $row['MediaID']);
2928+
2929+ $blackList->appendChild($file);
2930+ }
2931+ }
2932+ catch (Exception $e) {
2933+ Debug::LogEntry('error', $e->getMessage());
2934 return new SoapFault('Sender', 'Unable to get a list of blacklisted files');
2935 }
2936
2937- // Add a black list element for each file
2938- while ($row = $db->get_row($results))
2939- {
2940- $file = $requiredFilesXml->createElement("file");
2941- $file->setAttribute("id", $row[0]);
2942-
2943- $blackList->appendChild($file);
2944- }
2945-
2946 // Phone Home?
2947 $this->PhoneHome();
2948
2949 if ($this->isAuditing == 1)
2950- Debug::LogEntry("audit", $requiredFilesXml->saveXML(), 'xmds', 'RequiredFiles', '', $this->displayId);
2951-
2952+ Debug::LogEntry("audit", "[OUT]" . $requiredFilesXml->saveXML(), "xmds", "RequiredFiles");
2953+
2954 // Return the results of requiredFiles()
2955 $requiredFilesXml->formatOutput = true;
2956 $output = $requiredFilesXml->saveXML();
2957
2958 // Log Bandwidth
2959- $this->LogBandwidth($this->displayId, 2, strlen($output));
2960+ $this->LogBandwidth($this->displayId, Bandwidth::$RF, strlen($output));
2961
2962 return $output;
2963 }
2964
2965 /**
2966- * Gets the specified file
2967- * @return
2968- * @param $hardwareKey Object
2969- * @param $filePath Object
2970- * @param $fileType Object
2971+ * Get File
2972+ * @param string $serverKey The ServerKey for this CMS
2973+ * @param string $hardwareKey The HardwareKey for this Display
2974+ * @param int $mediaId The ID
2975+ * @param string $fileType The File Type
2976+ * @param int $chunkOffset The Offset of the Chunk Requested
2977+ * @param string $chunkSize The Size of the Chunk Requested
2978 */
2979 function GetFile($serverKey, $hardwareKey, $filePath, $fileType, $chunkOffset, $chunkSize, $version)
2980 {
2981- $db =& $this->db;
2982-
2983 // Sanitize
2984- $serverKey = Kit::ValidateParam($serverKey, _STRING);
2985- $hardwareKey = Kit::ValidateParam($hardwareKey, _STRING);
2986- $fileType = Kit::ValidateParam($fileType, _WORD);
2987- $chunkOffset = Kit::ValidateParam($chunkOffset, _INT);
2988- $chunkSize = Kit::ValidateParam($chunkSize, _INT);
2989- $version = Kit::ValidateParam($version, _STRING);
2990+ $serverKey = Kit::ValidateParam($serverKey, _STRING);
2991+ $hardwareKey = Kit::ValidateParam($hardwareKey, _STRING);
2992+ $filePath = Kit::ValidateParam($filePath, _STRING);
2993+ $fileType = Kit::ValidateParam($fileType, _WORD);
2994+ $chunkOffset = Kit::ValidateParam($chunkOffset, _INT);
2995+ $chunkSize = Kit::ValidateParam($chunkSize, _INT);
2996
2997 $libraryLocation = Config::GetSetting("LIBRARY_LOCATION");
2998
2999- // Make sure we are talking the same language
3000- if (!$this->CheckVersion($version))
3001- {
3002- throw new SoapFault('Receiver', "Your client is not of the correct version for communication with this server.");
3003- }
3004-
3005+ // Check the serverKey matches
3006 if ($serverKey != Config::GetSetting('SERVER_KEY'))
3007 throw new SoapFault('Sender', 'The Server key you entered does not match with the server key at this address');
3008
3009@@ -401,60 +389,67 @@
3010 if (!$this->CheckBandwidth())
3011 throw new SoapFault('Receiver', "Bandwidth Limit exceeded");
3012
3013- //auth this request...
3014+ // Authenticate this request...
3015 if (!$this->AuthDisplay($hardwareKey))
3016- {
3017 throw new SoapFault('Receiver', "This display client is not licensed");
3018- }
3019
3020 if ($this->isAuditing == 1)
3021- {
3022- Debug::LogEntry("audit", "[IN]", "xmds", "GetFile");
3023- Debug::LogEntry("audit", "Params: [$hardwareKey] [$filePath] [$fileType] [$chunkOffset] [$chunkSize]", "xmds", "GetFile");
3024- }
3025-
3026- if ($fileType == "layout")
3027- {
3028- $filePath = Kit::ValidateParam($filePath, _INT);
3029-
3030- $SQL = sprintf("SELECT xml FROM layout WHERE layoutid = %d", $filePath);
3031- if (!$results = $db->query($SQL))
3032- {
3033- trigger_error($db->error());
3034+ Debug::LogEntry("audit", "[IN] Params: [$hardwareKey] [$filePath] [$fileType] [$chunkOffset] [$chunkSize]", "xmds", "GetFile", '', $this->displayId);
3035+
3036+ $nonce = new Nonce();
3037+
3038+ if ($fileType == "layout") {
3039+ $fileId = Kit::ValidateParam($filePath, _INT);
3040+
3041+ // Validate the nonce
3042+ if (!$nonce->AllowedFile('layout', $this->displayId, NULL, $fileId))
3043+ throw new SoapFault('Receiver', 'Requested an invalid file.');
3044+
3045+ try {
3046+ $dbh = PDOConnect::init();
3047+
3048+ $sth = $dbh->prepare('SELECT xml FROM layout WHERE layoutid = :layoutid');
3049+ $sth->execute(array('layoutid' => $fileId));
3050+
3051+ if (!$row = $sth->fetch())
3052+ throw new Exception('No file found with that ID');
3053+
3054+ $file = $row['xml'];
3055+
3056+ // Store file size for bandwidth log
3057+ $chunkSize = strlen($file);
3058+ }
3059+ catch (Exception $e) {
3060+ Debug::LogEntry('error', $e->getMessage());
3061 return new SoapFault('Receiver', 'Unable the find layout.');
3062 }
3063-
3064- $row = $db->get_row($results);
3065- $file = $row[0];
3066-
3067- // Store file size for bandwidth log
3068- $chunkSize = strlen($file);
3069 }
3070- elseif ($fileType == "media")
3071+ else if ($fileType == "media")
3072 {
3073- $filePath = Kit::ValidateParam($filePath, _STRING);
3074-
3075+ // Get the ID
3076 if (strstr($filePath, '/') || strstr($filePath, '\\'))
3077- {
3078 throw new SoapFault('Receiver', "Invalid file path.");
3079- }
3080
3081+ // Validate the nonce
3082+ if (!$nonce->AllowedFile('oldfile', $this->displayId, $filePath))
3083+ throw new SoapFault('Receiver', 'Requested an invalid file.');
3084+
3085 // Return the Chunk size specified
3086- $f = fopen($libraryLocation.$filePath,"r");
3087+ $f = fopen($libraryLocation . $filePath, 'r');
3088
3089 fseek($f, $chunkOffset);
3090
3091 $file = fread($f, $chunkSize);
3092+
3093+ // Store file size for bandwidth log
3094+ $chunkSize = strlen($file);
3095 }
3096- else
3097- {
3098+ else {
3099 throw new SoapFault('Receiver', 'Unknown FileType Requested.');
3100 }
3101
3102- if ($this->isAuditing == 1) Debug::LogEntry("audit", "[OUT]", "xmds", "GetFile");
3103-
3104 // Log Bandwidth
3105- $this->LogBandwidth($this->displayId, 4, $chunkSize);
3106+ $this->LogBandwidth($this->displayId, Bandwidth::$GETFILE, $chunkSize);
3107
3108 return $file;
3109 }
3110@@ -466,18 +461,12 @@
3111 */
3112 function Schedule($serverKey, $hardwareKey, $version)
3113 {
3114- $db =& $this->db;
3115-
3116 // Sanitize
3117- $serverKey = Kit::ValidateParam($serverKey, _STRING);
3118- $hardwareKey = Kit::ValidateParam($hardwareKey, _STRING);
3119- $version = Kit::ValidateParam($version, _STRING);
3120+ $serverKey = Kit::ValidateParam($serverKey, _STRING);
3121+ $hardwareKey = Kit::ValidateParam($hardwareKey, _STRING);
3122 $rfLookahead = Kit::ValidateParam(Config::GetSetting('REQUIRED_FILES_LOOKAHEAD'), _INT);
3123
3124- // Make sure we are talking the same language
3125- if (!$this->CheckVersion($version))
3126- throw new SoapFault('Sender', "Your client is not of the correct version for communication with this server.");
3127-
3128+ // Check the serverKey matches
3129 if ($serverKey != Config::GetSetting('SERVER_KEY'))
3130 throw new SoapFault('Sender', 'The Server key you entered does not match with the server key at this address');
3131
3132@@ -489,9 +478,6 @@
3133 if (!$this->AuthDisplay($hardwareKey))
3134 throw new SoapFault('Sender', "This display client is not licensed");
3135
3136- if ($this->isAuditing == 1)
3137- Debug::LogEntry("audit", "[IN] $hardwareKey", 'xmds', 'Schedule', '', $this->displayId);
3138-
3139 $scheduleXml = new DOMDocument("1.0");
3140 $layoutElements = $scheduleXml->createElement("schedule");
3141
3142@@ -512,57 +498,70 @@
3143
3144 if ($this->isAuditing == 1)
3145 Debug::LogEntry('audit', sprintf('FromDT = %s. ToDt = %s', date('Y-m-d h:i:s', $fromFilter), date('Y-m-d h:i:s', $toFilter)), 'xmds', 'Schedule', '', $this->displayId);
3146-
3147- // Add file nodes to the $fileElements
3148- // Firstly get all the scheduled layouts
3149- $SQL = " SELECT layout.layoutID, schedule_detail.FromDT, schedule_detail.ToDT, schedule_detail.eventID, schedule_detail.is_priority, ";
3150- $SQL .= " (SELECT GROUP_CONCAT(StoredAs) FROM media INNER JOIN lklayoutmedia ON lklayoutmedia.MediaID = media.MediaID WHERE lklayoutmedia.LayoutID = layout.LayoutID GROUP BY lklayoutmedia.LayoutID) AS Dependents";
3151- $SQL .= " FROM `campaign` ";
3152- $SQL .= " INNER JOIN schedule_detail ON schedule_detail.CampaignID = campaign.CampaignID ";
3153- $SQL .= " INNER JOIN `lkcampaignlayout` ON lkcampaignlayout.CampaignID = campaign.CampaignID ";
3154- $SQL .= " INNER JOIN `layout` ON lkcampaignlayout.LayoutID = layout.LayoutID ";
3155- $SQL .= " INNER JOIN lkdisplaydg ON lkdisplaydg.DisplayGroupID = schedule_detail.DisplayGroupID ";
3156- $SQL .= sprintf(" WHERE lkdisplaydg.DisplayID = %d ", $this->displayId);
3157- $SQL .= sprintf(" AND (schedule_detail.FromDT < %d AND schedule_detail.ToDT > %d )", $toFilter, $fromFilter);
3158- $SQL .= " AND layout.retired = 0 ";
3159- $SQL .= " ORDER BY schedule_detail.DisplayOrder, lkcampaignlayout.DisplayOrder, schedule_detail.eventID ";
3160-
3161- if ($this->isAuditing == 1)
3162- Debug::LogEntry("audit", $SQL, 'xmds', 'Schedule', '', $this->displayId);
3163-
3164- // Run the query
3165- if (!$results = $db->query($SQL))
3166- {
3167- trigger_error($db->error());
3168+
3169+ try {
3170+ $dbh = PDOConnect::init();
3171+
3172+ // Get all the module dependants
3173+ $sth = $dbh->prepare("SELECT DISTINCT StoredAs FROM `media` WHERE media.type = 'font' OR (media.type = 'module' AND media.moduleSystemFile = 1) ");
3174+ $sth->execute(array());
3175+ $rows = $sth->fetchAll();
3176+ $moduleDependents = array();
3177+
3178+ foreach($rows as $dependent)
3179+ $moduleDependents[] = $dependent['StoredAs'];
3180+
3181+ // Add file nodes to the $fileElements
3182+ // Firstly get all the scheduled layouts
3183+ $SQL = " SELECT layout.layoutID, schedule_detail.FromDT, schedule_detail.ToDT, schedule.eventID, schedule.is_priority, ";
3184+ $SQL .= " (SELECT GROUP_CONCAT(DISTINCT StoredAs) FROM media INNER JOIN lklayoutmedia ON lklayoutmedia.MediaID = media.MediaID WHERE lklayoutmedia.LayoutID = layout.LayoutID AND lklayoutmedia.regionID <> 'module' GROUP BY lklayoutmedia.LayoutID) AS Dependents";
3185+ $SQL .= " FROM `campaign` ";
3186+ $SQL .= " INNER JOIN schedule ON schedule.CampaignID = campaign.CampaignID ";
3187+ $SQL .= " INNER JOIN schedule_detail ON schedule_detail.eventID = schedule.eventID ";
3188+ $SQL .= " INNER JOIN `lkcampaignlayout` ON lkcampaignlayout.CampaignID = campaign.CampaignID ";
3189+ $SQL .= " INNER JOIN `layout` ON lkcampaignlayout.LayoutID = layout.LayoutID ";
3190+ $SQL .= " INNER JOIN lkdisplaydg ON lkdisplaydg.DisplayGroupID = schedule_detail.DisplayGroupID ";
3191+ $SQL .= " WHERE lkdisplaydg.DisplayID = :displayId ";
3192+ $SQL .= " AND (schedule_detail.FromDT < :fromdt AND schedule_detail.ToDT > :todt )";
3193+ $SQL .= " AND layout.retired = 0 ";
3194+ $SQL .= " ORDER BY schedule.DisplayOrder, lkcampaignlayout.DisplayOrder, schedule_detail.eventID ";
3195+
3196+ $sth = $dbh->prepare($SQL);
3197+ $sth->execute(array(
3198+ 'displayId' => $this->displayId,
3199+ 'fromdt' => $toFilter,
3200+ 'todt' => $fromFilter
3201+ ));
3202+
3203+ // We must have some results in here by this point
3204+ foreach ($sth->fetchAll() as $row) {
3205+ $layoutid = $row[0];
3206+ $fromdt = date('Y-m-d H:i:s', $row[1]);
3207+ $todt = date('Y-m-d H:i:s', $row[2]);
3208+ $scheduleid = $row[3];
3209+ $is_priority = Kit::ValidateParam($row[4], _INT);
3210+ $dependents = implode(',', $moduleDependents) . ',' . Kit::ValidateParam($row[5], _STRING);
3211+
3212+ // Add a layout node to the schedule
3213+ $layout = $scheduleXml->createElement("layout");
3214+
3215+ $layout->setAttribute("file", $layoutid);
3216+ $layout->setAttribute("fromdt", $fromdt);
3217+ $layout->setAttribute("todt", $todt);
3218+ $layout->setAttribute("scheduleid", $scheduleid);
3219+ $layout->setAttribute("priority", $is_priority);
3220+ $layout->setAttribute("dependents", $dependents);
3221+
3222+ $layoutElements->appendChild($layout);
3223+ }
3224+ }
3225+ catch (Exception $e) {
3226+ Debug::LogEntry('error', $e->getMessage());
3227 return new SoapFault('Unable to get A list of layouts for the schedule');
3228 }
3229
3230- // We must have some results in here by this point
3231- while ($row = $db->get_row($results))
3232- {
3233- $layoutid = $row[0];
3234- $fromdt = date('Y-m-d H:i:s', $row[1]);
3235- $todt = date('Y-m-d H:i:s', $row[2]);
3236- $scheduleid = $row[3];
3237- $is_priority = Kit::ValidateParam($row[4], _INT);
3238- $dependents = Kit::ValidateParam($row[5], _STRING);
3239-
3240- // Add a layout node to the schedule
3241- $layout = $scheduleXml->createElement("layout");
3242-
3243- $layout->setAttribute("file", $layoutid);
3244- $layout->setAttribute("fromdt", $fromdt);
3245- $layout->setAttribute("todt", $todt);
3246- $layout->setAttribute("scheduleid", $scheduleid);
3247- $layout->setAttribute("priority", $is_priority);
3248- $layout->setAttribute("dependents", $dependents);
3249-
3250- $layoutElements->appendChild($layout);
3251- }
3252-
3253 // Are we interleaving the default?
3254- if ($this->includeSchedule == 1)
3255- {
3256+ if ($this->includeSchedule == 1) {
3257 // Add as a node at the end of the schedule.
3258 $layout = $scheduleXml->createElement("layout");
3259
3260@@ -571,6 +570,7 @@
3261 $layout->setAttribute("todt", '2030-01-19 00:00:00');
3262 $layout->setAttribute("scheduleid", 0);
3263 $layout->setAttribute("priority", 0);
3264+ $layout->setAttribute("dependents", implode(',', $moduleDependents));
3265
3266 $layoutElements->appendChild($layout);
3267 }
3268@@ -580,16 +580,25 @@
3269 $default->setAttribute("file", $this->defaultLayoutId);
3270 $layoutElements->appendChild($default);
3271
3272+ // Add on a list of global dependants
3273+ $globalDependents = $scheduleXml->createElement("dependants");
3274+
3275+ foreach ($moduleDependents as $dep) {
3276+ $dependent = $scheduleXml->createElement("file", $dep);
3277+ $globalDependents->appendChild($dependent);
3278+ }
3279+ $layoutElements->appendChild($globalDependents);
3280+
3281 // Format the output
3282 $scheduleXml->formatOutput = true;
3283
3284 if ($this->isAuditing == 1)
3285- Debug::LogEntry("audit", $scheduleXml->saveXML(), 'xmds', 'Schedule', '', $this->displayId);
3286+ Debug::LogEntry("audit", $scheduleXml->saveXML(), "xmds", "Schedule");
3287
3288 $output = $scheduleXml->saveXML();
3289
3290 // Log Bandwidth
3291- $this->LogBandwidth($this->displayId, 3, strlen($output));
3292+ $this->LogBandwidth($this->displayId, Bandwidth::$SCHEDULE, strlen($output));
3293
3294 return $output;
3295 }
3296@@ -603,22 +612,14 @@
3297 */
3298 function BlackList($serverKey, $hardwareKey, $mediaId, $type, $reason, $version)
3299 {
3300- $db =& $this->db;
3301-
3302 // Sanitize
3303- $serverKey = Kit::ValidateParam($serverKey, _STRING);
3304- $hardwareKey = Kit::ValidateParam($hardwareKey, _STRING);
3305- $mediaId = Kit::ValidateParam($mediaId, _STRING);
3306- $type = Kit::ValidateParam($type, _STRING);
3307- $reason = Kit::ValidateParam($reason, _STRING);
3308- $version = Kit::ValidateParam($version, _STRING);
3309-
3310- // Make sure we are talking the same language
3311- if (!$this->CheckVersion($version))
3312- {
3313- throw new SoapFault('Receiver', "Your client is not of the correct version for communication with this server.", $serverKey);
3314- }
3315-
3316+ $serverKey = Kit::ValidateParam($serverKey, _STRING);
3317+ $hardwareKey = Kit::ValidateParam($hardwareKey, _STRING);
3318+ $mediaId = Kit::ValidateParam($mediaId, _STRING);
3319+ $type = Kit::ValidateParam($type, _STRING);
3320+ $reason = Kit::ValidateParam($reason, _STRING);
3321+
3322+ // Check the serverKey matches
3323 if ($serverKey != Config::GetSetting('SERVER_KEY'))
3324 throw new SoapFault('Sender', 'The Server key you entered does not match with the server key at this address');
3325
3326@@ -626,58 +627,67 @@
3327 if (!$this->CheckBandwidth())
3328 throw new SoapFault('Receiver', "Bandwidth Limit exceeded");
3329
3330- // Auth this request...
3331+ // Authenticate this request...
3332 if (!$this->AuthDisplay($hardwareKey))
3333- {
3334- throw new SoapFault('Receiver', "This display client is not licensed", $hardwareKey);
3335+ throw new SoapFault('Receiver', "This display client is not licensed", $hardwareKey);
3336+
3337+ if ($this->isAuditing == 1)
3338+ Debug::LogEntry( "audit", "[IN] $xml", "xmds", "BlackList", "", $this->displayId);
3339+
3340+ try {
3341+ $dbh = PDOConnect::init();
3342+
3343+ // Check to see if this media / display is already blacklisted (and not ignored)
3344+ $sth = $dbh->prepare('SELECT BlackListID FROM blacklist WHERE MediaID = :mediaid AND isIgnored = 0 AND DisplayID = :displayid');
3345+ $sth->execute(array(
3346+ 'mediaid' => $mediaId,
3347+ 'displayid' => $this->displayId
3348+ ));
3349+
3350+ $results = $sth->fetchAll();
3351+
3352+ if (count($results) == 0) {
3353+
3354+ $insertSth = $dbh->prepare('
3355+ INSERT INTO blacklist (MediaID, DisplayID, ReportingDisplayID, Reason)
3356+ VALUES (:mediaid, :displayid, :reportingdisplayid, :reason)
3357+ ');
3358+
3359+ // Insert the black list record
3360+ if ($type == BLACKLIST_SINGLE) {
3361+ $insertSth->execute(array(
3362+ 'mediaid' => $mediaId,
3363+ 'displayid' => $displayId,
3364+ 'reportingdisplayid' => $this->displayId,
3365+ 'reason' => $reason
3366+ ));
3367+ }
3368+ else {
3369+ $displaySth = $dbh->prepare('SELECT displayID FROM `display`');
3370+ $displaySth->execute();
3371+
3372+ foreach ($displaySth->fetchAll() as $row) {
3373+
3374+ $insertSth->execute(array(
3375+ 'mediaid' => $mediaId,
3376+ 'displayid' => $row['displayID'],
3377+ 'reportingdisplayid' => $this->displayId,
3378+ 'reason' => $reason
3379+ ));
3380+ }
3381+ }
3382+ }
3383+ else {
3384+ if ($this->isAuditing == 1)
3385+ Debug::LogEntry( "audit", "Media Already BlackListed [$mediaId]", "xmds", "BlackList", "", $this->displayId);
3386+ }
3387 }
3388-
3389- if ($this->isAuditing == 1) Debug::LogEntry( "audit", "[IN]", "xmds", "BlackList", "", $this->displayId);
3390- if ($this->isAuditing == 1) Debug::LogEntry( "audit", "$xml", "xmds", "BlackList", "", $this->displayId);
3391-
3392- // Check to see if this media/display is already blacklisted (and not ignored)
3393- $SQL = "SELECT BlackListID FROM blacklist WHERE MediaID = $mediaId AND isIgnored = 0 AND DisplayID = " . $this->displayId;
3394-
3395- if (!$results = $db->query($SQL))
3396- {
3397- trigger_error($db->error());
3398+ catch (Exception $e) {
3399+ Debug::LogEntry('error', $e->getMessage());
3400 return new SoapFault("Unable to query for BlackList records.");
3401 }
3402
3403- if ($db->num_rows($results) == 0)
3404- {
3405- // Insert the black list record
3406- $SQL = "SELECT displayID FROM display";
3407-
3408- if ($type == BLACKLIST_SINGLE)
3409- $SQL .= " WHERE displayID = " . $this->displayId;
3410-
3411- if (!$displays = $db->query($SQL))
3412- {
3413- trigger_error($db->error());
3414- return new SoapFault("Unable to query for BlackList Displays.");
3415- }
3416-
3417- while ($row = $db->get_row($displays))
3418- {
3419- $displayId = $row[0];
3420-
3421- $SQL = "INSERT INTO blacklist (MediaID, DisplayID, ReportingDisplayID, Reason)
3422- VALUES ($mediaId, $displayId, " . $this->displayId . ", '$reason') ";
3423-
3424- if (!$db->query($SQL))
3425- {
3426- trigger_error($db->error());
3427- return new SoapFault("Unable to insert BlackList records.");
3428- }
3429- }
3430- }
3431- else
3432- {
3433- if ($this->isAuditing == 1) Debug::LogEntry( "audit", "Media Already BlackListed [$mediaId]", "xmds", "BlackList", "", $this->displayId);
3434- }
3435-
3436- if ($this->isAuditing == 1) Debug::LogEntry( "audit", "[OUT]", "xmds", "BlackList", "", $this->displayId);
3437+ $this->LogBandwidth($this->displayId, Bandwidth::$BLACKLIST, strlen($reason));
3438
3439 return true;
3440 }
3441@@ -685,111 +695,91 @@
3442 /**
3443 * Submit client logging
3444 * @return
3445- * @param $version Object
3446 * @param $serverKey Object
3447 * @param $hardwareKey Object
3448 * @param $logXml Object
3449 */
3450 function SubmitLog($version, $serverKey, $hardwareKey, $logXml)
3451 {
3452- $db =& $this->db;
3453-
3454 // Sanitize
3455- $serverKey = Kit::ValidateParam($serverKey, _STRING);
3456- $hardwareKey = Kit::ValidateParam($hardwareKey, _STRING);
3457- $version = Kit::ValidateParam($version, _STRING);
3458- $logXml = Kit::ValidateParam($logXml, _HTMLSTRING);
3459-
3460- // Make sure we are talking the same language
3461- if (!$this->CheckVersion($version))
3462- {
3463- throw new SoapFault('Sender', "Your client is not of the correct version for communication with this server.");
3464- }
3465-
3466+ $serverKey = Kit::ValidateParam($serverKey, _STRING);
3467+ $hardwareKey = Kit::ValidateParam($hardwareKey, _STRING);
3468+ $logXml = Kit::ValidateParam($logXml, _HTMLSTRING);
3469+
3470+ // Check the serverKey matches
3471 if ($serverKey != Config::GetSetting('SERVER_KEY'))
3472- throw new SoapFault('Sender', 'The Server key you entered does not match with the server key at this address');
3473-
3474+ throw new SoapFault('Sender', 'The Server key you entered does not match with the server key at this address. ');
3475+
3476 // Make sure we are sticking to our bandwidth limit
3477 if (!$this->CheckBandwidth())
3478 throw new SoapFault('Receiver', "Bandwidth Limit exceeded");
3479
3480 // Auth this request...
3481 if (!$this->AuthDisplay($hardwareKey))
3482- {
3483 throw new SoapFault('Sender', 'This display client is not licensed.');
3484- }
3485-
3486- if ($this->isAuditing == 1) Debug::LogEntry( "audit", "IN", "xmds", "SubmitLog", "", $this->displayId);
3487- if ($this->isAuditing == 1) Debug::LogEntry( "audit", 'XML [' . $logXml . ']', "xmds", "SubmitLog", "", $this->displayId);
3488+
3489+ if ($this->isAuditing == 1)
3490+ Debug::LogEntry( "audit", 'IN. XML [' . $logXml . ']', "xmds", "SubmitLog", "", $this->displayId);
3491
3492 // Load the XML into a DOMDocument
3493 $document = new DOMDocument("1.0");
3494
3495 if (!$document->loadXML($logXml))
3496- {
3497 throw new SoapFault('Receiver', "XML Cannot be loaded into DOM Document.");
3498- }
3499
3500- foreach ($document->documentElement->childNodes as $node)
3501- {
3502+ foreach ($document->documentElement->childNodes as $node) {
3503+
3504 // Make sure we dont consider any text nodes
3505- if ($node->nodeType == XML_TEXT_NODE) continue;
3506+ if ($node->nodeType == XML_TEXT_NODE)
3507+ continue;
3508
3509- //Zero out the common vars
3510- $date = "";
3511- $message = "";
3512+ // Zero out the common vars
3513+ $date = "";
3514+ $message = "";
3515 $scheduleID = "";
3516- $layoutID = "";
3517- $mediaID = "";
3518- $cat = '';
3519- $method = '';
3520+ $layoutID = "";
3521+ $mediaID = "";
3522+ $cat = '';
3523+ $method = '';
3524 $thread = '';
3525
3526 // This will be a bunch of trace nodes
3527 $message = $node->textContent;
3528
3529- if ($this->isAuditing == 1)
3530- Debug::LogEntry( "audit", 'Trace Message: [' . $message . ']', "xmds", "SubmitLog", "", $this->displayId);
3531+ // if ($this->isAuditing == 1)
3532+ // Debug::LogEntry("audit", 'Trace Message: [' . $message . ']', "xmds", "SubmitLog", "", $this->displayId);
3533
3534 // Each element should have a category and a date
3535- $date = $node->getAttribute('date');
3536- $cat = strtolower($node->getAttribute('category'));
3537+ $date = $node->getAttribute('date');
3538+ $cat = strtolower($node->getAttribute('category'));
3539
3540- if ($date == '' || $cat == '')
3541- {
3542+ if ($date == '' || $cat == '') {
3543 trigger_error('Log submitted without a date or category attribute');
3544 continue;
3545 }
3546
3547 // Get the date and the message (all log types have these)
3548- foreach ($node->childNodes as $nodeElements)
3549- {
3550- if ($nodeElements->nodeName == "scheduleID")
3551- {
3552+ foreach ($node->childNodes as $nodeElements) {
3553+
3554+ if ($nodeElements->nodeName == "scheduleID") {
3555 $scheduleID = $nodeElements->textContent;
3556 }
3557- else if ($nodeElements->nodeName == "layoutID")
3558- {
3559+ else if ($nodeElements->nodeName == "layoutID") {
3560 $layoutID = $nodeElements->textContent;
3561 }
3562- else if ($nodeElements->nodeName == "mediaID")
3563- {
3564+ else if ($nodeElements->nodeName == "mediaID") {
3565 $mediaID = $nodeElements->textContent;
3566 }
3567- else if ($nodeElements->nodeName == "type")
3568- {
3569+ else if ($nodeElements->nodeName == "type") {
3570 $type = $nodeElements->textContent;
3571 }
3572- else if ($nodeElements->nodeName == "method")
3573- {
3574+ else if ($nodeElements->nodeName == "method") {
3575 $method = $nodeElements->textContent;
3576 }
3577- else if ($nodeElements->nodeName == "message")
3578- {
3579+ else if ($nodeElements->nodeName == "message") {
3580 $message = $nodeElements->textContent;
3581 }
3582- else if ($nodeElements->nodeName == "thread")
3583- {
3584+ else if ($nodeElements->nodeName == "thread") {
3585 if ($nodeElements->textContent != '')
3586 $thread = '[' . $nodeElements->textContent . '] ';
3587 }
3588@@ -805,7 +795,7 @@
3589 Debug::LogEntry($logType, $message, 'Client', $thread . $method, $date, $this->displayId, $scheduleID, $layoutID, $mediaID);
3590 }
3591
3592- if ($this->isAuditing == 1) Debug::LogEntry( "audit", "OUT", "xmds", "SubmitLog", "", $this->displayId);
3593+ $this->LogBandwidth($this->displayId, Bandwidth::$SUBMITLOG, strlen($logXml));
3594
3595 return true;
3596 }
3597@@ -813,100 +803,84 @@
3598 /**
3599 * Submit display statistics to the server
3600 * @return
3601- * @param $version Object
3602 * @param $serverKey Object
3603 * @param $hardwareKey Object
3604 * @param $statXml Object
3605 */
3606 function SubmitStats($version, $serverKey, $hardwareKey, $statXml)
3607 {
3608- $db =& $this->db;
3609-
3610 // Sanitize
3611- $serverKey = Kit::ValidateParam($serverKey, _STRING);
3612- $hardwareKey = Kit::ValidateParam($hardwareKey, _STRING);
3613- $version = Kit::ValidateParam($version, _STRING);
3614- $statXml = Kit::ValidateParam($statXml, _HTMLSTRING);
3615-
3616- // Make sure we are talking the same language
3617- if (!$this->CheckVersion($version))
3618- {
3619- throw new SoapFault('Receiver', "Your client is not of the correct version for communication with this server.");
3620- }
3621-
3622+ $serverKey = Kit::ValidateParam($serverKey, _STRING);
3623+ $hardwareKey = Kit::ValidateParam($hardwareKey, _STRING);
3624+ $statXml = Kit::ValidateParam($statXml, _HTMLSTRING);
3625+
3626+ // Check the serverKey matches
3627 if ($serverKey != Config::GetSetting('SERVER_KEY'))
3628 throw new SoapFault('Sender', 'The Server key you entered does not match with the server key at this address');
3629-
3630+
3631 // Make sure we are sticking to our bandwidth limit
3632 if (!$this->CheckBandwidth())
3633 throw new SoapFault('Receiver', "Bandwidth Limit exceeded");
3634
3635 // Auth this request...
3636 if (!$this->AuthDisplay($hardwareKey))
3637- {
3638 throw new SoapFault('Receiver', "This display client is not licensed");
3639- }
3640-
3641- if ($this->isAuditing == 1) Debug::LogEntry( "audit", "IN", "xmds", "SubmitStats", "", $this->displayId);
3642- if ($this->isAuditing == 1) Debug::LogEntry( "audit", "StatXml: [" . $statXml . "]", "xmds", "SubmitStats", "", $this->displayId);
3643+
3644+ if ($this->isAuditing == 1)
3645+ Debug::LogEntry( "audit", "IN. StatXml: [" . $statXml . "]", "xmds", "SubmitStats", "", $this->displayId);
3646
3647 if ($statXml == "")
3648- {
3649 throw new SoapFault('Receiver', "Stat XML is empty.");
3650- }
3651-
3652- // Log
3653- if ($this->isAuditing == 1) Debug::LogEntry( "audit", "About to create Stat Object.", "xmds", "SubmitStats", "", $this->displayId);
3654-
3655- $statObject = new Stat($db);
3656-
3657- // Log
3658- if ($this->isAuditing == 1) Debug::LogEntry( "audit", "About to Create DOMDocument.", "xmds", "SubmitStats", "", $this->displayId);
3659+
3660+ // Log
3661+ $statObject = new Stat();
3662+
3663+ // Log
3664+ if ($this->isAuditing == 1)
3665+ Debug::LogEntry( "audit", "About to Create DOMDocument.", "xmds", "SubmitStats", "", $this->displayId);
3666
3667 // Load the XML into a DOMDocument
3668 $document = new DOMDocument("1.0");
3669 $document->loadXML($statXml);
3670
3671- foreach ($document->documentElement->childNodes as $node)
3672- {
3673+ foreach ($document->documentElement->childNodes as $node) {
3674 // Make sure we dont consider any text nodes
3675- if ($node->nodeType == XML_TEXT_NODE) continue;
3676+ if ($node->nodeType == XML_TEXT_NODE)
3677+ continue;
3678
3679- //Zero out the common vars
3680+ // Zero out the common vars
3681 $fromdt = '';
3682- $todt = '';
3683- $type = '';
3684+ $todt = '';
3685+ $type = '';
3686
3687 $scheduleID = 0;
3688- $layoutID = 0;
3689- $mediaID = '';
3690- $tag = '';
3691+ $layoutID = 0;
3692+ $mediaID = '';
3693+ $tag = '';
3694
3695 // Each element should have these attributes
3696 $fromdt = $node->getAttribute('fromdt');
3697- $todt = $node->getAttribute('todt');
3698- $type = $node->getAttribute('type');
3699+ $todt = $node->getAttribute('todt');
3700+ $type = $node->getAttribute('type');
3701
3702- if ($fromdt == '' || $todt == '' || $type == '')
3703- {
3704+ if ($fromdt == '' || $todt == '' || $type == '') {
3705 trigger_error('Stat submitted without the fromdt, todt or type attributes.');
3706 continue;
3707 }
3708
3709 $scheduleID = $node->getAttribute('scheduleid');
3710- $layoutID = $node->getAttribute('layoutid');
3711- $mediaID = $node->getAttribute('mediaid');
3712- $tag = $node->getAttribute('tag');
3713+ $layoutID = $node->getAttribute('layoutid');
3714+ $mediaID = $node->getAttribute('mediaid');
3715+ $tag = $node->getAttribute('tag');
3716
3717 // Write the stat record with the information we have available to us.
3718- if (!$statObject->Add($type, $fromdt, $todt, $scheduleID, $this->displayId, $layoutID, $mediaID, $tag))
3719- {
3720+ if (!$statObject->Add($type, $fromdt, $todt, $scheduleID, $this->displayId, $layoutID, $mediaID, $tag)) {
3721 trigger_error(sprintf('Stat Add failed with error: %s', $statObject->GetErrorMessage()));
3722 continue;
3723 }
3724 }
3725
3726- if ($this->isAuditing == 1) Debug::LogEntry( "audit", "OUT", "xmds", "SubmitStats", "", $this->displayId);
3727+ $this->LogBandwidth($this->displayId, Bandwidth::$SUBMITSTATS, strlen($statXml));
3728
3729 return true;
3730 }
3731@@ -918,18 +892,12 @@
3732 */
3733 public function MediaInventory($version, $serverKey, $hardwareKey, $inventory)
3734 {
3735- $db =& $this->db;
3736-
3737 // Sanitize
3738- $serverKey = Kit::ValidateParam($serverKey, _STRING);
3739- $hardwareKey = Kit::ValidateParam($hardwareKey, _STRING);
3740- $version = Kit::ValidateParam($version, _STRING);
3741- $inventory = Kit::ValidateParam($inventory, _HTMLSTRING);
3742-
3743- // Make sure we are talking the same language
3744- if (!$this->CheckVersion($version))
3745- throw new SoapFault('Receiver', "Your client is not of the correct version for communication with this server.");
3746-
3747+ $serverKey = Kit::ValidateParam($serverKey, _STRING);
3748+ $hardwareKey = Kit::ValidateParam($hardwareKey, _STRING);
3749+ $inventory = Kit::ValidateParam($inventory, _HTMLSTRING);
3750+
3751+ // Check the serverKey matches
3752 if ($serverKey != Config::GetSetting('SERVER_KEY'))
3753 throw new SoapFault('Sender', 'The Server key you entered does not match with the server key at this address');
3754
3755@@ -941,7 +909,8 @@
3756 if (!$this->AuthDisplay($hardwareKey))
3757 throw new SoapFault('Receiver', 'This display client is not licensed');
3758
3759- if ($this->isAuditing == 1) Debug::LogEntry( 'audit', $inventory, 'xmds', 'MediaInventory', '', $this->displayId);
3760+ if ($this->isAuditing == 1)
3761+ Debug::LogEntry( 'audit', $inventory, 'xmds', 'MediaInventory', '', $this->displayId);
3762
3763 // Check that the $inventory contains something
3764 if ($inventory == '')
3765@@ -951,20 +920,13 @@
3766 $document = new DOMDocument("1.0");
3767 $document->loadXML($inventory);
3768
3769- // Get some information from the media inventory XML and update the display record with it.
3770- $macAddress = Kit::ValidateParam($document->documentElement->getAttribute('macAddress'), _STRING);
3771- $clientType = Kit::ValidateParam($document->documentElement->getAttribute('clientType'), _STRING);
3772- $clientVersion = Kit::ValidateParam($document->documentElement->getAttribute('clientVersion'), _STRING);
3773- $clientCode = Kit::ValidateParam($document->documentElement->getAttribute('clientCode'), _INT);
3774-
3775 // Assume we are complete (but we are getting some)
3776 $mediaInventoryComplete = 1;
3777
3778 $xpath = new DOMXPath($document);
3779 $fileNodes = $xpath->query("//file");
3780
3781- foreach ($fileNodes as $node)
3782- {
3783+ foreach ($fileNodes as $node) {
3784 $mediaId = $node->getAttribute('id');
3785 $complete = $node->getAttribute('complete');
3786 $md5 = $node->getAttribute('md5');
3787@@ -978,8 +940,10 @@
3788 }
3789
3790 // Touch the display record
3791- $displayObject = new Display($db);
3792- $displayObject->Touch($hardwareKey, '', $mediaInventoryComplete, $inventory, $macAddress, $clientType, $clientVersion, $clientCode);
3793+ $displayObject = new Display();
3794+ $displayObject->Touch($this->displayId, array('mediaInventoryStatus' => $mediaInventoryComplete, 'mediaInventoryXml' => $inventory));
3795+
3796+ $this->LogBandwidth($this->displayId, Bandwidth::$MEDIAINVENTORY, strlen($inventory));
3797
3798 return true;
3799 }
3800@@ -991,24 +955,17 @@
3801 * @param <type> $layoutId
3802 * @param <type> $regionId
3803 * @param <type> $mediaId
3804- * @param <type> $version
3805 */
3806 function GetResource($serverKey, $hardwareKey, $layoutId, $regionId, $mediaId, $version)
3807 {
3808- $db =& $this->db;
3809-
3810 // Sanitize
3811 $serverKey = Kit::ValidateParam($serverKey, _STRING);
3812 $hardwareKey = Kit::ValidateParam($hardwareKey, _STRING);
3813 $layoutId = Kit::ValidateParam($layoutId, _INT);
3814 $regionId = Kit::ValidateParam($regionId, _STRING);
3815 $mediaId = Kit::ValidateParam($mediaId, _STRING);
3816- $version = Kit::ValidateParam($version, _STRING);
3817-
3818- // Make sure we are talking the same language
3819- if (!$this->CheckVersion($version))
3820- throw new SoapFault('Receiver', "Your client is not of the correct version for communication with this server.");
3821-
3822+
3823+ // Check the serverKey matches
3824 if ($serverKey != Config::GetSetting('SERVER_KEY'))
3825 throw new SoapFault('Sender', 'The Server key you entered does not match with the server key at this address');
3826
3827@@ -1020,23 +977,32 @@
3828 if (!$this->AuthDisplay($hardwareKey))
3829 throw new SoapFault('Receiver', "This display client is not licensed");
3830
3831+ // Validate the nonce
3832+ $nonce = new Nonce();
3833+ if (!$nonce->AllowedFile('resource', $this->displayId, NULL, $layoutId, $regionId, $mediaId))
3834+ throw new SoapFault('Receiver', 'Requested an invalid file.');
3835+
3836 // What type of module is this?
3837 Kit::ClassLoader('region');
3838- $region = new region($db);
3839+ $region = new region();
3840 $type = $region->GetMediaNodeType($layoutId, $regionId, $mediaId);
3841
3842 if ($type == '')
3843 throw new SoapFault('Receiver', 'Unable to get the media node type');
3844
3845 // Dummy User Object
3846- $user = new User($db);
3847+ $user = new User();
3848 $user->userid = 0;
3849 $user->usertypeid = 1;
3850
3851+ // Initialise the theme (for global styles in GetResource)
3852+ new Theme($user);
3853+ Theme::SetPagename('module');
3854+
3855 // Get the resource from the module
3856 require_once('modules/' . $type . '.module.php');
3857
3858- if (!$module = new $type($db, $user, $mediaId, $layoutId, $regionId))
3859+ if (!$module = new $type(new Database(), $user, $mediaId, $layoutId, $regionId))
3860 throw new SoapFault('Receiver', 'Cannot create module. Check CMS Log');
3861
3862 $resource = $module->GetResource($this->displayId);
3863@@ -1045,7 +1011,7 @@
3864 throw new SoapFault('Receiver', 'Unable to get the media resource');
3865
3866 // Log Bandwidth
3867- $this->LogBandwidth($this->displayId, 5, strlen($resource));
3868+ $this->LogBandwidth($this->displayId, Bandwidth::$GETRESOURCE, strlen($resource));
3869
3870 return $resource;
3871 }
3872@@ -1053,19 +1019,13 @@
3873 /**
3874 * PHONE_HOME if required
3875 */
3876- private function PhoneHome() {
3877-
3878- if (Config::GetSetting('PHONE_HOME') == 'On')
3879- {
3880+ private function PhoneHome()
3881+ {
3882+ if (Config::GetSetting('PHONE_HOME') == 'On') {
3883 // Find out when we last PHONED_HOME :D
3884 // If it's been > 28 days since last PHONE_HOME then
3885- if (Config::GetSetting('PHONE_HOME_DATE') < (time() - (60 * 60 * 24 * 28)))
3886- {
3887- if ($this->isAuditing == 1)
3888- {
3889- Debug::LogEntry("audit", "PHONE_HOME [IN]", "xmds", "RequiredFiles");
3890- }
3891-
3892+ if (Config::GetSetting('PHONE_HOME_DATE') < (time() - (60 * 60 * 24 * 28))) {
3893+
3894 try {
3895 $dbh = PDOConnect::init();
3896
3897@@ -1110,90 +1070,94 @@
3898 * @param <type> $hardwareKey
3899 * @return <type>
3900 */
3901- private function AuthDisplay($hardwareKey)
3902- {
3903- $db =& $this->db;
3904-
3905- // check in the database for this hardwareKey
3906- $SQL = "SELECT licensed, inc_schedule, isAuditing, displayID, defaultlayoutid, loggedin, email_alert, display, version_instructions FROM display WHERE license = '$hardwareKey'";
3907-
3908- if (!$result = $db->query($SQL))
3909- {
3910- trigger_error("License key query failed:" .$db->error());
3911+ private function AuthDisplay($hardwareKey, $status = NULL)
3912+ {
3913+ try {
3914+ $dbh = PDOConnect::init();
3915+
3916+ $sth = $dbh->prepare('
3917+ SELECT licensed, inc_schedule, isAuditing, displayID, defaultlayoutid, loggedin,
3918+ email_alert, display, version_instructions, client_type, client_code, client_version
3919+ FROM display
3920+ WHERE license = :hardwareKey
3921+ ');
3922+
3923+ $sth->execute(array(
3924+ 'hardwareKey' => $hardwareKey
3925+ ));
3926+
3927+ $result = $sth->fetchAll();
3928+
3929+ // Is it there?
3930+ if (count($result) == 0)
3931+ return false;
3932+
3933+ // We have seen this display before, so check the licensed value
3934+ $row = $result[0];
3935+
3936+ if ($row['licensed'] == 0)
3937+ return false;
3938+
3939+ // See if the client was off-line and if appropriate send an alert
3940+ // to say that it has come back on-line
3941+ $this->AlertDisplayUp($row['displayID'], $row['display'], $row['loggedin'], $row['email_alert']);
3942+
3943+ // It is licensed?
3944+ $this->licensed = true;
3945+ $this->includeSchedule = $row['inc_schedule'];
3946+ $this->isAuditing = $row['isAuditing'];
3947+ $this->displayId = $row['displayID'];
3948+ $this->defaultLayoutId = $row['defaultlayoutid'];
3949+ $this->version_instructions = $row['version_instructions'];
3950+ $this->clientType = $row['client_type'];
3951+ $this->clientVersion = $row['client_version'];
3952+ $this->clientCode = $row['client_code'];
3953+
3954+ // Last accessed date on the display
3955+ $displayObject = new Display();
3956+ $displayObject->Touch($this->displayId, array('clientAddress' => Kit::GetParam('REMOTE_ADDR', $_SERVER, _STRING)));
3957+
3958+ return true;
3959+ }
3960+ catch (Exception $e) {
3961+ Debug::LogEntry('error', $e->getMessage());
3962 return false;
3963+ }
3964 }
3965
3966- //Is it there?
3967- if ($db->num_rows($result) == 0)
3968- return false;
3969-
3970- //we have seen this display before, so check the licensed value
3971- $row = $db->get_row($result);
3972-
3973- if ($row[0] == 0)
3974- return false;
3975-
3976- // Pull the client IP address
3977- $clientAddress = Kit::GetParam('REMOTE_ADDR', $_SERVER, _STRING);
3978-
3979- // See if the client was offline and if appropriate send an alert
3980- // to say that it has come back online
3981- if ($row[5] == 0 && $row[6] == 1
3982- && (Config::GetSetting('MAINTENANCE_ENABLED') == 'On' || Config::GetSetting('MAINTENANCE_ENABLED') == 'Protected')
3983- && Config::GetSetting('MAINTENANCE_EMAIL_ALERTS') == 'On')
3984- {
3985- $msgTo = Kit::ValidateParam(Config::GetSetting("mail_to"),_PASSWORD);
3986- $msgFrom = Kit::ValidateParam(Config::GetSetting("mail_from"),_PASSWORD);
3987-
3988- $subject = sprintf(__("Recovery for Display %s"),$row[7]);
3989- $body = sprintf(__("Display %s with ID %d is now back online."), $row[7], $row[3]);
3990-
3991- // Get a list of people that have view access to the display?
3992- if (Config::GetSetting('MAINTENANCE_ALERTS_FOR_VIEW_USERS') == 1) {
3993- foreach (Display::getUsers($row[3]) as $user) {
3994- if ($user['email'] != '') {
3995- Kit::SendEmail($user['email'], $msgFrom, $subject, $body);
3996+ private function AlertDisplayUp($displayId, $display, $loggedIn, $emailAlert)
3997+ {
3998+ $maintenanceEnabled = Config::GetSetting('MAINTENANCE_ENABLED');
3999+
4000+ if ($loggedIn == 0) {
4001+
4002+ // Log display up
4003+ $statObject = new Stat();
4004+ $statObject->displayUp($displayId);
4005+
4006+ // Do we need to email?
4007+ if ($emailAlert == 1 && ($maintenanceEnabled == 'On' || $maintenanceEnabled == 'Protected')
4008+ && Config::GetSetting('MAINTENANCE_EMAIL_ALERTS') == 'On') {
4009+
4010+ $msgTo = Kit::ValidateParam(Config::GetSetting("mail_to") ,_PASSWORD);
4011+ $msgFrom = Kit::ValidateParam(Config::GetSetting("mail_from"), _PASSWORD);
4012+
4013+ $subject = sprintf(__("Recovery for Display %s"), $display);
4014+ $body = sprintf(__("Display %s with ID %d is now back online."), $display, $displayId);
4015+
4016+ // Get a list of people that have view access to the display?
4017+ if (Config::GetSetting('MAINTENANCE_ALERTS_FOR_VIEW_USERS') == 1) {
4018+ foreach (Display::getUsers($displayId) as $user) {
4019+ if ($user['email'] != '') {
4020+ Kit::SendEmail($user['email'], $msgFrom, $subject, $body);
4021+ }
4022 }
4023 }
4024+
4025+ // Send to the original admin contact
4026+ Kit::SendEmail($msgTo, $msgFrom, $subject, $body);
4027 }
4028-
4029- Kit::SendEmail($msgTo, $msgFrom, $subject, $body);
4030- }
4031-
4032- // Last accessed date on the display
4033- $displayObject = new Display($db);
4034- $displayObject->Touch($hardwareKey, $clientAddress);
4035-
4036- // It is licensed?
4037- $this->licensed = true;
4038- $this->includeSchedule = $row[1];
4039- $this->isAuditing = $row[2];
4040- $this->displayId = $row[3];
4041- $this->defaultLayoutId = $row[4];
4042- $this->version_instructions = $row[8];
4043-
4044- return true;
4045- }
4046-
4047- /**
4048- * Checks that the calling service is talking the correct version
4049- * @return
4050- * @param $version Object
4051- */
4052- private function CheckVersion($version)
4053- {
4054- $db =& $this->db;
4055-
4056- // Look up the Service XMDS version from the Version table
4057- $serverVersion = Config::Version('XmdsVersion');
4058-
4059- if ($version != $serverVersion)
4060- {
4061- Debug::LogEntry('audit', sprintf('A Client with an incorrect version connected. Client Version: [%s] Server Version [%s]', $version, $serverVersion));
4062- return false;
4063- }
4064-
4065- return true;
4066+ }
4067 }
4068
4069 /**
4070@@ -1206,13 +1170,23 @@
4071 if ($xmdsLimit <= 0)
4072 return true;
4073
4074- // Test bandwidth for the current month
4075- $startOfMonth = strtotime(date('m').'/02/'.date('Y').' 00:00:00');
4076-
4077- $sql = sprintf('SELECT IFNULL(SUM(Size), 0) AS BandwidthUsage FROM `bandwidth` WHERE Month = %d', $startOfMonth);
4078- $bandwidthUsage = $this->db->GetSingleValue($sql, 'BandwidthUsage', _INT);
4079-
4080- return ($bandwidthUsage >= ($xmdsLimit * 1024)) ? false : true;
4081+ try {
4082+ $dbh = PDOConnect::init();
4083+
4084+ // Test bandwidth for the current month
4085+ $sth = $dbh->prepare('SELECT IFNULL(SUM(Size), 0) AS BandwidthUsage FROM `bandwidth` WHERE Month = :month');
4086+ $sth->execute(array(
4087+ 'month' => strtotime(date('m').'/02/'.date('Y').' 00:00:00')
4088+ ));
4089+
4090+ $bandwidthUsage = $sth->fetchColumn(0);
4091+
4092+ return ($bandwidthUsage >= ($xmdsLimit * 1024)) ? false : true;
4093+ }
4094+ catch (Exception $e) {
4095+ Debug::LogEntry('error', $e->getMessage());
4096+ return false;
4097+ }
4098 }
4099
4100 /**
4101@@ -1222,16 +1196,9 @@
4102 * @param <type> $sizeInBytes
4103 */
4104 private function LogBandwidth($displayId, $type, $sizeInBytes)
4105- {
4106- $startOfMonth = strtotime(date('m').'/02/'.date('Y').' 00:00:00');
4107-
4108- $sql = "INSERT INTO `bandwidth` (Month, Type, DisplayID, Size) VALUES (%d, %d, %d, %d) ";
4109- $sql .= "ON DUPLICATE KEY UPDATE Size = Size + %d ";
4110- $sql = sprintf($sql, $startOfMonth, $type, $displayId, $sizeInBytes, $sizeInBytes);
4111-
4112- $this->db->query($sql);
4113-
4114- return true;
4115+ {
4116+ $bandwidth = new Bandwidth();
4117+ $bandwidth->Log($displayId, $type, $sizeInBytes);
4118 }
4119 }
4120 ?>
4121
4122=== modified file 'server/lib/service/xmdssoap4.class.php'
4123--- server/lib/service/xmdssoap4.class.php 2014-11-25 11:09:21 +0000
4124+++ server/lib/service/xmdssoap4.class.php 2015-01-09 11:27:10 +0000
4125@@ -21,8 +21,8 @@
4126 define('BLACKLIST_ALL', "All");
4127 define('BLACKLIST_SINGLE', "Single");
4128
4129-class XMDSSoap4 {
4130-
4131+class XMDSSoap4
4132+{
4133 private $licensed;
4134 private $includeSchedule;
4135 private $isAuditing;
4136@@ -40,8 +40,8 @@
4137 * @param <type> $displayName
4138 * @return <type>
4139 */
4140- public function RegisterDisplay($serverKey, $hardwareKey, $displayName, $clientType, $clientVersion, $clientCode, $operatingSystem, $macAddress) {
4141-
4142+ public function RegisterDisplay($serverKey, $hardwareKey, $displayName, $clientType, $clientVersion, $clientCode, $operatingSystem, $macAddress)
4143+ {
4144 // Sanitize
4145 $serverKey = Kit::ValidateParam($serverKey, _STRING);
4146 $hardwareKey = Kit::ValidateParam($hardwareKey, _STRING);
4147@@ -138,6 +138,9 @@
4148 $displayProfile->displayProfileId = (empty($row['displayprofileid']) ? 0 : Kit::ValidateParam($row['displayprofileid'], _INT));
4149
4150 if ($displayProfile->displayProfileId == 0) {
4151+ // We need a theme
4152+ new Theme(new User());
4153+
4154 // Load the default profile
4155 $displayProfile->type = $clientType;
4156 $displayProfile->LoadDefault();
4157@@ -213,8 +216,8 @@
4158 * @param string $hardwareKey Display Hardware Key
4159 * @return string $requiredXml Xml Formatted String
4160 */
4161- function RequiredFiles($serverKey, $hardwareKey) {
4162-
4163+ function RequiredFiles($serverKey, $hardwareKey)
4164+ {
4165 // Sanitize
4166 $serverKey = Kit::ValidateParam($serverKey, _STRING);
4167 $hardwareKey = Kit::ValidateParam($hardwareKey, _STRING);
4168@@ -485,8 +488,8 @@
4169 * @param int $chunkOffset The Offset of the Chunk Requested
4170 * @param string $chunkSize The Size of the Chunk Requested
4171 */
4172- function GetFile($serverKey, $hardwareKey, $fileId, $fileType, $chunkOffset, $chunkSize) {
4173-
4174+ function GetFile($serverKey, $hardwareKey, $fileId, $fileType, $chunkOffset, $chunkSize)
4175+ {
4176 // Sanitize
4177 $serverKey = Kit::ValidateParam($serverKey, _STRING);
4178 $hardwareKey = Kit::ValidateParam($hardwareKey, _STRING);
4179@@ -585,7 +588,8 @@
4180 * @return
4181 * @param $hardwareKey Object
4182 */
4183- function Schedule($serverKey, $hardwareKey) {
4184+ function Schedule($serverKey, $hardwareKey)
4185+ {
4186 // Sanitize
4187 $serverKey = Kit::ValidateParam($serverKey, _STRING);
4188 $hardwareKey = Kit::ValidateParam($hardwareKey, _STRING);
4189@@ -735,8 +739,8 @@
4190 * @param $mediaId Object
4191 * @param $type Object
4192 */
4193- function BlackList($serverKey, $hardwareKey, $mediaId, $type, $reason) {
4194-
4195+ function BlackList($serverKey, $hardwareKey, $mediaId, $type, $reason)
4196+ {
4197 // Sanitize
4198 $serverKey = Kit::ValidateParam($serverKey, _STRING);
4199 $hardwareKey = Kit::ValidateParam($hardwareKey, _STRING);
4200@@ -824,8 +828,8 @@
4201 * @param $hardwareKey Object
4202 * @param $logXml Object
4203 */
4204- function SubmitLog($serverKey, $hardwareKey, $logXml) {
4205-
4206+ function SubmitLog($serverKey, $hardwareKey, $logXml)
4207+ {
4208 // Sanitize
4209 $serverKey = Kit::ValidateParam($serverKey, _STRING);
4210 $hardwareKey = Kit::ValidateParam($hardwareKey, _STRING);
4211@@ -932,8 +936,8 @@
4212 * @param $hardwareKey Object
4213 * @param $statXml Object
4214 */
4215- function SubmitStats($serverKey, $hardwareKey, $statXml) {
4216-
4217+ function SubmitStats($serverKey, $hardwareKey, $statXml)
4218+ {
4219 // Sanitize
4220 $serverKey = Kit::ValidateParam($serverKey, _STRING);
4221 $hardwareKey = Kit::ValidateParam($hardwareKey, _STRING);
4222@@ -1015,7 +1019,8 @@
4223 * @param <type> $hardwareKey
4224 * @param <type> $inventory
4225 */
4226- public function MediaInventory($serverKey, $hardwareKey, $inventory) {
4227+ public function MediaInventory($serverKey, $hardwareKey, $inventory)
4228+ {
4229 // Sanitize
4230 $serverKey = Kit::ValidateParam($serverKey, _STRING);
4231 $hardwareKey = Kit::ValidateParam($hardwareKey, _STRING);
4232@@ -1080,8 +1085,8 @@
4233 * @param <type> $regionId
4234 * @param <type> $mediaId
4235 */
4236- function GetResource($serverKey, $hardwareKey, $layoutId, $regionId, $mediaId) {
4237-
4238+ function GetResource($serverKey, $hardwareKey, $layoutId, $regionId, $mediaId)
4239+ {
4240 // Sanitize
4241 $serverKey = Kit::ValidateParam($serverKey, _STRING);
4242 $hardwareKey = Kit::ValidateParam($hardwareKey, _STRING);
4243@@ -1140,7 +1145,8 @@
4244 return $resource;
4245 }
4246
4247- public function NotifyStatus($serverKey, $hardwareKey, $status) {
4248+ public function NotifyStatus($serverKey, $hardwareKey, $status)
4249+ {
4250 // Sanitize
4251 $serverKey = Kit::ValidateParam($serverKey, _STRING);
4252 $hardwareKey = Kit::ValidateParam($hardwareKey, _STRING);
4253@@ -1161,16 +1167,17 @@
4254 if ($this->isAuditing == 1)
4255 Debug::LogEntry('audit', $status, 'xmds', 'Status', '', $this->displayId);
4256
4257+ $this->LogBandwidth($this->displayId, Bandwidth::$NOTIFYSTATUS, strlen($status));
4258+
4259 // Touch the display record
4260 $displayObject = new Display();
4261 $displayObject->Touch($this->displayId, json_decode($status, true));
4262
4263- $this->LogBandwidth($this->displayId, Bandwidth::$NOTIFYSTATUS, strlen($status));
4264-
4265 return true;
4266 }
4267
4268- public function SubmitScreenShot($serverKey, $hardwareKey, $screenShot) {
4269+ public function SubmitScreenShot($serverKey, $hardwareKey, $screenShot)
4270+ {
4271 // Sanitize
4272 $serverKey = Kit::ValidateParam($serverKey, _STRING);
4273 $hardwareKey = Kit::ValidateParam($hardwareKey, _STRING);
4274@@ -1210,8 +1217,8 @@
4275 /**
4276 * PHONE_HOME if required
4277 */
4278- private function PhoneHome() {
4279-
4280+ private function PhoneHome()
4281+ {
4282 if (Config::GetSetting('PHONE_HOME') == 'On') {
4283 // Find out when we last PHONED_HOME :D
4284 // If it's been > 28 days since last PHONE_HOME then
4285@@ -1261,8 +1268,8 @@
4286 * @param <type> $hardwareKey
4287 * @return <type>
4288 */
4289- private function AuthDisplay($hardwareKey, $status = NULL) {
4290-
4291+ private function AuthDisplay($hardwareKey, $status = NULL)
4292+ {
4293 try {
4294 $dbh = PDOConnect::init();
4295
4296@@ -1316,8 +1323,8 @@
4297 }
4298 }
4299
4300- private function AlertDisplayUp($displayId, $display, $loggedIn, $emailAlert) {
4301-
4302+ private function AlertDisplayUp($displayId, $display, $loggedIn, $emailAlert)
4303+ {
4304 $maintenanceEnabled = Config::GetSetting('MAINTENANCE_ENABLED');
4305
4306 if ($loggedIn == 0) {
4307@@ -1354,7 +1361,8 @@
4308 /**
4309 * Check we havent exceeded the bandwidth limits
4310 */
4311- private function CheckBandwidth() {
4312+ private function CheckBandwidth()
4313+ {
4314 $xmdsLimit = Config::GetSetting('MONTHLY_XMDS_TRANSFER_LIMIT_KB');
4315
4316 if ($xmdsLimit <= 0)
4317@@ -1385,8 +1393,8 @@
4318 * @param <type> $type
4319 * @param <type> $sizeInBytes
4320 */
4321- private function LogBandwidth($displayId, $type, $sizeInBytes) {
4322-
4323+ private function LogBandwidth($displayId, $type, $sizeInBytes)
4324+ {
4325 $bandwidth = new Bandwidth();
4326 $bandwidth->Log($displayId, $type, $sizeInBytes);
4327 }
4328
4329=== added directory 'server/modules/3rdparty/twitter-oauth'
4330=== added file 'server/modules/3rdparty/twitter-oauth/OAuth.php'
4331--- server/modules/3rdparty/twitter-oauth/OAuth.php 1970-01-01 00:00:00 +0000
4332+++ server/modules/3rdparty/twitter-oauth/OAuth.php 2015-01-09 11:27:10 +0000
4333@@ -0,0 +1,872 @@
4334+<?php
4335+// vim: foldmethod=marker
4336+
4337+/* Generic exception class
4338+ */
4339+if (!class_exists('TwitterOAuthException')) {
4340+ class TwitterOAuthException extends Exception {
4341+ // pass
4342+ }
4343+}
4344+
4345+class TwitterOAuthConsumer {
4346+ public $key;
4347+ public $secret;
4348+
4349+ function __construct($key, $secret, $callback_url=NULL) {
4350+ $this->key = $key;
4351+ $this->secret = $secret;
4352+ $this->callback_url = $callback_url;
4353+ }
4354+
4355+ function __toString() {
4356+ return "OAuthConsumer[key=$this->key,secret=$this->secret]";
4357+ }
4358+}
4359+
4360+class TwitterOAuthToken {
4361+ // access tokens and request tokens
4362+ public $key;
4363+ public $secret;
4364+
4365+ /**
4366+ * key = the token
4367+ * secret = the token secret
4368+ */
4369+ function __construct($key, $secret) {
4370+ $this->key = $key;
4371+ $this->secret = $secret;
4372+ }
4373+
4374+ /**
4375+ * generates the basic string serialization of a token that a server
4376+ * would respond to request_token and access_token calls with
4377+ */
4378+ function to_string() {
4379+ return "oauth_token=" .
4380+ TwitterOAuthUtil::urlencode_rfc3986($this->key) .
4381+ "&oauth_token_secret=" .
4382+ TwitterOAuthUtil::urlencode_rfc3986($this->secret);
4383+ }
4384+
4385+ function __toString() {
4386+ return $this->to_string();
4387+ }
4388+}
4389+
4390+/**
4391+ * A class for implementing a Signature Method
4392+ * See section 9 ("Signing Requests") in the spec
4393+ */
4394+abstract class TwitterOAuthSignatureMethod {
4395+ /**
4396+ * Needs to return the name of the Signature Method (ie HMAC-SHA1)
4397+ * @return string
4398+ */
4399+ abstract public function get_name();
4400+
4401+ /**
4402+ * Build up the signature
4403+ * NOTE: The output of this function MUST NOT be urlencoded.
4404+ * the encoding is handled in TwitterOAuthRequest when the final
4405+ * request is serialized
4406+ * @param TwitterOAuthRequest $request
4407+ * @param OAuthConsumer $consumer
4408+ * @param OAuthToken $token
4409+ * @return string
4410+ */
4411+ abstract public function build_signature($request, $consumer, $token);
4412+
4413+ /**
4414+ * Verifies that a given signature is correct
4415+ * @param TwitterOAuthRequest $request
4416+ * @param OAuthConsumer $consumer
4417+ * @param OAuthToken $token
4418+ * @param string $signature
4419+ * @return bool
4420+ */
4421+ public function check_signature($request, $consumer, $token, $signature) {
4422+ $built = $this->build_signature($request, $consumer, $token);
4423+ return $built == $signature;
4424+ }
4425+}
4426+
4427+/**
4428+ * The HMAC-SHA1 signature method uses the HMAC-SHA1 signature algorithm as defined in [RFC2104]
4429+ * where the Signature Base String is the text and the key is the concatenated values (each first
4430+ * encoded per Parameter Encoding) of the Consumer Secret and Token Secret, separated by an '&'
4431+ * character (ASCII code 38) even if empty.
4432+ * - Chapter 9.2 ("HMAC-SHA1")
4433+ */
4434+class TwitterOAuthSignatureMethod_HMAC_SHA1 extends TwitterOAuthSignatureMethod {
4435+ function get_name() {
4436+ return "HMAC-SHA1";
4437+ }
4438+
4439+ public function build_signature($request, $consumer, $token) {
4440+ $base_string = $request->get_signature_base_string();
4441+ $request->base_string = $base_string;
4442+
4443+ $key_parts = array(
4444+ $consumer->secret,
4445+ ($token) ? $token->secret : ""
4446+ );
4447+
4448+ $key_parts = TwitterOAuthUtil::urlencode_rfc3986($key_parts);
4449+ $key = implode('&', $key_parts);
4450+
4451+ return base64_encode(hash_hmac('sha1', $base_string, $key, true));
4452+ }
4453+}
4454+
4455+/**
4456+ * The PLAINTEXT method does not provide any security protection and SHOULD only be used
4457+ * over a secure channel such as HTTPS. It does not use the Signature Base String.
4458+ * - Chapter 9.4 ("PLAINTEXT")
4459+ */
4460+class TwitterOAuthSignatureMethod_PLAINTEXT extends TwitterOAuthSignatureMethod {
4461+ public function get_name() {
4462+ return "PLAINTEXT";
4463+ }
4464+
4465+ /**
4466+ * oauth_signature is set to the concatenated encoded values of the Consumer Secret and
4467+ * Token Secret, separated by a '&' character (ASCII code 38), even if either secret is
4468+ * empty. The result MUST be encoded again.
4469+ * - Chapter 9.4.1 ("Generating Signatures")
4470+ *
4471+ * Please note that the second encoding MUST NOT happen in the SignatureMethod, as
4472+ * TwitterOAuthRequest handles this!
4473+ */
4474+ public function build_signature($request, $consumer, $token) {
4475+ $key_parts = array(
4476+ $consumer->secret,
4477+ ($token) ? $token->secret : ""
4478+ );
4479+
4480+ $key_parts = TwitterOAuthUtil::urlencode_rfc3986($key_parts);
4481+ $key = implode('&', $key_parts);
4482+ $request->base_string = $key;
4483+
4484+ return $key;
4485+ }
4486+}
4487+
4488+/**
4489+ * The RSA-SHA1 signature method uses the RSASSA-PKCS1-v1_5 signature algorithm as defined in
4490+ * [RFC3447] section 8.2 (more simply known as PKCS#1), using SHA-1 as the hash function for
4491+ * EMSA-PKCS1-v1_5. It is assumed that the Consumer has provided its RSA public key in a
4492+ * verified way to the Service Provider, in a manner which is beyond the scope of this
4493+ * specification.
4494+ * - Chapter 9.3 ("RSA-SHA1")
4495+ */
4496+abstract class TwitterOAuthSignatureMethod_RSA_SHA1 extends TwitterOAuthSignatureMethod {
4497+ public function get_name() {
4498+ return "RSA-SHA1";
4499+ }
4500+
4501+ // Up to the SP to implement this lookup of keys. Possible ideas are:
4502+ // (1) do a lookup in a table of trusted certs keyed off of consumer
4503+ // (2) fetch via http using a url provided by the requester
4504+ // (3) some sort of specific discovery code based on request
4505+ //
4506+ // Either way should return a string representation of the certificate
4507+ protected abstract function fetch_public_cert(&$request);
4508+
4509+ // Up to the SP to implement this lookup of keys. Possible ideas are:
4510+ // (1) do a lookup in a table of trusted certs keyed off of consumer
4511+ //
4512+ // Either way should return a string representation of the certificate
4513+ protected abstract function fetch_private_cert(&$request);
4514+
4515+ public function build_signature($request, $consumer, $token) {
4516+ $base_string = $request->get_signature_base_string();
4517+ $request->base_string = $base_string;
4518+
4519+ // Fetch the private key cert based on the request
4520+ $cert = $this->fetch_private_cert($request);
4521+
4522+ // Pull the private key ID from the certificate
4523+ $privatekeyid = openssl_get_privatekey($cert);
4524+
4525+ // Sign using the key
4526+ $ok = openssl_sign($base_string, $signature, $privatekeyid);
4527+
4528+ // Release the key resource
4529+ openssl_free_key($privatekeyid);
4530+
4531+ return base64_encode($signature);
4532+ }
4533+
4534+ public function check_signature($request, $consumer, $token, $signature) {
4535+ $decoded_sig = base64_decode($signature);
4536+
4537+ $base_string = $request->get_signature_base_string();
4538+
4539+ // Fetch the public key cert based on the request
4540+ $cert = $this->fetch_public_cert($request);
4541+
4542+ // Pull the public key ID from the certificate
4543+ $publickeyid = openssl_get_publickey($cert);
4544+
4545+ // Check the computed signature against the one passed in the query
4546+ $ok = openssl_verify($base_string, $decoded_sig, $publickeyid);
4547+
4548+ // Release the key resource
4549+ openssl_free_key($publickeyid);
4550+
4551+ return $ok == 1;
4552+ }
4553+}
4554+
4555+class TwitterOAuthRequest {
4556+ private $parameters;
4557+ private $http_method;
4558+ private $http_url;
4559+ // for debug purposes
4560+ public $base_string;
4561+ public static $version = '1.0';
4562+ public static $POST_INPUT = 'php://input';
4563+
4564+ function __construct($http_method, $http_url, $parameters=NULL) {
4565+ @$parameters or $parameters = array();
4566+ $parameters = array_merge( TwitterOAuthUtil::parse_parameters(parse_url($http_url, PHP_URL_QUERY)), $parameters);
4567+ $this->parameters = $parameters;
4568+ $this->http_method = $http_method;
4569+ $this->http_url = $http_url;
4570+ }
4571+
4572+
4573+ /**
4574+ * attempt to build up a request from what was passed to the server
4575+ */
4576+ public static function from_request($http_method=NULL, $http_url=NULL, $parameters=NULL) {
4577+ $scheme = (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] != "on")
4578+ ? 'http'
4579+ : 'https';
4580+ @$http_url or $http_url = $scheme .
4581+ '://' . $_SERVER['HTTP_HOST'] .
4582+ ':' .
4583+ $_SERVER['SERVER_PORT'] .
4584+ $_SERVER['REQUEST_URI'];
4585+ @$http_method or $http_method = $_SERVER['REQUEST_METHOD'];
4586+
4587+ // We weren't handed any parameters, so let's find the ones relevant to
4588+ // this request.
4589+ // If you run XML-RPC or similar you should use this to provide your own
4590+ // parsed parameter-list
4591+ if (!$parameters) {
4592+ // Find request headers
4593+ $request_headers = TwitterOAuthUtil::get_headers();
4594+
4595+ // Parse the query-string to find GET parameters
4596+ $parameters = TwitterOAuthUtil::parse_parameters($_SERVER['QUERY_STRING']);
4597+
4598+ // It's a POST request of the proper content-type, so parse POST
4599+ // parameters and add those overriding any duplicates from GET
4600+ if ($http_method == "POST"
4601+ && @strstr($request_headers["Content-Type"],
4602+ "application/x-www-form-urlencoded")
4603+ ) {
4604+ $post_data = TwitterOAuthUtil::parse_parameters(
4605+ file_get_contents(self::$POST_INPUT)
4606+ );
4607+ $parameters = array_merge($parameters, $post_data);
4608+ }
4609+
4610+ // We have a Authorization-header with OAuth data. Parse the header
4611+ // and add those overriding any duplicates from GET or POST
4612+ if (@substr($request_headers['Authorization'], 0, 6) == "OAuth ") {
4613+ $header_parameters = TwitterOAuthUtil::split_header(
4614+ $request_headers['Authorization']
4615+ );
4616+ $parameters = array_merge($parameters, $header_parameters);
4617+ }
4618+
4619+ }
4620+
4621+ return new TwitterOAuthRequest($http_method, $http_url, $parameters);
4622+ }
4623+
4624+ /**
4625+ * pretty much a helper function to set up the request
4626+ */
4627+ public static function from_consumer_and_token($consumer, $token, $http_method, $http_url, $parameters=NULL) {
4628+ @$parameters or $parameters = array();
4629+ $defaults = array("oauth_version" => TwitterOAuthRequest::$version,
4630+ "oauth_nonce" => TwitterOAuthRequest::generate_nonce(),
4631+ "oauth_timestamp" => TwitterOAuthRequest::generate_timestamp(),
4632+ "oauth_consumer_key" => $consumer->key);
4633+ if ($token)
4634+ $defaults['oauth_token'] = $token->key;
4635+
4636+ $parameters = array_merge($defaults, $parameters);
4637+
4638+ return new TwitterOAuthRequest($http_method, $http_url, $parameters);
4639+ }
4640+
4641+ public function set_parameter($name, $value, $allow_duplicates = true) {
4642+ if ($allow_duplicates && isset($this->parameters[$name])) {
4643+ // We have already added parameter(s) with this name, so add to the list
4644+ if (is_scalar($this->parameters[$name])) {
4645+ // This is the first duplicate, so transform scalar (string)
4646+ // into an array so we can add the duplicates
4647+ $this->parameters[$name] = array($this->parameters[$name]);
4648+ }
4649+
4650+ $this->parameters[$name][] = $value;
4651+ } else {
4652+ $this->parameters[$name] = $value;
4653+ }
4654+ }
4655+
4656+ public function get_parameter($name) {
4657+ return isset($this->parameters[$name]) ? $this->parameters[$name] : null;
4658+ }
4659+
4660+ public function get_parameters() {
4661+ return $this->parameters;
4662+ }
4663+
4664+ public function unset_parameter($name) {
4665+ unset($this->parameters[$name]);
4666+ }
4667+
4668+ /**
4669+ * The request parameters, sorted and concatenated into a normalized string.
4670+ * @return string
4671+ */
4672+ public function get_signable_parameters() {
4673+ // Grab all parameters
4674+ $params = $this->parameters;
4675+
4676+ // Remove oauth_signature if present
4677+ // Ref: Spec: 9.1.1 ("The oauth_signature parameter MUST be excluded.")
4678+ if (isset($params['oauth_signature'])) {
4679+ unset($params['oauth_signature']);
4680+ }
4681+
4682+ return TwitterOAuthUtil::build_http_query($params);
4683+ }
4684+
4685+ /**
4686+ * Returns the base string of this request
4687+ *
4688+ * The base string defined as the method, the url
4689+ * and the parameters (normalized), each urlencoded
4690+ * and the concated with &.
4691+ */
4692+ public function get_signature_base_string() {
4693+ $parts = array(
4694+ $this->get_normalized_http_method(),
4695+ $this->get_normalized_http_url(),
4696+ $this->get_signable_parameters()
4697+ );
4698+
4699+ $parts = TwitterOAuthUtil::urlencode_rfc3986($parts);
4700+
4701+ return implode('&', $parts);
4702+ }
4703+
4704+ /**
4705+ * just uppercases the http method
4706+ */
4707+ public function get_normalized_http_method() {
4708+ return strtoupper($this->http_method);
4709+ }
4710+
4711+ /**
4712+ * parses the url and rebuilds it to be
4713+ * scheme://host/path
4714+ */
4715+ public function get_normalized_http_url() {
4716+ $parts = parse_url($this->http_url);
4717+
4718+ $port = (isset($parts['port'])) ? $parts['port'] : (($scheme == 'https') ? '443' : '80');
4719+ $scheme = $parts['scheme'];
4720+ $host = $parts['host'];
4721+ $path = @$parts['path'];
4722+
4723+ if (($scheme == 'https' && $port != '443')
4724+ || ($scheme == 'http' && $port != '80')) {
4725+ $host = "$host:$port";
4726+ }
4727+ return "$scheme://$host$path";
4728+ }
4729+
4730+ /**
4731+ * builds a url usable for a GET request
4732+ */
4733+ public function to_url() {
4734+ $post_data = $this->to_postdata();
4735+ $out = $this->get_normalized_http_url();
4736+ if ($post_data) {
4737+ $out .= '?'.$post_data;
4738+ }
4739+ return $out;
4740+ }
4741+
4742+ /**
4743+ * builds the data one would send in a POST request
4744+ */
4745+ public function to_postdata() {
4746+ return TwitterOAuthUtil::build_http_query($this->parameters);
4747+ }
4748+
4749+ /**
4750+ * builds the Authorization: header
4751+ */
4752+ public function to_header($realm=null) {
4753+ $first = true;
4754+ if($realm) {
4755+ $out = 'Authorization: OAuth realm="' . TwitterOAuthUtil::urlencode_rfc3986($realm) . '"';
4756+ $first = false;
4757+ } else
4758+ $out = 'Authorization: OAuth';
4759+
4760+ $total = array();
4761+ foreach ($this->parameters as $k => $v) {
4762+ if (substr($k, 0, 5) != "oauth") continue;
4763+ if (is_array($v)) {
4764+ throw new OAuthException('Arrays not supported in headers');
4765+ }
4766+ $out .= ($first) ? ' ' : ',';
4767+ $out .= TwitterOAuthUtil::urlencode_rfc3986($k) .
4768+ '="' .
4769+ TwitterOAuthUtil::urlencode_rfc3986($v) .
4770+ '"';
4771+ $first = false;
4772+ }
4773+ return $out;
4774+ }
4775+
4776+ public function __toString() {
4777+ return $this->to_url();
4778+ }
4779+
4780+
4781+ public function sign_request($signature_method, $consumer, $token) {
4782+ $this->set_parameter(
4783+ "oauth_signature_method",
4784+ $signature_method->get_name(),
4785+ false
4786+ );
4787+ $signature = $this->build_signature($signature_method, $consumer, $token);
4788+ $this->set_parameter("oauth_signature", $signature, false);
4789+ }
4790+
4791+ public function build_signature($signature_method, $consumer, $token) {
4792+ $signature = $signature_method->build_signature($this, $consumer, $token);
4793+ return $signature;
4794+ }
4795+
4796+ /**
4797+ * util function: current timestamp
4798+ */
4799+ private static function generate_timestamp() {
4800+ return time();
4801+ }
4802+
4803+ /**
4804+ * util function: current nonce
4805+ */
4806+ private static function generate_nonce() {
4807+ $mt = microtime();
4808+ $rand = mt_rand();
4809+
4810+ return md5($mt . $rand); // md5s look nicer than numbers
4811+ }
4812+}
4813+
4814+class TwitterOAuthServer {
4815+ protected $timestamp_threshold = 300; // in seconds, five minutes
4816+ protected $version = '1.0'; // hi blaine
4817+ protected $signature_methods = array();
4818+
4819+ protected $data_store;
4820+
4821+ function __construct($data_store) {
4822+ $this->data_store = $data_store;
4823+ }
4824+
4825+ public function add_signature_method($signature_method) {
4826+ $this->signature_methods[$signature_method->get_name()] =
4827+ $signature_method;
4828+ }
4829+
4830+ // high level functions
4831+
4832+ /**
4833+ * process a request_token request
4834+ * returns the request token on success
4835+ */
4836+ public function fetch_request_token(&$request) {
4837+ $this->get_version($request);
4838+
4839+ $consumer = $this->get_consumer($request);
4840+
4841+ // no token required for the initial token request
4842+ $token = NULL;
4843+
4844+ $this->check_signature($request, $consumer, $token);
4845+
4846+ // Rev A change
4847+ $callback = $request->get_parameter('oauth_callback');
4848+ $new_token = $this->data_store->new_request_token($consumer, $callback);
4849+
4850+ return $new_token;
4851+ }
4852+
4853+ /**
4854+ * process an access_token request
4855+ * returns the access token on success
4856+ */
4857+ public function fetch_access_token(&$request) {
4858+ $this->get_version($request);
4859+
4860+ $consumer = $this->get_consumer($request);
4861+
4862+ // requires authorized request token
4863+ $token = $this->get_token($request, $consumer, "request");
4864+
4865+ $this->check_signature($request, $consumer, $token);
4866+
4867+ // Rev A change
4868+ $verifier = $request->get_parameter('oauth_verifier');
4869+ $new_token = $this->data_store->new_access_token($token, $consumer, $verifier);
4870+
4871+ return $new_token;
4872+ }
4873+
4874+ /**
4875+ * verify an api call, checks all the parameters
4876+ */
4877+ public function verify_request(&$request) {
4878+ $this->get_version($request);
4879+ $consumer = $this->get_consumer($request);
4880+ $token = $this->get_token($request, $consumer, "access");
4881+ $this->check_signature($request, $consumer, $token);
4882+ return array($consumer, $token);
4883+ }
4884+
4885+ // Internals from here
4886+ /**
4887+ * version 1
4888+ */
4889+ private function get_version(&$request) {
4890+ $version = $request->get_parameter("oauth_version");
4891+ if (!$version) {
4892+ // Service Providers MUST assume the protocol version to be 1.0 if this parameter is not present.
4893+ // Chapter 7.0 ("Accessing Protected Ressources")
4894+ $version = '1.0';
4895+ }
4896+ if ($version !== $this->version) {
4897+ throw new OAuthException("OAuth version '$version' not supported");
4898+ }
4899+ return $version;
4900+ }
4901+
4902+ /**
4903+ * figure out the signature with some defaults
4904+ */
4905+ private function get_signature_method(&$request) {
4906+ $signature_method =
4907+ @$request->get_parameter("oauth_signature_method");
4908+
4909+ if (!$signature_method) {
4910+ // According to chapter 7 ("Accessing Protected Ressources") the signature-method
4911+ // parameter is required, and we can't just fallback to PLAINTEXT
4912+ throw new OAuthException('No signature method parameter. This parameter is required');
4913+ }
4914+
4915+ if (!in_array($signature_method,
4916+ array_keys($this->signature_methods))) {
4917+ throw new OAuthException(
4918+ "Signature method '$signature_method' not supported " .
4919+ "try one of the following: " .
4920+ implode(", ", array_keys($this->signature_methods))
4921+ );
4922+ }
4923+ return $this->signature_methods[$signature_method];
4924+ }
4925+
4926+ /**
4927+ * try to find the consumer for the provided request's consumer key
4928+ */
4929+ private function get_consumer(&$request) {
4930+ $consumer_key = @$request->get_parameter("oauth_consumer_key");
4931+ if (!$consumer_key) {
4932+ throw new OAuthException("Invalid consumer key");
4933+ }
4934+
4935+ $consumer = $this->data_store->lookup_consumer($consumer_key);
4936+ if (!$consumer) {
4937+ throw new OAuthException("Invalid consumer");
4938+ }
4939+
4940+ return $consumer;
4941+ }
4942+
4943+ /**
4944+ * try to find the token for the provided request's token key
4945+ */
4946+ private function get_token(&$request, $consumer, $token_type="access") {
4947+ $token_field = @$request->get_parameter('oauth_token');
4948+ $token = $this->data_store->lookup_token(
4949+ $consumer, $token_type, $token_field
4950+ );
4951+ if (!$token) {
4952+ throw new OAuthException("Invalid $token_type token: $token_field");
4953+ }
4954+ return $token;
4955+ }
4956+
4957+ /**
4958+ * all-in-one function to check the signature on a request
4959+ * should guess the signature method appropriately
4960+ */
4961+ private function check_signature(&$request, $consumer, $token) {
4962+ // this should probably be in a different method
4963+ $timestamp = @$request->get_parameter('oauth_timestamp');
4964+ $nonce = @$request->get_parameter('oauth_nonce');
4965+
4966+ $this->check_timestamp($timestamp);
4967+ $this->check_nonce($consumer, $token, $nonce, $timestamp);
4968+
4969+ $signature_method = $this->get_signature_method($request);
4970+
4971+ $signature = $request->get_parameter('oauth_signature');
4972+ $valid_sig = $signature_method->check_signature(
4973+ $request,
4974+ $consumer,
4975+ $token,
4976+ $signature
4977+ );
4978+
4979+ if (!$valid_sig) {
4980+ throw new OAuthException("Invalid signature");
4981+ }
4982+ }
4983+
4984+ /**
4985+ * check that the timestamp is new enough
4986+ */
4987+ private function check_timestamp($timestamp) {
4988+ if( ! $timestamp )
4989+ throw new OAuthException(
4990+ 'Missing timestamp parameter. The parameter is required'
4991+ );
4992+
4993+ // verify that timestamp is recentish
4994+ $now = time();
4995+ if (abs($now - $timestamp) > $this->timestamp_threshold) {
4996+ throw new OAuthException(
4997+ "Expired timestamp, yours $timestamp, ours $now"
4998+ );
4999+ }
5000+ }
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches