/** * Saves a file, usually sent by the client * @method save * @static * @param {array} $params * @param {string} [$params.data] the file data * @param {string} [$params.path="uploads"] parent path under web dir (see subpath) * @param {string} [$params.subpath=""] subpath that should follow the path, to save the image under * @param {string} [$params.name] override the name of the file, after the subpath * @param {string} [$params.skipAccess=false] if true, skips the check for authorization to write files there * @param {boolean} [$params.audio] set this to true if the file is an audio file * @return {array} Returns array containing ($name => $tailUrl) pair */ static function save($params) { if (empty($params['data'])) { throw new Q_Exception(array('field' => 'file'), 'data'); } // check whether we can write to this path, and create dirs if needed $data = $params['data']; $audio = $params['audio']; $path = isset($params['path']) ? $params['path'] : 'uploads'; $subpath = isset($params['subpath']) ? $params['subpath'] : ''; $realPath = Q::realPath(APP_WEB_DIR . DS . $path); if ($realPath === false) { throw new Q_Exception_MissingFile(array('filename' => APP_WEB_DIR . DS . $path)); } $name = isset($params['name']) ? $params['name'] : 'file'; if (!preg_match('/^[\\w.-]+$/', $name)) { $info = pathinfo($name); $name = Q_Utils::normalize($info['filename']) . '.' . $info['extension']; } // TODO: recognize some extensions maybe $writePath = $realPath . ($subpath ? DS . $subpath : ''); $lastChar = substr($writePath, -1); if ($lastChar !== DS and $lastChar !== '/') { $writePath .= DS; } $skipAccess = !empty($params['skipAccess']); Q_Utils::canWriteToPath($writePath, $skipAccess ? null : true, true); file_put_contents($writePath . $name, $data); $size = filesize($writePath . $name); $tailUrl = $subpath ? "{$path}/{$subpath}/{$name}" : "{$path}/{$name}"; /** * @event Q/file/save {after} * @param {string} user the user * @param {string} path the path in the url * @param {string} subpath the subpath in the url * @param {string} name the actual name of the file * @param {string} writePath the actual folder where the path is written * @param {string} data the data written to the file * @param {string} tailUrl consists of $path/[$subpath/]$name * @param {integer} size the size of the file that was written * @param {boolean} skipAccess whether we are skipping access checks * @param {boolean} audio whether the file is audio */ Q::event('Q/file/save', compact('path', 'subpath', 'name', 'writePath', 'data', 'tailUrl', 'size', 'skipAccess', 'audio'), 'after'); return array($name => $tailUrl); }
/** * Saves a file, usually sent by the client * @method save * @static * @param {array} $params * @param {string} [$params.data] the file data * @param {string} [$params.path="uploads"] parent path under web dir (see subpath) * @param {string} [$params.subpath=""] subpath that should follow the path, to save the image under * @param {string} [$params.name] override the name of the file, after the subpath * @param {string} [$params.skipAccess=false] if true, skips the check for authorization to write files there * @return {array} Returns array containing ($name => $tailUrl) pair */ static function save($params) { if (empty($params['data'])) { throw new Q_Exception(array('field' => 'file'), 'data'); } // check whether we can write to this path, and create dirs if needed $data = $params['data']; $path = isset($params['path']) ? $params['path'] : 'uploads'; $subpath = isset($params['subpath']) ? $params['subpath'] : ''; $realPath = Q::realPath(APP_WEB_DIR . DS . $path); if ($realPath === false) { throw new Q_Exception_MissingFile(array('filename' => APP_WEB_DIR . DS . $path)); } $name = isset($params['name']) ? $params['name'] : 'file'; if (!preg_match('/^[\\w.-]+$/', $name)) { $info = pathinfo($name); $name = Q_Utils::normalize($info['filename']) . '.' . $info['extension']; } // TODO: recognize some extensions maybe $writePath = $realPath . ($subpath ? DS . $subpath : ''); $lastChar = substr($writePath, -1); if ($lastChar !== DS and $lastChar !== '/') { $writePath .= DS; } $throwIfNotWritable = empty($params['skipAccess']) ? true : null; Q_Utils::canWriteToPath($writePath, $throwIfNotWritable, true); file_put_contents($writePath . DS . $name, $data); $tailUrl = $subpath ? "{$path}/{$subpath}/{$name}" : "{$path}/{$name}"; /** * @event Q/file/save {after} * @param {string} user * @param {string} path * @param {string} subpath * @param {string} name * @param {string} writePath * @param {string} data */ Q::event('Q/file/save', compact('path', 'subpath', 'name', 'writePath', 'data', 'tailUrl'), 'after'); return array($name => $tailUrl); }
/** * Imports an icon and sets $user->icon to the url. * @method importIcon * @static * @param {array} $user The user for whom the icon should be downloaded * @param {array} [$urls=array()] Array of urls * @param {string} [$directory=null] Defaults to APP/files/APP/uploads/Users/USERID/icon/imported * @return {string} the path to the icon directory */ static function importIcon($user, $urls = array(), $directory = null) { if (empty($directory)) { $app = Q_Config::expect('Q', 'app'); $directory = APP_FILES_DIR . DS . $app . DS . 'uploads' . DS . 'Users' . DS . Q_Utils::splitId($user->id) . DS . 'icon' . DS . 'imported'; } if (empty($urls)) { return $directory; } Q_Utils::canWriteToPath($directory, false, true); $type = Q_Config::get('Users', 'login', 'iconType', 'wavatar'); $largestSize = 0; $largestUrl = null; $largestImage = null; foreach ($urls as $basename => $url) { if (!is_string($url)) { continue; } $filename = $directory . DS . $basename; $info = pathinfo($filename); $size = $info['filename']; if ((string) (int) $size !== $size) { continue; } if ($largestSize < (int) $size) { $largestSize = (int) $size; $largestUrl = $url; } } if ($largestSize) { $largestImage = imagecreatefromstring(file_get_contents($largestUrl)); } foreach ($urls as $basename => $url) { if (is_string($url)) { $filename = $directory . DS . $basename; $info = pathinfo($filename); $size = $info['filename']; $success = false; if ($largestImage and (string) (int) $size === $size) { if ($size == $largestSize) { $image = $largestImage; $success = true; } else { $image = imagecreatetruecolor($size, $size); imagealphablending($image, false); $success = imagecopyresampled($image, $largestImage, 0, 0, 0, 0, $size, $size, $largestSize, $largestSize); } } if (!$success) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); $data = curl_exec($ch); curl_close($ch); $image = imagecreatefromstring($data); } $info = pathinfo($filename); switch ($info['extension']) { case 'png': $func = 'imagepng'; imagesavealpha($image, true); imagealphablending($image, true); break; case 'jpeg': case 'jpeg': $func = 'imagejpeg'; break; case 'gif': $func = 'imagegif'; break; } call_user_func($func, $image, $directory . DS . $info['filename'] . '.png'); } else { Q_Image::put($directory . DS . $basename, $url['hash'], $url['size'], $type, Q_Config::get('Users', 'login', 'gravatar', false)); } } $head = APP_FILES_DIR . DS . $app . DS . 'uploads'; $tail = str_replace(DS, '/', substr($directory, strlen($head))); $user->icon = '{{baseUrl}}/uploads' . $tail; return $directory; }
/** * Invites a user (or a future user) to a stream . * @method invite * @static * @param {string} $publisherId The id of the stream publisher * @param {string} $streamName The name of the stream the user will be invited to * @param {array} $who Array that can contain the following keys: * @param {string|array} [$who.userId] user id or an array of user ids * @param {string|array} [$who.fb_uid] fb user id or array of fb user ids * @param {string|array} [$who.label] label or an array of labels, or tab-delimited string * @param {string|array} [$who.identifier] identifier or an array of identifiers, or tab-delimited string * @param {integer} [$who.newFutureUsers] the number of new Users_User objects to create via Users::futureUser in order to invite them to this stream. This typically is used in conjunction with passing the "html" option to this function. * @param {array} [$options=array()] * @param {string|array} [$options.label] label or an array of labels for adding publisher's contacts * @param {string|array} [$options.myLabel] label or an array of labels for adding logged-in user's contacts * @param {integer} [$options.readLevel] => the read level to grant those who are invited * @param {integer} [$options.writeLevel] => the write level to grant those who are invited * @param {integer} [$options.adminLevel] => the admin level to grant those who are invited * @param {string} [$options.displayName] => the display name to use to represent the inviting user * @param {string} [$options.appUrl] => Can be used to override the URL to which the invited user will be redirected and receive "Q.Streams.token" in the querystring. * @param {array} [$options.html] => an array of ($template, $batchName) such as ("MyApp/foo.handlebars", "foo") for generating html snippets which can then be viewed from and printed via the action Streams/invitations?batchName=$batchName * @param {array} [$options.asUserId=null] Invite as this user id * @see Users::addLink() * @return {array} returns array with keys "success", "invited", "statuses", "identifierTypes", "alreadyParticipating" */ static function invite($publisherId, $streamName, $who, $options = array()) { if (isset($options['asUserId'])) { $asUserId = $options['asUserId']; $asUser = Users_User::fetch($asUserId); } else { $asUser = Users::loggedInUser(true); $asUserId = $asUser->id; } // Fetch the stream as the logged-in user $stream = Streams::fetch($asUserId, $publisherId, $streamName); if (!$stream) { throw new Q_Exception_MissingRow(array('table' => 'stream', 'criteria' => 'with that name'), 'streamName'); } $stream = reset($stream); // Do we have enough admin rights to invite others to this stream? if (!$stream->testAdminLevel('invite') || !$stream->testWriteLevel('join')) { throw new Users_Exception_NotAuthorized(); } if (isset($options['html'])) { $html = $options['html']; if (!is_array($html) or count($html) < 2) { throw new Q_Exception_WrongType(array('field' => "options.html", 'type' => 'array of 2 strings')); } list($template, $batchName) = $html; // validate these paths $filename = APP_VIEWS_DIR . DS . $template; if (!Q::realPath($filename)) { throw new Q_Exception_MissingFile(compact('filename')); } $ext = $pathinfo = pathinfo($template, PATHINFO_EXTENSION); if ($ext !== 'handlebars') { throw new Q_Exception_WrongValue(array('field' => 'options.html[0]', 'range' => 'a filename with extension .handlebars')); } $path = Streams::invitationsPath($asUserId) . DS . $batchName; Q_Utils::canWriteToPath($path, true, true); } // get user ids if any to array, throw if user not found $raw_userIds = isset($who['userId']) ? Users_User::verifyUserIds($who['userId'], true) : array(); // merge labels if any if (isset($who['label'])) { $label = $who['label']; if (is_string($label)) { $label = array_map('trim', explode("\t", $labels)); } $raw_userIds = array_merge($raw_userIds, Users_User::labelsToIds($asUserId, $label)); } // merge identifiers if any $identifierType = null; $statuses = null; if (isset($who['identifier'])) { $identifier = $who['identifier']; if (is_string($identifier)) { if (Q_Valid::email($who['identifier'])) { $identifierType = 'email'; } else { if (Q_Valid::phone($who['identifier'])) { $identifierType = 'mobile'; } } $identifier = array_map('trim', explode("\t", $identifier)); } $statuses = array(); $identifier_ids = Users_User::idsFromIdentifiers($identifier, $statuses); $raw_userIds = array_merge($raw_userIds, $identifier_ids); } // merge fb uids if any if (isset($who['fb_uid'])) { $fb_uids = $who['fb_uid']; if (is_string($fb_uids)) { $fb_uids = array_map('trim', explode("\t", $fb_uids)); } $raw_userIds = array_merge($raw_userIds, Users_User::idsFromFacebook($fb_uids)); } if (!empty($who['newFutureUsers'])) { $nfu = $who['newFutureUsers']; for ($i = 0; $i < $nfu; ++$i) { $raw_userIds[] = Users::futureUser('none', null)->id; } } // ensure that each userId is included only once // and remove already participating users $raw_userIds = array_unique($raw_userIds); $total = count($raw_userIds); $userIds = Streams_Participant::filter($raw_userIds, $stream); $to_invite = count($userIds); $appUrl = !empty($options['appUrl']) ? $options['appUrl'] : Q_Request::baseUrl() . '/' . Q_Config::get("Streams", "types", $stream->type, "invite", "url", "plugins/Streams/stream"); // now check and define levels for invited user $readLevel = isset($options['readLevel']) ? $options['readLevel'] : null; if (isset($readLevel)) { if (!$stream->testReadLevel($readLevel)) { // We can't assign greater read level to other people than we have ourselves! throw new Users_Exception_NotAuthorized(); } } $writeLevel = isset($options['writeLevel']) ? $options['writeLevel'] : null; if (isset($writeLevel)) { if (!$stream->testWriteLevel($writeLevel)) { // We can't assign greater write level to other people than we have ourselves! throw new Users_Exception_NotAuthorized(); } } $adminLevel = isset($options['adminLevel']) ? $options['adminLevel'] : null; if (isset($adminLevel)) { if (!$stream->testAdminLevel($adminLevel + 1)) { // We can't assign an admin level greater, or equal, to our own! // A stream's publisher can assign owners. Owners can assign admins. // Admins can confer powers to invite others, to some people. // Those people can confer the privilege to publish a message re this stream. // But admins can't assign other admins, and even stream owners // can't assign other owners. throw new Users_Exception_NotAuthorized(); } } // calculate expiry time $duration = Q_Config::get("Streams", "types", $stream->type, "invite", "duration", false); $expiry = $duration ? strtotime($duration) : null; // let node handle the rest, and get the result $params = array("Q/method" => "Streams/Stream/invite", "invitingUserId" => $asUserId, "username" => $asUser->username, "userIds" => Q::json_encode($userIds), "stream" => Q::json_encode($stream->toArray()), "appUrl" => $appUrl, "label" => Q::ifset($options, 'label', null), "myLabel" => Q::ifset($options, 'myLabel', null), "readLevel" => $readLevel, "writeLevel" => $writeLevel, "adminLevel" => $adminLevel, "displayName" => isset($options['displayName']) ? $options['displayName'] : Streams::displayName($asUser), "expiry" => $expiry); if ($template) { $params['template'] = $template; $params['batchName'] = $batchName; } $result = Q_Utils::queryInternal('Q/node', $params); return array('success' => $result, 'invited' => $userIds, 'statuses' => $statuses, 'identifierType' => $identifierType, 'alreadyParticipating' => $total - $to_invite); }
/** * Saves an image, usually sent by the client, in one or more sizes. * @method save * @static * @param {array} $params * @param {string} [$params.data] the image data * @param {string} [$params.path="uploads"] parent path under web dir (see subpath) * @param {string} [$params.subpath=""] subpath that should follow the path, to save the image under * @param {string} [$params.merge=""] path under web dir for an optional image to use as a background * @param {string} [$params.crop] array with keys "x", "y", "w", "h" to crop the original image * @param {string} [$params.save=array("x" => "")] array of $size => $basename pairs * where the size is of the format "WxH", and either W or H can be empty. * @param {string} [$params.skipAccess=false] if true, skips the check for authorization to write files there * @return {array} an array of ($size => $fullImagePath) pairs */ static function save($params) { if (empty($params['data'])) { throw new Q_Exception("Image data is missing"); } $imageData = $params['data']; $image = imagecreatefromstring($imageData); if (!$image) { throw new Q_Exception("Image type not supported"); } // image dimensions $maxW = Q_Config::get('Q', 'uploads', 'limits', 'image', 'width', 5000); $maxH = Q_Config::get('Q', 'uploads', 'limits', 'image', 'height', 5000); $iw = imagesx($image); $ih = imagesy($image); if ($maxW and $iw > $maxW) { throw new Q_Exception("Uploaded image width exceeds {$maxW}"); } if ($maxH and $iw > $maxH) { throw new Q_Exception("Uploaded image width exceeds {$maxH}"); } // check whether we can write to this path, and create dirs if needed $path = isset($params['path']) ? $params['path'] : 'uploads'; $subpath = isset($params['subpath']) ? $params['subpath'] : ''; $realPath = Q::realPath(APP_WEB_DIR . DS . $path); if ($realPath === false) { throw new Q_Exception_MissingFile(array('filename' => APP_WEB_DIR . DS . $path)); } $writePath = $realPath . ($subpath ? DS . $subpath : ''); $lastChar = substr($writePath, -1); if ($lastChar !== DS and $lastChar !== '/') { $writePath .= DS; } $throwIfNotWritable = empty($params['skipAccess']) ? true : null; Q_Utils::canWriteToPath($writePath, $throwIfNotWritable, true); // check if exif is available if (self::isJPEG($imageData)) { $exif = exif_read_data("data://image/jpeg;base64," . base64_encode($imageData)); // rotate original image if necessary (hopefully it's not too large). if (!empty($exif['Orientation'])) { switch ($exif['Orientation']) { case 3: $image = imagerotate($image, 180, 0); break; case 6: $image = imagerotate($image, -90, 0); break; case 8: $image = imagerotate($image, 90, 0); break; } } } $crop = isset($params['crop']) ? $params['crop'] : array(); $save = !empty($params['save']) ? $params['save'] : array('x' => ''); // crop parameters - size of source image $isw = isset($crop['w']) ? $crop['w'] : $iw; $ish = isset($crop['h']) ? $crop['h'] : $ih; $isx = isset($crop['x']) ? $crop['x'] : 0; $isy = isset($crop['y']) ? $crop['y'] : 0; // process requested thumbs $data = array(); $merge = null; $m = isset($params['merge']) ? $params['merge'] : null; if (isset($m) && strtolower(substr($m, -4)) === '.png') { $mergePath = Q::realPath(APP_WEB_DIR . DS . implode(DS, explode('/', $m))); if ($mergePath) { $merge = imagecreatefrompng($mergePath); $mw = imagesx($merge); $mh = imagesy($merge); } } foreach ($save as $size => $name) { if (empty($name)) { // generate a filename do { $name = Q_Utils::unique(8) . '.png'; } while (file_exists($writePath . $name)); } if (strrpos($name, '.') === false) { $name .= '.png'; } list($n, $ext) = explode('.', $name); $sw = $isw; $sh = $ish; $sx = $isx; $sy = $isy; // determine destination image size if (!empty($size)) { $sa = explode('x', $size); if (count($sa) > 1) { if ($sa[0] === '') { if ($sa[1] === '') { $dw = $sw; $dh = $sh; } else { $dh = intval($sa[1]); $dw = $sw * $dh / $sh; } } else { $dw = intval($sa[0]); if ($sa[1] === '') { $dh = $sh * $dw / $sw; } else { $dh = intval($sa[1]); } } } else { $dw = $dh = intval($sa[0]); } // calculate the origin point of source image // we have a cropped image of dimension $sw, $sh and need to make new with dimension $dw, $dh if ($dw / $sw < $dh / $sh) { // source is wider then destination $new = $dw / $dh * $sh; $sx += round(($sw - $new) / 2); $sw = round($new); } else { // source is narrower then destination $new = $dh / $dw * $sw; $sy += round(($sh - $new) / 2); $sh = round($new); } } else { $size = ''; $dw = $sw; $dh = $sh; } // create destination image $maxWidth = Q_Config::get('Q', 'images', 'maxWidth', null); $maxHeight = Q_Config::get('Q', 'images', 'maxHeight', null); if (isset($maxWidth) and $dw > $maxWidth) { throw new Q_Exception("Image width exceeds maximum width of {$dw}"); } if (isset($maxHeight) and $dh > $maxHeight) { throw new Q_Exception("Image height exceeds maximum height of {$dh}"); } $thumb = imagecreatetruecolor($dw, $dh); $res = $sw === $dw && $sh === $dh ? imagecopy($thumb, $image, 0, 0, $sx, $sy, $sw, $sh) : imagecopyresampled($thumb, $image, 0, 0, $sx, $sy, $dw, $dh, $sw, $sh); if (!$res) { throw new Q_Exception("Failed to save image file of type '{$ext}'"); } if ($merge) { $mergethumb = imagecreatetruecolor($mw, $mh); imagesavealpha($mergethumb, false); imagealphablending($mergethumb, false); if (imagecopyresized($mergethumb, $merge, 0, 0, 0, 0, $dw, $dh, $mw, $mh)) { imagecopy($thumb, $mergethumb, 0, 0, 0, 0, $dw, $dh); } } switch ($ext) { case 'jpeg': case 'jpeg': $func = 'imagejpeg'; break; case 'gif': $func = 'imagegif'; break; case 'png': default: $func = 'imagepng'; break; } if ($res = call_user_func($func, $thumb, $writePath . $name)) { $data[$size] = $subpath ? "{$path}/{$subpath}/{$name}" : "{$path}/{$name}"; } } $data[''] = $subpath ? "{$path}/{$subpath}" : "{$path}"; /** * @event Q/image/save {after} * @param {string} user * @param {string} path * @param {string} subpath * @param {string} writePath * @param {string} data */ Q::event('Q/image/save', compact('path', 'subpath', 'writePath', 'data', 'save', 'crop'), 'after'); return $data; }