redmine_apijs 6.8.2 → 6.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|