Array ('subitem' => true), ); $this->permMapping = array_merge($this->permMapping, $permissions); } /** * Define alternative event processing method names * * @return void * @see kEventHandler::$eventMethods * @access protected */ protected function mapEvents() { parent::mapEvents(); // ensure auto-adding of approve/decline and so on events $image_events = Array ( 'OnAfterCopyToTemp'=>'ImageAction', 'OnBeforeDeleteFromLive'=>'ImageAction', 'OnBeforeCopyToLive'=>'ImageAction', 'OnBeforeItemDelete'=>'ImageAction', 'OnAfterClone'=>'ImageAction', ); $this->eventMethods = array_merge($this->eventMethods, $image_events); } /** * Returns special of main item for linking with sub-item * * @param kEvent $event * @return string * @access protected */ protected function getMainSpecial(kEvent $event) { if ( $event->Special == 'list' && !$this->Application->isAdmin ) { // ListImages aggregated tag uses this special return ''; } return parent::getMainSpecial($event); } /** * Don't allow to delete primary category item image, when there are no more images * * @param kEvent $event * @param string $type * @return void * @access protected */ protected function customProcessing(kEvent $event, $type) { /** @var kDBItem $object */ $object = $event->getObject(); if ( $event->Name == 'OnMassDelete' && $type == 'before' ) { $ids = $event->getEventParam('ids'); $parent_info = $object->getLinkedInfo($event->Special); $sql = 'SELECT ImageId FROM ' . $object->TableName . ' WHERE DefaultImg = 1 AND ' . $parent_info['ForeignKey'] . ' = ' . $parent_info['ParentId']; $primary_file_id = $this->Conn->GetOne($sql); if ( $primary_file_id ) { $file_id_index = array_search($primary_file_id, $ids); if ( $file_id_index ) { // allow deleting of primary product file, when there is another file to make primary $sql = 'SELECT COUNT(*) FROM ' . $object->TableName . ' WHERE DefaultImg = 0 AND ' . $parent_info['ForeignKey'] . ' = ' . $parent_info['ParentId']; $non_primary_file_count = $this->Conn->GetOne($sql); if ( $non_primary_file_count ) { unset($ids[$file_id_index]); } } } $event->setEventParam('ids', $ids); } switch ($type) { case 'before' : // empty unused fields $object->SetDBField($object->GetDBField('LocalImage') ? 'Url' : 'LocalPath', ''); $object->SetDBField($object->GetDBField('LocalThumb') ? 'ThumbUrl' : 'ThumbPath', ''); if ( $object->GetDBField('SameImages') ) { $object->SetDBField('LocalImage', 1); $object->SetDBField('LocalPath', ''); $object->SetDBField('Url', ''); } break; case 'after': // make sure, that there is only one primary image for the item if ( $object->GetDBField('DefaultImg') ) { $sql = 'UPDATE ' . $object->TableName . ' SET DefaultImg = 0 WHERE ResourceId = ' . $object->GetDBField('ResourceId') . ' AND ImageId <> ' . $object->GetID(); $this->Conn->Query($sql); } break; } } /** * Performs temp-table related action on current image record * * @param kEvent $event * @return void * @access protected */ protected function ImageAction($event) { $id = $event->getEventParam('id'); /** @var kDBItem $object */ $object = $this->Application->recallObject($event->Prefix . '.-item', $event->Prefix, Array ('skip_autoload' => true)); if ( in_array($event->Name, Array ('OnBeforeDeleteFromLive', 'OnAfterClone')) ) { $object->SwitchToLive(); } elseif ( $event->Name == 'OnBeforeItemDelete' ) { // keep current table } else { $object->SwitchToTemp(); } $object->Load($id); /** @var FileHelper $file_helper */ $file_helper = $this->Application->recallObject('FileHelper'); $fields = Array ('LocalPath' => 'LocalImage', 'ThumbPath' => 'LocalThumb'); foreach ($fields as $a_field => $mode_field) { $file = $object->GetDBField($a_field); if ( !$file ) { continue; } $source_file = FULL_PATH . $file; switch ($event->Name) { // Copy image files to pending dir and update corresponding fields in temp record // Checking for existing files and renaming if necessary - two users may upload same pending files at the same time! case 'OnAfterCopyToTemp': $file = preg_replace('/^' . preg_quote(IMAGES_PATH, '/') . '/', IMAGES_PENDING_PATH, $file, 1); $new_file = $file_helper->ensureUniqueFilename(FULL_PATH, $file); $dst_file = FULL_PATH . $new_file; copy($source_file, $dst_file); $object->SetFieldOption($a_field, 'skip_empty', false); $object->SetDBField($a_field, $new_file); break; // Copy image files to live dir (checking if file exists and renaming if necessary) // and update corresponding fields in temp record (which gets copied to live automatically) case 'OnBeforeCopyToLive': if ( $object->GetDBField($mode_field) ) { // if image is local -> rename file if it exists in live folder $file = preg_replace('/^' . preg_quote(IMAGES_PENDING_PATH, '/') . '/', IMAGES_PATH, $file, 1); $new_file = $file_helper->ensureUniqueFilename(FULL_PATH, $file); $dst_file = FULL_PATH . $new_file; rename($source_file, $dst_file); } else { // if image is remote url - remove local file (if any), update local file field with empty value if ( file_exists($source_file) ) { @unlink($source_file); } $new_file = ''; } $object->SetFieldOption($a_field, 'skip_empty', false); $object->SetDBField($a_field, $new_file); break; case 'OnBeforeDeleteFromLive': // Delete image files from live folder before copying over from temp case 'OnBeforeItemDelete': // Delete image files when deleting Image object @unlink(FULL_PATH . $file); break; case 'OnAfterClone': // Copy files when cloning objects, renaming it on the fly $new_file = $file_helper->ensureUniqueFilename(FULL_PATH, $file); $dst_file = FULL_PATH . $new_file; copy($source_file, $dst_file); $object->SetFieldOption($a_field, 'skip_empty', false); $object->SetDBField($a_field, $new_file); break; } } if ( in_array($event->Name, Array ('OnAfterClone', 'OnBeforeCopyToLive', 'OnAfterCopyToTemp')) ) { $object->Update(null, null, true); } } /** * Sets primary image of user/category/category item * * @param kEvent $event * @return void * @access protected */ protected function OnSetPrimary($event) { /** @var kDBItem $object */ $object = $event->getObject(); $object->SetDBField('DefaultImg', 1); $object->Update(); } /** * Occurs before updating item * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeItemUpdate(kEvent $event) { parent::OnBeforeItemUpdate($event); $this->processImageStatus($event); } /** * Occurs after creating item * * @param kEvent $event * @return void * @access protected */ protected function OnAfterItemCreate(kEvent $event) { parent::OnAfterItemCreate($event); $this->processImageStatus($event); /** @var kDBItem $object */ $object = $event->getObject(); $object->Update(); } /** * Occurs before item changed * * @param kEvent $event */ function processImageStatus($event) { /** @var kDBItem $object */ $object = $event->getObject(); $id = $object->GetDBField('ResourceId'); $sql = 'SELECT ImageId FROM ' . $object->TableName . ' WHERE ResourceId = ' . $id . ' AND DefaultImg = 1'; $primary_image_id = $this->Conn->GetOne($sql); if ( !$primary_image_id ) { $object->SetDBField('DefaultImg', 1); } if ( $object->GetDBField('DefaultImg') && $object->Validate() ) { $sql = 'UPDATE ' . $object->TableName . ' SET DefaultImg = 0 WHERE ResourceId = ' . $id . ' AND ImageId <> ' . $object->GetDBField('ImageId'); $this->Conn->Query($sql); $object->SetDBField('Enabled', 1); } } /** * Apply any custom changes to list's sql query * * @param kEvent $event * @return void * @access protected * @see kDBEventHandler::OnListBuild() */ protected function SetCustomQuery(kEvent $event) { parent::SetCustomQuery($event); /** @var kDBList $object */ $object = $event->getObject(); if ( !$this->Application->isAdminUser ) { $object->addFilter('active', '%1$s.Enabled = 1'); } $product_id = $event->getEventParam('product_id'); if ( $product_id ) { $object->removeFilter('parent_filter'); $sql = 'SELECT ResourceId FROM ' . $this->Application->getUnitOption('p', 'TableName') . ' WHERE ProductId = ' . $product_id; $resource_id = (int)$this->Conn->GetOne($sql); $object->addFilter('product_images', '%1$s.ResourceId = ' . $resource_id); } /** @var kSearchHelper $search_helper */ $search_helper = $this->Application->recallObject('SearchHelper'); $types = $event->getEventParam('types'); $except_types = $event->getEventParam('except'); $type_clauses = $this->getTypeClauses($event); $search_helper->SetComplexFilter($event, $type_clauses, $types, $except_types); } /** * Return type clauses for list bulding on front * * @param kEvent $event * @return Array */ function getTypeClauses($event) { $type_clauses = Array (); $type_clauses['additional']['include'] = '%1$s.DefaultImg != 1'; $type_clauses['additional']['except'] = '%1$s.DefaultImg = 1'; $type_clauses['additional']['having_filter'] = false; return $type_clauses; } /** * [SCHEDULED TASK] Remove unused images from "/system/images" and "/system/images/pending" folders * * @param kEvent $event */ function OnCleanImages($event) { // 1. get images, that are currently in use $active_images = $this->_getActiveImages( $this->Application->getUnitOption('img', 'TableName') ); $active_images[] = 'noimage.gif'; // 2. get images on disk $this->_deleteUnusedImages(FULL_PATH . IMAGES_PATH, $active_images); // 3. get images in use from "images/pending" folder $active_images = $this->_getPendingImages(); // 4. get image on disk $this->_deleteUnusedImages(FULL_PATH . IMAGES_PENDING_PATH, $active_images); } /** * Gets image filenames (no path) from given table * * @param string $image_table * @return Array */ function _getActiveImages($image_table) { $sql = 'SELECT LocalPath, ThumbPath FROM ' . $image_table . ' WHERE COALESCE(LocalPath, "") <> "" OR COALESCE(ThumbPath) <> ""'; $images = $this->Conn->Query($sql); $active_images = Array (); foreach ($images as $image) { if ($image['LocalPath']) { $active_images[] = basename($image['LocalPath']); } if ($image['ThumbPath']) { $active_images[] = basename($image['ThumbPath']); } } return $active_images; } /** * Gets active images, that are currently beeing edited inside temporary tables * * @return Array */ function _getPendingImages() { $tables = $this->Conn->GetCol('SHOW TABLES'); $mask_edit_table = '/'.TABLE_PREFIX.'ses_(.*)_edit_' . TABLE_PREFIX . 'CatalogImages/'; $active_images = Array (); foreach ($tables as $table) { if (!preg_match($mask_edit_table, $table)) { continue; } $active_images = array_unique( array_merge($active_images, $this->_getActiveImages($table)) ); } return $active_images; } /** * Deletes all files in given path, except of given $active_images * * @param string $path * @param Array $active_images */ function _deleteUnusedImages($path, &$active_images) { $images = glob($path . '*.*'); if ($images) { $images = array_map('basename', $images); // delete images, that are on disk, but are not mentioned in CatalogImages table $delete_images = array_diff($images, $active_images); foreach ($delete_images as $delete_image) { unlink($path . $delete_image); } } } }