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.
- checksums.yaml +4 -4
- data/README +4 -4
- data/app/controllers/apijs_controller.rb +41 -41
- data/app/views/attachments/_links.html.erb +3 -3
- data/app/views/settings/_apijs.html.erb +5 -3
- data/assets/javascripts/apijs-redmine.js +6 -5
- data/assets/javascripts/apijs-redmine.min.js +1 -1
- data/assets/javascripts/apijs-redmine.min.js.map +1 -1
- data/assets/javascripts/apijs.min.js +2 -2
- data/assets/javascripts/apijs.min.js.map +1 -1
- data/assets/stylesheets/apijs-print.min.css +1 -1
- data/assets/stylesheets/apijs-print.min.css.map +1 -1
- data/assets/stylesheets/apijs-screen-rtl.min.css +2 -2
- data/assets/stylesheets/apijs-screen-rtl.min.css.map +1 -1
- data/assets/stylesheets/apijs-screen.min.css +2 -2
- data/assets/stylesheets/apijs-screen.min.css.map +1 -1
- data/config/locales/cs.yml +1 -1
- data/config/locales/de.yml +1 -1
- data/config/locales/el.yml +1 -1
- data/config/locales/es.yml +1 -1
- data/config/locales/hu.yml +1 -1
- data/config/locales/it.yml +1 -1
- data/config/locales/ja.yml +1 -1
- data/config/locales/nl.yml +1 -1
- data/config/locales/pl.yml +1 -1
- data/config/locales/pt-BR.yml +1 -1
- data/config/locales/pt.yml +1 -1
- data/config/locales/ro.yml +1 -1
- data/config/locales/ru.yml +1 -1
- data/config/locales/sk.yml +1 -1
- data/config/locales/tr.yml +15 -15
- data/config/locales/uk.yml +1 -1
- data/config/locales/zh.yml +1 -1
- data/init.rb +27 -7
- data/lib/apijs_attachment.rb +3 -3
- data/lib/image.py +119 -19
- data/lib/redmine_apijs.rb +34 -11
- data/lib/useragentparser.rb +9 -9
- data/redmine_apijs.gemspec +2 -4
- metadata +5 -6
- data/lib/apijs_const.rb +0 -35
- data/lib/video.py +0 -80
data/config/locales/tr.yml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
tr:
|
2
|
-
apijs_check_extension: "
|
3
|
-
apijs_clearcache_done: "
|
4
|
-
apijs_browser_warning: "<strong>
|
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: "
|
14
|
-
apijs_example2_text: "
|
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: "
|
18
|
-
apijs_config_browser_help: "Firefox 36+, Chrome 32+, Opera 19+, Edge 16+, Safari 9+ (
|
19
|
-
permission_edit_attachments: "(apijs)
|
20
|
-
permission_rename_attachments: "(apijs)
|
21
|
-
permission_delete_attachments: "(apijs)
|
22
|
-
apijs_config_directories: "
|
23
|
-
apijs_config_clearcache: "
|
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: "
|
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: "
|
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: "
|
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"
|
data/config/locales/uk.yml
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
uk:
|
2
2
|
apijs_check_extension: "The file extension can not be changed."
|
3
|
-
apijs_clearcache_done: "
|
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})"
|
data/config/locales/zh.yml
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
zh:
|
2
2
|
apijs_check_extension: "The file extension can not be changed."
|
3
|
-
apijs_clearcache_done: "
|
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
|
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
|
-
|
19
|
-
require '
|
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.
|
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
|
|
data/lib/apijs_attachment.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
# Created V/27/12/2013
|
3
|
-
# Updated J/
|
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__),
|
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 =~
|
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/
|
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)
|
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)
|
32
|
-
print("
|
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__)
|
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
|
-
|
98
|
-
|
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
|
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
|
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
|
240
|
-
dest =
|
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
|
-
|
244
|
-
|
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
|
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
|
-
#
|
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
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
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
|
data/lib/useragentparser.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
#
|
3
|
-
# Copyright 2013-
|
3
|
+
# Copyright 2013-2022 | Jesse G. Donat <donatj~gmail~com>
|
4
4
|
# https://github.com/donatj/PhpUserAgent
|
5
5
|
#
|
6
|
-
# Copyright 2019-
|
7
|
-
# https://gist.github.com/luigifab/19a68d9aa98fa80f2961809d7cec59c0 (1.
|
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|
|
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-
|
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>[
|
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] =~ /
|
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] =~ /
|
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)
|
data/redmine_apijs.gemspec
CHANGED
@@ -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.
|
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.
|
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-
|
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.
|
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
|