redmine_apijs 6.8.2 → 6.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/README +4 -4
  3. data/app/controllers/apijs_controller.rb +41 -41
  4. data/app/views/attachments/_links.html.erb +3 -3
  5. data/app/views/settings/_apijs.html.erb +5 -3
  6. data/assets/javascripts/apijs-redmine.js +6 -5
  7. data/assets/javascripts/apijs-redmine.min.js +1 -1
  8. data/assets/javascripts/apijs-redmine.min.js.map +1 -1
  9. data/assets/javascripts/apijs.min.js +2 -2
  10. data/assets/javascripts/apijs.min.js.map +1 -1
  11. data/assets/stylesheets/apijs-print.min.css +1 -1
  12. data/assets/stylesheets/apijs-print.min.css.map +1 -1
  13. data/assets/stylesheets/apijs-screen-rtl.min.css +2 -2
  14. data/assets/stylesheets/apijs-screen-rtl.min.css.map +1 -1
  15. data/assets/stylesheets/apijs-screen.min.css +2 -2
  16. data/assets/stylesheets/apijs-screen.min.css.map +1 -1
  17. data/config/locales/cs.yml +1 -1
  18. data/config/locales/de.yml +1 -1
  19. data/config/locales/el.yml +1 -1
  20. data/config/locales/es.yml +1 -1
  21. data/config/locales/hu.yml +1 -1
  22. data/config/locales/it.yml +1 -1
  23. data/config/locales/ja.yml +1 -1
  24. data/config/locales/nl.yml +1 -1
  25. data/config/locales/pl.yml +1 -1
  26. data/config/locales/pt-BR.yml +1 -1
  27. data/config/locales/pt.yml +1 -1
  28. data/config/locales/ro.yml +1 -1
  29. data/config/locales/ru.yml +1 -1
  30. data/config/locales/sk.yml +1 -1
  31. data/config/locales/tr.yml +15 -15
  32. data/config/locales/uk.yml +1 -1
  33. data/config/locales/zh.yml +1 -1
  34. data/init.rb +27 -7
  35. data/lib/apijs_attachment.rb +3 -3
  36. data/lib/image.py +119 -19
  37. data/lib/redmine_apijs.rb +34 -11
  38. data/lib/useragentparser.rb +9 -9
  39. data/redmine_apijs.gemspec +2 -4
  40. metadata +5 -6
  41. data/lib/apijs_const.rb +0 -35
  42. data/lib/video.py +0 -80
@@ -1,7 +1,7 @@
1
1
  tr:
2
- apijs_check_extension: "The file extension can not be changed."
3
- apijs_clearcache_done: "The image cache was cleaned."
4
- apijs_browser_warning: "<strong>Warning:</strong> your browser <strong>%{n} %{v}</strong> is outdated, please <a %{l}>upgrade your browser</a>."
2
+ apijs_check_extension: "Dosya uzantısı değiştirilemez."
3
+ apijs_clearcache_done: "Resim ön belleği temizlendi."
4
+ apijs_browser_warning: "<strong>UYARI :</strong> tarayıcınız <strong>%{n} %{v}</strong> eski, lütfen <a %{l}>güncelleyiniz</a>."
5
5
  apijs_title_album: "Fotoğraf Galerisi"
6
6
  apijs_title_download: "İndir (%{v})"
7
7
  apijs_title_files: "Eklenti listesi"
@@ -10,25 +10,25 @@ tr:
10
10
  apijs_example1_title: "PHP Kılavuzu"
11
11
  apijs_example1_text: "Yalnız şuna dikkat edin : Epostanın sunucu tarafından teslim alınması, epostanın alıcısına ulaştığı anlamına gelmez."
12
12
  apijs_example: "Örnek %{v}"
13
- apijs_example2_title: "Surprise!"
14
- apijs_example2_text: "You are watching this video without proprietary extension thanks to the new [em]video[/em] tag included in the [strong]HTML 5[/strong] language."
13
+ apijs_example2_title: "Sürpriz!"
14
+ apijs_example2_text: "Bu videoyu [strong]HTML 5[/strong] dilindeki [em]video[/em] tanımlayıcısı sayesinde herhangi bir eklentiye ihtiyaç olmadan izlemektesiniz."
15
15
  apijs_example2_link: "Web sitesi : %{v}"
16
16
  apijs_config_sort_order: "Eklentileri alfabetik olarak sırala"
17
- apijs_config_browser: "Check browser version"
18
- apijs_config_browser_help: "Firefox 36+, Chrome 32+, Opera 19+, Edge 16+, Safari 9+ (checks even if the apijs is disabled)."
19
- permission_edit_attachments: "(apijs) Eklenti açıklamalarını düzenle"
20
- permission_rename_attachments: "(apijs) Rename files"
21
- permission_delete_attachments: "(apijs) Eklenti Sil"
22
- apijs_config_directories: "Directories size"
23
- apijs_config_clearcache: "clear cache"
17
+ apijs_config_browser: "Tarayıcı versiyonunu kontrol et"
18
+ apijs_config_browser_help: "Firefox 36+, Chrome 32+, Opera 19+, Edge 16+, Safari 9+ (apijs etkin değilken de kontrol edilir)."
19
+ permission_edit_attachments: "(apijs) Açıklama düzenle"
20
+ permission_rename_attachments: "(apijs) Dosya adı değiştir"
21
+ permission_delete_attachments: "(apijs) Dosya Sil"
22
+ apijs_config_directories: "Klasör boyutu"
23
+ apijs_config_clearcache: "önbelleği temizle"
24
24
  apijs_config_album: "Fotoğraf Albümü"
25
25
  apijs_config_show_album: "Fotoğraf albümünü görüntüle"
26
- apijs_config_show_album_infos: "Display all information"
26
+ apijs_config_show_album_infos: "Tüm bilgileri göster"
27
27
  apijs_config_show_filename: "Fotoğraf adını göster"
28
28
  apijs_config_show_exifdate: "Fotoğraf tarihini göster"
29
- apijs_config_mimetypes: "Use the following files"
29
+ apijs_config_mimetypes: "Bu dosyaları kullan"
30
30
  apijs_config_album_exclude_name: "Fotoğraf isimleri bununla<br />başlayanları hariç tur"
31
31
  apijs_config_album_exclude_desc: "Fotoğraf açıklamaları bunlarla<br />başlayanları hariç tut"
32
- apijs_config_create_all: "Generate the three images"
32
+ apijs_config_create_all: "Ağaç görünümü oluştur"
33
33
  apijs_config_create_all_info: "Simge görüntüsünü ve ön görünümü aynı anda oluşturmak sayfayı ilk görüntülemeyi yavaşlatır fakat slayt gösterisini hızlandırır."
34
34
  apijs_config_programs: "Program versiyonu"
@@ -1,6 +1,6 @@
1
1
  uk:
2
2
  apijs_check_extension: "The file extension can not be changed."
3
- apijs_clearcache_done: "The image cache was cleaned."
3
+ apijs_clearcache_done: "Кеш зображення було очищено."
4
4
  apijs_browser_warning: "<strong>Warning:</strong> your browser <strong>%{n} %{v}</strong> is outdated, please <a %{l}>upgrade your browser</a>."
5
5
  apijs_title_album: "Photo gallery"
6
6
  apijs_title_download: "Завантажити (%{v})"
@@ -1,6 +1,6 @@
1
1
  zh:
2
2
  apijs_check_extension: "The file extension can not be changed."
3
- apijs_clearcache_done: "The image cache was cleaned."
3
+ apijs_clearcache_done: "图片缓存被清除了。"
4
4
  apijs_browser_warning: "<strong>Warning:</strong> your browser <strong>%{n} %{v}</strong> is outdated, please <a %{l}>upgrade your browser</a>."
5
5
  apijs_title_album: "照片库"
6
6
  apijs_title_download: "下载(%{v})"
data/init.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # encoding: utf-8
2
2
  # Created L/21/05/2012
3
- # Updated V/22/10/2021
3
+ # Updated M/05/07/2022
4
4
  #
5
5
  # Copyright 2008-2022 | Fabrice Creuzot (luigifab) <code~luigifab~fr>
6
6
  # https://www.luigifab.fr/redmine/apijs
@@ -15,18 +15,38 @@
15
15
  # merchantability or fitness for a particular purpose. See the
16
16
  # GNU General Public License (GPL) for more details.
17
17
 
18
- require 'redmine'
19
- require 'apijs_const'
20
- require 'apijs_files'
21
- require 'apijs_attachment'
22
- require 'useragentparser'
18
+ if Rails::VERSION::MAJOR < 6 or ENV['redmine_apijs_gem']
19
+ require 'redmine'
20
+ require 'apijs_files'
21
+ require 'apijs_attachment'
22
+ require 'useragentparser'
23
+ end
24
+
25
+ if Redmine::VERSION::MAJOR >= 3
26
+ if defined? Redmine.root
27
+ ALL_FILES = Redmine::Configuration['attachments_storage_path'] || File.join(Redmine.root, 'files')
28
+ APIJS_ROOT = File.join(Redmine.root, 'tmp', 'apijs')
29
+ else
30
+ ALL_FILES = Redmine::Configuration['attachments_storage_path'] || File.join(Rails.root, 'files')
31
+ APIJS_ROOT = File.join(Rails.root, 'tmp', 'apijs')
32
+ end
33
+ elsif ENV['RAILS_TMP']
34
+ ALL_FILES = Redmine::Configuration['attachments_storage_path'] || File.join(ENV['RAILS_VAR'], 'files')
35
+ APIJS_ROOT = File.join(ENV['RAILS_TMP'], 'tmp', 'apijs')
36
+ elsif ENV['RAILS_CACHE']
37
+ ALL_FILES = Redmine::Configuration['attachments_storage_path'] || File.join(ENV['RAILS_VAR'], 'files')
38
+ APIJS_ROOT = File.join(ENV['RAILS_CACHE'], 'tmp', 'apijs')
39
+ else
40
+ ALL_FILES = Redmine::Configuration['attachments_storage_path'] || File.join(Rails.root, 'files')
41
+ APIJS_ROOT = File.join(Rails.root, 'tmp', 'apijs')
42
+ end
23
43
 
24
44
  Redmine::Plugin.register :redmine_apijs do
25
45
 
26
46
  name 'Redmine Apijs plugin'
27
47
  author 'Fabrice Creuzot'
28
48
  description 'Integrate the apijs JavaScript library into Redmine. Provides a gallery for image and video attachments.'
29
- version '6.8.2-gem'
49
+ version '6.9.0-gem'
30
50
  url 'https://www.luigifab.fr/redmine/apijs'
31
51
  author_url 'https://www.luigifab.fr/'
32
52
 
@@ -1,6 +1,6 @@
1
1
  # encoding: utf-8
2
2
  # Created V/27/12/2013
3
- # Updated J/18/02/2021
3
+ # Updated J/21/04/2022
4
4
  #
5
5
  # Copyright 2008-2022 | Fabrice Creuzot (luigifab) <code~luigifab~fr>
6
6
  # https://www.luigifab.fr/redmine/apijs
@@ -182,7 +182,7 @@ module ApijsAttachment
182
182
  cmd = getPython
183
183
  cmd = 'notfound' if not cmd or cmd.length == 0
184
184
 
185
- script = File.join(File.dirname(__FILE__), (self.isPhoto? ? 'image.py' : 'video.py'))
185
+ script = File.join(File.dirname(__FILE__), 'image.py')
186
186
  return cmd + ' ' + script.to_s + ' ' + source.to_s + ' ' + target.to_s + ' ' +
187
187
  width.to_s + ' ' + height.to_s + (fixed ? ' 90 fixed' : ' 90') + ' 2>&1'
188
188
  end
@@ -232,7 +232,7 @@ module ApijsAttachment
232
232
  logger.info 'APIJS::ApijsAttachment#update_date: ' + cmd + ' (' + result + ')'
233
233
 
234
234
  # 2014:06:14 16:43:53 (utilise le fuseau horaire de l'utilisateur)
235
- if result =~ /^[0-9]{4}.[0-9]{2}.[0-9]{2} [0-9]{2}.[0-9]{2}.[0-9]{2}/
235
+ if result =~ /^\d{4}.\d{2}.\d{2} \d{2}.\d{2}.\d{2}/
236
236
 
237
237
  date = result[0..9].gsub(':', '-') + ' ' + result[11..18]
238
238
  zone = User.current.time_zone
data/lib/image.py CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/python3
2
2
  # -*- coding: utf8 -*-
3
3
  # Created J/26/12/2013
4
- # Updated M/11/05/2021
4
+ # Updated M/05/07/2022
5
5
  #
6
6
  # Copyright 2008-2022 | Fabrice Creuzot (luigifab) <code~luigifab~fr>
7
7
  # Copyright 2020-2022 | Fabrice Creuzot <fabrice~cellublue~com>
@@ -17,25 +17,31 @@
17
17
  # merchantability or fitness for a particular purpose. See the
18
18
  # GNU General Public License (GPL) for more details.
19
19
 
20
- import os
21
- import sys
20
+ import os, sys
22
21
 
23
22
  try:
24
23
  filein = str(sys.argv[1])
25
24
  fileout = str(sys.argv[2])
26
25
  size = (int(sys.argv[3]), int(sys.argv[4]))
27
- quality = int(sys.argv[5])
28
- fixed = len(sys.argv) == 7 and sys.argv[6] == 'fixed'
26
+ quality = int(sys.argv[5]) if len(sys.argv) >= 6 else 0
27
+ fixed = len(sys.argv) >= 7 and sys.argv[6] == 'fixed'
29
28
  except:
30
29
  print("Usage: image.py source destination width height [quality=0=auto] [fixed]")
31
- print("source: all supported format by python-pil (including animated gif/png/webp) or svg")
32
- print("destination: jpg,png,gif,webp or svg")
30
+ print("source: all supported format by python-pil (including animated gif/png/webp),")
31
+ print(" or all supported format by ffmpegthumbnailer,")
32
+ print(" or svg")
33
+ print("destination: jpg,gif,png,webp or svg")
33
34
  exit(-1)
34
35
 
36
+ # https://stackoverflow.com/a/273227/2980105
35
37
  if not os.path.exists(os.path.dirname(fileout)):
36
- os.makedirs(os.path.dirname(fileout))
38
+ os.makedirs(os.path.dirname(fileout), exist_ok=True)
37
39
 
40
+ same = filein == fileout
38
41
  fileout = fileout + '.save'
42
+ if os.path.isfile(fileout):
43
+ exit(0)
44
+ os.close(os.open(fileout, os.O_CREAT))
39
45
 
40
46
 
41
47
  # tools
@@ -55,10 +61,13 @@ def calcSize(source, size):
55
61
 
56
62
  def createThumb(source, size, fixed, new=False):
57
63
 
64
+ # https://pillow.readthedocs.io/en/latest/releasenotes/7.0.0.html?highlight=thumbnail (reducing_gap)
58
65
  # https://pillow.readthedocs.io/en/latest/reference/Image.html#PIL.Image.Image.thumbnail
59
66
  # https://pillow.readthedocs.io/en/latest/handbook/concepts.html#filters-comparison-table
60
67
  # https://pillow.readthedocs.io/en/latest/reference/Image.html#PIL.Image.Image.paste
61
- if hasattr(Image, '__version__') and versionTuple(Image.__version__) > (7,0,0):
68
+ if hasattr(Image, '__version__') and versionTuple(Image.__version__) >= (9,1,0):
69
+ source.thumbnail(size, Image.Resampling.LANCZOS, 4)
70
+ elif hasattr(Image, '__version__') and versionTuple(Image.__version__) >= (7,0,0):
62
71
  source.thumbnail(size, Image.LANCZOS, 4)
63
72
  else:
64
73
  source.thumbnail(size, Image.ANTIALIAS)
@@ -80,6 +89,31 @@ def createThumb(source, size, fixed, new=False):
80
89
 
81
90
  return dest
82
91
 
92
+ def videoToImage(filein, fileout, size):
93
+
94
+ os.system('ffmpegthumbnailer -i ' + filein + ' -o ' + fileout + ' -q 10 -s ' + str(size[0]))
95
+ if os.path.isfile(fileout) and os.path.getsize(fileout) > 0:
96
+ img = Image.open(fileout)
97
+ else:
98
+ img = Image.new('RGBA', size, (0,0,0,0))
99
+
100
+ return img
101
+
102
+ def hasTransparency(img):
103
+
104
+ # https://stackoverflow.com/a/58567453/2980105
105
+ if img.mode == 'P':
106
+ transparent = img.info.get('transparency', -1)
107
+ for _, index in img.getcolors():
108
+ if index == transparent:
109
+ return True
110
+ elif img.mode == 'RGBA':
111
+ extrema = img.getextrema()
112
+ if extrema[3][0] < 255:
113
+ return True
114
+
115
+ return False
116
+
83
117
 
84
118
  # Animated resizing
85
119
  # based on https://stackoverflow.com/a/41827681/2980105
@@ -94,8 +128,12 @@ def resizeAnimatedGif(source, size, fixed):
94
128
  while True:
95
129
  # If the GIF uses local colour tables, each frame will have its own palette.
96
130
  # If not, we need to apply the global palette to the new frame.
97
- if not source.getpalette():
98
- source.putpalette(colors)
131
+ # Ignore ValueError (illegal image mode)
132
+ try:
133
+ if not source.getpalette():
134
+ source.putpalette(colors)
135
+ except ValueError:
136
+ pass
99
137
 
100
138
  frame = Image.new('RGBA', source.size, (255,255,255,1))
101
139
  frame.paste(source, (0,0), source.convert('RGBA'))
@@ -211,6 +249,14 @@ def saveJpg(dest, fileout, quality):
211
249
  elif quality > 95:
212
250
  quality = 95
213
251
 
252
+ # https://github.com/wanadev/pyguetzli
253
+ # warning: extremely slow performance (https://github.com/google/guetzli/issues/50)
254
+ #try:
255
+ # import pyguetzli
256
+ # filefinal = pyguetzli.process_pil_image(dest.convert('RGB'), quality)
257
+ # output = open(fileout, "wb")
258
+ # output.write(filefinal)
259
+ #except ImportError:
214
260
  dest.convert('RGB').save(fileout, 'JPEG', optimize=True, subsampling=0, quality=quality)
215
261
 
216
262
 
@@ -227,21 +273,75 @@ if ".svg" in fileout:
227
273
  # python-pil
228
274
  else:
229
275
  from PIL import Image, ImageSequence
230
- source = Image.open(filein)
231
- size = calcSize(source, size)
232
276
 
233
- if source.format == 'GIF' and ".gif" in fileout:
277
+ if ".ogv" in filein or ".webm" in filein or ".mp4" in filein:
278
+ # from video
279
+ source = videoToImage(filein, fileout, size)
280
+ imgext = 'VIDEO'
281
+ else:
282
+ # from image
283
+ source = Image.open(filein)
284
+ imgext = source.format
285
+ size = calcSize(source, size)
286
+
287
+ # filein = fileout = /tmp/phpA5kbkc when replace tmp image with re-sampled copy to exclude images with malicious data
288
+ if imgext == 'GIF' and (same or ".gif" in fileout):
234
289
  dest = resizeAnimatedGif(source, size, fixed)
235
290
  saveGif(dest, fileout, quality)
236
- elif source.format == 'PNG' and ".png" in fileout:
291
+ elif imgext == 'PNG' and (same or ".png" in fileout):
237
292
  dest = resizeAnimatedPng(source, size, fixed)
238
293
  savePng(dest, fileout, quality)
239
- elif source.format == 'WEBP' and ".webp" in fileout:
240
- dest = resizeAnimatedWeb(source, size, fixed)
294
+ elif imgext == 'WEBP' and (same or ".webp" in fileout):
295
+ dest = resizeAnimatedWebp(source, size, fixed)
241
296
  saveWebp(dest, fileout, quality)
242
297
  else:
243
- dest = createThumb(source, size, fixed, True)
244
- saveJpg(dest, fileout, quality)
298
+ if imgext == 'VIDEO':
299
+ # from video
300
+ offset_x = max(int((size[0] - source.size[0]) / 2), 0)
301
+ offset_y = max(int((size[1] - source.size[1]) / 2), 0)
302
+ # Color detection
303
+ # white background black player OR black background white player
304
+ pixels = source.getcolors(size[0] * size[1])
305
+ pixels = sorted(pixels, key=lambda t: t[0])
306
+ if (pixels[-1][1] > (127,127,127)):
307
+ dest = Image.new('RGBA', size, (255,255,255,0))
308
+ dest.paste(source, (offset_x, offset_y))
309
+ # https://stackoverflow.com/a/59082116 (replace only last lib)
310
+ # /var/lib/gems/xyz/gems/redmine_apijs-xyz/lib » /var/lib/gems/xyz/gems/redmine_apijs-xyz/assets/images/...
311
+ path = os.path.dirname(__file__)
312
+ path = ('/assets/images/apijs/player-black-' + str(size[0]) + '.png').join(path.rsplit('/lib', 1))
313
+ play = Image.open(path)
314
+ dest.paste(play, (0, 0), play)
315
+ else:
316
+ dest = Image.new('RGBA', size, (0,0,0,0))
317
+ dest.paste(source, (offset_x, offset_y))
318
+ # https://stackoverflow.com/a/59082116 (replace only last lib)
319
+ # /var/lib/gems/xyz/gems/redmine_apijs-xyz/lib » /var/lib/gems/xyz/gems/redmine_apijs-xyz/assets/images/...
320
+ path = os.path.dirname(__file__)
321
+ path = ('/assets/images/apijs/player-white-' + str(size[0]) + '.png').join(path.rsplit('/lib', 1))
322
+ play = Image.open(path)
323
+ dest.paste(play, (0, 0), play)
324
+ else:
325
+ # from image
326
+ # Auto rotate image
327
+ # https://github.com/python-pillow/Pillow/commit/1ba774ae7faf93355b85c7b005fbaf0b0e66d426
328
+ if hasattr(Image, '__version__') and versionTuple(Image.__version__) >= (6,0,0) and source.getexif().get(0x0112):
329
+ from PIL import ImageOps
330
+ source = ImageOps.exif_transpose(source)
331
+ # create thumbnail
332
+ dest = createThumb(source, size, fixed, True)
333
+
334
+ if hasTransparency(source) is False:
335
+ dest = dest.convert('RGB')
336
+
337
+ if ".gif" in fileout or (same and imgext == 'GIF'):
338
+ saveGif(dest, fileout, quality)
339
+ elif ".png" in fileout or (same and imgext == 'PNG'):
340
+ savePng(dest, fileout, quality)
341
+ elif ".webp" in fileout or (same and imgext == 'WEBP'):
342
+ saveWebp(dest, fileout, quality)
343
+ else:
344
+ saveJpg(dest, fileout, quality)
245
345
 
246
346
  if os.path.isfile(fileout):
247
347
  os.rename(fileout, fileout.replace('.save', ''))
data/lib/redmine_apijs.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # encoding: utf-8
2
2
  # Created L/13/07/2020
3
- # Updated D/27/08/2020
3
+ # Updated M/05/07/2022
4
4
  #
5
5
  # Copyright 2008-2022 | Fabrice Creuzot (luigifab) <code~luigifab~fr>
6
6
  # https://www.luigifab.fr/redmine/apijs
@@ -20,29 +20,52 @@ require 'rails/engine'
20
20
  module RedmineApijs
21
21
 
22
22
  class Plugin < ::Rails::Engine
23
+
23
24
  config.after_initialize do
24
25
 
25
- # Gemify redmine plugin (https://github.com/koppen/redmine_github_hook/commit/a82bcccfd0731503509771d3f715d8fb1e6f1bfe)
26
+ # for Redmine 5.0+ with Rails 6.0+
27
+ ENV['redmine_apijs_gem'] = 'yes'
28
+
29
+ # Gemify redmine plugin (based on https://github.com/koppen/redmine_github_hook/commit/a82bcccfd0731503509771d3f715d8fb1e6f1bfe)
30
+ # it works out of box with Redmine 3.0 - 4.0, for Redmine 4.1+ see https://www.redmine.org/issues/31110
26
31
  # run the classic redmine plugin initializer after rails boot
27
32
  require File.expand_path('../../init', __FILE__)
28
33
  ::ActionController::Base.prepend_view_path(File.expand_path('../../app/views/', __FILE__))
29
34
 
30
35
  # and copy plugin assets
31
36
  unless ::Redmine::Configuration['mirror_plugins_assets_on_startup'] == false
32
- module ::Redmine
33
- class Plugin
34
- def assets_directory
35
- if directory =~ /redmine_apijs/
36
- File.join(File.expand_path('../../', __FILE__), 'assets')
37
- else
38
- File.join(directory, 'assets')
37
+ if Redmine::VERSION::MAJOR >= 5
38
+ # https://github.com/redmine/redmine/blob/9aa34eb651e2941aeae69cc3acf6e1c1b909729e/lib/redmine/plugin_loader.rb#L41
39
+ # Update the name of the plugin directory
40
+ module ::Redmine
41
+ class PluginPath
42
+ def update_dir(dir)
43
+ @dir = dir
44
+ end
45
+ end
46
+ end
47
+ # go
48
+ obj = Redmine::PluginPath.new(File.expand_path('../../', __FILE__))
49
+ obj.update_dir('redmine_apijs')
50
+ obj.mirror_assets
51
+ else
52
+ # https://github.com/redmine/redmine/blob/94f3510036690d049ac7425d19fa7c055044ae57/lib/redmine/plugin.rb#L207
53
+ # Returns the absolute path to the plugin assets directory
54
+ module ::Redmine
55
+ class Plugin
56
+ def assets_directory
57
+ if directory =~ /redmine_apijs/
58
+ File.join(File.expand_path('../../', __FILE__), 'assets')
59
+ else
60
+ File.join(directory, 'assets')
61
+ end
39
62
  end
40
63
  end
41
64
  end
65
+ # go
66
+ Redmine::Plugin.mirror_assets('redmine_apijs')
42
67
  end
43
- Redmine::Plugin.mirror_assets('redmine_apijs')
44
68
  end
45
-
46
69
  end
47
70
  end
48
71
  end
@@ -1,10 +1,10 @@
1
1
  # encoding: utf-8
2
2
  #
3
- # Copyright 2013-2021 | Jesse G. Donat <donatj~gmail~com>
3
+ # Copyright 2013-2022 | Jesse G. Donat <donatj~gmail~com>
4
4
  # https://github.com/donatj/PhpUserAgent
5
5
  #
6
- # Copyright 2019-2021 | Fabrice Creuzot (luigifab) <code~luigifab~fr>
7
- # https://gist.github.com/luigifab/19a68d9aa98fa80f2961809d7cec59c0 (1.5.0-fork1)
6
+ # Copyright 2019-2022 | Fabrice Creuzot (luigifab) <code~luigifab~fr>
7
+ # https://gist.github.com/luigifab/19a68d9aa98fa80f2961809d7cec59c0 (1.6.1-fork1)
8
8
  #
9
9
  # Parses a user agent string into its important parts
10
10
  # Licensed under the MIT License
@@ -49,13 +49,13 @@ class Useragentparser
49
49
  end
50
50
 
51
51
  result = userAgent.to_enum(:scan, # ["browser" => ["Firefox"...], "version" => ["45.0"...]]
52
- /(?<browser>Camino|Kindle(\ Fire)?|Firefox|Iceweasel|IceCat|Safari|MSIE|Trident|AppleWebKit|TizenBrowser|(?:Headless)?Chrome|YaBrowser|Vivaldi|IEMobile|Opera|OPR|Silk|Midori|Edge|Edg|CriOS|UCBrowser|Puffin|OculusBrowser|SamsungBrowser|SailfishBrowser|XiaoMi\/MiuiBrowser|Baiduspider|Applebot|Facebot|Googlebot|YandexBot|bingbot|Lynx|Version|Wget|curl|Valve\ Steam\ Tenfoot|NintendoBrowser|PLAYSTATION\ (\d|Vita)+)\)?;?(?:[:\/ ](?<version>[0-9A-Z.]+)|\/[A-Z]*)/ix
52
+ /(?<browser>Camino|Kindle(\ Fire)?|Firefox|Iceweasel|IceCat|Safari|MSIE|Trident|AppleWebKit|TizenBrowser|(?:Headless)?Chrome|YaBrowser|Vivaldi|IEMobile|Opera|OPR|Silk|Midori|(?-i:Edge)|EdgA?|CriOS|UCBrowser|Puffin|OculusBrowser|SamsungBrowser|SailfishBrowser|XiaoMi\/MiuiBrowser|Baiduspider|Applebot|Facebot|Googlebot|YandexBot|bingbot|Lynx|Version|Wget|curl|Valve\ Steam\ Tenfoot|NintendoBrowser|PLAYSTATION\ (\d|Vita)+)\)?;?(?:[:\/ ](?<version>[\dA-Z.]+)|\/[A-Z]*)/ix
53
53
  ).map { Regexp.last_match.names.collect{ |x| {x => $~[x]} }.reduce({}, :merge) }
54
54
  .reduce({}) { |h,pairs| pairs.each {|k,v| (h[k] ||= []) << v}; h }
55
55
 
56
56
  # If nothing matched, return nil (to avoid undefined index errors)
57
57
  if !result['browser'] || !result['browser'][0] || !result['version'] || !result['version'][0]
58
- result = userAgent.match(/^(?!Mozilla)(?<browser>[A-Z0-9\-]+)(\/(?<version>[0-9A-Z.]+))?/ix)
58
+ result = userAgent.match(/^(?!Mozilla)(?<browser>[A-Z\d\-]+)(\/(?<version>[\dA-Z.]+))?/ix)
59
59
  if result
60
60
  return {
61
61
  'platform' => platform ? platform : nil,
@@ -66,7 +66,7 @@ class Useragentparser
66
66
  return empty
67
67
  end
68
68
 
69
- rv_result = userAgent.match(/rv:(?<version>[0-9A-Z.]+)/i)
69
+ rv_result = userAgent.match(/rv:(?<version>[\dA-Z.]+)/i)
70
70
  if rv_result && rv_result['version']
71
71
  rv_result = rv_result['version']
72
72
  end
@@ -80,16 +80,16 @@ class Useragentparser
80
80
  refpla = [platform]
81
81
  refval = ['']
82
82
 
83
- if findt(lowerBrowser, {'OPR' => 'Opera', 'Facebot' => 'iMessageBot', 'UCBrowser' => 'UC Browser', 'YaBrowser' => 'Yandex', 'Iceweasel' => 'Firefox', 'Icecat' => 'Firefox', 'CriOS' => 'Chrome', 'Edg' => 'Edge', 'XiaoMi/MiuiBrowser' => 'MiuiBrowser'}, refkey, refbro)
83
+ if findt(lowerBrowser, {'OPR' => 'Opera', 'Facebot' => 'iMessageBot', 'UCBrowser' => 'UC Browser', 'YaBrowser' => 'Yandex', 'Iceweasel' => 'Firefox', 'Icecat' => 'Firefox', 'CriOS' => 'Chrome', 'Edg' => 'Edge', 'EdgA' => 'Edge', 'XiaoMi/MiuiBrowser' => 'MiuiBrowser'}, refkey, refbro)
84
84
  browser = refbro[0]
85
- version = result['version'][refkey[0]][0] =~ /[0-9]/ ? result['version'][refkey[0]] : nil
85
+ version = result['version'][refkey[0]][0] =~ /\d/ ? result['version'][refkey[0]] : nil
86
86
  elsif find(lowerBrowser, 'Playstation Vita', refkey, platform)
87
87
  platform = 'PlayStation Vita'
88
88
  browser = 'Browser'
89
89
  elsif find(lowerBrowser, ['Kindle Fire', 'Silk'], refkey, refval)
90
90
  browser = refval[0] == 'Silk' ? 'Silk' : 'Kindle'
91
91
  platform = 'Kindle Fire'
92
- if !(version = result['version'][refkey[0]]) || !(version[0] =~ /[0-9]/ ? true : false)
92
+ if !(version = result['version'][refkey[0]]) || !(version[0] =~ /\d/ ? true : false)
93
93
  version = result['version'][result['browser'].index('Version')]
94
94
  end
95
95
  elsif platform == 'Nintendo 3DS' || find(lowerBrowser, 'NintendoBrowser', refkey)
@@ -1,9 +1,9 @@
1
1
  # coding: utf-8
2
2
  Gem::Specification.new do |s|
3
3
  s.name = 'redmine_apijs'
4
- s.version = '6.8.2'
4
+ s.version = '6.9.0'
5
5
  s.summary = 'Redmine Apijs plugin'
6
- s.description = 'Integrate the apijs JavaScript library into Redmine. Provides a gallery for image and video attachments.'
6
+ s.description = 'Integrate the apijs JavaScript library into Redmine. Provides a gallery for image and video attachments. Gem for Redmine 3.0+ (tested with 3.0..5.0), for Redmine 4.1+, read https://redmine.org/issues/31110#note-8'
7
7
  s.homepage = 'https://github.com/luigifab/redmine-apijs'
8
8
  s.license = 'GPL-2.0-or-later'
9
9
  s.authors = ['Fabrice Creuzot']
@@ -66,12 +66,10 @@ Gem::Specification.new do |s|
66
66
  Gemfile
67
67
  init.rb
68
68
  lib/apijs_attachment.rb
69
- lib/apijs_const.rb
70
69
  lib/apijs_files.rb
71
70
  lib/image.py
72
71
  lib/redmine_apijs.rb
73
72
  lib/useragentparser.rb
74
- lib/video.py
75
73
  LICENSE
76
74
  README
77
75
  redmine_apijs.gemspec
metadata CHANGED
@@ -1,17 +1,18 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redmine_apijs
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.8.2
4
+ version: 6.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Fabrice Creuzot
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-01-01 00:00:00.000000000 Z
11
+ date: 2022-07-07 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Integrate the apijs JavaScript library into Redmine. Provides a gallery
14
- for image and video attachments.
14
+ for image and video attachments. Gem for Redmine 3.0+ (tested with 3.0..5.0), for
15
+ Redmine 4.1+, read https://redmine.org/issues/31110#note-8
15
16
  email: code@luigifab.fr
16
17
  executables: []
17
18
  extensions: []
@@ -70,12 +71,10 @@ files:
70
71
  - config/routes.rb
71
72
  - init.rb
72
73
  - lib/apijs_attachment.rb
73
- - lib/apijs_const.rb
74
74
  - lib/apijs_files.rb
75
75
  - lib/image.py
76
76
  - lib/redmine_apijs.rb
77
77
  - lib/useragentparser.rb
78
- - lib/video.py
79
78
  - redmine_apijs.gemspec
80
79
  homepage: https://github.com/luigifab/redmine-apijs
81
80
  licenses:
@@ -99,7 +98,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
99
98
  - !ruby/object:Gem::Version
100
99
  version: '0'
101
100
  requirements: []
102
- rubygems_version: 3.2.27
101
+ rubygems_version: 3.3.15
103
102
  signing_key:
104
103
  specification_version: 4
105
104
  summary: Redmine Apijs plugin
data/lib/apijs_const.rb DELETED
@@ -1,35 +0,0 @@
1
- # encoding: utf-8
2
- # Created L/01/09/2014
3
- # Updated J/23/07/2020
4
- #
5
- # Copyright 2008-2022 | Fabrice Creuzot (luigifab) <code~luigifab~fr>
6
- # https://www.luigifab.fr/redmine/apijs
7
- #
8
- # This program is free software, you can redistribute it or modify
9
- # it under the terms of the GNU General Public License (GPL) as published
10
- # by the free software foundation, either version 2 of the license, or
11
- # (at your option) any later version.
12
- #
13
- # This program is distributed in the hope that it will be useful,
14
- # but without any warranty, without even the implied warranty of
15
- # merchantability or fitness for a particular purpose. See the
16
- # GNU General Public License (GPL) for more details.
17
-
18
- if Redmine::VERSION::MAJOR >= 3
19
- if defined? Redmine.root
20
- ALL_FILES = Redmine::Configuration['attachments_storage_path'] || File.join(Redmine.root, 'files')
21
- APIJS_ROOT = File.join(Redmine.root, 'tmp', 'apijs')
22
- else
23
- ALL_FILES = Redmine::Configuration['attachments_storage_path'] || File.join(Rails.root, 'files')
24
- APIJS_ROOT = File.join(Rails.root, 'tmp', 'apijs')
25
- end
26
- elsif ENV['RAILS_TMP']
27
- ALL_FILES = Redmine::Configuration['attachments_storage_path'] || File.join(ENV['RAILS_VAR'], 'files')
28
- APIJS_ROOT = File.join(ENV['RAILS_TMP'], 'tmp', 'apijs')
29
- elsif ENV['RAILS_CACHE']
30
- ALL_FILES = Redmine::Configuration['attachments_storage_path'] || File.join(ENV['RAILS_VAR'], 'files')
31
- APIJS_ROOT = File.join(ENV['RAILS_CACHE'], 'tmp', 'apijs')
32
- else
33
- ALL_FILES = Redmine::Configuration['attachments_storage_path'] || File.join(Rails.root, 'files')
34
- APIJS_ROOT = File.join(Rails.root, 'tmp', 'apijs')
35
- end