GalleryBehavior.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506
  1. <?php
  2. namespace app\forks\galleryManager;
  3. use Imagine\Image\Box;
  4. use Imagine\Image\ImageInterface;
  5. use yii\base\Behavior;
  6. use yii\base\Exception;
  7. use yii\db\ActiveRecord;
  8. use yii\db\Query;
  9. use yii\helpers\FileHelper;
  10. use yii\imagine\Image;
  11. use app\forks\galleryManager\GalleryImage;
  12. /**
  13. * Behavior for adding gallery to any model.
  14. *
  15. * @author Bogdan Savluk <savluk.bogdan@gmail.com>
  16. *
  17. * @property string $galleryId
  18. */
  19. class GalleryBehavior extends Behavior
  20. {
  21. /**
  22. * Glue used to implode composite primary keys
  23. * @var string
  24. */
  25. public $pkGlue = '_';
  26. /**
  27. * @var string Type name assigned to model in image attachment action
  28. * @see GalleryManagerAction::$types
  29. * @example $type = 'Post' where 'Post' is the model name
  30. */
  31. public $type;
  32. /**
  33. * @var ActiveRecord the owner of this behavior
  34. * @example $owner = Post where Post is the ActiveRecord with GalleryBehavior attached under public function behaviors()
  35. */
  36. public $owner;
  37. /**
  38. * Widget preview height
  39. * @var int
  40. */
  41. public $previewHeight = 200;
  42. /**
  43. * Widget preview width
  44. * @var int
  45. */
  46. public $previewWidth = 200;
  47. /**
  48. * Extension for saved images
  49. * @var string
  50. */
  51. public $extension;
  52. /**
  53. * Path to directory where to save uploaded images
  54. * @var string
  55. */
  56. public $directory;
  57. /**
  58. * Directory Url, without trailing slash
  59. * @var string
  60. */
  61. public $url;
  62. /**
  63. * @var array Functions to generate image versions
  64. * @note Be sure to not modify image passed to your version function,
  65. * because it will be reused in all other versions,
  66. * Before modification you should copy images as in examples below
  67. * @note 'preview' & 'original' versions names are reserved for image preview in widget
  68. * and original image files, if it is required - you can override them
  69. * @example
  70. * [
  71. * 'small' => function ($img) {
  72. * return $img
  73. * ->copy()
  74. * ->resize($img->getSize()->widen(200));
  75. * },
  76. * 'medium' => function ($img) {
  77. * $dstSize = $img->getSize();
  78. * $maxWidth = 800;
  79. * ]
  80. */
  81. public $versions;
  82. /**
  83. * name of query param for modification time hash
  84. * to avoid using outdated version from cache - set it to false
  85. * @var string
  86. */
  87. public $timeHash = '_';
  88. /**
  89. * Used by GalleryManager
  90. * @var bool
  91. * @see GalleryManager::run
  92. */
  93. public $hasName = true;
  94. /**
  95. * Used by GalleryManager
  96. * @var bool
  97. * @see GalleryManager::run
  98. */
  99. public $hasDescription = true;
  100. /**
  101. * Used by GalleryManager
  102. * @var string
  103. * @see GalleryManager::run
  104. */
  105. public $copyName = "";
  106. /**
  107. * @var string Table name for saving gallery images meta information
  108. */
  109. public $tableName = '{{%gallery_image}}';
  110. protected $_galleryId;
  111. /**
  112. * @param ActiveRecord $owner
  113. */
  114. public function attach($owner)
  115. {
  116. parent::attach($owner);
  117. if (!isset($this->versions['original'])) {
  118. $this->versions['original'] = function ($image) {
  119. return $image;
  120. };
  121. }
  122. if (!isset($this->versions['preview'])) {
  123. $this->versions['preview'] = function ($originalImage) {
  124. /** @var ImageInterface $originalImage */
  125. return $originalImage
  126. ->thumbnail(new Box($this->previewWidth, $this->previewHeight));
  127. };
  128. }
  129. }
  130. public function events()
  131. {
  132. return [
  133. ActiveRecord::EVENT_BEFORE_DELETE => 'beforeDelete',
  134. ActiveRecord::EVENT_AFTER_UPDATE => 'afterUpdate',
  135. ActiveRecord::EVENT_AFTER_FIND => 'afterFind',
  136. ];
  137. }
  138. public function beforeDelete()
  139. {
  140. $images = $this->getImages();
  141. foreach ($images as $image) {
  142. $this->deleteImage($image->id);
  143. }
  144. $dirPath = $this->directory . '/' . $this->getGalleryId();
  145. @rmdir($dirPath);
  146. }
  147. public function afterFind()
  148. {
  149. $this->_galleryId = $this->getGalleryId();
  150. }
  151. public function afterUpdate()
  152. {
  153. $galleryId = $this->getGalleryId();
  154. if ($this->_galleryId != $galleryId) {
  155. $dirPath1 = $this->directory . '/' . $this->_galleryId;
  156. $dirPath2 = $this->directory . '/' . $galleryId;
  157. rename($dirPath1, $dirPath2);
  158. }
  159. }
  160. protected $_images = null;
  161. /**
  162. * @return GalleryImage[]
  163. */
  164. public function getImages()
  165. {
  166. if ($this->_images === null) {
  167. $query = new \yii\db\Query();
  168. // ->where(['type' => $this->type, 'ownerId' => $this->getGalleryId()])
  169. $imagesData = $query
  170. ->select(['id', 'name', 'description', 'rank', 'type'])
  171. ->from($this->tableName)
  172. ->where([ 'ownerId' => $this->getGalleryId()])
  173. ->orderBy(['rank' => 'asc'])
  174. ->all();
  175. $this->_images = [];
  176. foreach ($imagesData as $imageData) {
  177. $this->_images[] = new GalleryImage($this, $imageData);
  178. }
  179. }
  180. return $this->_images;
  181. }
  182. protected function getFileName($imageId, $version = 'original')
  183. {
  184. return implode(
  185. '/',
  186. [
  187. $this->getGalleryId(),
  188. $imageId,
  189. $version . '.' . $this->extension,
  190. ]
  191. );
  192. }
  193. public function getUrl($imageId, $version = 'original')
  194. {
  195. $path = $this->getFilePath($imageId, $version);
  196. if (!file_exists($path)) {
  197. $old_ext = $this->extension;
  198. $this->extension = 'png';
  199. $path = $this->getFilePath($imageId, $version);
  200. if (!file_exists($path)) {
  201. if($version=='large'){
  202. return $this->getUrl($imageId, 'medium');
  203. }
  204. $this->extension = $old_ext;
  205. return null;
  206. }
  207. }
  208. if (!empty($this->timeHash)) {
  209. $time = filemtime($path);
  210. $suffix = '?' . $this->timeHash . '=' . crc32($time);
  211. } else {
  212. $suffix = '';
  213. }
  214. return $this->url . '/' . $this->getFileName($imageId, $version) . $suffix;
  215. }
  216. public function getFilePath($imageId, $version = 'original')
  217. {
  218. return $this->directory . '/' . $this->getFileName($imageId, $version);
  219. }
  220. /**
  221. * Replace existing image by specified file
  222. *
  223. * @param $imageId
  224. * @param $path
  225. */
  226. public function replaceImage($imageId, $path)
  227. {
  228. $this->createFolders($this->getFilePath($imageId, 'original'));
  229. $ses = false;
  230. if( \Yii::$app->session->isActive ){
  231. \Yii::$app->session->close();
  232. $ses = true;
  233. }
  234. $originalImage = Image::getImagine()->open($path);
  235. //save image in original size
  236. //create image preview for gallery manager
  237. foreach ($this->versions as $version => $fn) {
  238. /** @var ImageInterface $image */
  239. $t = time();
  240. $image = call_user_func($fn, $originalImage, $this->getFilePath($imageId, $version));
  241. if( $image !== true ) {
  242. if (is_array($image)) {
  243. list($image, $options) = $image;
  244. } else {
  245. $options = [];
  246. }
  247. $image->save($this->getFilePath($imageId, $version), $options);
  248. }
  249. $te = time();
  250. \yii\BaseYii::Debug("time ($version):".($te-$t), 'photo gen');
  251. }
  252. if( $ses ) \Yii::$app->session->open();
  253. }
  254. private function removeFile($fileName)
  255. {
  256. try {
  257. return FileHelper::unlink($fileName);
  258. } catch (\Exception $ex){
  259. return true;
  260. }
  261. }
  262. /**
  263. * Get Gallery Id
  264. *
  265. * @return mixed as string or integer
  266. * @throws Exception
  267. */
  268. public function getGalleryId()
  269. {
  270. $pk = $this->owner->getPrimaryKey();
  271. if (is_array($pk)) {
  272. return implode($this->pkGlue, $pk);
  273. } else {
  274. return $pk;
  275. }
  276. }
  277. private function createFolders($filePath)
  278. {
  279. return FileHelper::createDirectory(FileHelper::normalizePath(dirname($filePath)), 0777);
  280. }
  281. /////////////////////////////// ========== Public Actions ============ ///////////////////////////
  282. public function deleteImage($imageId)
  283. {
  284. foreach ($this->versions as $version => $fn) {
  285. $filePath = $this->getFilePath($imageId, $version);
  286. $this->removeFile($filePath);
  287. }
  288. $filePath = $this->getFilePath($imageId, 'original');
  289. $parts = explode('/', $filePath);
  290. $parts = array_slice($parts, 0, count($parts) - 1);
  291. $dirPath = implode('/', $parts);
  292. @rmdir($dirPath);
  293. $db = \Yii::$app->db;
  294. $db->createCommand()
  295. ->delete(
  296. $this->tableName,
  297. ['id' => $imageId]
  298. )->execute();
  299. }
  300. public function deleteImages($imageIds)
  301. {
  302. foreach ($imageIds as $imageId) {
  303. $this->deleteImage($imageId);
  304. }
  305. if ($this->_images !== null) {
  306. $removed = array_combine($imageIds, $imageIds);
  307. $this->_images = array_filter(
  308. $this->_images,
  309. function ($image) use (&$removed) {
  310. return !isset($removed[$image->id]);
  311. }
  312. );
  313. }
  314. }
  315. public function addImage($fileName)
  316. {
  317. $db = \Yii::$app->db;
  318. $db->createCommand()
  319. ->insert(
  320. $this->tableName,
  321. [
  322. 'type' => $this->type,
  323. 'ownerId' => $this->getGalleryId()
  324. ]
  325. )->execute();
  326. $id = $db->getLastInsertID('gallery_image_id_seq');
  327. $db->createCommand()
  328. ->update(
  329. $this->tableName,
  330. ['rank' => $id],
  331. ['id' => $id]
  332. )->execute();
  333. $this->replaceImage($id, $fileName);
  334. $originname = $_FILES['gallery-image']['name'];
  335. $galleryImage = new GalleryImage($this, ['id' => $id, 'name'=>$originname]);
  336. if ($this->_images !== null) {
  337. $this->_images[] = $galleryImage;
  338. }
  339. return $galleryImage;
  340. }
  341. public function arrange($order)
  342. {
  343. $orders = [];
  344. $i = 0;
  345. foreach ($order as $k => $v) {
  346. if (!$v) {
  347. $order[$k] = $k;
  348. }
  349. $orders[] = $order[$k];
  350. $i++;
  351. }
  352. sort($orders);
  353. $i = 0;
  354. $res = [];
  355. foreach ($order as $k => $v) {
  356. $res[$k] = $orders[$i];
  357. \Yii::$app->db->createCommand()
  358. ->update(
  359. $this->tableName,
  360. ['rank' => $orders[$i]],
  361. ['id' => $k]
  362. )->execute();
  363. $i++;
  364. }
  365. // todo: arrange images if presented
  366. return $order;
  367. }
  368. /**
  369. * @param array $imagesData
  370. *
  371. * @return GalleryImage[]
  372. */
  373. public function updateImagesData($imagesData)
  374. {
  375. $imageIds = array_keys($imagesData);
  376. $imagesToUpdate = [];
  377. if ($this->_images !== null) {
  378. $selected = array_combine($imageIds, $imageIds);
  379. foreach ($this->_images as $img) {
  380. if (isset($selected[$img->id])) {
  381. $imagesToUpdate[] = $selected[$img->id];
  382. }
  383. }
  384. } else {
  385. $rawImages = (new Query())
  386. ->select(['id', 'name', 'description', 'rank'])
  387. ->from($this->tableName)
  388. ->where([ 'ownerId' => $this->getGalleryId()])
  389. // ->where(['type' => $this->type, 'ownerId' => $this->getGalleryId()])
  390. ->andWhere(['in', 'id', $imageIds])
  391. ->orderBy(['rank' => 'asc'])
  392. ->all();
  393. foreach ($rawImages as $image) {
  394. $imagesToUpdate[] = new GalleryImage($this, $image);
  395. }
  396. }
  397. foreach ($imagesToUpdate as $image) {
  398. if (isset($imagesData[$image->id]['name'])) {
  399. $image->name = $imagesData[$image->id]['name'];
  400. }
  401. if (isset($imagesData[$image->id]['description'])) {
  402. $image->description = $imagesData[$image->id]['description'];
  403. }
  404. \Yii::$app->db->createCommand()
  405. ->update(
  406. $this->tableName,
  407. ['name' => $image->name, 'description' => $image->description],
  408. ['id' => $image->id]
  409. )->execute();
  410. }
  411. return $imagesToUpdate;
  412. }
  413. /**
  414. * Regenerate image versions
  415. * Should be called in migration on every model after changes in versions configuration
  416. *
  417. * @param string|null $oldExtension
  418. */
  419. public function updateImages($oldExtension = null)
  420. {
  421. $ids = array_map(function ($image) {
  422. /** @var GalleryImage $image */
  423. return $image->id;
  424. }, $this->getImages());
  425. foreach ($ids as $id) {
  426. if ($oldExtension !== null) {
  427. $newExtension = $this->extension;
  428. $this->extension = $oldExtension;
  429. $originalImage = Image::getImagine()
  430. ->open($this->getFilePath($id, 'original'));
  431. foreach ($this->versions as $version => $fn) {
  432. $this->removeFile($this->getFilePath($id, $version));
  433. }
  434. $this->extension = $newExtension;
  435. $originalImage->save($this->getFilePath($id, 'original'));
  436. } else {
  437. $originalImage = Image::getImagine()
  438. ->open($this->getFilePath($id, 'original'));
  439. }
  440. foreach ($this->versions as $version => $fn) {
  441. if ($version !== 'original') {
  442. $this->removeFile($this->getFilePath($id, $version), $this->getFilePath($id, $version));
  443. /** @var ImageInterface $image */
  444. // Yii::$app->async->run(function() {
  445. $image = call_user_func($fn, $originalImage, $this->getFilePath($id, $version));
  446. if( $image !== true ) {
  447. if (is_array($image)) {
  448. list($image, $options) = $image;
  449. } else {
  450. $options = [];
  451. }
  452. $image->save($this->getFilePath($id, $version), $options);
  453. }
  454. // });
  455. }
  456. }
  457. }
  458. }
  459. }