/** * Delete the thumbnails of an image. * @param string $folder absolute path of the folder which contains the image * @param string $file the image file name * @return nothing * @access public */ function GAL_deleteThumbnails($folder, $oldName) { $thumbFolder = $folder . $GLOBALS['GAL_thumbnail_folder']; if ($oldName != '') { $path_info = KT_pathinfo($oldName); $regexp = '/' . preg_quote($path_info['filename'], '/') . '_\\d+x\\d+'; if ($path_info['extension'] != "") { $regexp .= '\\.' . preg_quote($path_info['extension'], '/'); } $regexp .= '/'; $folderObj = new KT_folder(); $entry = $folderObj->readFolder($thumbFolder, false); if (!$folderObj->hasError()) { foreach ($entry['files'] as $key => $fDetail) { if (preg_match($regexp, $fDetail['name'])) { @unlink($thumbFolder . $fDetail['name']); } } } GAL_deleteImageInfo($folder, $oldName); } }
/** * Checks the type of the uploaded folder. * The name is checked against the this.allowedTypes array. * @return nothing; * @access public */ function checkExtensions() { if ($this->fileExists) { $path_info = KT_pathinfo($this->fileInfo['name']); foreach ($this->allowedExtensions as $key => $extension) { if (strtolower($extension) == strtolower($path_info['extension'])) { return; } if ($extension == '*') { return; } } $this->setError('UPLOAD_EXT_NOT_ALLOWED', array(), array($path_info['extension'])); } }
/** * Main class method. Return a fake recordset. * @var string * @access private */ function Execute() { $relFolder = KT_DynamicData($this->folder, '', '', false, array(), false); $relFolder = KT_TransformToUrlPath($relFolder, true); if (substr($relFolder, 0, 1) == '/') { $relFolder = substr($relFolder, 1); } $fullFolderPath = KT_realpath($this->baseFolder . $relFolder, true); if (substr($fullFolderPath, 0, strlen($this->baseFolder)) != $this->baseFolder) { if (isset($GLOBALS['tNG_debug_mode']) && $GLOBALS['tNG_debug_mode'] == "DEVELOPMENT") { die("Security error. The folder '" . $fullFolderPath . "' is out of base folder '" . $this->baseFolder . "'"); } else { die("Security error. Access to this folder is forbidden."); } } $this->path = $fullFolderPath; $noOfEntries = 0; $startCountEntries = $this->page * $this->recordsPerPage; $this->totalNo = 0; if (file_exists($this->path)) { //read folders $folder = new KT_folder(); $entries = $folder->readFolder($this->path, true); if ($folder->hasError()) { $err = $folder->getError(); if (isset($GLOBALS['tNG_debug_mode']) && $GLOBALS['tNG_debug_mode'] == "DEVELOPMENT") { $this->error = $err[1]; } else { $this->error = $err[0]; } } $this->filesArr = $entries['files']; $tmpFilesArr = array(); $tmpArr = array(); for ($i = 0; $i < count($this->filesArr); $i++) { $this->filesArr[$i]['fullname'] = $relFolder . $this->filesArr[$i]['name']; $path_info = KT_pathinfo($this->filesArr[$i]['name']); $this->filesArr[$i]['extension'] = $path_info['extension']; $filetime = filectime($this->path . $this->filesArr[$i]['name']); $this->filesArr[$i]['date'] = $filetime; if (in_array(strtolower($this->filesArr[$i]['extension']), $this->allowedExtensions) || in_array("*", $this->allowedExtensions)) { $tmpArr[] = $this->filesArr[$i][$this->orderField]; $tmpFilesArr[] = $this->filesArr[$i]; } } $this->filesArr = $tmpFilesArr; $this->Sort($tmpArr); $this->totalNo = count($this->filesArr); if ($this->recordsPerPage > 0) { $from = $this->page * $this->recordsPerPage; $this->filesArr = array_slice($this->filesArr, $from, $this->recordsPerPage); } for ($i = 0; $i < count($this->filesArr); $i++) { $this->filesArr[$i]['date'] = KT_convertDate(date("Y-m-d H:i:s", $this->filesArr[$i]['date']), "yyyy-mm-dd HH:ii:ss", $GLOBALS['KT_screen_date_format'] . ' ' . $GLOBALS['KT_screen_time_format_internal']); } // create fake recordset $this->filesArr = $this->formatData($this->filesArr); } $KT_FakeRecordset = new KT_FakeRecordset($this->conn); $ret = $KT_FakeRecordset->getFakeRecordset($this->filesArr); if ($ret === NULL) { if (isset($GLOBALS['tNG_debug_mode']) && $GLOBALS['tNG_debug_mode'] == "DEVELOPMENT") { die("Internal error: cannot create fake recordset. " . $KT_FakeRecordset->getError()); } else { die("Internal error: cannot create fake recordset."); } } return $ret; }
/** * Delete the thumbnails of an image. * @param string $folder absolute path of the folder which contains the image * @param string $file the image file name * @param string $md5 md5 string * @return nothing * @access public */ function tNG_deleteThumbnails($folder, $fileName, $md5) { if ($fileName != '') { $path_info = KT_pathinfo($fileName); if ($md5 == '') { $regexp = '/^' . preg_quote($path_info['filename'], '/') . '_\\d+x\\d+(_w_(\\w+))?'; } else { $regexp = '/^' . preg_quote($path_info['filename'], '/') . '(_w_(\\w+))?'; } if ($path_info['extension'] != "") { $regexp .= '\\.' . preg_quote($path_info['extension'], '/'); } $regexp .= '$/i'; $folderObj = new KT_folder(); $entry = $folderObj->readFolder($folder, false); if (!$folderObj->hasError()) { foreach ($entry['files'] as $key => $fDetail) { if (preg_match($regexp, $fDetail['name'], $matches)) { if ($md5 != '') { if (isset($matches[2]) && $matches[2] != $md5) { @unlink($folder . $fDetail['name']); } } else { @unlink($folder . $fDetail['name']); } } } } } }
/** * the main method, execute the code of the class; * Upload the file, set the file name in transaction; * return mix null or error object * @access public */ function Execute() { if ($this->tNG->getTransactionType() == "_import") { $this->tNG->uploadObj =& $this; } $ret = null; if ($this->dbFieldName != '') { $oldFileName = $this->tNG->getSavedValue($this->dbFieldName); $saveFileName = $this->tNG->getColumnValue($this->dbFieldName); if ($this->tNG->getColumnType($this->dbFieldName) != 'FILE_TYPE') { $errObj = new tNG_error('FILE_UPLOAD_WRONG_COLTYPE', array(), array($this->dbFieldName)); $errObj->addFieldError($this->dbFieldName, 'FILE_UPLOAD_WRONG_COLTYPE_D', array($this->dbFieldName)); return $errObj; } } else { $oldFileName = KT_DynamicData($this->renameRule, $this->tNG, '', true); if (isset($this->tNG->multipleIdx)) { $saveFileName = @$_FILES[$this->formFieldName . "_" . $this->tNG->multipleIdx]['name']; } else { $saveFileName = @$_FILES[$this->formFieldName]['name']; } } $this->dynamicFolder = KT_DynamicData($this->folder, $this->tNG, '', false); $arrArgs = array(); $autoRename = false; switch ($this->rename) { case 'auto': $autoRename = true; break; case 'none': break; case 'custom': $path_info = KT_pathinfo($saveFileName); $arrArgs = array('KT_name' => $path_info['filename'], 'KT_ext' => $path_info['extension']); $saveFileName = KT_DynamicData($this->renameRule, $this->tNG, '', false, $arrArgs); break; default: die('INTERNAL ERROR: Unknown upload rename method.'); } if (tNG_isFileInsideBaseFolder($this->folder, $saveFileName) === false) { $baseFileName = dirname(KT_realPath($this->dynamicFolder . $saveFileName, false)); return new tNG_error("FOLDER_DEL_SECURITY_ERROR", array(), array($baseFileName, tNG_getBaseFolder($this->folder))); } // Upload File $fileUpload = new KT_fileUpload(); if (isset($this->tNG->multipleIdx)) { $fileUpload->setFileInfo($this->formFieldName . "_" . $this->tNG->multipleIdx); } else { $fileUpload->setFileInfo($this->formFieldName); } $fileUpload->setFolder($this->dynamicFolder); $fileUpload->setRequired(false); $fileUpload->setAllowedExtensions($this->allowedExtensions); $fileUpload->setAutoRename($autoRename); $fileUpload->setMaxSize($this->maxSize); $this->uploadedFileName = $fileUpload->uploadFile($saveFileName, $oldFileName); $updateDB = basename($this->uploadedFileName); if ($fileUpload->hasError()) { $arrError = $fileUpload->getError(); $errObj = new tNG_error('FILE_UPLOAD_ERROR', array($arrError[0]), array($arrError[1])); if ($this->dbFieldName != '') { $errObj->addFieldError($this->dbFieldName, '%s', array($arrError[0])); } $ret = $errObj; } else { $this->dynamicFolder = KT_realpath($this->dynamicFolder); if ($this->uploadedFileName == "") { //Check if for update we need to rename file if ($this->rename == "custom") { $path_info = KT_pathinfo($oldFileName); $arrArgs['KT_ext'] = $path_info['extension']; } $tmpFileName = KT_DynamicData($this->renameRule, $this->tNG, '', false, $arrArgs); if ($tmpFileName != "" && $oldFileName != "" && $tmpFileName != $oldFileName) { if (file_exists($this->dynamicFolder . $oldFileName)) { if (@rename($this->dynamicFolder . $oldFileName, $this->dynamicFolder . $tmpFileName) === true) { $this->uploadedFileName = $tmpFileName; $updateDB = basename($this->uploadedFileName); } else { $ret = new tNG_error('FILE_UPLOAD_RENAME', array(), array($this->dynamicFolder . $oldFileName, $this->dynamicFolder . $tmpFileName)); } } } } if ($ret === null) { if ($this->tNG->getTransactionType() == "_insert" || $this->tNG->getTransactionType() == "_multipleInsert") { $this->tNG->registerTrigger('ERROR', 'Trigger_Default_RollBack', 1, $this); } $this->deleteThumbnails($this->dynamicFolder . 'thumbnails' . DIRECTORY_SEPARATOR, $oldFileName); if ($this->uploadedFileName != '') { $this->deleteThumbnails($this->dynamicFolder . 'thumbnails' . DIRECTORY_SEPARATOR, $this->uploadedFileName); } if ($this->dbFieldName != '' && $this->uploadedFileName != "") { $ret = $this->tNG->afterUpdateField($this->dbFieldName, $updateDB); } } if ($ret === null && $this->dbFieldName != "") { $this->tNG->setRawColumnValue($this->dbFieldName, $updateDB); } } $this->errObj = $ret; return $ret; }
/** * Main class method. Resize the image and apply the watermark; * @return string error string or url to thumbnail * @access public */ function Execute() { $ret = ""; $relpath = $this->relpath; $folder = KT_TransformToUrlPath($this->folder); $fileName = KT_DynamicData($this->renameRule, null); $fileName = KT_TransformToUrlPath($fileName, false); $fullFolder = KT_realpath($folder, true); $fullFileName = KT_realpath($fullFolder . $fileName, false); $path_info = KT_pathinfo($fullFileName); $thumbnailFolder = $path_info['dirname'] . '/thumbnails/'; if (substr($fullFileName, 0, strlen($fullFolder)) != $fullFolder) { if ($GLOBALS['tNG_debug_mode'] == 'DEVELOPMENT') { $baseFileName = dirname($fullFileName); $errorMsg = KT_getResource("FOLDER_DEL_SECURITY_ERROR_D", "tNG", array($baseFileName, $fullFolder)); $ret = $relpath . "includes/tng/styles/cannot_thumbnail.gif\" />" . $errorMsg . "<img style=\"display:none\" src=\"" . $relpath . "includes/tng/styles/cannot_thumbnail.gif"; } else { $ret = $relpath . "includes/tng/styles/cannot_thumbnail.gif"; } } else { if ($this->getFileName() !== false) { // make the resize $proportional = $this->keepProportion; $width = $this->width; $height = $this->height; if (!$this->watermark) { $thumbnailName = $path_info['filename'] . '_' . $width . 'x' . $height . (isset($path_info['extension']) ? '.' . $path_info['extension'] : ''); } else { $hash = tNG_watermarkHash(KT_realpath($this->watermarkImage, false), $this->watermarkAlpha, $this->watermarkResize, $this->watermarkAlignment); $thumbnailName = $path_info['filename'] . '_' . $width . 'x' . $height . '_w_' . $hash . (isset($path_info['extension']) ? '.' . $path_info['extension'] : ''); } $thumbnailFullName = $thumbnailFolder . $thumbnailName; if (!file_exists(KT_realpath($thumbnailFullName, false))) { $imageObj = new KT_image(); $imageObj->setPreferedLib($GLOBALS['tNG_prefered_image_lib']); $imageObj->addCommand($GLOBALS['tNG_prefered_imagemagick_path']); $imageObj->thumbnail($fullFileName, $thumbnailFolder, $thumbnailName, (int) $width, (int) $height, $proportional); if ($imageObj->hasError()) { $errorArr = $imageObj->getError(); if ($GLOBALS['tNG_debug_mode'] == 'DEVELOPMENT') { $errMsg = $errorArr[1]; $ret = $relpath . "includes/tng/styles/cannot_thumbnail.gif\" />" . $errMsg . "<img style=\"display:none\" src=\"" . $relpath . "includes/tng/styles/cannot_thumbnail.gif"; } else { $ret = $relpath . "includes/tng/styles/cannot_thumbnail.gif"; } return $ret; } else { // apply watermark if ($this->watermark) { // delete other watermarks for same picture tNG_deleteThumbnails($thumbnailFolder, $path_info['filename'] . '_' . $width . 'x' . $height, $hash); $imageObj = new KT_image(); $imageObj->setPreferedLib($GLOBALS['tNG_prefered_image_lib']); $imageObj->addCommand($GLOBALS['tNG_prefered_imagemagick_path']); $imageObj->watermark($thumbnailFullName, $thumbnailFullName, KT_realpath($this->watermarkImage, false), $this->watermarkAlpha, $this->watermarkResize, $this->watermarkAlignment); if ($imageObj->hasError()) { @unlink($thumbnailFullName); $arrError = $imageObj->getError(); $errObj = new tNG_error('IMG_WATERMARK', array(), array($arrError[1])); if ($GLOBALS['tNG_debug_mode'] == 'DEVELOPMENT') { $errMsg = $arrError[1]; $ret = $relpath . "includes/tng/styles/cannot_thumbnail.gif\" />" . $errMsg . "<img style=\"display:none\" src=\"" . $relpath . "includes/tng/styles/cannot_thumbnail.gif"; } else { $ret = $relpath . "includes/tng/styles/cannot_thumbnail.gif"; } return $ret; } } } $thumbnailURL = $this->folder . KT_DynamicData($this->renameRule, null); $thumbnailURL = dirname($thumbnailURL) . "/thumbnails/" . $thumbnailName; $ret = KT_CanonizeRelPath($thumbnailURL); if (!$imageObj->hasError()) { //$ret .= '?' . md5(filectime($ret)); } } else { $thumbnailURL = $this->folder . KT_DynamicData($this->renameRule, null); $thumbnailURL = dirname($thumbnailURL) . "/thumbnails/" . $thumbnailName; $ret = KT_CanonizeRelPath($thumbnailURL); } } else { $ret = $relpath . "includes/tng/styles/img_not_found.gif"; } } return $ret; }
$image->resize('../../../' . $hashFile['fullfilename'], '../../../' . $folder, $fileName, $hash['popupwidth'], $hash['popupheight'], true); if ($image->hasError()) { $err = $image->getError(); } $hashFile['fullfilename'] = $fileName; $wasResized = true; } } // apply the watermark if (isset($hash['popupWatermark']) && $hash['popupWatermark']) { if (!isset($wasResized)) { $arr = explode('/', $hashFile['fullfilename']); $fileName = array_pop($arr); $hashThumbnail = tNG_watermarkHash($hash['watermark'], $hash['watermarkAlpha'], $hash['watermarkResize'], $hash['watermarkAlignment']); $folder = implode('/', $arr) . '/thumbnails/'; $path_info = KT_pathinfo($fileName); $fileName = $path_info['filename'] . '_' . $size[0] . 'x' . $size[1] . '_w_' . $hashThumbnail . (isset($path_info['extension']) ? '.' . $path_info['extension'] : ''); $thumbnailForDelete = $path_info['filename'] . '_' . $size[0] . 'x' . $size[1]; } $image = new KT_image(); $image->setPreferedLib($GLOBALS['tNG_prefered_image_lib']); $image->addCommand($GLOBALS['tNG_prefered_imagemagick_path']); if (!isset($wasResized)) { if (!file_exists('../../../' . $folder . $fileName)) { $image->watermark('../../../' . $hashFile['fullfilename'], '../../../' . $folder . $fileName, $hash['watermark'], $hash['watermarkAlpha'], $hash['watermarkResize'], $hash['watermarkAlignment']); tNG_deleteThumbnails('../../../' . $folder, $thumbnailForDelete, $hashThumbnail); } } else { $image->watermark('../../../' . $folder . $fileName, '../../../' . $folder . $fileName, $hash['watermark'], $hash['watermarkAlpha'], $hash['watermarkResize'], $hash['watermarkAlignment']); } if ($image->hasError()) {
function getPhotoList() { if (!isset($this->config['photo_folder'])) { $arr = array('error' => 'Photo folder is not set. Please check to see if you have cookies enabled.'); return $arr; } $folderName = KT_RealPath($this->config['photo_folder'], true); $thumb_path = $folderName . $GLOBALS['GAL_thumbnail_folder']; $folder = new KT_folder(); $arr = $folder->readFolder($folderName, true); if ($folder->hasError()) { $errors = $folder->getError(); $errorLevel = !empty($GLOBALS['tNG_debug_mode']) ? $GLOBALS['tNG_debug_mode'] == 'PRODUCTION' ? 0 : 1 : 1; $toret['error'] = $errors[$errorLevel]; return $toret; } $ret = array(); foreach ($arr['files'] as $key => $value) { $fullFileName = $folderName . $value['name']; $info = KT_pathinfo($fullFileName); if (GAL_isImage($fullFileName)) { $fullThumbnailName = $info['filename'] . '_' . $this->config['thumbnails']['width'] . 'x' . $this->config['thumbnails']['height'] . '.' . $info['extension']; $imageDetails = GAL_getImageInfo($folderName, $value['name']); clearstatcache(); if ($imageDetails['dateLastModified'] != @filemtime($folderName . $value['name'])) { GAL_deleteThumbnails($folderName, $value['name']); GAL_getImageInfo($folderName, $value['name']); } # if this image has a thumbnail already. if (!file_exists($thumb_path . $fullThumbnailName)) { $value['thumbnail'] = NULL; } else { ob_start(); $thumbSizeArr = getimagesize($folderName . $GLOBALS['GAL_thumbnail_folder'] . $fullThumbnailName); $error = ob_get_contents(); ob_end_clean(); if (is_array($thumbSizeArr)) { $value['thumbnail'] = array('name' => $GLOBALS['GAL_thumbnail_folder'] . $fullThumbnailName, 'width' => $thumbSizeArr[0], 'height' => $thumbSizeArr[1]); } else { $value['thumbnail'] = array('error' => $error); } } if (is_readable($fullFileName)) { $imageSizeArr = getimagesize($fullFileName); $value['width'] = $imageSizeArr[0]; $value['height'] = $imageSizeArr[1]; } else { $value['error'] = 'The ' . $fullFileName . ' is not readable'; } $ret[] = $value; } } return $ret; }
/** * contruct the SQL and execute it. it is using as value for the field the primarey key value from the transaction; * return mix null or error object; * @access public */ function Execute() { $pk_value = $this->tNG->getPrimaryKeyValue(); $pk_type = $this->tNG->getColumnType($this->tNG->getPrimaryKey()); $pk_value = KT_escapeForSql($pk_value, $pk_type); if (count($this->fileRenameRule) > 0 || count($this->folderRenameRule) > 0) { $sql = 'SELECT * FROM ' . $this->table . ' WHERE ' . KT_escapeFieldName($this->field) . " = " . $pk_value; $rs = $this->tNG->connection->Execute($sql); if ($rs === false) { return new tNG_error('DEL_DR_SQL_ERROR', array(), array($this->tNG->connection->ErrorMsg(), $sql)); } if ($rs->RecordCount() == 0) { return null; } } // prepare to delete files if (count($this->fileRenameRule) > 0) { $fullFileName = array(); $fullFileNameFolder = array(); for ($i = 0; $i < count($this->fileRenameRule); $i++) { while (!$rs->EOF) { $arr = array(); foreach ($rs->fields as $col => $value) { $arr[$col] = $value; } $folder = $this->fileFolder[$i]; $fileName = KT_DynamicData($this->fileRenameRule[$i], $this->tNG, '', false, $arr); // security if (substr(KT_realpath($folder . $fileName), 0, strlen($folder)) != $folder) { $baseFileName = dirname(KT_realpath($folder . $fileName, false)); $ret = new tNG_error("FOLDER_DEL_SECURITY_ERROR", array(), array($baseFileName, $folder)); return $ret; } $fullFileName[] = $fileName; $fullFileNameFolder[] = $folder; $rs->MoveNext(); } $rs->MoveFirst(); } } // prepare to delete related folders if (count($this->folderRenameRule) > 0) { $relatedFolder = array(); for ($i = 0; $i < count($this->folderRenameRule); $i++) { while (!$rs->EOF) { $arr = array(); foreach ($rs->fields as $col => $value) { $arr[$col] = $value; } $folder = $this->folder[$i]; $f = KT_DynamicData($this->folderRenameRule[$i], $this->tNG, '', false, $arr); // security if (substr(KT_realpath($folder . $f), 0, strlen($folder)) != $folder) { $baseFileName = dirname(KT_realpath($folder . $f, false)); $ret = new tNG_error("FOLDER_DEL_SECURITY_ERROR", array(), array($baseFileName, $folder)); return $ret; } $relatedFolder[] = $folder . $f; $rs->MoveNext(); } $rs->MoveFirst(); } } // delete reocords $sql = "DELETE FROM " . $this->table . " WHERE " . KT_escapeFieldName($this->field) . " = " . $pk_value; $ret = $this->tNG->connection->Execute($sql); if ($ret === false) { return new tNG_error('DEL_DR_SQL_ERROR', array(), array($this->tNG->connection->ErrorMsg(), $sql)); } // delete files if (count($this->fileRenameRule) > 0) { for ($i = 0; $i < count($fullFileName); $i++) { if (file_exists($fullFileNameFolder[$i] . $fullFileName[$i])) { $delRet = @unlink($fullFileNameFolder[$i] . $fullFileName[$i]); $path_info = KT_pathinfo($fullFileNameFolder[$i] . $fullFileName[$i]); $this->deleteThumbnails($path_info['dirname'] . '/thumbnails/', $path_info['basename']); } } } // delete related folder if (count($this->folderRenameRule) > 0) { for ($i = 0; $i < count($relatedFolder); $i++) { $folder = new KT_Folder(); // delete thumbnails $folder->deleteFolderNR($relatedFolder[$i]); } } return null; }
/** * Getter. * @return string error string or object string * @access public */ function getSwfPath() { $ret = ''; $folder = $this->folder; $fileName = KT_DynamicData($this->renameRule, null); // security $base = KT_realpath($folder, true); if (substr(KT_realpath($base . $fileName), 0, strlen($base)) != $base) { return $ret; } $path_info = KT_pathinfo($folder . $fileName); $ret = $path_info['dirname'] . '/' . $path_info['filename']; return $ret; }
/** * the main method, execute the code of the class * return mix null or error object * @access public */ function Execute() { $ret = NULL; $baseFolder = KT_realpath($this->baseFolder); if ($this->rename == false && $this->dbFieldName != '') { $fileName = $this->tNG->getSavedValue($this->dbFieldName); } else { $fileName = KT_DynamicData($this->renameRule, $this->tNG, '', true); } $folder = KT_DynamicData($this->folder, $this->tNG, '', true); // security if (substr(KT_realpath($baseFolder . $folder . $fileName), 0, strlen($baseFolder)) != $baseFolder) { $ret = new tNG_error("FOLDER_DEL_SECURITY_ERROR", array(), array(dirname(KT_realpath($baseFolder . $folder . $fileName, false)), $baseFolder)); return $ret; } if ($fileName != "") { $fullFileName = $baseFolder . $folder . $fileName; if (file_exists($fullFileName)) { $delRet = @unlink($fullFileName); if ($delRet !== true) { $ret = new tNG_error('FILE_DEL_ERROR', array(), array($fullFileName)); $ret->setFieldError($this->fieldName, 'FILE_DEL_ERROR_D', array($fullFileName)); } else { $path_info = KT_pathinfo($fullFileName); $this->deleteThumbnails($path_info['dirname'] . '/thumbnails/', $path_info['basename']); } } } return $ret; }