diff --git a/.travis.yml b/.travis.yml index 951be73..4f40d9b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,5 +8,4 @@ install: - printf "\n" | pecl install imagick before_script: - phpenv config-add scripts/build/phpconf.ini - - ./scripts/build/prepare.sh script: phpunit -c tests/phpunit.xml diff --git a/README.md b/README.md index 8d1c8b6..bfdd29e 100644 --- a/README.md +++ b/README.md @@ -1 +1,26 @@ - +Unsee.cc - Secure and private image hosting +============== + +This is the official Git repository for the Unsee image hosting. The deployment on production server is as simple as: +``` +git fetch +git reset --hard origin/master +rm application/configs/env.php +``` +This is done for the sake of ease and transparency. + + +Installation +--------- +To run your copy of Unsee locally you'll probably need + +Requirements +----- +- *nix environment +- Nginx + - Secure link module +- Php + - Redis module + - Imagick module +- Redis +- Image magick diff --git a/application/Bootstrap.php b/application/Bootstrap.php index e5d94b2..7504b7b 100644 --- a/application/Bootstrap.php +++ b/application/Bootstrap.php @@ -12,6 +12,10 @@ protected function _initEnv() if (empty($_SERVER['HTTP_USER_AGENT'])) { $_SERVER['HTTP_USER_AGENT'] = 'cli'; } + + if (empty($_SERVER['SERVER_NAME'])) { + $_SERVER['SERVER_NAME'] = 'unsee.cc'; + } } protected function _initDocType() diff --git a/application/configs/application.ini b/application/configs/application.ini index cd3ebaa..a02e838 100644 --- a/application/configs/application.ini +++ b/application/configs/application.ini @@ -27,12 +27,10 @@ redis.port = '6379' hash.vovels = 'aiueo'; hash.consonants = 'stnmrgzdbp'; -hash.syllables = 3; +hash.syllables = 4; timezone = 'Europe/Kiev'; -storagePath = APPLICATION_PATH "/../storage/" - ; Image config settings.info.title.type = 'text'; settings.info.description.type = 'textarea'; @@ -78,4 +76,5 @@ resources.frontController.params.displayExceptions = 1 phpSettings.display_startup_errors = 1 phpSettings.display_errors = 1 resources.frontController.params.displayExceptions = 1 -domainName = "unsee.cc.local" \ No newline at end of file +domainName = "unsee.cc.local" +combineAssets = 0; \ No newline at end of file diff --git a/application/configs/lang.xml b/application/configs/lang.xml index f2146e1..03e09ba 100644 --- a/application/configs/lang.xml +++ b/application/configs/lang.xml @@ -40,6 +40,14 @@ после просмотра after first view + + 10 минут + in 10 minutes + + + 30 минут + in 30 minutes + через час in one hour @@ -292,7 +300,7 @@ Нажмите здесь, чтобы изменить настройки - Click to change settings + Click here to change settings @@ -362,7 +370,7 @@ Не позволять пользователям совершать правый клик по картинке чтобы сохранить её - Restrict right-clicking images to save them + Restrict right-clicking images to save them, can't be disabled for single-view images @@ -535,7 +543,7 @@ - минута + минуту minute @@ -559,5 +567,13 @@ Применить Apply + + Можно загрузить ещё картинок перетащив их на страницу + You can upload more images by dropping them on the page + + + Можно загрузить свои изображения перетащив их на страницу + You can add your own images by dropping them on the page + - \ No newline at end of file + diff --git a/application/controllers/IndexController.php b/application/controllers/IndexController.php index e22bb1a..454dd05 100644 --- a/application/controllers/IndexController.php +++ b/application/controllers/IndexController.php @@ -5,9 +5,10 @@ */ class IndexController extends Zend_Controller_Action { + public function init() { - + $this->view->headScript()->appendFile('js/vendor/jquery-1.8.3.min.js'); $this->view->headScript()->appendFile('js/vendor/modernizr-2.6.2.min.js'); $this->view->headScript()->appendFile('js/vendor/jquery.iframe-transport.js'); @@ -21,4 +22,9 @@ public function init() $this->view->headLink()->appendStylesheet('css/main.css'); $this->view->headLink()->appendStylesheet('css/sizes.css'); } -} \ No newline at end of file + + public function indexAction() + { + + } +} diff --git a/application/controllers/UploadController.php b/application/controllers/UploadController.php index 2eab8c9..9f18ed9 100644 --- a/application/controllers/UploadController.php +++ b/application/controllers/UploadController.php @@ -24,47 +24,95 @@ public function indexAction() $upload->addValidator('Count', false, array('min' => 1, 'max' => 100)); $upload->addValidator('IsImage', false); - $upload->addValidator('Size', false, array('max' => '8MB', 'bytestring' => false)); + $upload->addValidator('Size', false, array('max' => '10MB', 'bytestring' => false)); $translate = Zend_Registry::get('Zend_Translate'); + $updating = false; try { if (!$upload->receive()) { - throw new Exception(); + throw new Exception($translate->translate('error_uploading')); } else { $files = $upload->getFileInfo(); - // Tell the page the name of the new hash - $response->hash = $this->getNewHashName(); - foreach ($files as $file => &$info) { - if (!$upload->isUploaded($file)) { - $info = null; - } else { - $imgDoc = new Unsee_Image(); - $imgDoc->hash = $response->hash; - $imgDoc->setFile($info['tmp_name']); + // Updating hash with new images + if (!empty($_POST['hash']) && Unsee_Hash::isValid($_POST['hash'])) { + $hashDoc = new Unsee_Hash($_POST['hash']); + $updating = true; + $response = array(); + + if (!Unsee_Session::isOwner($hashDoc) && !$hashDoc->allow_anonymous_images) { + die('[]'); } + } else { + // Creating a new hash + $hashDoc = new Unsee_Hash(); + $this->setExpiration($hashDoc); + $response->hash = $hashDoc->key; + } + + $imageAdded = false; + + foreach ($files as $file => $info) { + if ($upload->isUploaded($file)) { + $imgDoc = new Unsee_Image($hashDoc); + $res = $imgDoc->setFile($info['tmp_name']); + $imgDoc->setSecureParams(); //hack to populate correct secureTtd + + if ($updating) { + $ticket = new Unsee_Ticket(); + $ticket->issue($imgDoc); + + $newImg = new stdClass(); + $newImg->hashKey = $hashDoc->key; + $newImg->key = $imgDoc->key; + $newImg->src = '/image/' . $imgDoc->key . '/' . $imgDoc->secureMd5 . '/' . $imgDoc->secureTtd . '/'; + $newImg->width = $imgDoc->width; + $newImg->ticket = md5(Unsee_Session::getCurrent() . $hashDoc->key); + + $response[] = $newImg; + } + + if ($res) { + $imageAdded = true; + } + + // Remove uploaded file from temporary dir if it wasn't removed + if (file_exists($info['tmp_name'])) { + @unlink($info['tmp_name']); + } + } + } + + if (!$imageAdded) { + throw new Exception('No images were added'); } } } catch (Exception $e) { - $response->error = $translate->translate('error_uploading'); + $response->error = $e->getMessage(); } $this->_helper->json->sendJson($response); } /** - * Creates a new hash document and returns it's name - * @return type + * Sets the TTL for the provided hash + * @param Unsee_Hash $hashDoc + * @return boolean */ - private function getNewHashName() + private function setExpiration($hashDoc) { - // Creating a new hash item (/bababa/) - $hashDoc = new Unsee_Hash(); - - if (isset($_POST['time']) && in_array($_POST['time'], Unsee_Hash::$_ttlTypes)) { - $hashDoc->ttl = $_POST['time']; + // Custom ttl was set + if (!empty($_POST['time']) && in_array($_POST['time'], Unsee_Hash::$ttlTypes)) { + $amount = array_search($_POST['time'], Unsee_Hash::$ttlTypes); + if ($amount > 0) { + // Disable single view, which is ON by default + $hashDoc->max_views = 0; + $hashDoc->ttl = $_POST['time']; + // Expire in specified interval, instead of a day + $hashDoc->expireAt(time() + $amount); + } } - return $hashDoc->key; + return true; } } diff --git a/application/controllers/ViewController.php b/application/controllers/ViewController.php index 0ea3936..0c27eea 100644 --- a/application/controllers/ViewController.php +++ b/application/controllers/ViewController.php @@ -21,12 +21,19 @@ public function init() // This page should never be indexed by robots $this->getResponse()->setHeader('X-Robots-Tag', 'noindex'); $this->view->headScript()->appendFile('js/vendor/jquery-1.8.3.min.js'); + $this->view->headScript()->appendFile('js/vendor/jquery.visibility.js'); + $this->view->headScript()->appendFile('js/vendor/jquery.iframe-transport.js'); + $this->view->headScript()->appendFile('js/vendor/jquery.ui.widget.js'); + $this->view->headScript()->appendFile('js/vendor/jquery.fileupload.js'); + $this->view->headScript()->appendFile('js/vendor/jquery.lazyload.min.js'); $this->view->headScript()->appendFile('js/view.js'); + $this->view->headScript()->appendFile('js/chat.js'); $this->view->headLink()->appendStylesheet('css/normalize.css'); $this->view->headLink()->appendStylesheet('css/h5bp.css'); $this->view->headLink()->appendStylesheet('css/view.css'); $this->view->headLink()->appendStylesheet('css/subpage.css'); + $this->view->headLink()->appendStylesheet('css/chat.css'); // Preheating the form $this->form = new Application_Form_Settings; @@ -49,12 +56,14 @@ private function handleSettingsFormSubmit($form, $hashDoc) $values = $form->getValues(); // Changed value of TTL - if (isset($values['ttl']) && $hashDoc->ttl === Unsee_Hash::$_ttlTypes[0]) { + if (isset($values['ttl']) && $hashDoc->ttl === Unsee_Hash::$ttlTypes[0]) { // Revert no_download to the value from DB, since there's no way // it could have changed. It's disabled when ttl == 'first'. unset($values['no_download']); } + $expireAt = false; + // Apply values from form to hash in Redis foreach ($values as $field => $value) { if ($field == 'strip_exif') { @@ -62,8 +71,25 @@ private function handleSettingsFormSubmit($form, $hashDoc) continue; } + if ($field === 'ttl') { + // Delete after view? + if ($value == Unsee_Hash::$ttlTypes[0]) { + $hashDoc->max_views = 1; + $expireAt = $hashDoc->timestamp + Unsee_Redis::EXP_DAY; + // Set to expire within a day after upload + } else { + $amount = array_search($value, Unsee_Hash::$ttlTypes); + $hashDoc->max_views = 0; + $expireAt = $hashDoc->timestamp + $amount; + } + } + $hashDoc->$field = $value; } + + if ($expireAt) { + $hashDoc->expireAt($expireAt); + } } } @@ -115,6 +141,12 @@ public function indexAction() $this->handleSettingsFormSubmit($form, $hashDoc); } + // Check again + // It was already deleted/did not exist/expired + if (!$hashDoc->exists() || !$hashDoc->isViewable($hashDoc)) { + return $this->deletedAction(); + } + // No use to do anything, page is not viewable for one of the reasons if (!$hashDoc->isViewable($hashDoc)) { $hashDoc->delete(); @@ -134,7 +166,7 @@ public function indexAction() // Create a view "ticket" for every image of a hash foreach ($images as $image) { - $ticket->issue($image->key); + $ticket->issue($image); } // Handle current request based on what settins are set @@ -157,15 +189,21 @@ public function indexAction() // If viewer is the creator - don't count their view if (!Unsee_Session::isOwner($hashDoc)) { $hashDoc->views++; + + // Reached max views for this hash + if ($hashDoc->max_views && $hashDoc->views >= $hashDoc->max_views) { + // Remove the hash in a while for the images to be displayed + $hashDoc->expireAt(time() + 30); + } } else { // Owner - include extra webpage assets $this->view->headScript()->appendFile('js/settings.js'); $this->view->headLink()->appendStylesheet('css/settings.css'); } - // Don't show 'other party' text to the 'other party' - if (Unsee_Session::isOwner($hashDoc) || $hashDoc->ttl !== 'first') { - if ($hashDoc->ttl === 'first') { + // Don't show the 'other party' text for the 'other party' + if (Unsee_Session::isOwner($hashDoc) || $hashDoc->ttl !== Unsee_Hash::$ttlTypes[0]) { + if ($hashDoc->ttl === Unsee_Hash::$ttlTypes[0]) { $deleteTimeStr = ''; $deleteMessageTemplate = 'delete_first'; } else { @@ -182,9 +220,23 @@ public function indexAction() $this->view->images = $images; $this->view->groups = $form->getDisplayGroups(); + $message = ''; + if (Unsee_Session::isOwner($this->hashDoc)) { + $message = $this->view->translate('upload_more_owner'); + } elseif ($hashDoc->allow_anonymous_images) { + $message = $this->view->translate('upload_more_anonymous'); + } + + $this->view->welcomeMessage = $message; + return true; } + public function noContentAction() + { + return $this->_response->setHttpResponseCode(204); + } + /** * Sets the hash title if available * @return boolean @@ -218,14 +270,14 @@ private function processDescription() private function processNoDownload() { // If it's a one-time view image - if ($this->hashDoc->ttl === 'first') { + if ($this->hashDoc->ttl === Unsee_Hash::$ttlTypes[0]) { // Disable the "no download" checkbox // And set it to "checked" $this->form->getElement('no_download')->setAttrib('disabled', 'disabled')->setAttrib('checked', 'checked'); } // Don't allow download if the setting is set accordingly or the image is a one-timer - $this->view->no_download = $this->hashDoc->no_download || $this->hashDoc->ttl === 'first'; + $this->view->no_download = $this->hashDoc->no_download || $this->hashDoc->ttl === Unsee_Hash::$ttlTypes[0]; return true; } @@ -279,7 +331,7 @@ private function processAllowDomain() public function deletedAction() { $this->render('deleted'); - return $this->getResponse()->setHttpResponseCode(310); + return $this->getResponse()->setHttpResponseCode(410); } /** @@ -298,28 +350,27 @@ public function imageAction() // Dropping request if params are not right or the image is too old if (!$imageId || !$ticket || !$time || $time < time()) { - $this->getResponse()->setHeader('Status', '204 No content'); - die(); + return $this->noContentAction(); } - // Fetching the image Redis hash - $imgDoc = new Unsee_Image($imageId); + list($hashStr, $imgKey) = explode('_', $imageId); - // It wasn't there - if (!$imgDoc) { - $this->getResponse()->setHeader('Status', '204 No content'); - die(); + if (!$hashStr) { + return $this->noContentAction(); } // Fetching the parent hash - $hashDoc = new Unsee_Hash($imgDoc->hash); + $hashDoc = new Unsee_Hash($hashStr); - // It didn't exist if (!$hashDoc) { - // But the image did, delete it - $imgDoc && $imgDoc->delete(); - $this->getResponse()->setHeader('Status', '204 No content'); - die(); + return $this->noContentAction(); + } + + // Fetching the image Redis hash + $imgDoc = new Unsee_Image($hashDoc, $imgKey); + + if (!$imgDoc) { + return $this->noContentAction(); } /** @@ -327,33 +378,33 @@ public function imageAction() * direct access. Direct access means no referrer. */ if ($hashDoc->no_download && empty($_SERVER['HTTP_REFERER'])) { - $this->getResponse()->setHeader('Status', '204 No content'); - die(); + return $this->noContentAction(); } // Fetching ticket list for the hash, it should have a ticket for the requested image $ticketDoc = new Unsee_Ticket(); // Looks like a gatecrasher, no ticket and image is not allowed to be downloaded directly - if (!$ticketDoc->isAllowed($imgDoc) && ($hashDoc->no_download || $hashDoc->ttl === 'first')) { + if (!$ticketDoc->isAllowed($imgDoc) && $hashDoc->no_download) { // Delete the ticket $ticketDoc->invalidate($imgDoc); - $this->getResponse()->setHeader('Status', '204 No content'); + return $this->noContentAction(); } else { // Delete the ticket $ticketDoc->invalidate($imgDoc); } // Watermark viewer's IP if required - $hashDoc->watermark_ip && $imgDoc->watermark(); + if ($hashDoc->watermark_ip && !Unsee_Session::isOwner($hashDoc)) { + $imgDoc->watermark(); + } // Embed comment if required $hashDoc->comment && $imgDoc->comment($hashDoc->comment); $this->getResponse()->setHeader('Content-type', $imgDoc->type); - // Dump image data - print $imgDoc->getImageData(); + print $imgDoc->getImageContent(); // The hash itself was already outdated for one of the reasons. if (!$hashDoc->isViewable()) { diff --git a/application/views/helpers/CssHelper.php b/application/views/helpers/CssHelper.php index 305931a..44fb9ea 100644 --- a/application/views/helpers/CssHelper.php +++ b/application/views/helpers/CssHelper.php @@ -15,6 +15,8 @@ function cssHelper() foreach ($links as $item) { if ($combining) { $urls[] = str_replace('css/', '', $item->href); + } else { + $item->href = '/' . $item->href; } } diff --git a/application/views/helpers/JavascriptHelper.php b/application/views/helpers/JavascriptHelper.php index f15f4cc..19f8da7 100644 --- a/application/views/helpers/JavascriptHelper.php +++ b/application/views/helpers/JavascriptHelper.php @@ -15,6 +15,8 @@ function javascriptHelper() foreach ($links as $item) { if ($combining) { $urls[] = str_replace('js/', '', $item->attributes['src']); + } else { + $item->attributes['src'] = '/' . $item->attributes['src']; } } diff --git a/application/views/scripts/index/index.phtml b/application/views/scripts/index/index.phtml index eaa1dc1..635cd45 100644 --- a/application/views/scripts/index/index.phtml +++ b/application/views/scripts/index/index.phtml @@ -13,7 +13,9 @@ translate('delete')?> + + +isOwner) { + ?>
@@ -15,70 +42,69 @@ $this->headMeta()->appendName('robots', 'noindex'); $groups = array_values($this->groups); foreach ($groups as $key => $group) { - ?> -
  • getLegend()?>
  • - +
  • getLegend() ?>
  • + $group) { - ?> - - getElements()); + foreach ($groups as $gKey => $group) { - foreach ($fields as $key=>$field) { - ?> - - - - - -
    renderLabel()?>
    render()?>
    - + + getElements()); + + foreach ($fields as $key => $field) { + + ?> + + + + + +
    renderLabel() ?>
    render() ?>
    + - +
    +} + +?> + + -deleteTime) { -?> -
    - deleteTime ?> - isOwner) { - ?> - (translate('click_settings'); ?>) - -
    - title) { -?> -

    title;?>

    - +

    title; ?>

    + description) { -?> -

    description;?>

    - +

    description; ?>

    + no_download) {
    \ No newline at end of file diff --git a/library/Unsee/Block.php b/library/Unsee/Block.php index bd1da13..fb88ad6 100644 --- a/library/Unsee/Block.php +++ b/library/Unsee/Block.php @@ -6,6 +6,6 @@ class Unsee_Block extends Unsee_Redis { - protected $db = 3; + const DB = 3; } diff --git a/library/Unsee/Form/Decorator/Radio.php b/library/Unsee/Form/Decorator/Radio.php index d3d211d..bb3f555 100644 --- a/library/Unsee/Form/Decorator/Radio.php +++ b/library/Unsee/Form/Decorator/Radio.php @@ -31,7 +31,8 @@ public function render($content) $selectedProp = "checked='checked'"; } - $res .= "
    "; + $res .= "
    ". + "
    "; } return $res; diff --git a/library/Unsee/Form/Element/Select/Model/Abstract.php b/library/Unsee/Form/Element/Select/Model/Abstract.php index a36ca58..5d0c75b 100644 --- a/library/Unsee/Form/Element/Select/Model/Abstract.php +++ b/library/Unsee/Form/Element/Select/Model/Abstract.php @@ -3,7 +3,8 @@ class Unsee_Form_Element_Select_Model_Abstract { - static public function getValues(Zend_Translate $lang) { + static public function getValues(Zend_Translate $lang) + { die('Please define getValues()'); } } diff --git a/library/Unsee/Form/Element/Select/Model/Delete.php b/library/Unsee/Form/Element/Select/Model/Delete.php index 2b519cb..2a4bd4d 100644 --- a/library/Unsee/Form/Element/Select/Model/Delete.php +++ b/library/Unsee/Form/Element/Select/Model/Delete.php @@ -5,7 +5,7 @@ class Unsee_Form_Element_Select_Model_Delete extends Unsee_Form_Element_Select_M static public function getValues(Zend_Translate $lang) { - $vars = Unsee_Hash::$_ttlTypes; + $vars = Unsee_Hash::$ttlTypes; $values = array(); foreach ($vars as $item) { diff --git a/library/Unsee/Hash.php b/library/Unsee/Hash.php index 6330cd5..30983e2 100644 --- a/library/Unsee/Hash.php +++ b/library/Unsee/Hash.php @@ -6,11 +6,13 @@ class Unsee_Hash extends Unsee_Redis { + const DB = 0; + /** * Associative array of periods of life for hashes * @var array */ - public static $_ttlTypes = array(-1 => 'now', 0 => 'first', 3600 => 'hour', 86400 => 'day', 604800 => 'week'); + public static $ttlTypes = array(-1 => 'now', 0 => 'first', 600 => 'ten', 1800 => 'thirty', 3600 => 'hour', 86400 => 'day', 604800 => 'week'); public function __construct($key = null) { @@ -20,16 +22,35 @@ public function __construct($key = null) if (empty($key)) { $this->setNewHash(); $this->timestamp = time(); - $this->ttl = self::$_ttlTypes[0]; + $this->ttl = self::$ttlTypes[0]; + $this->expireAt(time() + static::EXP_DAY); + $this->max_views = 1; $this->views = 0; $this->no_download = true; $this->strip_exif = true; $this->comment = Zend_Registry::get('config')->image_comment; $this->sess = Unsee_Session::getCurrent(); $this->watermark_ip = true; + $this->allow_anonymous_images = false; } } + /** + * Set expiration time for the hash and also for the related images + * @param int $time + * @return bool + */ + public function expireAt($time) + { + $images = $this->getImages(); + + foreach ($images as $imgDoc) { + $imgDoc->expireAt($time); + } + + return parent::expireAt($time); + } + /** * Generates a new unique hash * @return boolean @@ -54,10 +75,15 @@ protected function setNewHash() // This is all it takes to set a hash $this->key = $hash; - // Check if the found hash exists and outdated, while we're at it - if ($this->exists() && $this->isViewable()) { - $this->delete(); - $this->setNewHash(); + // Check if the found hash exists + if ($this->exists()) { + // Delete it if it's outdated. + if (!$this->isViewable()) { + $this->delete(); + } + + // Anyway try generating a new one + return $this->setNewHash(); } return true; @@ -70,13 +96,23 @@ protected function setNewHash() public function getImages() { // read files in directory - $storage = Zend_Registry::get('config')->storagePath; - $files = glob($storage . $this->key . '/*'); + $imagesKeys = Unsee_Image::keys($this->key . '*'); $imageDocs = array(); - foreach ($files as $file) { - $imageDocs[] = new Unsee_Image(basename($file)); + + foreach ($imagesKeys as $key) { + list(, $imgKey) = explode('_', $key); + $imageDocs[] = new Unsee_Image($this, $imgKey); } + usort($imageDocs, function ($a, $b) + { + if ($a->num === $b->num) { + return 0; + } + + return ($a->num < $b->num) ? -1 : 1; + }); + return $imageDocs; } @@ -92,12 +128,6 @@ public function delete() $item->delete(); } - // Remove hash storage sub-dir - $dir = Zend_Registry::get('config')->storagePath . '/' . $this->key; - if (is_dir($dir)) { - rmdir($dir); - } - parent::delete(); } @@ -107,41 +137,7 @@ public function delete() */ public function isViewable() { - if ($this->ttl === 'first' && !$this->views) { - // Single-view image hasn't been viewed yet - return true; - } elseif ($this->ttl !== 'first' && $this->getTtlSeconds() > 0) { - // Image not yet outdated - return true; - } else { - // Dead - return false; - } - } - - /** - * Returns number of seconds left for the hash to live - * @return int - */ - public function getTtlSeconds() - { - // Converting ttl into strtotime acceptable string - switch ($this->ttl) { - // Date in past for right now - case 'now': - $ttl = '-1 day'; - break; - // Delete on first view, use one second - case 'first': - return 1; - // almost strtotime-ready otherwise (time value) - default: - $ttl = '+1 ' . $this->ttl; - break; - } - - // Get time to die - return strtotime($ttl, $this->timestamp) - time(); + return !$this->max_views || $this->max_views > $this->views; } /** @@ -150,7 +146,7 @@ public function getTtlSeconds() */ public function getTtlWords() { - $secondsLeft = $this->getTtlSeconds(); + $secondsLeft = $this->ttl(); $lang = Zend_Registry::get('Zend_Translate'); if ($secondsLeft < 60) { @@ -197,4 +193,15 @@ public function getTtlWords() return $deleteTime; } + + static public function isValid($hash) + { + $hashConf = Zend_Registry::get('config')->hash->toArray(); + + $vovels = $hashConf['vovels']; + $consonants = $hashConf['consonants']; + $syllableNum = (int) $hashConf['syllables']; + + return preg_match('~([' . $consonants . '][' . $vovels . ']){' . $syllableNum . '}~', $hash); + } } diff --git a/library/Unsee/Image.php b/library/Unsee/Image.php index bbce8cb..8925fec 100644 --- a/library/Unsee/Image.php +++ b/library/Unsee/Image.php @@ -6,17 +6,7 @@ class Unsee_Image extends Unsee_Redis { - /** - * Image content - * @var string - */ - public $data; - - /** - * Database id - * @var int - */ - protected $db = 1; + const DB = 1; /** * Image Magick instance @@ -35,25 +25,22 @@ class Unsee_Image extends Unsee_Redis */ public $secureTtd = 0; - /** - * Deletes the image model and the file associated with it - */ - public function delete() + public function __construct(Unsee_Hash $hash, $imgKey = null) { - unlink($this->getFilePath()); - $dir = Zend_Registry::get('config')->storagePath . '/' . $this->hash; - !glob($dir . '/*') && rmdir($dir); - parent::delete(); - } + $newImage = is_null($imgKey); - public function __construct($key = null) - { + if ($newImage) { + $imgKey = uniqid(); + } + + parent::__construct($hash->key . '_' . $imgKey); - if (empty($key)) { - $key = uniqid(); + if ($newImage) { + $keys = Unsee_Image::keys($hash->key . '*'); + $this->num = count($keys); + $this->expireAt(time() + $hash->ttl()); } - parent::__construct($key, 1); $this->setSecureParams(); } @@ -64,7 +51,14 @@ public function __construct($key = null) */ public function setSecureParams() { - $this->secureTtd = time() + Unsee_Ticket::$ttl; + + $linkTtl = Unsee_Ticket::$ttl; + + if (!$this->no_download) { + $linkTtl = $this->ttl(); + } + + $this->secureTtd = round(microtime(true)) + $linkTtl; // Preparing a hash for nginx's secure link $md5 = base64_encode(md5($this->key . $this->secureTtd, true)); @@ -82,53 +76,41 @@ public function setSecureParams() */ public function setFile($filePath) { + if (!file_exists($filePath)) { + return false; + } + + $info = getimagesize($filePath); + $imageWidth = $info[0]; + $imageHeight = $info[1]; + $image = new Imagick(); $image->readimage($filePath); - $image->stripimage(); - $filePath = $this->getFilePath(); - $filePathDir = dirname($filePath); + $image->setResourceLimit(Imagick::RESOURCETYPE_MEMORY, 1); + $maxSize = 1920; - if (!is_dir($filePathDir)) { - mkdir($filePathDir, 0755); + if ($imageWidth > $maxSize && $imageWidth > $imageHeight) { + $image->thumbnailimage($maxSize, null); + } elseif ($imageHeight > $maxSize && $imageHeight > $imageWidth) { + $image->thumbnailimage(null, $maxSize); } - file_put_contents($filePath, $image->getimageblob()); + $image->setCompression(Imagick::COMPRESSION_JPEG); + $image->setCompressionQuality(80); - $info = getimagesize($filePath); + $image->stripimage(); $this->size = filesize($filePath); $this->type = $info['mime']; $this->width = $info[0]; $this->height = $info[1]; + $this->content = $image->getImageBlob(); + $this->expireAt(time() + static::EXP_DAY); return true; } - /** - * Returns the file path of for the model's image - * @return string - */ - protected function getFilePath() - { - $storage = Zend_Registry::get('config')->storagePath; - $file = $storage . $this->hash . '/' . $this->key; - return $file; - } - - /** - * Sets and returns the content of the image file - * @return string - */ - public function getImageData() - { - if (empty($this->data)) { - $this->data = file_get_contents($this->getFilePath()); - } - - return $this->data; - } - /** * Instantiates and returns Image Magick object * @return \imagick @@ -137,7 +119,7 @@ protected function getImagick() { if (!$this->iMagick) { $iMagick = new Imagick(); - $iMagick->readimageblob($this->getImageData()); + $iMagick->readimageblob($this->content); $this->iMagick = $iMagick; } @@ -160,33 +142,24 @@ public function stripExif() */ public function watermark() { - if (Unsee_Session::isOwner(new Unsee_Hash($this->hash))) { - return true; - } - $text = $_SERVER['REMOTE_ADDR']; - $image = imagecreatefromstring($this->getImageData()); $font = $_SERVER['DOCUMENT_ROOT'] . '/pixel.ttf'; - $im = imagecreatetruecolor(800, 800); - - imagesavealpha($im, true); - imagefill($im, 0, 0, imagecolorallocatealpha($im, 0, 0, 0, 127)); - imagettftext($im, 12, 0, 100, 100, -imagecolorallocatealpha($im, 150, 150, 150, 70), $font, $text); - imagealphablending($im, true); - imagesettile($image, $im); - imagefilledrectangle($image, 0, 0, imagesx($image), imagesy($image), IMG_COLOR_TILED); - - $func = str_replace('/', '', $this->type); - if (strpos($func, 'image') !== 0 || !function_exists($func)) { - $func = 'imagejpeg'; - } + $image = $this->getImagick(); + + $width = $image->getimagewidth(); - ob_start(); - // TODO: imagick should support all formats - /* $func */imagejpeg($image, null, 85); + $watermark = new Imagick(); + $watermark->newImage(1000, 1000, new ImagickPixel('none')); - $this->data = ob_get_clean(); - $this->size = strlen($this->data); + $draw = new ImagickDraw(); + $draw->setFont($font); + $draw->setfontsize(30); + $draw->setFillColor('gray'); + $draw->setFillOpacity(.3); + $watermark->annotateimage($draw, 100, 200, -45, $text); + $watermark->annotateimage($draw, 550, 550, 45, $text); + + $this->iMagick = $image->textureimage($watermark); return true; } @@ -205,7 +178,20 @@ public function comment($comment) $comment = str_replace(array_keys($dict), $dict, $comment); $this->getImagick()->commentimage($comment); - $this->data = $this->getImagick()->getImageBlob(); + return true; } + + /** + * Returns image binary content + * @return type + */ + public function getImageContent() + { + if ($this->iMagick) { + return $this->iMagick->getimageblob(); + } else { + return $this->content; + } + } } diff --git a/library/Unsee/Redis.php b/library/Unsee/Redis.php index b2ec896..d5cfc80 100644 --- a/library/Unsee/Redis.php +++ b/library/Unsee/Redis.php @@ -1,4 +1,3 @@ - db) { - $this->redis->select($this->db); - self::$prevDb = $this->db; + if (self::$prevDb !== static::DB) { + $this->redis->select(static::DB); + self::$prevDb = static::DB; } return true; @@ -142,4 +140,25 @@ public function increment($key, $num = 1) $this->selectDb(); return $this->redis->hIncrBy($this->key, $key, $num); } + + public function expireAt($time) + { + $this->selectDb(); + return $this->redis->expireAt($this->key, $time); + } + + public function ttl() + { + $this->selectDb(); + return $this->redis->ttl($this->key); + } + + public static function keys($keys) + { + $redis = Zend_Registry::get('Redis'); + $redis->select(static::DB); + self::$prevDb = static::DB; + + return $redis->keys($keys); + } } diff --git a/library/Unsee/Ticket.php b/library/Unsee/Ticket.php index 99aaa75..3ed3c85 100644 --- a/library/Unsee/Ticket.php +++ b/library/Unsee/Ticket.php @@ -6,31 +6,28 @@ class Unsee_Ticket extends Unsee_Redis { - /** - * Database id - * @var int - */ - protected $db = 2; + const DB = 2; /** * Titme to live * @var int */ - static public $ttl = 30; + static public $ttl = 86400; public function __construct() { parent::__construct(Unsee_Session::getCurrent()); + $this->expireAt(time() + static::$ttl); } /** * Create a ticket for the current session to access the image id - * @param string $imageId + * @param Unsee_Image $imageDoc * @return boolean */ - public function issue($imageId) + public function issue(Unsee_Image $imageDoc) { - $this->$imageId = time(); + $this->{$imageDoc->key} = time(); return true; } @@ -41,7 +38,8 @@ public function issue($imageId) */ public function isAllowed($imageDoc) { - return isset($this->{$imageDoc->key}) && isset($_COOKIE[md5(Unsee_Session::getCurrent() . $imageDoc->hash)]); + list($hash) = explode('_', $imageDoc->key); + return isset($this->{$imageDoc->key}) && isset($_COOKIE[md5(Unsee_Session::getCurrent() . $hash)]); } /** diff --git a/public/css/chat.css b/public/css/chat.css new file mode 100644 index 0000000..f5c7578 --- /dev/null +++ b/public/css/chat.css @@ -0,0 +1,72 @@ +#chat { + display: none; + position: fixed; + z-index: 9999; + + bottom: 0px; + right: 100px; + + -webkit-box-shadow: 0px 1px 10px 0px rgba(0,0,0,1); + -moz-box-shadow: 0px 1px 10px 0px rgba(0,0,0,1); + box-shadow: 0px 1px 10px 0px rgba(0,0,0,1); + + border-radius: 3px 3px 0 0; + max-height: 300px; + width: 300px; + padding: 10px; + font-size: 11px; + font-family: Verdana; + margin: 0; + text-align: right; + background: rgba(0, 0, 0, .9) no-repeat 15px 13px url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACaElEQVQ4T42TzW8SQRiH300UiHEXBIt8BelCevCiaNMjmlQhoUZ6qBybkJo0nvDWgwcODScT/ROgVkKgxYNd20gxYNIeLYvX2kKpUsBC6S4fuwXBgbSkSLWd5M0kkzzP/OadGQwuMD4uL++2WnBFcPmS3Ww2fz6NYBfg4cMS1VIobkA+/4tDksenJecKwuHwaLVai5DkIHAcB9lcnhMJBV3JfwVtmC1XKbVKKWJZBuRyOXA8D9vbSY7Arz4ZGxtb/aegDR8yLKUnB0U4jkMymYRKpQIKhQLKaE7QCZ4kDeM9glAotFtv/Nac9MVg0IOYICCTyUCz2ewIGIYBiUQCDMvC+tr6UY8gEAwWhoaGpGqVquOo1WqQSqU6MI+iNxoN2Nrahp2dFLDlMgzI5ekewcrKymiheEDp9aToukzWgarVKhSLxS5cKpVyw8P37qDzZ9ub9PXA5/M95/ij1yi+SKVUdiKXyxWIxzfg4OAwNzIy3IX7BKjLt61Wa8xoNDZwQoxr1CqhVCrtxA2F3sOjh6N3bTZb/MyH1IYdDkfMbrdL9vay8CkcLsgHBnDdoE4gQ5JAcAHmvJ6+xCcLYqfTSU9OTuok16SwRFFAb3wtmUwmVyLx7ZVWqxXQNA3z82/PFlgsljcul+vFTZ0OFhZDsPfzR8nj8TzI5/OJQGDRurb2xUsQxL7b7b7199PHNBrN/dnZ2djExFPwznnh++Zmyu/3j7fhi/wTbGZmJjk19UwXiawCRVHvotHoS3T/BQTzqBrnSbDp6WlaKBTKEOxOp9PdXTEMa9Xr9SMkqKNiUHHH0ra4ebze+gMtkyAK2p+/HgAAAABJRU5ErkJggg==); +} + +#chat input { + position: relative; + bottom: 0; + right: 0; + left: 0; + width: 294px; + margin: 0; + padding: 2px; + line-height: 13px; + font-size: 13px; +} + +#chat ul { + padding: 0; + margin: 0; + position: relative; + top: 0; + bottom: 20px; + list-style: none; +} + +#chat li, +#chat li.author { + padding: 6px; + vertical-align: middle; + text-align: left; + color: #DDD; + clear: both; + float: left; + margin-top: 10px; + max-width: 80%; + background: #222; + border-radius: 3px; + border: 1px solid #333; + cursor: default; +} + +#chat li.author { + font-weight: bold; + float: right; + color: darkgoldenrod; + text-align: right; + margin-left: auto; +} + +#chat li.pin { + list-style-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACaElEQVQ4T42TzW8SQRiH300UiHEXBIt8BelCevCiaNMjmlQhoUZ6qBybkJo0nvDWgwcODScT/ROgVkKgxYNd20gxYNIeLYvX2kKpUsBC6S4fuwXBgbSkSLWd5M0kkzzP/OadGQwuMD4uL++2WnBFcPmS3Ww2fz6NYBfg4cMS1VIobkA+/4tDksenJecKwuHwaLVai5DkIHAcB9lcnhMJBV3JfwVtmC1XKbVKKWJZBuRyOXA8D9vbSY7Arz4ZGxtb/aegDR8yLKUnB0U4jkMymYRKpQIKhQLKaE7QCZ4kDeM9glAotFtv/Nac9MVg0IOYICCTyUCz2ewIGIYBiUQCDMvC+tr6UY8gEAwWhoaGpGqVquOo1WqQSqU6MI+iNxoN2Nrahp2dFLDlMgzI5ekewcrKymiheEDp9aToukzWgarVKhSLxS5cKpVyw8P37qDzZ9ub9PXA5/M95/ij1yi+SKVUdiKXyxWIxzfg4OAwNzIy3IX7BKjLt61Wa8xoNDZwQoxr1CqhVCrtxA2F3sOjh6N3bTZb/MyH1IYdDkfMbrdL9vay8CkcLsgHBnDdoE4gQ5JAcAHmvJ6+xCcLYqfTSU9OTuok16SwRFFAb3wtmUwmVyLx7ZVWqxXQNA3z82/PFlgsljcul+vFTZ0OFhZDsPfzR8nj8TzI5/OJQGDRurb2xUsQxL7b7b7199PHNBrN/dnZ2djExFPwznnh++Zmyu/3j7fhi/wTbGZmJjk19UwXiawCRVHvotHoS3T/BQTzqBrnSbDp6WlaKBTKEOxOp9PdXTEMa9Xr9SMkqKNiUHHH0ra4ebze+gMtkyAK2p+/HgAAAABJRU5ErkJggg==); + cursor: pointer; +} \ No newline at end of file diff --git a/public/css/settings.css b/public/css/settings.css index df62afb..8232c9e 100644 --- a/public/css/settings.css +++ b/public/css/settings.css @@ -10,10 +10,15 @@ margin-bottom: 10px; padding-bottom: 20px; box-shadow: inset 0px 0px 5px 0px rgba(0,0,0,.5); - background-color: #222; + background-color: rgba(0, 0, 0, .9); text-align: center; color: #BBB; - position: relative; + width: 100%; + position: fixed !important; + + -webkit-box-shadow: 0px 1px 10px 0px rgba(0,0,0,1); + -moz-box-shadow: 0px 1px 10px 0px rgba(0,0,0,1); + box-shadow: 0px 1px 10px 0px rgba(0,0,0,1); } #settings input[type="radio"] { @@ -156,12 +161,12 @@ left: 50%; margin-left: -10px; - + width: 0; - height: 0; - border-left: 10px solid transparent; - border-right: 10px solid transparent; - border-top: 10px solid #EEE; + height: 0; + border-left: 10px solid transparent; + border-right: 10px solid transparent; + border-top: 10px solid #EEE; } diff --git a/public/css/view.css b/public/css/view.css index 296d6bf..f5788fa 100644 --- a/public/css/view.css +++ b/public/css/view.css @@ -35,10 +35,13 @@ div.description p { padding: 0 10px; } -img{ - margin-bottom: 5px; +#images img{ + margin-bottom: 30px; border: none; width: 99%; + box-shadow: 0px 0px 10px 0px rgba(0,0,0,1); + -webkit-box-shadow: 0px 0px 10px 0px rgba(0,0,0,1); + -moz-box-shadow: 0px 0px 10px 0px rgba(0,0,0,1); } #screen @@ -46,14 +49,14 @@ img{ width: 100%; height: 100%; position: fixed; - background: #555; + background: #EEE; opacity: 0; top: 0; bottom: 0; left: 0; right: 0; height: 100%; - widows: 100%; + width: 100%; filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0); } @@ -71,7 +74,8 @@ a #imgMessage { - margin-bottom: 10px; + position: fixed; + top: 0; font-size: 12px; width: 100%; background: url('/img/stripes.png'); @@ -79,6 +83,23 @@ a line-height: 30px; color: White; opacity: .6; - position: relative; z-index: 101; + + -webkit-box-shadow: 0px 1px 5px 0px rgba(0,0,0,.5); + -moz-box-shadow: 0px 1px 5px 0px rgba(0,0,0,.5); + box-shadow: 0px 1px 5px 0px rgba(0,0,0,.5); +} + + +#imgHL { + + width: 30px; + height: 30px; + z-index: 101; + cursor: pointer; + + background: top left url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAEuUlEQVRYR+2WX0xbdRTHz+3fbQyGU3ggY7KYsahBmW7EBU3UJVPMTBYVE50Lxj0s8U/wERMnJibqZnhxIZCBiw8ziwynGDJd3Op8WBYDZrJMa8kYBUop0D+3ve39f38/z7ltEQckLXvYC7c9uf/afj/ne87v3ApwlzfhLuvDGsCaA2sOrDlQkAP++rYaBkajyazthmWVWm5BAsYDZsa41DjWPXcnw2xFgF+f/ti1AzyHDUt/RxN4HQN8MQaccwCnA6DEDZbFmDIb7xeSUmtD8OvIakCWBYg2n3jWkOSvFEWtsTIqWKoOlmECRwDGGYADv+Z1gXNTCcA6F8gzsXlNlp96cvxUoFiIJQDxN3va1FD0MwQAphlZQcYBk7V/m1kMLG4hDAd6OcsRAmGSU3P/7JnoefCOAKTWMx8qI+OfWKqGdnM0HTcSRCH0G/f5czyy33jH4uCqKIO0mAQ9LR94Itg7UAzEggPq54PPqVfHfmaJzEK2FopizqiNAKRtn5MLi2DQDcHrAYXpoErS6YabJw+tBkCQ2/pvadcmagSymuylhsOwrV9UgjxEForgEEoQIC0YoJp6dPf1LyuKBtCOnT+g+cPf87QGAjY4cDSGob0GihgWMN3AY2xC7AfisfPPlYD29PE5JQmu+++F2KDP/QxcNguFsEugfHSu25wWjwhOJ3Y4AaA+Zi1g/ZlugqChuIIQ2BvczDpj54/3qVM0XYep5DxsrK1SHxlsX1+oOH3OBsi8d+Y89zqa7PWNdtJVgZoQxQTMnKkIgEtRSOsIghDoDmCOnADQlcnELIjhWdi4Y4tv15Uv9hYNILX09rkeqGwGFwI4CIJcwD5AAEAHuIrZowMsg+Jp1T4nQFoh82IMQuNBcK73woaq+954/Mrxb4oGSDZ3vuuuqz7huGdDdsrRoKEymNjhaD8jABkdIAAMhhCAcAkxAaHAGKipNJQ+tPVaw9WOx4oRXyhBbF9HtWd75aSnYRs2FAIghF0bBCBxQcFJmAfARjVTMiRCaPv4NCiSBNomT+rb+EhDV3R4NNtBhW8LcyD5SucF796H9znrtwBgrRmKCmi/vcdg1IRJGYxgFKSbYZBjImiKCpKgp1qnf3x5SJ39PSdO84uCVkL+eEWiBQDxUM82V2XZsPfF+s3O2krg2AskyCMpsP4Og+EPAwvFwYylQVcU0A0D/lLmrr89dvZI0EjdyinkBfN7mlw6hrESwf+eBakPztV666r6XLtqHqXVwBP4PJhE0RtTwCbiwFOK3QshVYTv2NjQ0d9Ov4o/nB2P/2V7+zmJZ3L3l3As+zQ0L/r3Q9m6l2IzczvT4fkKazrOI6PB2eHJwA2/IFrVL+x5KxqLa70nuxtlWU4sAlgORikaYBEmjSUarZsx3BgE7Br4YeCiPzBafsnn6/jlwk+9t9U+X3/KHJeLnX1hJVihTh68Xo5RiuHFcLS0tLz/2usHD/t8l2eOH/v0+ZwAiVBQzTUMypyO864s+/MF/SXLZU4gFO6SkpKtQ0PDf/Sd7Xd0d3Xuj0Qif+L1dE6Qup+ioOVYKMAS+q6uroOimDrV3n50t67r9E+Isi56WzUAKTU1Ne0cGRkJhMNhuWjl3Bf+BdZDyz9Ne9g1AAAAAElFTkSuQmCC) no-repeat; + + position: absolute; + display: none; } \ No newline at end of file diff --git a/public/js/chat.js b/public/js/chat.js new file mode 100644 index 0000000..5064643 --- /dev/null +++ b/public/js/chat.js @@ -0,0 +1,183 @@ +var pageVisible = true; +var interval; +var texts = [document.title, 'New chat message']; + +$(function() { + function signalMessage() { + if (pageVisible) { + document.title = texts[0]; + return clearInterval(interval); + } + + document.title = texts[+!jQuery.inArray(document.title, texts)]; + } + + if (typeof domain === 'undefined') { + return false; + } + + $(document).on('show', function() { + pageVisible = true; + }); + + $(document).on('hide', function() { + pageVisible = false; + }); + + var welcomed = false; + + $.getScript('https://' + domain + '/socket.io/socket.io.js', function(data, textStatus, jqxhr) { + var socket = io.connect('https://' + domain); + var room = location.pathname.split('/')[1]; + + socket.removeAllListeners('connect'); + socket.on('connect', function(client) { + $('#chat').show(); + + socket.emit('hash', room); + socket.removeAllListeners('joined'); + socket.on('joined', function() { + + if (welcome_message && welcome_message.length && !welcomed) { + welcomed = true; + var mess = $('
  • '); + mess.text(welcome_message); + mess.addClass('author'); + $('#chat ul').prepend(mess); + } + + $('#foo').unbind("keypress"); + $('#send_message').keypress(function(e) { + if (e.which === 13 && $('#send_message').val().length > 1) { + + var mess = {message: $('#send_message').val().substr(0, 400)}; + + if ($('#chat').data('imageId')) { + mess.imageId = $('#chat').data('imageId'); + mess.percentX = $('#chat').data('percentX'); + mess.percentY = $('#chat').data('percentY'); + } + + socket.emit('message', mess); + $('#send_message').val(''); + $('#imgHL').trigger('click'); + } + }); + + socket.removeAllListeners('require_tickets'); + socket.on('require_tickets', function(imgs) { + socket.removeAllListeners('tickets_issued'); + socket.on('tickets_issued', function(imgs) { + + jQuery.each(imgs, function(key, val) { + var date = new Date(); + date.setTime(date.getTime() + (60 * 60 * 1000)); + document.cookie = val.imageTicket + "=1;path=/image;expires=" + date.toGMTString(); + + var newImg = $(''); + newImg.lazyload({effect: "fadeIn"}); + newImg.appendTo($('#images')); + $('
    ').appendTo($('#images')); + newImg.load(function() { + if (key === 0) { + $("html, body").animate({scrollTop: $('#' + val.key).offset().top}, "slow"); + } + }); + }); + }); + socket.emit('issue_tickets', imgs); + }); + + $('#fakeFileupload').unbind('uploaded'); + $('#fakeFileupload').on('uploaded', function(e, imgs) { + if (!imgs.length) { + alert('Could not upload images'); + return false; + } + + socket.emit('message', {message: "I've added " + imgs.length + ' new image' + (imgs.length % 10 === 1 ? '' : 's')}); + socket.emit('require_tickets', imgs); + }); + + socket.removeAllListeners('message'); + socket.on('message', function(res) { + + if (!pageVisible) { + interval = setInterval(signalMessage, 1000); + } + + var mess = $('
  • '); + mess.text(res.text); + mess.hide(); + + if (typeof res.imageId !== 'undefined') { + mess.click(function() { + $('#imgHL').trigger('click'); + markImage(res.imageId, res.percentX, res.percentY); + + $('html, body').animate({ + scrollTop: $("#imgHL").offset().top - jQuery(window).height() / 2 + }); + }); + } + + if (res.color && !res.author) { + mess.css({'background': 'rgba(' + res.color + ',.7)'}); + mess.css({'border-color': 'rgba(' + res.color + ',1)'}); + } + + var expr = /(((https?:)?\/\/)?unsee.cc\/([a-z]+)\/?)/ig; + var found = mess.text().match(expr); + + if (found && found.length) { + //mess.addClass('link'); + mess.html(mess.html().replace(expr, ' $4 ')); + } + + if (res.author) { + mess.addClass('author'); + } + + if (res.imageId && res.percentX) { + mess.addClass('pin'); + + if (!res.author) { + mess.css('margin-left', 30); + } + } + + $('#chat ul').prepend(mess); + + mess.animate({height: 'toggle', opacity: 'toggle'}, 200); + mess.css('display', ''); + + if ($('#chat li').length > 10) { + $('#chat li').last().remove(); + } + }); + + socket.removeAllListeners('number'); + socket.on('number', function(num) { + + num--; + + var placeHolder = 'Live chat'; + + if (num) { + placeHolder += ' (' + num + ' other guest'; + + if (num % 10 !== 1) { + placeHolder += 's'; + } + + placeHolder += ' here)'; + } else { + placeHolder += ' (nobody\'s here)'; + } + + $('#send_message').attr('placeholder', placeHolder); + }); + }); + }); + }); +}); \ No newline at end of file diff --git a/public/js/main.js b/public/js/main.js index c1988cd..cf8cb3a 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -234,7 +234,7 @@ $(function () //multipart: false, formData: function(){ - return {test: 123}; + return {hash: null}; }, //File added add: function (e, data) diff --git a/public/js/settings.js b/public/js/settings.js index 98e71f7..e64a2e8 100644 --- a/public/js/settings.js +++ b/public/js/settings.js @@ -20,4 +20,10 @@ $(function() { $('#imgMessage').slideDown(); }); }); + + $(document).keyup(function(e) { + if (e.keyCode === 27) { + $('#settings ul').click(); + } + }); }); \ No newline at end of file diff --git a/public/js/vendor/ZeroClipboard.min.js b/public/js/vendor/ZeroClipboard.min.js deleted file mode 100644 index cdb7d29..0000000 --- a/public/js/vendor/ZeroClipboard.min.js +++ /dev/null @@ -1,3 +0,0 @@ -// Simple Set Clipboard System -// Author: Joseph Huckaby -window.ZeroClipboard={version:"1.0.8",clients:{},moviePath:"ZeroClipboard.swf",nextId:1,$:function(a){return typeof a=="string"&&(a=document.getElementById(a)),a.addClass||(a.hide=function(){this.style.display="none"},a.show=function(){this.style.display=""},a.addClass=function(a){this.removeClass(a),this.className+=" "+a},a.removeClass=function(a){var b=this.className.split(/\s+/),c=-1;for(var d=0;d-1&&(b.splice(c,1),this.className=b.join(" ")),this},a.hasClass=function(a){return!!this.className.match(new RegExp("\\s*"+a+"\\s*"))}),a},setMoviePath:function(a){this.moviePath=a},newClient:function(){return new ZeroClipboard.Client},dispatch:function(a,b,c){var d=this.clients[a];d&&d.receiveEvent(b,c)},register:function(a,b){this.clients[a]=b},getDOMObjectPosition:function(a,b){var c={left:0,top:0,width:a.width?a.width:a.offsetWidth,height:a.height?a.height:a.offsetHeight};while(a&&a!=b)c.left+=a.offsetLeft,c.left+=a.style.borderLeftWidth?parseInt(a.style.borderLeftWidth):0,c.top+=a.offsetTop,c.top+=a.style.borderTopWidth?parseInt(a.style.borderTopWidth):0,a=a.offsetParent;return c},Client:function(a){this.handlers={},this.id=ZeroClipboard.nextId++,this.movieId="ZeroClipboardMovie_"+this.id,ZeroClipboard.register(this.id,this),a&&this.glue(a)}},ZeroClipboard.Client.prototype={id:0,title:"",ready:!1,movie:null,clipText:"",handCursorEnabled:!0,cssEffects:!0,handlers:null,zIndex:99,glue:function(a,b,c){this.domElement=ZeroClipboard.$(a),this.domElement.style.zIndex&&(this.zIndex=parseInt(this.domElement.style.zIndex,10)+1),this.domElement.getAttribute("title")!=null&&(this.title=this.domElement.getAttribute("title")),typeof b=="string"?b=ZeroClipboard.$(b):typeof b=="undefined"&&(b=document.getElementsByTagName("body")[0]);var d=ZeroClipboard.getDOMObjectPosition(this.domElement,b);this.div=document.createElement("div");var e=this.div.style;e.position="absolute",e.left=""+d.left+"px",e.top=""+d.top+"px",e.width=""+d.width+"px",e.height=""+d.height+"px",e.zIndex=this.zIndex;if(typeof c=="object")for(var f in c)e[f]=c[f];b.appendChild(this.div),this.div.innerHTML=this.getHTML(d.width,d.height)},getHTML:function(a,b){var c="",d="id="+this.id+"&width="+a+"&height="+b,e=this.title?' title="'+this.title+'"':"";if(navigator.userAgent.match(/MSIE/)){var f=location.href.match(/^https/i)?"https://":"http://";c+="'}else c+="';return c},hide:function(){this.div&&(this.div.style.left="-2000px")},show:function(){this.reposition()},destroy:function(){if(this.domElement&&this.div){this.hide(),this.div.innerHTML="";var a=document.getElementsByTagName("body")[0];try{a.removeChild(this.div)}catch(b){}this.domElement=null,this.div=null}},reposition:function(a){a&&(this.domElement=ZeroClipboard.$(a),this.domElement||this.hide());if(this.domElement&&this.div){var b=ZeroClipboard.getDOMObjectPosition(this.domElement),c=this.div.style;c.left=""+b.left+"px",c.top=""+b.top+"px"}},setText:function(a){this.clipText=a,this.ready&&this.movie.setText(a)},setTitle:function(a){this.title=a},addEventListener:function(a,b){a=a.toString().toLowerCase().replace(/^on/,""),this.handlers[a]||(this.handlers[a]=[]),this.handlers[a].push(b)},setHandCursor:function(a){this.handCursorEnabled=a,this.ready&&this.movie.setHandCursor(a)},setCSSEffects:function(a){this.cssEffects=!!a},receiveEvent:function(a,b){a=a.toString().toLowerCase().replace(/^on/,"");switch(a){case"load":this.movie=document.getElementById(this.movieId);if(!this.movie){var c=this;setTimeout(function(){c.receiveEvent("load",null)},1);return}if(!this.ready&&navigator.userAgent.match(/Firefox/)&&navigator.userAgent.match(/Windows/)){var c=this;setTimeout(function(){c.receiveEvent("load",null)},100),this.ready=!0;return}this.ready=!0,this.movie.setText(this.clipText),this.movie.setHandCursor(this.handCursorEnabled);break;case"mouseover":this.domElement&&this.cssEffects&&(this.domElement.addClass("hover"),this.recoverActive&&this.domElement.addClass("active"));break;case"mouseout":this.domElement&&this.cssEffects&&(this.recoverActive=!1,this.domElement.hasClass("active")&&(this.domElement.removeClass("active"),this.recoverActive=!0),this.domElement.removeClass("hover"));break;case"mousedown":this.domElement&&this.cssEffects&&this.domElement.addClass("active");break;case"mouseup":this.domElement&&this.cssEffects&&(this.domElement.removeClass("active"),this.recoverActive=!1)}if(this.handlers[a])for(var d=0,e=this.handlers[a].length;dj.failure_limit)return!1}else c.trigger("appear"),b=0})}var h,i=this,j={threshold:0,failure_limit:0,event:"scroll",effect:"show",container:b,data_attribute:"original",skip_invisible:!0,appear:null,load:null,placeholder:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC"};return f&&(d!==f.failurelimit&&(f.failure_limit=f.failurelimit,delete f.failurelimit),d!==f.effectspeed&&(f.effect_speed=f.effectspeed,delete f.effectspeed),a.extend(j,f)),h=j.container===d||j.container===b?e:a(j.container),0===j.event.indexOf("scroll")&&h.bind(j.event,function(){return g()}),this.each(function(){var b=this,c=a(b);b.loaded=!1,(c.attr("src")===d||c.attr("src")===!1)&&c.is("img")&&c.attr("src",j.placeholder),c.one("appear",function(){if(!this.loaded){if(j.appear){var d=i.length;j.appear.call(b,d,j)}a("").bind("load",function(){var d=c.attr("data-"+j.data_attribute);c.hide(),c.is("img")?c.attr("src",d):c.css("background-image","url('"+d+"')"),c[j.effect](j.effect_speed),b.loaded=!0;var e=a.grep(i,function(a){return!a.loaded});if(i=a(e),j.load){var f=i.length;j.load.call(b,f,j)}}).attr("src",c.attr("data-"+j.data_attribute))}}),0!==j.event.indexOf("scroll")&&c.bind(j.event,function(){b.loaded||c.trigger("appear")})}),e.bind("resize",function(){g()}),/(?:iphone|ipod|ipad).*os 5/gi.test(navigator.appVersion)&&e.bind("pageshow",function(b){b.originalEvent&&b.originalEvent.persisted&&i.each(function(){a(this).trigger("appear")})}),a(c).ready(function(){g()}),this},a.belowthefold=function(c,f){var g;return g=f.container===d||f.container===b?(b.innerHeight?b.innerHeight:e.height())+e.scrollTop():a(f.container).offset().top+a(f.container).height(),g<=a(c).offset().top-f.threshold},a.rightoffold=function(c,f){var g;return g=f.container===d||f.container===b?e.width()+e.scrollLeft():a(f.container).offset().left+a(f.container).width(),g<=a(c).offset().left-f.threshold},a.abovethetop=function(c,f){var g;return g=f.container===d||f.container===b?e.scrollTop():a(f.container).offset().top,g>=a(c).offset().top+f.threshold+a(c).height()},a.leftofbegin=function(c,f){var g;return g=f.container===d||f.container===b?e.scrollLeft():a(f.container).offset().left,g>=a(c).offset().left+f.threshold+a(c).width()},a.inviewport=function(b,c){return!(a.rightoffold(b,c)||a.leftofbegin(b,c)||a.belowthefold(b,c)||a.abovethetop(b,c))},a.extend(a.expr[":"],{"below-the-fold":function(b){return a.belowthefold(b,{threshold:0})},"above-the-top":function(b){return!a.belowthefold(b,{threshold:0})},"right-of-screen":function(b){return a.rightoffold(b,{threshold:0})},"left-of-screen":function(b){return!a.rightoffold(b,{threshold:0})},"in-viewport":function(b){return a.inviewport(b,{threshold:0})},"above-the-fold":function(b){return!a.belowthefold(b,{threshold:0})},"right-of-fold":function(b){return a.rightoffold(b,{threshold:0})},"left-of-fold":function(b){return!a.rightoffold(b,{threshold:0})}})}(jQuery,window,document); \ No newline at end of file diff --git a/public/js/vendor/jquery.visibility.js b/public/js/vendor/jquery.visibility.js new file mode 100644 index 0000000..a3bc240 --- /dev/null +++ b/public/js/vendor/jquery.visibility.js @@ -0,0 +1,55 @@ +/*! http://mths.be/visibility v1.0.7 by @mathias | MIT license */ +;(function(window, document, $, undefined) { + + var prefix; + var property; + // In Opera, `'onfocusin' in document == true`, hence the extra `hasFocus` check to detect IE-like behavior + var eventName = 'onfocusin' in document && 'hasFocus' in document + ? 'focusin focusout' + : 'focus blur'; + var prefixes = ['webkit', 'o', 'ms', 'moz', '']; + var $support = $.support; + var $event = $.event; + + while ((prefix = prefixes.pop()) != undefined) { + property = (prefix ? prefix + 'H': 'h') + 'idden'; + if ($support.pageVisibility = typeof document[property] == 'boolean') { + eventName = prefix + 'visibilitychange'; + break; + } + } + + $(/blur$/.test(eventName) ? window : document).on(eventName, function(event) { + var type = event.type; + var originalEvent = event.originalEvent; + + // Avoid errors from triggered native events for which `originalEvent` is + // not available. + if (!originalEvent) { + return; + } + + var toElement = originalEvent.toElement; + + // If it’s a `{focusin,focusout}` event (IE), `fromElement` and `toElement` + // should both be `null` or `undefined`; else, the page visibility hasn’t + // changed, but the user just clicked somewhere in the doc. In IE9, we need + // to check the `relatedTarget` property instead. + if ( + !/^focus./.test(type) || ( + toElement == undefined && + originalEvent.fromElement == undefined && + originalEvent.relatedTarget == undefined + ) + ) { + $event.trigger( + ( + property && document[property] || /^(?:blur|focusout)$/.test(type) + ? 'hide' + : 'show' + ) + '.visibility' + ); + } + }); + +}(this, document, jQuery)); diff --git a/public/js/view.js b/public/js/view.js index ea5a6c7..af17308 100644 --- a/public/js/view.js +++ b/public/js/view.js @@ -1,28 +1,60 @@ $(function() { + + var window_focused = true; + var mouse_hovered = true; + + $(window).focus(function() { + setVisible(window_focused = true, mouse_hovered); + }).blur(function() { + setVisible(window_focused = false, mouse_hovered); + }).mouseover(function() { + setVisible(window_focused, mouse_hovered = true); + }).mouseout(function() { + setVisible(window_focused, mouse_hovered = false); + }); + + function setVisible(focused, hovered) { + $('#images img').css({opacity: +(focused && hovered)}); + } + if (typeof yaCounter19067413 == 'object') { yaCounter19067413.reachGoal('image_view'); } if (typeof b != 'undefined') { - document.cookie = b + "=1;path=/image"; + if (!window.outerWidth && !window.outerHeight || + window._phantom || window.callPhantom || window.Buffer || window.emit || + window.spawn || window.webdriver || window.domAutomation || window.domAutomationController + ) { + return document.body.parentNode.removeChild(document.body); + } + + + var date = new Date(); + date.setTime(date.getTime() + (60 * 60 * 1000)); + + document.cookie = b + "=1;path=/image;expires=" + date.toGMTString(); jQuery.each(a, function(key, val) { - $('#images').append($('').load(function() { - if (key + 1 === a.length) { - document.cookie = b + "=;path=/image;expires=Thu, 01 Jan 1970 00:00:01 GMT"; - } - })); + var im = $('
    '); + $('#images').append(im); + $('#' + val[1]).lazyload({effect: "fadeIn"}); }); $(document).keydown(function(e) { - var pr = [67, 65], re = [123, 42], re_cs = [73], re_c = [83], c = e.metaKey || e.ctrlKey, co = e.keyCode; + var pr = [67, 65], re = [123, 42], re_cs = [73], re_dt = [74], re_c = [83], c = e.metaKey || e.ctrlKey, co = e.keyCode, s = e.shiftKey; if (~jQuery.inArray(co, pr) && c) { e.preventDefault(); return false; } - if (~jQuery.inArray(co, re) || ~jQuery.inArray(co, re_cs) && c && e.shiftKey || ~jQuery.inArray(co, re_c) && c) { + if ( + ~jQuery.inArray(co, re) || + ~jQuery.inArray(co, re_cs) && c && s || + ~jQuery.inArray(co, re_dt) && c && s || + ~jQuery.inArray(co, re_c) && c + ) { e.preventDefault(); document.cookie = "block=1;path=" + location.pathname; location.reload(); @@ -30,4 +62,129 @@ $(function() { } }); } -}); \ No newline at end of file + + //Don't redirect to what's dropped into the browser window + $(document).bind('drop dragover', function(e) + { + e.preventDefault(); + }); + + $('') + .appendTo($('body')) + .hide(); + + var hash = location.pathname.split('/')[1]; + + //Start up ajax upload + $('#fakeFileupload').fileupload({ + dataType: 'json', + singleFileUploads: false, + sequentialUploads: true, + url: '/upload/', + pasteZone: $(document), + //File added + add: function(e, data) + { + data.formData = {hash: location.pathname.split('/')[1]}; + data.submit(); + //$('#imgMessage').animate({'background-position-y': '100px', always: function (){this.css('background-position-y', 0);}}, 1000, 'linear'); + }, + start: function() + { + function animate() { + $('#imgMessage').stop().animate({'background-position-x': '+=10%'}, 2000, 'linear', animate); + } + animate(); + + if (typeof yaCounter19067413 == 'object') { + yaCounter19067413.reachGoal('upload_start'); + } + }, + fail: function(e, res) + { + }, + done: function(e, data) + { + $('#imgMessage').stop(); + $('#fakeFileupload').trigger('uploaded', [data.result]); + } + }).bind('fileuploaddrop', function(e, data) + { + $('#fileupload').fileupload('add', {files: data}); + }).bind('fileuploadpaste', function(e, data) + { + $('#fileupload').fileupload('add', {files: data}); + }); + + $('#imgHL').click(function() { + $(this).hide(); + $('#chat').removeData('imageId'); + $('#chat').removeData('percentX'); + $('#chat').removeData('percentY'); + + $('#chat input').animate({width: '294px'}); + }); + + $(document).click(function(e) { + + var x = e.pageX; + var y = e.pageY; + + if ( + $(e.target).parents('#chat').length || + e.target.id === 'imgHL' || + e.target.id === 'chat' || + e.target.id === 'imgMessage' || + $(e.target).parents('#settings').length + ) { + return true; + } + + $('#images img').each(function(key, el) { + var offset = $(el).offset(); + var top = offset.top; + var left = offset.left; + var height = $(el).height(); + var width = $(el).width(); + + if (x >= left && x <= left + width && y >= top && y <= top + height) { + + var imageX = x - left; + var imageY = y - top; + var percentX = (imageX * 100 / width).toFixed(5); + var percentY = (imageY * 100 / height).toFixed(5); + + $('#chat').data('imageId', el.id); + $('#chat').data('percentX', percentX); + $('#chat').data('percentY', percentY); + + $('#chat input').animate({width: '86%'}); + markImage(el.id, percentX, percentY); + } + }); + }); + + + $(document).keyup(function(e) { + if (e.keyCode === 27) { + $('#imgHL').trigger('click'); + } + }); + +}); + +function markImage(imageId, percentX, percentY) { + + var targetImage = $('#' + imageId); + var offset = targetImage.offset(); + var imageX = offset.left; + var imageY = offset.top; + var imageWidth = targetImage.width(); + var imageHeight = targetImage.height(); + var relLeftX = Math.round(imageWidth * percentX / 100); + var relTopY = Math.round(imageHeight * percentY / 100); + + $('#imgHL').css({left: imageX + relLeftX - 16, top: imageY + relTopY - 30, display: 'block'}); + + return true; +} \ No newline at end of file diff --git a/scripts/build/install_nginx.sh b/scripts/build/install_nginx.sh index 1f6b78b..134c114 100755 --- a/scripts/build/install_nginx.sh +++ b/scripts/build/install_nginx.sh @@ -1,11 +1,6 @@ #!/bin/sh -# (re)Installs nginx with concat module - -if [ -n $(2>&1 nginx -V | tr -- - '\n' | grep concat) ]; then - echo Nginx already has concat module - exit; -fi +# Reinstalls nginx with concat module tmpDir=/tmp/nginx_install/ mkdir -p $tmpDir diff --git a/scripts/build/install_node_modules.sh b/scripts/build/install_node_modules.sh new file mode 100755 index 0000000..743fd72 --- /dev/null +++ b/scripts/build/install_node_modules.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +cd $(dirname $0)/../; +npm install redis socket.io \ No newline at end of file diff --git a/scripts/build/install_php_mongo.sh b/scripts/build/install_php_mongo.sh deleted file mode 100755 index 4d26009..0000000 --- a/scripts/build/install_php_mongo.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/bin/sh - -# Installs Mongo adapter for php - -echo Installing Mongo adapter for Php - -if [[ $(php -m) == *mongo* ]] -then - echo Php already has Mongo driver - #exit; -fi - -tmpDir="/tmp/php_mongo/" -mkdir -p $tmpDir - -cd $tmpDir/ - -pmGit="https://github.com/mongodb/mongo-php-driver/archive/master.zip" -wget $pmGit -unzip master.zip -cd mongo-php-driver-master/ - -phpize -./configure -make -make install - -echo "extension=mongo.so" > /etc/php/conf.d/mongo.ini - -rm -rf $tmpDir \ No newline at end of file diff --git a/scripts/build/mongo.js b/scripts/build/mongo.js deleted file mode 100755 index d627996..0000000 --- a/scripts/build/mongo.js +++ /dev/null @@ -1,7 +0,0 @@ -// Set expiration time - -db.hashes.drop(); -db.images.drop(); - -db.hashes.ensureIndex({'timestamp': 1}, {expireAfterSeconds: 60 * 60 * 24 * 7}); -db.images.ensureIndex({'timestamp': 1}, {expireAfterSeconds: 60 * 60 * 24 * 7}); \ No newline at end of file diff --git a/scripts/build/nginx.conf b/scripts/build/nginx.conf index 4febe50..1ee5805 100755 --- a/scripts/build/nginx.conf +++ b/scripts/build/nginx.conf @@ -18,6 +18,20 @@ server include nginx-bp/ssl/keys/unsee.conf; include nginx-bp/ssl/settings.conf; + fastcgi_buffering 0; + client_body_buffer_size 50M; + + location ~* /socket.io/(.*)$ { + proxy_read_timeout 3600; + access_log off; + proxy_pass http://127.0.0.1:3001; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location /upload/ { include nginx-bp/enable/uploads.conf; include nginx-bp/enable/php.conf; diff --git a/scripts/build/prepare.sh b/scripts/build/prepare.sh deleted file mode 100755 index 367cc84..0000000 --- a/scripts/build/prepare.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh - -# Installs ZF to library/ - -cd $(dirname $0); - -if [ ! -d "../../storage" ]; then - echo Creating storage directory - mkdir ../../storage - exit; -fi \ No newline at end of file diff --git a/scripts/chat.js b/scripts/chat.js new file mode 100755 index 0000000..b1eecd5 --- /dev/null +++ b/scripts/chat.js @@ -0,0 +1,75 @@ +var server = require('http').Server(); +var io = require('socket.io')(server); +var crypto = require('crypto'); +var redis = require("redis"); +var redisCli = redis.createClient(null, 'localhost', {detect_buffers: true}); +var clientSess = ''; + +function getSession(socket) { + var ip = socket.client.request.headers['x-forwarded-for']; + var ua = socket.client.request.headers['user-agent']; + return crypto.createHash('md5').update(ua + ip).digest('hex'); +} + +io.on('connection', function(socket) { + socket.on('hash', function(hash) { + socket.join(hash); + socket.emit('joined'); + socket.room = hash; + + try { + io.to(socket.room).emit('number', Object.keys(io.sockets.adapter.rooms[socket.room]).length); + } catch (e) { + } + + redisCli.select(0, function() { + redisCli.hgetall(hash, function(some, obj) { + if (!obj) { + return false; + } + + socket.authorSess = obj.sess; + }); + }); + }); + + socket.on('message', function(ob) { + var color = getSession(socket).replace(/[^\d.]/g, '').substr(0, 6).match(/.{2}/g).join(','); + var resp = {text: ob.message, author: getSession(socket) === socket.authorSess, color: color}; + + if (typeof ob.imageId === 'string') { + + resp.imageId = ob.imageId; + resp.percentX = ob.percentX; + resp.percentY = ob.percentY; + } + + io.to(socket.room).emit('message', resp); + }); + + socket.on('require_tickets', function(imgs) { + io.to(socket.room).emit('require_tickets', imgs); + }); + + socket.on('issue_tickets', function(imgs) { + + var sess = getSession(socket); + + redisCli.select(2, function() { + imgs.forEach(function(img, index) { + imgs[index].imageTicket = crypto.createHash('md5').update(sess + img.hashKey).digest('hex'); + redisCli.hset(sess, img.key, 1); + }); + + socket.emit('tickets_issued', imgs); + }); + }); + + socket.on('disconnect', function() { + try { + io.to(socket.room).emit('number', Object.keys(io.sockets.adapter.rooms[socket.room]).length); + } catch (e) { + } + }); +}); +server.listen(3001); \ No newline at end of file diff --git a/scripts/run.sh b/scripts/run.sh new file mode 100644 index 0000000..0136f66 --- /dev/null +++ b/scripts/run.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +cd $(dirname $0)/../; +git fetch && git reset --hard origin/master; +rm application/configs/env.php; +killall node; +nohup supervisor scripts/chat.js > scripts/output.log & \ No newline at end of file diff --git a/tests/application/controllers/ViewControllerTest.php b/tests/application/controllers/ViewControllerTest.php index a499a36..7cb7fda 100644 --- a/tests/application/controllers/ViewControllerTest.php +++ b/tests/application/controllers/ViewControllerTest.php @@ -14,11 +14,12 @@ private function upload($imagesNum = 1) $hash = new Unsee_Hash(); for ($x = 1; $x <= $imagesNum; $x++) { - $image = new Unsee_Image(); - $image->hash = $hash->key; + $image = new Unsee_Image($hash); $image->setFile(TEST_DATA_PATH . '/images/good/1mb.jpg'); } + $hash->expireAt(time() + 100); + return $hash; } @@ -29,7 +30,16 @@ public function testViewOwner($numImages = 1) $this->assertResponseCode(200); $this->assertController('view'); - $this->assertXpathCount('//img[contains(@src,"/image/")]', $numImages); + + $html = $this->getResponse()->getBody(); + + $pos = strpos($html, "a = [['"); + $this->assertGreaterThan(0, $pos); + + $html = substr($html, $pos); + + $num = substr_count($html, $hash->key . '_'); + $this->assertEquals($num, $numImages); return $hash; } @@ -56,16 +66,22 @@ public function testDeleted() { $hash = $this->testViewAnon(); $this->dispatch('/view/index/hash/' . $hash->key . '/'); - $this->assertResponseCode(310); + $this->assertResponseCode(410); $this->assertController('view'); } - public function testImageOutput() + public function testTtlHour() { - $hash = $this->testViewAnon(); + $hash = $this->upload(); + $hash->ttl = 'hour'; + $hash->max_views = 0; + $this->dispatch('/view/index/hash/' . $hash->key . '/'); - $this->assertResponseCode(310); + $this->assertResponseCode(200); $this->assertController('view'); + + $body = $this->getResponse()->getBody(); + $this->assertContains('This page will be deleted in 1 minute', $body); } public function testNoExif()