GalleryBehavior.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505
  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. $imagesData = $query
  169. ->select(['id', 'name', 'description', 'rank'])
  170. ->from($this->tableName)
  171. ->where(['type' => $this->type, 'ownerId' => $this->getGalleryId()])
  172. ->orderBy(['rank' => 'asc'])
  173. ->all();
  174. $this->_images = [];
  175. foreach ($imagesData as $imageData) {
  176. $this->_images[] = new GalleryImage($this, $imageData);
  177. }
  178. }
  179. return $this->_images;
  180. }
  181. protected function getFileName($imageId, $version = 'original')
  182. {
  183. return implode(
  184. '/',
  185. [
  186. $this->getGalleryId(),
  187. $imageId,
  188. $version . '.' . $this->extension,
  189. ]
  190. );
  191. }
  192. public function getUrl($imageId, $version = 'original')
  193. {
  194. $path = $this->getFilePath($imageId, $version);
  195. if (!file_exists($path)) {
  196. $old_ext = $this->extension;
  197. $this->extension = 'png';
  198. $path = $this->getFilePath($imageId, $version);
  199. if (!file_exists($path)) {
  200. if($version=='large'){
  201. return $this->getUrl($imageId, 'medium');
  202. }
  203. $this->extension = $old_ext;
  204. return null;
  205. }
  206. }
  207. if (!empty($this->timeHash)) {
  208. $time = filemtime($path);
  209. $suffix = '?' . $this->timeHash . '=' . crc32($time);
  210. } else {
  211. $suffix = '';
  212. }
  213. return $this->url . '/' . $this->getFileName($imageId, $version) . $suffix;
  214. }
  215. public function getFilePath($imageId, $version = 'original')
  216. {
  217. return $this->directory . '/' . $this->getFileName($imageId, $version);
  218. }
  219. /**
  220. * Replace existing image by specified file
  221. *
  222. * @param $imageId
  223. * @param $path
  224. */
  225. public function replaceImage($imageId, $path)
  226. {
  227. $this->createFolders($this->getFilePath($imageId, 'original'));
  228. $ses = false;
  229. if( \Yii::$app->session->isActive ){
  230. \Yii::$app->session->close();
  231. $ses = true;
  232. }
  233. $originalImage = Image::getImagine()->open($path);
  234. //save image in original size
  235. //create image preview for gallery manager
  236. foreach ($this->versions as $version => $fn) {
  237. /** @var ImageInterface $image */
  238. $t = time();
  239. $image = call_user_func($fn, $originalImage, $this->getFilePath($imageId, $version));
  240. if( $image !== true ) {
  241. if (is_array($image)) {
  242. list($image, $options) = $image;
  243. } else {
  244. $options = [];
  245. }
  246. $image->save($this->getFilePath($imageId, $version), $options);
  247. }
  248. $te = time();
  249. \yii\BaseYii::Debug("time ($version):".($te-$t), 'photo gen');
  250. }
  251. if( $ses ) \Yii::$app->session->open();
  252. }
  253. private function removeFile($fileName)
  254. {
  255. try {
  256. return FileHelper::unlink($fileName);
  257. } catch (\Exception $ex){
  258. return true;
  259. }
  260. }
  261. /**
  262. * Get Gallery Id
  263. *
  264. * @return mixed as string or integer
  265. * @throws Exception
  266. */
  267. public function getGalleryId()
  268. {
  269. $pk = $this->owner->getPrimaryKey();
  270. if (is_array($pk)) {
  271. return implode($this->pkGlue, $pk);
  272. } else {
  273. return $pk;
  274. }
  275. }
  276. private function createFolders($filePath)
  277. {
  278. return FileHelper::createDirectory(FileHelper::normalizePath(dirname($filePath)), 0777);
  279. }
  280. /////////////////////////////// ========== Public Actions ============ ///////////////////////////
  281. public function deleteImage($imageId)
  282. {
  283. foreach ($this->versions as $version => $fn) {
  284. $filePath = $this->getFilePath($imageId, $version);
  285. $this->removeFile($filePath);
  286. }
  287. $filePath = $this->getFilePath($imageId, 'original');
  288. $parts = explode('/', $filePath);
  289. $parts = array_slice($parts, 0, count($parts) - 1);
  290. $dirPath = implode('/', $parts);
  291. @rmdir($dirPath);
  292. $db = \Yii::$app->db;
  293. $db->createCommand()
  294. ->delete(
  295. $this->tableName,
  296. ['id' => $imageId]
  297. )->execute();
  298. }
  299. public function deleteImages($imageIds)
  300. {
  301. foreach ($imageIds as $imageId) {
  302. $this->deleteImage($imageId);
  303. }
  304. if ($this->_images !== null) {
  305. $removed = array_combine($imageIds, $imageIds);
  306. $this->_images = array_filter(
  307. $this->_images,
  308. function ($image) use (&$removed) {
  309. return !isset($removed[$image->id]);
  310. }
  311. );
  312. }
  313. }
  314. public function addImage($fileName)
  315. {
  316. $db = \Yii::$app->db;
  317. $db->createCommand()
  318. ->insert(
  319. $this->tableName,
  320. [
  321. 'type' => $this->type,
  322. 'ownerId' => $this->getGalleryId()
  323. ]
  324. )->execute();
  325. $id = $db->getLastInsertID('gallery_image_id_seq');
  326. $db->createCommand()
  327. ->update(
  328. $this->tableName,
  329. ['rank' => $id],
  330. ['id' => $id]
  331. )->execute();
  332. $this->replaceImage($id, $fileName);
  333. $originname = $_FILES['gallery-image']['name'];
  334. $galleryImage = new GalleryImage($this, ['id' => $id, 'name'=>$originname]);
  335. if ($this->_images !== null) {
  336. $this->_images[] = $galleryImage;
  337. }
  338. return $galleryImage;
  339. }
  340. public function arrange($order)
  341. {
  342. $orders = [];
  343. $i = 0;
  344. foreach ($order as $k => $v) {
  345. if (!$v) {
  346. $order[$k] = $k;
  347. }
  348. $orders[] = $order[$k];
  349. $i++;
  350. }
  351. sort($orders);
  352. $i = 0;
  353. $res = [];
  354. foreach ($order as $k => $v) {
  355. $res[$k] = $orders[$i];
  356. \Yii::$app->db->createCommand()
  357. ->update(
  358. $this->tableName,
  359. ['rank' => $orders[$i]],
  360. ['id' => $k]
  361. )->execute();
  362. $i++;
  363. }
  364. // todo: arrange images if presented
  365. return $order;
  366. }
  367. /**
  368. * @param array $imagesData
  369. *
  370. * @return GalleryImage[]
  371. */
  372. public function updateImagesData($imagesData)
  373. {
  374. $imageIds = array_keys($imagesData);
  375. $imagesToUpdate = [];
  376. if ($this->_images !== null) {
  377. $selected = array_combine($imageIds, $imageIds);
  378. foreach ($this->_images as $img) {
  379. if (isset($selected[$img->id])) {
  380. $imagesToUpdate[] = $selected[$img->id];
  381. }
  382. }
  383. } else {
  384. $rawImages = (new Query())
  385. ->select(['id', 'name', 'description', 'rank'])
  386. ->from($this->tableName)
  387. ->where(['type' => $this->type, 'ownerId' => $this->getGalleryId()])
  388. ->andWhere(['in', 'id', $imageIds])
  389. ->orderBy(['rank' => 'asc'])
  390. ->all();
  391. foreach ($rawImages as $image) {
  392. $imagesToUpdate[] = new GalleryImage($this, $image);
  393. }
  394. }
  395. foreach ($imagesToUpdate as $image) {
  396. if (isset($imagesData[$image->id]['name'])) {
  397. $image->name = $imagesData[$image->id]['name'];
  398. }
  399. if (isset($imagesData[$image->id]['description'])) {
  400. $image->description = $imagesData[$image->id]['description'];
  401. }
  402. \Yii::$app->db->createCommand()
  403. ->update(
  404. $this->tableName,
  405. ['name' => $image->name, 'description' => $image->description],
  406. ['id' => $image->id]
  407. )->execute();
  408. }
  409. return $imagesToUpdate;
  410. }
  411. /**
  412. * Regenerate image versions
  413. * Should be called in migration on every model after changes in versions configuration
  414. *
  415. * @param string|null $oldExtension
  416. */
  417. public function updateImages($oldExtension = null)
  418. {
  419. $ids = array_map(function ($image) {
  420. /** @var GalleryImage $image */
  421. return $image->id;
  422. }, $this->getImages());
  423. foreach ($ids as $id) {
  424. if ($oldExtension !== null) {
  425. $newExtension = $this->extension;
  426. $this->extension = $oldExtension;
  427. $originalImage = Image::getImagine()
  428. ->open($this->getFilePath($id, 'original'));
  429. foreach ($this->versions as $version => $fn) {
  430. $this->removeFile($this->getFilePath($id, $version));
  431. }
  432. $this->extension = $newExtension;
  433. $originalImage->save($this->getFilePath($id, 'original'));
  434. } else {
  435. $originalImage = Image::getImagine()
  436. ->open($this->getFilePath($id, 'original'));
  437. }
  438. foreach ($this->versions as $version => $fn) {
  439. if ($version !== 'original') {
  440. $this->removeFile($this->getFilePath($id, $version), $this->getFilePath($id, $version));
  441. /** @var ImageInterface $image */
  442. // Yii::$app->async->run(function() {
  443. $image = call_user_func($fn, $originalImage, $this->getFilePath($id, $version));
  444. if( $image !== true ) {
  445. if (is_array($image)) {
  446. list($image, $options) = $image;
  447. } else {
  448. $options = [];
  449. }
  450. $image->save($this->getFilePath($id, $version), $options);
  451. }
  452. // });
  453. }
  454. }
  455. }
  456. }
  457. }